diff options
3 files changed, 80 insertions, 4 deletions
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 ca15eff4610b..fd4e008a887e 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 @@ -26,6 +26,7 @@ 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.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 @@ -61,6 +62,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() { @@ -92,7 +95,10 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { triggerMaxEducationSignals(BACK) // runCurrent() to trigger 1st education runCurrent() + + eduClock.offset(minDurationForNextEdu) triggerMaxEducationSignals(BACK) + assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification) } @@ -114,6 +120,39 @@ class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { } @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 { val model by diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt index e075b7edda40..c4ac585f7e4a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt @@ -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 @@ -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 @@ -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 @@ -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()) } @@ -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( 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 9520f474f936..0f3bf8a7f91e 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 @@ -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 @@ -37,7 +38,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 @@ -60,7 +64,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) @@ -136,10 +154,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 { |