Loading packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt +13 −1 Original line number Diff line number Diff line Loading @@ -28,6 +28,8 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.shared.education.GestureType.BACK_GESTURE import com.google.common.truth.Truth.assertThat import java.io.File import java.time.Clock import java.time.Instant import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.TestScope Loading @@ -48,6 +50,7 @@ class ContextualEducationRepositoryTest : SysuiTestCase() { private val dsScopeProvider: Provider<CoroutineScope> = Provider { TestScope(kosmos.testDispatcher).backgroundScope } private val clock: Clock = FakeEduClock(Instant.ofEpochMilli(1000)) private val testUserId = 1111 // For deleting any test files created after the test Loading @@ -59,7 +62,7 @@ class ContextualEducationRepositoryTest : SysuiTestCase() { // needed before calling TemporaryFolder.newFolder(). val testContext = TestContext(context, tmpFolder.newFolder()) val userRepository = UserContextualEducationRepository(testContext, dsScopeProvider) underTest = ContextualEducationRepository(userRepository) underTest = ContextualEducationRepositoryImpl(clock, userRepository) underTest.setUser(testUserId) } Loading @@ -85,6 +88,15 @@ class ContextualEducationRepositoryTest : SysuiTestCase() { assertThat(model?.signalCount).isEqualTo(1) } @Test fun dataAddedOnUpdateShortcutTriggerTime() = testScope.runTest { val model by collectLastValue(underTest.readGestureEduModelFlow(BACK_GESTURE)) assertThat(model?.lastShortcutTriggeredTime).isNull() underTest.updateShortcutTriggerTime(BACK_GESTURE) assertThat(model?.lastShortcutTriggeredTime).isEqualTo(clock.instant()) } /** Test context which allows overriding getFilesDir path */ private class TestContext(context: Context, private val folder: File) : SysuiTestableContext(context) { Loading packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt +17 −0 Original line number Diff line number Diff line Loading @@ -17,8 +17,12 @@ package com.android.systemui.education.dagger import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.education.data.repository.ContextualEducationRepository import com.android.systemui.education.data.repository.ContextualEducationRepositoryImpl import dagger.Binds import dagger.Module import dagger.Provides import java.time.Clock import javax.inject.Qualifier import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope Loading @@ -26,8 +30,15 @@ import kotlinx.coroutines.SupervisorJob @Module interface ContextualEducationModule { @Binds fun bindContextualEducationRepository( impl: ContextualEducationRepositoryImpl ): ContextualEducationRepository @Qualifier annotation class EduDataStoreScope @Qualifier annotation class EduClock companion object { @EduDataStoreScope @Provides Loading @@ -36,5 +47,11 @@ interface ContextualEducationModule { ): CoroutineScope { return CoroutineScope(bgDispatcher + SupervisorJob()) } @EduClock @Provides fun provideEduClock(): Clock { return Clock.systemUTC() } } } packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt +5 −2 Original line number Diff line number Diff line Loading @@ -16,11 +16,14 @@ package com.android.systemui.education.data.model import java.time.Instant /** * Model to store education data related to each gesture (e.g. Back, Home, All Apps, Overview). Each * gesture stores its own model separately. */ data class GestureEduModel( val signalCount: Int, val educationShownCount: Int, val signalCount: Int = 0, val educationShownCount: Int = 0, val lastShortcutTriggeredTime: Instant? = null, ) packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt +29 −5 Original line number Diff line number Diff line Loading @@ -17,26 +17,50 @@ package com.android.systemui.education.data.repository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.education.dagger.ContextualEducationModule.EduClock import com.android.systemui.education.data.model.GestureEduModel import com.android.systemui.shared.education.GestureType import java.time.Clock import javax.inject.Inject import kotlinx.coroutines.flow.Flow /** Encapsulates the functions of ContextualEducationRepository. */ interface ContextualEducationRepository { fun setUser(userId: Int) fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> suspend fun incrementSignalCount(gestureType: GestureType) suspend fun updateShortcutTriggerTime(gestureType: GestureType) } /** * Provide methods to read and update on field level and allow setting datastore when user is * changed */ @SysUISingleton class ContextualEducationRepository class ContextualEducationRepositoryImpl @Inject constructor(private val userEduRepository: UserContextualEducationRepository) { constructor( @EduClock private val clock: Clock, private val userEduRepository: UserContextualEducationRepository ) : ContextualEducationRepository { /** To change data store when user is changed */ fun setUser(userId: Int) = userEduRepository.setUser(userId) override fun setUser(userId: Int) = userEduRepository.setUser(userId) fun readGestureEduModelFlow(gestureType: GestureType) = override fun readGestureEduModelFlow(gestureType: GestureType) = userEduRepository.readGestureEduModelFlow(gestureType) suspend fun incrementSignalCount(gestureType: GestureType) { override suspend fun incrementSignalCount(gestureType: GestureType) { userEduRepository.updateGestureEduModel(gestureType) { it.copy(signalCount = it.signalCount + 1) } } override suspend fun updateShortcutTriggerTime(gestureType: GestureType) { userEduRepository.updateGestureEduModel(gestureType) { it.copy(lastShortcutTriggeredTime = clock.instant()) } } } packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt +28 −0 Original line number Diff line number Diff line Loading @@ -18,16 +18,19 @@ package com.android.systemui.education.data.repository import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.MutablePreferences import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences 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.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope import com.android.systemui.education.data.model.GestureEduModel import com.android.systemui.shared.education.GestureType import java.time.Instant import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.CoroutineScope Loading Loading @@ -55,6 +58,7 @@ constructor( companion object { 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" const val DATASTORE_DIR = "education/USER%s_ContextualEducation" } Loading Loading @@ -91,6 +95,10 @@ constructor( return GestureEduModel( signalCount = preferences[getSignalCountKey(gestureType)] ?: 0, educationShownCount = preferences[getEducationShownCountKey(gestureType)] ?: 0, lastShortcutTriggeredTime = preferences[getLastShortcutTriggeredTimeKey(gestureType)]?.let { Instant.ofEpochMilli(it) }, ) } Loading @@ -103,6 +111,11 @@ constructor( val updatedModel = transform(currentModel) preferences[getSignalCountKey(gestureType)] = updatedModel.signalCount preferences[getEducationShownCountKey(gestureType)] = updatedModel.educationShownCount updateTimeByInstant( preferences, updatedModel.lastShortcutTriggeredTime, getLastShortcutTriggeredTimeKey(gestureType) ) } } Loading @@ -111,4 +124,19 @@ constructor( private fun getEducationShownCountKey(gestureType: GestureType): Preferences.Key<Int> = intPreferencesKey(gestureType.name + NUMBER_OF_EDU_SHOWN_SUFFIX) private fun getLastShortcutTriggeredTimeKey(gestureType: GestureType): Preferences.Key<Long> = longPreferencesKey(gestureType.name + LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX) private fun updateTimeByInstant( preferences: MutablePreferences, instant: Instant?, key: Preferences.Key<Long> ) { if (instant != null) { preferences[key] = instant.toEpochMilli() } else { preferences.remove(key) } } } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt +13 −1 Original line number Diff line number Diff line Loading @@ -28,6 +28,8 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.shared.education.GestureType.BACK_GESTURE import com.google.common.truth.Truth.assertThat import java.io.File import java.time.Clock import java.time.Instant import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.TestScope Loading @@ -48,6 +50,7 @@ class ContextualEducationRepositoryTest : SysuiTestCase() { private val dsScopeProvider: Provider<CoroutineScope> = Provider { TestScope(kosmos.testDispatcher).backgroundScope } private val clock: Clock = FakeEduClock(Instant.ofEpochMilli(1000)) private val testUserId = 1111 // For deleting any test files created after the test Loading @@ -59,7 +62,7 @@ class ContextualEducationRepositoryTest : SysuiTestCase() { // needed before calling TemporaryFolder.newFolder(). val testContext = TestContext(context, tmpFolder.newFolder()) val userRepository = UserContextualEducationRepository(testContext, dsScopeProvider) underTest = ContextualEducationRepository(userRepository) underTest = ContextualEducationRepositoryImpl(clock, userRepository) underTest.setUser(testUserId) } Loading @@ -85,6 +88,15 @@ class ContextualEducationRepositoryTest : SysuiTestCase() { assertThat(model?.signalCount).isEqualTo(1) } @Test fun dataAddedOnUpdateShortcutTriggerTime() = testScope.runTest { val model by collectLastValue(underTest.readGestureEduModelFlow(BACK_GESTURE)) assertThat(model?.lastShortcutTriggeredTime).isNull() underTest.updateShortcutTriggerTime(BACK_GESTURE) assertThat(model?.lastShortcutTriggeredTime).isEqualTo(clock.instant()) } /** Test context which allows overriding getFilesDir path */ private class TestContext(context: Context, private val folder: File) : SysuiTestableContext(context) { Loading
packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt +17 −0 Original line number Diff line number Diff line Loading @@ -17,8 +17,12 @@ package com.android.systemui.education.dagger import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.education.data.repository.ContextualEducationRepository import com.android.systemui.education.data.repository.ContextualEducationRepositoryImpl import dagger.Binds import dagger.Module import dagger.Provides import java.time.Clock import javax.inject.Qualifier import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope Loading @@ -26,8 +30,15 @@ import kotlinx.coroutines.SupervisorJob @Module interface ContextualEducationModule { @Binds fun bindContextualEducationRepository( impl: ContextualEducationRepositoryImpl ): ContextualEducationRepository @Qualifier annotation class EduDataStoreScope @Qualifier annotation class EduClock companion object { @EduDataStoreScope @Provides Loading @@ -36,5 +47,11 @@ interface ContextualEducationModule { ): CoroutineScope { return CoroutineScope(bgDispatcher + SupervisorJob()) } @EduClock @Provides fun provideEduClock(): Clock { return Clock.systemUTC() } } }
packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt +5 −2 Original line number Diff line number Diff line Loading @@ -16,11 +16,14 @@ package com.android.systemui.education.data.model import java.time.Instant /** * Model to store education data related to each gesture (e.g. Back, Home, All Apps, Overview). Each * gesture stores its own model separately. */ data class GestureEduModel( val signalCount: Int, val educationShownCount: Int, val signalCount: Int = 0, val educationShownCount: Int = 0, val lastShortcutTriggeredTime: Instant? = null, )
packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt +29 −5 Original line number Diff line number Diff line Loading @@ -17,26 +17,50 @@ package com.android.systemui.education.data.repository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.education.dagger.ContextualEducationModule.EduClock import com.android.systemui.education.data.model.GestureEduModel import com.android.systemui.shared.education.GestureType import java.time.Clock import javax.inject.Inject import kotlinx.coroutines.flow.Flow /** Encapsulates the functions of ContextualEducationRepository. */ interface ContextualEducationRepository { fun setUser(userId: Int) fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> suspend fun incrementSignalCount(gestureType: GestureType) suspend fun updateShortcutTriggerTime(gestureType: GestureType) } /** * Provide methods to read and update on field level and allow setting datastore when user is * changed */ @SysUISingleton class ContextualEducationRepository class ContextualEducationRepositoryImpl @Inject constructor(private val userEduRepository: UserContextualEducationRepository) { constructor( @EduClock private val clock: Clock, private val userEduRepository: UserContextualEducationRepository ) : ContextualEducationRepository { /** To change data store when user is changed */ fun setUser(userId: Int) = userEduRepository.setUser(userId) override fun setUser(userId: Int) = userEduRepository.setUser(userId) fun readGestureEduModelFlow(gestureType: GestureType) = override fun readGestureEduModelFlow(gestureType: GestureType) = userEduRepository.readGestureEduModelFlow(gestureType) suspend fun incrementSignalCount(gestureType: GestureType) { override suspend fun incrementSignalCount(gestureType: GestureType) { userEduRepository.updateGestureEduModel(gestureType) { it.copy(signalCount = it.signalCount + 1) } } override suspend fun updateShortcutTriggerTime(gestureType: GestureType) { userEduRepository.updateGestureEduModel(gestureType) { it.copy(lastShortcutTriggeredTime = clock.instant()) } } }
packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt +28 −0 Original line number Diff line number Diff line Loading @@ -18,16 +18,19 @@ package com.android.systemui.education.data.repository import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.MutablePreferences import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences 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.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope import com.android.systemui.education.data.model.GestureEduModel import com.android.systemui.shared.education.GestureType import java.time.Instant import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.CoroutineScope Loading Loading @@ -55,6 +58,7 @@ constructor( companion object { 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" const val DATASTORE_DIR = "education/USER%s_ContextualEducation" } Loading Loading @@ -91,6 +95,10 @@ constructor( return GestureEduModel( signalCount = preferences[getSignalCountKey(gestureType)] ?: 0, educationShownCount = preferences[getEducationShownCountKey(gestureType)] ?: 0, lastShortcutTriggeredTime = preferences[getLastShortcutTriggeredTimeKey(gestureType)]?.let { Instant.ofEpochMilli(it) }, ) } Loading @@ -103,6 +111,11 @@ constructor( val updatedModel = transform(currentModel) preferences[getSignalCountKey(gestureType)] = updatedModel.signalCount preferences[getEducationShownCountKey(gestureType)] = updatedModel.educationShownCount updateTimeByInstant( preferences, updatedModel.lastShortcutTriggeredTime, getLastShortcutTriggeredTimeKey(gestureType) ) } } Loading @@ -111,4 +124,19 @@ constructor( private fun getEducationShownCountKey(gestureType: GestureType): Preferences.Key<Int> = intPreferencesKey(gestureType.name + NUMBER_OF_EDU_SHOWN_SUFFIX) private fun getLastShortcutTriggeredTimeKey(gestureType: GestureType): Preferences.Key<Long> = longPreferencesKey(gestureType.name + LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX) private fun updateTimeByInstant( preferences: MutablePreferences, instant: Instant?, key: Preferences.Key<Long> ) { if (instant != null) { preferences[key] = instant.toEpochMilli() } else { preferences.remove(key) } } }