diff options
5 files changed, 453 insertions, 155 deletions
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt index 9c9e9b009..1678ccced 100644 --- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt +++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt @@ -44,7 +44,6 @@ import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.dis import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.reportSafetySourceErrorWithPermission import com.android.safetycenter.testing.SafetyCenterFlags import com.android.safetycenter.testing.SafetyCenterTestConfigs -import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_1 import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_2 import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_5 @@ -68,6 +67,7 @@ import kotlinx.coroutines.TimeoutCancellationException import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.rules.TestName import org.junit.runner.RunWith /** Notification-related tests for [SafetyCenterManager]. */ @@ -81,6 +81,7 @@ class SafetyCenterNotificationTest { requireNotNull(context.getSystemService(SafetyCenterManager::class.java)) { "Could not get SafetyCenterManager" } + private var uniqueSafetySourceId: String = "" @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context) @get:Rule(order = 2) @@ -88,17 +89,23 @@ class SafetyCenterNotificationTest { SafetyCenterTestRule(safetyCenterTestHelper, withNotifications = true) @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule() @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule() + @get:Rule(order = 5) val testNameRule = TestName() @Before fun enableNotificationsForTestSourceBeforeTest() { SafetyCenterFlags.notificationsEnabled = true - setFlagsForImmediateNotifications(SINGLE_SOURCE_ID) - safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig) + uniqueSafetySourceId = testNameRule.methodName + setFlagsForImmediateNotifications(uniqueSafetySourceId) + safetyCenterTestHelper.setConfig( + safetyCenterTestConfigs.singleSourceConfig( + safetyCenterTestConfigs.dynamicSafetySourceBuilder(uniqueSafetySourceId).build() + ) + ) } @Test fun setSafetySourceData_withNoIssue_noNotification() { - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.information) + safetyCenterTestHelper.setData(uniqueSafetySourceId, safetySourceTestData.information) TestNotificationListener.waitForZeroNotifications() } @@ -108,7 +115,7 @@ class SafetyCenterNotificationTest { SafetyCenterFlags.immediateNotificationBehaviorIssues = emptySet() safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, + uniqueSafetySourceId, safetySourceTestData.recommendationWithAccountIssue ) @@ -120,7 +127,7 @@ class SafetyCenterNotificationTest { SafetyCenterFlags.notificationsAllowedSources = emptySet() safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, + uniqueSafetySourceId, safetySourceTestData.recommendationWithAccountIssue ) @@ -132,7 +139,7 @@ class SafetyCenterNotificationTest { SafetyCenterFlags.notificationsEnabled = false safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, + uniqueSafetySourceId, safetySourceTestData.recommendationWithAccountIssue ) @@ -153,7 +160,7 @@ class SafetyCenterNotificationTest { ) .build() - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data) TestNotificationListener.waitForZeroNotifications() } @@ -173,7 +180,7 @@ class SafetyCenterNotificationTest { ) .build() - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data) TestNotificationListener.waitForZeroNotifications() } @@ -204,7 +211,7 @@ class SafetyCenterNotificationTest { .addIssue(neverNotifyIssue) .build() - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data1) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data1) TestNotificationListener.waitForZeroNotifications() SafetyCenterFlags.notificationsMinDelay = TIMEOUT_SHORT @@ -212,13 +219,14 @@ class SafetyCenterNotificationTest { // Sending new data causes Safety Center to recompute which issues to send notifications // about and this should now include the delayed issue sent in data1 above. Thread.sleep(TIMEOUT_SHORT.toMillis()) - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data2) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data2) TestNotificationListener.waitForSingleNotificationMatching( NotificationCharacteristics( title = "Notify later", text = "This is not urgent.", - actions = listOf("See issue") + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, ) ) } @@ -239,13 +247,14 @@ class SafetyCenterNotificationTest { ) .build() - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data) TestNotificationListener.waitForSingleNotificationMatching( NotificationCharacteristics( title = "Notify immediately", text = "This is urgent!", - actions = listOf("See issue") + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, ) ) } @@ -267,29 +276,31 @@ class SafetyCenterNotificationTest { ) .build() - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data) TestNotificationListener.waitForSingleNotificationMatching( NotificationCharacteristics( title = "Notify immediately", text = "This is urgent!", - actions = listOf("See issue") + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, ) ) } @Test fun setSafetySourceData_withNotificationsAllowedForSourceByFlag_sendsNotification() { - SafetyCenterFlags.notificationsAllowedSources = setOf(SINGLE_SOURCE_ID) + SafetyCenterFlags.notificationsAllowedSources = setOf(uniqueSafetySourceId) val data = safetySourceTestData.recommendationWithAccountIssue - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data) TestNotificationListener.waitForSingleNotificationMatching( NotificationCharacteristics( title = "Recommendation issue title", text = "Recommendation issue summary", - actions = listOf("See issue") + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, ) ) } @@ -316,13 +327,14 @@ class SafetyCenterNotificationTest { ) .build() - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data) TestNotificationListener.waitForSingleNotificationMatching( NotificationCharacteristics( title = "Recommendation issue title", text = "Recommendation issue summary", - actions = listOf("Action 1", "Action 2") + actions = listOf("Action 1", "Action 2"), + safetySourceId = uniqueSafetySourceId, ) ) } @@ -355,7 +367,12 @@ class SafetyCenterNotificationTest { safetyCenterTestHelper.setData("MyNotifiableSource", data) - TestNotificationListener.waitForSingleNotification() + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("See issue"), + safetySourceId = "MyNotifiableSource", + ) + ) } @Test @@ -393,13 +410,14 @@ class SafetyCenterNotificationTest { ) .build() - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data) TestNotificationListener.waitForSingleNotificationMatching( NotificationCharacteristics( title = "Custom title", text = "Custom text", - actions = listOf("Custom action 1", "Custom action 2") + actions = listOf("Custom action 1", "Custom action 2"), + safetySourceId = uniqueSafetySourceId, ) ) } @@ -431,13 +449,14 @@ class SafetyCenterNotificationTest { ) .build() - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data) TestNotificationListener.waitForSingleNotificationMatching( NotificationCharacteristics( title = "Custom title", text = "Custom text", - actions = emptyList() + actions = emptyList(), + safetySourceId = uniqueSafetySourceId, ) ) } @@ -454,14 +473,15 @@ class SafetyCenterNotificationTest { ) .build() - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data1) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data1) val initialNotification = TestNotificationListener.waitForSingleNotificationMatching( NotificationCharacteristics( title = "Initial", text = "Blah", - actions = listOf("See issue") + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, ) ) @@ -485,14 +505,15 @@ class SafetyCenterNotificationTest { ) .build() - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data2) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data2) val revisedNotification = TestNotificationListener.waitForSingleNotificationMatching( NotificationCharacteristics( title = "Revised", text = "Different", - actions = listOf("See issue", "New action") + actions = listOf("See issue", "New action"), + safetySourceId = uniqueSafetySourceId, ) ) assertThat(initialNotification.statusBarNotification.key) @@ -503,11 +524,16 @@ class SafetyCenterNotificationTest { fun setSafetySourceData_twiceWithExactSameIssue_doNotNotifyTwice() { val data = safetySourceTestData.recommendationWithAccountIssue - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data) - TestNotificationListener.waitForSingleNotification() + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data) TestNotificationListener.waitForZeroNotificationEvents() } @@ -517,11 +543,16 @@ class SafetyCenterNotificationTest { val data1 = safetySourceTestData.recommendationWithAccountIssue val data2 = safetySourceTestData.information - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data1) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data1) - TestNotificationListener.waitForSingleNotification() + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data2) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data2) TestNotificationListener.waitForZeroNotifications() } @@ -532,12 +563,17 @@ class SafetyCenterNotificationTest { val data1 = safetySourceTestData.recommendationWithAccountIssue val data2 = safetySourceTestData.information - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data1) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data1) - TestNotificationListener.waitForSingleNotification() + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) SafetyCenterFlags.notificationsEnabled = false - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data2) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data2) TestNotificationListener.waitForZeroNotificationEvents() } @@ -550,11 +586,16 @@ class SafetyCenterNotificationTest { SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build() ) - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data) - TestNotificationListener.waitForSingleNotification() + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) - safetyCenterManager.reportSafetySourceErrorWithPermission(SINGLE_SOURCE_ID, error) + safetyCenterManager.reportSafetySourceErrorWithPermission(uniqueSafetySourceId, error) TestNotificationListener.waitForZeroNotifications() } @@ -584,20 +625,21 @@ class SafetyCenterNotificationTest { ) .build() - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data1) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data1) val notificationWithChannel = TestNotificationListener.waitForSingleNotificationMatching( NotificationCharacteristics( title = "Initial", text = "Blah", - actions = listOf("See issue") + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, ) ) TestNotificationListener.cancelAndWait(notificationWithChannel.statusBarNotification.key) - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data2) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data2) TestNotificationListener.waitForZeroNotifications() } @@ -623,13 +665,24 @@ class SafetyCenterNotificationTest { .build() ) .build() - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data1) - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + safetyCenterTestHelper.setData(uniqueSafetySourceId, data1) + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) TestNotificationListener.cancelAndWait(notificationWithChannel.statusBarNotification.key) - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data2) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data2) - TestNotificationListener.waitForSingleNotification() + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) } @Test @@ -658,6 +711,7 @@ class SafetyCenterNotificationTest { "Critical issue title", "Critical issue summary", actions = listOf("Solve issue"), + safetySourceId = SOURCE_ID_5, ) ) } @@ -677,7 +731,13 @@ class SafetyCenterNotificationTest { ) ) - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("See issue"), + safetySourceId = SOURCE_ID_1, + ) + ) TestNotificationListener.cancelAndWait(notificationWithChannel.statusBarNotification.key) @@ -693,13 +753,17 @@ class SafetyCenterNotificationTest { "Critical issue title", "Critical issue summary", actions = listOf("Solve issue"), + safetySourceId = SOURCE_ID_5, ) ) } @Test fun setSafetySourceData_withInformationIssue_lowImportanceBlockableNotification() { - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.informationWithIssue) + safetyCenterTestHelper.setData( + uniqueSafetySourceId, + safetySourceTestData.informationWithIssue + ) TestNotificationListener.waitForNotificationsMatching( NotificationCharacteristics( @@ -707,7 +771,8 @@ class SafetyCenterNotificationTest { "Information issue summary", actions = listOf("Review"), importance = NotificationManager.IMPORTANCE_LOW, - blockable = true + blockable = true, + safetySourceId = uniqueSafetySourceId, ) ) } @@ -715,7 +780,7 @@ class SafetyCenterNotificationTest { @Test fun setSafetySourceData_withRecommendationIssue_defaultImportanceUnblockableNotification() { safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, + uniqueSafetySourceId, safetySourceTestData.recommendationWithAccountIssue ) @@ -725,7 +790,8 @@ class SafetyCenterNotificationTest { "Recommendation issue summary", importance = NotificationManager.IMPORTANCE_DEFAULT, actions = listOf("See issue"), - blockable = false + blockable = false, + safetySourceId = uniqueSafetySourceId, ) ) } @@ -733,8 +799,8 @@ class SafetyCenterNotificationTest { @Test fun setSafetySourceData_withCriticalIssue_highImportanceUnblockableNotification() { safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, - safetySourceTestData.criticalWithResolvingDeviceIssue + uniqueSafetySourceId, + safetySourceTestData.criticalWithResolvingDeviceIssue(sourceId = uniqueSafetySourceId) ) TestNotificationListener.waitForNotificationsMatching( @@ -743,7 +809,8 @@ class SafetyCenterNotificationTest { "Critical issue summary", actions = listOf("Solve issue"), importance = NotificationManager.IMPORTANCE_HIGH, - blockable = false + blockable = false, + safetySourceId = uniqueSafetySourceId, ) ) } @@ -751,15 +818,20 @@ class SafetyCenterNotificationTest { @Test fun dismissSafetyCenterIssue_dismissesNotification() { safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, + uniqueSafetySourceId, safetySourceTestData.recommendationWithAccountIssue ) - TestNotificationListener.waitForSingleNotification() + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) safetyCenterManager.dismissSafetyCenterIssueWithPermission( SafetyCenterTestData.issueId( - SINGLE_SOURCE_ID, + uniqueSafetySourceId, SafetySourceTestData.RECOMMENDATION_ISSUE_ID ) ) @@ -770,14 +842,20 @@ class SafetyCenterNotificationTest { @Test fun dismissingNotification_doesNotUpdateSafetyCenterData() { safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, - safetySourceTestData.criticalWithResolvingGeneralIssue + uniqueSafetySourceId, + safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = uniqueSafetySourceId) ) // Add the listener after setting the initial data so that we don't need to consume/receive // an update for that val listener = safetyCenterTestHelper.addListener() - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("Solve issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) TestNotificationListener.cancelAndWait(notificationWithChannel.statusBarNotification.key) @@ -813,6 +891,7 @@ class SafetyCenterNotificationTest { "Critical issue title", "Critical issue summary", actions = listOf("Solve issue"), + safetySourceId = SOURCE_ID_1, ) ) @@ -829,9 +908,14 @@ class SafetyCenterNotificationTest { fun clearSafetySourceData_cancelsAllNotifications() { val data = safetySourceTestData.recommendationWithAccountIssue - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data) + safetyCenterTestHelper.setData(uniqueSafetySourceId, data) - TestNotificationListener.waitForSingleNotification() + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) safetyCenterManager.clearAllSafetySourceDataForTestsWithPermission() @@ -841,15 +925,21 @@ class SafetyCenterNotificationTest { @Test fun sendActionPendingIntent_successful_updatesListener() { safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, - safetySourceTestData.criticalWithResolvingGeneralIssue + uniqueSafetySourceId, + safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = uniqueSafetySourceId) ) - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("Solve issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) val action = notificationWithChannel.statusBarNotification.notification.actions.firstOrNull() checkNotNull(action) { "Notification action unexpectedly null" } SafetySourceReceiver.setResponse( - Request.ResolveAction(SINGLE_SOURCE_ID), + Request.ResolveAction(uniqueSafetySourceId), Response.SetData(safetySourceTestData.information) ) val listener = safetyCenterTestHelper.addListener() @@ -867,15 +957,21 @@ class SafetyCenterNotificationTest { @Test fun sendActionPendingIntent_successfulNoSuccessMessage_removesNotification() { safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, - safetySourceTestData.criticalWithResolvingGeneralIssue + uniqueSafetySourceId, + safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = uniqueSafetySourceId) ) - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("Solve issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) val action = notificationWithChannel.statusBarNotification.notification.actions.firstOrNull() checkNotNull(action) { "Notification action unexpectedly null" } SafetySourceReceiver.setResponse( - Request.ResolveAction(SINGLE_SOURCE_ID), + Request.ResolveAction(uniqueSafetySourceId), Response.SetData(safetySourceTestData.information) ) @@ -887,40 +983,61 @@ class SafetyCenterNotificationTest { @Test fun sendActionPendingIntent_successfulWithSuccessMessage_successNotification() { safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, - safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage + uniqueSafetySourceId, + safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage( + sourceId = uniqueSafetySourceId + ) ) - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("Solve issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) val action = notificationWithChannel.statusBarNotification.notification.actions.firstOrNull() checkNotNull(action) { "Notification action unexpectedly null" } SafetySourceReceiver.setResponse( - Request.ResolveAction(SINGLE_SOURCE_ID), + Request.ResolveAction(uniqueSafetySourceId), Response.SetData(safetySourceTestData.information) ) sendActionPendingIntentAndWaitWithPermission(action) - TestNotificationListener.waitForSuccessNotification("Issue solved") + TestNotificationListener.waitForSuccessNotification("Issue solved") { + assertThat(NotificationCharacteristics.safetySourceIdMatches(it, uniqueSafetySourceId)) + .isTrue() + } } @Test fun successNotification_notificationHasAutoCancel() { safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, - safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage + uniqueSafetySourceId, + safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage( + sourceId = uniqueSafetySourceId + ) ) - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("Solve issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) val action = notificationWithChannel.statusBarNotification.notification.actions.firstOrNull() checkNotNull(action) { "Notification action unexpectedly null" } SafetySourceReceiver.setResponse( - Request.ResolveAction(SINGLE_SOURCE_ID), + Request.ResolveAction(uniqueSafetySourceId), Response.SetData(safetySourceTestData.information) ) sendActionPendingIntentAndWaitWithPermission(action) TestNotificationListener.waitForSuccessNotification("Issue solved") { + assertThat(NotificationCharacteristics.safetySourceIdMatches(it, uniqueSafetySourceId)) + .isTrue() assertThat(it.hasAutoCancel()).isTrue() } } @@ -929,15 +1046,23 @@ class SafetyCenterNotificationTest { @Test fun sendActionPendingIntent_flagDisabled_pendingIntentNotSentToSource() { safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, - safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage + uniqueSafetySourceId, + safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage( + sourceId = uniqueSafetySourceId + ) ) - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("Solve issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) val action = notificationWithChannel.statusBarNotification.notification.actions.firstOrNull() checkNotNull(action) { "Notification action unexpectedly null" } SafetySourceReceiver.setResponse( - Request.ResolveAction(SINGLE_SOURCE_ID), + Request.ResolveAction(uniqueSafetySourceId), Response.SetData(safetySourceTestData.information) ) SafetyCenterFlags.notificationsEnabled = false @@ -950,15 +1075,23 @@ class SafetyCenterNotificationTest { @Test fun sendActionPendingIntent_sourceStateChangedSafetyEvent_successNotification() { safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, - safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage + uniqueSafetySourceId, + safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage( + sourceId = uniqueSafetySourceId + ) ) - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("Solve issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) val action = notificationWithChannel.statusBarNotification.notification.actions.firstOrNull() checkNotNull(action) { "Notification action unexpectedly null" } SafetySourceReceiver.setResponse( - Request.ResolveAction(SINGLE_SOURCE_ID), + Request.ResolveAction(uniqueSafetySourceId), Response.SetData( safetySourceTestData.information, overrideSafetyEvent = @@ -968,7 +1101,10 @@ class SafetyCenterNotificationTest { sendActionPendingIntentAndWaitWithPermission(action) - TestNotificationListener.waitForSuccessNotification("Issue solved") + TestNotificationListener.waitForSuccessNotification("Issue solved") { + assertThat(NotificationCharacteristics.safetySourceIdMatches(it, uniqueSafetySourceId)) + .isTrue() + } } @Test @@ -981,6 +1117,7 @@ class SafetyCenterNotificationTest { "notification_action_id", "Solve now!", safetySourceTestData.resolvingActionPendingIntent( + sourceId = uniqueSafetySourceId, sourceIssueActionId = "notification_action_id" ) ) @@ -1002,6 +1139,7 @@ class SafetyCenterNotificationTest { "issue_action_id", "Default action", safetySourceTestData.resolvingActionPendingIntent( + sourceId = uniqueSafetySourceId, sourceIssueActionId = "issue_action_id" ) ) @@ -1017,19 +1155,28 @@ class SafetyCenterNotificationTest { ) .build() - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data) - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + safetyCenterTestHelper.setData(uniqueSafetySourceId, data) + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("Solve now!"), + safetySourceId = uniqueSafetySourceId, + ) + ) val action = notificationWithChannel.statusBarNotification.notification.actions.firstOrNull() checkNotNull(action) { "Notification action unexpectedly null" } SafetySourceReceiver.setResponse( - Request.ResolveAction(SINGLE_SOURCE_ID), + Request.ResolveAction(uniqueSafetySourceId), Response.SetData(safetySourceTestData.information) ) sendActionPendingIntentAndWaitWithPermission(action) - TestNotificationListener.waitForSuccessNotification("Solved via notification action :)") + TestNotificationListener.waitForSuccessNotification("Solved via notification action :)") { + assertThat(NotificationCharacteristics.safetySourceIdMatches(it, uniqueSafetySourceId)) + .isTrue() + } } @Test @@ -1037,14 +1184,23 @@ class SafetyCenterNotificationTest { // Here we cause a notification with an action to be posted and prepare the fake receiver // to resolve that action successfully. safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, - safetySourceTestData.criticalWithResolvingGeneralIssue + uniqueSafetySourceId, + safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = uniqueSafetySourceId) ) - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("Solve issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) val action = notificationWithChannel.statusBarNotification.notification.actions.firstOrNull() checkNotNull(action) { "Notification action unexpectedly null" } - SafetySourceReceiver.setResponse(Request.ResolveAction(SINGLE_SOURCE_ID), Response.Error) + SafetySourceReceiver.setResponse( + Request.ResolveAction(uniqueSafetySourceId), + Response.Error + ) val listener = safetyCenterTestHelper.addListener() sendActionPendingIntentAndWaitWithPermission(action) @@ -1056,16 +1212,27 @@ class SafetyCenterNotificationTest { assertThat(listenerData2.inFlightActions).isEmpty() assertThat(listenerData2.status.severityLevel) .isEqualTo(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING) - TestNotificationListener.waitForSingleNotification() + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("Solve issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) } @Test fun sendContentPendingIntent_singleIssue_opensSafetyCenterWithIssueVisible() { safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, + uniqueSafetySourceId, safetySourceTestData.recommendationWithDeviceIssue ) - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) sendContentPendingIntent(notificationWithChannel) { waitSourceIssueDisplayed(safetySourceTestData.recommendationDeviceIssue) @@ -1082,9 +1249,15 @@ class SafetyCenterNotificationTest { ) safetyCenterTestHelper.setData( SOURCE_ID_2, - safetySourceTestData.criticalWithResolvingGeneralIssue + safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = SOURCE_ID_2) ) - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("See issue"), + safetySourceId = SOURCE_ID_1, + ) + ) sendContentPendingIntent(notificationWithChannel) { waitSourceIssueDisplayed(safetySourceTestData.criticalResolvingGeneralIssue) @@ -1094,8 +1267,17 @@ class SafetyCenterNotificationTest { @Test fun whenGreenIssue_notificationHasAutoCancel() { - safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.informationWithIssue) - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + safetyCenterTestHelper.setData( + uniqueSafetySourceId, + safetySourceTestData.informationWithIssue + ) + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("Review"), + safetySourceId = uniqueSafetySourceId, + ) + ) assertThat(notificationWithChannel.statusBarNotification.hasAutoCancel()).isTrue() } @@ -1103,10 +1285,16 @@ class SafetyCenterNotificationTest { @Test fun whenNotGreenIssue_notificationDoesntHaveAutoCancel() { safetyCenterTestHelper.setData( - SINGLE_SOURCE_ID, + uniqueSafetySourceId, safetySourceTestData.recommendationWithDeviceIssue ) - val notificationWithChannel = TestNotificationListener.waitForSingleNotification() + val notificationWithChannel = + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("See issue"), + safetySourceId = uniqueSafetySourceId, + ) + ) assertThat(notificationWithChannel.statusBarNotification.hasAutoCancel()).isFalse() } diff --git a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt index dc3cb3fc2..60e6e41ec 100644 --- a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt +++ b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt @@ -22,6 +22,7 @@ import android.safetycenter.SafetySourceIssue import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.safetycenter.pendingintents.PendingIntentSender +import com.android.safetycenter.testing.NotificationCharacteristics import com.android.safetycenter.testing.SafetyCenterActivityLauncher import com.android.safetycenter.testing.SafetyCenterFlags import com.android.safetycenter.testing.SafetyCenterTestConfigs @@ -74,7 +75,14 @@ class SafetyCenterNotificationLoggingHelperTests { fun openSafetyCenterFromNotification() { safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, newTestDataWithNotifiableIssue()) - sendContentPendingIntent(TestNotificationListener.waitForSingleNotification()) + sendContentPendingIntent( + TestNotificationListener.waitForSingleNotificationMatching( + NotificationCharacteristics( + actions = listOf("See issue"), + safetySourceId = SINGLE_SOURCE_ID, + ) + ) + ) } private fun newTestDataWithNotifiableIssue(): SafetySourceData = diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/NotificationCharacteristics.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/NotificationCharacteristics.kt index 177c2359c..81b752bca 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/NotificationCharacteristics.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/NotificationCharacteristics.kt @@ -17,18 +17,25 @@ package com.android.safetycenter.testing import android.app.Notification +import android.service.notification.StatusBarNotification +import com.android.safetycenter.internaldata.SafetyCenterIds /** The characteristic properties of a notification. */ data class NotificationCharacteristics( - val title: String, - val text: String, + val title: String? = null, + val text: String? = null, val actions: List<CharSequence> = emptyList(), val importance: Int = IMPORTANCE_ANY, - val blockable: Boolean? = null + val blockable: Boolean? = null, + val safetySourceId: String? = null, ) { companion object { const val IMPORTANCE_ANY = -1 + private fun stringMatches(actual: String?, expected: String?): Boolean { + return expected == null || actual == expected + } + private fun importanceMatches( statusBarNotificationWithChannel: StatusBarNotificationWithChannel, characteristicImportance: Int @@ -45,17 +52,31 @@ data class NotificationCharacteristics( statusBarNotificationWithChannel.channel.isBlockable == characteristicBlockable } + fun safetySourceIdMatches( + statusBarNotification: StatusBarNotification, + safetySourceId: String? + ): Boolean { + return safetySourceId == null || + SafetyCenterIds.issueKeyFromString(statusBarNotification.tag).safetySourceId == + safetySourceId + } + private fun isMatch( statusBarNotificationWithChannel: StatusBarNotificationWithChannel, characteristic: NotificationCharacteristics ): Boolean { val notif = statusBarNotificationWithChannel.statusBarNotification.notification + val extras = notif.extras return notif != null && - notif.extras.getString(Notification.EXTRA_TITLE) == characteristic.title && - notif.extras.getString(Notification.EXTRA_TEXT).orEmpty() == characteristic.text && + stringMatches(extras.getString(Notification.EXTRA_TITLE), characteristic.title) && + stringMatches(extras.getString(Notification.EXTRA_TEXT), characteristic.text) && notif.actions.orEmpty().map { it.title } == characteristic.actions && importanceMatches(statusBarNotificationWithChannel, characteristic.importance) && - blockableMatches(statusBarNotificationWithChannel, characteristic.blockable) + blockableMatches(statusBarNotificationWithChannel, characteristic.blockable) && + safetySourceIdMatches( + statusBarNotificationWithChannel.statusBarNotification, + characteristic.safetySourceId + ) } fun areMatching( diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt index 559215c0c..7e77c0827 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt @@ -464,6 +464,10 @@ class SafetySourceTestData(private val context: Context) { /** A [PendingIntent] used by the resolving [Action] in [criticalResolvingGeneralIssue]. */ val criticalIssueActionPendingIntent = resolvingActionPendingIntent() + /** A [PendingIntent] used by the resolving [Action] in [criticalResolvingGeneralIssue]. */ + fun criticalIssueActionPendingIntent(sourceId: String) = + resolvingActionPendingIntent(sourceId = sourceId) + /** * Returns a [PendingIntent] for a resolving [Action] with the given [sourceId], [sourceIssueId] * and [sourceIssueActionId]. Default values are the same as those used by @@ -489,6 +493,16 @@ class SafetySourceTestData(private val context: Context) { .setWillResolve(true) .build() + /** A resolving Critical [Action] */ + private fun criticalResolvingAction(sourceId: String) = + Action.Builder( + CRITICAL_ISSUE_ACTION_ID, + "Solve issue", + criticalIssueActionPendingIntent(sourceId = sourceId) + ) + .setWillResolve(true) + .build() + /** A resolving Critical [Action] with confirmation */ val criticalResolvingActionWithConfirmation: SafetySourceIssue.Action @RequiresApi(UPSIDE_DOWN_CAKE) @@ -518,6 +532,17 @@ class SafetySourceTestData(private val context: Context) { .setSuccessMessage("Issue solved") .build() + /** A resolving Critical [Action] that declares a success message */ + private fun criticalResolvingActionWithSuccessMessage(sourceId: String) = + Action.Builder( + CRITICAL_ISSUE_ACTION_ID, + "Solve issue", + criticalIssueActionPendingIntent(sourceId = sourceId) + ) + .setWillResolve(true) + .setSuccessMessage("Issue solved") + .build() + /** A [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving [Action]. */ val criticalResolvingIssueWithSuccessMessage = SafetySourceIssue.Builder( @@ -530,6 +555,18 @@ class SafetySourceTestData(private val context: Context) { .addAction(criticalResolvingActionWithSuccessMessage) .build() + /** A [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving [Action]. */ + private fun criticalResolvingIssueWithSuccessMessage(sourceId: String) = + SafetySourceIssue.Builder( + CRITICAL_ISSUE_ID, + "Critical issue title", + "Critical issue summary", + SEVERITY_LEVEL_CRITICAL_WARNING, + ISSUE_TYPE_ID + ) + .addAction(criticalResolvingActionWithSuccessMessage(sourceId = sourceId)) + .build() + /** * Another [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a redirecting * [Action]. @@ -574,7 +611,10 @@ class SafetySourceTestData(private val context: Context) { * [SafetySourceIssue.Builder] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving [Action] * . */ - fun defaultCriticalResolvingIssueBuilder(issueId: String = CRITICAL_ISSUE_ID) = + fun defaultCriticalResolvingIssueBuilder( + issueId: String = CRITICAL_ISSUE_ID, + sourceId: String = SINGLE_SOURCE_ID, + ) = SafetySourceIssue.Builder( issueId, "Critical issue title", @@ -582,7 +622,7 @@ class SafetySourceTestData(private val context: Context) { SEVERITY_LEVEL_CRITICAL_WARNING, ISSUE_TYPE_ID ) - .addAction(criticalResolvingAction) + .addAction(criticalResolvingAction(sourceId)) /** * General [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving [Action] @@ -591,6 +631,13 @@ class SafetySourceTestData(private val context: Context) { val criticalResolvingGeneralIssue = defaultCriticalResolvingIssueBuilder().build() /** + * General [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving [Action] + * . + */ + private fun criticalResolvingGeneralIssue(sourceId: String) = + defaultCriticalResolvingIssueBuilder(sourceId = sourceId).build() + + /** * General [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and with deduplication * info and a resolving [Action]. */ @@ -616,6 +663,15 @@ class SafetySourceTestData(private val context: Context) { .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE) .build() + /** + * Device related [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving + * [Action]. + */ + private fun criticalResolvingDeviceIssue(sourceId: String) = + defaultCriticalResolvingIssueBuilder(sourceId = sourceId) + .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE) + .build() + /** A [SafetySourceData.Builder] with a [SEVERITY_LEVEL_CRITICAL_WARNING] status. */ fun defaultCriticalDataBuilder() = SafetySourceData.Builder() @@ -689,6 +745,15 @@ class SafetySourceTestData(private val context: Context) { /** * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving general + * [SafetySourceIssue] and [SafetySourceStatus]. + */ + fun criticalWithResolvingGeneralIssue(sourceId: String) = + defaultCriticalDataBuilder() + .addIssue(criticalResolvingGeneralIssue(sourceId = sourceId)) + .build() + + /** + * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving general * [SafetySourceIssue] and [SafetySourceStatus], with confirmation dialog. */ val criticalWithResolvingGeneralIssueWithConfirmation: SafetySourceData @@ -719,6 +784,15 @@ class SafetySourceTestData(private val context: Context) { /** * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving device related + * [SafetySourceIssue] and [SafetySourceStatus]. + */ + fun criticalWithResolvingDeviceIssue(sourceId: String) = + defaultCriticalDataBuilder() + .addIssue(criticalResolvingDeviceIssue(sourceId = sourceId)) + .build() + + /** + * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving device related * [SafetySourceIssue] and [SafetySourceStatus] and a recommendation issue. */ val criticalWithResolvingDeviceIssueAndRecommendationIssue = @@ -746,6 +820,24 @@ class SafetySourceTestData(private val context: Context) { .build() /** + * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving [SafetySourceIssue] + * and [SafetySourceStatus]. + */ + fun criticalWithResolvingIssueWithSuccessMessage(sourceId: String) = + SafetySourceData.Builder() + .setStatus( + SafetySourceStatus.Builder( + "Critical title", + "Critical summary", + SEVERITY_LEVEL_CRITICAL_WARNING + ) + .setPendingIntent(createTestActivityRedirectPendingIntent()) + .build() + ) + .addIssue(criticalResolvingIssueWithSuccessMessage(sourceId = sourceId)) + .build() + + /** * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and * [SEVERITY_LEVEL_CRITICAL_WARNING] [SafetySourceStatus]. */ diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestNotificationListener.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestNotificationListener.kt index 2b2342d7a..21bf76fad 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestNotificationListener.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestNotificationListener.kt @@ -16,6 +16,7 @@ package com.android.safetycenter.testing +import android.app.Notification import android.app.NotificationChannel import android.content.ComponentName import android.content.Context @@ -101,35 +102,8 @@ class TestNotificationListener : NotificationListenerService() { * if it is met and then violated. */ fun waitForZeroNotifications(timeout: Duration = TIMEOUT_LONG) { - waitForNotificationCount(0, timeout) - } - - /** - * Blocks until there is exactly one Safety Center notification and ensures that remains - * true for a short duration. Returns that notification, or throws an [AssertionError] if a - * this condition is not met within [timeout], or if it is met and then violated. - */ - fun waitForSingleNotification( - timeout: Duration = TIMEOUT_LONG - ): StatusBarNotificationWithChannel { - return waitForNotificationCount(1, timeout).first() - } - - /** - * Blocks until there are exactly [count] Safety Center notifications and ensures that - * remains true for a short duration. Returns those notifications, or throws an - * [AssertionError] if a this condition is not met within [timeout], or if it is met and - * then violated. - */ - private fun waitForNotificationCount( - count: Int, - timeout: Duration = TIMEOUT_LONG - ): List<StatusBarNotificationWithChannel> { - return waitForNotificationsToSatisfy( - timeout = timeout, - description = "$count notifications" - ) { - it.size == count + waitForNotificationsToSatisfy(timeout = timeout, description = "No notifications") { + it.isEmpty() } } @@ -175,15 +149,19 @@ class TestNotificationListener : NotificationListenerService() { successMessage: String, onNotification: (StatusBarNotification) -> Unit = {} ) { - val successNotificationWithChannel = - waitForSingleNotificationMatching( - NotificationCharacteristics( - successMessage, - "", - actions = emptyList(), - ) - ) - val statusBarNotification = successNotificationWithChannel.statusBarNotification + // Only wait for the notification event and don't wait for all notifications to "settle" + // as this notification is auto-cancelled after 10s; which can cause flakyness. + val statusBarNotification = + (runBlockingWithTimeout { + waitForNotificationEventAsync { + (it is NotificationPosted && + it.statusBarNotification.notification + ?.extras + ?.getString(Notification.EXTRA_TITLE) == successMessage) + } + } + as NotificationPosted) + .statusBarNotification onNotification(statusBarNotification) // Cancel the notification directly to speed up the tests as it's only auto-cancelled // after 10 seconds, and the teardown waits for all notifications to be cancelled to @@ -253,6 +231,17 @@ class TestNotificationListener : NotificationListenerService() { return currentNotifications } + private suspend fun waitForNotificationEventAsync( + predicate: (NotificationEvent) -> Boolean + ): NotificationEvent { + var event: NotificationEvent + do { + event = safetyCenterNotificationEvents.receive() + Log.d(TAG, "Received notification event: $event") + } while (!predicate(event)) + return event + } + private fun getSafetyCenterNotifications(): List<StatusBarNotificationWithChannel> { return with(getInstanceOrThrow()) { val notificationsSnapshot = |