Loading packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +34 −8 Original line number Original line Diff line number Diff line Loading @@ -265,7 +265,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { with(kosmos) { testScope.runTest { testScope.runTest { // Device is dreaming and on communal. // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) val scene by collectLastValue(communalInteractor.desiredScene) Loading @@ -282,7 +282,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { with(kosmos) { testScope.runTest { testScope.runTest { // Device is not dreaming and on communal. // Device is not dreaming and on communal. fakeKeyguardRepository.setDreaming(false) updateDreaming(false) communalInteractor.changeScene(CommunalScenes.Communal) communalInteractor.changeScene(CommunalScenes.Communal) // Scene stays as Communal // Scene stays as Communal Loading @@ -297,7 +297,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { with(kosmos) { testScope.runTest { testScope.runTest { // Device is dreaming and on communal. // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) val scene by collectLastValue(communalInteractor.desiredScene) Loading @@ -309,7 +309,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { // Dream stops, timeout is cancelled and device stays on hub, because the regular // Dream stops, timeout is cancelled and device stays on hub, because the regular // screen timeout will take effect at this point. // screen timeout will take effect at this point. fakeKeyguardRepository.setDreaming(false) updateDreaming(false) advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Communal) assertThat(scene).isEqualTo(CommunalScenes.Communal) } } Loading @@ -320,7 +320,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { with(kosmos) { testScope.runTest { testScope.runTest { // Device is on communal, but not dreaming. // Device is on communal, but not dreaming. fakeKeyguardRepository.setDreaming(false) updateDreaming(false) communalInteractor.changeScene(CommunalScenes.Communal) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) val scene by collectLastValue(communalInteractor.desiredScene) Loading @@ -328,7 +328,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { // Wait a bit, but not long enough to timeout, then start dreaming. // Wait a bit, but not long enough to timeout, then start dreaming. advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) fakeKeyguardRepository.setDreaming(true) updateDreaming(true) assertThat(scene).isEqualTo(CommunalScenes.Communal) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Device times out after one screen timeout interval, dream doesn't reset timeout. // Device times out after one screen timeout interval, dream doesn't reset timeout. Loading @@ -337,12 +337,32 @@ class CommunalSceneStartableTest : SysuiTestCase() { } } } } @Test fun hubTimeout_dreamAfterInitialTimeout_goesToBlank() = with(kosmos) { testScope.runTest { // Device is on communal. communalInteractor.changeScene(CommunalScenes.Communal) // Device stays on the hub after the timeout since we're not dreaming. advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Start dreaming. updateDreaming(true) // Hub times out immediately. assertThat(scene).isEqualTo(CommunalScenes.Blank) } } @Test @Test fun hubTimeout_userActivityTriggered_resetsTimeout() = fun hubTimeout_userActivityTriggered_resetsTimeout() = with(kosmos) { with(kosmos) { testScope.runTest { testScope.runTest { // Device is dreaming and on communal. // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) val scene by collectLastValue(communalInteractor.desiredScene) Loading Loading @@ -371,7 +391,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2) fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2) // Device is dreaming and on communal. // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) val scene by collectLastValue(communalInteractor.desiredScene) Loading @@ -395,6 +415,12 @@ class CommunalSceneStartableTest : SysuiTestCase() { runCurrent() runCurrent() } } private fun TestScope.updateDreaming(dreaming: Boolean) = with(kosmos) { fakeKeyguardRepository.setDreaming(dreaming) runCurrent() } private suspend fun TestScope.enableCommunal() = private suspend fun TestScope.enableCommunal() = with(kosmos) { with(kosmos) { setCommunalAvailable(true) setCommunalAvailable(true) Loading packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +42 −15 Original line number Original line Diff line number Diff line Loading @@ -42,6 +42,7 @@ import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine Loading Loading @@ -73,6 +74,10 @@ constructor( ) : CoreStartable { ) : CoreStartable { private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT private var timeoutJob: Job? = null private var isDreaming: Boolean = false override fun start() { override fun start() { // Handle automatically switching based on keyguard state. // Handle automatically switching based on keyguard state. keyguardTransitionInteractor.startedKeyguardTransitionStep keyguardTransitionInteractor.startedKeyguardTransitionStep Loading Loading @@ -112,31 +117,35 @@ constructor( } } .launchIn(bgScope) .launchIn(bgScope) // Handle timing out back to the dream. // The hub mode timeout should start as soon as the user enters hub mode. At the end of the // timer, if the device is dreaming, hub mode should closed and reveal the dream. If the // dream is not running, nothing will happen. However if the dream starts again underneath // hub mode after the initial timeout expires, such as if the device is docked or the dream // app is updated by the Play store, a new timeout should be started. bgScope.launch { bgScope.launch { combine( combine( communalInteractor.desiredScene, communalInteractor.desiredScene, // Emit a value on start so the combine starts. // Emit a value on start so the combine starts. communalInteractor.userActivity.emitOnStart() communalInteractor.userActivity.emitOnStart() ) { scene, _ -> ) { scene, _ -> // Time out should run whenever we're dreaming and the hub is open, even if not // Only timeout if we're on the hub is open. // docked. scene == CommunalScenes.Communal scene == CommunalScenes.Communal } } // mapLatest cancels the previous action block when new values arrive, so any .collectLatest { shouldTimeout -> // already running timeout gets cancelled when conditions change or user interaction cancelHubTimeout() // is detected. if (shouldTimeout) { .mapLatest { shouldTimeout -> startHubTimeout() if (!shouldTimeout) { return@mapLatest false } } delay(screenTimeout.milliseconds) true } } .sample(keyguardInteractor.isDreaming, ::Pair) } .collect { (shouldTimeout, isDreaming) -> bgScope.launch { if (isDreaming && shouldTimeout) { keyguardInteractor.isDreaming .sample(communalInteractor.desiredScene, ::Pair) .collectLatest { (isDreaming, scene) -> this@CommunalSceneStartable.isDreaming = isDreaming if (scene == CommunalScenes.Communal && isDreaming && timeoutJob == null) { // If dreaming starts after timeout has expired, ex. if dream restarts under // the hub, just close the hub immediately. communalInteractor.changeScene(CommunalScenes.Blank) communalInteractor.changeScene(CommunalScenes.Blank) } } } } Loading @@ -151,6 +160,24 @@ constructor( } } } } private fun cancelHubTimeout() { timeoutJob?.cancel() timeoutJob = null } private fun startHubTimeout() { if (timeoutJob == null) { timeoutJob = bgScope.launch { delay(screenTimeout.milliseconds) if (isDreaming) { communalInteractor.changeScene(CommunalScenes.Blank) } timeoutJob = null } } } private suspend fun determineSceneAfterTransition( private suspend fun determineSceneAfterTransition( lastStartedTransition: TransitionStep, lastStartedTransition: TransitionStep, ): SceneKey? { ): SceneKey? { Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +34 −8 Original line number Original line Diff line number Diff line Loading @@ -265,7 +265,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { with(kosmos) { testScope.runTest { testScope.runTest { // Device is dreaming and on communal. // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) val scene by collectLastValue(communalInteractor.desiredScene) Loading @@ -282,7 +282,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { with(kosmos) { testScope.runTest { testScope.runTest { // Device is not dreaming and on communal. // Device is not dreaming and on communal. fakeKeyguardRepository.setDreaming(false) updateDreaming(false) communalInteractor.changeScene(CommunalScenes.Communal) communalInteractor.changeScene(CommunalScenes.Communal) // Scene stays as Communal // Scene stays as Communal Loading @@ -297,7 +297,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { with(kosmos) { testScope.runTest { testScope.runTest { // Device is dreaming and on communal. // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) val scene by collectLastValue(communalInteractor.desiredScene) Loading @@ -309,7 +309,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { // Dream stops, timeout is cancelled and device stays on hub, because the regular // Dream stops, timeout is cancelled and device stays on hub, because the regular // screen timeout will take effect at this point. // screen timeout will take effect at this point. fakeKeyguardRepository.setDreaming(false) updateDreaming(false) advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Communal) assertThat(scene).isEqualTo(CommunalScenes.Communal) } } Loading @@ -320,7 +320,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { with(kosmos) { testScope.runTest { testScope.runTest { // Device is on communal, but not dreaming. // Device is on communal, but not dreaming. fakeKeyguardRepository.setDreaming(false) updateDreaming(false) communalInteractor.changeScene(CommunalScenes.Communal) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) val scene by collectLastValue(communalInteractor.desiredScene) Loading @@ -328,7 +328,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { // Wait a bit, but not long enough to timeout, then start dreaming. // Wait a bit, but not long enough to timeout, then start dreaming. advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) fakeKeyguardRepository.setDreaming(true) updateDreaming(true) assertThat(scene).isEqualTo(CommunalScenes.Communal) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Device times out after one screen timeout interval, dream doesn't reset timeout. // Device times out after one screen timeout interval, dream doesn't reset timeout. Loading @@ -337,12 +337,32 @@ class CommunalSceneStartableTest : SysuiTestCase() { } } } } @Test fun hubTimeout_dreamAfterInitialTimeout_goesToBlank() = with(kosmos) { testScope.runTest { // Device is on communal. communalInteractor.changeScene(CommunalScenes.Communal) // Device stays on the hub after the timeout since we're not dreaming. advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Start dreaming. updateDreaming(true) // Hub times out immediately. assertThat(scene).isEqualTo(CommunalScenes.Blank) } } @Test @Test fun hubTimeout_userActivityTriggered_resetsTimeout() = fun hubTimeout_userActivityTriggered_resetsTimeout() = with(kosmos) { with(kosmos) { testScope.runTest { testScope.runTest { // Device is dreaming and on communal. // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) val scene by collectLastValue(communalInteractor.desiredScene) Loading Loading @@ -371,7 +391,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2) fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2) // Device is dreaming and on communal. // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) val scene by collectLastValue(communalInteractor.desiredScene) Loading @@ -395,6 +415,12 @@ class CommunalSceneStartableTest : SysuiTestCase() { runCurrent() runCurrent() } } private fun TestScope.updateDreaming(dreaming: Boolean) = with(kosmos) { fakeKeyguardRepository.setDreaming(dreaming) runCurrent() } private suspend fun TestScope.enableCommunal() = private suspend fun TestScope.enableCommunal() = with(kosmos) { with(kosmos) { setCommunalAvailable(true) setCommunalAvailable(true) Loading
packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +42 −15 Original line number Original line Diff line number Diff line Loading @@ -42,6 +42,7 @@ import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine Loading Loading @@ -73,6 +74,10 @@ constructor( ) : CoreStartable { ) : CoreStartable { private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT private var timeoutJob: Job? = null private var isDreaming: Boolean = false override fun start() { override fun start() { // Handle automatically switching based on keyguard state. // Handle automatically switching based on keyguard state. keyguardTransitionInteractor.startedKeyguardTransitionStep keyguardTransitionInteractor.startedKeyguardTransitionStep Loading Loading @@ -112,31 +117,35 @@ constructor( } } .launchIn(bgScope) .launchIn(bgScope) // Handle timing out back to the dream. // The hub mode timeout should start as soon as the user enters hub mode. At the end of the // timer, if the device is dreaming, hub mode should closed and reveal the dream. If the // dream is not running, nothing will happen. However if the dream starts again underneath // hub mode after the initial timeout expires, such as if the device is docked or the dream // app is updated by the Play store, a new timeout should be started. bgScope.launch { bgScope.launch { combine( combine( communalInteractor.desiredScene, communalInteractor.desiredScene, // Emit a value on start so the combine starts. // Emit a value on start so the combine starts. communalInteractor.userActivity.emitOnStart() communalInteractor.userActivity.emitOnStart() ) { scene, _ -> ) { scene, _ -> // Time out should run whenever we're dreaming and the hub is open, even if not // Only timeout if we're on the hub is open. // docked. scene == CommunalScenes.Communal scene == CommunalScenes.Communal } } // mapLatest cancels the previous action block when new values arrive, so any .collectLatest { shouldTimeout -> // already running timeout gets cancelled when conditions change or user interaction cancelHubTimeout() // is detected. if (shouldTimeout) { .mapLatest { shouldTimeout -> startHubTimeout() if (!shouldTimeout) { return@mapLatest false } } delay(screenTimeout.milliseconds) true } } .sample(keyguardInteractor.isDreaming, ::Pair) } .collect { (shouldTimeout, isDreaming) -> bgScope.launch { if (isDreaming && shouldTimeout) { keyguardInteractor.isDreaming .sample(communalInteractor.desiredScene, ::Pair) .collectLatest { (isDreaming, scene) -> this@CommunalSceneStartable.isDreaming = isDreaming if (scene == CommunalScenes.Communal && isDreaming && timeoutJob == null) { // If dreaming starts after timeout has expired, ex. if dream restarts under // the hub, just close the hub immediately. communalInteractor.changeScene(CommunalScenes.Blank) communalInteractor.changeScene(CommunalScenes.Blank) } } } } Loading @@ -151,6 +160,24 @@ constructor( } } } } private fun cancelHubTimeout() { timeoutJob?.cancel() timeoutJob = null } private fun startHubTimeout() { if (timeoutJob == null) { timeoutJob = bgScope.launch { delay(screenTimeout.milliseconds) if (isDreaming) { communalInteractor.changeScene(CommunalScenes.Blank) } timeoutJob = null } } } private suspend fun determineSceneAfterTransition( private suspend fun determineSceneAfterTransition( lastStartedTransition: TransitionStep, lastStartedTransition: TransitionStep, ): SceneKey? { ): SceneKey? { Loading