diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/WebRtcCallActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/WebRtcCallActivity.kt index f70628fe56..5a28582f0f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/WebRtcCallActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/WebRtcCallActivity.kt @@ -253,6 +253,10 @@ class WebRtcCallActivity : BaseActivity(), SafetyNumberChangeDialog.Callback, Re } override fun onStart() { + // This instance outlived being backgrounded (e.g. the screen was turned off and back on), so any relaunch scheduled in onStop() is unnecessary. + // Cancel it before super.onStart() dispatches the app-foregrounded event, otherwise it would yank the call out of PiP and into fullscreen. + AppDependencies.signalCallManager.cancelPipRelaunch() + super.onStart() ephemeralStateDisposable = AppDependencies.signalCallManager diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java index fe96fc5cdd..b1d893e0a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java @@ -8,6 +8,7 @@ import android.os.Build; import android.os.ResultReceiver; import androidx.annotation.AnyThread; +import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -144,6 +145,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall. private WebRtcServiceState serviceState; private RxStore ephemeralStateStore; private boolean needsToSetSelfUuid = true; + private RelaunchListener pipRelaunchListener; private RxStore> linkPeekInfoStore; @@ -1422,8 +1424,30 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall. callManager.addAsset(assetGroup, content); } + /** + * Schedules the call activity to be relaunched in PiP the next time the app is foregrounded. Only relevant if the call activity did not survive being + * backgrounded, e.g. the user dismissed the PiP window or another app's PiP evicted it. If it did survive, {@link #cancelPipRelaunch()} should be called + * to cancel this. + */ + @MainThread public void relaunchPipOnForeground() { - AppForegroundObserver.addListener(new RelaunchListener(AppForegroundObserver.isForegrounded())); + cancelPipRelaunch(); + + pipRelaunchListener = new RelaunchListener(AppForegroundObserver.isForegrounded()); + AppForegroundObserver.addListener(pipRelaunchListener); + } + + /** + * Cancels any pending PiP relaunch scheduled via {@link #relaunchPipOnForeground()}. Relaunching while the call activity is still alive delivers a + * launch-in-PiP intent to it, which pulls it out of PiP and into fullscreen, or forces a deliberately expanded call back into PiP. The former is both + * unprompted and, if it happens while the keyguard is going away, can leave the device wedged in a partially-locked state. + */ + @MainThread + public void cancelPipRelaunch() { + if (pipRelaunchListener != null) { + AppForegroundObserver.removeListener(pipRelaunchListener); + pipRelaunchListener = null; + } } private void processSendMessageFailureWithChangeDetection(@NonNull RemotePeer remotePeer, @@ -1471,6 +1495,9 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall. }); } AppForegroundObserver.removeListener(this); + if (pipRelaunchListener == this) { + pipRelaunchListener = null; + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java index 6c76b8df68..1dd3ff9edd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java @@ -16,6 +16,7 @@ public class LockManager { private static final String TAG = Log.tag(LockManager.class); private final PowerManager.WakeLock fullLock; + private final PowerManager.WakeLock fullWakeUpLock; private final PowerManager.WakeLock partialLock; private final WifiManager.WifiLock wifiLock; private final ProximityLock proximityLock; @@ -33,6 +34,7 @@ public class LockManager { private enum LockState { FULL, + FULL_WAKE_UP, PARTIAL, SLEEP, PROXIMITY @@ -40,7 +42,10 @@ public class LockManager { public LockManager(Context context) { PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - fullLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "signal:full"); + // These differ only in ACQUIRE_CAUSES_WAKEUP, and must stay separate: these locks are not reference counted, so every acquire() reaches + // PowerManagerService even when already held, and re-applying ACQUIRE_CAUSES_WAKEUP turns the screen back on after the user powered it off. + fullLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "signal:full"); + fullWakeUpLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "signal:full-wakeup"); partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "signal:partial"); proximityLock = new ProximityLock(pm); @@ -48,6 +53,7 @@ public class LockManager { wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "signal:wifi"); fullLock.setReferenceCounted(false); + fullWakeUpLock.setReferenceCounted(false); partialLock.setReferenceCounted(false); wifiLock.setReferenceCounted(false); } @@ -69,7 +75,7 @@ public class LockManager { setLockState(LockState.PARTIAL); break; case INTERACTIVE: - setLockState(LockState.FULL); + setLockState(LockState.FULL_WAKE_UP); break; case IN_HANDS_FREE_CALL: setLockState(LockState.PARTIAL); @@ -92,16 +98,26 @@ public class LockManager { fullLock.acquire(); partialLock.acquire(); wifiLock.acquire(); + fullWakeUpLock.release(); + proximityLock.release(); + break; + case FULL_WAKE_UP: + fullWakeUpLock.acquire(); + partialLock.acquire(); + wifiLock.acquire(); + fullLock.release(); proximityLock.release(); break; case PARTIAL: partialLock.acquire(); wifiLock.acquire(); fullLock.release(); + fullWakeUpLock.release(); proximityLock.release(); break; case SLEEP: fullLock.release(); + fullWakeUpLock.release(); partialLock.release(); wifiLock.release(); proximityLock.release(); @@ -111,6 +127,7 @@ public class LockManager { proximityLock.acquire(); wifiLock.acquire(); fullLock.release(); + fullWakeUpLock.release(); break; default: throw new IllegalArgumentException("Unhandled Mode: " + newState);