Loading packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt +6 −0 Original line number Diff line number Diff line Loading @@ -62,4 +62,10 @@ class SceneTransitionLayoutDataSource( coroutineScope = coroutineScope, ) } override fun snapToScene(toScene: SceneKey) { state.snapToScene( scene = toScene, ) } } packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +10 −0 Original line number Diff line number Diff line Loading @@ -70,6 +70,9 @@ class SceneContainerRepositoryTest : SysuiTestCase() { underTest.changeScene(Scenes.Shade) assertThat(currentScene).isEqualTo(Scenes.Shade) underTest.snapToScene(Scenes.QuickSettings) assertThat(currentScene).isEqualTo(Scenes.QuickSettings) } @Test(expected = IllegalStateException::class) Loading @@ -79,6 +82,13 @@ class SceneContainerRepositoryTest : SysuiTestCase() { underTest.changeScene(Scenes.Shade) } @Test(expected = IllegalStateException::class) fun snapToScene_noSuchSceneInContainer_throws() { kosmos.sceneKeys = listOf(Scenes.QuickSettings, Scenes.Lockscreen) val underTest = kosmos.sceneContainerRepository underTest.snapToScene(Scenes.Shade) } @Test fun isVisible() = testScope.runTest { Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +65 −0 Original line number Diff line number Diff line Loading @@ -125,6 +125,71 @@ class SceneInteractorTest : SysuiTestCase() { underTest.changeScene(Scenes.Gone, "reason") } @Test fun snapToScene_toUnknownScene_doesNothing() = testScope.runTest { val sceneKeys = listOf( Scenes.QuickSettings, Scenes.Shade, Scenes.Lockscreen, Scenes.Gone, Scenes.Communal, ) val navigationDistances = mapOf( Scenes.Gone to 0, Scenes.Lockscreen to 0, Scenes.Communal to 1, Scenes.Shade to 2, Scenes.QuickSettings to 3, ) kosmos.sceneContainerConfig = SceneContainerConfig(sceneKeys, Scenes.Lockscreen, navigationDistances) underTest = kosmos.sceneInteractor val currentScene by collectLastValue(underTest.currentScene) val previousScene = currentScene assertThat(previousScene).isNotEqualTo(Scenes.Bouncer) underTest.snapToScene(Scenes.Bouncer, "reason") assertThat(currentScene).isEqualTo(previousScene) } @Test fun snapToScene() = testScope.runTest { underTest = kosmos.sceneInteractor val currentScene by collectLastValue(underTest.currentScene) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) underTest.snapToScene(Scenes.Shade, "reason") assertThat(currentScene).isEqualTo(Scenes.Shade) } @Test fun snapToScene_toGoneWhenUnl_doesNotThrow() = testScope.runTest { underTest = kosmos.sceneInteractor val currentScene by collectLastValue(underTest.currentScene) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( SuccessFingerprintAuthenticationStatus(0, true) ) runCurrent() underTest.snapToScene(Scenes.Gone, "reason") assertThat(currentScene).isEqualTo(Scenes.Gone) } @Test(expected = IllegalStateException::class) fun snapToScene_toGoneWhenStillLocked_throws() = testScope.runTest { underTest = kosmos.sceneInteractor underTest.snapToScene(Scenes.Gone, "reason") } @Test fun sceneChanged_inDataSource() = testScope.runTest { Loading packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +8 −0 Original line number Diff line number Diff line Loading @@ -87,6 +87,14 @@ constructor( ) } fun snapToScene( toScene: SceneKey, ) { dataSource.snapToScene( toScene = toScene, ) } /** Sets whether the container is visible. */ fun setVisible(isVisible: Boolean) { _isVisible.value = isVisible Loading packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +64 −9 Original line number Diff line number Diff line Loading @@ -162,19 +162,45 @@ constructor( loggingReason: String, transitionKey: TransitionKey? = null, ) { if (!repository.allSceneKeys().contains(toScene)) { val currentSceneKey = currentScene.value if ( !validateSceneChange( from = currentSceneKey, to = toScene, loggingReason = loggingReason, ) ) { return } check( toScene != Scenes.Gone || deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked ) { "Cannot change to the Gone scene while the device is locked. Logging reason for scene" + " change was: $loggingReason" logger.logSceneChangeRequested( from = currentSceneKey, to = toScene, reason = loggingReason, isInstant = false, ) repository.changeScene(toScene, transitionKey) } /** * Requests a scene change to the given scene. * * The change is instantaneous and not animated; it will be observable in the next frame and * there will be no transition animation. */ fun snapToScene( toScene: SceneKey, loggingReason: String, ) { val currentSceneKey = currentScene.value if (currentSceneKey == toScene) { if ( !validateSceneChange( from = currentSceneKey, to = toScene, loggingReason = loggingReason, ) ) { return } Loading @@ -182,9 +208,10 @@ constructor( from = currentSceneKey, to = toScene, reason = loggingReason, isInstant = true, ) repository.changeScene(toScene, transitionKey) repository.snapToScene(toScene) } /** Loading Loading @@ -249,4 +276,32 @@ constructor( ): Boolean { return raw || isRemoteUserInteractionOngoing } /** * Validates that the given scene change is allowed. * * Will throw a runtime exception for illegal states (for example, attempting to change to a * scene that's not part of the current scene framework configuration). * * @param from The current scene being transitioned away from * @param to The desired destination scene to transition to * @param loggingReason The reason why the transition is requested, for logging purposes * @return `true` if the scene change is valid; `false` if it shouldn't happen */ private fun validateSceneChange( from: SceneKey, to: SceneKey, loggingReason: String, ): Boolean { if (!repository.allSceneKeys().contains(to)) { return false } check(to != Scenes.Gone || deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked) { "Cannot change to the Gone scene while the device is locked. Logging reason for scene" + " change was: $loggingReason" } return from != to } } Loading
packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt +6 −0 Original line number Diff line number Diff line Loading @@ -62,4 +62,10 @@ class SceneTransitionLayoutDataSource( coroutineScope = coroutineScope, ) } override fun snapToScene(toScene: SceneKey) { state.snapToScene( scene = toScene, ) } }
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +10 −0 Original line number Diff line number Diff line Loading @@ -70,6 +70,9 @@ class SceneContainerRepositoryTest : SysuiTestCase() { underTest.changeScene(Scenes.Shade) assertThat(currentScene).isEqualTo(Scenes.Shade) underTest.snapToScene(Scenes.QuickSettings) assertThat(currentScene).isEqualTo(Scenes.QuickSettings) } @Test(expected = IllegalStateException::class) Loading @@ -79,6 +82,13 @@ class SceneContainerRepositoryTest : SysuiTestCase() { underTest.changeScene(Scenes.Shade) } @Test(expected = IllegalStateException::class) fun snapToScene_noSuchSceneInContainer_throws() { kosmos.sceneKeys = listOf(Scenes.QuickSettings, Scenes.Lockscreen) val underTest = kosmos.sceneContainerRepository underTest.snapToScene(Scenes.Shade) } @Test fun isVisible() = testScope.runTest { Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +65 −0 Original line number Diff line number Diff line Loading @@ -125,6 +125,71 @@ class SceneInteractorTest : SysuiTestCase() { underTest.changeScene(Scenes.Gone, "reason") } @Test fun snapToScene_toUnknownScene_doesNothing() = testScope.runTest { val sceneKeys = listOf( Scenes.QuickSettings, Scenes.Shade, Scenes.Lockscreen, Scenes.Gone, Scenes.Communal, ) val navigationDistances = mapOf( Scenes.Gone to 0, Scenes.Lockscreen to 0, Scenes.Communal to 1, Scenes.Shade to 2, Scenes.QuickSettings to 3, ) kosmos.sceneContainerConfig = SceneContainerConfig(sceneKeys, Scenes.Lockscreen, navigationDistances) underTest = kosmos.sceneInteractor val currentScene by collectLastValue(underTest.currentScene) val previousScene = currentScene assertThat(previousScene).isNotEqualTo(Scenes.Bouncer) underTest.snapToScene(Scenes.Bouncer, "reason") assertThat(currentScene).isEqualTo(previousScene) } @Test fun snapToScene() = testScope.runTest { underTest = kosmos.sceneInteractor val currentScene by collectLastValue(underTest.currentScene) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) underTest.snapToScene(Scenes.Shade, "reason") assertThat(currentScene).isEqualTo(Scenes.Shade) } @Test fun snapToScene_toGoneWhenUnl_doesNotThrow() = testScope.runTest { underTest = kosmos.sceneInteractor val currentScene by collectLastValue(underTest.currentScene) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( SuccessFingerprintAuthenticationStatus(0, true) ) runCurrent() underTest.snapToScene(Scenes.Gone, "reason") assertThat(currentScene).isEqualTo(Scenes.Gone) } @Test(expected = IllegalStateException::class) fun snapToScene_toGoneWhenStillLocked_throws() = testScope.runTest { underTest = kosmos.sceneInteractor underTest.snapToScene(Scenes.Gone, "reason") } @Test fun sceneChanged_inDataSource() = testScope.runTest { Loading
packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +8 −0 Original line number Diff line number Diff line Loading @@ -87,6 +87,14 @@ constructor( ) } fun snapToScene( toScene: SceneKey, ) { dataSource.snapToScene( toScene = toScene, ) } /** Sets whether the container is visible. */ fun setVisible(isVisible: Boolean) { _isVisible.value = isVisible Loading
packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +64 −9 Original line number Diff line number Diff line Loading @@ -162,19 +162,45 @@ constructor( loggingReason: String, transitionKey: TransitionKey? = null, ) { if (!repository.allSceneKeys().contains(toScene)) { val currentSceneKey = currentScene.value if ( !validateSceneChange( from = currentSceneKey, to = toScene, loggingReason = loggingReason, ) ) { return } check( toScene != Scenes.Gone || deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked ) { "Cannot change to the Gone scene while the device is locked. Logging reason for scene" + " change was: $loggingReason" logger.logSceneChangeRequested( from = currentSceneKey, to = toScene, reason = loggingReason, isInstant = false, ) repository.changeScene(toScene, transitionKey) } /** * Requests a scene change to the given scene. * * The change is instantaneous and not animated; it will be observable in the next frame and * there will be no transition animation. */ fun snapToScene( toScene: SceneKey, loggingReason: String, ) { val currentSceneKey = currentScene.value if (currentSceneKey == toScene) { if ( !validateSceneChange( from = currentSceneKey, to = toScene, loggingReason = loggingReason, ) ) { return } Loading @@ -182,9 +208,10 @@ constructor( from = currentSceneKey, to = toScene, reason = loggingReason, isInstant = true, ) repository.changeScene(toScene, transitionKey) repository.snapToScene(toScene) } /** Loading Loading @@ -249,4 +276,32 @@ constructor( ): Boolean { return raw || isRemoteUserInteractionOngoing } /** * Validates that the given scene change is allowed. * * Will throw a runtime exception for illegal states (for example, attempting to change to a * scene that's not part of the current scene framework configuration). * * @param from The current scene being transitioned away from * @param to The desired destination scene to transition to * @param loggingReason The reason why the transition is requested, for logging purposes * @return `true` if the scene change is valid; `false` if it shouldn't happen */ private fun validateSceneChange( from: SceneKey, to: SceneKey, loggingReason: String, ): Boolean { if (!repository.allSceneKeys().contains(to)) { return false } check(to != Scenes.Gone || deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked) { "Cannot change to the Gone scene while the device is locked. Logging reason for scene" + " change was: $loggingReason" } return from != to } }