blob: e1beb34db6bdb54a8aa631781d5cfc21a3bbd5f9 [file] [log] [blame]
Ned Burnsf098dbf2019-09-13 19:17:53 -04001/*
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
17package com.android.systemui.statusbar.notification.collection;
18
19import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
20import static android.service.notification.NotificationListenerService.REASON_CLICK;
21
22import static com.android.internal.util.Preconditions.checkNotNull;
23import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN;
24
25import static org.junit.Assert.assertEquals;
26import static org.junit.Assert.assertFalse;
27import static org.junit.Assert.assertNotEquals;
28import static org.junit.Assert.assertNotNull;
29import static org.junit.Assert.assertTrue;
30import static org.mockito.ArgumentMatchers.anyInt;
31import static org.mockito.ArgumentMatchers.eq;
32import static org.mockito.Mockito.clearInvocations;
33import static org.mockito.Mockito.never;
34import static org.mockito.Mockito.verify;
35
36import android.annotation.Nullable;
37import android.os.RemoteException;
38import android.service.notification.NotificationListenerService.Ranking;
39import android.service.notification.NotificationListenerService.RankingMap;
40import android.service.notification.NotificationStats;
41import android.service.notification.StatusBarNotification;
42import android.testing.AndroidTestingRunner;
43import android.testing.TestableLooper;
44import android.util.ArrayMap;
45
46import androidx.test.filters.SmallTest;
47
48import com.android.internal.statusbar.IStatusBarService;
49import com.android.internal.statusbar.NotificationVisibility;
50import com.android.systemui.SysuiTestCase;
51import com.android.systemui.statusbar.NotificationEntryBuilder;
52import com.android.systemui.statusbar.NotificationListener;
53import com.android.systemui.statusbar.NotificationListener.NotifServiceListener;
54import com.android.systemui.statusbar.RankingBuilder;
55import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
56import com.android.systemui.util.Assert;
57
58import org.junit.Before;
59import org.junit.Test;
60import org.junit.runner.RunWith;
61import org.mockito.ArgumentCaptor;
62import org.mockito.Captor;
63import org.mockito.Mock;
64import org.mockito.MockitoAnnotations;
65import org.mockito.Spy;
66
67import java.util.Arrays;
68import java.util.Map;
69
70@SmallTest
71@RunWith(AndroidTestingRunner.class)
72@TestableLooper.RunWithLooper
73public class NotifCollectionTest extends SysuiTestCase {
74
75 @Mock private IStatusBarService mStatusBarService;
76 @Mock private NotificationListener mListenerService;
77 @Spy private RecordingCollectionListener mCollectionListener;
78
79 @Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1");
80 @Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2");
81 @Spy private RecordingLifetimeExtender mExtender3 = new RecordingLifetimeExtender("Extender3");
82
83 @Captor private ArgumentCaptor<NotifServiceListener> mListenerCaptor;
84 @Captor private ArgumentCaptor<NotificationEntry> mEntryCaptor;
85
86 private NotifCollection mCollection;
87 private NotifServiceListener mServiceListener;
88
89 private NoManSimulator mNoMan;
90
91 @Before
92 public void setUp() {
93 MockitoAnnotations.initMocks(this);
94 Assert.sMainLooper = TestableLooper.get(this).getLooper();
95
96 mCollection = new NotifCollection(mStatusBarService);
97 mCollection.attach(mListenerService);
98 mCollection.addCollectionListener(mCollectionListener);
99
100 // Capture the listener object that the collection registers with the listener service so
101 // we can simulate listener service events in tests below
102 verify(mListenerService).setDownstreamListener(mListenerCaptor.capture());
103 mServiceListener = checkNotNull(mListenerCaptor.getValue());
104
105 mNoMan = new NoManSimulator(mServiceListener);
106 }
107
108 @Test
109 public void testEventDispatchedWhenNotifPosted() {
110 // WHEN a notification is posted
111 PostedNotif notif1 = mNoMan.postNotif(
112 buildNotif(TEST_PACKAGE, 3)
113 .setRank(4747));
114
115 // THEN the listener is notified
116 verify(mCollectionListener).onEntryAdded(mEntryCaptor.capture());
117
118 NotificationEntry entry = mEntryCaptor.getValue();
Evan Laird9afe7662019-10-16 17:16:39 -0400119 assertEquals(notif1.key, entry.getKey());
120 assertEquals(notif1.sbn, entry.getSbn());
121 assertEquals(notif1.ranking, entry.getRanking());
Ned Burnsf098dbf2019-09-13 19:17:53 -0400122 }
123
124 @Test
125 public void testEventDispatchedWhenNotifUpdated() {
126 // GIVEN a collection with one notif
127 mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)
128 .setRank(4747));
129
130 // WHEN the notif is reposted
131 PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)
132 .setRank(89));
133
134 // THEN the listener is notified
135 verify(mCollectionListener).onEntryUpdated(mEntryCaptor.capture());
136
137 NotificationEntry entry = mEntryCaptor.getValue();
Evan Laird9afe7662019-10-16 17:16:39 -0400138 assertEquals(notif2.key, entry.getKey());
139 assertEquals(notif2.sbn, entry.getSbn());
140 assertEquals(notif2.ranking, entry.getRanking());
Ned Burnsf098dbf2019-09-13 19:17:53 -0400141 }
142
143 @Test
144 public void testEventDispatchedWhenNotifRemoved() {
145 // GIVEN a collection with one notif
146 mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3));
147 clearInvocations(mCollectionListener);
148
149 PostedNotif notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
150 NotificationEntry entry = mCollectionListener.getEntry(notif.key);
151 clearInvocations(mCollectionListener);
152
153 // WHEN a notif is retracted
154 mNoMan.retractNotif(notif.sbn, REASON_APP_CANCEL);
155
156 // THEN the listener is notified
157 verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL, false);
Evan Laird9afe7662019-10-16 17:16:39 -0400158 assertEquals(notif.sbn, entry.getSbn());
159 assertEquals(notif.ranking, entry.getRanking());
Ned Burnsf098dbf2019-09-13 19:17:53 -0400160 }
161
162 @Test
163 public void testRankingsAreUpdatedForOtherNotifs() {
164 // GIVEN a collection with one notif
165 PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)
166 .setRank(47));
167 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
168
169 // WHEN a new notif is posted, triggering a rerank
170 mNoMan.setRanking(notif1.sbn.getKey(), new RankingBuilder(notif1.ranking)
171 .setRank(56)
172 .build());
173 mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 77));
174
175 // THEN the ranking is updated on the first entry
Evan Laird9afe7662019-10-16 17:16:39 -0400176 assertEquals(56, entry1.getRanking().getRank());
Ned Burnsf098dbf2019-09-13 19:17:53 -0400177 }
178
179 @Test
180 public void testRankingUpdateIsProperlyIssuedToEveryone() {
181 // GIVEN a collection with a couple notifs
182 PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)
183 .setRank(3));
184 PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 8)
185 .setRank(2));
186 PostedNotif notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 77)
187 .setRank(1));
188
189 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
190 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
191 NotificationEntry entry3 = mCollectionListener.getEntry(notif3.key);
192
193 // WHEN a ranking update is delivered
194 Ranking newRanking1 = new RankingBuilder(notif1.ranking)
195 .setRank(4)
196 .setExplanation("Foo bar")
197 .build();
198 Ranking newRanking2 = new RankingBuilder(notif2.ranking)
199 .setRank(5)
200 .setExplanation("baz buzz")
201 .build();
202 Ranking newRanking3 = new RankingBuilder(notif3.ranking)
203 .setRank(6)
204 .setExplanation("Penguin pizza")
205 .build();
206
207 mNoMan.setRanking(notif1.sbn.getKey(), newRanking1);
208 mNoMan.setRanking(notif2.sbn.getKey(), newRanking2);
209 mNoMan.setRanking(notif3.sbn.getKey(), newRanking3);
210 mNoMan.issueRankingUpdate();
211
212 // THEN all of the NotifEntries have their rankings properly updated
Evan Laird9afe7662019-10-16 17:16:39 -0400213 assertEquals(newRanking1, entry1.getRanking());
214 assertEquals(newRanking2, entry2.getRanking());
215 assertEquals(newRanking3, entry3.getRanking());
Ned Burnsf098dbf2019-09-13 19:17:53 -0400216 }
217
218 @Test
219 public void testNotifEntriesAreNotPersistedAcrossRemovalAndReposting() {
220 // GIVEN a notification that has been posted
221 PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3));
222 NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
223
224 // WHEN the notification is retracted and then reposted
225 mNoMan.retractNotif(notif1.sbn, REASON_APP_CANCEL);
226 mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3));
227
228 // THEN the new NotificationEntry is a new object
229 NotificationEntry entry2 = mCollectionListener.getEntry(notif1.key);
230 assertNotEquals(entry2, entry1);
231 }
232
233 @Test
234 public void testDismissNotification() throws RemoteException {
235 // GIVEN a collection with a couple notifications and a lifetime extender
236 mCollection.addNotificationLifetimeExtender(mExtender1);
237
238 PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
239 PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
240 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
241
242 // WHEN a notification is manually dismissed
243 DismissedByUserStats stats = new DismissedByUserStats(
244 NotificationStats.DISMISSAL_SHADE,
245 NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
Evan Laird9afe7662019-10-16 17:16:39 -0400246 NotificationVisibility.obtain(entry2.getKey(), 7, 2, true));
Ned Burnsf098dbf2019-09-13 19:17:53 -0400247
248 mCollection.dismissNotification(entry2, REASON_CLICK, stats);
249
250 // THEN we check for lifetime extension
251 verify(mExtender1).shouldExtendLifetime(entry2, REASON_CLICK);
252
253 // THEN we send the dismissal to system server
254 verify(mStatusBarService).onNotificationClear(
255 notif2.sbn.getPackageName(),
256 notif2.sbn.getTag(),
257 88,
258 notif2.sbn.getUser().getIdentifier(),
259 notif2.sbn.getKey(),
260 stats.dismissalSurface,
261 stats.dismissalSentiment,
262 stats.notificationVisibility);
263
264 // THEN we fire a remove event
265 verify(mCollectionListener).onEntryRemoved(entry2, REASON_CLICK, true);
266 }
267
268 @Test(expected = IllegalStateException.class)
269 public void testDismissingNonExistentNotificationThrows() {
270 // GIVEN a collection that originally had three notifs, but where one was dismissed
271 PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
272 PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
273 PostedNotif notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 99));
274 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
275 mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
276
277 // WHEN we try to dismiss a notification that isn't present
278 mCollection.dismissNotification(
279 entry2,
280 REASON_CLICK,
281 new DismissedByUserStats(0, 0, NotificationVisibility.obtain("foo", 47, 3, true)));
282
283 // THEN an exception is thrown
284 }
285
286 @Test
287 public void testLifetimeExtendersAreQueriedWhenNotifRemoved() {
288 // GIVEN a couple notifications and a few lifetime extenders
289 mExtender1.shouldExtendLifetime = true;
290 mExtender2.shouldExtendLifetime = true;
291
292 mCollection.addNotificationLifetimeExtender(mExtender1);
293 mCollection.addNotificationLifetimeExtender(mExtender2);
294 mCollection.addNotificationLifetimeExtender(mExtender3);
295
296 PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
297 PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
298 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
299
300 // WHEN a notification is removed
301 mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
302
303 // THEN each extender is asked whether to extend, even if earlier ones return true
304 verify(mExtender1).shouldExtendLifetime(entry2, REASON_UNKNOWN);
305 verify(mExtender2).shouldExtendLifetime(entry2, REASON_UNKNOWN);
306 verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN);
307
308 // THEN the entry is not removed
309 assertTrue(mCollection.getNotifs().contains(entry2));
310
311 // THEN the entry properly records all extenders that returned true
312 assertEquals(Arrays.asList(mExtender1, mExtender2), entry2.mLifetimeExtenders);
313 }
314
315 @Test
316 public void testWhenLastLifetimeExtenderExpiresAllAreReQueried() {
317 // GIVEN a couple notifications and a few lifetime extenders
318 mExtender2.shouldExtendLifetime = true;
319
320 mCollection.addNotificationLifetimeExtender(mExtender1);
321 mCollection.addNotificationLifetimeExtender(mExtender2);
322 mCollection.addNotificationLifetimeExtender(mExtender3);
323
324 PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
325 PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
326 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
327
328 // GIVEN a notification gets lifetime-extended by one of them
329 mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
330 assertTrue(mCollection.getNotifs().contains(entry2));
331 clearInvocations(mExtender1, mExtender2, mExtender3);
332
333 // WHEN the last active extender expires (but new ones become active)
334 mExtender1.shouldExtendLifetime = true;
335 mExtender2.shouldExtendLifetime = false;
336 mExtender3.shouldExtendLifetime = true;
337 mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
338
339 // THEN each extender is re-queried
340 verify(mExtender1).shouldExtendLifetime(entry2, REASON_UNKNOWN);
341 verify(mExtender2).shouldExtendLifetime(entry2, REASON_UNKNOWN);
342 verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN);
343
344 // THEN the entry is not removed
345 assertTrue(mCollection.getNotifs().contains(entry2));
346
347 // THEN the entry properly records all extenders that returned true
348 assertEquals(Arrays.asList(mExtender1, mExtender3), entry2.mLifetimeExtenders);
349 }
350
351 @Test
352 public void testExtendersAreNotReQueriedUntilFinalActiveExtenderExpires() {
353 // GIVEN a couple notifications and a few lifetime extenders
354 mExtender1.shouldExtendLifetime = true;
355 mExtender2.shouldExtendLifetime = true;
356
357 mCollection.addNotificationLifetimeExtender(mExtender1);
358 mCollection.addNotificationLifetimeExtender(mExtender2);
359 mCollection.addNotificationLifetimeExtender(mExtender3);
360
361 PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
362 PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
363 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
364
365 // GIVEN a notification gets lifetime-extended by a couple of them
366 mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
367 assertTrue(mCollection.getNotifs().contains(entry2));
368 clearInvocations(mExtender1, mExtender2, mExtender3);
369
370 // WHEN one (but not all) of the extenders expires
371 mExtender2.shouldExtendLifetime = false;
372 mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
373
374 // THEN the entry is not removed
375 assertTrue(mCollection.getNotifs().contains(entry2));
376
377 // THEN we don't re-query the extenders
378 verify(mExtender1, never()).shouldExtendLifetime(eq(entry2), anyInt());
379 verify(mExtender2, never()).shouldExtendLifetime(eq(entry2), anyInt());
380 verify(mExtender3, never()).shouldExtendLifetime(eq(entry2), anyInt());
381
382 // THEN the entry properly records all extenders that returned true
383 assertEquals(Arrays.asList(mExtender1), entry2.mLifetimeExtenders);
384 }
385
386 @Test
387 public void testNotificationIsRemovedWhenAllLifetimeExtendersExpire() {
388 // GIVEN a couple notifications and a few lifetime extenders
389 mExtender1.shouldExtendLifetime = true;
390 mExtender2.shouldExtendLifetime = true;
391
392 mCollection.addNotificationLifetimeExtender(mExtender1);
393 mCollection.addNotificationLifetimeExtender(mExtender2);
394 mCollection.addNotificationLifetimeExtender(mExtender3);
395
396 PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
397 PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
398 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
399
400 // GIVEN a notification gets lifetime-extended by a couple of them
401 mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
402 assertTrue(mCollection.getNotifs().contains(entry2));
403 clearInvocations(mExtender1, mExtender2, mExtender3);
404
405 // WHEN all of the active extenders expire
406 mExtender2.shouldExtendLifetime = false;
407 mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
408 mExtender1.shouldExtendLifetime = false;
409 mExtender1.callback.onEndLifetimeExtension(mExtender1, entry2);
410
411 // THEN the entry removed
412 assertFalse(mCollection.getNotifs().contains(entry2));
413 verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN, false);
414 }
415
416 @Test
417 public void testLifetimeExtensionIsCanceledWhenNotifIsUpdated() {
418 // GIVEN a few lifetime extenders and a couple notifications
419 mCollection.addNotificationLifetimeExtender(mExtender1);
420 mCollection.addNotificationLifetimeExtender(mExtender2);
421 mCollection.addNotificationLifetimeExtender(mExtender3);
422
423 mExtender1.shouldExtendLifetime = true;
424 mExtender2.shouldExtendLifetime = true;
425
426 PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
427 PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
428 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
429
430 // GIVEN a notification gets lifetime-extended by a couple of them
431 mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
432 assertTrue(mCollection.getNotifs().contains(entry2));
433 clearInvocations(mExtender1, mExtender2, mExtender3);
434
435 // WHEN the notification is reposted
436 mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
437
438 // THEN all of the active lifetime extenders are canceled
439 verify(mExtender1).cancelLifetimeExtension(entry2);
440 verify(mExtender2).cancelLifetimeExtension(entry2);
441
442 // THEN the notification is still present
443 assertTrue(mCollection.getNotifs().contains(entry2));
444 }
445
446 @Test(expected = IllegalStateException.class)
447 public void testReentrantCallsToLifetimeExtendersThrow() {
448 // GIVEN a few lifetime extenders and a couple notifications
449 mCollection.addNotificationLifetimeExtender(mExtender1);
450 mCollection.addNotificationLifetimeExtender(mExtender2);
451 mCollection.addNotificationLifetimeExtender(mExtender3);
452
453 mExtender1.shouldExtendLifetime = true;
454 mExtender2.shouldExtendLifetime = true;
455
456 PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
457 PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
458 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
459
460 // GIVEN a notification gets lifetime-extended by a couple of them
461 mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
462 assertTrue(mCollection.getNotifs().contains(entry2));
463 clearInvocations(mExtender1, mExtender2, mExtender3);
464
465 // WHEN a lifetime extender makes a reentrant call during cancelLifetimeExtension()
466 mExtender2.onCancelLifetimeExtension = () -> {
467 mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
468 };
469 // This triggers the call to cancelLifetimeExtension()
470 mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
471
472 // THEN an exception is thrown
473 }
474
475 @Test
476 public void testRankingIsUpdatedWhenALifetimeExtendedNotifIsReposted() {
477 // GIVEN a few lifetime extenders and a couple notifications
478 mCollection.addNotificationLifetimeExtender(mExtender1);
479 mCollection.addNotificationLifetimeExtender(mExtender2);
480 mCollection.addNotificationLifetimeExtender(mExtender3);
481
482 mExtender1.shouldExtendLifetime = true;
483 mExtender2.shouldExtendLifetime = true;
484
485 PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47));
486 PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88));
487 NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
488
489 // GIVEN a notification gets lifetime-extended by a couple of them
490 mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
491 assertTrue(mCollection.getNotifs().contains(entry2));
492 clearInvocations(mExtender1, mExtender2, mExtender3);
493
494 // WHEN the notification is reposted
495 PostedNotif notif2a = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)
496 .setRank(4747)
497 .setExplanation("Some new explanation"));
498
499 // THEN the notification's ranking is properly updated
Evan Laird9afe7662019-10-16 17:16:39 -0400500 assertEquals(notif2a.ranking, entry2.getRanking());
Ned Burnsf098dbf2019-09-13 19:17:53 -0400501 }
502
503 private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) {
504 return new NotificationEntryBuilder()
505 .setPkg(pkg)
506 .setId(id)
507 .setTag(tag);
508 }
509
510 private static NotificationEntryBuilder buildNotif(String pkg, int id) {
511 return new NotificationEntryBuilder()
512 .setPkg(pkg)
513 .setId(id);
514 }
515
516 private static class NoManSimulator {
517 private final NotifServiceListener mListener;
518 private final Map<String, Ranking> mRankings = new ArrayMap<>();
519
520 private NoManSimulator(
521 NotifServiceListener listener) {
522 mListener = listener;
523 }
524
525 PostedNotif postNotif(NotificationEntryBuilder builder) {
526 NotificationEntry entry = builder.build();
Evan Laird9afe7662019-10-16 17:16:39 -0400527 mRankings.put(entry.getKey(), entry.getRanking());
528 mListener.onNotificationPosted(entry.getSbn(), buildRankingMap());
529 return new PostedNotif(entry.getSbn(), entry.getRanking());
Ned Burnsf098dbf2019-09-13 19:17:53 -0400530 }
531
532 void retractNotif(StatusBarNotification sbn, int reason) {
533 assertNotNull(mRankings.remove(sbn.getKey()));
534 mListener.onNotificationRemoved(sbn, buildRankingMap(), reason);
535 }
536
537 void issueRankingUpdate() {
538 mListener.onNotificationRankingUpdate(buildRankingMap());
539 }
540
541 void setRanking(String key, Ranking ranking) {
542 mRankings.put(key, ranking);
543 }
544
545 private RankingMap buildRankingMap() {
546 return new RankingMap(mRankings.values().toArray(new Ranking[0]));
547 }
548 }
549
550 private static class PostedNotif {
551 public final String key;
552 public final StatusBarNotification sbn;
553 public final Ranking ranking;
554
555 private PostedNotif(StatusBarNotification sbn,
556 Ranking ranking) {
557 this.key = sbn.getKey();
558 this.sbn = sbn;
559 this.ranking = ranking;
560 }
561 }
562
563 private static class RecordingCollectionListener implements NotifCollectionListener {
564 private final Map<String, NotificationEntry> mLastSeenEntries = new ArrayMap<>();
565
566 @Override
567 public void onEntryAdded(NotificationEntry entry) {
Evan Laird9afe7662019-10-16 17:16:39 -0400568 mLastSeenEntries.put(entry.getKey(), entry);
Ned Burnsf098dbf2019-09-13 19:17:53 -0400569 }
570
571 @Override
572 public void onEntryUpdated(NotificationEntry entry) {
573 }
574
575 @Override
576 public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) {
577 }
578
579 public NotificationEntry getEntry(String key) {
580 if (!mLastSeenEntries.containsKey(key)) {
581 throw new RuntimeException("Key not found: " + key);
582 }
583 return mLastSeenEntries.get(key);
584 }
585 }
586
587 private static class RecordingLifetimeExtender implements NotifLifetimeExtender {
588 private final String mName;
589
590 public @Nullable OnEndLifetimeExtensionCallback callback;
591 public boolean shouldExtendLifetime = false;
592 public @Nullable Runnable onCancelLifetimeExtension;
593
594 private RecordingLifetimeExtender(String name) {
595 mName = name;
596 }
597
598 @Override
599 public String getName() {
600 return mName;
601 }
602
603 @Override
604 public void setCallback(OnEndLifetimeExtensionCallback callback) {
605 this.callback = callback;
606 }
607
608 @Override
609 public boolean shouldExtendLifetime(
610 NotificationEntry entry,
611 @CancellationReason int reason) {
612 return shouldExtendLifetime;
613 }
614
615 @Override
616 public void cancelLifetimeExtension(NotificationEntry entry) {
617 if (onCancelLifetimeExtension != null) {
618 onCancelLifetimeExtension.run();
619 }
620 }
621 }
622
623 private static final String TEST_PACKAGE = "com.android.test.collection";
624 private static final String TEST_PACKAGE2 = "com.android.test.collection2";
625}