Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ddf24911 authored by Beverly's avatar Beverly
Browse files

[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
parent b5508fff
Loading
Loading
Loading
Loading
+52 −11
Original line number Diff line number Diff line
@@ -28,9 +28,7 @@ import com.android.compose.animation.scene.SceneKey
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 @@ class SceneContainerStartableTest : SysuiTestCase() {
            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )
            runCurrent()

            assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
            assertThat(alternateBouncerVisible).isFalse()
@@ -480,6 +479,33 @@ class SceneContainerStartableTest : SysuiTestCase() {
            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
        }

    @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 {
@@ -1618,19 +1644,27 @@ class SceneContainerStartableTest : SysuiTestCase() {
        }

    @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 @@ class SceneContainerStartableTest : SysuiTestCase() {
    @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()

+9 −4
Original line number Diff line number Diff line
@@ -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 @@ constructor(
    deviceEntryInteractor: DeviceEntryInteractor,
    quickSettingsSceneFamilyResolver: QuickSettingsSceneFamilyResolver,
    notifShadeSceneFamilyResolver: NotifShadeSceneFamilyResolver,
    powerInteractor: PowerInteractor,
    alternateBouncerInteractor: AlternateBouncerInteractor,
) {
    val dismissAction: Flow<DismissAction> = repository.dismissAction

@@ -124,10 +127,12 @@ constructor(
                    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 }
+26 −10
Original line number Diff line number Diff line
@@ -152,7 +152,7 @@ constructor(
            hydrateBackStack()
            resetShadeSessions()
            handleKeyguardEnabledness()
            notifyKeyguardDismissCallbacks()
            notifyKeyguardDismissCancelledCallbacks()
            refreshLockscreenEnabled()
        } else {
            sceneLogger.logFrameworkEnabled(
@@ -379,8 +379,10 @@ constructor(
                    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 @@ constructor(
                                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 @@ constructor(
                                    "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 @@ constructor(
        applicationScope.launch {
            powerInteractor.isAsleep.collect { isAsleep ->
                if (isAsleep) {
                    alternateBouncerInteractor.hide()
                    dismissCallbackRegistry.notifyDismissCancelled()

                    switchToScene(
                        targetSceneKey = Scenes.Lockscreen,
                        loggingReason = "device is starting to sleep",
@@ -771,13 +779,21 @@ constructor(
        }
    }

    private fun notifyKeyguardDismissCallbacks() {
    private fun notifyKeyguardDismissCancelledCallbacks() {
        applicationScope.launch {
            sceneInteractor.currentScene.pairwise().collect { (from, to) ->
            combine(
                    deviceEntryInteractor.isUnlocked,
                    sceneInteractor.currentScene.pairwise(),
                ) { isUnlocked, (from, to) ->
                    when {
                    from != Scenes.Bouncer -> Unit
                    to == Scenes.Gone -> dismissCallbackRegistry.notifyDismissSucceeded()
                    else -> dismissCallbackRegistry.notifyDismissCancelled()
                        from != Scenes.Bouncer -> false
                        to != Scenes.Gone && !isUnlocked -> true
                        else -> false
                    }
                }
                .collect { notifyKeyguardDismissCancelled ->
                    if (notifyKeyguardDismissCancelled) {
                        dismissCallbackRegistry.notifyDismissCancelled()
                    }
                }
        }
+3 −1
Original line number Diff line number Diff line
@@ -545,6 +545,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb

    @VisibleForTesting
    void consumeFromAlternateBouncerTransitionSteps(TransitionStep step) {
        SceneContainerFlag.assertInLegacyMode();
        hideAlternateBouncer(false);
    }

@@ -554,6 +555,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
     */
    @VisibleForTesting
    void consumeKeyguardAuthenticatedBiometricsHandled(Unit handled) {
        SceneContainerFlag.assertInLegacyMode();
        if (mAlternateBouncerInteractor.isVisibleState()) {
            hideAlternateBouncer(false);
        }
@@ -981,7 +983,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
            } else {
                showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset);
            }
            if (hideBouncerWhenShowing) {
            if (!SceneContainerFlag.isEnabled() && hideBouncerWhenShowing) {
                hideAlternateBouncer(true);
            }
            mKeyguardUpdateManager.sendKeyguardReset();
+33 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest
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.data.repository.fakeKeyguardRepository
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 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() {
                deviceEntryInteractor = kosmos.deviceEntryInteractor,
                quickSettingsSceneFamilyResolver = kosmos.quickSettingsSceneFamilyResolver,
                notifShadeSceneFamilyResolver = kosmos.notifShadeSceneFamilyResolver,
                powerInteractor = kosmos.powerInteractor,
                alternateBouncerInteractor = kosmos.alternateBouncerInteractor,
            )
    }

@@ -233,6 +240,32 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() {
            assertThat(resetDismissAction).isNull()
        }

    @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 {
Loading