Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7f460f89 authored by Helen Cheuk's avatar Helen Cheuk Committed by Android (Google) Code Review
Browse files

Merge "[Contextual Edu] Enrich education logic" into main

parents e2cc5fba fb8cd19c
Loading
Loading
Loading
Loading
+39 −0
Original line number Diff line number Diff line
@@ -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
@@ -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() {
@@ -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)
        }

@@ -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 {
+9 −0
Original line number Diff line number Diff line
@@ -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(
+32 −4
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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)
@@ -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 {