From cdeb4bb5458272a557da2b4b66db908e4bbadc08 Mon Sep 17 00:00:00 2001 From: Lucas Silva Date: Tue, 11 Feb 2025 11:43:10 -0500 Subject: Remove custom timeout logic from hub v2 In v2, we can let PowerManagerService handle the timeout behavior since we no longer keep the dream active underneath the hub. Bug: 394915563 Test: atest CommunalSceneStartableTest Flag: com.android.systemui.glanceable_hub_v2 Change-Id: I24d7db4c09e17a4852cbc9d842352904ba4527fc --- .../communal/CommunalSceneStartableTest.kt | 540 ++++++++++----------- .../systemui/communal/CommunalSceneStartable.kt | 183 +++---- 2 files changed, 327 insertions(+), 396 deletions(-) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index e9b88499a6f7..7051f81cfc88 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -24,45 +24,47 @@ import android.provider.Settings import androidx.test.filters.SmallTest import com.android.internal.logging.uiEventLoggerFake import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.SysuiTestCase import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable +import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalScenes -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.dock.dockManager import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor -import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notificationShadeWindowController -import com.android.systemui.statusbar.phone.centralSurfacesOptional import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat +import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.advanceTimeBy -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(ParameterizedAndroidJunit4::class) @EnableFlags(FLAG_COMMUNAL_HUB) @@ -74,7 +76,8 @@ class CommunalSceneStartableTest(flags: FlagsParameterization) : SysuiTestCase() @JvmStatic @Parameters(name = "{0}") fun getParams(): List { - return FlagsParameterization.allCombinationsOf().andSceneContainer() + return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2) + .andSceneContainer() } } @@ -82,9 +85,22 @@ class CommunalSceneStartableTest(flags: FlagsParameterization) : SysuiTestCase() mSetFlagsRule.setFlagsParameterization(flags) } - private val kosmos = testKosmos() - - private lateinit var underTest: CommunalSceneStartable + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + + private val Kosmos.underTest by + Kosmos.Fixture { + CommunalSceneStartable( + communalInteractor = communalInteractor, + communalSettingsInteractor = communalSettingsInteractor, + communalSceneInteractor = communalSceneInteractor, + keyguardInteractor = keyguardInteractor, + systemSettings = fakeSettings, + notificationShadeWindowController = notificationShadeWindowController, + bgScope = applicationCoroutineScope, + mainDispatcher = testDispatcher, + uiEventLogger = uiEventLoggerFake, + ) + } @Before fun setUp() { @@ -94,352 +110,314 @@ class CommunalSceneStartableTest(flags: FlagsParameterization) : SysuiTestCase() SCREEN_TIMEOUT, UserHandle.USER_CURRENT, ) - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) - - underTest = - CommunalSceneStartable( - dockManager = dockManager, - communalInteractor = communalInteractor, - communalSettingsInteractor = communalSettingsInteractor, - communalSceneInteractor = communalSceneInteractor, - keyguardTransitionInteractor = keyguardTransitionInteractor, - keyguardInteractor = keyguardInteractor, - systemSettings = fakeSettings, - notificationShadeWindowController = notificationShadeWindowController, - applicationScope = applicationCoroutineScope, - bgScope = applicationCoroutineScope, - mainDispatcher = testDispatcher, - centralSurfacesOpt = centralSurfacesOptional, - uiEventLogger = uiEventLoggerFake, - ) - .apply { start() } + fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) + + underTest.start() // Make communal available so that communalInteractor.desiredScene accurately reflects // scene changes instead of just returning Blank. - with(kosmos.testScope) { - launch { setCommunalAvailable(true) } - testScheduler.runCurrent() - } + runBlocking { setCommunalAvailable(true) } + setCommunalV2ConfigEnabled(true) } } @Test - @DisableFlags(FLAG_SCENE_CONTAINER) + @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2) fun hubTimeout_whenDreaming_goesToBlank() = - with(kosmos) { - testScope.runTest { - // Device is dreaming and on communal. - updateDreaming(true) - communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") - - val scene by collectLastValue(communalSceneInteractor.currentScene) - assertThat(scene).isEqualTo(CommunalScenes.Communal) - - // Scene times out back to blank after the screen timeout. - advanceTimeBy(SCREEN_TIMEOUT.milliseconds) - assertThat(scene).isEqualTo(CommunalScenes.Blank) - } + kosmos.runTest { + // Device is dreaming and on communal. + fakeKeyguardRepository.setDreaming(true) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + + val scene by collectLastValue(communalSceneInteractor.currentScene) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + + // Scene times out back to blank after the screen timeout. + advanceTimeBy(SCREEN_TIMEOUT.milliseconds) + assertThat(scene).isEqualTo(CommunalScenes.Blank) } @Test - @DisableFlags(FLAG_SCENE_CONTAINER) + @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2) fun hubTimeout_notDreaming_staysOnCommunal() = - with(kosmos) { - testScope.runTest { - // Device is not dreaming and on communal. - updateDreaming(false) - communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") - - // Scene stays as Communal - advanceTimeBy(SCREEN_TIMEOUT.milliseconds) - val scene by collectLastValue(communalSceneInteractor.currentScene) - assertThat(scene).isEqualTo(CommunalScenes.Communal) - } + kosmos.runTest { + // Device is not dreaming and on communal. + fakeKeyguardRepository.setDreaming(false) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + + // Scene stays as Communal + advanceTimeBy(SCREEN_TIMEOUT.milliseconds) + val scene by collectLastValue(communalSceneInteractor.currentScene) + assertThat(scene).isEqualTo(CommunalScenes.Communal) } @Test - @DisableFlags(FLAG_SCENE_CONTAINER) + @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2) fun hubTimeout_dreamStopped_staysOnCommunal() = - with(kosmos) { - testScope.runTest { - // Device is dreaming and on communal. - updateDreaming(true) - communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") - - val scene by collectLastValue(communalSceneInteractor.currentScene) - assertThat(scene).isEqualTo(CommunalScenes.Communal) - - // Wait a bit, but not long enough to timeout. - advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - assertThat(scene).isEqualTo(CommunalScenes.Communal) - - // Dream stops, timeout is cancelled and device stays on hub, because the regular - // screen timeout will take effect at this point. - updateDreaming(false) - advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - assertThat(scene).isEqualTo(CommunalScenes.Communal) - } + kosmos.runTest { + // Device is dreaming and on communal. + fakeKeyguardRepository.setDreaming(true) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + + val scene by collectLastValue(communalSceneInteractor.currentScene) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + + // Wait a bit, but not long enough to timeout. + advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + + // Dream stops, timeout is cancelled and device stays on hub, because the regular + // screen timeout will take effect at this point. + fakeKeyguardRepository.setDreaming(false) + advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) + assertThat(scene).isEqualTo(CommunalScenes.Communal) } @Test - @DisableFlags(FLAG_SCENE_CONTAINER) + @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2) fun hubTimeout_dreamStartedHalfway_goesToCommunal() = - with(kosmos) { - testScope.runTest { - // Device is on communal, but not dreaming. - updateDreaming(false) - communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") - - val scene by collectLastValue(communalSceneInteractor.currentScene) - assertThat(scene).isEqualTo(CommunalScenes.Communal) - - // Wait a bit, but not long enough to timeout, then start dreaming. - advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - updateDreaming(true) - assertThat(scene).isEqualTo(CommunalScenes.Communal) - - // Device times out after one screen timeout interval, dream doesn't reset timeout. - advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - assertThat(scene).isEqualTo(CommunalScenes.Blank) - } + kosmos.runTest { + // Device is on communal, but not dreaming. + fakeKeyguardRepository.setDreaming(false) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + + val scene by collectLastValue(communalSceneInteractor.currentScene) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + + // Wait a bit, but not long enough to timeout, then start dreaming. + advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) + fakeKeyguardRepository.setDreaming(true) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + + // Device times out after one screen timeout interval, dream doesn't reset timeout. + advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) + assertThat(scene).isEqualTo(CommunalScenes.Blank) } @Test - @DisableFlags(FLAG_SCENE_CONTAINER) + @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2) fun hubTimeout_dreamAfterInitialTimeout_goesToBlank() = - with(kosmos) { - testScope.runTest { - // Device is on communal. - communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") - - // Device stays on the hub after the timeout since we're not dreaming. - advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2) - val scene by collectLastValue(communalSceneInteractor.currentScene) - assertThat(scene).isEqualTo(CommunalScenes.Communal) - - // Start dreaming. - updateDreaming(true) - advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS) - - // Hub times out immediately. - assertThat(scene).isEqualTo(CommunalScenes.Blank) - } + kosmos.runTest { + // Device is on communal. + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + + // Device stays on the hub after the timeout since we're not dreaming. + testScope.advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2) + val scene by collectLastValue(communalSceneInteractor.currentScene) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + + // Start dreaming. + fakeKeyguardRepository.setDreaming(true) + advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS.milliseconds) + + // Hub times out immediately. + assertThat(scene).isEqualTo(CommunalScenes.Blank) } @Test - @DisableFlags(FLAG_SCENE_CONTAINER) + @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2) fun hubTimeout_userActivityTriggered_resetsTimeout() = - with(kosmos) { - testScope.runTest { - // Device is dreaming and on communal. - updateDreaming(true) - communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + kosmos.runTest { + // Device is dreaming and on communal. + fakeKeyguardRepository.setDreaming(true) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") - val scene by collectLastValue(communalSceneInteractor.currentScene) - assertThat(scene).isEqualTo(CommunalScenes.Communal) + val scene by collectLastValue(communalSceneInteractor.currentScene) + assertThat(scene).isEqualTo(CommunalScenes.Communal) - // Wait a bit, but not long enough to timeout. - advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) + // Wait a bit, but not long enough to timeout. + advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - // Send user interaction to reset timeout. - communalInteractor.signalUserInteraction() + // Send user interaction to reset timeout. + communalInteractor.signalUserInteraction() - // If user activity didn't reset timeout, we would have gone back to Blank by now. - advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - assertThat(scene).isEqualTo(CommunalScenes.Communal) + // If user activity didn't reset timeout, we would have gone back to Blank by now. + advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) + assertThat(scene).isEqualTo(CommunalScenes.Communal) - // Timeout happens one interval after the user interaction. - advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - assertThat(scene).isEqualTo(CommunalScenes.Blank) - } + // Timeout happens one interval after the user interaction. + advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) + assertThat(scene).isEqualTo(CommunalScenes.Blank) } @Test - @DisableFlags(FLAG_SCENE_CONTAINER) + @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2) fun hubTimeout_screenTimeoutChanged() = - with(kosmos) { - testScope.runTest { - fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2) - - // Device is dreaming and on communal. - updateDreaming(true) - communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") - - val scene by collectLastValue(communalSceneInteractor.currentScene) - assertThat(scene).isEqualTo(CommunalScenes.Communal) - - // Scene times out back to blank after the screen timeout. - advanceTimeBy(SCREEN_TIMEOUT.milliseconds) - assertThat(scene).isEqualTo(CommunalScenes.Communal) - - advanceTimeBy(SCREEN_TIMEOUT.milliseconds) - assertThat(scene).isEqualTo(CommunalScenes.Blank) - assertThat(uiEventLoggerFake.logs.first().eventId) - .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id) - assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) - } + kosmos.runTest { + fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2) + + // Device is dreaming and on communal. + fakeKeyguardRepository.setDreaming(true) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + + val scene by collectLastValue(communalSceneInteractor.currentScene) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + + // Scene times out back to blank after the screen timeout. + advanceTimeBy(SCREEN_TIMEOUT.milliseconds) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + + advanceTimeBy(SCREEN_TIMEOUT.milliseconds) + assertThat(scene).isEqualTo(CommunalScenes.Blank) + assertThat(uiEventLoggerFake.logs.first().eventId) + .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) } @Test @EnableFlags(FLAG_SCENE_CONTAINER) + @DisableFlags(FLAG_GLANCEABLE_HUB_V2) fun hubTimeout_withSceneContainer_whenDreaming_goesToBlank() = - with(kosmos) { - testScope.runTest { - // Device is dreaming and on communal. - updateDreaming(true) - sceneInteractor.changeScene(Scenes.Communal, "test") - - val scene by collectLastValue(sceneInteractor.currentScene) - assertThat(scene).isEqualTo(Scenes.Communal) - - // Scene times out back to blank after the screen timeout. - advanceTimeBy(SCREEN_TIMEOUT.milliseconds) - assertThat(scene).isEqualTo(Scenes.Dream) - } + kosmos.runTest { + // Device is dreaming and on communal. + fakeKeyguardRepository.setDreaming(true) + sceneInteractor.changeScene(Scenes.Communal, "test") + + val scene by collectLastValue(sceneInteractor.currentScene) + assertThat(scene).isEqualTo(Scenes.Communal) + + // Scene times out back to blank after the screen timeout. + advanceTimeBy(SCREEN_TIMEOUT.milliseconds) + assertThat(scene).isEqualTo(Scenes.Dream) } @Test @EnableFlags(FLAG_SCENE_CONTAINER) + @DisableFlags(FLAG_GLANCEABLE_HUB_V2) fun hubTimeout_withSceneContainer_notDreaming_staysOnCommunal() = - with(kosmos) { - testScope.runTest { - // Device is not dreaming and on communal. - updateDreaming(false) - sceneInteractor.changeScene(Scenes.Communal, "test") - - // Scene stays as Communal - advanceTimeBy(SCREEN_TIMEOUT.milliseconds) - val scene by collectLastValue(sceneInteractor.currentScene) - assertThat(scene).isEqualTo(Scenes.Communal) - } + kosmos.runTest { + // Device is not dreaming and on communal. + fakeKeyguardRepository.setDreaming(false) + sceneInteractor.changeScene(Scenes.Communal, "test") + + // Scene stays as Communal + advanceTimeBy(SCREEN_TIMEOUT.milliseconds) + val scene by collectLastValue(sceneInteractor.currentScene) + assertThat(scene).isEqualTo(Scenes.Communal) } @Test @EnableFlags(FLAG_SCENE_CONTAINER) + @DisableFlags(FLAG_GLANCEABLE_HUB_V2) fun hubTimeout_withSceneContainer_dreamStopped_staysOnCommunal() = - with(kosmos) { - testScope.runTest { - // Device is dreaming and on communal. - updateDreaming(true) - sceneInteractor.changeScene(Scenes.Communal, "test") - - val scene by collectLastValue(sceneInteractor.currentScene) - assertThat(scene).isEqualTo(Scenes.Communal) - - // Wait a bit, but not long enough to timeout. - advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - assertThat(scene).isEqualTo(Scenes.Communal) - - // Dream stops, timeout is cancelled and device stays on hub, because the regular - // screen timeout will take effect at this point. - updateDreaming(false) - advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - assertThat(scene).isEqualTo(Scenes.Communal) - } + kosmos.runTest { + // Device is dreaming and on communal. + fakeKeyguardRepository.setDreaming(true) + sceneInteractor.changeScene(Scenes.Communal, "test") + + val scene by collectLastValue(sceneInteractor.currentScene) + assertThat(scene).isEqualTo(Scenes.Communal) + + // Wait a bit, but not long enough to timeout. + advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) + assertThat(scene).isEqualTo(Scenes.Communal) + + // Dream stops, timeout is cancelled and device stays on hub, because the regular + // screen timeout will take effect at this point. + fakeKeyguardRepository.setDreaming(false) + advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) + assertThat(scene).isEqualTo(Scenes.Communal) } @Test @EnableFlags(FLAG_SCENE_CONTAINER) + @DisableFlags(FLAG_GLANCEABLE_HUB_V2) fun hubTimeout_withSceneContainer_dreamStartedHalfway_goesToCommunal() = - with(kosmos) { - testScope.runTest { - // Device is on communal, but not dreaming. - updateDreaming(false) - sceneInteractor.changeScene(Scenes.Communal, "test") - - val scene by collectLastValue(sceneInteractor.currentScene) - assertThat(scene).isEqualTo(Scenes.Communal) - - // Wait a bit, but not long enough to timeout, then start dreaming. - advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - updateDreaming(true) - assertThat(scene).isEqualTo(Scenes.Communal) - - // Device times out after one screen timeout interval, dream doesn't reset timeout. - advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - assertThat(scene).isEqualTo(Scenes.Dream) - } + kosmos.runTest { + // Device is on communal, but not dreaming. + fakeKeyguardRepository.setDreaming(false) + sceneInteractor.changeScene(Scenes.Communal, "test") + + val scene by collectLastValue(sceneInteractor.currentScene) + assertThat(scene).isEqualTo(Scenes.Communal) + + // Wait a bit, but not long enough to timeout, then start dreaming. + advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) + fakeKeyguardRepository.setDreaming(true) + assertThat(scene).isEqualTo(Scenes.Communal) + + // Device times out after one screen timeout interval, dream doesn't reset timeout. + advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) + assertThat(scene).isEqualTo(Scenes.Dream) } @Test @EnableFlags(FLAG_SCENE_CONTAINER) + @DisableFlags(FLAG_GLANCEABLE_HUB_V2) fun hubTimeout_withSceneContainer_dreamAfterInitialTimeout_goesToBlank() = - with(kosmos) { - testScope.runTest { - // Device is on communal. - sceneInteractor.changeScene(Scenes.Communal, "test") - - // Device stays on the hub after the timeout since we're not dreaming. - advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2) - val scene by collectLastValue(sceneInteractor.currentScene) - assertThat(scene).isEqualTo(Scenes.Communal) - - // Start dreaming. - updateDreaming(true) - advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS) - - // Hub times out immediately. - assertThat(scene).isEqualTo(Scenes.Dream) - } + kosmos.runTest { + // Device is on communal. + sceneInteractor.changeScene(Scenes.Communal, "test") + + // Device stays on the hub after the timeout since we're not dreaming. + advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2) + val scene by collectLastValue(sceneInteractor.currentScene) + assertThat(scene).isEqualTo(Scenes.Communal) + + // Start dreaming. + fakeKeyguardRepository.setDreaming(true) + advanceTimeBy(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS.milliseconds) + + // Hub times out immediately. + assertThat(scene).isEqualTo(Scenes.Dream) } @Test @EnableFlags(FLAG_SCENE_CONTAINER) + @DisableFlags(FLAG_GLANCEABLE_HUB_V2) fun hubTimeout_withSceneContainer_userActivityTriggered_resetsTimeout() = - with(kosmos) { - testScope.runTest { - // Device is dreaming and on communal. - updateDreaming(true) - sceneInteractor.changeScene(Scenes.Communal, "test") + kosmos.runTest { + // Device is dreaming and on communal. + fakeKeyguardRepository.setDreaming(true) + sceneInteractor.changeScene(Scenes.Communal, "test") - val scene by collectLastValue(sceneInteractor.currentScene) - assertThat(scene).isEqualTo(Scenes.Communal) + val scene by collectLastValue(sceneInteractor.currentScene) + assertThat(scene).isEqualTo(Scenes.Communal) - // Wait a bit, but not long enough to timeout. - advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) + // Wait a bit, but not long enough to timeout. + advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - // Send user interaction to reset timeout. - communalInteractor.signalUserInteraction() + // Send user interaction to reset timeout. + communalInteractor.signalUserInteraction() - // If user activity didn't reset timeout, we would have gone back to Blank by now. - advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - assertThat(scene).isEqualTo(Scenes.Communal) + // If user activity didn't reset timeout, we would have gone back to Blank by now. + advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) + assertThat(scene).isEqualTo(Scenes.Communal) - // Timeout happens one interval after the user interaction. - advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - assertThat(scene).isEqualTo(Scenes.Dream) - } + // Timeout happens one interval after the user interaction. + advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) + assertThat(scene).isEqualTo(Scenes.Dream) } @Test @EnableFlags(FLAG_SCENE_CONTAINER) + @DisableFlags(FLAG_GLANCEABLE_HUB_V2) fun hubTimeout_withSceneContainer_screenTimeoutChanged() = - with(kosmos) { - testScope.runTest { - fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2) - - // Device is dreaming and on communal. - updateDreaming(true) - sceneInteractor.changeScene(Scenes.Communal, "test") - - val scene by collectLastValue(sceneInteractor.currentScene) - assertThat(scene).isEqualTo(Scenes.Communal) - - // Scene times out back to blank after the screen timeout. - advanceTimeBy(SCREEN_TIMEOUT.milliseconds) - assertThat(scene).isEqualTo(Scenes.Communal) - - advanceTimeBy(SCREEN_TIMEOUT.milliseconds) - assertThat(scene).isEqualTo(Scenes.Dream) - assertThat(uiEventLoggerFake.logs.first().eventId) - .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id) - assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) - } - } + kosmos.runTest { + fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2) - private fun TestScope.updateDreaming(dreaming: Boolean) = - with(kosmos) { - fakeKeyguardRepository.setDreaming(dreaming) - runCurrent() + // Device is dreaming and on communal. + fakeKeyguardRepository.setDreaming(true) + sceneInteractor.changeScene(Scenes.Communal, "test") + + val scene by collectLastValue(sceneInteractor.currentScene) + assertThat(scene).isEqualTo(Scenes.Communal) + + // Scene times out back to blank after the screen timeout. + advanceTimeBy(SCREEN_TIMEOUT.milliseconds) + assertThat(scene).isEqualTo(Scenes.Communal) + + advanceTimeBy(SCREEN_TIMEOUT.milliseconds) + assertThat(scene).isEqualTo(Scenes.Dream) + assertThat(uiEventLoggerFake.logs.first().eventId) + .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT.id) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) } + + /** + * Advances time by duration + 1 millisecond, to ensure that tasks scheduled to run at + * currentTime + duration are scheduled. + */ + private fun Kosmos.advanceTimeBy(duration: Duration) = + testScope.advanceTimeBy(duration + 1.milliseconds) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index 4f956fc802e4..e36e85565293 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -19,8 +19,6 @@ package com.android.systemui.communal import android.os.UserHandle import android.provider.Settings import com.android.app.tracing.coroutines.launchTraced as launch -import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.TransitionKey import com.android.internal.logging.UiEventLogger import com.android.systemui.CoreStartable import com.android.systemui.communal.domain.interactor.CommunalInteractor @@ -31,25 +29,16 @@ import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalScenes.isCommunal import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.dock.DockManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.statusbar.NotificationShadeWindowController -import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.util.kotlin.emitOnStart -import com.android.systemui.util.kotlin.getValue import com.android.systemui.util.kotlin.sample import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import com.android.systemui.util.settings.SystemSettings -import java.util.Optional import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -68,16 +57,12 @@ import kotlinx.coroutines.withContext class CommunalSceneStartable @Inject constructor( - private val dockManager: DockManager, private val communalInteractor: CommunalInteractor, private val communalSettingsInteractor: CommunalSettingsInteractor, private val communalSceneInteractor: CommunalSceneInteractor, - private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val keyguardInteractor: KeyguardInteractor, private val systemSettings: SystemSettings, - centralSurfacesOpt: Optional, private val notificationShadeWindowController: NotificationShadeWindowController, - @Application private val applicationScope: CoroutineScope, @Background private val bgScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, private val uiEventLogger: UiEventLogger, @@ -88,75 +73,85 @@ constructor( private var isDreaming: Boolean = false - private val centralSurfaces: CentralSurfaces? by centralSurfacesOpt - override fun start() { if (!communalSettingsInteractor.isCommunalFlagEnabled()) { return } - systemSettings - .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT) - // Read the setting value on start. - .emitOnStart() - .onEach { - screenTimeout = - systemSettings.getIntForUser( - Settings.System.SCREEN_OFF_TIMEOUT, - DEFAULT_SCREEN_TIMEOUT, - UserHandle.USER_CURRENT, - ) - } - .launchIn(bgScope) - - // 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 { - combine( - communalSceneInteractor.currentScene, - // Emit a value on start so the combine starts. - communalInteractor.userActivity.emitOnStart(), - ) { scene, _ -> - // Only timeout if we're on the hub is open. - scene.isCommunal() - } - .collectLatest { shouldTimeout -> - cancelHubTimeout() - if (shouldTimeout) { - startHubTimeout() - } + communalSceneInteractor.isIdleOnCommunal.collectLatest { + withContext(mainDispatcher) { + notificationShadeWindowController.setGlanceableHubShowing(it) } + } } - bgScope.launch { - keyguardInteractor.isDreaming - .sample(communalSceneInteractor.currentScene, ::Pair) - .collectLatest { (isDreaming, scene) -> - this@CommunalSceneStartable.isDreaming = isDreaming - if (scene.isCommunal() && isDreaming && timeoutJob == null) { - // If dreaming starts after timeout has expired, ex. if dream restarts under - // the hub, wait for IS_ABLE_TO_DREAM_DELAY_MS and then close the hub. The - // delay is necessary so the KeyguardInteractor.isAbleToDream flow passes - // through that same amount of delay and publishes a new value which is then - // picked up by the HomeSceneFamilyResolver such that the next call to - // SceneInteractor.changeScene(Home) will resolve "Home" to "Dream". - delay(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS) - communalSceneInteractor.changeScene( - CommunalScenes.Blank, - "dream started after timeout", + + // In V2, the timeout is handled by PowerManagerService since we no longer keep the dream + // active underneath the hub. + if (!communalSettingsInteractor.isV2FlagEnabled()) { + systemSettings + .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT) + // Read the setting value on start. + .emitOnStart() + .onEach { + screenTimeout = + systemSettings.getIntForUser( + Settings.System.SCREEN_OFF_TIMEOUT, + DEFAULT_SCREEN_TIMEOUT, + UserHandle.USER_CURRENT, ) - uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT) - } } - } + .launchIn(bgScope) - bgScope.launch { - communalSceneInteractor.isIdleOnCommunal.collectLatest { - withContext(mainDispatcher) { - notificationShadeWindowController.setGlanceableHubShowing(it) - } + // 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 { + combine( + communalSceneInteractor.currentScene, + // Emit a value on start so the combine starts. + communalInteractor.userActivity.emitOnStart(), + ) { scene, _ -> + // Only timeout if we're on the hub is open. + scene.isCommunal() + } + .collectLatest { shouldTimeout -> + cancelHubTimeout() + if (shouldTimeout) { + startHubTimeout() + } + } + } + + bgScope.launch { + keyguardInteractor.isDreaming + .sample(communalSceneInteractor.currentScene, ::Pair) + .collectLatest { (isDreaming, scene) -> + this@CommunalSceneStartable.isDreaming = isDreaming + if (scene.isCommunal() && isDreaming && timeoutJob == null) { + // If dreaming starts after timeout has expired, ex. if dream restarts + // under + // the hub, wait for IS_ABLE_TO_DREAM_DELAY_MS and then close the hub. + // The + // delay is necessary so the KeyguardInteractor.isAbleToDream flow + // passes + // through that same amount of delay and publishes a new value which is + // then + // picked up by the HomeSceneFamilyResolver such that the next call to + // SceneInteractor.changeScene(Home) will resolve "Home" to "Dream". + delay(KeyguardInteractor.IS_ABLE_TO_DREAM_DELAY_MS) + communalSceneInteractor.changeScene( + CommunalScenes.Blank, + "dream started after timeout", + ) + uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT) + } + } } } } @@ -187,49 +182,7 @@ constructor( } } - private suspend fun determineSceneAfterTransition( - lastStartedTransition: TransitionStep - ): Pair? { - val to = lastStartedTransition.to - val from = lastStartedTransition.from - val docked = dockManager.isDocked - val launchingActivityOverLockscreen = - centralSurfaces?.isLaunchingActivityOverLockscreen ?: false - - return when { - to == KeyguardState.OCCLUDED && !launchingActivityOverLockscreen -> { - // Hide communal when an activity is started on keyguard, to ensure the activity - // underneath the hub is shown. When launching activities over lockscreen, we only - // change scenes once the activity launch animation is finished, so avoid - // changing the scene here. - Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade) - } - to == KeyguardState.GLANCEABLE_HUB && from == KeyguardState.OCCLUDED -> { - // When transitioning to the hub from an occluded state, fade out the hub without - // doing any translation. - Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade) - } - // Transitioning to Blank scene when entering the edit mode will be handled separately - // with custom animations. - to == KeyguardState.GONE && !communalInteractor.editModeOpen.value -> - Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade) - !docked && !KeyguardState.deviceIsAwakeInState(to) -> { - // If the user taps the screen and wakes the device within this timeout, we don't - // want to dismiss the hub - delay(AWAKE_DEBOUNCE_DELAY) - Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade) - } - from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> { - // Make sure the communal hub is showing when transitioning from dozing to hub. - Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade) - } - else -> null - } - } - companion object { - val AWAKE_DEBOUNCE_DELAY = 5.seconds - val DOCK_DEBOUNCE_DELAY = 1.seconds val DEFAULT_SCREEN_TIMEOUT = 15000 } } -- cgit v1.2.3-59-g8ed1b