summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author yyalan <yyalan@google.com> 2025-02-12 18:53:19 +0000
committer yyalan <yyalan@google.com> 2025-02-14 14:31:26 +0000
commit096c1cc8902d1822efdef9a9fd8c71ac55e45ab9 (patch)
treebd33c1716f4c64f26dbaf91a23d471765c5dbbfe
parenteceea7f395c4da8fb309ce173ed80bec6e3b1295 (diff)
[OOBE] Update the notification
Handle cases like: show keyboard notification then a touchpad, and the notification should be updated for both Fixes: 394779964 Flag: com.android.systemui.shared.new_touchpad_gestures_tutorial Test: TutorialNotificationCoordinatorTest.kt Change-Id: Icce3e9ddc77e2c570eb41f8ed981494ea8210f36
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt80
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt8
2 files changed, 65 insertions, 23 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
index ff5fa3959c6d..7374f181760c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
@@ -37,7 +37,6 @@ 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.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -51,7 +50,6 @@ import org.mockito.Mockito.times
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
-import org.mockito.kotlin.firstValue
import org.mockito.kotlin.never
import org.mockito.kotlin.secondValue
import org.mockito.kotlin.verify
@@ -116,7 +114,6 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {
fun showTouchpadNotification() = runTestAndClear {
touchpadRepository.setIsAnyTouchpadConnected(true)
testScope.advanceTimeBy(LAUNCH_DELAY)
- mockExistingNotification()
verifyNotification(
R.string.launch_touchpad_tutorial_notification_title,
R.string.launch_touchpad_tutorial_notification_content,
@@ -142,14 +139,10 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {
}
@Test
- fun showKeyboardNotificationThenDisconnectKeyboard() = runTestAndClear {
+ fun cancelKeyboardNotificationWhenKeyboardDisconnects() = runTestAndClear {
keyboardRepository.setIsAnyKeyboardConnected(true)
testScope.advanceTimeBy(LAUNCH_DELAY)
- verifyNotification(
- R.string.launch_keyboard_tutorial_notification_title,
- R.string.launch_keyboard_tutorial_notification_content,
- )
- mockExistingNotification()
+ mockNotifications(hasTutorialNotification = true)
// After the keyboard is disconnected, i.e. there is nothing connected, the notification
// should be cancelled
@@ -158,22 +151,71 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {
}
@Test
- fun showKeyboardTouchpadNotificationThenDisconnectKeyboard() = runTestAndClear {
+ fun updateNotificationToTouchpadOnlyWhenKeyboardDisconnects() = runTestAndClear {
keyboardRepository.setIsAnyKeyboardConnected(true)
touchpadRepository.setIsAnyTouchpadConnected(true)
testScope.advanceTimeBy(LAUNCH_DELAY)
- mockExistingNotification()
+ mockNotifications(hasTutorialNotification = true)
+
keyboardRepository.setIsAnyKeyboardConnected(false)
verify(notificationManager, times(2))
.notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), notificationCaptor.capture(), any())
- // Connect both device and the first notification is for both
- notificationCaptor.firstValue.verify(
+ // Connect both device and the first notification is for both. After the keyboard is
+ // disconnected, i.e. with only the touchpad left, the notification should be update to
+ // touchpad only
+ notificationCaptor.secondValue.verify(
+ R.string.launch_touchpad_tutorial_notification_title,
+ R.string.launch_touchpad_tutorial_notification_content,
+ )
+ }
+
+ @Test
+ fun updateNotificationToBothDevicesWhenTouchpadConnects() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+ mockNotifications(hasTutorialNotification = true)
+
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+
+ verify(notificationManager, times(2))
+ .notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), notificationCaptor.capture(), any())
+ // Update the notification from keyboard to both devices
+ notificationCaptor.secondValue.verify(
R.string.launch_keyboard_touchpad_tutorial_notification_title,
R.string.launch_keyboard_touchpad_tutorial_notification_content,
)
- // After the keyboard is disconnected, i.e. with only the touchpad left, the notification
- // should be update to the one for only touchpad
+ }
+
+ @Test
+ fun doNotShowUpdateNotificationWhenInitialNotificationIsDismissed() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+ mockNotifications(hasTutorialNotification = false)
+
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+
+ // There's only one notification being shown throughout this scenario. We don't update the
+ // notification because it has been dismissed when the touchpad connects
+ verifyNotification(
+ R.string.launch_keyboard_tutorial_notification_title,
+ R.string.launch_keyboard_tutorial_notification_content,
+ )
+ }
+
+ @Test
+ fun showTouchpadNotificationAfterDelayAndKeyboardNotificationIsDismissed() = runTestAndClear {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+ mockNotifications(hasTutorialNotification = false)
+
+ touchpadRepository.setIsAnyTouchpadConnected(true)
+ testScope.advanceTimeBy(LAUNCH_DELAY)
+
+ verify(notificationManager, times(2))
+ .notifyAsUser(eq(TAG), eq(NOTIFICATION_ID), notificationCaptor.capture(), any())
+ // The keyboard notification was shown and dismissed; the touchpad notification is scheduled
+ // independently
notificationCaptor.secondValue.verify(
R.string.launch_touchpad_tutorial_notification_title,
R.string.launch_touchpad_tutorial_notification_content,
@@ -189,10 +231,12 @@ class TutorialNotificationCoordinatorTest : SysuiTestCase() {
}
}
- // Assume that there's an existing notification when the updater checks activeNotifications
- private fun mockExistingNotification() {
+ // Mock an active notification, so when the updater checks activeNotifications, it returns one
+ // with the given id. Otherwise, return an empty array (i.e. no active notifications)
+ private fun mockNotifications(hasTutorialNotification: Boolean) {
whenever(notification.id).thenReturn(NOTIFICATION_ID)
- whenever(notificationManager.activeNotifications).thenReturn(arrayOf(notification))
+ val notifications = if (hasTutorialNotification) arrayOf(notification) else emptyArray()
+ whenever(notificationManager.activeNotifications).thenReturn(notifications)
}
private fun verifyNotification(@StringRes titleResId: Int, @StringRes contentResId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
index b712fdeaf623..0d57a57c3416 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
@@ -102,10 +102,9 @@ constructor(
}
// This flow is used by the notification updater once an initial notification is launched. It
- // listens to the device connection changes for both keyboard and touchpad. When either of the
- // device is disconnected, resolve the tutorial type base on the latest connection state.
- // Dropping the initial state because it's the existing notification. Filtering out BOTH because
- // we only care about disconnections.
+ // listens to the changes of keyboard and touchpad connection and resolve the tutorial type base
+ // on the latest connection state.
+ // Dropping the initial state because it represents the existing notification.
val tutorialTypeUpdates: Flow<TutorialType> =
keyboardRepository.isAnyKeyboardConnected
.combine(touchpadRepository.isAnyTouchpadConnected, ::Pair)
@@ -118,7 +117,6 @@ constructor(
}
}
.drop(1)
- .filter { it != TutorialType.BOTH }
private suspend fun waitForDeviceConnection(deviceType: DeviceType) =
isAnyDeviceConnected[deviceType]!!.filter { it }.first()