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()
+    }
+}