1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar.notification.collection; 18 19 import static android.app.Notification.FLAG_NO_CLEAR; 20 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; 21 import static android.service.notification.NotificationListenerService.REASON_CANCEL; 22 import static android.service.notification.NotificationListenerService.REASON_CLICK; 23 import static android.service.notification.NotificationStats.DISMISSAL_SHADE; 24 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; 25 26 import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED; 27 import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN; 28 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.DISMISSED; 29 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED; 30 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED; 31 32 import static org.junit.Assert.assertEquals; 33 import static org.junit.Assert.assertFalse; 34 import static org.junit.Assert.assertNotEquals; 35 import static org.junit.Assert.assertNotNull; 36 import static org.junit.Assert.assertTrue; 37 import static org.mockito.ArgumentMatchers.any; 38 import static org.mockito.ArgumentMatchers.anyInt; 39 import static org.mockito.ArgumentMatchers.eq; 40 import static org.mockito.Mockito.clearInvocations; 41 import static org.mockito.Mockito.inOrder; 42 import static org.mockito.Mockito.mock; 43 import static org.mockito.Mockito.never; 44 import static org.mockito.Mockito.times; 45 import static org.mockito.Mockito.verify; 46 import static org.mockito.Mockito.when; 47 48 import static java.util.Collections.singletonList; 49 import static java.util.Objects.requireNonNull; 50 51 import android.annotation.Nullable; 52 import android.app.Notification; 53 import android.os.RemoteException; 54 import android.service.notification.NotificationListenerService.Ranking; 55 import android.service.notification.NotificationListenerService.RankingMap; 56 import android.service.notification.StatusBarNotification; 57 import android.testing.AndroidTestingRunner; 58 import android.testing.TestableLooper; 59 import android.util.ArrayMap; 60 import android.util.ArraySet; 61 import android.util.Pair; 62 63 import androidx.test.filters.SmallTest; 64 65 import com.android.internal.statusbar.IStatusBarService; 66 import com.android.internal.statusbar.NotificationVisibility; 67 import com.android.systemui.SysuiTestCase; 68 import com.android.systemui.dump.DumpManager; 69 import com.android.systemui.dump.LogBufferEulogizer; 70 import com.android.systemui.statusbar.FeatureFlags; 71 import com.android.systemui.statusbar.RankingBuilder; 72 import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent; 73 import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; 74 import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent; 75 import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; 76 import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler; 77 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; 78 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; 79 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; 80 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger; 81 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; 82 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; 83 import com.android.systemui.util.time.FakeSystemClock; 84 85 import org.junit.Before; 86 import org.junit.Test; 87 import org.junit.runner.RunWith; 88 import org.mockito.ArgumentCaptor; 89 import org.mockito.Captor; 90 import org.mockito.InOrder; 91 import org.mockito.Mock; 92 import org.mockito.MockitoAnnotations; 93 import org.mockito.Spy; 94 95 import java.util.Arrays; 96 import java.util.Collection; 97 import java.util.List; 98 import java.util.Map; 99 100 @SmallTest 101 @RunWith(AndroidTestingRunner.class) 102 @TestableLooper.RunWithLooper 103 public class NotifCollectionTest extends SysuiTestCase { 104 105 @Mock private IStatusBarService mStatusBarService; 106 @Mock private FeatureFlags mFeatureFlags; 107 @Mock private NotifCollectionLogger mLogger; 108 @Mock private LogBufferEulogizer mEulogizer; 109 110 @Mock private GroupCoalescer mGroupCoalescer; 111 @Spy private RecordingCollectionListener mCollectionListener; 112 @Mock private CollectionReadyForBuildListener mBuildListener; 113 114 @Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1"); 115 @Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2"); 116 @Spy private RecordingLifetimeExtender mExtender3 = new RecordingLifetimeExtender("Extender3"); 117 118 @Spy private RecordingDismissInterceptor mInterceptor1 = new RecordingDismissInterceptor( 119 "Interceptor1"); 120 @Spy private RecordingDismissInterceptor mInterceptor2 = new RecordingDismissInterceptor( 121 "Interceptor2"); 122 @Spy private RecordingDismissInterceptor mInterceptor3 = new RecordingDismissInterceptor( 123 "Interceptor3"); 124 125 @Captor private ArgumentCaptor<BatchableNotificationHandler> mListenerCaptor; 126 @Captor private ArgumentCaptor<NotificationEntry> mEntryCaptor; 127 @Captor private ArgumentCaptor<Collection<NotificationEntry>> mBuildListCaptor; 128 129 private NotifCollection mCollection; 130 private BatchableNotificationHandler mNotifHandler; 131 132 private InOrder mListenerInOrder; 133 134 private NoManSimulator mNoMan; 135 private FakeSystemClock mClock = new FakeSystemClock(); 136 137 @Before setUp()138 public void setUp() { 139 MockitoAnnotations.initMocks(this); 140 allowTestableLooperAsMainThread(); 141 142 when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(true); 143 when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(true); 144 145 when(mEulogizer.record(any(Exception.class))).thenAnswer(i -> i.getArguments()[0]); 146 147 mListenerInOrder = inOrder(mCollectionListener); 148 149 mCollection = new NotifCollection( 150 mStatusBarService, 151 mClock, 152 mFeatureFlags, 153 mLogger, 154 mEulogizer, 155 mock(DumpManager.class)); 156 mCollection.attach(mGroupCoalescer); 157 mCollection.addCollectionListener(mCollectionListener); 158 mCollection.setBuildListener(mBuildListener); 159 160 // Capture the listener object that the collection registers with the listener service so 161 // we can simulate listener service events in tests below 162 verify(mGroupCoalescer).setNotificationHandler(mListenerCaptor.capture()); 163 mNotifHandler = requireNonNull(mListenerCaptor.getValue()); 164 165 mNoMan = new NoManSimulator(); 166 mNoMan.addListener(mNotifHandler); 167 168 mNotifHandler.onNotificationsInitialized(); 169 } 170 171 @Test testEventDispatchedWhenNotifPosted()172 public void testEventDispatchedWhenNotifPosted() { 173 // WHEN a notification is posted 174 NotifEvent notif1 = mNoMan.postNotif( 175 buildNotif(TEST_PACKAGE, 3) 176 .setRank(4747)); 177 178 // THEN the listener is notified 179 final NotificationEntry entry = mCollectionListener.getEntry(notif1.key); 180 181 mListenerInOrder.verify(mCollectionListener).onEntryInit(entry); 182 mListenerInOrder.verify(mCollectionListener).onEntryAdded(entry); 183 mListenerInOrder.verify(mCollectionListener).onRankingApplied(); 184 185 assertEquals(notif1.key, entry.getKey()); 186 assertEquals(notif1.sbn, entry.getSbn()); 187 assertEquals(notif1.ranking, entry.getRanking()); 188 } 189 190 @Test testEventDispatchedWhenNotifBatchPosted()191 public void testEventDispatchedWhenNotifBatchPosted() { 192 // GIVEN a NotifCollection with one notif already posted 193 mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2) 194 .setGroup(mContext, "group_1") 195 .setContentTitle(mContext, "Old version")); 196 197 clearInvocations(mCollectionListener); 198 clearInvocations(mBuildListener); 199 200 // WHEN three notifications from the same group are posted (one of them an update, two of 201 // them new) 202 NotificationEntry entry1 = buildNotif(TEST_PACKAGE, 1) 203 .setGroup(mContext, "group_1") 204 .build(); 205 NotificationEntry entry2 = buildNotif(TEST_PACKAGE, 2) 206 .setGroup(mContext, "group_1") 207 .setContentTitle(mContext, "New version") 208 .build(); 209 NotificationEntry entry3 = buildNotif(TEST_PACKAGE, 3) 210 .setGroup(mContext, "group_1") 211 .build(); 212 213 mNotifHandler.onNotificationBatchPosted(Arrays.asList( 214 new CoalescedEvent(entry1.getKey(), 0, entry1.getSbn(), entry1.getRanking(), null), 215 new CoalescedEvent(entry2.getKey(), 1, entry2.getSbn(), entry2.getRanking(), null), 216 new CoalescedEvent(entry3.getKey(), 2, entry3.getSbn(), entry3.getRanking(), null) 217 )); 218 219 // THEN onEntryAdded is called on the new ones 220 verify(mCollectionListener, times(2)).onEntryAdded(mEntryCaptor.capture()); 221 222 List<NotificationEntry> capturedAdds = mEntryCaptor.getAllValues(); 223 224 assertEquals(entry1.getSbn(), capturedAdds.get(0).getSbn()); 225 assertEquals(entry1.getRanking(), capturedAdds.get(0).getRanking()); 226 227 assertEquals(entry3.getSbn(), capturedAdds.get(1).getSbn()); 228 assertEquals(entry3.getRanking(), capturedAdds.get(1).getRanking()); 229 230 // THEN onEntryUpdated is called on the middle one 231 verify(mCollectionListener).onEntryUpdated(mEntryCaptor.capture()); 232 NotificationEntry capturedUpdate = mEntryCaptor.getValue(); 233 assertEquals(entry2.getSbn(), capturedUpdate.getSbn()); 234 assertEquals(entry2.getRanking(), capturedUpdate.getRanking()); 235 236 // THEN onBuildList is called only once 237 verifyBuiltList( 238 List.of( 239 capturedAdds.get(0), 240 capturedAdds.get(1), 241 capturedUpdate)); 242 } 243 244 @Test testEventDispatchedWhenNotifUpdated()245 public void testEventDispatchedWhenNotifUpdated() { 246 // GIVEN a collection with one notif 247 mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3) 248 .setRank(4747)); 249 250 // WHEN the notif is reposted 251 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3) 252 .setRank(89)); 253 254 // THEN the listener is notified 255 final NotificationEntry entry = mCollectionListener.getEntry(notif2.key); 256 257 mListenerInOrder.verify(mCollectionListener).onEntryUpdated(entry); 258 mListenerInOrder.verify(mCollectionListener).onRankingApplied(); 259 260 assertEquals(notif2.key, entry.getKey()); 261 assertEquals(notif2.sbn, entry.getSbn()); 262 assertEquals(notif2.ranking, entry.getRanking()); 263 } 264 265 @Test testEventDispatchedWhenNotifRemoved()266 public void testEventDispatchedWhenNotifRemoved() { 267 // GIVEN a collection with one notif 268 mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)); 269 clearInvocations(mCollectionListener); 270 271 NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); 272 NotificationEntry entry = mCollectionListener.getEntry(notif.key); 273 clearInvocations(mCollectionListener); 274 275 // WHEN a notif is retracted 276 mNoMan.retractNotif(notif.sbn, REASON_APP_CANCEL); 277 278 // THEN the listener is notified 279 mListenerInOrder.verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL); 280 mListenerInOrder.verify(mCollectionListener).onEntryCleanUp(entry); 281 mListenerInOrder.verify(mCollectionListener).onRankingApplied(); 282 283 assertEquals(notif.sbn, entry.getSbn()); 284 assertEquals(notif.ranking, entry.getRanking()); 285 } 286 287 @Test testRankingsAreUpdatedForOtherNotifs()288 public void testRankingsAreUpdatedForOtherNotifs() { 289 // GIVEN a collection with one notif 290 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3) 291 .setRank(47)); 292 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 293 294 // WHEN a new notif is posted, triggering a rerank 295 mNoMan.setRanking(notif1.sbn.getKey(), new RankingBuilder(notif1.ranking) 296 .setRank(56) 297 .build()); 298 mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 77)); 299 300 // THEN the ranking is updated on the first entry 301 assertEquals(56, entry1.getRanking().getRank()); 302 } 303 304 @Test testRankingUpdateIsProperlyIssuedToEveryone()305 public void testRankingUpdateIsProperlyIssuedToEveryone() { 306 // GIVEN a collection with a couple notifs 307 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3) 308 .setRank(3)); 309 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 8) 310 .setRank(2)); 311 NotifEvent notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 77) 312 .setRank(1)); 313 314 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 315 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 316 NotificationEntry entry3 = mCollectionListener.getEntry(notif3.key); 317 318 // WHEN a ranking update is delivered 319 Ranking newRanking1 = new RankingBuilder(notif1.ranking) 320 .setRank(4) 321 .setExplanation("Foo bar") 322 .build(); 323 Ranking newRanking2 = new RankingBuilder(notif2.ranking) 324 .setRank(5) 325 .setExplanation("baz buzz") 326 .build(); 327 328 // WHEN entry3's ranking update includes an update to its overrideGroupKey 329 final String newOverrideGroupKey = "newOverrideGroupKey"; 330 Ranking newRanking3 = new RankingBuilder(notif3.ranking) 331 .setRank(6) 332 .setExplanation("Penguin pizza") 333 .setOverrideGroupKey(newOverrideGroupKey) 334 .build(); 335 336 mNoMan.setRanking(notif1.sbn.getKey(), newRanking1); 337 mNoMan.setRanking(notif2.sbn.getKey(), newRanking2); 338 mNoMan.setRanking(notif3.sbn.getKey(), newRanking3); 339 mNoMan.issueRankingUpdate(); 340 341 // THEN all of the NotifEntries have their rankings properly updated 342 assertEquals(newRanking1, entry1.getRanking()); 343 assertEquals(newRanking2, entry2.getRanking()); 344 assertEquals(newRanking3, entry3.getRanking()); 345 346 // THEN the entry3's overrideGroupKey is updated along with its groupKey 347 assertEquals(newOverrideGroupKey, entry3.getSbn().getOverrideGroupKey()); 348 assertNotNull(entry3.getSbn().getGroupKey()); 349 } 350 351 @Test testNotifEntriesAreNotPersistedAcrossRemovalAndReposting()352 public void testNotifEntriesAreNotPersistedAcrossRemovalAndReposting() { 353 // GIVEN a notification that has been posted 354 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)); 355 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 356 357 // WHEN the notification is retracted and then reposted 358 mNoMan.retractNotif(notif1.sbn, REASON_APP_CANCEL); 359 mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)); 360 361 // THEN the new NotificationEntry is a new object 362 NotificationEntry entry2 = mCollectionListener.getEntry(notif1.key); 363 assertNotEquals(entry2, entry1); 364 } 365 366 @Test testDismissNotificationSentToSystemServer()367 public void testDismissNotificationSentToSystemServer() throws RemoteException { 368 // GIVEN a collection with a couple notifications 369 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 370 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag")); 371 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 372 373 // WHEN a notification is manually dismissed 374 DismissedByUserStats stats = defaultStats(entry2); 375 mCollection.dismissNotification(entry2, defaultStats(entry2)); 376 377 // THEN we send the dismissal to system server 378 verify(mStatusBarService).onNotificationClear( 379 notif2.sbn.getPackageName(), 380 notif2.sbn.getTag(), 381 88, 382 notif2.sbn.getUser().getIdentifier(), 383 notif2.sbn.getKey(), 384 stats.dismissalSurface, 385 stats.dismissalSentiment, 386 stats.notificationVisibility); 387 } 388 389 @Test testDismissedNotificationsAreMarkedAsDismissedLocally()390 public void testDismissedNotificationsAreMarkedAsDismissedLocally() { 391 // GIVEN a collection with a notification 392 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 393 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 394 395 // WHEN a notification is manually dismissed 396 mCollection.dismissNotification(entry1, defaultStats(entry1)); 397 398 // THEN the entry is marked as dismissed locally 399 assertEquals(DISMISSED, entry1.getDismissState()); 400 } 401 402 @Test testDismissedNotificationsCannotBeLifetimeExtended()403 public void testDismissedNotificationsCannotBeLifetimeExtended() { 404 // GIVEN a collection with a notification and a lifetime extender 405 mCollection.addNotificationLifetimeExtender(mExtender1); 406 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 407 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 408 409 // WHEN a notification is manually dismissed 410 mCollection.dismissNotification(entry1, defaultStats(entry1)); 411 412 // THEN lifetime extenders are never queried 413 verify(mExtender1, never()).shouldExtendLifetime(eq(entry1), anyInt()); 414 } 415 416 @Test testDismissedNotificationsDoNotTriggerRemovalEvents()417 public void testDismissedNotificationsDoNotTriggerRemovalEvents() { 418 // GIVEN a collection with a notification 419 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 420 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 421 422 // WHEN a notification is manually dismissed 423 mCollection.dismissNotification(entry1, defaultStats(entry1)); 424 425 // THEN onEntryRemoved is not called 426 verify(mCollectionListener, never()).onEntryRemoved(eq(entry1), anyInt()); 427 } 428 429 @Test testDismissedNotificationsStillAppearInNotificationSet()430 public void testDismissedNotificationsStillAppearInNotificationSet() { 431 // GIVEN a collection with a notification 432 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 433 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 434 435 // WHEN a notification is manually dismissed 436 mCollection.dismissNotification(entry1, defaultStats(entry1)); 437 438 // THEN the dismissed entry still appears in the notification set 439 assertEquals( 440 new ArraySet<>(singletonList(entry1)), 441 new ArraySet<>(mCollection.getAllNotifs())); 442 } 443 444 @Test testDismissingLifetimeExtendedSummaryDoesNotDismissChildren()445 public void testDismissingLifetimeExtendedSummaryDoesNotDismissChildren() { 446 // GIVEN A notif group with one summary and two children 447 mCollection.addNotificationLifetimeExtender(mExtender1); 448 CollectionEvent notif1 = postNotif( 449 buildNotif(TEST_PACKAGE, 1, "myTag") 450 .setGroup(mContext, GROUP_1) 451 .setGroupSummary(mContext, true)); 452 CollectionEvent notif2 = postNotif( 453 buildNotif(TEST_PACKAGE, 2, "myTag") 454 .setGroup(mContext, GROUP_1)); 455 CollectionEvent notif3 = postNotif( 456 buildNotif(TEST_PACKAGE, 3, "myTag") 457 .setGroup(mContext, GROUP_1)); 458 459 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 460 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 461 NotificationEntry entry3 = mCollectionListener.getEntry(notif3.key); 462 463 // GIVEN that the summary and one child are retracted, but both are lifetime-extended 464 mExtender1.shouldExtendLifetime = true; 465 mNoMan.retractNotif(notif1.sbn, REASON_CANCEL); 466 mNoMan.retractNotif(notif2.sbn, REASON_CANCEL); 467 assertEquals( 468 new ArraySet<>(List.of(entry1, entry2, entry3)), 469 new ArraySet<>(mCollection.getAllNotifs())); 470 471 // WHEN the summary is dismissed by the user 472 mCollection.dismissNotification(entry1, defaultStats(entry1)); 473 474 // THEN the summary is removed, but both children stick around 475 assertEquals( 476 new ArraySet<>(List.of(entry2, entry3)), 477 new ArraySet<>(mCollection.getAllNotifs())); 478 assertEquals(NOT_DISMISSED, entry2.getDismissState()); 479 assertEquals(NOT_DISMISSED, entry3.getDismissState()); 480 } 481 482 @Test testDismissNotificationCallsDismissInterceptors()483 public void testDismissNotificationCallsDismissInterceptors() throws RemoteException { 484 // GIVEN a collection with notifications with multiple dismiss interceptors 485 mInterceptor1.shouldInterceptDismissal = true; 486 mInterceptor2.shouldInterceptDismissal = true; 487 mInterceptor3.shouldInterceptDismissal = false; 488 mCollection.addNotificationDismissInterceptor(mInterceptor1); 489 mCollection.addNotificationDismissInterceptor(mInterceptor2); 490 mCollection.addNotificationDismissInterceptor(mInterceptor3); 491 492 NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 493 NotificationEntry entry = mCollectionListener.getEntry(notif.key); 494 495 // WHEN a notification is manually dismissed 496 DismissedByUserStats stats = defaultStats(entry); 497 mCollection.dismissNotification(entry, stats); 498 499 // THEN all interceptors get checked 500 verify(mInterceptor1).shouldInterceptDismissal(entry); 501 verify(mInterceptor2).shouldInterceptDismissal(entry); 502 verify(mInterceptor3).shouldInterceptDismissal(entry); 503 assertEquals(List.of(mInterceptor1, mInterceptor2), entry.mDismissInterceptors); 504 505 // THEN we never send the dismissal to system server 506 verify(mStatusBarService, never()).onNotificationClear( 507 notif.sbn.getPackageName(), 508 notif.sbn.getTag(), 509 47, 510 notif.sbn.getUser().getIdentifier(), 511 notif.sbn.getKey(), 512 stats.dismissalSurface, 513 stats.dismissalSentiment, 514 stats.notificationVisibility); 515 } 516 517 @Test testDismissInterceptorsCanceledWhenNotifIsUpdated()518 public void testDismissInterceptorsCanceledWhenNotifIsUpdated() throws RemoteException { 519 // GIVEN a few lifetime extenders and a couple notifications 520 mCollection.addNotificationDismissInterceptor(mInterceptor1); 521 mCollection.addNotificationDismissInterceptor(mInterceptor2); 522 523 mInterceptor1.shouldInterceptDismissal = true; 524 mInterceptor2.shouldInterceptDismissal = true; 525 526 NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); 527 NotificationEntry entry = mCollectionListener.getEntry(notif.key); 528 529 // WHEN a notification is manually dismissed and intercepted 530 DismissedByUserStats stats = defaultStats(entry); 531 mCollection.dismissNotification(entry, stats); 532 assertEquals(List.of(mInterceptor1, mInterceptor2), entry.mDismissInterceptors); 533 clearInvocations(mInterceptor1, mInterceptor2); 534 535 // WHEN the notification is reposted 536 mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); 537 538 // THEN all of the active dismissal interceptors are canceled 539 verify(mInterceptor1).cancelDismissInterception(entry); 540 verify(mInterceptor2).cancelDismissInterception(entry); 541 assertEquals(List.of(), entry.mDismissInterceptors); 542 543 // THEN the notification is never sent to system server to dismiss 544 verify(mStatusBarService, never()).onNotificationClear( 545 eq(notif.sbn.getPackageName()), 546 eq(notif.sbn.getTag()), 547 eq(47), 548 eq(notif.sbn.getUser().getIdentifier()), 549 eq(notif.sbn.getKey()), 550 anyInt(), 551 anyInt(), 552 eq(stats.notificationVisibility)); 553 } 554 555 @Test testEndingAllDismissInterceptorsSendsDismiss()556 public void testEndingAllDismissInterceptorsSendsDismiss() throws RemoteException { 557 // GIVEN a collection with notifications a dismiss interceptor 558 mInterceptor1.shouldInterceptDismissal = true; 559 mCollection.addNotificationDismissInterceptor(mInterceptor1); 560 561 NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 562 NotificationEntry entry = mCollectionListener.getEntry(notif.key); 563 564 // GIVEN a notification is manually dismissed 565 DismissedByUserStats stats = defaultStats(entry); 566 mCollection.dismissNotification(entry, stats); 567 568 // WHEN all interceptors end their interception dismissal 569 mInterceptor1.shouldInterceptDismissal = false; 570 mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry, 571 stats); 572 573 // THEN we send the dismissal to system server 574 verify(mStatusBarService).onNotificationClear( 575 eq(notif.sbn.getPackageName()), 576 eq(notif.sbn.getTag()), 577 eq(47), 578 eq(notif.sbn.getUser().getIdentifier()), 579 eq(notif.sbn.getKey()), 580 anyInt(), 581 anyInt(), 582 eq(stats.notificationVisibility)); 583 } 584 585 @Test testEndDismissInterceptionUpdatesDismissInterceptors()586 public void testEndDismissInterceptionUpdatesDismissInterceptors() { 587 // GIVEN a collection with notifications with multiple dismiss interceptors 588 mInterceptor1.shouldInterceptDismissal = true; 589 mInterceptor2.shouldInterceptDismissal = true; 590 mInterceptor3.shouldInterceptDismissal = false; 591 mCollection.addNotificationDismissInterceptor(mInterceptor1); 592 mCollection.addNotificationDismissInterceptor(mInterceptor2); 593 mCollection.addNotificationDismissInterceptor(mInterceptor3); 594 595 NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 596 NotificationEntry entry = mCollectionListener.getEntry(notif.key); 597 598 // GIVEN a notification is manually dismissed 599 mCollection.dismissNotification(entry, defaultStats(entry)); 600 601 // WHEN an interceptor ends its interception 602 mInterceptor1.shouldInterceptDismissal = false; 603 mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry, 604 defaultStats(entry)); 605 606 // THEN all interceptors get checked 607 verify(mInterceptor1).shouldInterceptDismissal(entry); 608 verify(mInterceptor2).shouldInterceptDismissal(entry); 609 verify(mInterceptor3).shouldInterceptDismissal(entry); 610 611 // THEN mInterceptor2 is the only dismiss interceptor 612 assertEquals(List.of(mInterceptor2), entry.mDismissInterceptors); 613 } 614 615 616 @Test(expected = IllegalStateException.class) testEndingDismissalOfNonInterceptedThrows()617 public void testEndingDismissalOfNonInterceptedThrows() { 618 // GIVEN a collection with notifications with a dismiss interceptor that hasn't been called 619 mInterceptor1.shouldInterceptDismissal = false; 620 mCollection.addNotificationDismissInterceptor(mInterceptor1); 621 622 NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 623 NotificationEntry entry = mCollectionListener.getEntry(notif.key); 624 625 // WHEN we try to end the dismissal of an interceptor that didn't intercept the notif 626 mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry, 627 defaultStats(entry)); 628 629 // THEN an exception is thrown 630 } 631 632 @Test(expected = IllegalStateException.class) testDismissingNonExistentNotificationThrows()633 public void testDismissingNonExistentNotificationThrows() { 634 // GIVEN a collection that originally had three notifs, but where one was dismissed 635 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); 636 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); 637 NotifEvent notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 99)); 638 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 639 mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); 640 641 // WHEN we try to dismiss a notification that isn't present 642 mCollection.dismissNotification(entry2, defaultStats(entry2)); 643 644 // THEN an exception is thrown 645 } 646 647 @Test testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed()648 public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() { 649 // GIVEN a collection with two grouped notifs in it 650 CollectionEvent notif0 = postNotif( 651 buildNotif(TEST_PACKAGE, 0) 652 .setGroup(mContext, GROUP_1) 653 .setGroupSummary(mContext, true)); 654 CollectionEvent notif1 = postNotif( 655 buildNotif(TEST_PACKAGE, 1) 656 .setGroup(mContext, GROUP_1)); 657 NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key); 658 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 659 660 // WHEN the summary is dismissed 661 mCollection.dismissNotification(entry0, defaultStats(entry0)); 662 663 // THEN all members of the group are marked as dismissed locally 664 assertEquals(DISMISSED, entry0.getDismissState()); 665 assertEquals(PARENT_DISMISSED, entry1.getDismissState()); 666 } 667 668 @Test testUpdatingDismissedSummaryBringsChildrenBack()669 public void testUpdatingDismissedSummaryBringsChildrenBack() { 670 // GIVEN a collection with two grouped notifs in it 671 CollectionEvent notif0 = postNotif( 672 buildNotif(TEST_PACKAGE, 0) 673 .setGroup(mContext, GROUP_1) 674 .setGroupSummary(mContext, true)); 675 CollectionEvent notif1 = postNotif( 676 buildNotif(TEST_PACKAGE, 1) 677 .setGroup(mContext, GROUP_1)); 678 NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key); 679 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 680 681 // WHEN the summary is dismissed but then reposted without a group 682 mCollection.dismissNotification(entry0, defaultStats(entry0)); 683 NotifEvent notif0a = mNoMan.postNotif( 684 buildNotif(TEST_PACKAGE, 0)); 685 686 // THEN it and all of its previous children are no longer dismissed locally 687 assertEquals(NOT_DISMISSED, entry0.getDismissState()); 688 assertEquals(NOT_DISMISSED, entry1.getDismissState()); 689 } 690 691 @Test testDismissedChildrenAreNotResetByParentUpdate()692 public void testDismissedChildrenAreNotResetByParentUpdate() { 693 // GIVEN a collection with three grouped notifs in it 694 CollectionEvent notif0 = postNotif( 695 buildNotif(TEST_PACKAGE, 0) 696 .setGroup(mContext, GROUP_1) 697 .setGroupSummary(mContext, true)); 698 CollectionEvent notif1 = postNotif( 699 buildNotif(TEST_PACKAGE, 1) 700 .setGroup(mContext, GROUP_1)); 701 CollectionEvent notif2 = postNotif( 702 buildNotif(TEST_PACKAGE, 2) 703 .setGroup(mContext, GROUP_1)); 704 NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key); 705 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 706 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 707 708 // WHEN a child is dismissed, then the parent is dismissed, then the parent is updated 709 mCollection.dismissNotification(entry1, defaultStats(entry1)); 710 mCollection.dismissNotification(entry0, defaultStats(entry0)); 711 NotifEvent notif0a = mNoMan.postNotif( 712 buildNotif(TEST_PACKAGE, 0)); 713 714 // THEN the manually-dismissed child is still marked as dismissed 715 assertEquals(NOT_DISMISSED, entry0.getDismissState()); 716 assertEquals(DISMISSED, entry1.getDismissState()); 717 assertEquals(NOT_DISMISSED, entry2.getDismissState()); 718 } 719 720 @Test testUpdatingGroupKeyOfDismissedSummaryBringsChildrenBack()721 public void testUpdatingGroupKeyOfDismissedSummaryBringsChildrenBack() { 722 // GIVEN a collection with two grouped notifs in it 723 CollectionEvent notif0 = postNotif( 724 buildNotif(TEST_PACKAGE, 0) 725 .setOverrideGroupKey(GROUP_1) 726 .setGroupSummary(mContext, true)); 727 CollectionEvent notif1 = postNotif( 728 buildNotif(TEST_PACKAGE, 1) 729 .setOverrideGroupKey(GROUP_1)); 730 NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key); 731 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 732 733 // WHEN the summary is dismissed but then reposted AND in the same update one of the 734 // children's ranking loses its override group 735 mCollection.dismissNotification(entry0, defaultStats(entry0)); 736 mNoMan.setRanking(entry1.getKey(), new RankingBuilder() 737 .setKey(entry1.getKey()) 738 .build()); 739 mNoMan.postNotif( 740 buildNotif(TEST_PACKAGE, 0) 741 .setOverrideGroupKey(GROUP_1) 742 .setGroupSummary(mContext, true)); 743 744 // THEN it and all of its previous children are no longer dismissed locally, including the 745 // child that is no longer part of the group 746 assertEquals(NOT_DISMISSED, entry0.getDismissState()); 747 assertEquals(NOT_DISMISSED, entry1.getDismissState()); 748 } 749 750 @Test testDismissingSummaryDoesNotDismissForegroundServiceChildren()751 public void testDismissingSummaryDoesNotDismissForegroundServiceChildren() { 752 // GIVEN a collection with three grouped notifs in it 753 CollectionEvent notif0 = postNotif( 754 buildNotif(TEST_PACKAGE, 0) 755 .setGroup(mContext, GROUP_1) 756 .setGroupSummary(mContext, true)); 757 CollectionEvent notif1 = postNotif( 758 buildNotif(TEST_PACKAGE, 1) 759 .setGroup(mContext, GROUP_1) 760 .setFlag(mContext, Notification.FLAG_FOREGROUND_SERVICE, true)); 761 CollectionEvent notif2 = postNotif( 762 buildNotif(TEST_PACKAGE, 2) 763 .setGroup(mContext, GROUP_1)); 764 765 // WHEN the summary is dismissed 766 mCollection.dismissNotification(notif0.entry, defaultStats(notif0.entry)); 767 768 // THEN the foreground service child is not dismissed 769 assertEquals(DISMISSED, notif0.entry.getDismissState()); 770 assertEquals(NOT_DISMISSED, notif1.entry.getDismissState()); 771 assertEquals(PARENT_DISMISSED, notif2.entry.getDismissState()); 772 } 773 774 @Test testDismissingSummaryDoesNotDismissBubbledChildren()775 public void testDismissingSummaryDoesNotDismissBubbledChildren() { 776 // GIVEN a collection with three grouped notifs in it 777 CollectionEvent notif0 = postNotif( 778 buildNotif(TEST_PACKAGE, 0) 779 .setGroup(mContext, GROUP_1) 780 .setGroupSummary(mContext, true)); 781 CollectionEvent notif1 = postNotif( 782 buildNotif(TEST_PACKAGE, 1) 783 .setGroup(mContext, GROUP_1) 784 .setFlag(mContext, Notification.FLAG_BUBBLE, true)); 785 CollectionEvent notif2 = postNotif( 786 buildNotif(TEST_PACKAGE, 2) 787 .setGroup(mContext, GROUP_1)); 788 789 // WHEN the summary is dismissed 790 mCollection.dismissNotification(notif0.entry, defaultStats(notif0.entry)); 791 792 // THEN the bubbled child is not dismissed 793 assertEquals(DISMISSED, notif0.entry.getDismissState()); 794 assertEquals(NOT_DISMISSED, notif1.entry.getDismissState()); 795 assertEquals(PARENT_DISMISSED, notif2.entry.getDismissState()); 796 } 797 798 @Test testDismissingSummaryDoesNotDismissDuplicateSummaries()799 public void testDismissingSummaryDoesNotDismissDuplicateSummaries() { 800 // GIVEN a group with a two summaries 801 CollectionEvent notif0 = postNotif( 802 buildNotif(TEST_PACKAGE, 0) 803 .setGroup(mContext, GROUP_1) 804 .setGroupSummary(mContext, true)); 805 CollectionEvent notif1 = postNotif( 806 buildNotif(TEST_PACKAGE, 1) 807 .setGroup(mContext, GROUP_1) 808 .setGroupSummary(mContext, true)); 809 CollectionEvent notif2 = postNotif( 810 buildNotif(TEST_PACKAGE, 2) 811 .setGroup(mContext, GROUP_1)); 812 813 // WHEN the first summary is dismissed 814 mCollection.dismissNotification(notif0.entry, defaultStats(notif0.entry)); 815 816 // THEN the second summary is not auto-dismissed (but the child is) 817 assertEquals(DISMISSED, notif0.entry.getDismissState()); 818 assertEquals(NOT_DISMISSED, notif1.entry.getDismissState()); 819 assertEquals(PARENT_DISMISSED, notif2.entry.getDismissState()); 820 } 821 822 @Test testLifetimeExtendersAreQueriedWhenNotifRemoved()823 public void testLifetimeExtendersAreQueriedWhenNotifRemoved() { 824 // GIVEN a couple notifications and a few lifetime extenders 825 mExtender1.shouldExtendLifetime = true; 826 mExtender2.shouldExtendLifetime = true; 827 828 mCollection.addNotificationLifetimeExtender(mExtender1); 829 mCollection.addNotificationLifetimeExtender(mExtender2); 830 mCollection.addNotificationLifetimeExtender(mExtender3); 831 832 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); 833 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); 834 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 835 836 // WHEN a notification is removed 837 mNoMan.retractNotif(notif2.sbn, REASON_CLICK); 838 839 // THEN each extender is asked whether to extend, even if earlier ones return true 840 verify(mExtender1).shouldExtendLifetime(entry2, REASON_CLICK); 841 verify(mExtender2).shouldExtendLifetime(entry2, REASON_CLICK); 842 verify(mExtender3).shouldExtendLifetime(entry2, REASON_CLICK); 843 844 // THEN the entry is not removed 845 assertTrue(mCollection.getAllNotifs().contains(entry2)); 846 847 // THEN the entry properly records all extenders that returned true 848 assertEquals(Arrays.asList(mExtender1, mExtender2), entry2.mLifetimeExtenders); 849 } 850 851 @Test testWhenLastLifetimeExtenderExpiresAllAreReQueried()852 public void testWhenLastLifetimeExtenderExpiresAllAreReQueried() { 853 // GIVEN a couple notifications and a few lifetime extenders 854 mExtender2.shouldExtendLifetime = true; 855 856 mCollection.addNotificationLifetimeExtender(mExtender1); 857 mCollection.addNotificationLifetimeExtender(mExtender2); 858 mCollection.addNotificationLifetimeExtender(mExtender3); 859 860 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); 861 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); 862 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 863 864 // GIVEN a notification gets lifetime-extended by one of them 865 mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL); 866 assertTrue(mCollection.getAllNotifs().contains(entry2)); 867 clearInvocations(mExtender1, mExtender2, mExtender3); 868 869 // WHEN the last active extender expires (but new ones become active) 870 mExtender1.shouldExtendLifetime = true; 871 mExtender2.shouldExtendLifetime = false; 872 mExtender3.shouldExtendLifetime = true; 873 mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2); 874 875 // THEN each extender is re-queried 876 verify(mExtender1).shouldExtendLifetime(entry2, REASON_APP_CANCEL); 877 verify(mExtender2).shouldExtendLifetime(entry2, REASON_APP_CANCEL); 878 verify(mExtender3).shouldExtendLifetime(entry2, REASON_APP_CANCEL); 879 880 // THEN the entry is not removed 881 assertTrue(mCollection.getAllNotifs().contains(entry2)); 882 883 // THEN the entry properly records all extenders that returned true 884 assertEquals(Arrays.asList(mExtender1, mExtender3), entry2.mLifetimeExtenders); 885 } 886 887 @Test testExtendersAreNotReQueriedUntilFinalActiveExtenderExpires()888 public void testExtendersAreNotReQueriedUntilFinalActiveExtenderExpires() { 889 // GIVEN a couple notifications and a few lifetime extenders 890 mExtender1.shouldExtendLifetime = true; 891 mExtender2.shouldExtendLifetime = true; 892 893 mCollection.addNotificationLifetimeExtender(mExtender1); 894 mCollection.addNotificationLifetimeExtender(mExtender2); 895 mCollection.addNotificationLifetimeExtender(mExtender3); 896 897 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); 898 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); 899 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 900 901 // GIVEN a notification gets lifetime-extended by a couple of them 902 mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL); 903 assertTrue(mCollection.getAllNotifs().contains(entry2)); 904 clearInvocations(mExtender1, mExtender2, mExtender3); 905 906 // WHEN one (but not all) of the extenders expires 907 mExtender2.shouldExtendLifetime = false; 908 mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2); 909 910 // THEN the entry is not removed 911 assertTrue(mCollection.getAllNotifs().contains(entry2)); 912 913 // THEN we don't re-query the extenders 914 verify(mExtender1, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL); 915 verify(mExtender2, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL); 916 verify(mExtender3, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL); 917 918 // THEN the entry properly records all extenders that returned true 919 assertEquals(singletonList(mExtender1), entry2.mLifetimeExtenders); 920 } 921 922 @Test testNotificationIsRemovedWhenAllLifetimeExtendersExpire()923 public void testNotificationIsRemovedWhenAllLifetimeExtendersExpire() { 924 // GIVEN a couple notifications and a few lifetime extenders 925 mExtender1.shouldExtendLifetime = true; 926 mExtender2.shouldExtendLifetime = true; 927 928 mCollection.addNotificationLifetimeExtender(mExtender1); 929 mCollection.addNotificationLifetimeExtender(mExtender2); 930 mCollection.addNotificationLifetimeExtender(mExtender3); 931 932 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); 933 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); 934 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 935 936 // GIVEN a notification gets lifetime-extended by a couple of them 937 mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); 938 assertTrue(mCollection.getAllNotifs().contains(entry2)); 939 clearInvocations(mExtender1, mExtender2, mExtender3); 940 941 // WHEN all of the active extenders expire 942 mExtender2.shouldExtendLifetime = false; 943 mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2); 944 mExtender1.shouldExtendLifetime = false; 945 mExtender1.callback.onEndLifetimeExtension(mExtender1, entry2); 946 947 // THEN the entry removed 948 assertFalse(mCollection.getAllNotifs().contains(entry2)); 949 verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN); 950 } 951 952 @Test testLifetimeExtensionIsCanceledWhenNotifIsUpdated()953 public void testLifetimeExtensionIsCanceledWhenNotifIsUpdated() { 954 // GIVEN a few lifetime extenders and a couple notifications 955 mCollection.addNotificationLifetimeExtender(mExtender1); 956 mCollection.addNotificationLifetimeExtender(mExtender2); 957 mCollection.addNotificationLifetimeExtender(mExtender3); 958 959 mExtender1.shouldExtendLifetime = true; 960 mExtender2.shouldExtendLifetime = true; 961 962 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); 963 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); 964 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 965 966 // GIVEN a notification gets lifetime-extended by a couple of them 967 mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); 968 assertTrue(mCollection.getAllNotifs().contains(entry2)); 969 clearInvocations(mExtender1, mExtender2, mExtender3); 970 971 // WHEN the notification is reposted 972 mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); 973 974 // THEN all of the active lifetime extenders are canceled 975 verify(mExtender1).cancelLifetimeExtension(entry2); 976 verify(mExtender2).cancelLifetimeExtension(entry2); 977 978 // THEN the notification is still present 979 assertTrue(mCollection.getAllNotifs().contains(entry2)); 980 } 981 982 @Test(expected = IllegalStateException.class) testReentrantCallsToLifetimeExtendersThrow()983 public void testReentrantCallsToLifetimeExtendersThrow() { 984 // GIVEN a few lifetime extenders and a couple notifications 985 mCollection.addNotificationLifetimeExtender(mExtender1); 986 mCollection.addNotificationLifetimeExtender(mExtender2); 987 mCollection.addNotificationLifetimeExtender(mExtender3); 988 989 mExtender1.shouldExtendLifetime = true; 990 mExtender2.shouldExtendLifetime = true; 991 992 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); 993 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); 994 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 995 996 // GIVEN a notification gets lifetime-extended by a couple of them 997 mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); 998 assertTrue(mCollection.getAllNotifs().contains(entry2)); 999 clearInvocations(mExtender1, mExtender2, mExtender3); 1000 1001 // WHEN a lifetime extender makes a reentrant call during cancelLifetimeExtension() 1002 mExtender2.onCancelLifetimeExtension = () -> { 1003 mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2); 1004 }; 1005 // This triggers the call to cancelLifetimeExtension() 1006 mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); 1007 1008 // THEN an exception is thrown 1009 } 1010 1011 @Test testRankingIsUpdatedWhenALifetimeExtendedNotifIsReposted()1012 public void testRankingIsUpdatedWhenALifetimeExtendedNotifIsReposted() { 1013 // GIVEN a few lifetime extenders and a couple notifications 1014 mCollection.addNotificationLifetimeExtender(mExtender1); 1015 mCollection.addNotificationLifetimeExtender(mExtender2); 1016 mCollection.addNotificationLifetimeExtender(mExtender3); 1017 1018 mExtender1.shouldExtendLifetime = true; 1019 mExtender2.shouldExtendLifetime = true; 1020 1021 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); 1022 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); 1023 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 1024 1025 // GIVEN a notification gets lifetime-extended by a couple of them 1026 mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); 1027 assertTrue(mCollection.getAllNotifs().contains(entry2)); 1028 clearInvocations(mExtender1, mExtender2, mExtender3); 1029 1030 // WHEN the notification is reposted 1031 NotifEvent notif2a = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88) 1032 .setRank(4747) 1033 .setExplanation("Some new explanation")); 1034 1035 // THEN the notification's ranking is properly updated 1036 assertEquals(notif2a.ranking, entry2.getRanking()); 1037 } 1038 1039 @Test testCancellationReasonIsSetWhenNotifIsCancelled()1040 public void testCancellationReasonIsSetWhenNotifIsCancelled() { 1041 // GIVEN a notification 1042 NotifEvent notif0 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)); 1043 NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key); 1044 1045 // WHEN the notification is retracted 1046 mNoMan.retractNotif(notif0.sbn, REASON_APP_CANCEL); 1047 1048 // THEN the retraction reason is stored on the notif 1049 assertEquals(REASON_APP_CANCEL, entry0.mCancellationReason); 1050 } 1051 1052 @Test testCancellationReasonIsClearedWhenNotifIsUpdated()1053 public void testCancellationReasonIsClearedWhenNotifIsUpdated() { 1054 // GIVEN a notification and a lifetime extender that will preserve it 1055 NotifEvent notif0 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)); 1056 NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key); 1057 mCollection.addNotificationLifetimeExtender(mExtender1); 1058 mExtender1.shouldExtendLifetime = true; 1059 1060 // WHEN the notification is retracted and subsequently reposted 1061 mNoMan.retractNotif(notif0.sbn, REASON_APP_CANCEL); 1062 assertEquals(REASON_APP_CANCEL, entry0.mCancellationReason); 1063 mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)); 1064 1065 // THEN the notification has its cancellation reason cleared 1066 assertEquals(REASON_NOT_CANCELED, entry0.mCancellationReason); 1067 } 1068 1069 @Test testDismissNotificationsRebuildsOnce()1070 public void testDismissNotificationsRebuildsOnce() { 1071 // GIVEN a collection with a couple notifications 1072 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 1073 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag")); 1074 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 1075 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 1076 clearInvocations(mBuildListener); 1077 1078 // WHEN both notifications are manually dismissed together 1079 mCollection.dismissNotifications( 1080 List.of(new Pair<>(entry1, defaultStats(entry1)), 1081 new Pair<>(entry2, defaultStats(entry2)))); 1082 1083 // THEN build list is only called one time 1084 verifyBuiltList(List.of(entry1, entry2)); 1085 } 1086 1087 @Test testDismissNotificationsSentToSystemServer()1088 public void testDismissNotificationsSentToSystemServer() throws RemoteException { 1089 // GIVEN a collection with a couple notifications 1090 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 1091 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag")); 1092 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 1093 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 1094 1095 // WHEN both notifications are manually dismissed together 1096 DismissedByUserStats stats1 = defaultStats(entry1); 1097 DismissedByUserStats stats2 = defaultStats(entry2); 1098 mCollection.dismissNotifications( 1099 List.of(new Pair<>(entry1, defaultStats(entry1)), 1100 new Pair<>(entry2, defaultStats(entry2)))); 1101 1102 // THEN we send the dismissals to system server 1103 verify(mStatusBarService).onNotificationClear( 1104 notif1.sbn.getPackageName(), 1105 notif1.sbn.getTag(), 1106 47, 1107 notif1.sbn.getUser().getIdentifier(), 1108 notif1.sbn.getKey(), 1109 stats1.dismissalSurface, 1110 stats1.dismissalSentiment, 1111 stats1.notificationVisibility); 1112 1113 verify(mStatusBarService).onNotificationClear( 1114 notif2.sbn.getPackageName(), 1115 notif2.sbn.getTag(), 1116 88, 1117 notif2.sbn.getUser().getIdentifier(), 1118 notif2.sbn.getKey(), 1119 stats2.dismissalSurface, 1120 stats2.dismissalSentiment, 1121 stats2.notificationVisibility); 1122 } 1123 1124 @Test testDismissNotificationsMarkedAsDismissed()1125 public void testDismissNotificationsMarkedAsDismissed() { 1126 // GIVEN a collection with a couple notifications 1127 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 1128 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag")); 1129 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 1130 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 1131 1132 // WHEN both notifications are manually dismissed together 1133 mCollection.dismissNotifications( 1134 List.of(new Pair<>(entry1, defaultStats(entry1)), 1135 new Pair<>(entry2, defaultStats(entry2)))); 1136 1137 // THEN the entries are marked as dismissed 1138 assertEquals(DISMISSED, entry1.getDismissState()); 1139 assertEquals(DISMISSED, entry2.getDismissState()); 1140 } 1141 1142 @Test testDismissNotificationssCallsDismissInterceptors()1143 public void testDismissNotificationssCallsDismissInterceptors() { 1144 // GIVEN a collection with notifications with multiple dismiss interceptors 1145 mInterceptor1.shouldInterceptDismissal = true; 1146 mInterceptor2.shouldInterceptDismissal = true; 1147 mInterceptor3.shouldInterceptDismissal = false; 1148 mCollection.addNotificationDismissInterceptor(mInterceptor1); 1149 mCollection.addNotificationDismissInterceptor(mInterceptor2); 1150 mCollection.addNotificationDismissInterceptor(mInterceptor3); 1151 1152 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 1153 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag")); 1154 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 1155 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 1156 1157 // WHEN both notifications are manually dismissed together 1158 mCollection.dismissNotifications( 1159 List.of(new Pair<>(entry1, defaultStats(entry1)), 1160 new Pair<>(entry2, defaultStats(entry2)))); 1161 1162 // THEN all interceptors get checked 1163 verify(mInterceptor1).shouldInterceptDismissal(entry1); 1164 verify(mInterceptor2).shouldInterceptDismissal(entry1); 1165 verify(mInterceptor3).shouldInterceptDismissal(entry1); 1166 verify(mInterceptor1).shouldInterceptDismissal(entry2); 1167 verify(mInterceptor2).shouldInterceptDismissal(entry2); 1168 verify(mInterceptor3).shouldInterceptDismissal(entry2); 1169 1170 assertEquals(List.of(mInterceptor1, mInterceptor2), entry1.mDismissInterceptors); 1171 assertEquals(List.of(mInterceptor1, mInterceptor2), entry2.mDismissInterceptors); 1172 } 1173 1174 @Test testDismissAllNotificationsCallsRebuildOnce()1175 public void testDismissAllNotificationsCallsRebuildOnce() { 1176 // GIVEN a collection with a couple notifications 1177 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 1178 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag")); 1179 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 1180 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 1181 clearInvocations(mBuildListener); 1182 1183 // WHEN all notifications are dismissed for the user who posted both notifs 1184 mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier()); 1185 1186 // THEN build list is only called one time 1187 verifyBuiltList(List.of(entry1, entry2)); 1188 } 1189 1190 @Test testDismissAllNotificationsSentToSystemServer()1191 public void testDismissAllNotificationsSentToSystemServer() throws RemoteException { 1192 // GIVEN a collection with a couple notifications 1193 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 1194 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag")); 1195 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 1196 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 1197 1198 // WHEN all notifications are dismissed for the user who posted both notifs 1199 mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier()); 1200 1201 // THEN we send the dismissal to system server 1202 verify(mStatusBarService).onClearAllNotifications( 1203 entry1.getSbn().getUser().getIdentifier()); 1204 } 1205 1206 @Test testDismissAllNotificationsMarkedAsDismissed()1207 public void testDismissAllNotificationsMarkedAsDismissed() { 1208 // GIVEN a collection with a couple notifications 1209 NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); 1210 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag")); 1211 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); 1212 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 1213 1214 // WHEN all notifications are dismissed for the user who posted both notifs 1215 mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier()); 1216 1217 // THEN the entries are marked as dismissed 1218 assertEquals(DISMISSED, entry1.getDismissState()); 1219 assertEquals(DISMISSED, entry2.getDismissState()); 1220 } 1221 1222 @Test testDismissAllNotificationsDoesNotMarkDismissedUnclearableNotifs()1223 public void testDismissAllNotificationsDoesNotMarkDismissedUnclearableNotifs() { 1224 // GIVEN a collection with one unclearable notification and one clearable notification 1225 NotificationEntryBuilder notifEntryBuilder = buildNotif(TEST_PACKAGE, 47, "myTag"); 1226 notifEntryBuilder.modifyNotification(mContext) 1227 .setFlag(FLAG_NO_CLEAR, true); 1228 NotifEvent unclearabeNotif = mNoMan.postNotif(notifEntryBuilder); 1229 NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag")); 1230 NotificationEntry unclearableEntry = mCollectionListener.getEntry(unclearabeNotif.key); 1231 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); 1232 1233 // WHEN all notifications are dismissed for the user who posted both notifs 1234 mCollection.dismissAllNotifications(unclearableEntry.getSbn().getUser().getIdentifier()); 1235 1236 // THEN only the clearable entry is marked as dismissed 1237 assertEquals(NOT_DISMISSED, unclearableEntry.getDismissState()); 1238 assertEquals(DISMISSED, entry2.getDismissState()); 1239 } 1240 1241 @Test testDismissAllNotificationsCallsDismissInterceptorsOnlyOnUnclearableNotifs()1242 public void testDismissAllNotificationsCallsDismissInterceptorsOnlyOnUnclearableNotifs() { 1243 // GIVEN a collection with multiple dismiss interceptors 1244 mInterceptor1.shouldInterceptDismissal = true; 1245 mInterceptor2.shouldInterceptDismissal = true; 1246 mInterceptor3.shouldInterceptDismissal = false; 1247 mCollection.addNotificationDismissInterceptor(mInterceptor1); 1248 mCollection.addNotificationDismissInterceptor(mInterceptor2); 1249 mCollection.addNotificationDismissInterceptor(mInterceptor3); 1250 1251 // GIVEN a collection with one unclearable and one clearable notification 1252 NotifEvent unclearableNotif = mNoMan.postNotif( 1253 buildNotif(TEST_PACKAGE, 47, "myTag") 1254 .setFlag(mContext, FLAG_NO_CLEAR, true)); 1255 NotificationEntry unclearable = mCollectionListener.getEntry(unclearableNotif.key); 1256 NotifEvent clearableNotif = mNoMan.postNotif( 1257 buildNotif(TEST_PACKAGE, 88, "myTag") 1258 .setFlag(mContext, FLAG_NO_CLEAR, false)); 1259 NotificationEntry clearable = mCollectionListener.getEntry(clearableNotif.key); 1260 1261 // WHEN all notifications are dismissed for the user who posted the notif 1262 mCollection.dismissAllNotifications(clearable.getSbn().getUser().getIdentifier()); 1263 1264 // THEN all interceptors get checked for the unclearable notification 1265 verify(mInterceptor1).shouldInterceptDismissal(unclearable); 1266 verify(mInterceptor2).shouldInterceptDismissal(unclearable); 1267 verify(mInterceptor3).shouldInterceptDismissal(unclearable); 1268 assertEquals(List.of(mInterceptor1, mInterceptor2), unclearable.mDismissInterceptors); 1269 1270 // THEN no interceptors get checked for the clearable notification 1271 verify(mInterceptor1, never()).shouldInterceptDismissal(clearable); 1272 verify(mInterceptor2, never()).shouldInterceptDismissal(clearable); 1273 verify(mInterceptor3, never()).shouldInterceptDismissal(clearable); 1274 } 1275 1276 @Test testClearNotificationDoesntThrowIfMissing()1277 public void testClearNotificationDoesntThrowIfMissing() { 1278 // GIVEN that enough time has passed that we're beyond the forgiveness window 1279 mClock.advanceTime(5001); 1280 1281 // WHEN we get a remove event for a notification we don't know about 1282 final NotificationEntry container = new NotificationEntryBuilder() 1283 .setPkg(TEST_PACKAGE) 1284 .setId(47) 1285 .build(); 1286 mNotifHandler.onNotificationRemoved( 1287 container.getSbn(), 1288 new RankingMap(new Ranking[]{ container.getRanking() })); 1289 1290 // THEN the event is ignored 1291 verify(mCollectionListener, never()).onEntryRemoved(any(NotificationEntry.class), anyInt()); 1292 } 1293 1294 @Test testClearNotificationDoesntThrowIfInForgivenessWindow()1295 public void testClearNotificationDoesntThrowIfInForgivenessWindow() { 1296 // GIVEN that some time has passed but we're still within the initialization forgiveness 1297 // window 1298 mClock.advanceTime(4999); 1299 1300 // WHEN we get a remove event for a notification we don't know about 1301 final NotificationEntry container = new NotificationEntryBuilder() 1302 .setPkg(TEST_PACKAGE) 1303 .setId(47) 1304 .build(); 1305 mNotifHandler.onNotificationRemoved( 1306 container.getSbn(), 1307 new RankingMap(new Ranking[]{ container.getRanking() })); 1308 1309 // THEN no exception is thrown, but no event is fired 1310 verify(mCollectionListener, never()).onEntryRemoved(any(NotificationEntry.class), anyInt()); 1311 } 1312 buildNotif(String pkg, int id, String tag)1313 private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) { 1314 return new NotificationEntryBuilder() 1315 .setPkg(pkg) 1316 .setId(id) 1317 .setTag(tag); 1318 } 1319 buildNotif(String pkg, int id)1320 private static NotificationEntryBuilder buildNotif(String pkg, int id) { 1321 return new NotificationEntryBuilder() 1322 .setPkg(pkg) 1323 .setId(id); 1324 } 1325 defaultStats(NotificationEntry entry)1326 private static DismissedByUserStats defaultStats(NotificationEntry entry) { 1327 return new DismissedByUserStats( 1328 DISMISSAL_SHADE, 1329 DISMISS_SENTIMENT_NEUTRAL, 1330 NotificationVisibility.obtain(entry.getKey(), 7, 2, true)); 1331 } 1332 postNotif(NotificationEntryBuilder builder)1333 private CollectionEvent postNotif(NotificationEntryBuilder builder) { 1334 clearInvocations(mCollectionListener); 1335 NotifEvent rawEvent = mNoMan.postNotif(builder); 1336 verify(mCollectionListener).onEntryAdded(mEntryCaptor.capture()); 1337 return new CollectionEvent(rawEvent, requireNonNull(mEntryCaptor.getValue())); 1338 } 1339 verifyBuiltList(Collection<NotificationEntry> list)1340 private void verifyBuiltList(Collection<NotificationEntry> list) { 1341 verify(mBuildListener).onBuildList(mBuildListCaptor.capture()); 1342 assertEquals(new ArraySet<>(list), new ArraySet<>(mBuildListCaptor.getValue())); 1343 } 1344 1345 private static class RecordingCollectionListener implements NotifCollectionListener { 1346 private final Map<String, NotificationEntry> mLastSeenEntries = new ArrayMap<>(); 1347 1348 @Override onEntryInit(NotificationEntry entry)1349 public void onEntryInit(NotificationEntry entry) { 1350 } 1351 1352 @Override onEntryAdded(NotificationEntry entry)1353 public void onEntryAdded(NotificationEntry entry) { 1354 mLastSeenEntries.put(entry.getKey(), entry); 1355 } 1356 1357 @Override onEntryUpdated(NotificationEntry entry)1358 public void onEntryUpdated(NotificationEntry entry) { 1359 mLastSeenEntries.put(entry.getKey(), entry); 1360 } 1361 1362 @Override onEntryRemoved(NotificationEntry entry, int reason)1363 public void onEntryRemoved(NotificationEntry entry, int reason) { 1364 } 1365 1366 @Override onEntryCleanUp(NotificationEntry entry)1367 public void onEntryCleanUp(NotificationEntry entry) { 1368 } 1369 1370 @Override onRankingApplied()1371 public void onRankingApplied() { 1372 } 1373 1374 @Override onRankingUpdate(RankingMap rankingMap)1375 public void onRankingUpdate(RankingMap rankingMap) { 1376 } 1377 getEntry(String key)1378 public NotificationEntry getEntry(String key) { 1379 if (!mLastSeenEntries.containsKey(key)) { 1380 throw new RuntimeException("Key not found: " + key); 1381 } 1382 return mLastSeenEntries.get(key); 1383 } 1384 } 1385 1386 private static class RecordingLifetimeExtender implements NotifLifetimeExtender { 1387 private final String mName; 1388 1389 public @Nullable OnEndLifetimeExtensionCallback callback; 1390 public boolean shouldExtendLifetime = false; 1391 public @Nullable Runnable onCancelLifetimeExtension; 1392 RecordingLifetimeExtender(String name)1393 private RecordingLifetimeExtender(String name) { 1394 mName = name; 1395 } 1396 1397 @Override getName()1398 public String getName() { 1399 return mName; 1400 } 1401 1402 @Override setCallback(OnEndLifetimeExtensionCallback callback)1403 public void setCallback(OnEndLifetimeExtensionCallback callback) { 1404 this.callback = callback; 1405 } 1406 1407 @Override shouldExtendLifetime( NotificationEntry entry, @CancellationReason int reason)1408 public boolean shouldExtendLifetime( 1409 NotificationEntry entry, 1410 @CancellationReason int reason) { 1411 return shouldExtendLifetime; 1412 } 1413 1414 @Override cancelLifetimeExtension(NotificationEntry entry)1415 public void cancelLifetimeExtension(NotificationEntry entry) { 1416 if (onCancelLifetimeExtension != null) { 1417 onCancelLifetimeExtension.run(); 1418 } 1419 } 1420 } 1421 1422 private static class RecordingDismissInterceptor implements NotifDismissInterceptor { 1423 private final String mName; 1424 1425 public @Nullable OnEndDismissInterception onEndInterceptionCallback; 1426 public boolean shouldInterceptDismissal = false; 1427 RecordingDismissInterceptor(String name)1428 private RecordingDismissInterceptor(String name) { 1429 mName = name; 1430 } 1431 1432 @Override getName()1433 public String getName() { 1434 return mName; 1435 } 1436 1437 @Override setCallback(OnEndDismissInterception callback)1438 public void setCallback(OnEndDismissInterception callback) { 1439 this.onEndInterceptionCallback = callback; 1440 } 1441 1442 @Override shouldInterceptDismissal(NotificationEntry entry)1443 public boolean shouldInterceptDismissal(NotificationEntry entry) { 1444 return shouldInterceptDismissal; 1445 } 1446 1447 @Override cancelDismissInterception(NotificationEntry entry)1448 public void cancelDismissInterception(NotificationEntry entry) { 1449 } 1450 } 1451 1452 /** 1453 * Wrapper around {@link NotifEvent} that adds the NotificationEntry that the collection under 1454 * test creates. 1455 */ 1456 private static class CollectionEvent { 1457 public final String key; 1458 public final StatusBarNotification sbn; 1459 public final Ranking ranking; 1460 public final RankingMap rankingMap; 1461 public final NotificationEntry entry; 1462 CollectionEvent(NotifEvent rawEvent, NotificationEntry entry)1463 private CollectionEvent(NotifEvent rawEvent, NotificationEntry entry) { 1464 this.key = rawEvent.key; 1465 this.sbn = rawEvent.sbn; 1466 this.ranking = rawEvent.ranking; 1467 this.rankingMap = rawEvent.rankingMap; 1468 this.entry = entry; 1469 } 1470 } 1471 1472 private static final String TEST_PACKAGE = "com.android.test.collection"; 1473 private static final String TEST_PACKAGE2 = "com.android.test.collection2"; 1474 1475 private static final String GROUP_1 = "group_1"; 1476 } 1477