diff options
22 files changed, 643 insertions, 383 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 3dfdbbaaee77..f91baf298347 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -77,7 +77,7 @@ fun SceneContainer( SceneTransitionLayout( currentScene = currentSceneKey.toTransitionSceneKey(), - onChangeScene = { sceneKey -> viewModel.setCurrentScene(sceneKey.toModel()) }, + onChangeScene = viewModel::onSceneChanged, transitions = transitions {}, state = state, modifier = modifier.fillMaxSize(), @@ -154,3 +154,7 @@ private fun UserAction.toTransitionUserAction(): SceneTransitionUserAction { is UserAction.Back -> Back } } + +private fun SceneContainerViewModel.onSceneChanged(sceneKey: SceneTransitionSceneKey) { + onSceneChanged(sceneKey.toModel()) +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index aff25914ec36..57a859126db2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -476,18 +476,16 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) { // When the scene framework transitions from bouncer to gone, we dismiss the keyguard. mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow( - mSceneInteractor.get().getTransitions(), - sceneTransitionModel -> { - if (sceneTransitionModel != null - && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE - && sceneTransitionModel.getTo() == SceneKey.Gone.INSTANCE) { - final int selectedUserId = mUserInteractor.getSelectedUserId(); - showNextSecurityScreenOrFinish( - /* authenticated= */ true, - selectedUserId, - /* bypassSecondaryLockScreen= */ true, - mSecurityModel.getSecurityMode(selectedUserId)); - } + mSceneInteractor.get().finishedSceneTransitions( + /* from= */ SceneKey.Bouncer.INSTANCE, + /* to= */ SceneKey.Gone.INSTANCE), + unused -> { + final int selectedUserId = mUserInteractor.getSelectedUserId(); + showNextSecurityScreenOrFinish( + /* authenticated= */ true, + selectedUserId, + /* bypassSecondaryLockScreen= */ true, + mSecurityModel.getSecurityMode(selectedUserId)); }); } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index ffcae1cacb00..1bf3a9ead08e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -116,12 +116,12 @@ constructor( repository.setMessage( message ?: promptMessage(authenticationInteractor.getAuthenticationMethod()) ) - sceneInteractor.setCurrentScene( + sceneInteractor.changeScene( scene = SceneModel(SceneKey.Bouncer), loggingReason = "request to unlock device while authentication required", ) } else { - sceneInteractor.setCurrentScene( + sceneInteractor.changeScene( scene = SceneModel(SceneKey.Gone), loggingReason = "request to unlock device while authentication isn't required", ) @@ -169,7 +169,7 @@ constructor( authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null if (isAuthenticated) { - sceneInteractor.setCurrentScene( + sceneInteractor.changeScene( scene = SceneModel(SceneKey.Gone), loggingReason = "successful authentication", ) 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 fee3960ff0e1..350fa38f2052 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 @@ -18,50 +18,49 @@ package com.android.systemui.scene.data.repository +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.scene.shared.model.SceneTransitionModel import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn /** Source of truth for scene framework application state. */ class SceneContainerRepository @Inject constructor( + @Application applicationScope: CoroutineScope, private val config: SceneContainerConfig, ) { + private val _desiredScene = MutableStateFlow(SceneModel(config.initialSceneKey)) + val desiredScene: StateFlow<SceneModel> = _desiredScene.asStateFlow() private val _isVisible = MutableStateFlow(true) val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow() - private val _currentScene = MutableStateFlow(SceneModel(config.initialSceneKey)) - val currentScene: StateFlow<SceneModel> = _currentScene.asStateFlow() - - private val transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) - val transitionProgress: Flow<Float> = - transitionState.flatMapLatest { observableTransitionStateFlow -> - observableTransitionStateFlow?.flatMapLatest { observableTransitionState -> - when (observableTransitionState) { - is ObservableTransitionState.Idle -> flowOf(1f) - is ObservableTransitionState.Transition -> observableTransitionState.progress - } - } - ?: flowOf(1f) - } - - private val _transitions = MutableStateFlow<SceneTransitionModel?>(null) - val transitions: StateFlow<SceneTransitionModel?> = _transitions.asStateFlow() + private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey) + private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) + val transitionState: StateFlow<ObservableTransitionState> = + _transitionState + .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = defaultTransitionState, + ) /** - * Returns the keys to all scenes in the container with the given name. + * Returns the keys to all scenes in the container. * * The scenes will be sorted in z-order such that the last one is the one that should be * rendered on top of all previous ones. @@ -70,40 +69,19 @@ constructor( return config.sceneKeys } - /** Sets the current scene in the container with the given name. */ - fun setCurrentScene(scene: SceneModel) { + fun setDesiredScene(scene: SceneModel) { check(allSceneKeys().contains(scene.key)) { """ - Cannot set current scene key to "${scene.key}". The configuration does not contain a - scene with that key. - """ - .trimIndent() - } - - _currentScene.value = scene - } - - /** Sets the scene transition in the container with the given name. */ - fun setSceneTransition(from: SceneKey, to: SceneKey) { - check(allSceneKeys().contains(from)) { - """ - Cannot set current scene key to "$from". The configuration does not contain a scene - with that key. - """ - .trimIndent() - } - check(allSceneKeys().contains(to)) { - """ - Cannot set current scene key to "$to". The configuration does not contain a scene - with that key. + Cannot set the desired scene key to "${scene.key}". The configuration does not + contain a scene with that key. """ .trimIndent() } - _transitions.value = SceneTransitionModel(from = from, to = to) + _desiredScene.value = scene } - /** Sets whether the container with the given name is visible. */ + /** Sets whether the container is visible. */ fun setVisible(isVisible: Boolean) { _isVisible.value = isVisible } @@ -114,6 +92,6 @@ constructor( * Note that you must call is with `null` when the UI is done or risk a memory leak. */ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { - this.transitionState.value = transitionState + _transitionState.value = transitionState } } 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 64715bc26674..cf7abdd34b70 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 @@ -23,12 +23,15 @@ import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.RemoteUserInput import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.scene.shared.model.SceneTransitionModel +import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull /** * Generic business logic and app state accessors for the scene framework. @@ -46,7 +49,54 @@ constructor( ) { /** - * Returns the keys of all scenes in the container with the given name. + * The currently *desired* scene. + * + * **Important:** this value will _commonly be different_ from what is being rendered in the UI, + * by design. + * + * There are two intended sources for this value: + * 1. Programmatic requests to transition to another scene (calls to [changeScene]). + * 2. Reports from the UI about completing a transition to another scene (calls to + * [onSceneChanged]). + * + * Both the sources above cause the value of this flow to change; however, they cause mismatches + * in different ways. + * + * **Updates from programmatic transitions** + * + * When an external bit of code asks the framework to switch to another scene, the value here + * will update immediately. Downstream, the UI will detect this change and initiate the + * transition animation. As the transition animation progresses, a threshold will be reached, at + * which point the UI and the state here will match each other. + * + * **Updates from the UI** + * + * When the user interacts with the UI, the UI runs a transition animation that tracks the user + * pointer (for example, the user's finger). During this time, the state value here and what the + * UI shows will likely not match. Once/if a threshold is met, the UI reports it and commits the + * change, making the value here match the UI again. + */ + val desiredScene: StateFlow<SceneModel> = repository.desiredScene + + /** + * The current state of the transition. + * + * Consumers should use this state to know: + * 1. Whether there is an ongoing transition or if the system is at rest. + * 2. When transitioning, which scenes are being transitioned between. + * 3. When transitioning, what the progress of the transition is. + */ + val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState + + /** Whether the scene container is visible. */ + val isVisible: StateFlow<Boolean> = repository.isVisible + + private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null) + /** A flow of motion events originating from outside of the scene framework. */ + val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow() + + /** + * Returns the keys of all scenes in the container. * * The scenes will be sorted in z-order such that the last one is the one that should be * rendered on top of all previous ones. @@ -55,26 +105,20 @@ constructor( return repository.allSceneKeys() } - /** Sets the scene in the container with the given name. */ - fun setCurrentScene(scene: SceneModel, loggingReason: String) { - val currentSceneKey = repository.currentScene.value.key - if (currentSceneKey == scene.key) { - return - } - - logger.logSceneChange( - from = currentSceneKey, - to = scene.key, - reason = loggingReason, - ) - repository.setCurrentScene(scene) - repository.setSceneTransition(from = currentSceneKey, to = scene.key) + /** + * Requests a scene change to the given scene. + * + * The change is animated. Therefore, while the value in [desiredScene] will update immediately, + * it will be some time before the UI will switch to the desired scene. The scene change + * requested is remembered here but served by the UI layer, which will start a transition + * animation. Once enough of the transition has occurred, the system will come into agreement + * between the [desiredScene] and the UI. + */ + fun changeScene(scene: SceneModel, loggingReason: String) { + updateDesiredScene(scene, loggingReason, logger::logSceneChangeRequested) } - /** The current scene in the container with the given name. */ - val currentScene: StateFlow<SceneModel> = repository.currentScene - - /** Sets the visibility of the container with the given name. */ + /** Sets the visibility of the container. */ fun setVisible(isVisible: Boolean, loggingReason: String) { val wasVisible = repository.isVisible.value if (wasVisible == isVisible) { @@ -89,9 +133,6 @@ constructor( return repository.setVisible(isVisible) } - /** Whether the container with the given name is visible. */ - val isVisible: StateFlow<Boolean> = repository.isVisible - /** * Binds the given flow so the system remembers it. * @@ -101,23 +142,53 @@ constructor( repository.setTransitionState(transitionState) } - /** Progress of the transition into the current scene in the container with the given name. */ - val transitionProgress: Flow<Float> = repository.transitionProgress - /** - * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene - * transition occurs. The flow begins with a `null` value at first, because the initial scene is - * not something that we transition to from another scene. + * Returns a stream of events that emits one [Unit] every time the framework transitions from + * [from] to [to]. */ - val transitions: StateFlow<SceneTransitionModel?> = repository.transitions - - private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null) - - /** A flow of motion events originating from outside of the scene framework. */ - val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow() + fun finishedSceneTransitions(from: SceneKey, to: SceneKey): Flow<Unit> { + return transitionState + .mapNotNull { it as? ObservableTransitionState.Idle } + .map { idleState -> idleState.scene } + .distinctUntilChanged() + .pairwise() + .mapNotNull { (previousSceneKey, currentSceneKey) -> + Unit.takeIf { previousSceneKey == from && currentSceneKey == to } + } + } /** Handles a remote user input. */ fun onRemoteUserInput(input: RemoteUserInput) { _remoteUserInput.value = input } + + /** + * Notifies that the UI has transitioned sufficiently to the given scene. + * + * *Not intended for external use!* + * + * Once a transition between one scene and another passes a threshold, the UI invokes this + * method to report it, updating the value in [desiredScene] to match what the UI shows. + */ + internal fun onSceneChanged(scene: SceneModel, loggingReason: String) { + updateDesiredScene(scene, loggingReason, logger::logSceneChangeCommitted) + } + + private fun updateDesiredScene( + scene: SceneModel, + loggingReason: String, + log: (from: SceneKey, to: SceneKey, loggingReason: String) -> Unit, + ) { + val currentSceneKey = desiredScene.value.key + if (currentSceneKey == scene.key) { + return + } + + log( + /* from= */ currentSceneKey, + /* to= */ scene.key, + /* loggingReason= */ loggingReason, + ) + repository.setDesiredScene(scene) + } } 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 bd233f80b47b..afefccb27214 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 @@ -30,6 +30,7 @@ import com.android.systemui.model.SysUiState import com.android.systemui.model.updateFlags import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.logger.SceneLogger +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING @@ -40,8 +41,8 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_B import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch /** @@ -73,14 +74,31 @@ constructor( } } - /** Updates the visibility of the scene container based on the current scene. */ + /** Updates the visibility of the scene container. */ private fun hydrateVisibility() { applicationScope.launch { - sceneInteractor.currentScene - .map { it.key } + sceneInteractor.transitionState + .mapNotNull { state -> + when (state) { + is ObservableTransitionState.Idle -> { + if (state.scene != SceneKey.Gone) { + true to "scene is not Gone" + } else { + false to "scene is Gone" + } + } + is ObservableTransitionState.Transition -> { + if (state.fromScene == SceneKey.Gone) { + true to "scene transitioning away from Gone" + } else { + null + } + } + } + } .distinctUntilChanged() - .collect { sceneKey -> - sceneInteractor.setVisible(sceneKey != SceneKey.Gone, "scene is $sceneKey") + .collect { (isVisible, loggingReason) -> + sceneInteractor.setVisible(isVisible, loggingReason) } } } @@ -89,43 +107,55 @@ constructor( private fun automaticallySwitchScenes() { applicationScope.launch { authenticationInteractor.isUnlocked - .map { isUnlocked -> - val currentSceneKey = sceneInteractor.currentScene.value.key + .mapNotNull { isUnlocked -> + val renderedScenes = + when (val transitionState = sceneInteractor.transitionState.value) { + is ObservableTransitionState.Idle -> setOf(transitionState.scene) + is ObservableTransitionState.Transition -> + setOf( + transitionState.progress, + transitionState.toScene, + ) + } val isBypassEnabled = authenticationInteractor.isBypassEnabled() when { isUnlocked -> - when (currentSceneKey) { + when { // When the device becomes unlocked in Bouncer, go to Gone. - is SceneKey.Bouncer -> + renderedScenes.contains(SceneKey.Bouncer) -> SceneKey.Gone to "device unlocked in Bouncer scene" + // When the device becomes unlocked in Lockscreen, go to Gone if // bypass is enabled. - is SceneKey.Lockscreen -> + renderedScenes.contains(SceneKey.Lockscreen) -> if (isBypassEnabled) { SceneKey.Gone to "device unlocked in Lockscreen scene with bypass" } else { null } + // We got unlocked while on a scene that's not Lockscreen or // Bouncer, no need to change scenes. else -> null } + // When the device becomes locked, to Lockscreen. !isUnlocked -> - when (currentSceneKey) { + when { // Already on lockscreen or bouncer, no need to change scenes. - is SceneKey.Lockscreen, - is SceneKey.Bouncer -> null + renderedScenes.contains(SceneKey.Lockscreen) || + renderedScenes.contains(SceneKey.Bouncer) -> null + // We got locked while on a scene that's not Lockscreen or Bouncer, // go to Lockscreen. else -> - SceneKey.Lockscreen to "device locked in $currentSceneKey scene" + SceneKey.Lockscreen to + "device locked in non-Lockscreen and non-Bouncer scene" } else -> null } } - .filterNotNull() .collect { (targetSceneKey, loggingReason) -> switchToScene( targetSceneKey = targetSceneKey, @@ -143,7 +173,7 @@ constructor( WakefulnessState.STARTING_TO_SLEEP -> { switchToScene( targetSceneKey = SceneKey.Lockscreen, - loggingReason = "device is asleep", + loggingReason = "device is starting to sleep", ) } WakefulnessState.STARTING_TO_WAKE -> { @@ -165,8 +195,9 @@ constructor( /** Keeps [SysUiState] up-to-date */ private fun hydrateSystemUiState() { applicationScope.launch { - sceneInteractor.currentScene - .map { it.key } + sceneInteractor.transitionState + .mapNotNull { it as? ObservableTransitionState.Idle } + .map { it.scene } .distinctUntilChanged() .collect { sceneKey -> sysUiState.updateFlags( @@ -183,7 +214,7 @@ constructor( } private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) { - sceneInteractor.setCurrentScene( + sceneInteractor.changeScene( scene = SceneModel(targetSceneKey), loggingReason = loggingReason, ) 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 0adbd5ad19a7..62136dcd8e1d 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 @@ -37,7 +37,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: ) } - fun logSceneChange( + fun logSceneChangeRequested( from: SceneKey, to: SceneKey, reason: String, @@ -50,7 +50,24 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: str2 = to.toString() str3 = reason }, - messagePrinter = { "$str1 → $str2, reason: $str3" }, + messagePrinter = { "Scene change requested: $str1 → $str2, reason: $str3" }, + ) + } + + fun logSceneChangeCommitted( + from: SceneKey, + to: SceneKey, + reason: String, + ) { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = { + str1 = from.toString() + str2 = to.toString() + str3 = reason + }, + messagePrinter = { "Scene change committed: $str1 → $str2, reason: $str3" }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt deleted file mode 100644 index c8f46a72d64f..000000000000 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.scene.shared.model - -/** Models a transition between two scenes. */ -data class SceneTransitionModel( - /** The scene we transitioned away from. */ - val from: SceneKey, - /** The scene we transitioned into. */ - val to: SceneKey, -) diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index b4ebaece21f1..3e9bbe464e2c 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -45,15 +45,15 @@ constructor( */ val allSceneKeys: List<SceneKey> = interactor.allSceneKeys() - /** The current scene. */ - val currentScene: StateFlow<SceneModel> = interactor.currentScene + /** The scene that should be rendered. */ + val currentScene: StateFlow<SceneModel> = interactor.desiredScene /** Whether the container is visible. */ val isVisible: StateFlow<Boolean> = interactor.isVisible - /** Requests a transition to the scene with the given key. */ - fun setCurrentScene(scene: SceneModel) { - interactor.setCurrentScene( + /** Notifies that the UI has transitioned sufficiently to the given scene. */ + fun onSceneChanged(scene: SceneModel) { + interactor.onSceneChanged( scene = scene, loggingReason = SCENE_TRANSITION_LOGGING_REASON, ) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index efb981e5cebb..a6bd9363a08b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -50,6 +50,7 @@ import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.plugins.FalsingManager import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.statusbar.policy.ConfigurationController @@ -66,6 +67,8 @@ import com.google.common.truth.Truth import java.util.Optional import junit.framework.Assert import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -139,6 +142,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { private lateinit var testableResources: TestableResources private lateinit var sceneTestUtils: SceneTestUtils private lateinit var sceneInteractor: SceneInteractor + private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState> private lateinit var underTest: KeyguardSecurityContainerController @@ -198,6 +202,9 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID) sceneTestUtils = SceneTestUtils(this) sceneInteractor = sceneTestUtils.sceneInteractor() + sceneTransitionStateFlow = + MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen)) + sceneInteractor.setTransitionState(sceneTransitionStateFlow) underTest = KeyguardSecurityContainerController( @@ -733,20 +740,39 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // is // not enough to trigger a dismissal of the keyguard. underTest.onViewAttached() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition( + SceneKey.Lockscreen, + SceneKey.Bouncer, + flowOf(.5f) + ) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) // While listening, going from the bouncer scene to the gone scene, does dismiss the // keyguard. - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) runCurrent() verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) // While listening, moving back to the bouncer scene does not dismiss the keyguard // again. clearInvocations(viewMediatorCallback) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) @@ -754,12 +780,22 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // scene // does not dismiss the keyguard while we're not listening. underTest.onViewDetached() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) // While not listening, moving back to the bouncer does not dismiss the keyguard. - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) runCurrent() verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) @@ -767,7 +803,12 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // gone // scene now does dismiss the keyguard again. underTest.onViewAttached() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = + ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f)) + runCurrent() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") + sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) runCurrent() verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index df4d2225f459..86e0c751085b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -70,7 +70,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) @@ -102,7 +102,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) @@ -139,7 +139,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) @@ -169,7 +169,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun passwordAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password @@ -202,7 +202,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun patternAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern @@ -236,7 +236,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun showOrUnlockDevice_notLocked_switchesToGoneScene() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -249,7 +249,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) utils.authenticationRepository.setLockscreenEnabled(true) utils.authenticationRepository.setUnlocked(false) @@ -262,7 +262,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun showOrUnlockDevice_customMessageShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password @@ -283,7 +283,7 @@ class BouncerInteractorTest : SysuiTestCase() { val isThrottled by collectLastValue(underTest.isThrottled) val throttling by collectLastValue(underTest.throttling) val message by collectLastValue(underTest.message) - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() underTest.showOrUnlockDevice() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index 4e9fe8d91da1..4380af80efbd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -73,14 +73,15 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -93,14 +94,15 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onPasswordInputChanged() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -115,12 +117,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("password") @@ -133,14 +136,15 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("wrong") @@ -155,14 +159,15 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPasswordInputChanged("wrong") diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 000200c606b3..ea2cad2ab517 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -76,7 +76,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -84,7 +84,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -98,7 +99,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragStart() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -106,7 +107,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -122,14 +124,15 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() @@ -169,7 +172,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -177,7 +180,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() @@ -201,7 +205,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -209,7 +213,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onDragStart() diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 4b667c393b62..531f86abdfbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -76,11 +76,13 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -93,12 +95,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onPinButtonClicked() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -113,12 +117,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onBackspaceButtonClicked() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -135,11 +141,13 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onPinEdit() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() @@ -157,12 +165,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onBackspaceButtonLongPressed() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() runCurrent() @@ -181,10 +191,12 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> @@ -199,12 +211,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPinButtonClicked(1) @@ -223,12 +237,14 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() underTest.onPinButtonClicked(1) @@ -255,11 +271,13 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAutoConfirm_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) utils.authenticationRepository.setAutoConfirmEnabled(true) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> @@ -272,13 +290,15 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAutoConfirm_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) utils.authenticationRepository.setAutoConfirmEnabled(true) - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit -> diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 834b9c526669..45d7a5ebb60a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -107,7 +107,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() @@ -120,7 +120,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -133,7 +133,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() @@ -146,7 +146,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun onLockButtonClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index bb365d05e9e2..2cb02058ab03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -56,7 +56,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -69,7 +69,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index 56e3e9649fe7..181f8a7e3003 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -25,7 +25,6 @@ import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.scene.shared.model.SceneTransitionModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -39,6 +38,7 @@ import org.junit.runners.JUnit4 class SceneContainerRepositoryTest : SysuiTestCase() { private val utils = SceneTestUtils(this) + private val testScope = utils.testScope @Test fun allSceneKeys() { @@ -56,97 +56,82 @@ class SceneContainerRepositoryTest : SysuiTestCase() { } @Test - fun currentScene() = runTest { - val underTest = utils.fakeSceneContainerRepository() - val currentScene by collectLastValue(underTest.currentScene) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + fun desiredScene() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val currentScene by collectLastValue(underTest.desiredScene) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - underTest.setCurrentScene(SceneModel(SceneKey.Shade)) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) - } + underTest.setDesiredScene(SceneModel(SceneKey.Shade)) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) + } @Test(expected = IllegalStateException::class) - fun setCurrentScene_noSuchSceneInContainer_throws() { + fun setDesiredScene_noSuchSceneInContainer_throws() { val underTest = utils.fakeSceneContainerRepository( utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)), ) - underTest.setCurrentScene(SceneModel(SceneKey.Shade)) + underTest.setDesiredScene(SceneModel(SceneKey.Shade)) } @Test - fun isVisible() = runTest { - val underTest = utils.fakeSceneContainerRepository() - val isVisible by collectLastValue(underTest.isVisible) - assertThat(isVisible).isTrue() + fun isVisible() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val isVisible by collectLastValue(underTest.isVisible) + assertThat(isVisible).isTrue() - underTest.setVisible(false) - assertThat(isVisible).isFalse() + underTest.setVisible(false) + assertThat(isVisible).isFalse() - underTest.setVisible(true) - assertThat(isVisible).isTrue() - } + underTest.setVisible(true) + assertThat(isVisible).isTrue() + } @Test - fun transitionProgress() = runTest { - val underTest = utils.fakeSceneContainerRepository() - val sceneTransitionProgress by collectLastValue(underTest.transitionProgress) - assertThat(sceneTransitionProgress).isEqualTo(1f) - - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.Lockscreen) - ) - underTest.setTransitionState(transitionState) - assertThat(sceneTransitionProgress).isEqualTo(1f) - - val progress = MutableStateFlow(1f) - transitionState.value = - ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = SceneKey.Shade, - progress = progress, - ) - assertThat(sceneTransitionProgress).isEqualTo(1f) - - progress.value = 0.1f - assertThat(sceneTransitionProgress).isEqualTo(0.1f) - - progress.value = 0.9f - assertThat(sceneTransitionProgress).isEqualTo(0.9f) - - underTest.setTransitionState(null) - assertThat(sceneTransitionProgress).isEqualTo(1f) - } + fun transitionState_defaultsToIdle() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val transitionState by collectLastValue(underTest.transitionState) + + assertThat(transitionState) + .isEqualTo( + ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ) + } @Test - fun setSceneTransition() = runTest { - val underTest = utils.fakeSceneContainerRepository() - val sceneTransition by collectLastValue(underTest.transitions) - assertThat(sceneTransition).isNull() + fun transitionState_reflectsUpdates() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) + ) + underTest.setTransitionState(transitionState) + val reflectedTransitionState by collectLastValue(underTest.transitionState) + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) + + val progress = MutableStateFlow(1f) + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = SceneKey.Shade, + progress = progress, + ) + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) - underTest.setSceneTransition(SceneKey.Lockscreen, SceneKey.QuickSettings) - assertThat(sceneTransition) - .isEqualTo( - SceneTransitionModel(from = SceneKey.Lockscreen, to = SceneKey.QuickSettings) - ) - } + progress.value = 0.1f + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) - @Test(expected = IllegalStateException::class) - fun setSceneTransition_noFromSceneInContainer_throws() { - val underTest = - utils.fakeSceneContainerRepository( - utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)), - ) - underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen) - } + progress.value = 0.9f + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) - @Test(expected = IllegalStateException::class) - fun setSceneTransition_noToSceneInContainer_throws() { - val underTest = - utils.fakeSceneContainerRepository( - utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)), - ) - underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen) - } + underTest.setTransitionState(null) + assertThat(reflectedTransitionState) + .isEqualTo( + ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index 4facc7a6a36d..0a93a7ca465f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -25,10 +25,12 @@ import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.scene.shared.model.SceneTransitionModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -39,6 +41,7 @@ import org.junit.runners.JUnit4 class SceneInteractorTest : SysuiTestCase() { private val utils = SceneTestUtils(this) + private val testScope = utils.testScope private val repository = utils.fakeSceneContainerRepository() private val underTest = utils.sceneInteractor(repository = repository) @@ -48,77 +51,156 @@ class SceneInteractorTest : SysuiTestCase() { } @Test - fun currentScene() = runTest { - val currentScene by collectLastValue(underTest.currentScene) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + fun changeScene() = + testScope.runTest { + val desiredScene by collectLastValue(underTest.desiredScene) + assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - underTest.setCurrentScene(SceneModel(SceneKey.Shade), "reason") - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) - } + underTest.changeScene(SceneModel(SceneKey.Shade), "reason") + assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Shade)) + } + + @Test + fun onSceneChanged() = + testScope.runTest { + val desiredScene by collectLastValue(underTest.desiredScene) + assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + + underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Shade)) + } @Test - fun sceneTransitionProgress() = runTest { - val transitionProgress by collectLastValue(underTest.transitionProgress) - assertThat(transitionProgress).isEqualTo(1f) + fun transitionState() = + testScope.runTest { + val underTest = utils.fakeSceneContainerRepository() + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) + ) + underTest.setTransitionState(transitionState) + val reflectedTransitionState by collectLastValue(underTest.transitionState) + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) - val progress = MutableStateFlow(0.55f) - repository.setTransitionState( - MutableStateFlow( + val progress = MutableStateFlow(1f) + transitionState.value = ObservableTransitionState.Transition( fromScene = SceneKey.Lockscreen, toScene = SceneKey.Shade, progress = progress, - ), - ) - ) - assertThat(transitionProgress).isEqualTo(0.55f) - } + ) + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) + + progress.value = 0.1f + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) + + progress.value = 0.9f + assertThat(reflectedTransitionState).isEqualTo(transitionState.value) + + underTest.setTransitionState(null) + assertThat(reflectedTransitionState) + .isEqualTo( + ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ) + } @Test - fun isVisible() = runTest { - val isVisible by collectLastValue(underTest.isVisible) - assertThat(isVisible).isTrue() + fun isVisible() = + testScope.runTest { + val isVisible by collectLastValue(underTest.isVisible) + assertThat(isVisible).isTrue() - underTest.setVisible(false, "reason") - assertThat(isVisible).isFalse() + underTest.setVisible(false, "reason") + assertThat(isVisible).isFalse() - underTest.setVisible(true, "reason") - assertThat(isVisible).isTrue() - } + underTest.setVisible(true, "reason") + assertThat(isVisible).isTrue() + } @Test - fun sceneTransitions() = runTest { - val transitions by collectLastValue(underTest.transitions) - assertThat(transitions).isNull() - - val initialSceneKey = underTest.currentScene.value.key - underTest.setCurrentScene(SceneModel(SceneKey.Shade), "reason") - assertThat(transitions) - .isEqualTo( - SceneTransitionModel( - from = initialSceneKey, - to = SceneKey.Shade, + fun finishedSceneTransitions() = + testScope.runTest { + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) ) - ) - - underTest.setCurrentScene(SceneModel(SceneKey.QuickSettings), "reason") - assertThat(transitions) - .isEqualTo( - SceneTransitionModel( - from = SceneKey.Shade, - to = SceneKey.QuickSettings, + underTest.setTransitionState(transitionState) + var transitionCount = 0 + val job = launch { + underTest + .finishedSceneTransitions( + from = SceneKey.Shade, + to = SceneKey.QuickSettings, + ) + .collect { transitionCount++ } + } + + assertThat(transitionCount).isEqualTo(0) + + underTest.changeScene(SceneModel(SceneKey.Shade), "reason") + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = SceneKey.Shade, + progress = flowOf(0.5f), ) - ) - } + runCurrent() + underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade) + runCurrent() + assertThat(transitionCount).isEqualTo(0) + + underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason") + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Shade, + toScene = SceneKey.QuickSettings, + progress = flowOf(0.5f), + ) + runCurrent() + underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason") + transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings) + runCurrent() + assertThat(transitionCount).isEqualTo(1) + + underTest.changeScene(SceneModel(SceneKey.Shade), "reason") + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.QuickSettings, + toScene = SceneKey.Shade, + progress = flowOf(0.5f), + ) + runCurrent() + underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade) + runCurrent() + assertThat(transitionCount).isEqualTo(1) + + underTest.changeScene(SceneModel(SceneKey.QuickSettings), "reason") + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Shade, + toScene = SceneKey.QuickSettings, + progress = flowOf(0.5f), + ) + runCurrent() + underTest.onSceneChanged(SceneModel(SceneKey.QuickSettings), "reason") + transitionState.value = ObservableTransitionState.Idle(SceneKey.QuickSettings) + runCurrent() + assertThat(transitionCount).isEqualTo(2) - @Test - fun remoteUserInput() = runTest { - val remoteUserInput by collectLastValue(underTest.remoteUserInput) - assertThat(remoteUserInput).isNull() + job.cancel() + } - for (input in SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE) { - underTest.onRemoteUserInput(input) - assertThat(remoteUserInput).isEqualTo(input) + @Test + fun remoteUserInput() = + testScope.runTest { + val remoteUserInput by collectLastValue(underTest.remoteUserInput) + assertThat(remoteUserInput).isNull() + + for (input in SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE) { + underTest.onRemoteUserInput(input) + assertThat(remoteUserInput).isEqualTo(input) + } } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index bec0b77e6480..45db7a0b17f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -29,15 +29,17 @@ import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.model.SysUiState import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -77,59 +79,86 @@ class SceneContainerStartableTest : SysuiTestCase() { sceneLogger = mock(), ) - @Before - fun setUp() { - prepareState() - } - @Test fun hydrateVisibility_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentDesiredSceneKey by + collectLastValue(sceneInteractor.desiredScene.map { it.key }) val isVisible by collectLastValue(sceneInteractor.isVisible) - prepareState( - isFeatureEnabled = true, - isDeviceUnlocked = true, - initialSceneKey = SceneKey.Gone, - ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + val transitionStateFlow = + prepareState( + isFeatureEnabled = true, + isDeviceUnlocked = true, + initialSceneKey = SceneKey.Gone, + ) + assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone) assertThat(isVisible).isTrue() underTest.start() - assertThat(isVisible).isFalse() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason") + transitionStateFlow.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Gone, + toScene = SceneKey.Shade, + progress = flowOf(0.5f), + ) + assertThat(isVisible).isTrue() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade) + assertThat(isVisible).isTrue() + + sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason") + transitionStateFlow.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Shade, + toScene = SceneKey.Gone, + progress = flowOf(0.5f), + ) assertThat(isVisible).isTrue() + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason") + transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) + assertThat(isVisible).isFalse() } @Test fun hydrateVisibility_featureDisabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentDesiredSceneKey by + collectLastValue(sceneInteractor.desiredScene.map { it.key }) val isVisible by collectLastValue(sceneInteractor.isVisible) - prepareState( - isFeatureEnabled = false, - isDeviceUnlocked = true, - initialSceneKey = SceneKey.Lockscreen, - ) - assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + val transitionStateFlow = + prepareState( + isFeatureEnabled = false, + isDeviceUnlocked = true, + initialSceneKey = SceneKey.Gone, + ) + assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone) assertThat(isVisible).isTrue() underTest.start() + assertThat(isVisible).isTrue() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason") + sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason") + transitionStateFlow.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Gone, + toScene = SceneKey.Shade, + progress = flowOf(0.5f), + ) assertThat(isVisible).isTrue() - sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason") + transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade) assertThat(isVisible).isTrue() } @Test fun switchToLockscreenWhenDeviceLocks_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isDeviceUnlocked = true, @@ -146,7 +175,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchToLockscreenWhenDeviceLocks_featureDisabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = false, isDeviceUnlocked = false, @@ -163,7 +192,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isDeviceUnlocked = false, @@ -180,7 +209,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = false, isDeviceUnlocked = false, @@ -197,7 +226,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isBypassEnabled = true, @@ -214,7 +243,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isBypassEnabled = false, @@ -231,7 +260,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = false, isBypassEnabled = true, @@ -248,7 +277,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, isDeviceUnlocked = false, @@ -265,7 +294,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = false, isDeviceUnlocked = false, @@ -282,6 +311,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun hydrateSystemUiState() = testScope.runTest { + val transitionStateFlow = prepareState() underTest.start() runCurrent() clearInvocations(sysUiState) @@ -294,9 +324,16 @@ class SceneContainerStartableTest : SysuiTestCase() { SceneKey.QuickSettings, ) .forEachIndexed { index, sceneKey -> - sceneInteractor.setCurrentScene(SceneModel(sceneKey), "reason") + sceneInteractor.changeScene(SceneModel(sceneKey), "reason") + runCurrent() + verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY) + + sceneInteractor.onSceneChanged(SceneModel(sceneKey), "reason") runCurrent() + verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY) + transitionStateFlow.value = ObservableTransitionState.Idle(sceneKey) + runCurrent() verify(sysUiState, times(index + 1)).commitUpdate(Display.DEFAULT_DISPLAY) } } @@ -304,7 +341,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, initialSceneKey = SceneKey.Lockscreen, @@ -321,7 +358,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNotNone_featureEnabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = true, initialSceneKey = SceneKey.Lockscreen, @@ -338,7 +375,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureDisabled() = testScope.runTest { - val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key }) + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( isFeatureEnabled = false, initialSceneKey = SceneKey.Lockscreen, @@ -358,17 +395,27 @@ class SceneContainerStartableTest : SysuiTestCase() { isBypassEnabled: Boolean = false, initialSceneKey: SceneKey? = null, authenticationMethod: AuthenticationMethodModel? = null, - ) { + ): MutableStateFlow<ObservableTransitionState> { featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled) authenticationRepository.setUnlocked(isDeviceUnlocked) keyguardRepository.setBypassEnabled(isBypassEnabled) - initialSceneKey?.let { sceneInteractor.setCurrentScene(SceneModel(it), "reason") } + val transitionStateFlow = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) + ) + sceneInteractor.setTransitionState(transitionStateFlow) + initialSceneKey?.let { + transitionStateFlow.value = ObservableTransitionState.Idle(it) + sceneInteractor.changeScene(SceneModel(it), "reason") + sceneInteractor.onSceneChanged(SceneModel(it), "reason") + } authenticationMethod?.let { authenticationRepository.setAuthenticationMethod(authenticationMethod) authenticationRepository.setLockscreenEnabled( authenticationMethod != AuthenticationMethodModel.None ) } + return transitionStateFlow } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index 9f3b12bd2042..da6c42694666 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -69,7 +69,8 @@ class SceneContainerViewModelTest : SysuiTestCase() { val currentScene by collectLastValue(underTest.currentScene) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - underTest.setCurrentScene(SceneModel(SceneKey.Shade)) + underTest.onSceneChanged(SceneModel(SceneKey.Shade)) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index d9301604c67f..7443097a2628 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -78,7 +78,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(true) runCurrent() @@ -91,7 +91,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @Test fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.authenticationRepository.setUnlocked(false) runCurrent() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 62087df8c238..507267e2d185 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -97,7 +97,7 @@ class SceneTestUtils( fun fakeSceneContainerRepository( containerConfig: SceneContainerConfig = fakeSceneContainerConfig(), ): SceneContainerRepository { - return SceneContainerRepository(containerConfig) + return SceneContainerRepository(applicationScope(), containerConfig) } fun fakeSceneKeys(): List<SceneKey> { |