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

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

Merge "Revert "[Contextual Edu] Check if device is connected before...

Merge "Revert "[Contextual Edu] Check if device is connected before incrementing signal count"" into main
parents 2ace70c3 bfe734d8
Loading
Loading
Loading
Loading
+6 −179
Original line number Diff line number Diff line
@@ -19,23 +19,16 @@ package com.android.systemui.education.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
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.contextualeducation.GestureType.BACK
import com.android.systemui.education.data.repository.contextualEducationRepository
import com.android.systemui.education.data.repository.fakeEduClock
import com.android.systemui.inputdevice.data.model.UserDeviceConnectionStatus
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
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.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -43,190 +36,24 @@ class KeyboardTouchpadStatsInteractorTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val underTest = kosmos.keyboardTouchpadEduStatsInteractor
    private val repository = kosmos.contextualEducationRepository
    private val fakeClock = kosmos.fakeEduClock
    private val initialDelayElapsedDuration =
        KeyboardTouchpadEduStatsInteractorImpl.initialDelayDuration + 1.seconds

    @Test
    fun dataUpdatedOnIncrementSignalCountWhenTouchpadConnected() =
        testScope.runTest {
            setUpForInitialDelayElapse()
            whenever(mockUserInputDeviceRepository.isAnyTouchpadConnectedForUser)
                .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))

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

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

    @Test
    fun dataUnchangedOnIncrementSignalCountWhenTouchpadDisconnected() =
        testScope.runTest {
            setUpForInitialDelayElapse()
            whenever(mockUserInputDeviceRepository.isAnyTouchpadConnectedForUser)
                .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = false, userId = 0)))

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

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

    @Test
    fun dataUpdatedOnIncrementSignalCountWhenKeyboardConnected() =
        testScope.runTest {
            setUpForInitialDelayElapse()
            whenever(mockUserInputDeviceRepository.isAnyKeyboardConnectedForUser)
                .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))

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

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

    @Test
    fun dataUnchangedOnIncrementSignalCountWhenKeyboardDisconnected() =
        testScope.runTest {
            setUpForInitialDelayElapse()
            whenever(mockUserInputDeviceRepository.isAnyKeyboardConnectedForUser)
                .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = false, userId = 0)))

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

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

    @Test
    fun dataUpdatedOnIncrementSignalCountAfterOobeLaunchInitialDelay() =
        testScope.runTest {
            setUpForDeviceConnection()
            whenever(mockTutorialSchedulerRepository.launchTime(any<DeviceType>()))
                .thenReturn(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 dataUnchangedOnIncrementSignalCountBeforeOobeLaunchInitialDelay() =
        testScope.runTest {
            setUpForDeviceConnection()
            whenever(mockTutorialSchedulerRepository.launchTime(any<DeviceType>()))
                .thenReturn(fakeClock.instant())

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

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

    @Test
    fun dataUpdatedOnIncrementSignalCountAfterTouchpadConnectionInitialDelay() =
    fun dataUpdatedOnIncrementSignalCount() =
        testScope.runTest {
            setUpForDeviceConnection()
            repository.updateEduDeviceConnectionTime { model ->
                model.copy(touchpadFirstConnectionTime = fakeClock.instant())
            }
            fakeClock.offset(initialDelayElapsedDuration)

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

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

    @Test
    fun dataUnchangedOnIncrementSignalCountBeforeTouchpadConnectionInitialDelay() =
        testScope.runTest {
            setUpForDeviceConnection()
            repository.updateEduDeviceConnectionTime { model ->
                model.copy(touchpadFirstConnectionTime = fakeClock.instant())
            }

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

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

    @Test
    fun dataUpdatedOnIncrementSignalCountAfterKeyboardConnectionInitialDelay() =
        testScope.runTest {
            setUpForDeviceConnection()
            repository.updateEduDeviceConnectionTime { model ->
                model.copy(keyboardFirstConnectionTime = fakeClock.instant())
            }
            fakeClock.offset(initialDelayElapsedDuration)

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

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

    @Test
    fun dataUnchangedOnIncrementSignalCountBeforeKeyboardConnectionInitialDelay() =
        testScope.runTest {
            setUpForDeviceConnection()
            repository.updateEduDeviceConnectionTime { model ->
                model.copy(keyboardFirstConnectionTime = fakeClock.instant())
            }

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

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

    @Test
    fun dataUnchangedOnIncrementSignalCountWhenNoSetupTime() =
        testScope.runTest {
            whenever(mockUserInputDeviceRepository.isAnyTouchpadConnectedForUser)
                .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))

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

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

    @Test
    fun dataAddedOnUpdateShortcutTriggerTime() =
        testScope.runTest {
            val model by collectLastValue(repository.readGestureEduModelFlow(BACK))
            val model by
                collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
            assertThat(model?.lastShortcutTriggeredTime).isNull()
            underTest.updateShortcutTriggerTime(BACK)
            assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant())
        }

    private suspend fun setUpForInitialDelayElapse() {
        whenever(mockTutorialSchedulerRepository.launchTime(any<DeviceType>()))
            .thenReturn(fakeClock.instant())
        fakeClock.offset(initialDelayElapsedDuration)
    }

    private fun setUpForDeviceConnection() {
        whenever(mockUserInputDeviceRepository.isAnyTouchpadConnectedForUser)
            .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))
        whenever(mockUserInputDeviceRepository.isAnyKeyboardConnectedForUser)
            .thenReturn(flowOf(UserDeviceConnectionStatus(isConnected = true, userId = 0)))
    }
}
+0 −4
Original line number Diff line number Diff line
@@ -83,7 +83,6 @@ constructor(
    }

    override fun start() {
        // Listen to back gesture model changes and trigger education if needed
        backgroundScope.launch {
            contextualEducationInteractor.backGestureModelFlow.collect {
                if (isUsageSessionExpired(it)) {
@@ -95,7 +94,6 @@ constructor(
            }
        }

        // Listen to touchpad connection changes and update the first connection time
        backgroundScope.launch {
            userInputDeviceRepository.isAnyTouchpadConnectedForUser.collect {
                if (
@@ -109,7 +107,6 @@ constructor(
            }
        }

        // Listen to keyboard connection changes and update the first connection time
        backgroundScope.launch {
            userInputDeviceRepository.isAnyKeyboardConnectedForUser.collect {
                if (
@@ -123,7 +120,6 @@ constructor(
            }
        }

        // Listen to keyboard shortcut triggered and update the last trigger time
        backgroundScope.launch {
            keyboardShortcutTriggered.collect {
                contextualEducationInteractor.updateShortcutTriggerTime(it)
+4 −62
Original line number Diff line number Diff line
@@ -16,25 +16,11 @@

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 com.android.systemui.contextualeducation.GestureType
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

/**
@@ -53,29 +39,12 @@ class KeyboardTouchpadEduStatsInteractorImpl
@Inject
constructor(
    @Background private val backgroundScope: CoroutineScope,
    private val contextualEducationInteractor: ContextualEducationInteractor,
    private val inputDeviceRepository: UserInputDeviceRepository,
    private val tutorialRepository: TutorialSchedulerRepository,
    @EduClock private val clock: Clock,
    private val contextualEducationInteractor: ContextualEducationInteractor
) : 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) && hasInitialDelayElapsed(targetDevice)) {
                contextualEducationInteractor.incrementSignalCount(gestureType)
            }
        }
        // Todo: check if keyboard/touchpad is connected before update
        backgroundScope.launch { contextualEducationInteractor.incrementSignalCount(gestureType) }
    }

    override fun updateShortcutTriggerTime(gestureType: GestureType) {
@@ -83,31 +52,4 @@ constructor(
            contextualEducationInteractor.updateShortcutTriggerTime(gestureType)
        }
    }

    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 false
    }

    /**
     * Keyboard shortcut education would be provided for All Apps. Touchpad gesture education would
     * be provided for the rest of the gesture types (i.e. Home, Overview, Back). This method maps
     * gesture to its target education device.
     */
    private fun getTargetDevice(gestureType: GestureType) =
        when (gestureType) {
            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))
    }
}
+1 −8
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ 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.data.repository.TutorialSchedulerRepository
import com.android.systemui.keyboard.data.repository.keyboardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
@@ -51,12 +50,6 @@ var Kosmos.keyboardTouchpadEduStatsInteractor by
    Kosmos.Fixture {
        KeyboardTouchpadEduStatsInteractorImpl(
            backgroundScope = testScope.backgroundScope,
            contextualEducationInteractor = contextualEducationInteractor,
            inputDeviceRepository = mockUserInputDeviceRepository,
            tutorialRepository = mockTutorialSchedulerRepository,
            clock = fakeEduClock
            contextualEducationInteractor = contextualEducationInteractor
        )
    }

var mockUserInputDeviceRepository = mock<UserInputDeviceRepository>()
var mockTutorialSchedulerRepository = mock<TutorialSchedulerRepository>()