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

Commit 5e1fef2e authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Refactor scene switching logic to selectively hide overlays" into main

parents 601dc87b 1315dd96
Loading
Loading
Loading
Loading
+100 −32
Original line number Diff line number Diff line
@@ -54,7 +54,6 @@ import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.falsingCollector
import com.android.systemui.classifier.falsingManager
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryBypassRepository
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
@@ -135,8 +134,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -560,9 +557,74 @@ class SceneContainerStartableTest : SysuiTestCase() {
        }

    @Test
    fun switchFromBouncerToQuickSettingsWhenDeviceUnlocked_whenLeaveOpenShade() =
    fun switchFromBouncerToQuickSettingsWhenDeviceUnlocked_whenLeaveOpenShade_singleShade() =
        kosmos.runTest {
            enableSingleShade()
            switchFromBouncerToQuickSettingsWhenDeviceUnlocked_whenLeaveOpenShade(
                switchToQs = {
                    sceneInteractor.changeScene(Scenes.QuickSettings, "switching to qs for test")
                    ObservableTransitionState.Idle(currentScene = Scenes.QuickSettings)
                },
                expectedSceneWhileBouncerIsShowing = Scenes.QuickSettings,
                expectedLastBackStackSceneWhileBouncerIsShowing = Scenes.Lockscreen,
                expectedSceneAfterUnlock = Scenes.QuickSettings,
                expectedOverlaysAfterUnlock = emptySet(),
                expectedLastBackStackSceneAfterUnlock = Scenes.Gone,
            )
        }

    @Test
    fun switchFromBouncerToQuickSettingsWhenDeviceUnlocked_whenLeaveOpenShade_dualShade() =
        kosmos.runTest {
            enableDualShade()
            switchFromBouncerToQuickSettingsWhenDeviceUnlocked_whenLeaveOpenShade(
                switchToQs = {
                    sceneInteractor.showOverlay(
                        Overlays.QuickSettingsShade,
                        "switching to qs for test",
                    )
                    ObservableTransitionState.Idle(
                        currentScene = Scenes.Lockscreen,
                        currentOverlays = setOf(Overlays.QuickSettingsShade),
                    )
                },
                expectedSceneWhileBouncerIsShowing = Scenes.Lockscreen,
                expectedLastBackStackSceneWhileBouncerIsShowing = null,
                expectedSceneAfterUnlock = Scenes.Gone,
                expectedOverlaysAfterUnlock = setOf(Overlays.QuickSettingsShade),
                expectedLastBackStackSceneAfterUnlock = null,
            )
        }

    /**
     * Runs through the scenario where the bouncer is accessed while QS is being shown and the
     * device gets unlocked. This is a helper that can help multiple scenarios.
     *
     * @param switchToQs A function that switches to the QS scene or overlay and returns the `Idle`
     *   representation of the expected current scene and overlays
     * @param expectedSceneWhileBouncerIsShowing The expected scene while the bouncer is showing.
     *   The helper function will check that the current _scene_ is this while the bouncer is
     *   showing
     * @param expectedLastBackStackSceneWhileBouncerIsShowing The expected last back stack scene
     *   while the bouncer is showing. The helper function will check that this is the last scene on
     *   the back stack while the bouncer is showing
     * @param expectedSceneAfterUnlock The expected scene once the device is unlocked. The helper
     *   function will check that this is the current _scene_ once the device is unlocked
     * @param expectedOverlaysAfterUnlock The expected overlays once the device is unlocked. The
     *   helper function will check that these are the current _overlays_ once the device is
     *   unlocked
     * @param expectedLastBackStackSceneAfterUnlock The expected last back stack scene after the
     *   device is unlocked. The helper function will check that this is the last scene on the back
     *   stack once the device is unlocked
     */
    private fun Kosmos.switchFromBouncerToQuickSettingsWhenDeviceUnlocked_whenLeaveOpenShade(
        switchToQs: Kosmos.() -> ObservableTransitionState.Idle,
        expectedSceneWhileBouncerIsShowing: SceneKey,
        expectedLastBackStackSceneWhileBouncerIsShowing: SceneKey?,
        expectedSceneAfterUnlock: SceneKey,
        expectedOverlaysAfterUnlock: Set<OverlayKey>,
        expectedLastBackStackSceneAfterUnlock: SceneKey?,
    ) {
        val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
        val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
        val backStack by collectLastValue(sceneBackInteractor.backStack)
@@ -578,22 +640,28 @@ class SceneContainerStartableTest : SysuiTestCase() {
        underTest.start()
        runCurrent()

            sceneInteractor.changeScene(Scenes.QuickSettings, "switching to qs for test")
            transitionState.value = ObservableTransitionState.Idle(Scenes.QuickSettings)
        val idleOnQs = switchToQs()
        transitionState.value = idleOnQs
        runCurrent()
            assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
        assertThat(currentSceneKey).isEqualTo(idleOnQs.currentScene)
        assertThat(currentOverlays).isEqualTo(idleOnQs.currentOverlays)

        sceneInteractor.showOverlay(Overlays.Bouncer, "showing bouncer for test")
        transitionState.value =
                ObservableTransitionState.Idle(Scenes.QuickSettings, setOf(Overlays.Bouncer))
            ObservableTransitionState.Idle(
                expectedSceneWhileBouncerIsShowing,
                setOf(Overlays.Bouncer),
            )
        runCurrent()
        assertThat(currentOverlays).contains(Overlays.Bouncer)
            assertThat(backStack?.asIterable()?.last()).isEqualTo(Scenes.Lockscreen)
        assertThat(backStack?.asIterable()?.lastOrNull())
            .isEqualTo(expectedLastBackStackSceneWhileBouncerIsShowing)

        updateFingerprintAuthStatus(isSuccess = true)
            assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
            assertThat(currentOverlays).doesNotContain(Overlays.Bouncer)
            assertThat(backStack?.asIterable()?.last()).isEqualTo(Scenes.Gone)
        assertThat(currentSceneKey).isEqualTo(expectedSceneAfterUnlock)
        assertThat(currentOverlays).isEqualTo(expectedOverlaysAfterUnlock)
        assertThat(backStack?.asIterable()?.lastOrNull())
            .isEqualTo(expectedLastBackStackSceneAfterUnlock)
    }

    @Test
+106 −37
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInte
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
import com.android.systemui.kairos.internal.util.fastForEach
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -442,8 +443,8 @@ constructor(
                    initialValue = null,
                )
            deviceUnlockedInteractor.deviceUnlockStatus
                .mapNotNull { deviceUnlockStatus ->
                    val (renderedScenes: List<SceneKey>, renderedOverlays) =
                .map { deviceUnlockStatus ->
                    val (renderedScenes: List<SceneKey>, renderedOverlays: Set<OverlayKey>) =
                        when (val transitionState = sceneInteractor.transitionState.value) {
                            is ObservableTransitionState.Idle ->
                                listOf(transitionState.currentScene) to
@@ -454,31 +455,34 @@ constructor(
                            is ObservableTransitionState.Transition.OverlayTransition ->
                                listOf(transitionState.currentScene) to
                                    setOfNotNull(
                                        transitionState.toContent.takeIf { it is OverlayKey },
                                        transitionState.fromContent.takeIf { it is OverlayKey },
                                        transitionState.toContent as? OverlayKey,
                                        transitionState.fromContent as? OverlayKey,
                                    )
                        }
                    val isOnLockscreen = renderedScenes.contains(Scenes.Lockscreen)
                    val isAlternateBouncerVisible = alternateBouncerInteractor.isVisibleState()
                    val isOnPrimaryBouncer = Overlays.Bouncer in renderedOverlays
                    if (!deviceUnlockStatus.isUnlocked) {
                        return@mapNotNull if (
                        return@map if (
                            renderedScenes.any { it in keyguardScenes } ||
                                Overlays.Bouncer in renderedOverlays
                        ) {
                            // Already on a keyguard scene or bouncer, no need to change scenes.
                            null
                            SwitchSceneCommand.NoOp
                        } else {
                            // The device locked while on a scene that's not a keyguard scene, go
                            // to Lockscreen.
                            Scenes.Lockscreen to "device locked in a non-keyguard scene"
                            SwitchSceneCommand.SwitchToScene(
                                targetSceneKey = Scenes.Lockscreen,
                                loggingReason = "device locked in a non-keyguard scene",
                            )
                        }
                    }

                    if (powerInteractor.detailedWakefulness.value.isAsleep()) {
                        // The logic below is for when the device becomes unlocked. That must be a
                        // no-op if the device is not awake.
                        return@mapNotNull null
                        return@map SwitchSceneCommand.NoOp
                    }

                    if (
@@ -487,6 +491,7 @@ constructor(
                    ) {
                        uiEventLogger.log(BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS)
                    }
                    val leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
                    when {
                        isAlternateBouncerVisible -> {
                            // When the device becomes unlocked when the alternate bouncer is
@@ -494,16 +499,16 @@ constructor(
                            alternateBouncerInteractor.hide()

                            // ... and go to Gone or stay on the current scene
                            if (
                                isOnLockscreen ||
                                    !statusBarStateController.leaveOpenOnKeyguardHide()
                            ) {
                                Scenes.Gone to
                                    "device was unlocked with alternate bouncer showing" +
                                        " and shade didn't need to be left open"
                            if (isOnLockscreen || !leaveShadeOpen) {
                                SwitchSceneCommand.SwitchToScene(
                                    targetSceneKey = Scenes.Gone,
                                    loggingReason =
                                        "device was unlocked while alternate bouncer" +
                                            " was showing and shade didn't need to be left open",
                                )
                            } else {
                                sceneBackInteractor.replaceLockscreenSceneOnBackStack()
                                null
                                SwitchSceneCommand.NoOp
                            }
                        }
                        isOnPrimaryBouncer -> {
@@ -511,20 +516,40 @@ constructor(
                            // Gone or remain in the current scene. If transition is a scene change,
                            // take the destination scene.
                            val targetScene = renderedScenes.last()
                            if (
                                targetScene == Scenes.Lockscreen ||
                                    !statusBarStateController.leaveOpenOnKeyguardHide()
                            ) {
                                Scenes.Gone to
                                    "device was unlocked with bouncer showing and shade" +
                                        " didn't need to be left open"
                            if (targetScene == Scenes.Lockscreen || !leaveShadeOpen) {
                                val loggingReason = buildString {
                                    append(
                                        "device was unlocked while the primary bouncer was showing"
                                    )
                                    if (leaveShadeOpen) {
                                        append(" and shade needed to be left open")
                                    } else {
                                        append(" and shade didn't need to be left open")
                                    }
                                }
                                SwitchSceneCommand.SwitchToScene(
                                    targetSceneKey = Scenes.Gone,
                                    hideOverlays =
                                        if (leaveShadeOpen) {
                                            // Only hide the bouncer overlay, leaving any other
                                            // overlay (right now the only other overlays are
                                            // shades) visible.
                                            HideOverlayCommand.HideSome(Overlays.Bouncer)
                                        } else {
                                            HideOverlayCommand.HideAll
                                        },
                                    loggingReason = loggingReason,
                                )
                            } else {
                                if (previousScene.value != Scenes.Gone) {
                                    sceneBackInteractor.replaceLockscreenSceneOnBackStack()
                                }
                                targetScene to
                                    "device was unlocked with primary bouncer showing," +
                                        " from sceneKey=$targetScene"
                                SwitchSceneCommand.SwitchToScene(
                                    targetSceneKey = targetScene,
                                    loggingReason =
                                        "device was unlocked with primary bouncer" +
                                            " showing, from sceneKey=${targetScene.debugName}",
                                )
                            }
                        }
                        isOnLockscreen ->
@@ -539,23 +564,36 @@ constructor(
                            when {
                                deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen ==
                                    true ->
                                    Scenes.Gone to
                                        "device has been unlocked on lockscreen with bypass " +
                                            "enabled or using an active authentication " +
                                            "mechanism: ${deviceUnlockStatus.deviceUnlockSource}"
                                else -> null
                                    SwitchSceneCommand.SwitchToScene(
                                        targetSceneKey = Scenes.Gone,
                                        loggingReason =
                                            "device was unlocked while lockscreen" +
                                                " with bypass enabled or using an active" +
                                                " authentication mechanism:" +
                                                " ${deviceUnlockStatus.deviceUnlockSource}",
                                    )
                                else -> SwitchSceneCommand.NoOp
                            }
                        // Not on lockscreen or bouncer, so remain in the current scene but since
                        // unlocked, replace the Lockscreen scene from the bottom of the navigation
                        // back stack with the Gone scene.
                        else -> {
                            sceneBackInteractor.replaceLockscreenSceneOnBackStack()
                            null
                            SwitchSceneCommand.NoOp
                        }
                    }
                }
                .collect { command: SwitchSceneCommand ->
                    when (command) {
                        is SwitchSceneCommand.SwitchToScene -> {
                            switchToScene(
                                targetSceneKey = command.targetSceneKey,
                                hideOverlays = command.hideOverlays,
                                loggingReason = command.loggingReason,
                            )
                        }
                        is SwitchSceneCommand.NoOp -> Unit
                    }
                .collect { (targetSceneKey, loggingReason) ->
                    switchToScene(targetSceneKey = targetSceneKey, loggingReason = loggingReason)
                }
        }
    }
@@ -645,7 +683,12 @@ constructor(
                        switchToScene(
                            targetSceneKey = SceneFamilies.Home,
                            loggingReason = "dream stopped",
                            hideAllOverlays = deviceUnlockedInteractor.isUnlocked,
                            hideOverlays =
                                if (deviceUnlockedInteractor.isUnlocked) {
                                    HideOverlayCommand.HideAll
                                } else {
                                    HideOverlayCommand.HideNone
                                },
                        )
                    }
                }
@@ -1004,14 +1047,20 @@ constructor(
        loggingReason: String,
        sceneState: Any? = null,
        freezeAndAnimateToCurrentState: Boolean = false,
        hideAllOverlays: Boolean = true,
        hideOverlays: HideOverlayCommand = HideOverlayCommand.HideAll,
    ) {
        if (hideOverlays is HideOverlayCommand.HideSome) {
            hideOverlays.overlays.fastForEach { overlay ->
                sceneInteractor.hideOverlay(overlay, loggingReason)
            }
        }

        sceneInteractor.changeScene(
            toScene = targetSceneKey,
            loggingReason = loggingReason,
            sceneState = sceneState,
            forceSettleToTargetScene = freezeAndAnimateToCurrentState,
            hideAllOverlays = hideAllOverlays,
            hideAllOverlays = hideOverlays == HideOverlayCommand.HideAll,
        )
    }

@@ -1104,6 +1153,26 @@ constructor(
        }
    }

    sealed interface SwitchSceneCommand {
        data object NoOp : SwitchSceneCommand

        data class SwitchToScene(
            val targetSceneKey: SceneKey,
            val loggingReason: String,
            val hideOverlays: HideOverlayCommand = HideOverlayCommand.HideAll,
        ) : SwitchSceneCommand
    }

    sealed interface HideOverlayCommand {
        data object HideAll : HideOverlayCommand

        data object HideNone : HideOverlayCommand

        class HideSome(val overlays: List<OverlayKey>) : HideOverlayCommand {
            constructor(overlay: OverlayKey) : this(listOf(overlay))
        }
    }

    companion object {
        private const val TAG = "SceneContainerStartable"
    }