[flexiglass] hide and cancel AlternateBouncer when device sleeps

After the device goes to sleep, the alternate bouncer should
immediately hide and any dismiss actions should be reset and
dismiss callbacks should receive an onDismissCancelled call.

Previously, the dismiss registry callbacks would miss success
and cancelled calls. This CL will call onDismissCancelled
when the device goes to sleep from the alternate or
primary bouncer and will call onDismissSucceeded when the
device becomes unlocked.

Fixes: 353955910
Flag: com.android.systemui.scene_container
Test: atest SceneContainerStartableTest
KeyguardDismissActionInteractorTest
Test: look at DismissCallbackRegistry logs from logcat and observe
the cancellation and success calls from the alt/primary bouncer
on AlternateBouncer => sleeping, PrimaryBouncer => sleeping,
AlternateBouncer => Primary Bouncer
Test: Tap on a notification with an intent to bring up the alternate
bouncer. Press the power button to put the device to sleep. Wake
the device and use the FP sensor to authenticate. Observe that the
device enters and does NOT open the stale notification intent.

Change-Id: Ifd6c1a1a1c36da584eaa8a96e73a3848bf943244
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 2dfff3f..5533a8b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -28,9 +28,7 @@
 import com.android.internal.logging.uiEventLoggerFake
 import com.android.internal.policy.IKeyguardDismissCallback
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
-import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
@@ -331,6 +329,7 @@
             kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                 SuccessFingerprintAuthenticationStatus(0, true)
             )
+            runCurrent()
 
             assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
             assertThat(alternateBouncerVisible).isFalse()
@@ -481,6 +480,33 @@
         }
 
     @Test
+    fun hideAlternateBouncerAndNotifyDismissCancelledWhenDeviceSleeps() =
+        testScope.runTest {
+            val alternateBouncerVisible by
+                collectLastValue(bouncerRepository.alternateBouncerVisible)
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            prepareState(
+                isDeviceUnlocked = false,
+                initialSceneKey = Scenes.Shade,
+            )
+            assertThat(currentSceneKey).isEqualTo(Scenes.Shade)
+            bouncerRepository.setAlternateVisible(true)
+            underTest.start()
+
+            // run all pending dismiss succeeded/cancelled calls from setup:
+            kosmos.fakeExecutor.runAllReady()
+
+            val dismissCallback: IKeyguardDismissCallback = mock()
+            kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
+            powerInteractor.setAsleepForTest()
+            runCurrent()
+            kosmos.fakeExecutor.runAllReady()
+
+            assertThat(alternateBouncerVisible).isFalse()
+            verify(dismissCallback).onDismissCancelled()
+        }
+
+    @Test
     fun switchToLockscreenWhenDeviceSleepsLocked() =
         testScope.runTest {
             val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -1618,19 +1644,27 @@
         }
 
     @Test
-    fun notifyKeyguardDismissCallbacks_whenUnlocking_onDismissSucceeded() =
+    fun notifyKeyguardDismissCallbacks_whenUnlockingFromBouncer_onDismissSucceeded() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
-            prepareState()
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            prepareState(
+                authenticationMethod = AuthenticationMethodModel.Pin,
+                isDeviceUnlocked = false,
+                initialSceneKey = Scenes.Bouncer,
+            )
+            assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer)
             underTest.start()
+
+            // run all pending dismiss succeeded/cancelled calls from setup:
+            kosmos.fakeExecutor.runAllReady()
+
             val dismissCallback: IKeyguardDismissCallback = mock()
             kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
 
-            // Switch to bouncer and unlock device:
-            sceneInteractor.changeScene(Scenes.Bouncer, "")
-            assertThat(currentScene).isEqualTo(Scenes.Bouncer)
-            kosmos.authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
-            assertThat(currentScene).isEqualTo(Scenes.Gone)
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
             kosmos.fakeExecutor.runAllReady()
 
             verify(dismissCallback).onDismissSucceeded()
@@ -1639,19 +1673,26 @@
     @Test
     fun notifyKeyguardDismissCallbacks_whenLeavingBouncer_onDismissCancelled() =
         testScope.runTest {
+            val isUnlocked by collectLastValue(kosmos.deviceEntryInteractor.isUnlocked)
             val currentScene by collectLastValue(sceneInteractor.currentScene)
             prepareState()
             underTest.start()
+
+            // run all pending dismiss succeeded/cancelled calls from setup:
+            kosmos.fakeExecutor.runAllReady()
+
             val dismissCallback: IKeyguardDismissCallback = mock()
             kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
 
             // Switch to bouncer:
             sceneInteractor.changeScene(Scenes.Bouncer, "")
             assertThat(currentScene).isEqualTo(Scenes.Bouncer)
+            runCurrent()
 
-            // Return to lockscreen:
+            // Return to lockscreen when isUnlocked=false:
             sceneInteractor.changeScene(Scenes.Lockscreen, "")
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(isUnlocked).isFalse()
             runCurrent()
             kosmos.fakeExecutor.runAllReady()
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index 1c445a7..7801c00 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -17,15 +17,16 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
-import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.domain.resolver.NotifShadeSceneFamilyResolver
 import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolver
@@ -61,6 +62,8 @@
     deviceEntryInteractor: DeviceEntryInteractor,
     quickSettingsSceneFamilyResolver: QuickSettingsSceneFamilyResolver,
     notifShadeSceneFamilyResolver: NotifShadeSceneFamilyResolver,
+    powerInteractor: PowerInteractor,
+    alternateBouncerInteractor: AlternateBouncerInteractor,
 ) {
     val dismissAction: Flow<DismissAction> = repository.dismissAction
 
@@ -124,10 +127,12 @@
                     scene = Scenes.Bouncer,
                     stateWithoutSceneContainer = PRIMARY_BOUNCER
                 ),
-                transitionInteractor.isFinishedIn(state = ALTERNATE_BOUNCER),
+                alternateBouncerInteractor.isVisible,
                 isOnShadeWhileUnlocked,
-            ) { isOnGone, isOnBouncer, isOnAltBouncer, isOnShadeWhileUnlocked ->
-                !isOnGone && !isOnBouncer && !isOnAltBouncer && !isOnShadeWhileUnlocked
+                powerInteractor.isAsleep,
+            ) { isOnGone, isOnBouncer, isOnAltBouncer, isOnShadeWhileUnlocked, isAsleep ->
+                (!isOnGone && !isOnBouncer && !isOnAltBouncer && !isOnShadeWhileUnlocked) ||
+                    isAsleep
             }
             .filter { it }
             .sampleFilter(dismissAction) { it !is DismissAction.None }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index e73664d..cc46216 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -152,7 +152,7 @@
             hydrateBackStack()
             resetShadeSessions()
             handleKeyguardEnabledness()
-            notifyKeyguardDismissCallbacks()
+            notifyKeyguardDismissCancelledCallbacks()
             refreshLockscreenEnabled()
         } else {
             sceneLogger.logFrameworkEnabled(
@@ -379,8 +379,10 @@
                     when {
                         isAlternateBouncerVisible -> {
                             // When the device becomes unlocked when the alternate bouncer is
-                            // showing, always hide the alternate bouncer...
+                            // showing, always hide the alternate bouncer and notify dismiss
+                            // succeeded
                             alternateBouncerInteractor.hide()
+                            dismissCallbackRegistry.notifyDismissSucceeded()
 
                             // ... and go to Gone or stay on the current scene
                             if (
@@ -394,9 +396,11 @@
                                 null
                             }
                         }
-                        isOnPrimaryBouncer ->
+                        isOnPrimaryBouncer -> {
                             // When the device becomes unlocked in primary Bouncer,
+                            // notify dismiss succeeded and
                             // go to previous scene or Gone.
+                            dismissCallbackRegistry.notifyDismissSucceeded()
                             if (
                                 previousScene.value == Scenes.Lockscreen ||
                                     !statusBarStateController.leaveOpenOnKeyguardHide()
@@ -410,6 +414,7 @@
                                     "device was unlocked with primary bouncer showing," +
                                         " from sceneKey=$prevScene"
                             }
+                        }
                         isOnLockscreen ->
                             // The lockscreen should be dismissed automatically in 2 scenarios:
                             // 1. When face auth bypass is enabled and authentication happens while
@@ -468,6 +473,9 @@
         applicationScope.launch {
             powerInteractor.isAsleep.collect { isAsleep ->
                 if (isAsleep) {
+                    alternateBouncerInteractor.hide()
+                    dismissCallbackRegistry.notifyDismissCancelled()
+
                     switchToScene(
                         targetSceneKey = Scenes.Lockscreen,
                         loggingReason = "device is starting to sleep",
@@ -771,15 +779,23 @@
         }
     }
 
-    private fun notifyKeyguardDismissCallbacks() {
+    private fun notifyKeyguardDismissCancelledCallbacks() {
         applicationScope.launch {
-            sceneInteractor.currentScene.pairwise().collect { (from, to) ->
-                when {
-                    from != Scenes.Bouncer -> Unit
-                    to == Scenes.Gone -> dismissCallbackRegistry.notifyDismissSucceeded()
-                    else -> dismissCallbackRegistry.notifyDismissCancelled()
+            combine(
+                    deviceEntryInteractor.isUnlocked,
+                    sceneInteractor.currentScene.pairwise(),
+                ) { isUnlocked, (from, to) ->
+                    when {
+                        from != Scenes.Bouncer -> false
+                        to != Scenes.Gone && !isUnlocked -> true
+                        else -> false
+                    }
                 }
-            }
+                .collect { notifyKeyguardDismissCancelled ->
+                    if (notifyKeyguardDismissCancelled) {
+                        dismissCallbackRegistry.notifyDismissCancelled()
+                    }
+                }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index de4d14d..0f93ff2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -545,6 +545,7 @@
 
     @VisibleForTesting
     void consumeFromAlternateBouncerTransitionSteps(TransitionStep step) {
+        SceneContainerFlag.assertInLegacyMode();
         hideAlternateBouncer(false);
     }
 
@@ -554,6 +555,7 @@
      */
     @VisibleForTesting
     void consumeKeyguardAuthenticatedBiometricsHandled(Unit handled) {
+        SceneContainerFlag.assertInLegacyMode();
         if (mAlternateBouncerInteractor.isVisibleState()) {
             hideAlternateBouncer(false);
         }
@@ -981,7 +983,7 @@
             } else {
                 showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset);
             }
-            if (hideBouncerWhenShowing) {
+            if (!SceneContainerFlag.isEnabled() && hideBouncerWhenShowing) {
                 hideAlternateBouncer(true);
             }
             mKeyguardUpdateManager.sendKeyguardReset();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index 32d059b..a0fe538b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.EnableSceneContainer
@@ -29,6 +30,10 @@
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.scene.data.repository.Idle
 import com.android.systemui.scene.data.repository.Transition
 import com.android.systemui.scene.data.repository.setSceneTransition
@@ -82,6 +87,8 @@
                 deviceEntryInteractor = kosmos.deviceEntryInteractor,
                 quickSettingsSceneFamilyResolver = kosmos.quickSettingsSceneFamilyResolver,
                 notifShadeSceneFamilyResolver = kosmos.notifShadeSceneFamilyResolver,
+                powerInteractor = kosmos.powerInteractor,
+                alternateBouncerInteractor = kosmos.alternateBouncerInteractor,
             )
     }
 
@@ -234,6 +241,32 @@
         }
 
     @Test
+    fun resetDismissAction_onBouncer_OnAsleep() =
+        testScope.runTest {
+            kosmos.setSceneTransition(Idle(Scenes.Bouncer))
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.None
+            )
+            val resetDismissAction by collectLastValue(underTest.resetDismissAction)
+            keyguardRepository.setDismissAction(
+                DismissAction.RunAfterKeyguardGone(
+                    dismissAction = {},
+                    onCancelAction = {},
+                    message = "message",
+                    willAnimateOnLockscreen = true,
+                )
+            )
+            assertThat(resetDismissAction).isNull()
+            kosmos.fakePowerRepository.updateWakefulness(
+                rawState = WakefulnessState.ASLEEP,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.TIMEOUT,
+                powerButtonLaunchGestureTriggered = false,
+            )
+            assertThat(resetDismissAction).isEqualTo(Unit)
+        }
+
+    @Test
     fun setDismissAction_callsCancelRunnableOnPreviousDismissAction() =
         testScope.runTest {
             val dismissAction by collectLastValue(underTest.dismissAction)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index 957f092..27eadb1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -16,10 +16,12 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.resolver.notifShadeSceneFamilyResolver
 import com.android.systemui.scene.domain.resolver.quickSettingsSceneFamilyResolver
@@ -37,5 +39,7 @@
             deviceEntryInteractor = deviceEntryInteractor,
             quickSettingsSceneFamilyResolver = quickSettingsSceneFamilyResolver,
             notifShadeSceneFamilyResolver = notifShadeSceneFamilyResolver,
+            powerInteractor = powerInteractor,
+            alternateBouncerInteractor = alternateBouncerInteractor,
         )
     }