From 286f6f492cdfdab055575fd681f9e95b3745f80a Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 4 Aug 2020 17:24:24 +0100 Subject: [PATCH] Share a single OS wake lock among all holders. --- .../api/system/AndroidWakeLockFactory.java | 9 +--- .../AndroidBluetoothTransportConnection.java | 3 +- .../bramble/plugin/tor/AndroidTorPlugin.java | 3 +- .../system/AndroidWakeLockFactoryImpl.java | 17 +++--- .../bramble/system/AndroidWakeLockImpl.java | 46 ++++++++++++++++ .../bramble/system/RenewableWakeLock.java | 52 +++++++++++-------- .../bramble/system/SharedWakeLock.java | 22 ++++++++ 7 files changed, 110 insertions(+), 42 deletions(-) create mode 100644 bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakeLockImpl.java create mode 100644 bramble-android/src/main/java/org/briarproject/bramble/system/SharedWakeLock.java diff --git a/bramble-android/src/main/java/org/briarproject/bramble/api/system/AndroidWakeLockFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/api/system/AndroidWakeLockFactory.java index d0aa27f04..7d7be15d0 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/api/system/AndroidWakeLockFactory.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/api/system/AndroidWakeLockFactory.java @@ -1,16 +1,9 @@ package org.briarproject.bramble.api.system; -import android.os.PowerManager; - import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @NotNullByDefault public interface AndroidWakeLockFactory { - /** - * Creates and returns a wake lock. - * - * @param levelAndFlags See {@link PowerManager#newWakeLock(int, String)} - */ - AndroidWakeLock createWakeLock(int levelAndFlags); + AndroidWakeLock createWakeLock(); } diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java index 0d2498f25..ad0f4f038 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java @@ -13,7 +13,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS; import static org.briarproject.bramble.util.AndroidUtils.isValidBluetoothAddress; @@ -36,7 +35,7 @@ class AndroidBluetoothTransportConnection this.socket = socket; in = timeoutMonitor.createTimeoutInputStream( socket.getInputStream(), plugin.getMaxIdleTime() * 2); - wakeLock = wakeLockFactory.createWakeLock(PARTIAL_WAKE_LOCK); + wakeLock = wakeLockFactory.createWakeLock(); wakeLock.acquire(); String address = socket.getRemoteDevice().getAddress(); if (isValidBluetoothAddress(address)) remote.put(PROP_ADDRESS, address); diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java index 2d26a9abf..391416019 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java @@ -23,7 +23,6 @@ import java.util.concurrent.Executor; import javax.net.SocketFactory; import static android.content.Context.MODE_PRIVATE; -import static android.os.PowerManager.PARTIAL_WAKE_LOCK; @MethodsNotNullByDefault @ParametersNotNullByDefault @@ -54,7 +53,7 @@ class AndroidTorPlugin extends TorPlugin { maxLatency, maxIdleTime, appContext.getDir("tor", MODE_PRIVATE)); this.appContext = appContext; - wakeLock = wakeLockFactory.createWakeLock(PARTIAL_WAKE_LOCK); + wakeLock = wakeLockFactory.createWakeLock(); } @Override diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakeLockFactoryImpl.java b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakeLockFactoryImpl.java index 0ddb3ed90..e174052d5 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakeLockFactoryImpl.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakeLockFactoryImpl.java @@ -15,6 +15,7 @@ import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static android.content.Context.POWER_SERVICE; +import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; @@ -34,22 +35,20 @@ class AndroidWakeLockFactoryImpl implements AndroidWakeLockFactory { */ private static final long SAFETY_MARGIN_MS = SECONDS.toMillis(10); - private final TaskScheduler scheduler; - private final PowerManager powerManager; - private final String tag; + private final SharedWakeLock sharedWakeLock; @Inject AndroidWakeLockFactoryImpl(TaskScheduler scheduler, Application app) { - this.scheduler = scheduler; - powerManager = (PowerManager) + PowerManager powerManager = (PowerManager) requireNonNull(app.getSystemService(POWER_SERVICE)); - tag = getWakeLockTag(app); + String tag = getWakeLockTag(app); + sharedWakeLock = new RenewableWakeLock(powerManager, scheduler, + PARTIAL_WAKE_LOCK, tag, LOCK_DURATION_MS, SAFETY_MARGIN_MS); } @Override - public AndroidWakeLock createWakeLock(int levelAndFlags) { - return new RenewableWakeLock(powerManager, scheduler, levelAndFlags, - tag, LOCK_DURATION_MS, SAFETY_MARGIN_MS); + public AndroidWakeLock createWakeLock() { + return new AndroidWakeLockImpl(sharedWakeLock); } private String getWakeLockTag(Context ctx) { diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakeLockImpl.java b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakeLockImpl.java new file mode 100644 index 000000000..d3d9a7c4b --- /dev/null +++ b/bramble-android/src/main/java/org/briarproject/bramble/system/AndroidWakeLockImpl.java @@ -0,0 +1,46 @@ +package org.briarproject.bramble.system; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.AndroidWakeLock; + +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.ThreadSafe; + +/** + * A wrapper around a {@link SharedWakeLock} that provides the more convenient + * semantics of {@link AndroidWakeLock} (i.e. calls to acquire() and release() + * don't need to be balanced). + */ +@ThreadSafe +@NotNullByDefault +class AndroidWakeLockImpl implements AndroidWakeLock { + + private final SharedWakeLock sharedWakeLock; + private final Object lock = new Object(); + @GuardedBy("lock") + private boolean held = false; + + AndroidWakeLockImpl(SharedWakeLock sharedWakeLock) { + this.sharedWakeLock = sharedWakeLock; + } + + @Override + public void acquire() { + synchronized (lock) { + if (!held) { + held = true; + sharedWakeLock.acquire(); + } + } + } + + @Override + public void release() { + synchronized (lock) { + if (held) { + held = false; + sharedWakeLock.release(); + } + } + } +} diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/RenewableWakeLock.java b/bramble-android/src/main/java/org/briarproject/bramble/system/RenewableWakeLock.java index a4cc520f2..5dcca9ce5 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/system/RenewableWakeLock.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/system/RenewableWakeLock.java @@ -4,22 +4,23 @@ import android.os.PowerManager; import android.os.PowerManager.WakeLock; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.system.AndroidWakeLock; import org.briarproject.bramble.api.system.TaskScheduler; import java.util.concurrent.Future; import java.util.logging.Logger; import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.logging.Level.INFO; import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; @ThreadSafe @NotNullByDefault -class RenewableWakeLock implements AndroidWakeLock { +class RenewableWakeLock implements SharedWakeLock { private static final Logger LOG = getLogger(RenewableWakeLock.class.getName()); @@ -31,10 +32,14 @@ class RenewableWakeLock implements AndroidWakeLock { private final long durationMs, safetyMarginMs; private final Object lock = new Object(); + @GuardedBy("lock") @Nullable - private WakeLock wakeLock; // Locking: lock + private WakeLock wakeLock; + @GuardedBy("lock") @Nullable - private Future future; // Locking: lock + private Future future; + @GuardedBy("lock") + private int refCount = 0; RenewableWakeLock(PowerManager powerManager, TaskScheduler scheduler, int levelAndFlags, String tag, long durationMs, @@ -49,16 +54,21 @@ class RenewableWakeLock implements AndroidWakeLock { @Override public void acquire() { - if (LOG.isLoggable(INFO)) LOG.info("Acquiring wake lock " + tag); synchronized (lock) { - if (wakeLock != null) { - LOG.info("Already acquired"); - return; + refCount++; + if (refCount == 1) { + if (LOG.isLoggable(INFO)) { + LOG.info("Acquiring wake lock " + tag); + } + wakeLock = powerManager.newWakeLock(levelAndFlags, tag); + // We do our own reference counting so we can replace the lock + // TODO: Check whether using a ref-counted wake lock affects + // power management apps + wakeLock.setReferenceCounted(false); + wakeLock.acquire(durationMs + safetyMarginMs); + future = scheduler.schedule(this::renew, durationMs, + MILLISECONDS); } - wakeLock = powerManager.newWakeLock(levelAndFlags, tag); - wakeLock.setReferenceCounted(false); - wakeLock.acquire(durationMs + safetyMarginMs); - future = scheduler.schedule(this::renew, durationMs, MILLISECONDS); } } @@ -80,17 +90,17 @@ class RenewableWakeLock implements AndroidWakeLock { @Override public void release() { - if (LOG.isLoggable(INFO)) LOG.info("Releasing wake lock " + tag); synchronized (lock) { - if (wakeLock == null) { - LOG.info("Already released"); - return; + refCount--; + if (refCount == 0) { + if (LOG.isLoggable(INFO)) { + LOG.info("Releasing wake lock " + tag); + } + requireNonNull(future).cancel(false); + future = null; + requireNonNull(wakeLock).release(); + wakeLock = null; } - if (future == null) throw new AssertionError(); - future.cancel(false); - future = null; - wakeLock.release(); - wakeLock = null; } } } diff --git a/bramble-android/src/main/java/org/briarproject/bramble/system/SharedWakeLock.java b/bramble-android/src/main/java/org/briarproject/bramble/system/SharedWakeLock.java new file mode 100644 index 000000000..db0a07043 --- /dev/null +++ b/bramble-android/src/main/java/org/briarproject/bramble/system/SharedWakeLock.java @@ -0,0 +1,22 @@ +package org.briarproject.bramble.system; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.AndroidWakeLock; + +@NotNullByDefault +interface SharedWakeLock { + + /** + * Acquires the wake lock. This increments the wake lock's reference count, + * so unlike {@link AndroidWakeLock#acquire()} every call to this method + * must be followed by a balancing call to {@link #release()}. + */ + void acquire(); + + /** + * Releases the wake lock. This decrements the wake lock's reference count, + * so unlike {@link AndroidWakeLock#release()} every call to this method + * must follow a balancing call to {@link #acquire()}. + */ + void release(); +}