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

Commit ac0894d0 authored by helencheuk's avatar helencheuk
Browse files

[Contextual Edu] Launch tutorial for specific gesture in education notification

Launch specific gesture tutorial when user clicks notification
For overview, it would open the generic touchpad tutorial because OOBE only has tutorial for back, home and all apps.

Bug: 381513411
Test: ContextualEduUiCoordinatorTest
Flag: com.android.systemui.keyboard_touchpad_contextual_education

Change-Id: I2c11011cffebca5f4f2d142f2548f606b95ce700
parent 878f510c
Loading
Loading
Loading
Loading
+67 −15
Original line number Diff line number Diff line
@@ -20,18 +20,20 @@ import android.app.Dialog
import android.app.Notification
import android.app.NotificationManager
import android.content.applicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
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.contextualeducation.GestureType.HOME
import com.android.systemui.contextualeducation.GestureType.OVERVIEW
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
import com.android.systemui.education.ui.view.ContextualEduUiCoordinator
import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
@@ -56,11 +58,13 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters

@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWith(ParameterizedAndroidJunit4::class)
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class ContextualEduUiCoordinatorTest : SysuiTestCase() {
class ContextualEduUiCoordinatorTest(private val gestureType: GestureType) : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val interactor = kosmos.contextualEducationInteractor
@@ -112,23 +116,23 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
    @Test
    fun showDialogOnNewEdu() =
        testScope.runTest {
            triggerEducation(BACK)
            triggerEducation(gestureType)
            verify(dialog).show()
        }

    @Test
    fun showNotificationOn2ndEdu() =
        testScope.runTest {
            triggerEducation(BACK)
            triggerEducation(gestureType)
            eduClock.offset(minDurationForNextEdu)
            triggerEducation(BACK)
            triggerEducation(gestureType)
            verify(notificationManager).notifyAsUser(any(), anyInt(), any(), any())
        }

    @Test
    fun dismissDialogAfterTimeout() =
        testScope.runTest {
            triggerEducation(BACK)
            triggerEducation(gestureType)
            advanceTimeBy(timeoutMillis + 1)
            verify(dialog).dismiss()
        }
@@ -142,26 +146,59 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
        }

    @Test
    fun verifyBackEduToastContent() =
    fun verifyEduToastContent() =
        testScope.runTest {
            triggerEducation(BACK)
            assertThat(toastContent).isEqualTo(context.getString(R.string.back_edu_toast_content))
            triggerEducation(gestureType)

            val expectedContent =
                when (gestureType) {
                    BACK -> R.string.back_edu_toast_content
                    HOME -> R.string.home_edu_toast_content
                    OVERVIEW -> R.string.overview_edu_toast_content
                    ALL_APPS -> R.string.all_apps_edu_toast_content
                }

            assertThat(toastContent).isEqualTo(context.getString(expectedContent))
        }

    @Test
    fun verifyBackEduNotificationContent() =
    fun verifyEduNotificationContent() =
        testScope.runTest {
            val notificationCaptor = ArgumentCaptor.forClass(Notification::class.java)
            triggerEducation(BACK)
            triggerEducation(gestureType)

            eduClock.offset(minDurationForNextEdu)
            triggerEducation(BACK)
            triggerEducation(gestureType)

            verify(notificationManager)
                .notifyAsUser(any(), anyInt(), notificationCaptor.capture(), any())

            val expectedTitle =
                when (gestureType) {
                    BACK -> R.string.back_edu_notification_title
                    HOME -> R.string.home_edu_notification_title
                    OVERVIEW -> R.string.overview_edu_notification_title
                    ALL_APPS -> R.string.all_apps_edu_notification_title
                }

            val expectedContent =
                when (gestureType) {
                    BACK -> R.string.back_edu_notification_content
                    HOME -> R.string.home_edu_notification_content
                    OVERVIEW -> R.string.overview_edu_notification_content
                    ALL_APPS -> R.string.all_apps_edu_notification_content
                }

            val expectedTutorialClassName =
                when (gestureType) {
                    OVERVIEW -> TUTORIAL_ACTION
                    else -> KeyboardTouchpadTutorialActivity::class.qualifiedName
                }

            verifyNotificationContent(
                R.string.back_edu_notification_title,
                R.string.back_edu_notification_content,
                expectedTitle,
                expectedContent,
                expectedTutorialClassName,
                notificationCaptor.value,
            )
        }
@@ -169,6 +206,7 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
    private fun verifyNotificationContent(
        titleResId: Int,
        contentResId: Int,
        expectedTutorialClassName: String?,
        notification: Notification,
    ) {
        val expectedContent = context.getString(contentResId)
@@ -177,6 +215,10 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
        val actualTitle = notification.getString(Notification.EXTRA_TITLE)
        assertThat(actualContent).isEqualTo(expectedContent)
        assertThat(actualTitle).isEqualTo(expectedTitle)
        val actualTutorialClassName =
            notification.contentIntent.intent.component?.className
                ?: notification.contentIntent.intent.action
        assertThat(actualTutorialClassName).isEqualTo(expectedTutorialClassName)
    }

    private fun Notification.getString(key: String): String =
@@ -188,4 +230,14 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
        }
        runCurrent()
    }

    companion object {
        @JvmStatic
        @Parameters(name = "{0}")
        fun getGestureTypes(): List<GestureType> {
            return listOf(BACK, HOME, OVERVIEW, ALL_APPS)
        }

        private const val TUTORIAL_ACTION: String = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
    }
}
+37 −10
Original line number Diff line number Diff line
@@ -29,6 +29,11 @@ import android.view.accessibility.AccessibilityManager
import androidx.core.app.NotificationCompat
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
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.contextualeducation.GestureType.HOME
import com.android.systemui.contextualeducation.GestureType.OVERVIEW
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.education.ui.viewmodel.ContextualEduNotificationViewModel
@@ -37,6 +42,10 @@ import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_ENTRY_POINT_CONTEXTUAL_EDU
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_ENTRY_POINT_KEY
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_KEY
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_KEYBOARD
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_TOUCHPAD_BACK
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_TOUCHPAD_HOME
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -59,6 +68,8 @@ constructor(
        private const val CHANNEL_ID = "ContextualEduNotificationChannel"
        private const val TAG = "ContextualEduUiCoordinator"
        private const val NOTIFICATION_ID = 1000
        private const val TUTORIAL_ACTION: String = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
        private const val SYSTEMUI_PACKAGE_NAME: String = "com.android.systemui"
    }

    @Inject
@@ -125,7 +136,7 @@ constructor(
                .setSmallIcon(R.drawable.ic_settings)
                .setContentTitle(model.title)
                .setContentText(model.message)
                .setContentIntent(createPendingIntent())
                .setContentIntent(createPendingIntent(model.gestureType))
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true)
                .addExtras(extras)
@@ -138,21 +149,37 @@ constructor(
        )
    }

    private fun createPendingIntent(): PendingIntent {
    private fun createPendingIntent(gestureType: GestureType): PendingIntent {
        val intent =
            Intent(context, KeyboardTouchpadTutorialActivity::class.java).apply {
                addCategory(Intent.CATEGORY_DEFAULT)
                flags = Intent.FLAG_ACTIVITY_NEW_TASK
                putExtra(
                    INTENT_TUTORIAL_ENTRY_POINT_KEY,
                    INTENT_TUTORIAL_ENTRY_POINT_CONTEXTUAL_EDU,
                )
            when (gestureType) {
                BACK -> createKeyboardTouchpadTutorialIntent(INTENT_TUTORIAL_SCOPE_TOUCHPAD_BACK)
                HOME -> createKeyboardTouchpadTutorialIntent(INTENT_TUTORIAL_SCOPE_TOUCHPAD_HOME)
                ALL_APPS -> createKeyboardTouchpadTutorialIntent(INTENT_TUTORIAL_SCOPE_KEYBOARD)
                OVERVIEW -> createTouchpadTutorialIntent()
            }

        return PendingIntent.getActivity(
            context,
            /* requestCode= */ 0,
            intent,
            PendingIntent.FLAG_IMMUTABLE,
            // FLAG_UPDATE_CURRENT to avoid caching of intent extras and always use latest values
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
        )
    }

    private fun createKeyboardTouchpadTutorialIntent(tutorialType: String): Intent {
        return Intent(context, KeyboardTouchpadTutorialActivity::class.java).apply {
            addCategory(Intent.CATEGORY_DEFAULT)
            flags = Intent.FLAG_ACTIVITY_NEW_TASK
            putExtra(INTENT_TUTORIAL_SCOPE_KEY, tutorialType)
            putExtra(INTENT_TUTORIAL_ENTRY_POINT_KEY, INTENT_TUTORIAL_ENTRY_POINT_CONTEXTUAL_EDU)
        }
    }

    private fun createTouchpadTutorialIntent(): Intent {
        return Intent(TUTORIAL_ACTION).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK
            setPackage(SYSTEMUI_PACKAGE_NAME)
        }
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -16,11 +16,14 @@

package com.android.systemui.education.ui.viewmodel

import com.android.systemui.contextualeducation.GestureType

sealed class ContextualEduContentViewModel(open val userId: Int)

data class ContextualEduNotificationViewModel(
    val title: String,
    val message: String,
    val gestureType: GestureType,
    override val userId: Int,
) : ContextualEduContentViewModel(userId)

+1 −0
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ constructor(
                    ContextualEduNotificationViewModel(
                        getEduTitle(it),
                        getEduContent(it),
                        it.gestureType,
                        it.userId,
                    )
                } else {