diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java index 2eb061d32eb67b4639c6a22cdb9ae78de2465f22..46a4f28ceb32c1a7815f34ee0a1ab5b6be5cbe6f 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java @@ -36,6 +36,7 @@ import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.util.IoUtils; +import org.briarproject.bramble.util.RenewableWakeLock; import org.briarproject.bramble.util.StringUtils; import java.io.Closeable; @@ -103,6 +104,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { private static final int COOKIE_TIMEOUT_MS = 3000; private static final int COOKIE_POLLING_INTERVAL_MS = 200; private static final Pattern ONION = Pattern.compile("[a-z2-7]{16}"); + // This tag may prevent Huawei's power manager from killing us + private static final String WAKE_LOCK_TAG = "LocationManagerService"; private static final Logger LOG = Logger.getLogger(TorPlugin.class.getName()); @@ -119,7 +122,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { private final ConnectionStatus connectionStatus; private final File torDirectory, torFile, geoIpFile, configFile; private final File doneFile, cookieFile; - private final PowerManager.WakeLock wakeLock; + private final RenewableWakeLock wakeLock; private final AtomicReference<Future<?>> connectivityCheck = new AtomicReference<>(); private final AtomicBoolean used = new AtomicBoolean(false); @@ -156,14 +159,13 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { configFile = new File(torDirectory, "torrc"); doneFile = new File(torDirectory, "done"); cookieFile = new File(torDirectory, ".tor/control_auth_cookie"); - Object o = appContext.getSystemService(POWER_SERVICE); - PowerManager pm = (PowerManager) o; - // This tag will prevent Huawei's powermanager from killing us. - wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService"); - wakeLock.setReferenceCounted(false); // Don't execute more than one connection status check at a time connectionStatusExecutor = new PoliteExecutor("TorPlugin", ioExecutor, 1); + PowerManager pm = (PowerManager) + appContext.getSystemService(POWER_SERVICE); + wakeLock = new RenewableWakeLock(pm, scheduler, PARTIAL_WAKE_LOCK, + WAKE_LOCK_TAG, 1, MINUTES); } @Override diff --git a/bramble-android/src/main/java/org/briarproject/bramble/util/RenewableWakeLock.java b/bramble-android/src/main/java/org/briarproject/bramble/util/RenewableWakeLock.java new file mode 100644 index 0000000000000000000000000000000000000000..1681cbfd18d30ce84630d7657828718c404bbd5f --- /dev/null +++ b/bramble-android/src/main/java/org/briarproject/bramble/util/RenewableWakeLock.java @@ -0,0 +1,100 @@ +package org.briarproject.bramble.util; + +import android.os.PowerManager; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.ThreadSafe; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.logging.Level.INFO; + +@ThreadSafe +@NotNullByDefault +public class RenewableWakeLock { + + private static final Logger LOG = + Logger.getLogger(RenewableWakeLock.class.getName()); + + /** + * Automatically release the lock this many milliseconds after it's due + * to have been replaced and released. + */ + private static final int SAFETY_MARGIN_MS = 10_000; + + private final PowerManager powerManager; + private final ScheduledExecutorService scheduler; + private final int levelAndFlags; + private final String tag; + private final long durationMs; + private final Runnable renewTask; + + private final Object lock = new Object(); + @Nullable + private PowerManager.WakeLock wakeLock; // Locking: lock + @Nullable + private ScheduledFuture future; // Locking: lock + + public RenewableWakeLock(PowerManager powerManager, + ScheduledExecutorService scheduler, int levelAndFlags, String tag, + long duration, TimeUnit timeUnit) { + this.powerManager = powerManager; + this.scheduler = scheduler; + this.levelAndFlags = levelAndFlags; + this.tag = tag; + durationMs = MILLISECONDS.convert(duration, timeUnit); + renewTask = this::renew; + } + + public void acquire() { + if (LOG.isLoggable(INFO)) LOG.info("Acquiring wake lock " + tag); + synchronized (lock) { + if (wakeLock != null) { + LOG.info("Already acquired"); + return; + } + wakeLock = powerManager.newWakeLock(levelAndFlags, tag); + wakeLock.setReferenceCounted(false); + wakeLock.acquire(durationMs + SAFETY_MARGIN_MS); + future = scheduler.schedule(renewTask, durationMs, MILLISECONDS); + } + } + + private void renew() { + if (LOG.isLoggable(INFO)) LOG.info("Renewing wake lock " + tag); + synchronized (lock) { + if (wakeLock == null) { + LOG.info("Already released"); + return; + } + PowerManager.WakeLock oldWakeLock = wakeLock; + wakeLock = powerManager.newWakeLock(levelAndFlags, tag); + wakeLock.setReferenceCounted(false); + wakeLock.acquire(durationMs + SAFETY_MARGIN_MS); + oldWakeLock.release(); + future = scheduler.schedule(renewTask, durationMs, MILLISECONDS); + } + } + + public void release() { + if (LOG.isLoggable(INFO)) LOG.info("Releasing wake lock " + tag); + synchronized (lock) { + if (wakeLock == null) { + LOG.info("Already released"); + return; + } + if (future == null) throw new AssertionError(); + future.cancel(false); + future = null; + wakeLock.release(); + wakeLock = null; + } + } +} +