diff options
9 files changed, 183 insertions, 10 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt index 05f8f4b38176..4b4b7ed33458 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt @@ -62,4 +62,10 @@ class SceneTransitionLayoutDataSource( coroutineScope = coroutineScope, ) } + + override fun snapToScene(toScene: SceneKey) { + state.snapToScene( + scene = toScene, + ) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index 883760c67310..df30c4bf1b5a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -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) @@ -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 { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index c16d5224e0ba..2fa94effdbd4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -126,6 +126,71 @@ class SceneInteractorTest : SysuiTestCase() { } @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 { underTest = kosmos.sceneInteractor diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt index 5748ad459ed6..eabc42b02665 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 93cef61d9dae..08efe39d7674 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -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 } @@ -182,9 +208,10 @@ constructor( from = currentSceneKey, to = toScene, reason = loggingReason, + isInstant = true, ) - repository.changeScene(toScene, transitionKey) + repository.snapToScene(toScene) } /** @@ -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 + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt index 5ebdd8698656..812141928e3c 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt @@ -47,6 +47,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: from: SceneKey, to: SceneKey, reason: String, + isInstant: Boolean, ) { logBuffer.log( tag = TAG, @@ -55,8 +56,17 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: str1 = from.toString() str2 = to.toString() str3 = reason + bool1 = isInstant + }, + messagePrinter = { + buildString { + append("Scene change requested: $str1 → $str2") + if (isInstant) { + append(" (instant)") + } + append(", reason: $str3") + } }, - messagePrinter = { "Scene change requested: $str1 → $str2, reason: $str3" }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt index 0e078d5d8064..034da25f1a45 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt @@ -40,4 +40,11 @@ interface SceneDataSource { toScene: SceneKey, transitionKey: TransitionKey? = null, ) + + /** + * Asks for an instant scene switch to [toScene], without an animated transition of any kind. + */ + fun snapToScene( + toScene: SceneKey, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt index 2fbcba977a91..43c3635f32fc 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt @@ -56,6 +56,12 @@ class SceneDataSourceDelegator( ) } + override fun snapToScene(toScene: SceneKey) { + delegateMutable.value.snapToScene( + toScene = toScene, + ) + } + /** * Binds the current, dependency injection provided [SceneDataSource] to the given object. * @@ -77,5 +83,7 @@ class SceneDataSourceDelegator( MutableStateFlow(initialSceneKey).asStateFlow() override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) = Unit + + override fun snapToScene(toScene: SceneKey) = Unit } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt index 59a01cbedc5c..957a60f83134 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt @@ -42,6 +42,10 @@ class FakeSceneDataSource( } } + override fun snapToScene(toScene: SceneKey) { + changeScene(toScene) + } + /** * Pauses scene changes. * |