Loading packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt +67 −15 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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() } Loading @@ -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, ) } Loading @@ -169,6 +206,7 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() { private fun verifyNotificationContent( titleResId: Int, contentResId: Int, expectedTutorialClassName: String?, notification: Notification, ) { val expectedContent = context.getString(contentResId) Loading @@ -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 = Loading @@ -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" } } packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt +37 −10 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) } } } packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt +3 −0 Original line number Diff line number Diff line Loading @@ -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) Loading packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt +1 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,7 @@ constructor( ContextualEduNotificationViewModel( getEduTitle(it), getEduContent(it), it.gestureType, it.userId, ) } else { Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt +67 −15 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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() } Loading @@ -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, ) } Loading @@ -169,6 +206,7 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() { private fun verifyNotificationContent( titleResId: Int, contentResId: Int, expectedTutorialClassName: String?, notification: Notification, ) { val expectedContent = context.getString(contentResId) Loading @@ -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 = Loading @@ -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" } }
packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt +37 −10 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) } } }
packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt +3 −0 Original line number Diff line number Diff line Loading @@ -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) Loading
packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt +1 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,7 @@ constructor( ContextualEduNotificationViewModel( getEduTitle(it), getEduContent(it), it.gestureType, it.userId, ) } else { Loading