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

Commit 44d9d1b7 authored by Coco Duan's avatar Coco Duan
Browse files

Update tutorial state based on the transition state to the communal scene

- Tutorial state is `not started` by default. Then be updated to `started` after
transitioning to the communal scene initially and shows the tutorial cards.
- Once transitioning away from communal scene, mark the tutorail as `completed`.
So when re-entering the communal scene, the glanceable hub will show the widgets.
- Tested with flexiglass on and off and it worked both with CommunalContainer
and SceneContainer transition layouts.

Bug: b/305734559
Flag: ACONFIG com.android.systemui.communal_hub DEVELOPMENT
Test: atest CommunalTutorialInteractorTest
Change-Id: I51125bb557b0f025149f0d4f6b20ad3c3d43de9a
parent 8252304d
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -1045,8 +1045,8 @@
    <!-- Indication on the keyguard that is shown when the device is dock charging. [CHAR LIMIT=80]-->
    <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>

    <!-- Indicator shown to start the communal tutorial. [CHAR LIMIT=100] -->
    <string name="communal_tutorial_indicator_text">Click on the arrow button to start the communal tutorial</string>
    <!-- Indicator on keyguard to start the communal tutorial. [CHAR LIMIT=100] -->
    <string name="communal_tutorial_indicator_text">Swipe left to start the communal tutorial</string>

    <!-- Related to user switcher --><skip/>

+75 −1
Original line number Diff line number Diff line
@@ -19,12 +19,23 @@ package com.android.systemui.communal.domain.interactor
import android.provider.Settings
import com.android.systemui.communal.data.repository.CommunalTutorialRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

/** Encapsulates business-logic related to communal tutorial state. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -32,8 +43,12 @@ import kotlinx.coroutines.flow.distinctUntilChanged
class CommunalTutorialInteractor
@Inject
constructor(
    communalTutorialRepository: CommunalTutorialRepository,
    @Application private val scope: CoroutineScope,
    private val communalTutorialRepository: CommunalTutorialRepository,
    keyguardInteractor: KeyguardInteractor,
    private val communalInteractor: CommunalInteractor,
    private val sceneContainerFlags: SceneContainerFlags,
    private val sceneInteractor: SceneInteractor,
) {
    /** An observable for whether the tutorial is available. */
    val isTutorialAvailable: Flow<Boolean> =
@@ -45,4 +60,63 @@ constructor(
                    tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
            }
            .distinctUntilChanged()

    /**
     * A flow of the new tutorial state after transitioning. The new state will be calculated based
     * on the current tutorial state and transition state as following:
     * HUB_MODE_TUTORIAL_NOT_STARTED + communal scene -> HUB_MODE_TUTORIAL_STARTED
     * HUB_MODE_TUTORIAL_STARTED + non-communal scene -> HUB_MODE_TUTORIAL_COMPLETED
     * HUB_MODE_TUTORIAL_COMPLETED + any scene -> won't emit
     */
    private val tutorialStateToUpdate: Flow<Int> =
        communalTutorialRepository.tutorialSettingState
            .flatMapLatest { tutorialSettingState ->
                if (tutorialSettingState == Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) {
                    return@flatMapLatest flowOf(null)
                }
                if (sceneContainerFlags.isEnabled()) {
                    sceneInteractor.desiredScene.map { sceneModel ->
                        nextStateAfterTransition(
                            tutorialSettingState,
                            sceneModel.key == SceneKey.Communal
                        )
                    }
                } else {
                    communalInteractor.isCommunalShowing.map {
                        nextStateAfterTransition(tutorialSettingState, it)
                    }
                }
            }
            .filterNotNull()
            .distinctUntilChanged()

    private fun nextStateAfterTransition(tutorialState: Int, isCommunalShowing: Boolean): Int? {
        if (tutorialState == Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED && isCommunalShowing) {
            return Settings.Secure.HUB_MODE_TUTORIAL_STARTED
        }
        if (tutorialState == Settings.Secure.HUB_MODE_TUTORIAL_STARTED && !isCommunalShowing) {
            return Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
        }
        return null
    }

    private var job: Job? = null
    private fun listenForTransitionToUpdateTutorialState() {
        if (!communalInteractor.isCommunalEnabled) {
            return
        }
        job =
            scope.launch {
                tutorialStateToUpdate.collect {
                    communalTutorialRepository.setTutorialState(it)
                    if (it == Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) {
                        job?.cancel()
                    }
                }
            }
    }

    init {
        listenForTransitionToUpdateTutorialState()
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -44,6 +44,8 @@ object CommunalTutorialIndicatorViewBinder {
                            )
                        }
                    }

                    launch { viewModel.alpha.collect { view.alpha = it } }
                }
            }

+6 −0
Original line number Diff line number Diff line
@@ -17,15 +17,21 @@
package com.android.systemui.communal.ui.viewmodel

import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged

/** View model for communal tutorial indicator on keyguard */
class CommunalTutorialIndicatorViewModel
@Inject
constructor(
    communalTutorialInteractor: CommunalTutorialInteractor,
    bottomAreaInteractor: KeyguardBottomAreaInteractor,
) {
    /** An observable for whether the tutorial indicator view should be visible. */
    val showIndicator: Flow<Boolean> = communalTutorialInteractor.isTutorialAvailable

    /** An observable for the alpha level for the tutorial indicator. */
    val alpha: Flow<Float> = bottomAreaInteractor.alpha.distinctUntilChanged()
}
+209 −5
Original line number Diff line number Diff line
@@ -21,16 +21,23 @@ import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_STARTED
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -46,18 +53,28 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {

    @Mock private lateinit var userTracker: UserTracker

    private val testDispatcher = StandardTestDispatcher()
    private val testScope = TestScope(testDispatcher)

    private lateinit var testScope: TestScope
    private lateinit var underTest: CommunalTutorialInteractor
    private lateinit var keyguardRepository: FakeKeyguardRepository
    private lateinit var keyguardInteractor: KeyguardInteractor
    private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository
    private lateinit var sceneContainerFlags: FakeSceneContainerFlags
    private lateinit var communalInteractor: CommunalInteractor
    private lateinit var communalRepository: FakeCommunalRepository

    private val utils = SceneTestUtils(this)
    private lateinit var sceneInteractor: SceneInteractor

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        sceneInteractor = utils.sceneInteractor()
        testScope = utils.testScope
        sceneContainerFlags = utils.sceneContainerFlags.apply { enabled = false }
        communalRepository = FakeCommunalRepository(isCommunalEnabled = true)
        communalInteractor = CommunalInteractor(communalRepository, FakeCommunalWidgetRepository())

        val withDeps = KeyguardInteractorFactory.create()
        keyguardInteractor = withDeps.keyguardInteractor
        keyguardRepository = withDeps.repository
@@ -65,8 +82,12 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {

        underTest =
            CommunalTutorialInteractor(
                keyguardInteractor = keyguardInteractor,
                scope = testScope.backgroundScope,
                communalTutorialRepository = communalTutorialRepository,
                keyguardInteractor = keyguardInteractor,
                communalInteractor = communalInteractor,
                sceneContainerFlags = sceneContainerFlags,
                sceneInteractor = sceneInteractor,
            )

        whenever(userTracker.userHandle).thenReturn(mock())
@@ -87,6 +108,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
            keyguardRepository.setKeyguardShowing(true)
            keyguardRepository.setKeyguardOccluded(false)
            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
            assertThat(isTutorialAvailable).isFalse()
        }
@@ -97,6 +119,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
            keyguardRepository.setKeyguardShowing(true)
            keyguardRepository.setKeyguardOccluded(false)
            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
            assertThat(isTutorialAvailable).isTrue()
        }
@@ -107,7 +130,188 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
            val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
            keyguardRepository.setKeyguardShowing(true)
            keyguardRepository.setKeyguardOccluded(false)
            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
            assertThat(isTutorialAvailable).isTrue()
        }

    /* Testing tutorial states with transitions when flexiglass off */
    @Test
    fun tutorialState_notStartedAndCommunalSceneShowing_tutorialStarted() =
        testScope.runTest {
            val tutorialSettingState by
                collectLastValue(communalTutorialRepository.tutorialSettingState)
            val currentScene by collectLastValue(communalInteractor.desiredScene)
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)

            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)

            assertThat(currentScene).isEqualTo(CommunalSceneKey.Communal)
            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
        }

    @Test
    fun tutorialState_startedAndCommunalSceneShowing_stateWillNotUpdate() =
        testScope.runTest {
            val tutorialSettingState by
                collectLastValue(communalTutorialRepository.tutorialSettingState)
            val currentScene by collectLastValue(communalInteractor.desiredScene)
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)

            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)

            assertThat(currentScene).isEqualTo(CommunalSceneKey.Communal)
            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
        }

    @Test
    fun tutorialState_completedAndCommunalSceneShowing_stateWillNotUpdate() =
        testScope.runTest {
            val tutorialSettingState by
                collectLastValue(communalTutorialRepository.tutorialSettingState)
            val currentScene by collectLastValue(communalInteractor.desiredScene)
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)

            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)

            assertThat(currentScene).isEqualTo(CommunalSceneKey.Communal)
            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
        }

    @Test
    fun tutorialState_notStartedAndCommunalSceneNotShowing_stateWillNotUpdate() =
        testScope.runTest {
            val tutorialSettingState by
                collectLastValue(communalTutorialRepository.tutorialSettingState)
            val currentScene by collectLastValue(communalInteractor.desiredScene)
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)

            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)

            assertThat(currentScene).isEqualTo(CommunalSceneKey.Blank)
            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
        }

    @Test
    fun tutorialState_startedAndCommunalSceneNotShowing_tutorialCompleted() =
        testScope.runTest {
            val tutorialSettingState by
                collectLastValue(communalTutorialRepository.tutorialSettingState)
            val currentScene by collectLastValue(communalInteractor.desiredScene)
            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)

            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)

            assertThat(currentScene).isEqualTo(CommunalSceneKey.Blank)
            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
        }

    @Test
    fun tutorialState_completedAndCommunalSceneNotShowing_stateWillNotUpdate() =
        testScope.runTest {
            val tutorialSettingState by
                collectLastValue(communalTutorialRepository.tutorialSettingState)
            val currentScene by collectLastValue(communalInteractor.desiredScene)
            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)

            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)

            assertThat(currentScene).isEqualTo(CommunalSceneKey.Blank)
            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
        }

    /* Testing tutorial states with transitions when flexiglass on */
    @Test
    fun tutorialState_notStartedCommunalSceneShowingAndFlexiglassOn_tutorialStarted() =
        testScope.runTest {
            sceneContainerFlags.enabled = true
            val tutorialSettingState by
                collectLastValue(communalTutorialRepository.tutorialSettingState)
            val currentScene by collectLastValue(sceneInteractor.desiredScene)
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)

            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Communal), "reason")

            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Communal))
            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
        }

    @Test
    fun tutorialState_startedCommunalSceneShowingAndFlexiglassOn_stateWillNotUpdate() =
        testScope.runTest {
            sceneContainerFlags.enabled = true
            val tutorialSettingState by
                collectLastValue(communalTutorialRepository.tutorialSettingState)
            val currentScene by collectLastValue(sceneInteractor.desiredScene)
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)

            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Communal), "reason")

            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Communal))
            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
        }

    @Test
    fun tutorialState_completedCommunalSceneShowingAndFlexiglassOn_stateWillNotUpdate() =
        testScope.runTest {
            sceneContainerFlags.enabled = true
            val tutorialSettingState by
                collectLastValue(communalTutorialRepository.tutorialSettingState)
            val currentScene by collectLastValue(sceneInteractor.desiredScene)
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)

            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Communal), "reason")

            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Communal))
            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
        }

    @Test
    fun tutorialState_notStartedCommunalSceneNotShowingAndFlexiglassOn_stateWillNotUpdate() =
        testScope.runTest {
            sceneContainerFlags.enabled = true
            val tutorialSettingState by
                collectLastValue(communalTutorialRepository.tutorialSettingState)
            val currentScene by collectLastValue(sceneInteractor.desiredScene)
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)

            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")

            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
        }

    @Test
    fun tutorialState_startedCommunalSceneNotShowingAndFlexiglassOn_tutorialCompleted() =
        testScope.runTest {
            sceneContainerFlags.enabled = true
            val tutorialSettingState by
                collectLastValue(communalTutorialRepository.tutorialSettingState)
            val currentScene by collectLastValue(sceneInteractor.desiredScene)
            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Communal), "reason")
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)

            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")

            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
        }

    @Test
    fun tutorialState_completedCommunalSceneNotShowingAndFlexiglassOn_stateWillNotUpdate() =
        testScope.runTest {
            sceneContainerFlags.enabled = true
            val tutorialSettingState by
                collectLastValue(communalTutorialRepository.tutorialSettingState)
            val currentScene by collectLastValue(sceneInteractor.desiredScene)
            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Communal), "reason")
            communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)

            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")

            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
            assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
        }
}
Loading