Loading packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +17 −8 Original line number Diff line number Diff line Loading @@ -664,22 +664,31 @@ class CommunalInteractorTest : SysuiTestCase() { testScope.runTest { // Verify default is false val isCommunalShowing by collectLastValue(underTest.isCommunalShowing) runCurrent() assertThat(isCommunalShowing).isFalse() // Verify scene changes without the flag doesn't have any impact underTest.changeScene(CommunalScenes.Communal, "test") runCurrent() assertThat(isCommunalShowing).isFalse() // Verify scene changes (with the flag) to communal sets the value to true sceneInteractor.changeScene(Scenes.Communal, loggingReason = "") runCurrent() assertThat(isCommunalShowing).isTrue() // Verify scene changes (with the flag) to lockscreen sets the value to false sceneInteractor.changeScene(Scenes.Lockscreen, loggingReason = "") runCurrent() assertThat(isCommunalShowing).isFalse() } @Test @EnableSceneContainer fun isCommunalShowing_whenSceneContainerEnabledAndChangeToLegacyScene() = testScope.runTest { // Verify default is false val isCommunalShowing by collectLastValue(underTest.isCommunalShowing) assertThat(isCommunalShowing).isFalse() // Verify legacy scene change still makes communal show underTest.changeScene(CommunalScenes.Communal, "test") assertThat(isCommunalShowing).isTrue() // Verify legacy scene change to blank makes communal hidden underTest.changeScene(CommunalScenes.Blank, "test") assertThat(isCommunalShowing).isFalse() } Loading packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt +235 −28 Original line number Diff line number Diff line Loading @@ -16,18 +16,23 @@ package com.android.systemui.communal.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.communal.data.repository.communalSceneRepository import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor.OnSceneAboutToChangeListener import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.EditModeState import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.andSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.scene.initialSceneKey import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi Loading @@ -42,10 +47,24 @@ import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(AndroidJUnit4::class) class CommunalSceneInteractorTest : SysuiTestCase() { @RunWith(ParameterizedAndroidJunit4::class) class CommunalSceneInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { companion object { @JvmStatic @Parameters(name = "{0}") fun getParams(): List<FlagsParameterization> { return FlagsParameterization.allCombinationsOf().andSceneContainer() } } init { mSetFlagsRule.setFlagsParameterization(flags) } private val kosmos = testKosmos() private val testScope = kosmos.testScope Loading @@ -53,6 +72,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { private val repository = kosmos.communalSceneRepository private val underTest by lazy { kosmos.communalSceneInteractor } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun changeScene() = testScope.runTest { Loading @@ -63,6 +83,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { assertThat(currentScene).isEqualTo(CommunalScenes.Communal) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun changeScene_callsSceneStateProcessor() = testScope.runTest { Loading @@ -78,6 +99,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { verify(callback).onSceneAboutToChange(CommunalScenes.Communal, null) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun changeScene_doesNotCallSceneStateProcessorForDuplicateState() = testScope.runTest { Loading @@ -93,6 +115,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { verify(callback, never()).onSceneAboutToChange(any(), anyOrNull()) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun snapToScene() = testScope.runTest { Loading @@ -104,6 +127,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { } @OptIn(ExperimentalCoroutinesApi::class) @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun snapToSceneWithDelay() = testScope.runTest { Loading @@ -119,30 +143,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { assertThat(currentScene).isEqualTo(CommunalScenes.Communal) } @Test fun changeSceneForActivityStartOnDismissKeyguard() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) underTest.snapToScene(CommunalScenes.Communal, "test") assertThat(currentScene).isEqualTo(CommunalScenes.Communal) underTest.changeSceneForActivityStartOnDismissKeyguard() assertThat(currentScene).isEqualTo(CommunalScenes.Blank) } @Test fun changeSceneForActivityStartOnDismissKeyguard_willNotChangeScene_forEditModeActivity() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) underTest.snapToScene(CommunalScenes.Communal, "test") assertThat(currentScene).isEqualTo(CommunalScenes.Communal) underTest.setEditModeState(EditModeState.STARTING) underTest.changeSceneForActivityStartOnDismissKeyguard() assertThat(currentScene).isEqualTo(CommunalScenes.Communal) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun transitionProgress_fullProgress() = testScope.runTest { Loading @@ -161,6 +162,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { .isEqualTo(CommunalTransitionProgressModel.Idle(CommunalScenes.Communal)) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun transitionProgress_transitioningAwayFromTrackedScene() = testScope.runTest { Loading Loading @@ -201,6 +203,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { .isEqualTo(CommunalTransitionProgressModel.Idle(CommunalScenes.Communal)) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun transitionProgress_transitioningToTrackedScene() = testScope.runTest { Loading Loading @@ -238,6 +241,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { .isEqualTo(CommunalTransitionProgressModel.Idle(CommunalScenes.Communal)) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun isIdleOnCommunal() = testScope.runTest { Loading Loading @@ -265,6 +269,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { assertThat(isIdleOnCommunal).isEqualTo(false) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun isCommunalVisible() = testScope.runTest { Loading Loading @@ -304,4 +309,206 @@ class CommunalSceneInteractorTest : SysuiTestCase() { ) assertThat(isCommunalVisible).isEqualTo(true) } @EnableFlags(FLAG_SCENE_CONTAINER) @Test fun changeScene_legacyCommunalScene_mapToStfScene() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) // Verify that the current scene is the initial scene assertThat(currentScene).isEqualTo(kosmos.initialSceneKey) // Change to legacy communal scene underTest.changeScene(CommunalScenes.Communal, loggingReason = "test") // Verify that scene changed to communal scene in STF assertThat(currentScene).isEqualTo(Scenes.Communal) // Now change to legacy blank scene underTest.changeScene(CommunalScenes.Blank, loggingReason = "test") // Verify that scene changed to lock screen scene in STF assertThat(currentScene).isEqualTo(Scenes.Lockscreen) } @EnableFlags(FLAG_SCENE_CONTAINER) @Test fun changeScene_stfScenes() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) // Verify that the current scene is the initial scene assertThat(currentScene).isEqualTo(kosmos.initialSceneKey) // Change to communal scene underTest.changeScene(Scenes.Communal, loggingReason = "test") // Verify changed to communal scene assertThat(currentScene).isEqualTo(Scenes.Communal) // Now change to lockscreen scene underTest.changeScene(Scenes.Lockscreen, loggingReason = "test") // Verify changed to lockscreen scene assertThat(currentScene).isEqualTo(Scenes.Lockscreen) } @EnableFlags(FLAG_SCENE_CONTAINER) @Test fun snapToScene_legacyCommunalScene_mapToStfScene() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) // Verify that the current scene is the initial scene assertThat(currentScene).isEqualTo(kosmos.initialSceneKey) // Snap to legacy communal scene underTest.snapToScene(CommunalScenes.Communal, loggingReason = "test") // Verify that scene changed to communal scene in STF assertThat(currentScene).isEqualTo(Scenes.Communal) // Now snap to legacy blank scene underTest.snapToScene(CommunalScenes.Blank, loggingReason = "test") // Verify that scene changed to lock screen scene in STF assertThat(currentScene).isEqualTo(Scenes.Lockscreen) } @EnableFlags(FLAG_SCENE_CONTAINER) @Test fun snapToScene_stfScenes() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) // Verify that the current scene is the initial scene assertThat(currentScene).isEqualTo(kosmos.initialSceneKey) // Snap to communal scene underTest.snapToScene(Scenes.Communal, loggingReason = "test") // Verify changed to communal scene assertThat(currentScene).isEqualTo(Scenes.Communal) // Now snap to lockscreen scene underTest.snapToScene(Scenes.Lockscreen, loggingReason = "test") // Verify changed to lockscreen scene assertThat(currentScene).isEqualTo(Scenes.Lockscreen) } @EnableFlags(FLAG_SCENE_CONTAINER) @Test fun isIdleOnCommunal_sceneContainerEnabled() = testScope.runTest { val transitionState: MutableStateFlow<ObservableTransitionState> = MutableStateFlow(ObservableTransitionState.Idle(Scenes.Lockscreen)) underTest.setTransitionState(transitionState) // isIdleOnCommunal is initially false val isIdleOnCommunal by collectLastValue(underTest.isIdleOnCommunal) assertThat(isIdleOnCommunal).isEqualTo(false) // Start transition to communal. transitionState.value = ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = Scenes.Communal, currentScene = flowOf(Scenes.Lockscreen), progress = flowOf(0.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) assertThat(isIdleOnCommunal).isEqualTo(false) // Finish transition to communal transitionState.value = ObservableTransitionState.Idle(Scenes.Communal) assertThat(isIdleOnCommunal).isEqualTo(true) // Start transition away from communal transitionState.value = ObservableTransitionState.Transition( fromScene = Scenes.Communal, toScene = Scenes.Lockscreen, currentScene = flowOf(Scenes.Communal), progress = flowOf(0.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) assertThat(isIdleOnCommunal).isEqualTo(false) // Finish transition to lock screen transitionState.value = ObservableTransitionState.Idle(Scenes.Lockscreen) assertThat(isIdleOnCommunal).isEqualTo(false) } @EnableFlags(FLAG_SCENE_CONTAINER) @Test fun isCommunalVisible_sceneContainerEnabled() = testScope.runTest { val transitionState: MutableStateFlow<ObservableTransitionState> = MutableStateFlow(ObservableTransitionState.Idle(Scenes.Lockscreen)) underTest.setTransitionState(transitionState) // isCommunalVisible is initially false val isCommunalVisible by collectLastValue(underTest.isCommunalVisible) assertThat(isCommunalVisible).isEqualTo(false) // Start transition to communal. transitionState.value = ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = Scenes.Communal, currentScene = flowOf(Scenes.Lockscreen), progress = flowOf(0.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) assertThat(isCommunalVisible).isEqualTo(true) // Half-way transition to communal. transitionState.value = ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = Scenes.Communal, currentScene = flowOf(Scenes.Lockscreen), progress = flowOf(0.5f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) assertThat(isCommunalVisible).isEqualTo(true) // Finish transition to communal transitionState.value = ObservableTransitionState.Idle(Scenes.Communal) assertThat(isCommunalVisible).isEqualTo(true) // Start transition away from communal transitionState.value = ObservableTransitionState.Transition( fromScene = Scenes.Communal, toScene = Scenes.Lockscreen, currentScene = flowOf(Scenes.Communal), progress = flowOf(0.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) assertThat(isCommunalVisible).isEqualTo(true) // Half-way transition away from communal transitionState.value = ObservableTransitionState.Transition( fromScene = Scenes.Communal, toScene = Scenes.Lockscreen, currentScene = flowOf(Scenes.Communal), progress = flowOf(0.5f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) assertThat(isCommunalVisible).isEqualTo(true) // Finish transition to lock screen transitionState.value = ObservableTransitionState.Idle(Scenes.Lockscreen) assertThat(isCommunalVisible).isEqualTo(false) } } packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +1 −0 Original line number Diff line number Diff line Loading @@ -335,6 +335,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test @DisableSceneContainer fun alpha_idleOnHub_isZero() = testScope.runTest { val alpha by collectLastValue(underTest.alpha(viewState)) Loading packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt +79 −39 Original line number Diff line number Diff line Loading @@ -24,11 +24,14 @@ import com.android.systemui.communal.data.repository.CommunalSceneRepository import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel import com.android.systemui.communal.shared.log.CommunalSceneLogger import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.shared.model.CommunalScenes.toSceneContainerSceneKey import com.android.systemui.communal.shared.model.EditModeState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.pairwiseBy import javax.inject.Inject Loading @@ -45,6 +48,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @OptIn(ExperimentalCoroutinesApi::class) Loading @@ -55,6 +59,7 @@ constructor( @Application private val applicationScope: CoroutineScope, private val repository: CommunalSceneRepository, private val logger: CommunalSceneLogger, private val sceneInteractor: SceneInteractor, ) { private val _isLaunchingWidget = MutableStateFlow(false) Loading @@ -72,8 +77,14 @@ constructor( private val onSceneAboutToChangeListener = mutableSetOf<OnSceneAboutToChangeListener>() /** Registers a listener which is called when the scene is about to change. */ /** * Registers a listener which is called when the scene is about to change. * * This API is for legacy communal container scenes, and should not be used when * [SceneContainerFlag] is enabled. */ fun registerSceneStateProcessor(processor: OnSceneAboutToChangeListener) { SceneContainerFlag.assertInLegacyMode() onSceneAboutToChangeListener.add(processor) } Loading @@ -87,6 +98,15 @@ constructor( transitionKey: TransitionKey? = null, keyguardState: KeyguardState? = null, ) { if (SceneContainerFlag.isEnabled) { return sceneInteractor.changeScene( toScene = newScene.toSceneContainerSceneKey(), loggingReason = loggingReason, transitionKey = transitionKey, sceneState = keyguardState, ) } applicationScope.launch("$TAG#changeScene") { if (currentScene.value == newScene) return@launch logger.logSceneChangeRequested( Loading @@ -107,6 +127,13 @@ constructor( delayMillis: Long = 0, keyguardState: KeyguardState? = null ) { if (SceneContainerFlag.isEnabled) { return sceneInteractor.snapToScene( toScene = newScene.toSceneContainerSceneKey(), loggingReason = loggingReason, ) } applicationScope.launch("$TAG#snapToScene") { delay(delayMillis) if (currentScene.value == newScene) return@launch Loading @@ -125,24 +152,13 @@ constructor( onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(newScene, keyguardState) } } /** Changes to Blank scene when starting an activity after dismissing keyguard. */ fun changeSceneForActivityStartOnDismissKeyguard() { // skip if we're starting edit mode activity, as it will be handled later by changeScene // with transition key [CommunalTransitionKeys.ToEditMode]. if (_editModeState.value == EditModeState.STARTING) { return } changeScene( CommunalScenes.Blank, "activity start dismissing keyguard", CommunalTransitionKeys.SimpleFade, ) } /** * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene]. */ val currentScene: StateFlow<SceneKey> = if (SceneContainerFlag.isEnabled) { sceneInteractor.currentScene } else { repository.currentScene .pairwiseBy(initialValue = repository.currentScene.value) { from, to -> logger.logSceneChangeCommitted( Loading @@ -156,6 +172,7 @@ constructor( started = SharingStarted.Eagerly, initialValue = repository.currentScene.value, ) } private val _editModeState = MutableStateFlow<EditModeState?>(null) /** Loading @@ -170,6 +187,9 @@ constructor( /** Transition state of the hub mode. */ val transitionState: StateFlow<ObservableTransitionState> = if (SceneContainerFlag.isEnabled) { sceneInteractor.transitionState } else { repository.transitionState .onEach { logger.logSceneTransition(it) } .stateIn( Loading @@ -177,6 +197,7 @@ constructor( started = SharingStarted.Eagerly, initialValue = repository.transitionState.value, ) } /** * Updates the transition state of the hub [SceneTransitionLayout]. Loading @@ -184,10 +205,19 @@ constructor( * Note that you must call is with `null` when the UI is done or risk a memory leak. */ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { if (SceneContainerFlag.isEnabled) { sceneInteractor.setTransitionState(transitionState) } else { repository.setTransitionState(transitionState) } } /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */ /** * Returns a flow that tracks the progress of transitions to the given scene from 0-1. * * This API is for legacy communal container scenes, and should not be used when * [SceneContainerFlag] is enabled. */ fun transitionProgressToScene(targetScene: SceneKey) = transitionState .flatMapLatest { state -> Loading @@ -209,6 +239,7 @@ constructor( } } .distinctUntilChanged() .onStart { SceneContainerFlag.assertInLegacyMode() } /** * Flow that emits a boolean if the communal UI is fully visible and not in transition. Loading @@ -219,7 +250,10 @@ constructor( val isIdleOnCommunal: StateFlow<Boolean> = transitionState .map { it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Communal it is ObservableTransitionState.Idle && (it.currentScene == if (SceneContainerFlag.isEnabled) Scenes.Communal else CommunalScenes.Communal) } .stateIn( scope = applicationScope, Loading @@ -239,7 +273,13 @@ constructor( val isCommunalVisible: StateFlow<Boolean> = transitionState .map { !(it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Blank) if (SceneContainerFlag.isEnabled) it is ObservableTransitionState.Idle && it.currentScene == Scenes.Communal || (it is ObservableTransitionState.Transition && (it.fromContent == Scenes.Communal || it.toContent == Scenes.Communal)) else !(it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Blank) } .stateIn( scope = applicationScope, Loading packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalScenes.kt +28 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package com.android.systemui.communal.shared.model import com.android.compose.animation.scene.SceneKey import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes /** Definition of the possible scenes for the communal UI. */ object CommunalScenes { Loading @@ -27,4 +29,30 @@ object CommunalScenes { @JvmField val Communal = SceneKey("communal") @JvmField val Default = Blank private fun SceneKey.isCommunalScene(): Boolean { return this == Blank || this == Communal } /** * Maps a legacy communal scene to a scene in the scene container. * * The rules are simple: * - A legacy communal scene maps to a communal scene in the Scene Transition Framework (STF). * - A legacy blank scene means that the communal scene layout does not render anything so * whatever is beneath the layout is shown. That usually means lockscreen or dream, both of * which are represented by the lockscreen scene in STF (but different keyguard states in * KTF). */ fun SceneKey.toSceneContainerSceneKey(): SceneKey { if (!isCommunalScene() || !SceneContainerFlag.isEnabled) { return this } return when (this) { Communal -> Scenes.Communal Blank -> Scenes.Lockscreen else -> throw Throwable("Unrecognized communal scene: $this") } } } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +17 −8 Original line number Diff line number Diff line Loading @@ -664,22 +664,31 @@ class CommunalInteractorTest : SysuiTestCase() { testScope.runTest { // Verify default is false val isCommunalShowing by collectLastValue(underTest.isCommunalShowing) runCurrent() assertThat(isCommunalShowing).isFalse() // Verify scene changes without the flag doesn't have any impact underTest.changeScene(CommunalScenes.Communal, "test") runCurrent() assertThat(isCommunalShowing).isFalse() // Verify scene changes (with the flag) to communal sets the value to true sceneInteractor.changeScene(Scenes.Communal, loggingReason = "") runCurrent() assertThat(isCommunalShowing).isTrue() // Verify scene changes (with the flag) to lockscreen sets the value to false sceneInteractor.changeScene(Scenes.Lockscreen, loggingReason = "") runCurrent() assertThat(isCommunalShowing).isFalse() } @Test @EnableSceneContainer fun isCommunalShowing_whenSceneContainerEnabledAndChangeToLegacyScene() = testScope.runTest { // Verify default is false val isCommunalShowing by collectLastValue(underTest.isCommunalShowing) assertThat(isCommunalShowing).isFalse() // Verify legacy scene change still makes communal show underTest.changeScene(CommunalScenes.Communal, "test") assertThat(isCommunalShowing).isTrue() // Verify legacy scene change to blank makes communal hidden underTest.changeScene(CommunalScenes.Blank, "test") assertThat(isCommunalShowing).isFalse() } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt +235 −28 Original line number Diff line number Diff line Loading @@ -16,18 +16,23 @@ package com.android.systemui.communal.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.communal.data.repository.communalSceneRepository import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor.OnSceneAboutToChangeListener import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.EditModeState import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.andSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.scene.initialSceneKey import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi Loading @@ -42,10 +47,24 @@ import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(AndroidJUnit4::class) class CommunalSceneInteractorTest : SysuiTestCase() { @RunWith(ParameterizedAndroidJunit4::class) class CommunalSceneInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { companion object { @JvmStatic @Parameters(name = "{0}") fun getParams(): List<FlagsParameterization> { return FlagsParameterization.allCombinationsOf().andSceneContainer() } } init { mSetFlagsRule.setFlagsParameterization(flags) } private val kosmos = testKosmos() private val testScope = kosmos.testScope Loading @@ -53,6 +72,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { private val repository = kosmos.communalSceneRepository private val underTest by lazy { kosmos.communalSceneInteractor } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun changeScene() = testScope.runTest { Loading @@ -63,6 +83,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { assertThat(currentScene).isEqualTo(CommunalScenes.Communal) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun changeScene_callsSceneStateProcessor() = testScope.runTest { Loading @@ -78,6 +99,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { verify(callback).onSceneAboutToChange(CommunalScenes.Communal, null) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun changeScene_doesNotCallSceneStateProcessorForDuplicateState() = testScope.runTest { Loading @@ -93,6 +115,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { verify(callback, never()).onSceneAboutToChange(any(), anyOrNull()) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun snapToScene() = testScope.runTest { Loading @@ -104,6 +127,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { } @OptIn(ExperimentalCoroutinesApi::class) @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun snapToSceneWithDelay() = testScope.runTest { Loading @@ -119,30 +143,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { assertThat(currentScene).isEqualTo(CommunalScenes.Communal) } @Test fun changeSceneForActivityStartOnDismissKeyguard() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) underTest.snapToScene(CommunalScenes.Communal, "test") assertThat(currentScene).isEqualTo(CommunalScenes.Communal) underTest.changeSceneForActivityStartOnDismissKeyguard() assertThat(currentScene).isEqualTo(CommunalScenes.Blank) } @Test fun changeSceneForActivityStartOnDismissKeyguard_willNotChangeScene_forEditModeActivity() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) underTest.snapToScene(CommunalScenes.Communal, "test") assertThat(currentScene).isEqualTo(CommunalScenes.Communal) underTest.setEditModeState(EditModeState.STARTING) underTest.changeSceneForActivityStartOnDismissKeyguard() assertThat(currentScene).isEqualTo(CommunalScenes.Communal) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun transitionProgress_fullProgress() = testScope.runTest { Loading @@ -161,6 +162,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { .isEqualTo(CommunalTransitionProgressModel.Idle(CommunalScenes.Communal)) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun transitionProgress_transitioningAwayFromTrackedScene() = testScope.runTest { Loading Loading @@ -201,6 +203,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { .isEqualTo(CommunalTransitionProgressModel.Idle(CommunalScenes.Communal)) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun transitionProgress_transitioningToTrackedScene() = testScope.runTest { Loading Loading @@ -238,6 +241,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { .isEqualTo(CommunalTransitionProgressModel.Idle(CommunalScenes.Communal)) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun isIdleOnCommunal() = testScope.runTest { Loading Loading @@ -265,6 +269,7 @@ class CommunalSceneInteractorTest : SysuiTestCase() { assertThat(isIdleOnCommunal).isEqualTo(false) } @DisableFlags(FLAG_SCENE_CONTAINER) @Test fun isCommunalVisible() = testScope.runTest { Loading Loading @@ -304,4 +309,206 @@ class CommunalSceneInteractorTest : SysuiTestCase() { ) assertThat(isCommunalVisible).isEqualTo(true) } @EnableFlags(FLAG_SCENE_CONTAINER) @Test fun changeScene_legacyCommunalScene_mapToStfScene() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) // Verify that the current scene is the initial scene assertThat(currentScene).isEqualTo(kosmos.initialSceneKey) // Change to legacy communal scene underTest.changeScene(CommunalScenes.Communal, loggingReason = "test") // Verify that scene changed to communal scene in STF assertThat(currentScene).isEqualTo(Scenes.Communal) // Now change to legacy blank scene underTest.changeScene(CommunalScenes.Blank, loggingReason = "test") // Verify that scene changed to lock screen scene in STF assertThat(currentScene).isEqualTo(Scenes.Lockscreen) } @EnableFlags(FLAG_SCENE_CONTAINER) @Test fun changeScene_stfScenes() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) // Verify that the current scene is the initial scene assertThat(currentScene).isEqualTo(kosmos.initialSceneKey) // Change to communal scene underTest.changeScene(Scenes.Communal, loggingReason = "test") // Verify changed to communal scene assertThat(currentScene).isEqualTo(Scenes.Communal) // Now change to lockscreen scene underTest.changeScene(Scenes.Lockscreen, loggingReason = "test") // Verify changed to lockscreen scene assertThat(currentScene).isEqualTo(Scenes.Lockscreen) } @EnableFlags(FLAG_SCENE_CONTAINER) @Test fun snapToScene_legacyCommunalScene_mapToStfScene() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) // Verify that the current scene is the initial scene assertThat(currentScene).isEqualTo(kosmos.initialSceneKey) // Snap to legacy communal scene underTest.snapToScene(CommunalScenes.Communal, loggingReason = "test") // Verify that scene changed to communal scene in STF assertThat(currentScene).isEqualTo(Scenes.Communal) // Now snap to legacy blank scene underTest.snapToScene(CommunalScenes.Blank, loggingReason = "test") // Verify that scene changed to lock screen scene in STF assertThat(currentScene).isEqualTo(Scenes.Lockscreen) } @EnableFlags(FLAG_SCENE_CONTAINER) @Test fun snapToScene_stfScenes() = testScope.runTest { val currentScene by collectLastValue(underTest.currentScene) // Verify that the current scene is the initial scene assertThat(currentScene).isEqualTo(kosmos.initialSceneKey) // Snap to communal scene underTest.snapToScene(Scenes.Communal, loggingReason = "test") // Verify changed to communal scene assertThat(currentScene).isEqualTo(Scenes.Communal) // Now snap to lockscreen scene underTest.snapToScene(Scenes.Lockscreen, loggingReason = "test") // Verify changed to lockscreen scene assertThat(currentScene).isEqualTo(Scenes.Lockscreen) } @EnableFlags(FLAG_SCENE_CONTAINER) @Test fun isIdleOnCommunal_sceneContainerEnabled() = testScope.runTest { val transitionState: MutableStateFlow<ObservableTransitionState> = MutableStateFlow(ObservableTransitionState.Idle(Scenes.Lockscreen)) underTest.setTransitionState(transitionState) // isIdleOnCommunal is initially false val isIdleOnCommunal by collectLastValue(underTest.isIdleOnCommunal) assertThat(isIdleOnCommunal).isEqualTo(false) // Start transition to communal. transitionState.value = ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = Scenes.Communal, currentScene = flowOf(Scenes.Lockscreen), progress = flowOf(0.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) assertThat(isIdleOnCommunal).isEqualTo(false) // Finish transition to communal transitionState.value = ObservableTransitionState.Idle(Scenes.Communal) assertThat(isIdleOnCommunal).isEqualTo(true) // Start transition away from communal transitionState.value = ObservableTransitionState.Transition( fromScene = Scenes.Communal, toScene = Scenes.Lockscreen, currentScene = flowOf(Scenes.Communal), progress = flowOf(0.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) assertThat(isIdleOnCommunal).isEqualTo(false) // Finish transition to lock screen transitionState.value = ObservableTransitionState.Idle(Scenes.Lockscreen) assertThat(isIdleOnCommunal).isEqualTo(false) } @EnableFlags(FLAG_SCENE_CONTAINER) @Test fun isCommunalVisible_sceneContainerEnabled() = testScope.runTest { val transitionState: MutableStateFlow<ObservableTransitionState> = MutableStateFlow(ObservableTransitionState.Idle(Scenes.Lockscreen)) underTest.setTransitionState(transitionState) // isCommunalVisible is initially false val isCommunalVisible by collectLastValue(underTest.isCommunalVisible) assertThat(isCommunalVisible).isEqualTo(false) // Start transition to communal. transitionState.value = ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = Scenes.Communal, currentScene = flowOf(Scenes.Lockscreen), progress = flowOf(0.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) assertThat(isCommunalVisible).isEqualTo(true) // Half-way transition to communal. transitionState.value = ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = Scenes.Communal, currentScene = flowOf(Scenes.Lockscreen), progress = flowOf(0.5f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) assertThat(isCommunalVisible).isEqualTo(true) // Finish transition to communal transitionState.value = ObservableTransitionState.Idle(Scenes.Communal) assertThat(isCommunalVisible).isEqualTo(true) // Start transition away from communal transitionState.value = ObservableTransitionState.Transition( fromScene = Scenes.Communal, toScene = Scenes.Lockscreen, currentScene = flowOf(Scenes.Communal), progress = flowOf(0.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) assertThat(isCommunalVisible).isEqualTo(true) // Half-way transition away from communal transitionState.value = ObservableTransitionState.Transition( fromScene = Scenes.Communal, toScene = Scenes.Lockscreen, currentScene = flowOf(Scenes.Communal), progress = flowOf(0.5f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) assertThat(isCommunalVisible).isEqualTo(true) // Finish transition to lock screen transitionState.value = ObservableTransitionState.Idle(Scenes.Lockscreen) assertThat(isCommunalVisible).isEqualTo(false) } }
packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +1 −0 Original line number Diff line number Diff line Loading @@ -335,6 +335,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() } @Test @DisableSceneContainer fun alpha_idleOnHub_isZero() = testScope.runTest { val alpha by collectLastValue(underTest.alpha(viewState)) Loading
packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt +79 −39 Original line number Diff line number Diff line Loading @@ -24,11 +24,14 @@ import com.android.systemui.communal.data.repository.CommunalSceneRepository import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel import com.android.systemui.communal.shared.log.CommunalSceneLogger import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.shared.model.CommunalScenes.toSceneContainerSceneKey import com.android.systemui.communal.shared.model.EditModeState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.pairwiseBy import javax.inject.Inject Loading @@ -45,6 +48,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @OptIn(ExperimentalCoroutinesApi::class) Loading @@ -55,6 +59,7 @@ constructor( @Application private val applicationScope: CoroutineScope, private val repository: CommunalSceneRepository, private val logger: CommunalSceneLogger, private val sceneInteractor: SceneInteractor, ) { private val _isLaunchingWidget = MutableStateFlow(false) Loading @@ -72,8 +77,14 @@ constructor( private val onSceneAboutToChangeListener = mutableSetOf<OnSceneAboutToChangeListener>() /** Registers a listener which is called when the scene is about to change. */ /** * Registers a listener which is called when the scene is about to change. * * This API is for legacy communal container scenes, and should not be used when * [SceneContainerFlag] is enabled. */ fun registerSceneStateProcessor(processor: OnSceneAboutToChangeListener) { SceneContainerFlag.assertInLegacyMode() onSceneAboutToChangeListener.add(processor) } Loading @@ -87,6 +98,15 @@ constructor( transitionKey: TransitionKey? = null, keyguardState: KeyguardState? = null, ) { if (SceneContainerFlag.isEnabled) { return sceneInteractor.changeScene( toScene = newScene.toSceneContainerSceneKey(), loggingReason = loggingReason, transitionKey = transitionKey, sceneState = keyguardState, ) } applicationScope.launch("$TAG#changeScene") { if (currentScene.value == newScene) return@launch logger.logSceneChangeRequested( Loading @@ -107,6 +127,13 @@ constructor( delayMillis: Long = 0, keyguardState: KeyguardState? = null ) { if (SceneContainerFlag.isEnabled) { return sceneInteractor.snapToScene( toScene = newScene.toSceneContainerSceneKey(), loggingReason = loggingReason, ) } applicationScope.launch("$TAG#snapToScene") { delay(delayMillis) if (currentScene.value == newScene) return@launch Loading @@ -125,24 +152,13 @@ constructor( onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(newScene, keyguardState) } } /** Changes to Blank scene when starting an activity after dismissing keyguard. */ fun changeSceneForActivityStartOnDismissKeyguard() { // skip if we're starting edit mode activity, as it will be handled later by changeScene // with transition key [CommunalTransitionKeys.ToEditMode]. if (_editModeState.value == EditModeState.STARTING) { return } changeScene( CommunalScenes.Blank, "activity start dismissing keyguard", CommunalTransitionKeys.SimpleFade, ) } /** * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene]. */ val currentScene: StateFlow<SceneKey> = if (SceneContainerFlag.isEnabled) { sceneInteractor.currentScene } else { repository.currentScene .pairwiseBy(initialValue = repository.currentScene.value) { from, to -> logger.logSceneChangeCommitted( Loading @@ -156,6 +172,7 @@ constructor( started = SharingStarted.Eagerly, initialValue = repository.currentScene.value, ) } private val _editModeState = MutableStateFlow<EditModeState?>(null) /** Loading @@ -170,6 +187,9 @@ constructor( /** Transition state of the hub mode. */ val transitionState: StateFlow<ObservableTransitionState> = if (SceneContainerFlag.isEnabled) { sceneInteractor.transitionState } else { repository.transitionState .onEach { logger.logSceneTransition(it) } .stateIn( Loading @@ -177,6 +197,7 @@ constructor( started = SharingStarted.Eagerly, initialValue = repository.transitionState.value, ) } /** * Updates the transition state of the hub [SceneTransitionLayout]. Loading @@ -184,10 +205,19 @@ constructor( * Note that you must call is with `null` when the UI is done or risk a memory leak. */ fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { if (SceneContainerFlag.isEnabled) { sceneInteractor.setTransitionState(transitionState) } else { repository.setTransitionState(transitionState) } } /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */ /** * Returns a flow that tracks the progress of transitions to the given scene from 0-1. * * This API is for legacy communal container scenes, and should not be used when * [SceneContainerFlag] is enabled. */ fun transitionProgressToScene(targetScene: SceneKey) = transitionState .flatMapLatest { state -> Loading @@ -209,6 +239,7 @@ constructor( } } .distinctUntilChanged() .onStart { SceneContainerFlag.assertInLegacyMode() } /** * Flow that emits a boolean if the communal UI is fully visible and not in transition. Loading @@ -219,7 +250,10 @@ constructor( val isIdleOnCommunal: StateFlow<Boolean> = transitionState .map { it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Communal it is ObservableTransitionState.Idle && (it.currentScene == if (SceneContainerFlag.isEnabled) Scenes.Communal else CommunalScenes.Communal) } .stateIn( scope = applicationScope, Loading @@ -239,7 +273,13 @@ constructor( val isCommunalVisible: StateFlow<Boolean> = transitionState .map { !(it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Blank) if (SceneContainerFlag.isEnabled) it is ObservableTransitionState.Idle && it.currentScene == Scenes.Communal || (it is ObservableTransitionState.Transition && (it.fromContent == Scenes.Communal || it.toContent == Scenes.Communal)) else !(it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Blank) } .stateIn( scope = applicationScope, Loading
packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalScenes.kt +28 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package com.android.systemui.communal.shared.model import com.android.compose.animation.scene.SceneKey import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes /** Definition of the possible scenes for the communal UI. */ object CommunalScenes { Loading @@ -27,4 +29,30 @@ object CommunalScenes { @JvmField val Communal = SceneKey("communal") @JvmField val Default = Blank private fun SceneKey.isCommunalScene(): Boolean { return this == Blank || this == Communal } /** * Maps a legacy communal scene to a scene in the scene container. * * The rules are simple: * - A legacy communal scene maps to a communal scene in the Scene Transition Framework (STF). * - A legacy blank scene means that the communal scene layout does not render anything so * whatever is beneath the layout is shown. That usually means lockscreen or dream, both of * which are represented by the lockscreen scene in STF (but different keyguard states in * KTF). */ fun SceneKey.toSceneContainerSceneKey(): SceneKey { if (!isCommunalScene() || !SceneContainerFlag.isEnabled) { return this } return when (this) { Communal -> Scenes.Communal Blank -> Scenes.Lockscreen else -> throw Throwable("Unrecognized communal scene: $this") } } }