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

Commit fe871963 authored by yyalan's avatar yyalan
Browse files

Store tutorial scheduling info

TutorialSchedulerRepository takes care of the data storage and loading
using Preference DataStore

Bug: 344862874
Flag: com.android.systemui.shared.new_touchpad_gestures_tutorial
Test: turn the device off and make sure tutorial is launched based on
first connection
Change-Id: I91ff6265be1a12403bb455ffe0518f941533f937
parent 6d6d6f5d
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ import com.android.systemui.dreams.AssistantAttentionMonitor
import com.android.systemui.dreams.DreamMonitor
import com.android.systemui.dreams.homecontrols.HomeControlsDreamStartable
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.inputdevice.oobe.KeyboardTouchpadOobeTutorialCoreStartable
import com.android.systemui.inputdevice.tutorial.KeyboardTouchpadTutorialCoreStartable
import com.android.systemui.keyboard.KeyboardUI
import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable
import com.android.systemui.keyguard.KeyguardViewConfigurator
@@ -258,9 +258,9 @@ abstract class SystemUICoreStartableModule {

    @Binds
    @IntoMap
    @ClassKey(KeyboardTouchpadOobeTutorialCoreStartable::class)
    abstract fun bindOobeSchedulerCoreStartable(
        listener: KeyboardTouchpadOobeTutorialCoreStartable
    @ClassKey(KeyboardTouchpadTutorialCoreStartable::class)
    abstract fun bindKeyboardTouchpadTutorialCoreStartable(
        listener: KeyboardTouchpadTutorialCoreStartable
    ): CoreStartable

    @Binds
+7 −6
Original line number Diff line number Diff line
@@ -14,23 +14,24 @@
 * limitations under the License.
 */

package com.android.systemui.inputdevice.oobe
package com.android.systemui.inputdevice.tutorial

import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.inputdevice.oobe.domain.interactor.OobeSchedulerInteractor
import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor
import com.android.systemui.shared.Flags.newTouchpadGesturesTutorial
import dagger.Lazy
import javax.inject.Inject

/** A [CoreStartable] to launch a scheduler for keyboard and touchpad OOBE education */
/** A [CoreStartable] to launch a scheduler for keyboard and touchpad education */
@SysUISingleton
class KeyboardTouchpadOobeTutorialCoreStartable
class KeyboardTouchpadTutorialCoreStartable
@Inject
constructor(private val oobeSchedulerInteractor: Lazy<OobeSchedulerInteractor>) : CoreStartable {
constructor(private val tutorialSchedulerInteractor: Lazy<TutorialSchedulerInteractor>) :
    CoreStartable {
    override fun start() {
        if (newTouchpadGesturesTutorial()) {
            oobeSchedulerInteractor.get().start()
            tutorialSchedulerInteractor.get().start()
        }
    }
}
+4 −4
Original line number Diff line number Diff line
@@ -14,14 +14,14 @@
 * limitations under the License.
 */

package com.android.systemui.inputdevice.oobe.data.model
package com.android.systemui.inputdevice.tutorial.data.model

data class OobeSchedulerInfo(
data class TutorialSchedulerInfo(
    val keyboard: DeviceSchedulerInfo = DeviceSchedulerInfo(),
    val touchpad: DeviceSchedulerInfo = DeviceSchedulerInfo()
)

data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectionTime: Long? = null) {
data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectTime: Long? = null) {
    val wasEverConnected: Boolean
        get() = connectionTime != null
        get() = connectTime != null
}
+83 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.inputdevice.tutorial.data.repository

import android.content.Context
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
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.inputdevice.tutorial.data.model.DeviceSchedulerInfo
import com.android.systemui.inputdevice.tutorial.data.model.TutorialSchedulerInfo
import javax.inject.Inject
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map

@SysUISingleton
class TutorialSchedulerRepository
@Inject
constructor(@Application private val applicationContext: Context) {

    private val Context.dataStore: DataStore<Preferences> by
        preferencesDataStore(name = DATASTORE_NAME)

    suspend fun loadData(): TutorialSchedulerInfo {
        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 updateLaunch(device: DeviceType) {
        applicationContext.dataStore.edit { pref -> pref[getLaunchedKey(device)] = true }
    }

    private fun getSchedulerInfo(pref: Preferences): TutorialSchedulerInfo {
        return TutorialSchedulerInfo(
            keyboard = getDeviceSchedulerInfo(pref, DeviceType.KEYBOARD),
            touchpad = getDeviceSchedulerInfo(pref, DeviceType.TOUCHPAD)
        )
    }

    private fun getDeviceSchedulerInfo(pref: Preferences, device: DeviceType): DeviceSchedulerInfo {
        val isLaunched = pref[getLaunchedKey(device)] ?: false
        val connectionTime = pref[getConnectKey(device)] ?: null
        return DeviceSchedulerInfo(isLaunched, connectionTime)
    }

    private fun getLaunchedKey(device: DeviceType) =
        booleanPreferencesKey(device.name + IS_LAUNCHED_SUFFIX)

    private fun getConnectKey(device: DeviceType) =
        longPreferencesKey(device.name + CONNECT_TIME_SUFFIX)

    companion object {
        const val DATASTORE_NAME = "TutorialScheduler"
        const val IS_LAUNCHED_SUFFIX = "_IS_LAUNCHED"
        const val CONNECT_TIME_SUFFIX = "_CONNECTED_TIME"
    }
}

enum class DeviceType {
    KEYBOARD,
    TOUCHPAD
}
+37 −21
Original line number Diff line number Diff line
@@ -14,14 +14,15 @@
 * limitations under the License.
 */

package com.android.systemui.inputdevice.oobe.domain.interactor
package com.android.systemui.inputdevice.tutorial.domain.interactor

import android.content.Context
import android.content.Intent
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.inputdevice.oobe.data.model.DeviceSchedulerInfo
import com.android.systemui.inputdevice.oobe.data.model.OobeSchedulerInfo
import com.android.systemui.inputdevice.tutorial.data.model.DeviceSchedulerInfo
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
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
@@ -35,49 +36,65 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch

/**
 * When the first time a keyboard or touchpad id connected, wait for [LAUNCH_DELAY], then launch the
 * When the first time a keyboard or touchpad is connected, wait for [LAUNCH_DELAY], then launch the
 * tutorial as soon as there's a connected device
 */
@SysUISingleton
class OobeSchedulerInteractor
class TutorialSchedulerInteractor
@Inject
constructor(
    @Application private val context: Context,
    @Application private val applicationScope: CoroutineScope,
    private val keyboardRepository: KeyboardRepository,
    private val touchpadRepository: TouchpadRepository
    private val touchpadRepository: TouchpadRepository,
    private val tutorialSchedulerRepository: TutorialSchedulerRepository
) {
    private val info = OobeSchedulerInfo()

    fun start() {
        applicationScope.launch {
            val info = tutorialSchedulerRepository.loadData()
            if (!info.keyboard.isLaunched) {
                applicationScope.launch {
                schedule(keyboardRepository.isAnyKeyboardConnected, info.keyboard)
                    schedule(
                        keyboardRepository.isAnyKeyboardConnected,
                        info.keyboard,
                        DeviceType.KEYBOARD
                    )
                }
            }
            if (!info.touchpad.isLaunched) {
                applicationScope.launch {
                schedule(touchpadRepository.isAnyTouchpadConnected, info.touchpad)
                    schedule(
                        touchpadRepository.isAnyTouchpadConnected,
                        info.touchpad,
                        DeviceType.TOUCHPAD
                    )
                }
            }
        }
    }

    private suspend fun schedule(isAnyDeviceConnected: Flow<Boolean>, info: DeviceSchedulerInfo) {
    private suspend fun schedule(
        isAnyDeviceConnected: Flow<Boolean>,
        info: DeviceSchedulerInfo,
        deviceType: DeviceType
    ) {
        if (!info.wasEverConnected) {
            waitForDeviceConnection(isAnyDeviceConnected)
            info.connectionTime = Instant.now().toEpochMilli()
            info.connectTime = Instant.now().toEpochMilli()
            tutorialSchedulerRepository.updateConnectTime(deviceType, info.connectTime!!)
        }
        delay(remainingTimeMillis(info.connectionTime!!))
        delay(remainingTimeMillis(info.connectTime!!))
        waitForDeviceConnection(isAnyDeviceConnected)
        info.isLaunched = true
        launchOobe()
        tutorialSchedulerRepository.updateLaunch(deviceType)
        launchTutorial()
    }

    private suspend fun waitForDeviceConnection(isAnyDeviceConnected: Flow<Boolean>): Boolean {
        return isAnyDeviceConnected.filter { it }.first()
    }

    private fun launchOobe() {
    private fun launchTutorial() {
        val intent = Intent(TUTORIAL_ACTION)
        intent.addCategory(Intent.CATEGORY_DEFAULT)
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -90,7 +107,6 @@ constructor(
    }

    companion object {
        const val TAG = "OobeSchedulerInteractor"
        const val TUTORIAL_ACTION = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
        private val LAUNCH_DELAY = Duration.ofHours(72).toMillis()
    }