Fix launched time. Add notified status.
Move launched time updating logic to where tutorial is launched. We need
it for for context edu. Add isNotified for oobe scheduling.
Drive-by renaming.
Bug: 378865553
Flag: NONE bug fix
Test: TutorialSchedulerInteractorTest
Change-Id: Iae15120a6e9c26c1ea849d5c9ce43eef8e8ed6f5
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt
index 8bb6962..4630674 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt
@@ -22,13 +22,12 @@
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD
import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundScope
import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import java.time.Instant
-import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
-import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,7 +37,7 @@
class TutorialSchedulerRepositoryTest : SysuiTestCase() {
private lateinit var underTest: TutorialSchedulerRepository
- private val kosmos = Kosmos()
+ private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@Before
@@ -46,45 +45,57 @@
underTest =
TutorialSchedulerRepository(
context,
- testScope.backgroundScope,
+ kosmos.backgroundScope,
"TutorialSchedulerRepositoryTest",
)
}
- @After
- fun clear() {
- testScope.launch { underTest.clear() }
+ @Test
+ fun initialState() = runTestAndClear {
+ assertThat(underTest.wasEverConnected(KEYBOARD)).isFalse()
+ assertThat(underTest.wasEverConnected(TOUCHPAD)).isFalse()
+ assertThat(underTest.isNotified(KEYBOARD)).isFalse()
+ assertThat(underTest.isNotified(TOUCHPAD)).isFalse()
+ assertThat(underTest.isScheduledTutorialLaunched(KEYBOARD)).isFalse()
+ assertThat(underTest.isScheduledTutorialLaunched(TOUCHPAD)).isFalse()
}
@Test
- fun initialState() =
- testScope.runTest {
- assertThat(underTest.wasEverConnected(KEYBOARD)).isFalse()
- assertThat(underTest.wasEverConnected(TOUCHPAD)).isFalse()
- assertThat(underTest.isLaunched(KEYBOARD)).isFalse()
- assertThat(underTest.isLaunched(TOUCHPAD)).isFalse()
- }
+ fun connectKeyboard() = runTestAndClear {
+ val now = Instant.now()
+ underTest.setFirstConnectionTime(KEYBOARD, now)
+
+ assertThat(underTest.wasEverConnected(KEYBOARD)).isTrue()
+ assertThat(underTest.getFirstConnectionTime(KEYBOARD)!!.epochSecond)
+ .isEqualTo(now.epochSecond)
+ assertThat(underTest.wasEverConnected(TOUCHPAD)).isFalse()
+ }
@Test
- fun connectKeyboard() =
- testScope.runTest {
- val now = Instant.now()
- underTest.updateFirstConnectionTime(KEYBOARD, now)
+ fun launchKeyboard() = runTestAndClear {
+ val now = Instant.now()
+ underTest.setScheduledTutorialLaunchTime(KEYBOARD, now)
- assertThat(underTest.wasEverConnected(KEYBOARD)).isTrue()
- assertThat(underTest.firstConnectionTime(KEYBOARD)!!.epochSecond)
- .isEqualTo(now.epochSecond)
- assertThat(underTest.wasEverConnected(TOUCHPAD)).isFalse()
- }
+ assertThat(underTest.isScheduledTutorialLaunched(KEYBOARD)).isTrue()
+ assertThat(underTest.getScheduledTutorialLaunchTime(KEYBOARD)!!.epochSecond)
+ .isEqualTo(now.epochSecond)
+ assertThat(underTest.isScheduledTutorialLaunched(TOUCHPAD)).isFalse()
+ }
@Test
- fun launchKeyboard() =
- testScope.runTest {
- val now = Instant.now()
- underTest.updateLaunchTime(KEYBOARD, now)
+ fun notifyKeyboard() = runTestAndClear {
+ underTest.setNotified(KEYBOARD)
- assertThat(underTest.isLaunched(KEYBOARD)).isTrue()
- assertThat(underTest.launchTime(KEYBOARD)!!.epochSecond).isEqualTo(now.epochSecond)
- assertThat(underTest.isLaunched(TOUCHPAD)).isFalse()
+ assertThat(underTest.isNotified(KEYBOARD)).isTrue()
+ assertThat(underTest.isNotified(TOUCHPAD)).isFalse()
+ }
+
+ private fun runTestAndClear(block: suspend () -> Unit) =
+ testScope.runTest {
+ try {
+ block()
+ } finally {
+ underTest.clear()
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
index bcac086..886aa51 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator
import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.kosmos.backgroundScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.settings.userTracker
@@ -34,14 +35,9 @@
import com.android.systemui.touchpad.data.repository.FakeTouchpadRepository
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.hours
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
-import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -65,7 +61,6 @@
private val testScope = kosmos.testScope
private val keyboardRepository = FakeKeyboardRepository()
private val touchpadRepository = FakeTouchpadRepository()
- private lateinit var dataStoreScope: CoroutineScope
private lateinit var repository: TutorialSchedulerRepository
@Mock private lateinit var notificationManager: NotificationManager
@Captor private lateinit var notificationCaptor: ArgumentCaptor<Notification>
@@ -73,11 +68,10 @@
@Before
fun setup() {
- dataStoreScope = CoroutineScope(Dispatchers.Unconfined)
repository =
TutorialSchedulerRepository(
context,
- dataStoreScope,
+ kosmos.backgroundScope,
dataStoreName = "TutorialNotificationCoordinatorTest",
)
val interactor =
@@ -87,6 +81,7 @@
repository,
kosmos.inputDeviceTutorialLogger,
kosmos.commandRegistry,
+ testScope.backgroundScope,
)
underTest =
TutorialNotificationCoordinator(
@@ -100,52 +95,51 @@
underTest.start()
}
- @After
- fun clear() {
- runBlocking { repository.clear() }
- dataStoreScope.cancel()
+ @Test
+ fun showKeyboardNotification() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+ verifyNotification(
+ R.string.launch_keyboard_tutorial_notification_title,
+ R.string.launch_keyboard_tutorial_notification_content,
+ )
}
@Test
- fun showKeyboardNotification() =
- testScope.runTest {
- keyboardRepository.setIsAnyKeyboardConnected(true)
- advanceTimeBy(LAUNCH_DELAY)
- verifyNotification(
- R.string.launch_keyboard_tutorial_notification_title,
- R.string.launch_keyboard_tutorial_notification_content,
- )
- }
+ fun showTouchpadNotification() = runTestAndClear {
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+ verifyNotification(
+ R.string.launch_touchpad_tutorial_notification_title,
+ R.string.launch_touchpad_tutorial_notification_content,
+ )
+ }
@Test
- fun showTouchpadNotification() =
- testScope.runTest {
- touchpadRepository.setIsAnyTouchpadConnected(true)
- advanceTimeBy(LAUNCH_DELAY)
- verifyNotification(
- R.string.launch_touchpad_tutorial_notification_title,
- R.string.launch_touchpad_tutorial_notification_content,
- )
- }
+ fun showKeyboardTouchpadNotification() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+ verifyNotification(
+ R.string.launch_keyboard_touchpad_tutorial_notification_title,
+ R.string.launch_keyboard_touchpad_tutorial_notification_content,
+ )
+ }
@Test
- fun showKeyboardTouchpadNotification() =
- testScope.runTest {
- keyboardRepository.setIsAnyKeyboardConnected(true)
- touchpadRepository.setIsAnyTouchpadConnected(true)
- advanceTimeBy(LAUNCH_DELAY)
- verifyNotification(
- R.string.launch_keyboard_touchpad_tutorial_notification_title,
- R.string.launch_keyboard_touchpad_tutorial_notification_content,
- )
- }
+ fun doNotShowNotification() = runTestAndClear {
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+ verify(notificationManager, never())
+ .notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), any(), any())
+ }
- @Test
- fun doNotShowNotification() =
+ private fun runTestAndClear(block: suspend () -> Unit) =
testScope.runTest {
- advanceTimeBy(LAUNCH_DELAY)
- verify(notificationManager, never())
- .notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), any(), any())
+ try {
+ block()
+ } finally {
+ repository.clear()
+ }
}
private fun verifyNotification(@StringRes titleResId: Int, @StringRes contentResId: Int) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
index 2efa2f3..722451e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
@@ -30,16 +30,11 @@
import com.android.systemui.touchpad.data.repository.FakeTouchpadRepository
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.hours
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
-import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -52,18 +47,16 @@
private lateinit var underTest: TutorialSchedulerInteractor
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private lateinit var dataStoreScope: CoroutineScope
private val keyboardRepository = FakeKeyboardRepository()
private val touchpadRepository = FakeTouchpadRepository()
private lateinit var schedulerRepository: TutorialSchedulerRepository
@Before
fun setup() {
- dataStoreScope = CoroutineScope(Dispatchers.Unconfined)
schedulerRepository =
TutorialSchedulerRepository(
context,
- dataStoreScope,
+ testScope.backgroundScope,
dataStoreName = "TutorialSchedulerInteractorTest",
)
underTest =
@@ -73,98 +66,95 @@
schedulerRepository,
kosmos.inputDeviceTutorialLogger,
kosmos.commandRegistry,
+ testScope.backgroundScope,
)
}
- @After
- fun clear() {
- runBlocking { schedulerRepository.clear() }
- dataStoreScope.cancel()
+ @Test
+ fun connectKeyboard_delayElapse_notifyForKeyboard() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+ notifyAndAssert(TutorialType.KEYBOARD)
}
@Test
- fun connectKeyboard_delayElapse_launchForKeyboard() =
- testScope.runTest {
- keyboardRepository.setIsAnyKeyboardConnected(true)
- advanceTimeBy(LAUNCH_DELAY)
+ fun connectBothDevices_delayElapse_notifyForBoth() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
- launchAndAssert(TutorialType.KEYBOARD)
- }
+ notifyAndAssert(TutorialType.BOTH)
+ }
@Test
- fun connectBothDevices_delayElapse_launchForBoth() =
- testScope.runTest {
- keyboardRepository.setIsAnyKeyboardConnected(true)
- touchpadRepository.setIsAnyTouchpadConnected(true)
- advanceTimeBy(LAUNCH_DELAY)
+ fun connectBothDevice_delayNotElapse_notifyNothing() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ testScope.advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
- launchAndAssert(TutorialType.BOTH)
- }
+ notifyAndAssert(TutorialType.NONE)
+ }
@Test
- fun connectBothDevice_delayNotElapse_launchNothing() =
- testScope.runTest {
- keyboardRepository.setIsAnyKeyboardConnected(true)
- touchpadRepository.setIsAnyTouchpadConnected(true)
- advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
+ fun nothingConnect_delayElapse_notifyNothing() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(false)
+ touchpadRepository.setIsAnyTouchpadConnected(false)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
- launchAndAssert(TutorialType.NONE)
- }
+ notifyAndAssert(TutorialType.NONE)
+ }
@Test
- fun nothingConnect_delayElapse_launchNothing() =
- testScope.runTest {
- keyboardRepository.setIsAnyKeyboardConnected(false)
- touchpadRepository.setIsAnyTouchpadConnected(false)
- advanceTimeBy(LAUNCH_DELAY)
+ fun connectKeyboard_thenTouchpad_delayElapse_notifyForBoth() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ testScope.advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ testScope.advanceTimeBy(REMAINING_TIME)
- launchAndAssert(TutorialType.NONE)
- }
+ notifyAndAssert(TutorialType.BOTH)
+ }
@Test
- fun connectKeyboard_thenTouchpad_delayElapse_launchForBoth() =
- testScope.runTest {
- keyboardRepository.setIsAnyKeyboardConnected(true)
- advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
- touchpadRepository.setIsAnyTouchpadConnected(true)
- advanceTimeBy(REMAINING_TIME)
+ fun connectKeyboard_thenTouchpad_removeKeyboard_delayElapse_notifyNothing() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ testScope.advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ keyboardRepository.setIsAnyKeyboardConnected(false)
+ testScope.advanceTimeBy(REMAINING_TIME)
- launchAndAssert(TutorialType.BOTH)
+ notifyAndAssert(TutorialType.NONE)
+ }
+
+ private fun runTestAndClear(block: suspend () -> Unit) =
+ testScope.runTest {
+ try {
+ block()
+ } finally {
+ schedulerRepository.clear()
+ }
}
- @Test
- fun connectKeyboard_thenTouchpad_removeKeyboard_delayElapse_launchNothing() =
- testScope.runTest {
- keyboardRepository.setIsAnyKeyboardConnected(true)
- advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
- touchpadRepository.setIsAnyTouchpadConnected(true)
- keyboardRepository.setIsAnyKeyboardConnected(false)
- advanceTimeBy(REMAINING_TIME)
- launchAndAssert(TutorialType.NONE)
- }
-
- private suspend fun launchAndAssert(expectedTutorial: TutorialType) =
+ private fun notifyAndAssert(expectedTutorial: TutorialType) =
testScope.backgroundScope.launch {
val actualTutorial = underTest.tutorials.first()
assertThat(actualTutorial).isEqualTo(expectedTutorial)
- // TODO: need to update after we move launch into the tutorial
when (expectedTutorial) {
TutorialType.KEYBOARD -> {
- assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isTrue()
- assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isFalse()
+ assertThat(schedulerRepository.isNotified(DeviceType.KEYBOARD)).isTrue()
+ assertThat(schedulerRepository.isNotified(DeviceType.TOUCHPAD)).isFalse()
}
TutorialType.TOUCHPAD -> {
- assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isFalse()
- assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isTrue()
+ assertThat(schedulerRepository.isNotified(DeviceType.KEYBOARD)).isFalse()
+ assertThat(schedulerRepository.isNotified(DeviceType.TOUCHPAD)).isTrue()
}
TutorialType.BOTH -> {
- assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isTrue()
- assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isTrue()
+ assertThat(schedulerRepository.isNotified(DeviceType.KEYBOARD)).isTrue()
+ assertThat(schedulerRepository.isNotified(DeviceType.TOUCHPAD)).isTrue()
}
TutorialType.NONE -> {
- assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isFalse()
- assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isFalse()
+ assertThat(schedulerRepository.isNotified(DeviceType.KEYBOARD)).isFalse()
+ assertThat(schedulerRepository.isNotified(DeviceType.TOUCHPAD)).isFalse()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
index e264635..21002c6 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.education.domain.interactor
import android.os.SystemProperties
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.contextualeducation.GestureType
@@ -56,7 +57,6 @@
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.merge
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Allow listening to new contextual education triggered */
@SysUISingleton
@@ -278,7 +278,8 @@
}
private suspend fun hasInitialDelayElapsed(deviceType: DeviceType): Boolean {
- val oobeLaunchTime = tutorialRepository.launchTime(deviceType) ?: return false
+ val oobeLaunchTime =
+ tutorialRepository.getScheduledTutorialLaunchTime(deviceType) ?: return false
return clock
.instant()
.isAfter(oobeLaunchTime.plusSeconds(initialDelayDuration.inWholeSeconds))
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/DeviceSchedulerInfo.kt
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/DeviceSchedulerInfo.kt
index 1dbe83a..c436ef0 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/DeviceSchedulerInfo.kt
@@ -20,14 +20,17 @@
data class DeviceSchedulerInfo(
var launchTime: Instant? = null,
- var firstConnectionTime: Instant? = null
+ var firstConnectionTime: Instant? = null,
+ var isNotified: Boolean = false,
) {
constructor(
launchTimeSec: Long?,
- firstConnectionTimeSec: Long?
+ firstConnectionTimeSec: Long?,
+ isNotified: Boolean = false,
) : this(
launchTimeSec?.let { Instant.ofEpochSecond(it) },
- firstConnectionTimeSec?.let { Instant.ofEpochSecond(it) }
+ firstConnectionTimeSec?.let { Instant.ofEpochSecond(it) },
+ isNotified,
)
val wasEverConnected: Boolean
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
index a89ec70..526e7db 100644
--- 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
@@ -19,6 +19,7 @@
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
@@ -47,28 +48,36 @@
private val Context.dataStore: DataStore<Preferences> by
preferencesDataStore(name = dataStoreName, scope = backgroundScope)
- suspend fun isLaunched(deviceType: DeviceType): Boolean = loadData()[deviceType]!!.isLaunched
+ suspend fun setScheduledTutorialLaunchTime(device: DeviceType, time: Instant) {
+ applicationContext.dataStore.edit { pref -> pref[getLaunchKey(device)] = time.epochSecond }
+ }
- suspend fun launchTime(deviceType: DeviceType): Instant? = loadData()[deviceType]!!.launchTime
+ suspend fun isScheduledTutorialLaunched(deviceType: DeviceType): Boolean =
+ loadData()[deviceType]!!.isLaunched
+
+ suspend fun getScheduledTutorialLaunchTime(deviceType: DeviceType): Instant? =
+ loadData()[deviceType]!!.launchTime
+
+ suspend fun setFirstConnectionTime(device: DeviceType, time: Instant) {
+ applicationContext.dataStore.edit { pref -> pref[getConnectKey(device)] = time.epochSecond }
+ }
+
+ suspend fun setNotified(device: DeviceType) {
+ applicationContext.dataStore.edit { pref -> pref[getNotificationKey(device)] = true }
+ }
+
+ suspend fun isNotified(deviceType: DeviceType): Boolean = loadData()[deviceType]!!.isNotified
suspend fun wasEverConnected(deviceType: DeviceType): Boolean =
loadData()[deviceType]!!.wasEverConnected
- suspend fun firstConnectionTime(deviceType: DeviceType): Instant? =
+ suspend fun getFirstConnectionTime(deviceType: DeviceType): Instant? =
loadData()[deviceType]!!.firstConnectionTime
private suspend fun loadData(): Map<DeviceType, DeviceSchedulerInfo> {
return applicationContext.dataStore.data.map { pref -> getSchedulerInfo(pref) }.first()
}
- suspend fun updateFirstConnectionTime(device: DeviceType, time: Instant) {
- applicationContext.dataStore.edit { pref -> pref[getConnectKey(device)] = time.epochSecond }
- }
-
- suspend fun updateLaunchTime(device: DeviceType, time: Instant) {
- applicationContext.dataStore.edit { pref -> pref[getLaunchKey(device)] = time.epochSecond }
- }
-
private fun getSchedulerInfo(pref: Preferences): Map<DeviceType, DeviceSchedulerInfo> {
return mapOf(
DeviceType.KEYBOARD to getDeviceSchedulerInfo(pref, DeviceType.KEYBOARD),
@@ -79,7 +88,8 @@
private fun getDeviceSchedulerInfo(pref: Preferences, device: DeviceType): DeviceSchedulerInfo {
val launchTime = pref[getLaunchKey(device)]
val connectionTime = pref[getConnectKey(device)]
- return DeviceSchedulerInfo(launchTime, connectionTime)
+ val isNotified = pref[getNotificationKey(device)] == true
+ return DeviceSchedulerInfo(launchTime, connectionTime, isNotified)
}
private fun getLaunchKey(device: DeviceType) =
@@ -88,6 +98,9 @@
private fun getConnectKey(device: DeviceType) =
longPreferencesKey(device.name + CONNECT_TIME_SUFFIX)
+ private fun getNotificationKey(device: DeviceType) =
+ booleanPreferencesKey(device.name + NOTIFIED_SUFFIX)
+
suspend fun clear() {
applicationContext.dataStore.edit { it.clear() }
}
@@ -96,6 +109,7 @@
const val DATASTORE_NAME = "TutorialScheduler"
const val LAUNCH_TIME_SUFFIX = "_LAUNCH_TIME"
const val CONNECT_TIME_SUFFIX = "_CONNECT_TIME"
+ const val NOTIFIED_SUFFIX = "_NOTIFIED"
}
}
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
index 7758dcc..419eefe 100644
--- 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
@@ -17,8 +17,8 @@
package com.android.systemui.inputdevice.tutorial.domain.interactor
import android.os.SystemProperties
-import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD
@@ -35,6 +35,7 @@
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.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -43,6 +44,7 @@
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
/**
@@ -58,6 +60,7 @@
private val repo: TutorialSchedulerRepository,
private val logger: InputDeviceTutorialLogger,
commandRegistry: CommandRegistry,
+ @Background private val backgroundScope: CoroutineScope,
) {
init {
commandRegistry.registerCommand(COMMAND) { TutorialCommand() }
@@ -70,14 +73,14 @@
)
private val touchpadScheduleFlow = flow {
- if (!repo.isLaunched(TOUCHPAD)) {
+ if (!repo.isNotified(TOUCHPAD)) {
schedule(TOUCHPAD)
emit(TOUCHPAD)
}
}
private val keyboardScheduleFlow = flow {
- if (!repo.isLaunched(KEYBOARD)) {
+ if (!repo.isNotified(KEYBOARD)) {
schedule(KEYBOARD)
emit(KEYBOARD)
}
@@ -88,9 +91,9 @@
logger.d("Waiting for $deviceType to connect")
waitForDeviceConnection(deviceType)
logger.logDeviceFirstConnection(deviceType)
- repo.updateFirstConnectionTime(deviceType, Instant.now())
+ repo.setFirstConnectionTime(deviceType, Instant.now())
}
- val remainingTime = remainingTime(start = repo.firstConnectionTime(deviceType)!!)
+ val remainingTime = remainingTime(start = repo.getFirstConnectionTime(deviceType)!!)
logger.d("Tutorial is scheduled in ${remainingTime.inWholeSeconds} seconds")
delay(remainingTime)
waitForDeviceConnection(deviceType)
@@ -100,18 +103,17 @@
isAnyDeviceConnected[deviceType]!!.filter { it }.first()
// Only for testing notifications. This should behave independently from scheduling
- @VisibleForTesting val commandTutorials = MutableStateFlow(TutorialType.NONE)
+ val commandTutorials = MutableStateFlow(TutorialType.NONE)
// Merging two flows ensures that tutorial is launched consecutively to avoid race condition
val tutorials: Flow<TutorialType> =
merge(touchpadScheduleFlow, keyboardScheduleFlow).map {
val tutorialType = resolveTutorialType(it)
- // TODO: notifying time is not oobe launching time - move these updates into oobe
if (tutorialType == TutorialType.KEYBOARD || tutorialType == TutorialType.BOTH)
- repo.updateLaunchTime(KEYBOARD, Instant.now())
+ repo.setNotified(KEYBOARD)
if (tutorialType == TutorialType.TOUCHPAD || tutorialType == TutorialType.BOTH)
- repo.updateLaunchTime(TOUCHPAD, Instant.now())
+ repo.setNotified(TOUCHPAD)
logger.logTutorialLaunched(tutorialType)
tutorialType
@@ -121,10 +123,10 @@
// Resolve the type of tutorial depending on which device are connected when the tutorial is
// launched. E.g. when the keyboard is connected for [LAUNCH_DELAY], both keyboard and
// touchpad are connected, we launch the tutorial for both.
- if (repo.isLaunched(deviceType)) return TutorialType.NONE
+ if (repo.isNotified(deviceType)) return TutorialType.NONE
val otherDevice = if (deviceType == KEYBOARD) TOUCHPAD else KEYBOARD
val isOtherDeviceConnected = isAnyDeviceConnected[otherDevice]!!.first()
- if (!repo.isLaunched(otherDevice) && isOtherDeviceConnected) return TutorialType.BOTH
+ if (!repo.isNotified(otherDevice) && isOtherDeviceConnected) return TutorialType.BOTH
return if (deviceType == KEYBOARD) TutorialType.KEYBOARD else TutorialType.TOUCHPAD
}
@@ -133,6 +135,15 @@
return LAUNCH_DELAY.minus(elapsed).toKotlinDuration()
}
+ fun updateLaunchInfo(tutorialType: TutorialType) {
+ backgroundScope.launch {
+ if (tutorialType == TutorialType.KEYBOARD || tutorialType == TutorialType.BOTH)
+ repo.setScheduledTutorialLaunchTime(KEYBOARD, Instant.now())
+ if (tutorialType == TutorialType.TOUCHPAD || tutorialType == TutorialType.BOTH)
+ repo.setScheduledTutorialLaunchTime(TOUCHPAD, Instant.now())
+ }
+ }
+
inner class TutorialCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
if (args.isEmpty()) {
@@ -147,10 +158,20 @@
}
"info" ->
runBlocking {
- pw.println("Keyboard connect time = ${repo.firstConnectionTime(KEYBOARD)}")
- pw.println(" launch time = ${repo.launchTime(KEYBOARD)}")
- pw.println("Touchpad connect time = ${repo.firstConnectionTime(TOUCHPAD)}")
- pw.println(" launch time = ${repo.launchTime(TOUCHPAD)}")
+ pw.println(
+ "Keyboard connect time = ${repo.getFirstConnectionTime(KEYBOARD)}"
+ )
+ pw.println(" notified = ${repo.isNotified(KEYBOARD)}")
+ pw.println(
+ " launch time = ${repo.getScheduledTutorialLaunchTime(KEYBOARD)}"
+ )
+ pw.println(
+ "Touchpad connect time = ${repo.getFirstConnectionTime(TOUCHPAD)}"
+ )
+ pw.println(" notified = ${repo.isNotified(TOUCHPAD)}")
+ pw.println(
+ " launch time = ${repo.getScheduledTutorialLaunchTime(TOUCHPAD)}"
+ )
}
"notify" -> {
if (args.size != 2) help(pw)
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
index 67b307f..639e9b1 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
@@ -33,6 +33,8 @@
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext
import com.android.systemui.inputdevice.tutorial.KeyboardTouchpadTutorialMetricsLogger
import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider
+import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor
+import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor.TutorialType
import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen
import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel
import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel.Factory.ViewModelFactoryAssistedProvider
@@ -51,6 +53,7 @@
constructor(
private val viewModelFactoryAssistedProvider: ViewModelFactoryAssistedProvider,
private val touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>,
+ private val schedulerInteractor: TutorialSchedulerInteractor,
private val logger: InputDeviceTutorialLogger,
private val metricsLogger: KeyboardTouchpadTutorialMetricsLogger,
) : ComponentActivity() {
@@ -93,15 +96,28 @@
setContent {
PlatformTheme { KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) }
}
- // TODO(b/376692701): Update launchTime when the activity is launched by Companion App
if (savedInstanceState == null) {
- metricsLogger.logPeripheralTutorialLaunched(
- intent.getStringExtra(INTENT_TUTORIAL_ENTRY_POINT_KEY),
- intent.getStringExtra(INTENT_TUTORIAL_SCOPE_KEY),
- )
logger.logOpenTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL)
+
+ val entryPointExtra = intent.getStringExtra(INTENT_TUTORIAL_ENTRY_POINT_KEY)
+ val tutorialTypeExtra = intent.getStringExtra(INTENT_TUTORIAL_SCOPE_KEY)
+ metricsLogger.logPeripheralTutorialLaunched(entryPointExtra, tutorialTypeExtra)
+ // We only update launched info when the tutorial is triggered by the scheduler
+ if (entryPointExtra.equals(INTENT_TUTORIAL_ENTRY_POINT_SCHEDULER))
+ updateLaunchInfo(tutorialTypeExtra)
}
}
+
+ private fun updateLaunchInfo(tutorialTypeExtra: String?) {
+ val type =
+ when (tutorialTypeExtra) {
+ INTENT_TUTORIAL_SCOPE_KEYBOARD -> TutorialType.KEYBOARD
+ INTENT_TUTORIAL_SCOPE_TOUCHPAD -> TutorialType.TOUCHPAD
+ INTENT_TUTORIAL_SCOPE_ALL -> TutorialType.BOTH
+ else -> TutorialType.NONE
+ }
+ schedulerInteractor.updateLaunchInfo(type)
+ }
}
@Composable
diff --git a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
index d7fcb6a..74e8257 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
@@ -357,7 +357,10 @@
fun dataUpdatedOnIncrementSignalCountAfterInitialDelay() =
testScope.runTest {
setUpForDeviceConnection()
- tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant())
+ tutorialSchedulerRepository.setScheduledTutorialLaunchTime(
+ DeviceType.TOUCHPAD,
+ eduClock.instant(),
+ )
val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
val originalValue = model!!.signalCount
@@ -372,7 +375,10 @@
fun dataUnchangedOnIncrementSignalCountBeforeInitialDelay() =
testScope.runTest {
setUpForDeviceConnection()
- tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant())
+ tutorialSchedulerRepository.setScheduledTutorialLaunchTime(
+ DeviceType.TOUCHPAD,
+ eduClock.instant(),
+ )
val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
val originalValue = model!!.signalCount
@@ -398,8 +404,14 @@
}
private suspend fun setUpForInitialDelayElapse() {
- tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant())
- tutorialSchedulerRepository.updateLaunchTime(DeviceType.KEYBOARD, eduClock.instant())
+ tutorialSchedulerRepository.setScheduledTutorialLaunchTime(
+ DeviceType.TOUCHPAD,
+ eduClock.instant(),
+ )
+ tutorialSchedulerRepository.setScheduledTutorialLaunchTime(
+ DeviceType.KEYBOARD,
+ eduClock.instant(),
+ )
eduClock.offset(initialDelayElapsedDuration)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index 580f631..692b9c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -151,8 +151,14 @@
}
private suspend fun setUpForInitialDelayElapse() {
- tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant())
- tutorialSchedulerRepository.updateLaunchTime(DeviceType.KEYBOARD, eduClock.instant())
+ tutorialSchedulerRepository.setScheduledTutorialLaunchTime(
+ DeviceType.TOUCHPAD,
+ eduClock.instant(),
+ )
+ tutorialSchedulerRepository.setScheduledTutorialLaunchTime(
+ DeviceType.KEYBOARD,
+ eduClock.instant(),
+ )
eduClock.offset(initialDelayElapsedDuration)
}