diff options
10 files changed, 212 insertions, 100 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt index 50b727c3fed9..9cfd328a9484 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.contextualeducation.GestureType.BACK import com.android.systemui.coroutines.collectLastValue import com.android.systemui.education.data.model.EduDeviceConnectionTime import com.android.systemui.education.data.model.GestureEduModel +import com.android.systemui.education.domain.interactor.mockEduInputManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope @@ -62,7 +63,13 @@ class ContextualEducationRepositoryTest : SysuiTestCase() { // Create TestContext here because TemporaryFolder.create() is called in @Before. It is // needed before calling TemporaryFolder.newFolder(). val testContext = TestContext(context, tmpFolder.newFolder()) - underTest = UserContextualEducationRepository(testContext, dsScopeProvider) + underTest = + UserContextualEducationRepository( + testContext, + dsScopeProvider, + kosmos.mockEduInputManager, + kosmos.testDispatcher + ) underTest.setUser(testUserId) } @@ -99,7 +106,8 @@ class ContextualEducationRepositoryTest : SysuiTestCase() { lastShortcutTriggeredTime = kosmos.fakeEduClock.instant(), lastEducationTime = kosmos.fakeEduClock.instant(), usageSessionStartTime = kosmos.fakeEduClock.instant(), - userId = testUserId + userId = testUserId, + gestureType = BACK ) underTest.updateGestureEduModel(BACK) { newModel } val model by collectLastValue(underTest.readGestureEduModelFlow(BACK)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt index 64915fbf551f..8201bbe4dc47 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt @@ -17,15 +17,13 @@ package com.android.systemui.education.domain.interactor import android.content.pm.UserInfo -import android.hardware.input.InputManager -import android.hardware.input.KeyGestureEvent -import android.view.KeyEvent -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.contextualeducation.GestureType import com.android.systemui.contextualeducation.GestureType.ALL_APPS import com.android.systemui.contextualeducation.GestureType.BACK +import com.android.systemui.contextualeducation.GestureType.HOME +import com.android.systemui.contextualeducation.GestureType.OVERVIEW import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.education.data.model.GestureEduModel @@ -40,20 +38,21 @@ import com.android.systemui.user.data.repository.fakeUserRepository import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.kotlin.any -import org.mockito.kotlin.verify +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @kotlinx.coroutines.ExperimentalCoroutinesApi -class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { +class KeyboardTouchpadEduInteractorTest(private val gestureType: GestureType) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val contextualEduInteractor = kosmos.contextualEducationInteractor @@ -71,21 +70,27 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { underTest.start() contextualEduInteractor.start() userRepository.setUserInfos(USER_INFOS) + testScope.launch { + contextualEduInteractor.updateKeyboardFirstConnectionTime() + contextualEduInteractor.updateTouchpadFirstConnectionTime() + } } @Test fun newEducationInfoOnMaxSignalCountReached() = testScope.runTest { - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) val model by collectLastValue(underTest.educationTriggered) - assertThat(model?.gestureType).isEqualTo(BACK) + + assertThat(model?.gestureType).isEqualTo(gestureType) } @Test fun newEducationToastOn1stEducation() = testScope.runTest { val model by collectLastValue(underTest.educationTriggered) - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) + assertThat(model?.educationUiType).isEqualTo(EducationUiType.Toast) } @@ -93,12 +98,12 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { fun newEducationNotificationOn2ndEducation() = testScope.runTest { val model by collectLastValue(underTest.educationTriggered) - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) // runCurrent() to trigger 1st education runCurrent() eduClock.offset(minDurationForNextEdu) - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification) } @@ -106,7 +111,7 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { @Test fun noEducationInfoBeforeMaxSignalCountReached() = testScope.runTest { - contextualEduInteractor.incrementSignalCount(BACK) + contextualEduInteractor.incrementSignalCount(gestureType) val model by collectLastValue(underTest.educationTriggered) assertThat(model).isNull() } @@ -115,8 +120,8 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { fun noEducationInfoWhenShortcutTriggeredPreviously() = testScope.runTest { val model by collectLastValue(underTest.educationTriggered) - contextualEduInteractor.updateShortcutTriggerTime(BACK) - triggerMaxEducationSignals(BACK) + contextualEduInteractor.updateShortcutTriggerTime(gestureType) + triggerMaxEducationSignals(gestureType) assertThat(model).isNull() } @@ -124,12 +129,12 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { fun no2ndEducationBeforeMinEduIntervalReached() = testScope.runTest { val models by collectValues(underTest.educationTriggered) - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) runCurrent() // Offset a duration that is less than the required education interval eduClock.offset(1.seconds) - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) runCurrent() assertThat(models.filterNotNull().size).isEqualTo(1) @@ -140,15 +145,15 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { testScope.runTest { val models by collectValues(underTest.educationTriggered) // Trigger 2 educations - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) runCurrent() eduClock.offset(minDurationForNextEdu) - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) runCurrent() // Try triggering 3rd education eduClock.offset(minDurationForNextEdu) - triggerMaxEducationSignals(BACK) + triggerMaxEducationSignals(gestureType) assertThat(models.filterNotNull().size).isEqualTo(2) } @@ -157,18 +162,21 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() = testScope.runTest { val model by - collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK)) - contextualEduInteractor.incrementSignalCount(BACK) + collectLastValue( + kosmos.contextualEducationRepository.readGestureEduModelFlow(gestureType) + ) + contextualEduInteractor.incrementSignalCount(gestureType) eduClock.offset(KeyboardTouchpadEduInteractor.usageSessionDuration.plus(1.seconds)) val secondSignalReceivedTime = eduClock.instant() - contextualEduInteractor.incrementSignalCount(BACK) + contextualEduInteractor.incrementSignalCount(gestureType) assertThat(model) .isEqualTo( GestureEduModel( signalCount = 1, usageSessionStartTime = secondSignalReceivedTime, - userId = 0 + userId = 0, + gestureType = gestureType ) ) } @@ -252,22 +260,9 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { @Test fun updateShortcutTimeOnKeyboardShortcutTriggered() = testScope.runTest { - // runCurrent() to trigger inputManager#registerKeyGestureEventListener in the - // interactor - runCurrent() - val listenerCaptor = - ArgumentCaptor.forClass(InputManager.KeyGestureEventListener::class.java) - verify(kosmos.mockEduInputManager) - .registerKeyGestureEventListener(any(), listenerCaptor.capture()) - - val allAppsKeyGestureEvent = - KeyGestureEvent.Builder() - .setDeviceId(1) - .setModifierState(KeyEvent.META_META_ON) - .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS) - .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - .build() - listenerCaptor.value.onKeyGestureEvent(allAppsKeyGestureEvent) + // Only All Apps needs to update the keyboard shortcut + assumeTrue(gestureType == ALL_APPS) + kosmos.contextualEducationRepository.setKeyboardShortcutTriggered(ALL_APPS) val model by collectLastValue( @@ -293,10 +288,18 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { runCurrent() } + private suspend fun setUpForDeviceConnection() { + contextualEduInteractor.updateKeyboardFirstConnectionTime() + contextualEduInteractor.updateTouchpadFirstConnectionTime() + } + companion object { - private val USER_INFOS = - listOf( - UserInfo(101, "Second User", 0), - ) + private val USER_INFOS = listOf(UserInfo(101, "Second User", 0)) + + @JvmStatic + @Parameters(name = "{0}") + fun getGestureTypes(): List<GestureType> { + return listOf(BACK, HOME, OVERVIEW, ALL_APPS) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt index c4ac585f7e4a..ab33269ec954 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt @@ -37,6 +37,7 @@ import com.android.systemui.res.R import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -69,6 +70,11 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() { @Before fun setUp() { + testScope.launch { + interactor.updateKeyboardFirstConnectionTime() + interactor.updateTouchpadFirstConnectionTime() + } + val viewModel = ContextualEduViewModel( kosmos.applicationContext.resources, diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt index 1daaa1128ba0..500c5b387ac6 100644 --- a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt +++ b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.education.data.model +import com.android.systemui.contextualeducation.GestureType import java.time.Instant /** @@ -23,6 +24,7 @@ import java.time.Instant * gesture stores its own model separately. */ data class GestureEduModel( + val gestureType: GestureType, val signalCount: Int = 0, val educationShownCount: Int = 0, val lastShortcutTriggeredTime: Instant? = null, diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt index 01f838ff1ea7..29785959de18 100644 --- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt @@ -17,6 +17,8 @@ package com.android.systemui.education.data.repository import android.content.Context +import android.hardware.input.InputManager +import android.hardware.input.KeyGestureEvent import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.MutablePreferences import androidx.datastore.preferences.core.PreferenceDataStoreFactory @@ -25,23 +27,31 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.preferencesDataStoreFile +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.contextualeducation.GestureType.ALL_APPS import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope import com.android.systemui.education.data.model.EduDeviceConnectionTime import com.android.systemui.education.data.model.GestureEduModel +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.time.Instant +import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Provider import kotlin.properties.Delegates.notNull +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map /** @@ -64,6 +74,8 @@ interface ContextualEducationRepository { suspend fun updateEduDeviceConnectionTime( transform: (EduDeviceConnectionTime) -> EduDeviceConnectionTime ) + + val keyboardShortcutTriggered: Flow<GestureType> } /** @@ -75,9 +87,13 @@ class UserContextualEducationRepository @Inject constructor( @Application private val applicationContext: Context, - @EduDataStoreScope private val dataStoreScopeProvider: Provider<CoroutineScope> + @EduDataStoreScope private val dataStoreScopeProvider: Provider<CoroutineScope>, + private val inputManager: InputManager, + @Background private val backgroundDispatcher: CoroutineDispatcher, ) : ContextualEducationRepository { companion object { + const val TAG = "UserContextualEducationRepository" + const val SIGNAL_COUNT_SUFFIX = "_SIGNAL_COUNT" const val NUMBER_OF_EDU_SHOWN_SUFFIX = "_NUMBER_OF_EDU_SHOWN" const val LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX = "_LAST_SHORTCUT_TRIGGERED_TIME" @@ -98,6 +114,30 @@ constructor( @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) private val prefData: Flow<Preferences> = datastore.filterNotNull().flatMapLatest { it.data } + override val keyboardShortcutTriggered: Flow<GestureType> = + conflatedCallbackFlow { + val listener = + InputManager.KeyGestureEventListener { event -> + // Only store keyboard shortcut time for gestures providing keyboard + // education + val shortcutType = + when (event.keyGestureType) { + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, + KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS -> ALL_APPS + + else -> null + } + + if (shortcutType != null) { + trySendWithFailureLogging(shortcutType, TAG) + } + } + + inputManager.registerKeyGestureEventListener(Executor(Runnable::run), listener) + awaitClose { inputManager.unregisterKeyGestureEventListener(listener) } + } + .flowOn(backgroundDispatcher) + override fun setUser(userId: Int) { dataStoreScope?.cancel() val newDsScope = dataStoreScopeProvider.get() @@ -136,7 +176,8 @@ constructor( preferences[getLastEducationTimeKey(gestureType)]?.let { Instant.ofEpochSecond(it) }, - userId = userId + userId = userId, + gestureType = gestureType, ) } diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt index 10be26e1ce0f..c88b36495ac2 100644 --- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt @@ -18,7 +18,10 @@ package com.android.systemui.education.domain.interactor import com.android.systemui.CoreStartable import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.contextualeducation.GestureType.ALL_APPS import com.android.systemui.contextualeducation.GestureType.BACK +import com.android.systemui.contextualeducation.GestureType.HOME +import com.android.systemui.contextualeducation.GestureType.OVERVIEW import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.education.dagger.ContextualEducationModule.EduClock @@ -53,6 +56,13 @@ constructor( ) : CoreStartable { val backGestureModelFlow = readEduModelsOnSignalCountChanged(BACK) + val homeGestureModelFlow = readEduModelsOnSignalCountChanged(HOME) + val overviewGestureModelFlow = readEduModelsOnSignalCountChanged(OVERVIEW) + val allAppsGestureModelFlow = readEduModelsOnSignalCountChanged(ALL_APPS) + val eduDeviceConnectionTimeFlow = + repository.readEduDeviceConnectionTime().distinctUntilChanged() + + val keyboardShortcutTriggered = repository.keyboardShortcutTriggered override fun start() { backgroundScope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt index 43855d971a2a..faee32694964 100644 --- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt @@ -16,15 +16,8 @@ package com.android.systemui.education.domain.interactor -import android.hardware.input.InputManager -import android.hardware.input.InputManager.KeyGestureEventListener -import android.hardware.input.KeyGestureEvent import android.os.SystemProperties import com.android.systemui.CoreStartable -import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.contextualeducation.GestureType -import com.android.systemui.contextualeducation.GestureType.ALL_APPS -import com.android.systemui.contextualeducation.GestureType.BACK import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.education.dagger.ContextualEducationModule.EduClock @@ -32,19 +25,19 @@ import com.android.systemui.education.data.model.GestureEduModel import com.android.systemui.education.shared.model.EducationInfo import com.android.systemui.education.shared.model.EducationUiType import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository -import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.time.Clock -import java.util.concurrent.Executor import javax.inject.Inject import kotlin.time.Duration import kotlin.time.Duration.Companion.days import kotlin.time.DurationUnit import kotlin.time.toDuration import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch /** Allow listening to new contextual education triggered */ @@ -55,7 +48,6 @@ constructor( @Background private val backgroundScope: CoroutineScope, private val contextualEducationInteractor: ContextualEducationInteractor, private val userInputDeviceRepository: UserInputDeviceRepository, - private val inputManager: InputManager, @EduClock private val clock: Clock, ) : CoreStartable { @@ -82,34 +74,32 @@ constructor( private val _educationTriggered = MutableStateFlow<EducationInfo?>(null) val educationTriggered = _educationTriggered.asStateFlow() - private val keyboardShortcutTriggered: Flow<GestureType> = conflatedCallbackFlow { - val listener = KeyGestureEventListener { event -> - // Only store keyboard shortcut time for gestures providing keyboard education - val shortcutType = - when (event.keyGestureType) { - KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS -> ALL_APPS - else -> null - } - - if (shortcutType != null) { - trySendWithFailureLogging(shortcutType, TAG) - } - } - - inputManager.registerKeyGestureEventListener(Executor(Runnable::run), listener) - awaitClose { inputManager.unregisterKeyGestureEventListener(listener) } - } - + @OptIn(ExperimentalCoroutinesApi::class) override fun start() { backgroundScope.launch { - contextualEducationInteractor.backGestureModelFlow.collect { - if (isUsageSessionExpired(it)) { - contextualEducationInteractor.startNewUsageSession(BACK) - } else if (isEducationNeeded(it)) { - _educationTriggered.value = EducationInfo(BACK, getEduType(it), it.userId) - contextualEducationInteractor.updateOnEduTriggered(BACK) + contextualEducationInteractor.eduDeviceConnectionTimeFlow + .flatMapLatest { + val gestureFlows = mutableListOf<Flow<GestureEduModel>>() + if (it.touchpadFirstConnectionTime != null) { + gestureFlows.add(contextualEducationInteractor.backGestureModelFlow) + gestureFlows.add(contextualEducationInteractor.homeGestureModelFlow) + gestureFlows.add(contextualEducationInteractor.overviewGestureModelFlow) + } + + if (it.keyboardFirstConnectionTime != null) { + gestureFlows.add(contextualEducationInteractor.allAppsGestureModelFlow) + } + gestureFlows.merge() + } + .collect { + if (isUsageSessionExpired(it)) { + contextualEducationInteractor.startNewUsageSession(it.gestureType) + } else if (isEducationNeeded(it)) { + _educationTriggered.value = + EducationInfo(it.gestureType, getEduType(it), it.userId) + contextualEducationInteractor.updateOnEduTriggered(it.gestureType) + } } - } } backgroundScope.launch { @@ -139,7 +129,7 @@ constructor( } backgroundScope.launch { - keyboardShortcutTriggered.collect { + contextualEducationInteractor.keyboardShortcutTriggered.collect { contextualEducationInteractor.updateShortcutTriggerTime(it) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt index 1d2bce2f9b99..1df3ef48d5a7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt @@ -19,7 +19,7 @@ package com.android.systemui.education.data.repository import com.android.systemui.kosmos.Kosmos import java.time.Instant -var Kosmos.contextualEducationRepository: ContextualEducationRepository by +var Kosmos.contextualEducationRepository: FakeContextualEducationRepository by Kosmos.Fixture { FakeContextualEducationRepository() } var Kosmos.fakeEduClock: FakeEduClock by Kosmos.Fixture { FakeEduClock(Instant.MIN) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt index fb4e2fb5fd14..4667bf5292fa 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt @@ -17,39 +17,77 @@ package com.android.systemui.education.data.repository import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.contextualeducation.GestureType.ALL_APPS +import com.android.systemui.contextualeducation.GestureType.BACK +import com.android.systemui.contextualeducation.GestureType.HOME +import com.android.systemui.contextualeducation.GestureType.OVERVIEW import com.android.systemui.education.data.model.EduDeviceConnectionTime import com.android.systemui.education.data.model.GestureEduModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterNotNull class FakeContextualEducationRepository : ContextualEducationRepository { - private val userGestureMap = mutableMapOf<Int, GestureEduModel>() - private val _gestureEduModels = MutableStateFlow(GestureEduModel(userId = 0)) - private val gestureEduModelsFlow = _gestureEduModels.asStateFlow() + private val userGestureMap = mutableMapOf<Int, MutableMap<GestureType, GestureEduModel>>() + + private val _backGestureEduModels = MutableStateFlow(GestureEduModel(BACK, userId = 0)) + private val backGestureEduModelsFlow = _backGestureEduModels.asStateFlow() + + private val _homeGestureEduModels = MutableStateFlow(GestureEduModel(HOME, userId = 0)) + private val homeEduModelsFlow = _homeGestureEduModels.asStateFlow() + + private val _allAppsGestureEduModels = MutableStateFlow(GestureEduModel(ALL_APPS, userId = 0)) + private val allAppsGestureEduModels = _allAppsGestureEduModels.asStateFlow() + + private val _overviewsGestureEduModels = MutableStateFlow(GestureEduModel(OVERVIEW, userId = 0)) + private val overviewsGestureEduModels = _overviewsGestureEduModels.asStateFlow() private val userEduDeviceConnectionTimeMap = mutableMapOf<Int, EduDeviceConnectionTime>() private val _eduDeviceConnectionTime = MutableStateFlow(EduDeviceConnectionTime()) private val eduDeviceConnectionTime = _eduDeviceConnectionTime.asStateFlow() + private val _keyboardShortcutTriggered = MutableStateFlow<GestureType?>(null) + private var currentUser: Int = 0 override fun setUser(userId: Int) { if (!userGestureMap.contains(userId)) { - userGestureMap[userId] = GestureEduModel(userId = userId) + userGestureMap[userId] = createGestureEduModelMap(userId = userId) userEduDeviceConnectionTimeMap[userId] = EduDeviceConnectionTime() } // save data of current user to the map - userGestureMap[currentUser] = _gestureEduModels.value - userEduDeviceConnectionTimeMap[currentUser] = _eduDeviceConnectionTime.value + val currentUserMap = userGestureMap[currentUser]!! + currentUserMap[BACK] = _backGestureEduModels.value + currentUserMap[HOME] = _homeGestureEduModels.value + currentUserMap[ALL_APPS] = _allAppsGestureEduModels.value + currentUserMap[OVERVIEW] = _overviewsGestureEduModels.value + // switch to data of new user - _gestureEduModels.value = userGestureMap[userId]!! + val newUserGestureMap = userGestureMap[userId]!! + newUserGestureMap[BACK]?.let { _backGestureEduModels.value = it } + newUserGestureMap[HOME]?.let { _homeGestureEduModels.value = it } + newUserGestureMap[ALL_APPS]?.let { _allAppsGestureEduModels.value = it } + newUserGestureMap[OVERVIEW]?.let { _overviewsGestureEduModels.value = it } + + userEduDeviceConnectionTimeMap[currentUser] = _eduDeviceConnectionTime.value _eduDeviceConnectionTime.value = userEduDeviceConnectionTimeMap[userId]!! } + private fun createGestureEduModelMap(userId: Int): MutableMap<GestureType, GestureEduModel> { + val gestureModelMap = mutableMapOf<GestureType, GestureEduModel>() + GestureType.values().forEach { gestureModelMap[it] = GestureEduModel(it, userId = userId) } + return gestureModelMap + } + override fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> { - return gestureEduModelsFlow + return when (gestureType) { + BACK -> backGestureEduModelsFlow + HOME -> homeEduModelsFlow + ALL_APPS -> allAppsGestureEduModels + OVERVIEW -> overviewsGestureEduModels + } } override fun readEduDeviceConnectionTime(): Flow<EduDeviceConnectionTime> { @@ -60,8 +98,16 @@ class FakeContextualEducationRepository : ContextualEducationRepository { gestureType: GestureType, transform: (GestureEduModel) -> GestureEduModel ) { - val currentModel = _gestureEduModels.value - _gestureEduModels.value = transform(currentModel) + val gestureModels = + when (gestureType) { + BACK -> _backGestureEduModels + HOME -> _homeGestureEduModels + ALL_APPS -> _allAppsGestureEduModels + OVERVIEW -> _overviewsGestureEduModels + } + + val currentModel = gestureModels.value + gestureModels.value = transform(currentModel) } override suspend fun updateEduDeviceConnectionTime( @@ -70,4 +116,11 @@ class FakeContextualEducationRepository : ContextualEducationRepository { val currentModel = _eduDeviceConnectionTime.value _eduDeviceConnectionTime.value = transform(currentModel) } + + override val keyboardShortcutTriggered: Flow<GestureType> + get() = _keyboardShortcutTriggered.filterNotNull() + + fun setKeyboardShortcutTriggered(gestureType: GestureType) { + _keyboardShortcutTriggered.value = gestureType + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt index 80f6fc24ef2c..2d275f9e9691 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt @@ -40,8 +40,7 @@ var Kosmos.keyboardTouchpadEduInteractor by touchpadRepository, userRepository ), - clock = fakeEduClock, - inputManager = mockEduInputManager + clock = fakeEduClock ) } |