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
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 4286646..b0f2c18 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -30,7 +30,7 @@
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 @@
@Binds
@IntoMap
- @ClassKey(KeyboardTouchpadOobeTutorialCoreStartable::class)
- abstract fun bindOobeSchedulerCoreStartable(
- listener: KeyboardTouchpadOobeTutorialCoreStartable
+ @ClassKey(KeyboardTouchpadTutorialCoreStartable::class)
+ abstract fun bindKeyboardTouchpadTutorialCoreStartable(
+ listener: KeyboardTouchpadTutorialCoreStartable
): CoreStartable
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt
deleted file mode 100644
index b014c08..0000000
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.oobe.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.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 kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-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
- * tutorial as soon as there's a connected device
- */
-@SysUISingleton
-class OobeSchedulerInteractor
-@Inject
-constructor(
- @Application private val context: Context,
- @Application private val applicationScope: CoroutineScope,
- private val keyboardRepository: KeyboardRepository,
- private val touchpadRepository: TouchpadRepository
-) {
- private val info = OobeSchedulerInfo()
-
- fun start() {
- if (!info.keyboard.isLaunched) {
- applicationScope.launch {
- schedule(keyboardRepository.isAnyKeyboardConnected, info.keyboard)
- }
- }
- if (!info.touchpad.isLaunched) {
- applicationScope.launch {
- schedule(touchpadRepository.isAnyTouchpadConnected, info.touchpad)
- }
- }
- }
-
- private suspend fun schedule(isAnyDeviceConnected: Flow<Boolean>, info: DeviceSchedulerInfo) {
- if (!info.wasEverConnected) {
- waitForDeviceConnection(isAnyDeviceConnected)
- info.connectionTime = Instant.now().toEpochMilli()
- }
- delay(remainingTimeMillis(info.connectionTime!!))
- waitForDeviceConnection(isAnyDeviceConnected)
- info.isLaunched = true
- launchOobe()
- }
-
- private suspend fun waitForDeviceConnection(isAnyDeviceConnected: Flow<Boolean>): Boolean {
- return isAnyDeviceConnected.filter { it }.first()
- }
-
- private fun launchOobe() {
- val intent = Intent(TUTORIAL_ACTION)
- intent.addCategory(Intent.CATEGORY_DEFAULT)
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- context.startActivity(intent)
- }
-
- private fun remainingTimeMillis(start: Long): Long {
- val elapsed = Instant.now().toEpochMilli() - start
- return LAUNCH_DELAY - elapsed
- }
-
- companion object {
- const val TAG = "OobeSchedulerInteractor"
- const val TUTORIAL_ACTION = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
- private val LAUNCH_DELAY = Duration.ofHours(72).toMillis()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
index 701d3da..e8e1dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
@@ -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()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
similarity index 83%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
index e5aedc0..cfe64e2 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
@@ -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
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
new file mode 100644
index 0000000..31ff018
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
@@ -0,0 +1,83 @@
+/*
+ * 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
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
new file mode 100644
index 0000000..05e1044
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.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.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
+import java.time.Instant
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+/**
+ * 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 TutorialSchedulerInteractor
+@Inject
+constructor(
+ @Application private val context: Context,
+ @Application private val applicationScope: CoroutineScope,
+ private val keyboardRepository: KeyboardRepository,
+ private val touchpadRepository: TouchpadRepository,
+ private val tutorialSchedulerRepository: TutorialSchedulerRepository
+) {
+ fun start() {
+ applicationScope.launch {
+ val info = tutorialSchedulerRepository.loadData()
+ if (!info.keyboard.isLaunched) {
+ applicationScope.launch {
+ schedule(
+ keyboardRepository.isAnyKeyboardConnected,
+ info.keyboard,
+ DeviceType.KEYBOARD
+ )
+ }
+ }
+ if (!info.touchpad.isLaunched) {
+ applicationScope.launch {
+ schedule(
+ touchpadRepository.isAnyTouchpadConnected,
+ info.touchpad,
+ DeviceType.TOUCHPAD
+ )
+ }
+ }
+ }
+ }
+
+ private suspend fun schedule(
+ isAnyDeviceConnected: Flow<Boolean>,
+ info: DeviceSchedulerInfo,
+ deviceType: DeviceType
+ ) {
+ if (!info.wasEverConnected) {
+ waitForDeviceConnection(isAnyDeviceConnected)
+ info.connectTime = Instant.now().toEpochMilli()
+ tutorialSchedulerRepository.updateConnectTime(deviceType, info.connectTime!!)
+ }
+ delay(remainingTimeMillis(info.connectTime!!))
+ waitForDeviceConnection(isAnyDeviceConnected)
+ info.isLaunched = true
+ tutorialSchedulerRepository.updateLaunch(deviceType)
+ launchTutorial()
+ }
+
+ private suspend fun waitForDeviceConnection(isAnyDeviceConnected: Flow<Boolean>): Boolean {
+ return isAnyDeviceConnected.filter { it }.first()
+ }
+
+ private fun launchTutorial() {
+ val intent = Intent(TUTORIAL_ACTION)
+ intent.addCategory(Intent.CATEGORY_DEFAULT)
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(intent)
+ }
+
+ private fun remainingTimeMillis(start: Long): Long {
+ val elapsed = Instant.now().toEpochMilli() - start
+ return LAUNCH_DELAY - elapsed
+ }
+
+ companion object {
+ const val TUTORIAL_ACTION = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
+ private val LAUNCH_DELAY = Duration.ofHours(72).toMillis()
+ }
+}