Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c19d6a06 authored by William Xiao's avatar William Xiao Committed by Android (Google) Code Review
Browse files

Merge "Fix hub timeout not working if dream restarts underneath hub after timeout" into main

parents a90e5fb3 143e78dd
Loading
Loading
Loading
Loading
+34 −8
Original line number Original line Diff line number Diff line
@@ -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)
@@ -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
@@ -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)
@@ -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)
            }
            }
@@ -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)
@@ -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.
@@ -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)
@@ -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)
@@ -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)
+42 −15
Original line number Original line Diff line number Diff line
@@ -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
@@ -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
@@ -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)
                    }
                    }
                }
                }
@@ -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? {