diff options
2 files changed, 362 insertions, 15 deletions
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 763a1a943bf8..385089122fc4 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 @@ -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) @@ -221,6 +225,87 @@ class SceneContainerStartableTest : SysuiTestCase() { } @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 { val isVisible by collectLastValue(sceneInteractor.isVisible) @@ -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 = @@ -1795,6 +1882,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 { val currentScene by collectLastValue(sceneInteractor.currentScene) @@ -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( - fromScene = fromScene, - toScene = toScene, - currentScene = flowOf(fromScene), - progress = flowOf(0.5f), - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(true), - ) + 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, 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 e11ffccb0be3..b7e2cf23e3a8 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 @@ -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 } } |