Loading packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt +18 −2 Original line number Diff line number Diff line Loading @@ -16,7 +16,23 @@ package com.android.systemui.inputdevice.tutorial.data.model data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectTime: Long? = null) { import java.time.Instant data class DeviceSchedulerInfo( var launchTime: Instant? = null, var firstConnectionTime: Instant? = null ) { constructor( launchTimeSec: Long?, firstConnectionTimeSec: Long? ) : this( launchTimeSec?.let { Instant.ofEpochSecond(it) }, firstConnectionTimeSec?.let { Instant.ofEpochSecond(it) } ) val wasEverConnected: Boolean get() = connectTime != null get() = firstConnectionTime != null val isLaunched: Boolean get() = launchTime != null } packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt +18 −14 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ import android.content.Context import androidx.annotation.VisibleForTesting import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.preferencesDataStore Loading @@ -28,6 +27,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.inputdevice.tutorial.data.model.DeviceSchedulerInfo import java.time.Instant import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first Loading @@ -43,28 +43,31 @@ class TutorialSchedulerRepository( constructor( @Application applicationContext: Context, @Background backgroundScope: CoroutineScope ) : this(applicationContext, backgroundScope, dataStoreName = "TutorialScheduler") ) : this(applicationContext, backgroundScope, dataStoreName = DATASTORE_NAME) private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = dataStoreName, scope = backgroundScope) suspend fun isLaunched(deviceType: DeviceType): Boolean = loadData()[deviceType]!!.isLaunched suspend fun launchTime(deviceType: DeviceType): Instant? = loadData()[deviceType]!!.launchTime suspend fun wasEverConnected(deviceType: DeviceType): Boolean = loadData()[deviceType]!!.wasEverConnected suspend fun connectTime(deviceType: DeviceType): Long = loadData()[deviceType]!!.connectTime!! suspend fun firstConnectionTime(deviceType: DeviceType): Instant? = loadData()[deviceType]!!.firstConnectionTime private suspend fun loadData(): Map<DeviceType, DeviceSchedulerInfo> { return applicationContext.dataStore.data.map { pref -> getSchedulerInfo(pref) }.first() } suspend fun updateConnectTime(device: DeviceType, time: Long) { applicationContext.dataStore.edit { pref -> pref[getConnectKey(device)] = time } suspend fun updateFirstConnectionTime(device: DeviceType, time: Instant) { applicationContext.dataStore.edit { pref -> pref[getConnectKey(device)] = time.epochSecond } } suspend fun updateLaunch(device: DeviceType) { applicationContext.dataStore.edit { pref -> pref[getLaunchedKey(device)] = true } suspend fun updateLaunchTime(device: DeviceType, time: Instant) { applicationContext.dataStore.edit { pref -> pref[getLaunchKey(device)] = time.epochSecond } } private fun getSchedulerInfo(pref: Preferences): Map<DeviceType, DeviceSchedulerInfo> { Loading @@ -75,13 +78,13 @@ class TutorialSchedulerRepository( } private fun getDeviceSchedulerInfo(pref: Preferences, device: DeviceType): DeviceSchedulerInfo { val isLaunched = pref[getLaunchedKey(device)] ?: false val connectionTime = pref[getConnectKey(device)] ?: null return DeviceSchedulerInfo(isLaunched, connectionTime) val launchTime = pref[getLaunchKey(device)] val connectionTime = pref[getConnectKey(device)] return DeviceSchedulerInfo(launchTime, connectionTime) } private fun getLaunchedKey(device: DeviceType) = booleanPreferencesKey(device.name + IS_LAUNCHED_SUFFIX) private fun getLaunchKey(device: DeviceType) = longPreferencesKey(device.name + LAUNCH_TIME_SUFFIX) private fun getConnectKey(device: DeviceType) = longPreferencesKey(device.name + CONNECT_TIME_SUFFIX) Loading @@ -92,8 +95,9 @@ class TutorialSchedulerRepository( } companion object { const val IS_LAUNCHED_SUFFIX = "_IS_LAUNCHED" const val CONNECT_TIME_SUFFIX = "_CONNECTED_TIME" const val DATASTORE_NAME = "TutorialScheduler" const val LAUNCH_TIME_SUFFIX = "_LAUNCH_TIME" const val CONNECT_TIME_SUFFIX = "_CONNECT_TIME" } } Loading packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt +16 −12 Original line number Diff line number Diff line Loading @@ -26,9 +26,11 @@ import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUC import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository import com.android.systemui.keyboard.data.repository.KeyboardRepository import com.android.systemui.touchpad.data.repository.TouchpadRepository import java.time.Duration import java.time.Instant import javax.inject.Inject import kotlin.time.Duration.Companion.hours import kotlin.time.toKotlinDuration import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.filter Loading Loading @@ -84,9 +86,9 @@ constructor( private suspend fun schedule(deviceType: DeviceType) { if (!repo.wasEverConnected(deviceType)) { waitForDeviceConnection(deviceType) repo.updateConnectTime(deviceType, Instant.now().toEpochMilli()) repo.updateFirstConnectionTime(deviceType, Instant.now()) } delay(remainingTimeMillis(start = repo.connectTime(deviceType))) delay(remainingTime(start = repo.firstConnectionTime(deviceType)!!)) waitForDeviceConnection(deviceType) } Loading @@ -95,9 +97,9 @@ constructor( private suspend fun launchTutorial(tutorialType: TutorialType) { if (tutorialType == TutorialType.KEYBOARD || tutorialType == TutorialType.BOTH) repo.updateLaunch(KEYBOARD) repo.updateLaunchTime(KEYBOARD, Instant.now()) if (tutorialType == TutorialType.TOUCHPAD || tutorialType == TutorialType.BOTH) repo.updateLaunch(TOUCHPAD) repo.updateLaunchTime(TOUCHPAD, Instant.now()) // TODO: launch tutorial Log.d(TAG, "Launch tutorial for $tutorialType") } Loading @@ -113,19 +115,21 @@ constructor( return if (deviceType == KEYBOARD) TutorialType.KEYBOARD else TutorialType.TOUCHPAD } private fun remainingTimeMillis(start: Long): Long { val elapsed = Instant.now().toEpochMilli() - start return LAUNCH_DELAY - elapsed private fun remainingTime(start: Instant): kotlin.time.Duration { val elapsed = Duration.between(start, Instant.now()) return LAUNCH_DELAY.minus(elapsed).toKotlinDuration() } companion object { const val TAG = "TutorialSchedulerInteractor" private val DEFAULT_LAUNCH_DELAY = 72.hours.inWholeMilliseconds private val LAUNCH_DELAY: Long private val DEFAULT_LAUNCH_DELAY_SEC = 72.hours.inWholeSeconds private val LAUNCH_DELAY: Duration get() = Duration.ofSeconds( SystemProperties.getLong( "persist.peripheral_tutorial_delay_ms", DEFAULT_LAUNCH_DELAY "persist.peripheral_tutorial_delay_sec", DEFAULT_LAUNCH_DELAY_SEC ) ) } Loading packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt +7 −4 Original line number Diff line number Diff line Loading @@ -68,20 +68,23 @@ class TutorialSchedulerRepositoryTest : SysuiTestCase() { @Test fun connectKeyboard() = testScope.runTest { val now = Instant.now().toEpochMilli() underTest.updateConnectTime(KEYBOARD, now) val now = Instant.now() underTest.updateFirstConnectionTime(KEYBOARD, now) assertThat(underTest.wasEverConnected(KEYBOARD)).isTrue() assertThat(underTest.connectTime(KEYBOARD)).isEqualTo(now) assertThat(underTest.firstConnectionTime(KEYBOARD)!!.epochSecond) .isEqualTo(now.epochSecond) assertThat(underTest.wasEverConnected(TOUCHPAD)).isFalse() } @Test fun launchKeyboard() = testScope.runTest { underTest.updateLaunch(KEYBOARD) val now = Instant.now() underTest.updateLaunchTime(KEYBOARD, now) assertThat(underTest.isLaunched(KEYBOARD)).isTrue() assertThat(underTest.launchTime(KEYBOARD)!!.epochSecond).isEqualTo(now.epochSecond) assertThat(underTest.isLaunched(TOUCHPAD)).isFalse() } } Loading
packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt +18 −2 Original line number Diff line number Diff line Loading @@ -16,7 +16,23 @@ package com.android.systemui.inputdevice.tutorial.data.model data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectTime: Long? = null) { import java.time.Instant data class DeviceSchedulerInfo( var launchTime: Instant? = null, var firstConnectionTime: Instant? = null ) { constructor( launchTimeSec: Long?, firstConnectionTimeSec: Long? ) : this( launchTimeSec?.let { Instant.ofEpochSecond(it) }, firstConnectionTimeSec?.let { Instant.ofEpochSecond(it) } ) val wasEverConnected: Boolean get() = connectTime != null get() = firstConnectionTime != null val isLaunched: Boolean get() = launchTime != null }
packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt +18 −14 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ import android.content.Context import androidx.annotation.VisibleForTesting import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.preferencesDataStore Loading @@ -28,6 +27,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.inputdevice.tutorial.data.model.DeviceSchedulerInfo import java.time.Instant import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first Loading @@ -43,28 +43,31 @@ class TutorialSchedulerRepository( constructor( @Application applicationContext: Context, @Background backgroundScope: CoroutineScope ) : this(applicationContext, backgroundScope, dataStoreName = "TutorialScheduler") ) : this(applicationContext, backgroundScope, dataStoreName = DATASTORE_NAME) private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = dataStoreName, scope = backgroundScope) suspend fun isLaunched(deviceType: DeviceType): Boolean = loadData()[deviceType]!!.isLaunched suspend fun launchTime(deviceType: DeviceType): Instant? = loadData()[deviceType]!!.launchTime suspend fun wasEverConnected(deviceType: DeviceType): Boolean = loadData()[deviceType]!!.wasEverConnected suspend fun connectTime(deviceType: DeviceType): Long = loadData()[deviceType]!!.connectTime!! suspend fun firstConnectionTime(deviceType: DeviceType): Instant? = loadData()[deviceType]!!.firstConnectionTime private suspend fun loadData(): Map<DeviceType, DeviceSchedulerInfo> { return applicationContext.dataStore.data.map { pref -> getSchedulerInfo(pref) }.first() } suspend fun updateConnectTime(device: DeviceType, time: Long) { applicationContext.dataStore.edit { pref -> pref[getConnectKey(device)] = time } suspend fun updateFirstConnectionTime(device: DeviceType, time: Instant) { applicationContext.dataStore.edit { pref -> pref[getConnectKey(device)] = time.epochSecond } } suspend fun updateLaunch(device: DeviceType) { applicationContext.dataStore.edit { pref -> pref[getLaunchedKey(device)] = true } suspend fun updateLaunchTime(device: DeviceType, time: Instant) { applicationContext.dataStore.edit { pref -> pref[getLaunchKey(device)] = time.epochSecond } } private fun getSchedulerInfo(pref: Preferences): Map<DeviceType, DeviceSchedulerInfo> { Loading @@ -75,13 +78,13 @@ class TutorialSchedulerRepository( } private fun getDeviceSchedulerInfo(pref: Preferences, device: DeviceType): DeviceSchedulerInfo { val isLaunched = pref[getLaunchedKey(device)] ?: false val connectionTime = pref[getConnectKey(device)] ?: null return DeviceSchedulerInfo(isLaunched, connectionTime) val launchTime = pref[getLaunchKey(device)] val connectionTime = pref[getConnectKey(device)] return DeviceSchedulerInfo(launchTime, connectionTime) } private fun getLaunchedKey(device: DeviceType) = booleanPreferencesKey(device.name + IS_LAUNCHED_SUFFIX) private fun getLaunchKey(device: DeviceType) = longPreferencesKey(device.name + LAUNCH_TIME_SUFFIX) private fun getConnectKey(device: DeviceType) = longPreferencesKey(device.name + CONNECT_TIME_SUFFIX) Loading @@ -92,8 +95,9 @@ class TutorialSchedulerRepository( } companion object { const val IS_LAUNCHED_SUFFIX = "_IS_LAUNCHED" const val CONNECT_TIME_SUFFIX = "_CONNECTED_TIME" const val DATASTORE_NAME = "TutorialScheduler" const val LAUNCH_TIME_SUFFIX = "_LAUNCH_TIME" const val CONNECT_TIME_SUFFIX = "_CONNECT_TIME" } } Loading
packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt +16 −12 Original line number Diff line number Diff line Loading @@ -26,9 +26,11 @@ import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUC import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository import com.android.systemui.keyboard.data.repository.KeyboardRepository import com.android.systemui.touchpad.data.repository.TouchpadRepository import java.time.Duration import java.time.Instant import javax.inject.Inject import kotlin.time.Duration.Companion.hours import kotlin.time.toKotlinDuration import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.filter Loading Loading @@ -84,9 +86,9 @@ constructor( private suspend fun schedule(deviceType: DeviceType) { if (!repo.wasEverConnected(deviceType)) { waitForDeviceConnection(deviceType) repo.updateConnectTime(deviceType, Instant.now().toEpochMilli()) repo.updateFirstConnectionTime(deviceType, Instant.now()) } delay(remainingTimeMillis(start = repo.connectTime(deviceType))) delay(remainingTime(start = repo.firstConnectionTime(deviceType)!!)) waitForDeviceConnection(deviceType) } Loading @@ -95,9 +97,9 @@ constructor( private suspend fun launchTutorial(tutorialType: TutorialType) { if (tutorialType == TutorialType.KEYBOARD || tutorialType == TutorialType.BOTH) repo.updateLaunch(KEYBOARD) repo.updateLaunchTime(KEYBOARD, Instant.now()) if (tutorialType == TutorialType.TOUCHPAD || tutorialType == TutorialType.BOTH) repo.updateLaunch(TOUCHPAD) repo.updateLaunchTime(TOUCHPAD, Instant.now()) // TODO: launch tutorial Log.d(TAG, "Launch tutorial for $tutorialType") } Loading @@ -113,19 +115,21 @@ constructor( return if (deviceType == KEYBOARD) TutorialType.KEYBOARD else TutorialType.TOUCHPAD } private fun remainingTimeMillis(start: Long): Long { val elapsed = Instant.now().toEpochMilli() - start return LAUNCH_DELAY - elapsed private fun remainingTime(start: Instant): kotlin.time.Duration { val elapsed = Duration.between(start, Instant.now()) return LAUNCH_DELAY.minus(elapsed).toKotlinDuration() } companion object { const val TAG = "TutorialSchedulerInteractor" private val DEFAULT_LAUNCH_DELAY = 72.hours.inWholeMilliseconds private val LAUNCH_DELAY: Long private val DEFAULT_LAUNCH_DELAY_SEC = 72.hours.inWholeSeconds private val LAUNCH_DELAY: Duration get() = Duration.ofSeconds( SystemProperties.getLong( "persist.peripheral_tutorial_delay_ms", DEFAULT_LAUNCH_DELAY "persist.peripheral_tutorial_delay_sec", DEFAULT_LAUNCH_DELAY_SEC ) ) } Loading
packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt +7 −4 Original line number Diff line number Diff line Loading @@ -68,20 +68,23 @@ class TutorialSchedulerRepositoryTest : SysuiTestCase() { @Test fun connectKeyboard() = testScope.runTest { val now = Instant.now().toEpochMilli() underTest.updateConnectTime(KEYBOARD, now) val now = Instant.now() underTest.updateFirstConnectionTime(KEYBOARD, now) assertThat(underTest.wasEverConnected(KEYBOARD)).isTrue() assertThat(underTest.connectTime(KEYBOARD)).isEqualTo(now) assertThat(underTest.firstConnectionTime(KEYBOARD)!!.epochSecond) .isEqualTo(now.epochSecond) assertThat(underTest.wasEverConnected(TOUCHPAD)).isFalse() } @Test fun launchKeyboard() = testScope.runTest { underTest.updateLaunch(KEYBOARD) val now = Instant.now() underTest.updateLaunchTime(KEYBOARD, now) assertThat(underTest.isLaunched(KEYBOARD)).isTrue() assertThat(underTest.launchTime(KEYBOARD)!!.epochSecond).isEqualTo(now.epochSecond) assertThat(underTest.isLaunched(TOUCHPAD)).isFalse() } }