diff options
7 files changed, 296 insertions, 8 deletions
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 4c5204447db0..12bff4a27277 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -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/> diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt index 276df4eb68ec..7f43eb5682d6 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt @@ -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() + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt index dab6819e1028..4dfc371aaeef 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt @@ -44,6 +44,8 @@ object CommunalTutorialIndicatorViewBinder { ) } } + + launch { viewModel.alpha.collect { view.alpha = it } } } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt index eaf95508cf12..274e61a7499f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt @@ -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() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt index 0a9a15e06b1b..61d1502f307e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt @@ -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) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index 432bd0f2a050..456f1bcb6f73 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -51,6 +51,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() { SceneKey.Lockscreen, SceneKey.Bouncer, SceneKey.Gone, + SceneKey.Communal, ) ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index bdddc042009d..6beb513c32d7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -131,6 +131,7 @@ class SceneTestUtils( SceneKey.Lockscreen, SceneKey.Bouncer, SceneKey.Gone, + SceneKey.Communal, ) } |