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

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

Merge "[Contextual Edu] Check if initial delay is elapsed before updating signal count" into main

parents f8481647 931f02d9
Loading
Loading
Loading
Loading
+70 −0
Original line number Diff line number Diff line
@@ -24,12 +24,17 @@ 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.tutorial.data.repository.DeviceType
import com.android.systemui.inputdevice.tutorial.tutorialSchedulerRepository
import com.android.systemui.keyboard.data.repository.keyboardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.touchpad.data.repository.touchpadRepository
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith

@@ -42,10 +47,15 @@ class KeyboardTouchpadStatsInteractorTest : SysuiTestCase() {
    private val keyboardRepository = kosmos.keyboardRepository
    private val touchpadRepository = kosmos.touchpadRepository
    private val repository = kosmos.contextualEducationRepository
    private val fakeClock = kosmos.fakeEduClock
    private val tutorialSchedulerRepository = kosmos.tutorialSchedulerRepository
    private val initialDelayElapsedDuration =
        KeyboardTouchpadEduStatsInteractorImpl.initialDelayDuration + 1.seconds

    @Test
    fun dataUpdatedOnIncrementSignalCountWhenTouchpadConnected() =
        testScope.runTest {
            setUpForInitialDelayElapse()
            touchpadRepository.setIsAnyTouchpadConnected(true)

            val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
@@ -58,6 +68,7 @@ class KeyboardTouchpadStatsInteractorTest : SysuiTestCase() {
    @Test
    fun dataUnchangedOnIncrementSignalCountWhenTouchpadDisconnected() =
        testScope.runTest {
            setUpForInitialDelayElapse()
            touchpadRepository.setIsAnyTouchpadConnected(false)

            val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
@@ -70,6 +81,7 @@ class KeyboardTouchpadStatsInteractorTest : SysuiTestCase() {
    @Test
    fun dataUpdatedOnIncrementSignalCountWhenKeyboardConnected() =
        testScope.runTest {
            setUpForInitialDelayElapse()
            keyboardRepository.setIsAnyKeyboardConnected(true)

            val model by collectLastValue(repository.readGestureEduModelFlow(ALL_APPS))
@@ -82,6 +94,7 @@ class KeyboardTouchpadStatsInteractorTest : SysuiTestCase() {
    @Test
    fun dataUnchangedOnIncrementSignalCountWhenKeyboardDisconnected() =
        testScope.runTest {
            setUpForInitialDelayElapse()
            keyboardRepository.setIsAnyKeyboardConnected(false)

            val model by collectLastValue(repository.readGestureEduModelFlow(ALL_APPS))
@@ -99,4 +112,61 @@ class KeyboardTouchpadStatsInteractorTest : SysuiTestCase() {
            underTest.updateShortcutTriggerTime(BACK)
            assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant())
        }

    @Test
    fun dataUpdatedOnIncrementSignalCountAfterInitialDelay() =
        testScope.runTest {
            setUpForDeviceConnection()
            tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, 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 dataUnchangedOnIncrementSignalCountBeforeInitialDelay() =
        testScope.runTest {
            setUpForDeviceConnection()
            tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, fakeClock.instant())

            // No offset to the clock to simulate update before initial delay
            val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
            val originalValue = model!!.signalCount
            underTest.incrementSignalCount(BACK)

            assertThat(model?.signalCount).isEqualTo(originalValue)
        }

    @Test
    fun dataUnchangedOnIncrementSignalCountWithoutOobeLaunchTime() =
        testScope.runTest {
            // No update to OOBE launch time to simulate no OOBE is launched yet
            setUpForDeviceConnection()

            val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
            val originalValue = model!!.signalCount
            underTest.incrementSignalCount(BACK)

            assertThat(model?.signalCount).isEqualTo(originalValue)
        }

    private suspend fun setUpForInitialDelayElapse() {
        tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, fakeClock.instant())
        tutorialSchedulerRepository.updateLaunchTime(DeviceType.KEYBOARD, fakeClock.instant())
        fakeClock.offset(initialDelayElapsedDuration)
    }

    private fun setUpForDeviceConnection() {
        touchpadRepository.setIsAnyTouchpadConnected(true)
        keyboardRepository.setIsAnyKeyboardConnected(true)
    }

    @After
    fun clear() {
        testScope.launch { tutorialSchedulerRepository.clearDataStore() }
    }
}
+31 −6
Original line number Diff line number Diff line
@@ -16,15 +16,23 @@

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.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
@@ -47,12 +55,24 @@ constructor(
    @Background private val backgroundScope: CoroutineScope,
    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) {
        backgroundScope.launch {
            val targetDevice = getTargetDevice(gestureType)
            if (isTargetDeviceConnected(targetDevice)) {
            if (isTargetDeviceConnected(targetDevice) && hasInitialDelayElapsed(targetDevice)) {
                contextualEducationInteractor.incrementSignalCount(gestureType)
            }
        }
@@ -65,12 +85,10 @@ constructor(
    }

    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 when (deviceType) {
            KEYBOARD -> inputDeviceRepository.isAnyKeyboardConnectedForUser.first().isConnected
            TOUCHPAD -> inputDeviceRepository.isAnyTouchpadConnectedForUser.first().isConnected
        }
        return false
    }

    /**
@@ -83,4 +101,11 @@ constructor(
            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))
    }
}
+4 −1
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 com.android.systemui.education.data.repository.fakeEduClock
import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository
import com.android.systemui.inputdevice.tutorial.tutorialSchedulerRepository
import com.android.systemui.keyboard.data.repository.keyboardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
@@ -57,6 +58,8 @@ var Kosmos.keyboardTouchpadEduStatsInteractor by
                    keyboardRepository,
                    touchpadRepository,
                    userRepository
                )
                ),
            tutorialSchedulerRepository,
            fakeEduClock
        )
    }
+12 −0
Original line number Diff line number Diff line
@@ -16,8 +16,20 @@

package com.android.systemui.inputdevice.tutorial

import android.content.applicationContext
import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import org.mockito.kotlin.mock

var Kosmos.inputDeviceTutorialLogger: InputDeviceTutorialLogger by
    Kosmos.Fixture { mock<InputDeviceTutorialLogger>() }

var Kosmos.tutorialSchedulerRepository by
    Kosmos.Fixture {
        TutorialSchedulerRepository(
            applicationContext = applicationContext,
            testScope.backgroundScope,
            "KosmosTutorialSchedulerRepository"
        )
    }