Skip to content

Commit 82ee103

Browse files
toniheicopybara-github
authored andcommitted
Add emergency wifi lock release
This copies the same changes applied to WakeLockManager to WifiManager. See 80a3cde and fb2b678. PiperOrigin-RevId: 817641489
1 parent dcc1505 commit 82ee103

File tree

3 files changed

+88
-8
lines changed

3 files changed

+88
-8
lines changed

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/WifiLockManager.java

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
*/
1616
package androidx.media3.exoplayer;
1717

18+
import android.Manifest;
1819
import android.content.Context;
20+
import android.content.pm.PackageManager;
1921
import android.net.wifi.WifiManager;
2022
import android.net.wifi.WifiManager.WifiLock;
2123
import android.os.Looper;
22-
import androidx.annotation.Nullable;
2324
import androidx.media3.common.util.Clock;
2425
import androidx.media3.common.util.HandlerWrapper;
2526
import androidx.media3.common.util.Log;
27+
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
2628

2729
/**
2830
* Handles a {@link WifiLock}
@@ -34,9 +36,11 @@
3436

3537
private static final String TAG = "WifiLockManager";
3638
private static final String WIFI_LOCK_TAG = "ExoPlayer:WifiLockManager";
39+
private static final int UNREACTIVE_WIFILOCK_HANDLER_RELEASE_DELAY_MS = 1000;
3740

3841
private final WifiLockManagerInternal wifiLockManagerInternal;
3942
private final HandlerWrapper wifiLockHandler;
43+
private final HandlerWrapper mainHandler;
4044

4145
private boolean enabled;
4246
private boolean stayAwake;
@@ -51,6 +55,7 @@
5155
public WifiLockManager(Context context, Looper wifiLockLooper, Clock clock) {
5256
wifiLockManagerInternal = new WifiLockManagerInternal(context.getApplicationContext());
5357
wifiLockHandler = clock.createHandler(wifiLockLooper, /* callback= */ null);
58+
mainHandler = clock.createHandler(Looper.getMainLooper(), /* callback= */ null);
5459
}
5560

5661
/**
@@ -68,8 +73,7 @@ public void setEnabled(boolean enabled) {
6873
return;
6974
}
7075
this.enabled = enabled;
71-
boolean stayAwakeCurrent = stayAwake;
72-
wifiLockHandler.post(() -> wifiLockManagerInternal.updateWifiLock(enabled, stayAwakeCurrent));
76+
postUpdateWifiLock(enabled, stayAwake);
7377
}
7478

7579
/**
@@ -87,24 +91,48 @@ public void setStayAwake(boolean stayAwake) {
8791
}
8892
this.stayAwake = stayAwake;
8993
if (enabled) {
94+
postUpdateWifiLock(/* enabled= */ true, stayAwake);
95+
}
96+
}
97+
98+
private void postUpdateWifiLock(boolean enabled, boolean stayAwake) {
99+
if (shouldAcquireWifilock(enabled, stayAwake)) {
100+
wifiLockHandler.post(() -> wifiLockManagerInternal.updateWifiLock(enabled, stayAwake));
101+
} else {
102+
// When we are about to release a Wifi lock, add emergency safeguard on main thread in case
103+
// the lock handler thread is unresponsive.
104+
Runnable emergencyRelease = wifiLockManagerInternal::forceReleaseWifiLock;
105+
mainHandler.postDelayed(emergencyRelease, UNREACTIVE_WIFILOCK_HANDLER_RELEASE_DELAY_MS);
90106
wifiLockHandler.post(
91-
() -> wifiLockManagerInternal.updateWifiLock(/* enabled= */ true, stayAwake));
107+
() -> {
108+
mainHandler.removeCallbacks(emergencyRelease);
109+
wifiLockManagerInternal.updateWifiLock(enabled, stayAwake);
110+
});
92111
}
93112
}
94113

114+
private static boolean shouldAcquireWifilock(boolean enabled, boolean stayAwake) {
115+
return enabled && stayAwake;
116+
}
117+
95118
/** Internal methods called on the wifi lock Looper. */
96119
private static final class WifiLockManagerInternal {
97120

98121
private final Context applicationContext;
99122

100-
@Nullable private WifiLock wifiLock;
123+
private @MonotonicNonNull WifiLock wifiLock;
101124

102125
public WifiLockManagerInternal(Context applicationContext) {
103126
this.applicationContext = applicationContext;
104127
}
105128

106129
public void updateWifiLock(boolean enabled, boolean stayAwake) {
107130
if (enabled && wifiLock == null) {
131+
if (applicationContext.checkSelfPermission(Manifest.permission.WAKE_LOCK)
132+
!= PackageManager.PERMISSION_GRANTED) {
133+
Log.w(TAG, "WAKE_LOCK permission not granted, can't acquire wake lock for playback");
134+
return;
135+
}
108136
WifiManager wifiManager =
109137
(WifiManager)
110138
applicationContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
@@ -120,11 +148,17 @@ public void updateWifiLock(boolean enabled, boolean stayAwake) {
120148
return;
121149
}
122150

123-
if (enabled && stayAwake) {
151+
if (shouldAcquireWifilock(enabled, stayAwake)) {
124152
wifiLock.acquire();
125153
} else {
126154
wifiLock.release();
127155
}
128156
}
157+
158+
private synchronized void forceReleaseWifiLock() {
159+
if (wifiLock != null) {
160+
wifiLock.release();
161+
}
162+
}
129163
}
130164
}

libraries/exoplayer/src/test/java/androidx/media3/exoplayer/WakeLockManagerTest.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import android.os.HandlerThread;
2525
import android.os.PowerManager.WakeLock;
2626
import androidx.media3.common.util.Clock;
27+
import androidx.media3.common.util.ConditionVariable;
2728
import androidx.test.core.app.ApplicationProvider;
2829
import androidx.test.ext.junit.runners.AndroidJUnit4;
2930
import java.time.Duration;
@@ -100,10 +101,11 @@ public void blockedWakeLockThread_wakeLockIsStillReleasedAfterTimeout() {
100101
WakeLock wakeLock = ShadowPowerManager.getLatestWakeLock();
101102

102103
// Block the wake lock thread to prevent any progress.
104+
ConditionVariable blockedWakeLockThread = new ConditionVariable();
103105
new Handler(handlerThread.getLooper())
104106
.post(
105107
() -> {
106-
while (true) {}
108+
while (!blockedWakeLockThread.isOpen()) {}
107109
});
108110
wakeLockManager.setEnabled(false);
109111
ShadowLooper.idleMainLooper();
@@ -112,5 +114,10 @@ public void blockedWakeLockThread_wakeLockIsStillReleasedAfterTimeout() {
112114
ShadowLooper.idleMainLooper();
113115

114116
assertThat(wakeLock.isHeld()).isFalse();
117+
118+
// Verify that a slow background thread that unblocks itself can't cause any issues.
119+
blockedWakeLockThread.open();
120+
ShadowLooper.idleMainLooper();
121+
assertThat(wakeLock.isHeld()).isFalse();
115122
}
116123
}

libraries/exoplayer/src/test/java/androidx/media3/exoplayer/WifiLockManagerTest.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,36 @@
1818
import static com.google.common.truth.Truth.assertThat;
1919
import static org.robolectric.Shadows.shadowOf;
2020

21+
import android.Manifest;
22+
import android.app.Application;
2123
import android.content.Context;
2224
import android.net.wifi.WifiManager;
25+
import android.os.Handler;
2326
import android.os.HandlerThread;
2427
import androidx.media3.common.util.Clock;
28+
import androidx.media3.common.util.ConditionVariable;
2529
import androidx.test.core.app.ApplicationProvider;
2630
import androidx.test.ext.junit.runners.AndroidJUnit4;
31+
import java.time.Duration;
2732
import org.junit.After;
2833
import org.junit.Before;
2934
import org.junit.Test;
3035
import org.junit.runner.RunWith;
36+
import org.robolectric.shadows.ShadowLooper;
37+
import org.robolectric.shadows.ShadowSystemClock;
3138

3239
/** Unit test for {@link WifiLockManager}. */
3340
@RunWith(AndroidJUnit4.class)
3441
public class WifiLockManagerTest {
3542

36-
private Context context;
43+
private Application context;
3744
private HandlerThread handlerThread;
3845
private WifiManager wifiManager;
3946

4047
@Before
4148
public void setUp() {
4249
context = ApplicationProvider.getApplicationContext();
50+
shadowOf(context).grantPermissions(Manifest.permission.WAKE_LOCK);
4351
handlerThread = new HandlerThread("wifiLockManagerTest");
4452
handlerThread.start();
4553
wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
@@ -86,4 +94,35 @@ public void stayAwakeTrue_wifiLockIsOnlyHeldWhenEnabled() {
8694
assertThat(lockCountWhenStayAwake).isGreaterThan(initialLockCount);
8795
assertThat(lockCountAfterDisable).isLessThan(lockCountWhenStayAwake);
8896
}
97+
98+
@Test
99+
public void blockedWakeLockThread_wifiLockIsStillReleasedAfterTimeout() {
100+
WifiLockManager wifiLockManager =
101+
new WifiLockManager(context, handlerThread.getLooper(), Clock.DEFAULT);
102+
wifiLockManager.setEnabled(true);
103+
wifiLockManager.setStayAwake(true);
104+
shadowOf(handlerThread.getLooper()).idle();
105+
int lockCountWhenEnabled = shadowOf(wifiManager).getActiveLockCount();
106+
107+
// Block the wake lock thread to prevent any progress.
108+
ConditionVariable blockedWakeLockThread = new ConditionVariable();
109+
new Handler(handlerThread.getLooper())
110+
.post(
111+
() -> {
112+
while (!blockedWakeLockThread.isOpen()) {}
113+
});
114+
wifiLockManager.setEnabled(false);
115+
ShadowLooper.idleMainLooper();
116+
// Verify it didn't work yet.
117+
assertThat(shadowOf(wifiManager).getActiveLockCount()).isEqualTo(lockCountWhenEnabled);
118+
ShadowSystemClock.advanceBy(Duration.ofSeconds(5));
119+
ShadowLooper.idleMainLooper();
120+
121+
assertThat(shadowOf(wifiManager).getActiveLockCount()).isLessThan(lockCountWhenEnabled);
122+
123+
// Verify that a slow background thread that unblocks itself can't cause any issues.
124+
blockedWakeLockThread.open();
125+
ShadowLooper.idleMainLooper();
126+
assertThat(shadowOf(wifiManager).getActiveLockCount()).isLessThan(lockCountWhenEnabled);
127+
}
89128
}

0 commit comments

Comments
 (0)