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

Commit d5902b20 authored by Danny Burakov's avatar Danny Burakov Committed by Android (Google) Code Review
Browse files

Merge "[bc25] Ensure SceneContainer remains visible on Gone when shade is open." into main

parents 6a5c3d16 0c7184ae
Loading
Loading
Loading
Loading
+350 −8
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.internal.logging.uiEventLoggerFake
import com.android.internal.policy.IKeyguardDismissCallback
@@ -88,9 +89,11 @@ import com.android.systemui.scene.data.model.asIterable
import com.android.systemui.scene.data.repository.Transition
import com.android.systemui.scene.domain.interactor.sceneBackInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
@@ -161,6 +164,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
    }

    @Test
    @DisableFlags(DualShade.FLAG_NAME)
    fun hydrateVisibility() =
        testScope.runTest {
            val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene)
@@ -220,6 +224,87 @@ class SceneContainerStartableTest : SysuiTestCase() {
            assertThat(isVisible).isFalse()
        }

    @Test
    @EnableFlags(DualShade.FLAG_NAME)
    fun hydrateVisibility_dualShade() =
        testScope.runTest {
            val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene)
            val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays)
            val isVisible by collectLastValue(sceneInteractor.isVisible)
            val transitionStateFlow =
                prepareState(
                    authenticationMethod = AuthenticationMethodModel.Pin,
                    isDeviceUnlocked = true,
                    initialSceneKey = Scenes.Gone,
                )
            assertThat(currentDesiredSceneKey).isEqualTo(Scenes.Gone)
            assertThat(currentDesiredOverlays).isEmpty()
            assertThat(isVisible).isTrue()

            underTest.start()
            assertThat(isVisible).isFalse()

            // Expand the notifications shade.
            fakeSceneDataSource.pause()
            sceneInteractor.showOverlay(Overlays.NotificationsShade, "reason")
            transitionStateFlow.value =
                ObservableTransitionState.Transition.ShowOrHideOverlay(
                    overlay = Overlays.NotificationsShade,
                    fromContent = Scenes.Gone,
                    toContent = Overlays.NotificationsShade,
                    currentScene = Scenes.Gone,
                    currentOverlays = flowOf(emptySet()),
                    progress = flowOf(0.5f),
                    isInitiatedByUserInput = false,
                    isUserInputOngoing = flowOf(false),
                    previewProgress = flowOf(0f),
                    isInPreviewStage = flowOf(false),
                )
            assertThat(isVisible).isTrue()
            fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
            transitionStateFlow.value =
                ObservableTransitionState.Idle(
                    currentScene = Scenes.Gone,
                    currentOverlays = setOf(Overlays.NotificationsShade),
                )
            assertThat(isVisible).isTrue()

            // Collapse the notifications shade.
            fakeSceneDataSource.pause()
            sceneInteractor.hideOverlay(Overlays.NotificationsShade, "reason")
            transitionStateFlow.value =
                ObservableTransitionState.Transition.ShowOrHideOverlay(
                    overlay = Overlays.NotificationsShade,
                    fromContent = Overlays.NotificationsShade,
                    toContent = Scenes.Gone,
                    currentScene = Scenes.Gone,
                    currentOverlays = flowOf(setOf(Overlays.NotificationsShade)),
                    progress = flowOf(0.5f),
                    isInitiatedByUserInput = false,
                    isUserInputOngoing = flowOf(false),
                    previewProgress = flowOf(0f),
                    isInPreviewStage = flowOf(false),
                )
            assertThat(isVisible).isTrue()
            fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
            transitionStateFlow.value =
                ObservableTransitionState.Idle(
                    currentScene = Scenes.Gone,
                    currentOverlays = emptySet(),
                )
            assertThat(isVisible).isFalse()

            kosmos.headsUpNotificationRepository.setNotifications(
                buildNotificationRows(isPinned = true)
            )
            assertThat(isVisible).isTrue()

            kosmos.headsUpNotificationRepository.setNotifications(
                buildNotificationRows(isPinned = false)
            )
            assertThat(isVisible).isFalse()
        }

    @Test
    fun hydrateVisibility_basedOnDeviceProvisioning() =
        testScope.runTest {
@@ -1621,6 +1706,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
        }

    @Test
    @DisableFlags(DualShade.FLAG_NAME)
    fun hydrateInteractionState_whileLocked() =
        testScope.runTest {
            val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen)
@@ -1707,6 +1793,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
        }

    @Test
    @DisableFlags(DualShade.FLAG_NAME)
    fun hydrateInteractionState_whileUnlocked() =
        testScope.runTest {
            val transitionStateFlow =
@@ -1794,6 +1881,186 @@ class SceneContainerStartableTest : SysuiTestCase() {
            )
        }

    @Test
    @EnableFlags(DualShade.FLAG_NAME)
    fun hydrateInteractionState_dualShade_whileLocked() =
        testScope.runTest {
            val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays)
            val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen)
            underTest.start()
            runCurrent()
            verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
            assertThat(currentDesiredOverlays).isEmpty()

            clearInvocations(centralSurfaces)
            emulateSceneTransition(
                transitionStateFlow = transitionStateFlow,
                toScene = Scenes.Bouncer,
                verifyBeforeTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyDuringTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyAfterTransition = {
                    verify(centralSurfaces)
                        .setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false)
                },
            )

            clearInvocations(centralSurfaces)
            emulateSceneTransition(
                transitionStateFlow = transitionStateFlow,
                toScene = Scenes.Lockscreen,
                verifyBeforeTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyDuringTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyAfterTransition = {
                    verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
                },
            )

            clearInvocations(centralSurfaces)
            emulateOverlayTransition(
                transitionStateFlow = transitionStateFlow,
                toOverlay = Overlays.NotificationsShade,
                verifyBeforeTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyDuringTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyAfterTransition = {
                    verify(centralSurfaces)
                        .setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false)
                },
            )

            clearInvocations(centralSurfaces)
            emulateSceneTransition(
                transitionStateFlow = transitionStateFlow,
                toScene = Scenes.Lockscreen,
                verifyBeforeTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyDuringTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyAfterTransition = {
                    verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
                },
            )

            clearInvocations(centralSurfaces)
            emulateOverlayTransition(
                transitionStateFlow = transitionStateFlow,
                toOverlay = Overlays.QuickSettingsShade,
                verifyBeforeTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyDuringTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyAfterTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
            )
        }

    @Test
    @EnableFlags(DualShade.FLAG_NAME)
    fun hydrateInteractionState_dualShade_whileUnlocked() =
        testScope.runTest {
            val currentDesiredOverlays by collectLastValue(sceneInteractor.currentOverlays)
            val transitionStateFlow =
                prepareState(
                    authenticationMethod = AuthenticationMethodModel.Pin,
                    isDeviceUnlocked = true,
                    initialSceneKey = Scenes.Gone,
                )
            underTest.start()
            verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
            assertThat(currentDesiredOverlays).isEmpty()

            clearInvocations(centralSurfaces)
            emulateSceneTransition(
                transitionStateFlow = transitionStateFlow,
                toScene = Scenes.Bouncer,
                verifyBeforeTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyDuringTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyAfterTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
            )

            clearInvocations(centralSurfaces)
            emulateSceneTransition(
                transitionStateFlow = transitionStateFlow,
                toScene = Scenes.Lockscreen,
                verifyBeforeTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyDuringTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyAfterTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
            )

            clearInvocations(centralSurfaces)
            emulateSceneTransition(
                transitionStateFlow = transitionStateFlow,
                toScene = Scenes.Shade,
                verifyBeforeTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyDuringTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyAfterTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
            )

            clearInvocations(centralSurfaces)
            emulateSceneTransition(
                transitionStateFlow = transitionStateFlow,
                toScene = Scenes.Lockscreen,
                verifyBeforeTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyDuringTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyAfterTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
            )

            clearInvocations(centralSurfaces)
            emulateSceneTransition(
                transitionStateFlow = transitionStateFlow,
                toScene = Scenes.QuickSettings,
                verifyBeforeTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyDuringTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
                verifyAfterTransition = {
                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
                },
            )
        }

    @Test
    fun respondToFalsingDetections() =
        testScope.runTest {
@@ -2131,19 +2398,40 @@ class SceneContainerStartableTest : SysuiTestCase() {
        verifyAfterTransition: (() -> Unit)? = null,
    ) {
        val fromScene = sceneInteractor.currentScene.value
        val fromOverlays = sceneInteractor.currentOverlays.value
        sceneInteractor.changeScene(toScene, "reason")
        runCurrent()
        verifyBeforeTransition?.invoke()

        transitionStateFlow.value =
            ObservableTransitionState.Transition(
            if (fromOverlays.isEmpty()) {
                // Regular scene-to-scene transition.
                ObservableTransitionState.Transition.ChangeScene(
                    fromScene = fromScene,
                    toScene = toScene,
                    currentScene = flowOf(fromScene),
                    currentOverlays = fromOverlays,
                    progress = flowOf(0.5f),
                    isInitiatedByUserInput = true,
                    isUserInputOngoing = flowOf(true),
                    previewProgress = flowOf(0f),
                    isInPreviewStage = flowOf(false),
                )
            } else {
                // An overlay is present; hide it.
                ObservableTransitionState.Transition.ShowOrHideOverlay(
                    overlay = fromOverlays.first(),
                    fromContent = fromOverlays.first(),
                    toContent = toScene,
                    currentScene = fromScene,
                    currentOverlays = sceneInteractor.currentOverlays,
                    progress = flowOf(0.5f),
                    isInitiatedByUserInput = true,
                    isUserInputOngoing = flowOf(true),
                    previewProgress = flowOf(0f),
                    isInPreviewStage = flowOf(false),
                )
            }
        runCurrent()
        verifyDuringTransition?.invoke()

@@ -2152,6 +2440,60 @@ class SceneContainerStartableTest : SysuiTestCase() {
        verifyAfterTransition?.invoke()
    }

    private fun TestScope.emulateOverlayTransition(
        transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
        toOverlay: OverlayKey,
        verifyBeforeTransition: (() -> Unit)? = null,
        verifyDuringTransition: (() -> Unit)? = null,
        verifyAfterTransition: (() -> Unit)? = null,
    ) {
        val fromScene = sceneInteractor.currentScene.value
        val fromOverlays = sceneInteractor.currentOverlays.value
        sceneInteractor.showOverlay(toOverlay, "reason")
        runCurrent()
        verifyBeforeTransition?.invoke()

        transitionStateFlow.value =
            if (fromOverlays.isEmpty()) {
                // Show a new overlay.
                ObservableTransitionState.Transition.ShowOrHideOverlay(
                    overlay = toOverlay,
                    fromContent = fromScene,
                    toContent = toOverlay,
                    currentScene = fromScene,
                    currentOverlays = sceneInteractor.currentOverlays,
                    progress = flowOf(0.5f),
                    isInitiatedByUserInput = true,
                    isUserInputOngoing = flowOf(true),
                    previewProgress = flowOf(0f),
                    isInPreviewStage = flowOf(false),
                )
            } else {
                // Overlay-to-overlay transition.
                ObservableTransitionState.Transition.ReplaceOverlay(
                    fromOverlay = fromOverlays.first(),
                    toOverlay = toOverlay,
                    currentScene = fromScene,
                    currentOverlays = sceneInteractor.currentOverlays,
                    progress = flowOf(0.5f),
                    isInitiatedByUserInput = true,
                    isUserInputOngoing = flowOf(true),
                    previewProgress = flowOf(0f),
                    isInPreviewStage = flowOf(false),
                )
            }
        runCurrent()
        verifyDuringTransition?.invoke()

        transitionStateFlow.value =
            ObservableTransitionState.Idle(
                currentScene = fromScene,
                currentOverlays = setOf(toOverlay),
            )
        runCurrent()
        verifyAfterTransition?.invoke()
    }

    private fun TestScope.prepareState(
        isDeviceUnlocked: Boolean = false,
        isBypassEnabled: Boolean = false,
+12 −7
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.session.shared.SessionStorage
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -228,8 +229,10 @@ constructor(
                                        is ObservableTransitionState.Idle -> {
                                            if (state.currentScene != Scenes.Gone) {
                                                true to "scene is not Gone"
                                            } else if (state.currentOverlays.isNotEmpty()) {
                                                true to "overlay is shown"
                                            } else {
                                                false to "scene is Gone"
                                                false to "scene is Gone and no overlays are shown"
                                            }
                                        }
                                        is ObservableTransitionState.Transition -> {
@@ -712,19 +715,21 @@ constructor(
                    if (isDeviceLocked) {
                        sceneInteractor.transitionState
                            .mapNotNull { it as? ObservableTransitionState.Idle }
                            .map { it.currentScene }
                            .map { it.currentScene to it.currentOverlays }
                            .distinctUntilChanged()
                            .map { sceneKey ->
                                when (sceneKey) {
                            .map { (sceneKey, currentOverlays) ->
                                when {
                                    // When locked, showing the lockscreen scene should be reported
                                    // as "interacting" while showing other scenes should report as
                                    // "not interacting".
                                    //
                                    // This is done here in order to match the legacy
                                    // implementation. The real reason why is lost to lore and myth.
                                    Scenes.Lockscreen -> true
                                    Scenes.Bouncer -> false
                                    Scenes.Shade -> false
                                    Overlays.NotificationsShade in currentOverlays -> false
                                    Overlays.QuickSettingsShade in currentOverlays -> null
                                    sceneKey == Scenes.Lockscreen -> true
                                    sceneKey == Scenes.Bouncer -> false
                                    sceneKey == Scenes.Shade -> false
                                    else -> null
                                }
                            }