summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt185
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt66
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt9
4 files changed, 253 insertions, 11 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
index cd0c58feebed..8b5f59457e6e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
@@ -19,16 +19,23 @@ package com.android.systemui.education.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.education.data.repository.contextualEducationRepository
import com.android.systemui.education.data.repository.fakeEduClock
+import com.android.systemui.inputdevice.data.model.UserDeviceConnectionStatus
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -36,24 +43,190 @@ class KeyboardTouchpadStatsInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val underTest = kosmos.keyboardTouchpadEduStatsInteractor
+ private val repository = kosmos.contextualEducationRepository
+ private val fakeClock = kosmos.fakeEduClock
+ private val initialDelayElapsedDuration =
+ KeyboardTouchpadEduStatsInteractorImpl.initialDelayDuration + 1.seconds
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCountWhenTouchpadConnected() =
+ testScope.runTest {
+ setUpForInitialDelayElapse()
+ whenever(mockUserInputDeviceRepository.isAnyTouchpadConnectedForUser)
+ .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(BACK)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ }
+
+ @Test
+ fun dataUnchangedOnIncrementSignalCountWhenTouchpadDisconnected() =
+ testScope.runTest {
+ setUpForInitialDelayElapse()
+ whenever(mockUserInputDeviceRepository.isAnyTouchpadConnectedForUser)
+ .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = false, userId = 0)))
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(BACK)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCountWhenKeyboardConnected() =
+ testScope.runTest {
+ setUpForInitialDelayElapse()
+ whenever(mockUserInputDeviceRepository.isAnyKeyboardConnectedForUser)
+ .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(ALL_APPS))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(ALL_APPS)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ }
+
+ @Test
+ fun dataUnchangedOnIncrementSignalCountWhenKeyboardDisconnected() =
+ testScope.runTest {
+ setUpForInitialDelayElapse()
+ whenever(mockUserInputDeviceRepository.isAnyKeyboardConnectedForUser)
+ .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = false, userId = 0)))
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(ALL_APPS))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(ALL_APPS)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCountAfterOobeLaunchInitialDelay() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ whenever(mockTutorialSchedulerRepository.launchTime(any<DeviceType>()))
+ .thenReturn(fakeClock.instant())
+ fakeClock.offset(initialDelayElapsedDuration)
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(BACK)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ }
@Test
- fun dataUpdatedOnIncrementSignalCount() =
+ fun dataUnchangedOnIncrementSignalCountBeforeOobeLaunchInitialDelay() =
testScope.runTest {
- val model by
- collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
+ setUpForDeviceConnection()
+ whenever(mockTutorialSchedulerRepository.launchTime(any<DeviceType>()))
+ .thenReturn(fakeClock.instant())
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
val originalValue = model!!.signalCount
underTest.incrementSignalCount(BACK)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCountAfterTouchpadConnectionInitialDelay() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ repository.updateEduDeviceConnectionTime { model ->
+ model.copy(touchpadFirstConnectionTime = fakeClock.instant())
+ }
+ fakeClock.offset(initialDelayElapsedDuration)
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(BACK)
+
assertThat(model?.signalCount).isEqualTo(originalValue + 1)
}
@Test
+ fun dataUnchangedOnIncrementSignalCountBeforeTouchpadConnectionInitialDelay() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ repository.updateEduDeviceConnectionTime { model ->
+ model.copy(touchpadFirstConnectionTime = fakeClock.instant())
+ }
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(BACK)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
+ fun dataUpdatedOnIncrementSignalCountAfterKeyboardConnectionInitialDelay() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ repository.updateEduDeviceConnectionTime { model ->
+ model.copy(keyboardFirstConnectionTime = fakeClock.instant())
+ }
+ fakeClock.offset(initialDelayElapsedDuration)
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(ALL_APPS))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(ALL_APPS)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ }
+
+ @Test
+ fun dataUnchangedOnIncrementSignalCountBeforeKeyboardConnectionInitialDelay() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ repository.updateEduDeviceConnectionTime { model ->
+ model.copy(keyboardFirstConnectionTime = fakeClock.instant())
+ }
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(ALL_APPS))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(ALL_APPS)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
+ fun dataUnchangedOnIncrementSignalCountWhenNoSetupTime() =
+ testScope.runTest {
+ whenever(mockUserInputDeviceRepository.isAnyTouchpadConnectedForUser)
+ .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))
+
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
+ val originalValue = model!!.signalCount
+ underTest.incrementSignalCount(BACK)
+
+ assertThat(model?.signalCount).isEqualTo(originalValue)
+ }
+
+ @Test
fun dataAddedOnUpdateShortcutTriggerTime() =
testScope.runTest {
- val model by
- collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
+ val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
assertThat(model?.lastShortcutTriggeredTime).isNull()
underTest.updateShortcutTriggerTime(BACK)
assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant())
}
+
+ private suspend fun setUpForInitialDelayElapse() {
+ whenever(mockTutorialSchedulerRepository.launchTime(any<DeviceType>()))
+ .thenReturn(fakeClock.instant())
+ fakeClock.offset(initialDelayElapsedDuration)
+ }
+
+ private fun setUpForDeviceConnection() {
+ whenever(mockUserInputDeviceRepository.isAnyTouchpadConnectedForUser)
+ .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))
+ whenever(mockUserInputDeviceRepository.isAnyKeyboardConnectedForUser)
+ .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))
+ }
}
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 87eeebf333e9..9520f474f936 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
@@ -87,6 +87,7 @@ constructor(
}
override fun start() {
+ // Listen to back gesture model changes and trigger education if needed
backgroundScope.launch {
contextualEducationInteractor.backGestureModelFlow.collect {
if (isUsageSessionExpired(it)) {
@@ -98,6 +99,7 @@ constructor(
}
}
+ // Listen to touchpad connection changes and update the first connection time
backgroundScope.launch {
userInputDeviceRepository.isAnyTouchpadConnectedForUser.collect {
if (
@@ -111,6 +113,7 @@ constructor(
}
}
+ // Listen to keyboard connection changes and update the first connection time
backgroundScope.launch {
userInputDeviceRepository.isAnyKeyboardConnectedForUser.collect {
if (
@@ -124,6 +127,7 @@ constructor(
}
}
+ // Listen to keyboard shortcut triggered and update the last trigger time
backgroundScope.launch {
keyboardShortcutTriggered.collect {
contextualEducationInteractor.updateShortcutTriggerTime(it)
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
index 3223433568b9..7821f6940da4 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
@@ -16,11 +16,25 @@
package com.android.systemui.education.domain.interactor
+import android.os.SystemProperties
+import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
+import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
+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 java.time.Clock
import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.hours
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
/**
@@ -39,12 +53,29 @@ class KeyboardTouchpadEduStatsInteractorImpl
@Inject
constructor(
@Background private val backgroundScope: CoroutineScope,
- private val contextualEducationInteractor: ContextualEducationInteractor
+ private val contextualEducationInteractor: ContextualEducationInteractor,
+ private val inputDeviceRepository: UserInputDeviceRepository,
+ private val tutorialRepository: TutorialSchedulerRepository,
+ @EduClock private val clock: Clock,
) : KeyboardTouchpadEduStatsInteractor {
+ companion object {
+ val initialDelayDuration: Duration
+ get() =
+ SystemProperties.getLong(
+ "persist.contextual_edu.initial_delay_sec",
+ /* defaultValue= */ 72.hours.inWholeSeconds
+ )
+ .toDuration(DurationUnit.SECONDS)
+ }
+
override fun incrementSignalCount(gestureType: GestureType) {
- // Todo: check if keyboard/touchpad is connected before update
- backgroundScope.launch { contextualEducationInteractor.incrementSignalCount(gestureType) }
+ backgroundScope.launch {
+ val targetDevice = getTargetDevice(gestureType)
+ if (isTargetDeviceConnected(targetDevice) && hasInitialDelayElapsed(targetDevice)) {
+ contextualEducationInteractor.incrementSignalCount(gestureType)
+ }
+ }
}
override fun updateShortcutTriggerTime(gestureType: GestureType) {
@@ -52,4 +83,31 @@ constructor(
contextualEducationInteractor.updateShortcutTriggerTime(gestureType)
}
}
+
+ private suspend fun isTargetDeviceConnected(deviceType: DeviceType): Boolean {
+ if (deviceType == KEYBOARD) {
+ return inputDeviceRepository.isAnyKeyboardConnectedForUser.first().isConnected
+ } else if (deviceType == TOUCHPAD) {
+ return inputDeviceRepository.isAnyTouchpadConnectedForUser.first().isConnected
+ }
+ return false
+ }
+
+ /**
+ * Keyboard shortcut education would be provided for All Apps. Touchpad gesture education would
+ * be provided for the rest of the gesture types (i.e. Home, Overview, Back). This method maps
+ * gesture to its target education device.
+ */
+ private fun getTargetDevice(gestureType: GestureType) =
+ when (gestureType) {
+ ALL_APPS -> KEYBOARD
+ else -> TOUCHPAD
+ }
+
+ private suspend fun hasInitialDelayElapsed(deviceType: DeviceType): Boolean {
+ val oobeLaunchTime = tutorialRepository.launchTime(deviceType) ?: return false
+ return clock
+ .instant()
+ .isAfter(oobeLaunchTime.plusSeconds(initialDelayDuration.inWholeSeconds))
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
index 811c6533c656..7ccacb66e124 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.education.domain.interactor
import android.hardware.input.InputManager
import com.android.systemui.education.data.repository.fakeEduClock
import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository
+import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
import com.android.systemui.keyboard.data.repository.keyboardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
@@ -50,6 +51,12 @@ var Kosmos.keyboardTouchpadEduStatsInteractor by
Kosmos.Fixture {
KeyboardTouchpadEduStatsInteractorImpl(
backgroundScope = testScope.backgroundScope,
- contextualEducationInteractor = contextualEducationInteractor
+ contextualEducationInteractor = contextualEducationInteractor,
+ inputDeviceRepository = mockUserInputDeviceRepository,
+ tutorialRepository = mockTutorialSchedulerRepository,
+ clock = fakeEduClock
)
}
+
+var mockUserInputDeviceRepository = mock<UserInputDeviceRepository>()
+var mockTutorialSchedulerRepository = mock<TutorialSchedulerRepository>()