Loading packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt +39 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import com.android.systemui.contextualeducation.GestureType 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.coroutines.collectValues import com.android.systemui.education.data.model.GestureEduModel import com.android.systemui.education.data.repository.contextualEducationRepository import com.android.systemui.education.data.repository.fakeEduClock Loading Loading @@ -62,6 +63,8 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor private val eduClock = kosmos.fakeEduClock private val minDurationForNextEdu = KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds @Before fun setup() { Loading Loading @@ -93,7 +96,10 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { triggerMaxEducationSignals(BACK) // runCurrent() to trigger 1st education runCurrent() eduClock.offset(minDurationForNextEdu) triggerMaxEducationSignals(BACK) assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification) } Loading @@ -114,6 +120,39 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { assertThat(model).isNull() } @Test fun no2ndEducationBeforeMinEduIntervalReached() = testScope.runTest { val models by collectValues(underTest.educationTriggered) triggerMaxEducationSignals(BACK) runCurrent() // Offset a duration that is less than the required education interval eduClock.offset(1.seconds) triggerMaxEducationSignals(BACK) runCurrent() assertThat(models.filterNotNull().size).isEqualTo(1) } @Test fun noNewEducationInfoAfterMaxEducationCountReached() = testScope.runTest { val models by collectValues(underTest.educationTriggered) // Trigger 2 educations triggerMaxEducationSignals(BACK) runCurrent() eduClock.offset(minDurationForNextEdu) triggerMaxEducationSignals(BACK) runCurrent() // Try triggering 3rd education eduClock.offset(minDurationForNextEdu) triggerMaxEducationSignals(BACK) assertThat(models.filterNotNull().size).isEqualTo(2) } @Test fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() = testScope.runTest { Loading packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt +9 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.contextualeducation.GestureType import com.android.systemui.contextualeducation.GestureType.BACK import com.android.systemui.education.data.repository.fakeEduClock import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor import com.android.systemui.education.domain.interactor.contextualEducationInteractor import com.android.systemui.education.domain.interactor.keyboardTouchpadEduInteractor Loading @@ -35,6 +36,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest Loading @@ -56,6 +58,9 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val interactor = kosmos.contextualEducationInteractor private val eduClock = kosmos.fakeEduClock private val minDurationForNextEdu = KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds private lateinit var underTest: ContextualEduUiCoordinator @Mock private lateinit var toast: Toast @Mock private lateinit var notificationManager: NotificationManager Loading Loading @@ -94,6 +99,7 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() { fun showNotificationOn2ndEdu() = testScope.runTest { triggerEducation(BACK) eduClock.offset(minDurationForNextEdu) triggerEducation(BACK) verify(notificationManager).notifyAsUser(any(), anyInt(), any(), any()) } Loading @@ -110,7 +116,10 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() { testScope.runTest { val notificationCaptor = ArgumentCaptor.forClass(Notification::class.java) triggerEducation(BACK) eduClock.offset(minDurationForNextEdu) triggerEducation(BACK) verify(notificationManager) .notifyAsUser(any(), anyInt(), notificationCaptor.capture(), any()) verifyNotificationContent( Loading packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt +32 −4 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.education.domain.interactor import android.hardware.input.InputManager import android.hardware.input.InputManager.KeyGestureEventListener import android.hardware.input.KeyGestureEvent import android.os.SystemProperties import com.android.systemui.CoreStartable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.contextualeducation.GestureType Loading @@ -35,7 +36,10 @@ import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.time.Clock import java.util.concurrent.Executor import javax.inject.Inject import kotlin.time.Duration.Companion.hours import kotlin.time.Duration import kotlin.time.Duration.Companion.days import kotlin.time.DurationUnit import kotlin.time.toDuration import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow Loading @@ -58,7 +62,21 @@ constructor( companion object { const val TAG = "KeyboardTouchpadEduInteractor" const val MAX_SIGNAL_COUNT: Int = 2 val usageSessionDuration = 72.hours const val MAX_EDUCATION_SHOW_COUNT: Int = 2 val usageSessionDuration = getDurationForConfig("persist.contextual_edu.usage_session_sec", 3.days) val minIntervalBetweenEdu = getDurationForConfig("persist.contextual_edu.edu_interval_sec", 7.days) private fun getDurationForConfig( systemPropertyKey: String, defaultDuration: Duration ): Duration = SystemProperties.getLong( systemPropertyKey, /* defaultValue= */ defaultDuration.inWholeSeconds ) .toDuration(DurationUnit.SECONDS) } private val _educationTriggered = MutableStateFlow<EducationInfo?>(null) Loading Loading @@ -128,10 +146,20 @@ constructor( } private fun isEducationNeeded(model: GestureEduModel): Boolean { // Todo: b/354884305 - add complete education logic to show education in correct scenarios val lessThanMaxEduCount = model.educationShownCount < MAX_EDUCATION_SHOW_COUNT val noShortcutTriggered = model.lastShortcutTriggeredTime == null val signalCountReached = model.signalCount >= MAX_SIGNAL_COUNT return noShortcutTriggered && signalCountReached val isPreviousEduOlderThanMinInterval = if (model.educationShownCount == 1) { model.lastEducationTime ?.plusSeconds(minIntervalBetweenEdu.inWholeSeconds) ?.isBefore(clock.instant()) ?: true } else true return lessThanMaxEduCount && noShortcutTriggered && signalCountReached && isPreviousEduOlderThanMinInterval } private fun isUsageSessionExpired(model: GestureEduModel): Boolean { Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt +39 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import com.android.systemui.contextualeducation.GestureType 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.coroutines.collectValues import com.android.systemui.education.data.model.GestureEduModel import com.android.systemui.education.data.repository.contextualEducationRepository import com.android.systemui.education.data.repository.fakeEduClock Loading Loading @@ -62,6 +63,8 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor private val eduClock = kosmos.fakeEduClock private val minDurationForNextEdu = KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds @Before fun setup() { Loading Loading @@ -93,7 +96,10 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { triggerMaxEducationSignals(BACK) // runCurrent() to trigger 1st education runCurrent() eduClock.offset(minDurationForNextEdu) triggerMaxEducationSignals(BACK) assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification) } Loading @@ -114,6 +120,39 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { assertThat(model).isNull() } @Test fun no2ndEducationBeforeMinEduIntervalReached() = testScope.runTest { val models by collectValues(underTest.educationTriggered) triggerMaxEducationSignals(BACK) runCurrent() // Offset a duration that is less than the required education interval eduClock.offset(1.seconds) triggerMaxEducationSignals(BACK) runCurrent() assertThat(models.filterNotNull().size).isEqualTo(1) } @Test fun noNewEducationInfoAfterMaxEducationCountReached() = testScope.runTest { val models by collectValues(underTest.educationTriggered) // Trigger 2 educations triggerMaxEducationSignals(BACK) runCurrent() eduClock.offset(minDurationForNextEdu) triggerMaxEducationSignals(BACK) runCurrent() // Try triggering 3rd education eduClock.offset(minDurationForNextEdu) triggerMaxEducationSignals(BACK) assertThat(models.filterNotNull().size).isEqualTo(2) } @Test fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() = testScope.runTest { Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt +9 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.contextualeducation.GestureType import com.android.systemui.contextualeducation.GestureType.BACK import com.android.systemui.education.data.repository.fakeEduClock import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor import com.android.systemui.education.domain.interactor.contextualEducationInteractor import com.android.systemui.education.domain.interactor.keyboardTouchpadEduInteractor Loading @@ -35,6 +36,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest Loading @@ -56,6 +58,9 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val interactor = kosmos.contextualEducationInteractor private val eduClock = kosmos.fakeEduClock private val minDurationForNextEdu = KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds private lateinit var underTest: ContextualEduUiCoordinator @Mock private lateinit var toast: Toast @Mock private lateinit var notificationManager: NotificationManager Loading Loading @@ -94,6 +99,7 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() { fun showNotificationOn2ndEdu() = testScope.runTest { triggerEducation(BACK) eduClock.offset(minDurationForNextEdu) triggerEducation(BACK) verify(notificationManager).notifyAsUser(any(), anyInt(), any(), any()) } Loading @@ -110,7 +116,10 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() { testScope.runTest { val notificationCaptor = ArgumentCaptor.forClass(Notification::class.java) triggerEducation(BACK) eduClock.offset(minDurationForNextEdu) triggerEducation(BACK) verify(notificationManager) .notifyAsUser(any(), anyInt(), notificationCaptor.capture(), any()) verifyNotificationContent( Loading
packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt +32 −4 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.education.domain.interactor import android.hardware.input.InputManager import android.hardware.input.InputManager.KeyGestureEventListener import android.hardware.input.KeyGestureEvent import android.os.SystemProperties import com.android.systemui.CoreStartable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.contextualeducation.GestureType Loading @@ -35,7 +36,10 @@ import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.time.Clock import java.util.concurrent.Executor import javax.inject.Inject import kotlin.time.Duration.Companion.hours import kotlin.time.Duration import kotlin.time.Duration.Companion.days import kotlin.time.DurationUnit import kotlin.time.toDuration import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow Loading @@ -58,7 +62,21 @@ constructor( companion object { const val TAG = "KeyboardTouchpadEduInteractor" const val MAX_SIGNAL_COUNT: Int = 2 val usageSessionDuration = 72.hours const val MAX_EDUCATION_SHOW_COUNT: Int = 2 val usageSessionDuration = getDurationForConfig("persist.contextual_edu.usage_session_sec", 3.days) val minIntervalBetweenEdu = getDurationForConfig("persist.contextual_edu.edu_interval_sec", 7.days) private fun getDurationForConfig( systemPropertyKey: String, defaultDuration: Duration ): Duration = SystemProperties.getLong( systemPropertyKey, /* defaultValue= */ defaultDuration.inWholeSeconds ) .toDuration(DurationUnit.SECONDS) } private val _educationTriggered = MutableStateFlow<EducationInfo?>(null) Loading Loading @@ -128,10 +146,20 @@ constructor( } private fun isEducationNeeded(model: GestureEduModel): Boolean { // Todo: b/354884305 - add complete education logic to show education in correct scenarios val lessThanMaxEduCount = model.educationShownCount < MAX_EDUCATION_SHOW_COUNT val noShortcutTriggered = model.lastShortcutTriggeredTime == null val signalCountReached = model.signalCount >= MAX_SIGNAL_COUNT return noShortcutTriggered && signalCountReached val isPreviousEduOlderThanMinInterval = if (model.educationShownCount == 1) { model.lastEducationTime ?.plusSeconds(minIntervalBetweenEdu.inWholeSeconds) ?.isBefore(clock.instant()) ?: true } else true return lessThanMaxEduCount && noShortcutTriggered && signalCountReached && isPreviousEduOlderThanMinInterval } private fun isUsageSessionExpired(model: GestureEduModel): Boolean { Loading