summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt41
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt4
9 files changed, 142 insertions, 27 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index f82a7b8df089..5dd6c228e014 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -85,7 +85,9 @@ class ContextualEducationRepositoryTest : SysuiTestCase() {
GestureEduModel(
signalCount = 2,
educationShownCount = 1,
- lastShortcutTriggeredTime = kosmos.fakeEduClock.instant()
+ lastShortcutTriggeredTime = kosmos.fakeEduClock.instant(),
+ lastEducationTime = kosmos.fakeEduClock.instant(),
+ usageSessionStartTime = kosmos.fakeEduClock.instant(),
)
underTest.updateGestureEduModel(BACK) { newModel }
val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index 1b4632a546d4..6867089473da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -22,9 +22,15 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.education.data.model.GestureEduModel
+import com.android.systemui.education.data.repository.contextualEducationRepository
+import com.android.systemui.education.data.repository.fakeEduClock
+import com.android.systemui.education.shared.model.EducationUiType
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.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -37,6 +43,7 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
private val testScope = kosmos.testScope
private val contextualEduInteractor = kosmos.contextualEducationInteractor
private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
+ private val eduClock = kosmos.fakeEduClock
@Before
fun setup() {
@@ -46,12 +53,32 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
@Test
fun newEducationInfoOnMaxSignalCountReached() =
testScope.runTest {
- tryTriggeringEducation(BACK)
+ triggerMaxEducationSignals(BACK)
val model by collectLastValue(underTest.educationTriggered)
assertThat(model?.gestureType).isEqualTo(BACK)
}
@Test
+ fun newEducationToastOn1stEducation() =
+ testScope.runTest {
+ val model by collectLastValue(underTest.educationTriggered)
+ triggerMaxEducationSignals(BACK)
+ assertThat(model?.educationUiType).isEqualTo(EducationUiType.Toast)
+ }
+
+ @Test
+ @kotlinx.coroutines.ExperimentalCoroutinesApi
+ fun newEducationNotificationOn2ndEducation() =
+ testScope.runTest {
+ val model by collectLastValue(underTest.educationTriggered)
+ triggerMaxEducationSignals(BACK)
+ // runCurrent() to trigger 1st education
+ runCurrent()
+ triggerMaxEducationSignals(BACK)
+ assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification)
+ }
+
+ @Test
fun noEducationInfoBeforeMaxSignalCountReached() =
testScope.runTest {
contextualEduInteractor.incrementSignalCount(BACK)
@@ -64,11 +91,30 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
testScope.runTest {
val model by collectLastValue(underTest.educationTriggered)
contextualEduInteractor.updateShortcutTriggerTime(BACK)
- tryTriggeringEducation(BACK)
+ triggerMaxEducationSignals(BACK)
assertThat(model).isNull()
}
- private suspend fun tryTriggeringEducation(gestureType: GestureType) {
+ @Test
+ fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() =
+ testScope.runTest {
+ val model by
+ collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
+ contextualEduInteractor.incrementSignalCount(BACK)
+ eduClock.offset(KeyboardTouchpadEduInteractor.usageSessionDuration.plus(1.seconds))
+ val secondSignalReceivedTime = eduClock.instant()
+ contextualEduInteractor.incrementSignalCount(BACK)
+
+ assertThat(model)
+ .isEqualTo(
+ GestureEduModel(
+ signalCount = 1,
+ usageSessionStartTime = secondSignalReceivedTime
+ )
+ )
+ }
+
+ private suspend fun triggerMaxEducationSignals(gestureType: GestureType) {
// Increment max number of signal to try triggering education
for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
contextualEduInteractor.incrementSignalCount(gestureType)
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
index 9f6cb4d027e6..a171f8775768 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
@@ -26,4 +26,6 @@ data class GestureEduModel(
val signalCount: Int = 0,
val educationShownCount: Int = 0,
val lastShortcutTriggeredTime: Instant? = null,
+ val usageSessionStartTime: Instant? = null,
+ val lastEducationTime: Instant? = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 22ba4ad648f8..7c3d63388aa1 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -73,6 +73,8 @@ constructor(
const val SIGNAL_COUNT_SUFFIX = "_SIGNAL_COUNT"
const val NUMBER_OF_EDU_SHOWN_SUFFIX = "_NUMBER_OF_EDU_SHOWN"
const val LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX = "_LAST_SHORTCUT_TRIGGERED_TIME"
+ const val USAGE_SESSION_START_TIME_SUFFIX = "_USAGE_SESSION_START_TIME"
+ const val LAST_EDUCATION_TIME_SUFFIX = "_LAST_EDUCATION_TIME"
const val DATASTORE_DIR = "education/USER%s_ContextualEducation"
}
@@ -113,6 +115,14 @@ constructor(
preferences[getLastShortcutTriggeredTimeKey(gestureType)]?.let {
Instant.ofEpochSecond(it)
},
+ usageSessionStartTime =
+ preferences[getUsageSessionStartTimeKey(gestureType)]?.let {
+ Instant.ofEpochSecond(it)
+ },
+ lastEducationTime =
+ preferences[getLastEducationTimeKey(gestureType)]?.let {
+ Instant.ofEpochSecond(it)
+ },
)
}
@@ -125,11 +135,21 @@ constructor(
val updatedModel = transform(currentModel)
preferences[getSignalCountKey(gestureType)] = updatedModel.signalCount
preferences[getEducationShownCountKey(gestureType)] = updatedModel.educationShownCount
- updateTimeByInstant(
+ setInstant(
preferences,
updatedModel.lastShortcutTriggeredTime,
getLastShortcutTriggeredTimeKey(gestureType)
)
+ setInstant(
+ preferences,
+ updatedModel.usageSessionStartTime,
+ getUsageSessionStartTimeKey(gestureType)
+ )
+ setInstant(
+ preferences,
+ updatedModel.lastEducationTime,
+ getLastEducationTimeKey(gestureType)
+ )
}
}
@@ -142,7 +162,13 @@ constructor(
private fun getLastShortcutTriggeredTimeKey(gestureType: GestureType): Preferences.Key<Long> =
longPreferencesKey(gestureType.name + LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX)
- private fun updateTimeByInstant(
+ private fun getUsageSessionStartTimeKey(gestureType: GestureType): Preferences.Key<Long> =
+ longPreferencesKey(gestureType.name + USAGE_SESSION_START_TIME_SUFFIX)
+
+ private fun getLastEducationTimeKey(gestureType: GestureType): Preferences.Key<Long> =
+ longPreferencesKey(gestureType.name + LAST_EDUCATION_TIME_SUFFIX)
+
+ private fun setInstant(
preferences: MutablePreferences,
instant: Instant?,
key: Preferences.Key<Long>
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
index 5ec1006f8c42..db5c386a6c65 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
@@ -68,7 +68,13 @@ constructor(
}
suspend fun incrementSignalCount(gestureType: GestureType) {
- repository.updateGestureEduModel(gestureType) { it.copy(signalCount = it.signalCount + 1) }
+ repository.updateGestureEduModel(gestureType) {
+ it.copy(
+ signalCount = it.signalCount + 1,
+ usageSessionStartTime =
+ if (it.signalCount == 0) clock.instant() else it.usageSessionStartTime
+ )
+ }
}
suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
@@ -76,4 +82,22 @@ constructor(
it.copy(lastShortcutTriggeredTime = clock.instant())
}
}
+
+ suspend fun updateOnEduTriggered(gestureType: GestureType) {
+ repository.updateGestureEduModel(gestureType) {
+ it.copy(
+ // Reset signal counter and usageSessionStartTime after edu triggered
+ signalCount = 0,
+ lastEducationTime = clock.instant(),
+ educationShownCount = it.educationShownCount + 1,
+ usageSessionStartTime = null
+ )
+ }
+ }
+
+ suspend fun startNewUsageSession(gestureType: GestureType) {
+ repository.updateGestureEduModel(gestureType) {
+ it.copy(usageSessionStartTime = clock.instant(), signalCount = 1)
+ }
+ }
}
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 9016c7339c25..3a3fb8c6acbe 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,17 +17,19 @@
package com.android.systemui.education.domain.interactor
import com.android.systemui.CoreStartable
+import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.education.shared.model.EducationInfo
import com.android.systemui.education.shared.model.EducationUiType
+import java.time.Clock
import javax.inject.Inject
+import kotlin.time.Duration.Companion.hours
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
/** Allow listening to new contextual education triggered */
@@ -36,11 +38,13 @@ class KeyboardTouchpadEduInteractor
@Inject
constructor(
@Background private val backgroundScope: CoroutineScope,
- private val contextualEducationInteractor: ContextualEducationInteractor
+ private val contextualEducationInteractor: ContextualEducationInteractor,
+ @EduClock private val clock: Clock,
) : CoreStartable {
companion object {
const val MAX_SIGNAL_COUNT: Int = 2
+ val usageSessionDuration = 72.hours
}
private val _educationTriggered = MutableStateFlow<EducationInfo?>(null)
@@ -48,25 +52,30 @@ constructor(
override fun start() {
backgroundScope.launch {
- contextualEducationInteractor.backGestureModelFlow
- .mapNotNull { getEduType(it) }
- .collect { _educationTriggered.value = EducationInfo(BACK, it) }
- }
- }
-
- private fun getEduType(model: GestureEduModel): EducationUiType? {
- if (isEducationNeeded(model)) {
- return EducationUiType.Toast
- } else {
- return null
+ contextualEducationInteractor.backGestureModelFlow.collect {
+ if (isUsageSessionExpired(it)) {
+ contextualEducationInteractor.startNewUsageSession(BACK)
+ } else if (isEducationNeeded(it)) {
+ _educationTriggered.value = EducationInfo(BACK, getEduType(it))
+ contextualEducationInteractor.updateOnEduTriggered(BACK)
+ }
+ }
}
}
private fun isEducationNeeded(model: GestureEduModel): Boolean {
// Todo: b/354884305 - add complete education logic to show education in correct scenarios
- val shortcutWasTriggered = model.lastShortcutTriggeredTime == null
+ val noShortcutTriggered = model.lastShortcutTriggeredTime == null
val signalCountReached = model.signalCount >= MAX_SIGNAL_COUNT
+ return noShortcutTriggered && signalCountReached
+ }
- return shortcutWasTriggered && signalCountReached
+ private fun isUsageSessionExpired(model: GestureEduModel): Boolean {
+ return model.usageSessionStartTime
+ ?.plusSeconds(usageSessionDuration.inWholeSeconds)
+ ?.isBefore(clock.instant()) ?: false
}
+
+ private fun getEduType(model: GestureEduModel) =
+ if (model.educationShownCount > 0) EducationUiType.Notification else EducationUiType.Toast
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
index aac5e57207a7..1d2bce2f9b99 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
@@ -17,10 +17,9 @@
package com.android.systemui.education.data.repository
import com.android.systemui.kosmos.Kosmos
-import java.time.Clock
import java.time.Instant
var Kosmos.contextualEducationRepository: ContextualEducationRepository by
Kosmos.Fixture { FakeContextualEducationRepository() }
-var Kosmos.fakeEduClock: Clock by Kosmos.Fixture { FakeEduClock(Instant.MIN) }
+var Kosmos.fakeEduClock: FakeEduClock by Kosmos.Fixture { FakeEduClock(Instant.MIN) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
index 513c14381997..c9a5d4bffef2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
@@ -19,8 +19,9 @@ package com.android.systemui.education.data.repository
import java.time.Clock
import java.time.Instant
import java.time.ZoneId
+import kotlin.time.Duration
-class FakeEduClock(private val base: Instant) : Clock() {
+class FakeEduClock(private var base: Instant) : Clock() {
private val zone: ZoneId = ZoneId.of("UTC")
override fun instant(): Instant {
@@ -34,4 +35,8 @@ class FakeEduClock(private val base: Instant) : Clock() {
override fun getZone(): ZoneId {
return zone
}
+
+ fun offset(duration: Duration) {
+ base = base.plusSeconds(duration.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 fb4e9012f79d..5088677161d8 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
@@ -16,6 +16,7 @@
package com.android.systemui.education.domain.interactor
+import com.android.systemui.education.data.repository.fakeEduClock
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
@@ -23,7 +24,8 @@ var Kosmos.keyboardTouchpadEduInteractor by
Kosmos.Fixture {
KeyboardTouchpadEduInteractor(
backgroundScope = testScope.backgroundScope,
- contextualEducationInteractor = contextualEducationInteractor
+ contextualEducationInteractor = contextualEducationInteractor,
+ clock = fakeEduClock
)
}