Loading packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt +6 −10 Original line number Diff line number Diff line Loading @@ -48,17 +48,13 @@ import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.UnsquishingQS import com.android.systemui.scene.shared.model.Scenes object QuickSettings { private val SCENES = setOf( Scenes.QuickSettings, Scenes.Shade, ) private val SCENES = setOf(Scenes.QuickSettings, Scenes.Shade) object Elements { val Content = MovableElementKey( "QuickSettingsContent", contentPicker = MovableElementContentPicker(SCENES) contentPicker = MovableElementContentPicker(SCENES), ) val QuickQuickSettings = ElementKey("QuickQuickSettings") val SplitShadeQuickSettings = ElementKey("SplitShadeQuickSettings") Loading Loading @@ -87,7 +83,7 @@ object QuickSettings { private fun SceneScope.stateForQuickSettingsContent( isSplitShade: Boolean, squishiness: () -> Float = { QuickSettings.SharedValues.SquishinessValues.Default } squishiness: () -> Float = { QuickSettings.SharedValues.SquishinessValues.Default }, ): QSSceneAdapter.State { return when (val transitionState = layoutState.transitionState) { is TransitionState.Idle -> { Loading Loading @@ -122,7 +118,7 @@ private fun SceneScope.stateForQuickSettingsContent( } } is TransitionState.Transition.OverlayTransition -> TODO("b/359173565: Handle overlay transitions") error("Bad transition for QuickSettings scene: overlays not supported") } } Loading Loading @@ -172,7 +168,7 @@ fun SceneScope.QuickSettings( val height = heightProvider().coerceAtLeast(0) layout(placeable.width, height) { placeable.placeRelative(0, 0) } } }, ) { content { QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, contentState) } } Loading Loading @@ -225,7 +221,7 @@ private fun QuickSettingsContent( it.addView(view) } }, onRelease = { it.removeAllViews() } onRelease = { it.removeAllViews() }, ) } } Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt +137 −15 Original line number Diff line number Diff line Loading @@ -18,9 +18,12 @@ package com.android.systemui.scene.domain.interactor import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags 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.ObservableTransitionState.Transition.ShowOrHideOverlay import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository Loading @@ -29,8 +32,10 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.sceneDataSource import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock Loading Loading @@ -63,10 +68,11 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { private val sceneDataSource = kosmos.sceneDataSource.apply { changeScene(toScene = Scenes.Lockscreen) } private val underTest = kosmos.sceneContainerOcclusionInteractor private val underTest by lazy { kosmos.sceneContainerOcclusionInteractor } @Test fun invisibleDueToOcclusion() = @DisableFlags(DualShade.FLAG_NAME) fun invisibleDueToOcclusion_dualShadeDisabled() = testScope.runTest { val invisibleDueToOcclusion by collectLastValue(underTest.invisibleDueToOcclusion) val keyguardState by collectLastValue(keyguardTransitionInteractor.currentKeyguardState) Loading Loading @@ -126,6 +132,68 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { .isFalse() } @Test @EnableFlags(DualShade.FLAG_NAME) fun invisibleDueToOcclusion_dualShadeEnabled() = testScope.runTest { val invisibleDueToOcclusion by collectLastValue(underTest.invisibleDueToOcclusion) val keyguardState by collectLastValue(keyguardTransitionInteractor.currentKeyguardState) // Assert that we have the desired preconditions: assertThat(keyguardState).isEqualTo(KeyguardState.LOCKSCREEN) assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen) assertThat(sceneInteractor.transitionState.value) .isEqualTo(ObservableTransitionState.Idle(Scenes.Lockscreen)) assertWithMessage("Should start unoccluded").that(invisibleDueToOcclusion).isFalse() // Actual testing starts here: showOccludingActivity() assertWithMessage("Should become occluded when occluding activity is shown") .that(invisibleDueToOcclusion) .isTrue() transitionIntoAod { assertWithMessage("Should become unoccluded when transitioning into AOD") .that(invisibleDueToOcclusion) .isFalse() } assertWithMessage("Should stay unoccluded when in AOD") .that(invisibleDueToOcclusion) .isFalse() transitionOutOfAod { assertWithMessage("Should remain unoccluded while transitioning away from AOD") .that(invisibleDueToOcclusion) .isFalse() } assertWithMessage("Should become occluded now that no longer in AOD") .that(invisibleDueToOcclusion) .isTrue() expandDualShade { assertWithMessage("Should become unoccluded once shade begins to expand") .that(invisibleDueToOcclusion) .isFalse() } assertWithMessage("Should be unoccluded when shade is fully expanded") .that(invisibleDueToOcclusion) .isFalse() collapseDualShade { assertWithMessage("Should remain unoccluded while shade is collapsing") .that(invisibleDueToOcclusion) .isFalse() } assertWithMessage("Should become occluded now that shade is fully collapsed") .that(invisibleDueToOcclusion) .isTrue() hideOccludingActivity() assertWithMessage("Should become unoccluded once the occluding activity is hidden") .that(invisibleDueToOcclusion) .isFalse() } /** Simulates the appearance of a show-when-locked `Activity` in the foreground. */ private fun TestScope.showOccludingActivity() { keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( Loading @@ -138,15 +206,13 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { /** Simulates the disappearance of a show-when-locked `Activity` from the foreground. */ private fun TestScope.hideOccludingActivity() { keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( showWhenLockedActivityOnTop = false, showWhenLockedActivityOnTop = false ) runCurrent() } /** Simulates a user-driven gradual expansion of the shade. */ private fun TestScope.expandShade( assertMidTransition: () -> Unit = {}, ) { private fun TestScope.expandShade(assertMidTransition: () -> Unit = {}) { val progress = MutableStateFlow(0f) mutableTransitionState.value = ObservableTransitionState.Transition( Loading @@ -170,10 +236,41 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { runCurrent() } /** Simulates a user-driven gradual expansion of the dual shade (notifications). */ private fun TestScope.expandDualShade(assertMidTransition: () -> Unit = {}) { val progress = MutableStateFlow(0f) mutableTransitionState.value = ShowOrHideOverlay( overlay = Overlays.NotificationsShade, fromContent = sceneDataSource.currentScene.value, toContent = Overlays.NotificationsShade, currentScene = sceneDataSource.currentScene.value, currentOverlays = sceneDataSource.currentOverlays, progress = progress, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), previewProgress = flowOf(0f), isInPreviewStage = flowOf(false), ) runCurrent() progress.value = 0.5f runCurrent() assertMidTransition() progress.value = 1f runCurrent() mutableTransitionState.value = ObservableTransitionState.Idle( sceneDataSource.currentScene.value, setOf(Overlays.NotificationsShade), ) runCurrent() } /** Simulates a user-driven gradual collapse of the shade. */ private fun TestScope.collapseShade( assertMidTransition: () -> Unit = {}, ) { private fun TestScope.collapseShade(assertMidTransition: () -> Unit = {}) { val progress = MutableStateFlow(0f) mutableTransitionState.value = ObservableTransitionState.Transition( Loading @@ -197,10 +294,37 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { runCurrent() } /** Simulates a user-driven gradual collapse of the dual shade (notifications). */ private fun TestScope.collapseDualShade(assertMidTransition: () -> Unit = {}) { val progress = MutableStateFlow(0f) mutableTransitionState.value = ShowOrHideOverlay( overlay = Overlays.NotificationsShade, fromContent = Overlays.NotificationsShade, toContent = Scenes.Lockscreen, currentScene = Scenes.Lockscreen, currentOverlays = flowOf(setOf(Overlays.NotificationsShade)), progress = progress, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), previewProgress = flowOf(0f), isInPreviewStage = flowOf(false), ) runCurrent() progress.value = 0.5f runCurrent() assertMidTransition() progress.value = 1f runCurrent() mutableTransitionState.value = ObservableTransitionState.Idle(Scenes.Lockscreen) runCurrent() } /** Simulates a transition into AOD. */ private suspend fun TestScope.transitionIntoAod( assertMidTransition: () -> Unit = {}, ) { private suspend fun TestScope.transitionIntoAod(assertMidTransition: () -> Unit = {}) { val currentKeyguardState = keyguardTransitionInteractor.getCurrentState() keyguardTransitionRepository.sendTransitionStep( TransitionStep( Loading Loading @@ -235,9 +359,7 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { } /** Simulates a transition away from AOD. */ private suspend fun TestScope.transitionOutOfAod( assertMidTransition: () -> Unit = {}, ) { private suspend fun TestScope.transitionOutOfAod(assertMidTransition: () -> Unit = {}) { keyguardTransitionRepository.sendTransitionStep( TransitionStep( from = KeyguardState.AOD, Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +48 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ package com.android.systemui.scene.domain.interactor 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.ObservableTransitionState.Transition.ShowOrHideOverlay import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue Loading @@ -38,6 +39,7 @@ import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver import com.android.systemui.scene.overlayKeys import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.sceneKeys import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.SceneFamilies import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource Loading Loading @@ -256,7 +258,7 @@ class SceneInteractorTest : SysuiTestCase() { } @Test fun transitioningTo() = fun transitioningTo_sceneChange() = testScope.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( Loading Loading @@ -292,6 +294,51 @@ class SceneInteractorTest : SysuiTestCase() { assertThat(transitionTo).isNull() } @Test fun transitioningTo_overlayChange() = testScope.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(underTest.currentScene.value) ) underTest.setTransitionState(transitionState) val transitionTo by collectLastValue(underTest.transitioningTo) assertThat(transitionTo).isNull() underTest.showOverlay(Overlays.NotificationsShade, "reason") assertThat(transitionTo).isNull() val progress = MutableStateFlow(0f) transitionState.value = ShowOrHideOverlay( overlay = Overlays.NotificationsShade, fromContent = underTest.currentScene.value, toContent = Overlays.NotificationsShade, currentScene = underTest.currentScene.value, currentOverlays = underTest.currentOverlays, progress = progress, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), previewProgress = flowOf(0f), isInPreviewStage = flowOf(false), ) assertThat(transitionTo).isEqualTo(Overlays.NotificationsShade) progress.value = 0.5f assertThat(transitionTo).isEqualTo(Overlays.NotificationsShade) progress.value = 1f assertThat(transitionTo).isEqualTo(Overlays.NotificationsShade) transitionState.value = ObservableTransitionState.Idle( currentScene = underTest.currentScene.value, currentOverlays = setOf(Overlays.NotificationsShade), ) assertThat(transitionTo).isNull() } @Test fun isTransitionUserInputOngoing_idle_false() = testScope.runTest { Loading packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt +128 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,8 @@ package com.android.systemui.shade.domain.interactor 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.ObservableTransitionState.Transition.ShowOrHideOverlay import com.android.compose.animation.scene.OverlayKey import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue Loading @@ -32,6 +34,7 @@ import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintA import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope 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.testKosmos Loading Loading @@ -123,6 +126,63 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() { assertThat(panelExpansion).isEqualTo(1f) } @Test @EnableSceneContainer fun legacyPanelExpansion_dualShade_whenIdle_whenLocked() = testScope.runTest { underTest = kosmos.panelExpansionInteractorImpl val panelExpansion by collectLastValue(underTest.legacyPanelExpansion) changeScene(Scenes.Lockscreen) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) changeScene(Scenes.Bouncer) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) showOverlay(Overlays.NotificationsShade) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) showOverlay(Overlays.QuickSettingsShade) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) } @Test @EnableSceneContainer fun legacyPanelExpansion_dualShade_whenIdle_whenUnlocked() = testScope.runTest { underTest = kosmos.panelExpansionInteractorImpl val unlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( SuccessFingerprintAuthenticationStatus(0, true) ) runCurrent() assertThat(unlockStatus) .isEqualTo(DeviceUnlockStatus(true, DeviceUnlockSource.Fingerprint)) val panelExpansion by collectLastValue(underTest.legacyPanelExpansion) changeScene(Scenes.Gone) { assertThat(panelExpansion).isEqualTo(0f) } assertThat(panelExpansion).isEqualTo(0f) showOverlay(Overlays.NotificationsShade) { progress -> assertThat(panelExpansion).isEqualTo(progress) } assertThat(panelExpansion).isEqualTo(1f) showOverlay(Overlays.QuickSettingsShade) { // Notification shade is already expanded, so moving to QS shade should also be 1f. assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) } @Test @EnableSceneContainer fun shouldHideStatusBarIconsWhenExpanded_goneScene() = Loading Loading @@ -193,4 +253,72 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() { assertThat(currentScene).isEqualTo(toScene) } private fun TestScope.showOverlay( toOverlay: OverlayKey, assertDuringProgress: ((progress: Float) -> Unit) = {}, ) { val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val progressFlow = MutableStateFlow(0f) transitionState.value = if (checkNotNull(currentOverlays).isEmpty()) { ShowOrHideOverlay( overlay = toOverlay, fromContent = checkNotNull(currentScene), toContent = toOverlay, currentScene = checkNotNull(currentScene), currentOverlays = flowOf(emptySet()), progress = progressFlow, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), previewProgress = flowOf(0f), isInPreviewStage = flowOf(false), ) } else { ObservableTransitionState.Transition.ReplaceOverlay( fromOverlay = checkNotNull(currentOverlays).first(), toOverlay = toOverlay, currentScene = checkNotNull(currentScene), currentOverlays = flowOf(emptySet()), progress = progressFlow, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), previewProgress = flowOf(0f), isInPreviewStage = flowOf(false), ) } runCurrent() assertDuringProgress(progressFlow.value) progressFlow.value = 0.2f runCurrent() assertDuringProgress(progressFlow.value) progressFlow.value = 0.6f runCurrent() assertDuringProgress(progressFlow.value) progressFlow.value = 1f runCurrent() assertDuringProgress(progressFlow.value) transitionState.value = ObservableTransitionState.Idle( currentScene = checkNotNull(currentScene), currentOverlays = setOf(toOverlay), ) if (checkNotNull(currentOverlays).isEmpty()) { fakeSceneDataSource.showOverlay(toOverlay) } else { fakeSceneDataSource.replaceOverlay( from = checkNotNull(currentOverlays).first(), to = toOverlay, ) } runCurrent() assertDuringProgress(progressFlow.value) assertThat(currentOverlays).containsExactly(toOverlay) } } packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt +13 −10 Original line number Diff line number Diff line Loading @@ -16,13 +16,14 @@ package com.android.systemui.scene.domain.interactor import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.CoroutineScope Loading Loading @@ -117,28 +118,30 @@ constructor( private val ObservableTransitionState.canBeOccluded: Boolean get() = when (this) { is ObservableTransitionState.Idle -> currentScene.canBeOccluded is ObservableTransitionState.Transition.ChangeScene -> fromScene.canBeOccluded && toScene.canBeOccluded is ObservableTransitionState.Transition.ReplaceOverlay, is ObservableTransitionState.Transition.ShowOrHideOverlay -> TODO("b/359173565: Handle overlay transitions") is ObservableTransitionState.Idle -> currentOverlays.all { it.canBeOccluded } && currentScene.canBeOccluded is ObservableTransitionState.Transition -> // TODO(b/356596436): Should also verify currentOverlays.isEmpty(), but // currentOverlays is a Flow and we need a state. fromContent.canBeOccluded && toContent.canBeOccluded } /** * Whether the scene can be occluded by a "show when locked" activity. Some scenes should, on * Whether the content can be occluded by a "show when locked" activity. Some content should, on * principle not be occlude-able because they render as if they are expanding on top of the * occluding activity. */ private val SceneKey.canBeOccluded: Boolean private val ContentKey.canBeOccluded: Boolean get() = when (this) { Overlays.NotificationsShade -> false Overlays.QuickSettingsShade -> false Scenes.Bouncer -> false Scenes.Communal -> true Scenes.Gone -> true Scenes.Lockscreen -> true Scenes.QuickSettings -> false Scenes.Shade -> false else -> error("SceneKey \"$this\" doesn't have a mapping for canBeOccluded!") else -> error("ContentKey \"$this\" doesn't have a mapping for canBeOccluded!") } } Loading
packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt +6 −10 Original line number Diff line number Diff line Loading @@ -48,17 +48,13 @@ import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.UnsquishingQS import com.android.systemui.scene.shared.model.Scenes object QuickSettings { private val SCENES = setOf( Scenes.QuickSettings, Scenes.Shade, ) private val SCENES = setOf(Scenes.QuickSettings, Scenes.Shade) object Elements { val Content = MovableElementKey( "QuickSettingsContent", contentPicker = MovableElementContentPicker(SCENES) contentPicker = MovableElementContentPicker(SCENES), ) val QuickQuickSettings = ElementKey("QuickQuickSettings") val SplitShadeQuickSettings = ElementKey("SplitShadeQuickSettings") Loading Loading @@ -87,7 +83,7 @@ object QuickSettings { private fun SceneScope.stateForQuickSettingsContent( isSplitShade: Boolean, squishiness: () -> Float = { QuickSettings.SharedValues.SquishinessValues.Default } squishiness: () -> Float = { QuickSettings.SharedValues.SquishinessValues.Default }, ): QSSceneAdapter.State { return when (val transitionState = layoutState.transitionState) { is TransitionState.Idle -> { Loading Loading @@ -122,7 +118,7 @@ private fun SceneScope.stateForQuickSettingsContent( } } is TransitionState.Transition.OverlayTransition -> TODO("b/359173565: Handle overlay transitions") error("Bad transition for QuickSettings scene: overlays not supported") } } Loading Loading @@ -172,7 +168,7 @@ fun SceneScope.QuickSettings( val height = heightProvider().coerceAtLeast(0) layout(placeable.width, height) { placeable.placeRelative(0, 0) } } }, ) { content { QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, contentState) } } Loading Loading @@ -225,7 +221,7 @@ private fun QuickSettingsContent( it.addView(view) } }, onRelease = { it.removeAllViews() } onRelease = { it.removeAllViews() }, ) } } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt +137 −15 Original line number Diff line number Diff line Loading @@ -18,9 +18,12 @@ package com.android.systemui.scene.domain.interactor import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags 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.ObservableTransitionState.Transition.ShowOrHideOverlay import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository Loading @@ -29,8 +32,10 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.sceneDataSource import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock Loading Loading @@ -63,10 +68,11 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { private val sceneDataSource = kosmos.sceneDataSource.apply { changeScene(toScene = Scenes.Lockscreen) } private val underTest = kosmos.sceneContainerOcclusionInteractor private val underTest by lazy { kosmos.sceneContainerOcclusionInteractor } @Test fun invisibleDueToOcclusion() = @DisableFlags(DualShade.FLAG_NAME) fun invisibleDueToOcclusion_dualShadeDisabled() = testScope.runTest { val invisibleDueToOcclusion by collectLastValue(underTest.invisibleDueToOcclusion) val keyguardState by collectLastValue(keyguardTransitionInteractor.currentKeyguardState) Loading Loading @@ -126,6 +132,68 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { .isFalse() } @Test @EnableFlags(DualShade.FLAG_NAME) fun invisibleDueToOcclusion_dualShadeEnabled() = testScope.runTest { val invisibleDueToOcclusion by collectLastValue(underTest.invisibleDueToOcclusion) val keyguardState by collectLastValue(keyguardTransitionInteractor.currentKeyguardState) // Assert that we have the desired preconditions: assertThat(keyguardState).isEqualTo(KeyguardState.LOCKSCREEN) assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen) assertThat(sceneInteractor.transitionState.value) .isEqualTo(ObservableTransitionState.Idle(Scenes.Lockscreen)) assertWithMessage("Should start unoccluded").that(invisibleDueToOcclusion).isFalse() // Actual testing starts here: showOccludingActivity() assertWithMessage("Should become occluded when occluding activity is shown") .that(invisibleDueToOcclusion) .isTrue() transitionIntoAod { assertWithMessage("Should become unoccluded when transitioning into AOD") .that(invisibleDueToOcclusion) .isFalse() } assertWithMessage("Should stay unoccluded when in AOD") .that(invisibleDueToOcclusion) .isFalse() transitionOutOfAod { assertWithMessage("Should remain unoccluded while transitioning away from AOD") .that(invisibleDueToOcclusion) .isFalse() } assertWithMessage("Should become occluded now that no longer in AOD") .that(invisibleDueToOcclusion) .isTrue() expandDualShade { assertWithMessage("Should become unoccluded once shade begins to expand") .that(invisibleDueToOcclusion) .isFalse() } assertWithMessage("Should be unoccluded when shade is fully expanded") .that(invisibleDueToOcclusion) .isFalse() collapseDualShade { assertWithMessage("Should remain unoccluded while shade is collapsing") .that(invisibleDueToOcclusion) .isFalse() } assertWithMessage("Should become occluded now that shade is fully collapsed") .that(invisibleDueToOcclusion) .isTrue() hideOccludingActivity() assertWithMessage("Should become unoccluded once the occluding activity is hidden") .that(invisibleDueToOcclusion) .isFalse() } /** Simulates the appearance of a show-when-locked `Activity` in the foreground. */ private fun TestScope.showOccludingActivity() { keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( Loading @@ -138,15 +206,13 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { /** Simulates the disappearance of a show-when-locked `Activity` from the foreground. */ private fun TestScope.hideOccludingActivity() { keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( showWhenLockedActivityOnTop = false, showWhenLockedActivityOnTop = false ) runCurrent() } /** Simulates a user-driven gradual expansion of the shade. */ private fun TestScope.expandShade( assertMidTransition: () -> Unit = {}, ) { private fun TestScope.expandShade(assertMidTransition: () -> Unit = {}) { val progress = MutableStateFlow(0f) mutableTransitionState.value = ObservableTransitionState.Transition( Loading @@ -170,10 +236,41 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { runCurrent() } /** Simulates a user-driven gradual expansion of the dual shade (notifications). */ private fun TestScope.expandDualShade(assertMidTransition: () -> Unit = {}) { val progress = MutableStateFlow(0f) mutableTransitionState.value = ShowOrHideOverlay( overlay = Overlays.NotificationsShade, fromContent = sceneDataSource.currentScene.value, toContent = Overlays.NotificationsShade, currentScene = sceneDataSource.currentScene.value, currentOverlays = sceneDataSource.currentOverlays, progress = progress, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), previewProgress = flowOf(0f), isInPreviewStage = flowOf(false), ) runCurrent() progress.value = 0.5f runCurrent() assertMidTransition() progress.value = 1f runCurrent() mutableTransitionState.value = ObservableTransitionState.Idle( sceneDataSource.currentScene.value, setOf(Overlays.NotificationsShade), ) runCurrent() } /** Simulates a user-driven gradual collapse of the shade. */ private fun TestScope.collapseShade( assertMidTransition: () -> Unit = {}, ) { private fun TestScope.collapseShade(assertMidTransition: () -> Unit = {}) { val progress = MutableStateFlow(0f) mutableTransitionState.value = ObservableTransitionState.Transition( Loading @@ -197,10 +294,37 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { runCurrent() } /** Simulates a user-driven gradual collapse of the dual shade (notifications). */ private fun TestScope.collapseDualShade(assertMidTransition: () -> Unit = {}) { val progress = MutableStateFlow(0f) mutableTransitionState.value = ShowOrHideOverlay( overlay = Overlays.NotificationsShade, fromContent = Overlays.NotificationsShade, toContent = Scenes.Lockscreen, currentScene = Scenes.Lockscreen, currentOverlays = flowOf(setOf(Overlays.NotificationsShade)), progress = progress, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), previewProgress = flowOf(0f), isInPreviewStage = flowOf(false), ) runCurrent() progress.value = 0.5f runCurrent() assertMidTransition() progress.value = 1f runCurrent() mutableTransitionState.value = ObservableTransitionState.Idle(Scenes.Lockscreen) runCurrent() } /** Simulates a transition into AOD. */ private suspend fun TestScope.transitionIntoAod( assertMidTransition: () -> Unit = {}, ) { private suspend fun TestScope.transitionIntoAod(assertMidTransition: () -> Unit = {}) { val currentKeyguardState = keyguardTransitionInteractor.getCurrentState() keyguardTransitionRepository.sendTransitionStep( TransitionStep( Loading Loading @@ -235,9 +359,7 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { } /** Simulates a transition away from AOD. */ private suspend fun TestScope.transitionOutOfAod( assertMidTransition: () -> Unit = {}, ) { private suspend fun TestScope.transitionOutOfAod(assertMidTransition: () -> Unit = {}) { keyguardTransitionRepository.sendTransitionStep( TransitionStep( from = KeyguardState.AOD, Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +48 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ package com.android.systemui.scene.domain.interactor 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.ObservableTransitionState.Transition.ShowOrHideOverlay import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue Loading @@ -38,6 +39,7 @@ import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver import com.android.systemui.scene.overlayKeys import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.sceneKeys import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.SceneFamilies import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource Loading Loading @@ -256,7 +258,7 @@ class SceneInteractorTest : SysuiTestCase() { } @Test fun transitioningTo() = fun transitioningTo_sceneChange() = testScope.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( Loading Loading @@ -292,6 +294,51 @@ class SceneInteractorTest : SysuiTestCase() { assertThat(transitionTo).isNull() } @Test fun transitioningTo_overlayChange() = testScope.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(underTest.currentScene.value) ) underTest.setTransitionState(transitionState) val transitionTo by collectLastValue(underTest.transitioningTo) assertThat(transitionTo).isNull() underTest.showOverlay(Overlays.NotificationsShade, "reason") assertThat(transitionTo).isNull() val progress = MutableStateFlow(0f) transitionState.value = ShowOrHideOverlay( overlay = Overlays.NotificationsShade, fromContent = underTest.currentScene.value, toContent = Overlays.NotificationsShade, currentScene = underTest.currentScene.value, currentOverlays = underTest.currentOverlays, progress = progress, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), previewProgress = flowOf(0f), isInPreviewStage = flowOf(false), ) assertThat(transitionTo).isEqualTo(Overlays.NotificationsShade) progress.value = 0.5f assertThat(transitionTo).isEqualTo(Overlays.NotificationsShade) progress.value = 1f assertThat(transitionTo).isEqualTo(Overlays.NotificationsShade) transitionState.value = ObservableTransitionState.Idle( currentScene = underTest.currentScene.value, currentOverlays = setOf(Overlays.NotificationsShade), ) assertThat(transitionTo).isNull() } @Test fun isTransitionUserInputOngoing_idle_false() = testScope.runTest { Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt +128 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,8 @@ package com.android.systemui.shade.domain.interactor 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.ObservableTransitionState.Transition.ShowOrHideOverlay import com.android.compose.animation.scene.OverlayKey import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue Loading @@ -32,6 +34,7 @@ import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintA import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope 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.testKosmos Loading Loading @@ -123,6 +126,63 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() { assertThat(panelExpansion).isEqualTo(1f) } @Test @EnableSceneContainer fun legacyPanelExpansion_dualShade_whenIdle_whenLocked() = testScope.runTest { underTest = kosmos.panelExpansionInteractorImpl val panelExpansion by collectLastValue(underTest.legacyPanelExpansion) changeScene(Scenes.Lockscreen) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) changeScene(Scenes.Bouncer) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) showOverlay(Overlays.NotificationsShade) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) showOverlay(Overlays.QuickSettingsShade) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) } @Test @EnableSceneContainer fun legacyPanelExpansion_dualShade_whenIdle_whenUnlocked() = testScope.runTest { underTest = kosmos.panelExpansionInteractorImpl val unlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( SuccessFingerprintAuthenticationStatus(0, true) ) runCurrent() assertThat(unlockStatus) .isEqualTo(DeviceUnlockStatus(true, DeviceUnlockSource.Fingerprint)) val panelExpansion by collectLastValue(underTest.legacyPanelExpansion) changeScene(Scenes.Gone) { assertThat(panelExpansion).isEqualTo(0f) } assertThat(panelExpansion).isEqualTo(0f) showOverlay(Overlays.NotificationsShade) { progress -> assertThat(panelExpansion).isEqualTo(progress) } assertThat(panelExpansion).isEqualTo(1f) showOverlay(Overlays.QuickSettingsShade) { // Notification shade is already expanded, so moving to QS shade should also be 1f. assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) } assertThat(panelExpansion).isEqualTo(1f) } @Test @EnableSceneContainer fun shouldHideStatusBarIconsWhenExpanded_goneScene() = Loading Loading @@ -193,4 +253,72 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() { assertThat(currentScene).isEqualTo(toScene) } private fun TestScope.showOverlay( toOverlay: OverlayKey, assertDuringProgress: ((progress: Float) -> Unit) = {}, ) { val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val progressFlow = MutableStateFlow(0f) transitionState.value = if (checkNotNull(currentOverlays).isEmpty()) { ShowOrHideOverlay( overlay = toOverlay, fromContent = checkNotNull(currentScene), toContent = toOverlay, currentScene = checkNotNull(currentScene), currentOverlays = flowOf(emptySet()), progress = progressFlow, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), previewProgress = flowOf(0f), isInPreviewStage = flowOf(false), ) } else { ObservableTransitionState.Transition.ReplaceOverlay( fromOverlay = checkNotNull(currentOverlays).first(), toOverlay = toOverlay, currentScene = checkNotNull(currentScene), currentOverlays = flowOf(emptySet()), progress = progressFlow, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), previewProgress = flowOf(0f), isInPreviewStage = flowOf(false), ) } runCurrent() assertDuringProgress(progressFlow.value) progressFlow.value = 0.2f runCurrent() assertDuringProgress(progressFlow.value) progressFlow.value = 0.6f runCurrent() assertDuringProgress(progressFlow.value) progressFlow.value = 1f runCurrent() assertDuringProgress(progressFlow.value) transitionState.value = ObservableTransitionState.Idle( currentScene = checkNotNull(currentScene), currentOverlays = setOf(toOverlay), ) if (checkNotNull(currentOverlays).isEmpty()) { fakeSceneDataSource.showOverlay(toOverlay) } else { fakeSceneDataSource.replaceOverlay( from = checkNotNull(currentOverlays).first(), to = toOverlay, ) } runCurrent() assertDuringProgress(progressFlow.value) assertThat(currentOverlays).containsExactly(toOverlay) } }
packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt +13 −10 Original line number Diff line number Diff line Loading @@ -16,13 +16,14 @@ package com.android.systemui.scene.domain.interactor import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.CoroutineScope Loading Loading @@ -117,28 +118,30 @@ constructor( private val ObservableTransitionState.canBeOccluded: Boolean get() = when (this) { is ObservableTransitionState.Idle -> currentScene.canBeOccluded is ObservableTransitionState.Transition.ChangeScene -> fromScene.canBeOccluded && toScene.canBeOccluded is ObservableTransitionState.Transition.ReplaceOverlay, is ObservableTransitionState.Transition.ShowOrHideOverlay -> TODO("b/359173565: Handle overlay transitions") is ObservableTransitionState.Idle -> currentOverlays.all { it.canBeOccluded } && currentScene.canBeOccluded is ObservableTransitionState.Transition -> // TODO(b/356596436): Should also verify currentOverlays.isEmpty(), but // currentOverlays is a Flow and we need a state. fromContent.canBeOccluded && toContent.canBeOccluded } /** * Whether the scene can be occluded by a "show when locked" activity. Some scenes should, on * Whether the content can be occluded by a "show when locked" activity. Some content should, on * principle not be occlude-able because they render as if they are expanding on top of the * occluding activity. */ private val SceneKey.canBeOccluded: Boolean private val ContentKey.canBeOccluded: Boolean get() = when (this) { Overlays.NotificationsShade -> false Overlays.QuickSettingsShade -> false Scenes.Bouncer -> false Scenes.Communal -> true Scenes.Gone -> true Scenes.Lockscreen -> true Scenes.QuickSettings -> false Scenes.Shade -> false else -> error("SceneKey \"$this\" doesn't have a mapping for canBeOccluded!") else -> error("ContentKey \"$this\" doesn't have a mapping for canBeOccluded!") } }