Loading packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt +62 −18 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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, Loading @@ -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 Loading @@ -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, Loading @@ -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) { Loading packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt +3 −5 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -118,7 +117,6 @@ constructor( } } .drop(1) .filter { it != TutorialType.BOTH } private suspend fun waitForDeviceConnection(deviceType: DeviceType) = isAnyDeviceConnected[deviceType]!!.filter { it }.first() Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt +62 −18 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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, Loading @@ -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 Loading @@ -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, Loading @@ -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) { Loading
packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt +3 −5 Original line number Diff line number Diff line Loading @@ -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) Loading @@ -118,7 +117,6 @@ constructor( } } .drop(1) .filter { it != TutorialType.BOTH } private suspend fun waitForDeviceConnection(deviceType: DeviceType) = isAnyDeviceConnected[deviceType]!!.filter { it }.first() Loading