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

Commit 1738c8a4 authored by Yalan Yiue's avatar Yalan Yiue Committed by Android (Google) Code Review
Browse files

Merge "Fix launched time. Add notified status." into main

parents c974fe0a 730b2e5c
Loading
Loading
Loading
Loading
+42 −31
Original line number Diff line number Diff line
@@ -22,13 +22,12 @@ import com.android.systemui.SysuiTestCase
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 com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.backgroundScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import java.time.Instant
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,7 +37,7 @@ import org.junit.runner.RunWith
class TutorialSchedulerRepositoryTest : SysuiTestCase() {

    private lateinit var underTest: TutorialSchedulerRepository
    private val kosmos = Kosmos()
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    @Before
@@ -46,45 +45,57 @@ class TutorialSchedulerRepositoryTest : SysuiTestCase() {
        underTest =
            TutorialSchedulerRepository(
                context,
                testScope.backgroundScope,
                kosmos.backgroundScope,
                "TutorialSchedulerRepositoryTest",
            )
    }

    @After
    fun clear() {
        testScope.launch { underTest.clear() }
    }

    @Test
    fun initialState() =
        testScope.runTest {
    fun initialState() = runTestAndClear {
        assertThat(underTest.wasEverConnected(KEYBOARD)).isFalse()
        assertThat(underTest.wasEverConnected(TOUCHPAD)).isFalse()
            assertThat(underTest.isLaunched(KEYBOARD)).isFalse()
            assertThat(underTest.isLaunched(TOUCHPAD)).isFalse()
        assertThat(underTest.isNotified(KEYBOARD)).isFalse()
        assertThat(underTest.isNotified(TOUCHPAD)).isFalse()
        assertThat(underTest.isScheduledTutorialLaunched(KEYBOARD)).isFalse()
        assertThat(underTest.isScheduledTutorialLaunched(TOUCHPAD)).isFalse()
    }

    @Test
    fun connectKeyboard() =
        testScope.runTest {
    fun connectKeyboard() = runTestAndClear {
        val now = Instant.now()
            underTest.updateFirstConnectionTime(KEYBOARD, now)
        underTest.setFirstConnectionTime(KEYBOARD, now)

        assertThat(underTest.wasEverConnected(KEYBOARD)).isTrue()
            assertThat(underTest.firstConnectionTime(KEYBOARD)!!.epochSecond)
        assertThat(underTest.getFirstConnectionTime(KEYBOARD)!!.epochSecond)
            .isEqualTo(now.epochSecond)
        assertThat(underTest.wasEverConnected(TOUCHPAD)).isFalse()
    }

    @Test
    fun launchKeyboard() =
        testScope.runTest {
    fun launchKeyboard() = runTestAndClear {
        val now = Instant.now()
            underTest.updateLaunchTime(KEYBOARD, now)
        underTest.setScheduledTutorialLaunchTime(KEYBOARD, now)

        assertThat(underTest.isScheduledTutorialLaunched(KEYBOARD)).isTrue()
        assertThat(underTest.getScheduledTutorialLaunchTime(KEYBOARD)!!.epochSecond)
            .isEqualTo(now.epochSecond)
        assertThat(underTest.isScheduledTutorialLaunched(TOUCHPAD)).isFalse()
    }

    @Test
    fun notifyKeyboard() = runTestAndClear {
        underTest.setNotified(KEYBOARD)

            assertThat(underTest.isLaunched(KEYBOARD)).isTrue()
            assertThat(underTest.launchTime(KEYBOARD)!!.epochSecond).isEqualTo(now.epochSecond)
            assertThat(underTest.isLaunched(TOUCHPAD)).isFalse()
        assertThat(underTest.isNotified(KEYBOARD)).isTrue()
        assertThat(underTest.isNotified(TOUCHPAD)).isFalse()
    }

    private fun runTestAndClear(block: suspend () -> Unit) =
        testScope.runTest {
            try {
                block()
            } finally {
                underTest.clear()
            }
        }
}
+39 −45
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedul
import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator
import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.kosmos.backgroundScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.settings.userTracker
@@ -34,14 +35,9 @@ import com.android.systemui.testKosmos
import com.android.systemui.touchpad.data.repository.FakeTouchpadRepository
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.hours
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -65,7 +61,6 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {
    private val testScope = kosmos.testScope
    private val keyboardRepository = FakeKeyboardRepository()
    private val touchpadRepository = FakeTouchpadRepository()
    private lateinit var dataStoreScope: CoroutineScope
    private lateinit var repository: TutorialSchedulerRepository
    @Mock private lateinit var notificationManager: NotificationManager
    @Captor private lateinit var notificationCaptor: ArgumentCaptor<Notification>
@@ -73,11 +68,10 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {

    @Before
    fun setup() {
        dataStoreScope = CoroutineScope(Dispatchers.Unconfined)
        repository =
            TutorialSchedulerRepository(
                context,
                dataStoreScope,
                kosmos.backgroundScope,
                dataStoreName = "TutorialNotificationCoordinatorTest",
            )
        val interactor =
@@ -87,6 +81,7 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {
                repository,
                kosmos.inputDeviceTutorialLogger,
                kosmos.commandRegistry,
                testScope.backgroundScope,
            )
        underTest =
            TutorialNotificationCoordinator(
@@ -100,17 +95,10 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {
        underTest.start()
    }

    @After
    fun clear() {
        runBlocking { repository.clear() }
        dataStoreScope.cancel()
    }

    @Test
    fun showKeyboardNotification() =
        testScope.runTest {
    fun showKeyboardNotification() = runTestAndClear {
        keyboardRepository.setIsAnyKeyboardConnected(true)
            advanceTimeBy(LAUNCH_DELAY)
        testScope.advanceTimeBy(LAUNCH_DELAY)
        verifyNotification(
            R.string.launch_keyboard_tutorial_notification_title,
            R.string.launch_keyboard_tutorial_notification_content,
@@ -118,10 +106,9 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {
    }

    @Test
    fun showTouchpadNotification() =
        testScope.runTest {
    fun showTouchpadNotification() = runTestAndClear {
        touchpadRepository.setIsAnyTouchpadConnected(true)
            advanceTimeBy(LAUNCH_DELAY)
        testScope.advanceTimeBy(LAUNCH_DELAY)
        verifyNotification(
            R.string.launch_touchpad_tutorial_notification_title,
            R.string.launch_touchpad_tutorial_notification_content,
@@ -129,11 +116,10 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {
    }

    @Test
    fun showKeyboardTouchpadNotification() =
        testScope.runTest {
    fun showKeyboardTouchpadNotification() = runTestAndClear {
        keyboardRepository.setIsAnyKeyboardConnected(true)
        touchpadRepository.setIsAnyTouchpadConnected(true)
            advanceTimeBy(LAUNCH_DELAY)
        testScope.advanceTimeBy(LAUNCH_DELAY)
        verifyNotification(
            R.string.launch_keyboard_touchpad_tutorial_notification_title,
            R.string.launch_keyboard_touchpad_tutorial_notification_content,
@@ -141,13 +127,21 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {
    }

    @Test
    fun doNotShowNotification() =
        testScope.runTest {
            advanceTimeBy(LAUNCH_DELAY)
    fun doNotShowNotification() = runTestAndClear {
        testScope.advanceTimeBy(LAUNCH_DELAY)
        verify(notificationManager, never())
            .notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), any(), any())
    }

    private fun runTestAndClear(block: suspend () -> Unit) =
        testScope.runTest {
            try {
                block()
            } finally {
                repository.clear()
            }
        }

    private fun verifyNotification(@StringRes titleResId: Int, @StringRes contentResId: Int) {
        verify(notificationManager)
            .notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), notificationCaptor.capture(), any())
+56 −66
Original line number Diff line number Diff line
@@ -30,16 +30,11 @@ import com.android.systemui.testKosmos
import com.android.systemui.touchpad.data.repository.FakeTouchpadRepository
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.hours
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -52,18 +47,16 @@ class TutorialSchedulerInteractorTest : SysuiTestCase() {
    private lateinit var underTest: TutorialSchedulerInteractor
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private lateinit var dataStoreScope: CoroutineScope
    private val keyboardRepository = FakeKeyboardRepository()
    private val touchpadRepository = FakeTouchpadRepository()
    private lateinit var schedulerRepository: TutorialSchedulerRepository

    @Before
    fun setup() {
        dataStoreScope = CoroutineScope(Dispatchers.Unconfined)
        schedulerRepository =
            TutorialSchedulerRepository(
                context,
                dataStoreScope,
                testScope.backgroundScope,
                dataStoreName = "TutorialSchedulerInteractorTest",
            )
        underTest =
@@ -73,98 +66,95 @@ class TutorialSchedulerInteractorTest : SysuiTestCase() {
                schedulerRepository,
                kosmos.inputDeviceTutorialLogger,
                kosmos.commandRegistry,
                testScope.backgroundScope,
            )
    }

    @After
    fun clear() {
        runBlocking { schedulerRepository.clear() }
        dataStoreScope.cancel()
    }

    @Test
    fun connectKeyboard_delayElapse_launchForKeyboard() =
        testScope.runTest {
    fun connectKeyboard_delayElapse_notifyForKeyboard() = runTestAndClear {
        keyboardRepository.setIsAnyKeyboardConnected(true)
            advanceTimeBy(LAUNCH_DELAY)

            launchAndAssert(TutorialType.KEYBOARD)
        testScope.advanceTimeBy(LAUNCH_DELAY)
        notifyAndAssert(TutorialType.KEYBOARD)
    }

    @Test
    fun connectBothDevices_delayElapse_launchForBoth() =
        testScope.runTest {
    fun connectBothDevices_delayElapse_notifyForBoth() = runTestAndClear {
        keyboardRepository.setIsAnyKeyboardConnected(true)
        touchpadRepository.setIsAnyTouchpadConnected(true)
            advanceTimeBy(LAUNCH_DELAY)
        testScope.advanceTimeBy(LAUNCH_DELAY)

            launchAndAssert(TutorialType.BOTH)
        notifyAndAssert(TutorialType.BOTH)
    }

    @Test
    fun connectBothDevice_delayNotElapse_launchNothing() =
        testScope.runTest {
    fun connectBothDevice_delayNotElapse_notifyNothing() = runTestAndClear {
        keyboardRepository.setIsAnyKeyboardConnected(true)
        touchpadRepository.setIsAnyTouchpadConnected(true)
            advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
        testScope.advanceTimeBy(A_SHORT_PERIOD_OF_TIME)

            launchAndAssert(TutorialType.NONE)
        notifyAndAssert(TutorialType.NONE)
    }

    @Test
    fun nothingConnect_delayElapse_launchNothing() =
        testScope.runTest {
    fun nothingConnect_delayElapse_notifyNothing() = runTestAndClear {
        keyboardRepository.setIsAnyKeyboardConnected(false)
        touchpadRepository.setIsAnyTouchpadConnected(false)
            advanceTimeBy(LAUNCH_DELAY)
        testScope.advanceTimeBy(LAUNCH_DELAY)

            launchAndAssert(TutorialType.NONE)
        notifyAndAssert(TutorialType.NONE)
    }

    @Test
    fun connectKeyboard_thenTouchpad_delayElapse_launchForBoth() =
        testScope.runTest {
    fun connectKeyboard_thenTouchpad_delayElapse_notifyForBoth() = runTestAndClear {
        keyboardRepository.setIsAnyKeyboardConnected(true)
            advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
        testScope.advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
        touchpadRepository.setIsAnyTouchpadConnected(true)
            advanceTimeBy(REMAINING_TIME)
        testScope.advanceTimeBy(REMAINING_TIME)

            launchAndAssert(TutorialType.BOTH)
        notifyAndAssert(TutorialType.BOTH)
    }

    @Test
    fun connectKeyboard_thenTouchpad_removeKeyboard_delayElapse_launchNothing() =
        testScope.runTest {
    fun connectKeyboard_thenTouchpad_removeKeyboard_delayElapse_notifyNothing() = runTestAndClear {
        keyboardRepository.setIsAnyKeyboardConnected(true)
            advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
        testScope.advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
        touchpadRepository.setIsAnyTouchpadConnected(true)
        keyboardRepository.setIsAnyKeyboardConnected(false)
            advanceTimeBy(REMAINING_TIME)
            launchAndAssert(TutorialType.NONE)
        testScope.advanceTimeBy(REMAINING_TIME)

        notifyAndAssert(TutorialType.NONE)
    }

    private fun runTestAndClear(block: suspend () -> Unit) =
        testScope.runTest {
            try {
                block()
            } finally {
                schedulerRepository.clear()
            }
        }

    private suspend fun launchAndAssert(expectedTutorial: TutorialType) =
    private fun notifyAndAssert(expectedTutorial: TutorialType) =
        testScope.backgroundScope.launch {
            val actualTutorial = underTest.tutorials.first()
            assertThat(actualTutorial).isEqualTo(expectedTutorial)

            // TODO: need to update after we move launch into the tutorial
            when (expectedTutorial) {
                TutorialType.KEYBOARD -> {
                    assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isTrue()
                    assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isFalse()
                    assertThat(schedulerRepository.isNotified(DeviceType.KEYBOARD)).isTrue()
                    assertThat(schedulerRepository.isNotified(DeviceType.TOUCHPAD)).isFalse()
                }
                TutorialType.TOUCHPAD -> {
                    assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isFalse()
                    assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isTrue()
                    assertThat(schedulerRepository.isNotified(DeviceType.KEYBOARD)).isFalse()
                    assertThat(schedulerRepository.isNotified(DeviceType.TOUCHPAD)).isTrue()
                }
                TutorialType.BOTH -> {
                    assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isTrue()
                    assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isTrue()
                    assertThat(schedulerRepository.isNotified(DeviceType.KEYBOARD)).isTrue()
                    assertThat(schedulerRepository.isNotified(DeviceType.TOUCHPAD)).isTrue()
                }
                TutorialType.NONE -> {
                    assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isFalse()
                    assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isFalse()
                    assertThat(schedulerRepository.isNotified(DeviceType.KEYBOARD)).isFalse()
                    assertThat(schedulerRepository.isNotified(DeviceType.TOUCHPAD)).isFalse()
                }
            }
        }
+3 −2
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.education.domain.interactor

import android.os.SystemProperties
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.contextualeducation.GestureType
@@ -56,7 +57,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.merge
import com.android.app.tracing.coroutines.launchTraced as launch

/** Allow listening to new contextual education triggered */
@SysUISingleton
@@ -278,7 +278,8 @@ constructor(
        }

    private suspend fun hasInitialDelayElapsed(deviceType: DeviceType): Boolean {
        val oobeLaunchTime = tutorialRepository.launchTime(deviceType) ?: return false
        val oobeLaunchTime =
            tutorialRepository.getScheduledTutorialLaunchTime(deviceType) ?: return false
        return clock
            .instant()
            .isAfter(oobeLaunchTime.plusSeconds(initialDelayDuration.inWholeSeconds))
+6 −3
Original line number Diff line number Diff line
@@ -20,14 +20,17 @@ import java.time.Instant

data class DeviceSchedulerInfo(
    var launchTime: Instant? = null,
    var firstConnectionTime: Instant? = null
    var firstConnectionTime: Instant? = null,
    var isNotified: Boolean = false,
) {
    constructor(
        launchTimeSec: Long?,
        firstConnectionTimeSec: Long?
        firstConnectionTimeSec: Long?,
        isNotified: Boolean = false,
    ) : this(
        launchTimeSec?.let { Instant.ofEpochSecond(it) },
        firstConnectionTimeSec?.let { Instant.ofEpochSecond(it) }
        firstConnectionTimeSec?.let { Instant.ofEpochSecond(it) },
        isNotified,
    )

    val wasEverConnected: Boolean
Loading