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

Commit 4b33340b authored by Yalan Yiue's avatar Yalan Yiue Committed by Android (Google) Code Review
Browse files

Merge "Store launch time when tutorial is launched" into main

parents 2ff41208 8b1607df
Loading
Loading
Loading
Loading
+18 −2
Original line number Diff line number Diff line
@@ -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
}
+18 −14
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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> {
@@ -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)
@@ -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"
    }
}

+16 −12
Original line number Diff line number Diff line
@@ -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
@@ -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)
    }

@@ -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")
    }
@@ -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
                    )
                )
    }

+7 −4
Original line number Diff line number Diff line
@@ -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()
        }
}