blob: 27ca18cd80339483436c3d36439b26ad92f0641a [file] [log] [blame]
Ned Burns77050aa2019-10-17 21:55:24 -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
Beverlyb6f4dc22020-01-10 14:58:20 -050019import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree;
Ned Burns77050aa2019-10-17 21:55:24 -040020
21import static org.junit.Assert.assertEquals;
Beverly48220972020-01-13 16:55:38 -050022import static org.junit.Assert.assertNotNull;
Ned Burns77050aa2019-10-17 21:55:24 -040023import static org.junit.Assert.assertNull;
24import static org.mockito.ArgumentMatchers.any;
25import static org.mockito.ArgumentMatchers.anyList;
26import static org.mockito.ArgumentMatchers.anyLong;
27import static org.mockito.ArgumentMatchers.eq;
28import static org.mockito.Mockito.atLeastOnce;
29import static org.mockito.Mockito.clearInvocations;
30import static org.mockito.Mockito.inOrder;
Beverlyb6f4dc22020-01-10 14:58:20 -050031import static org.mockito.Mockito.mock;
Beverly142c5732019-12-11 14:26:49 -050032import static org.mockito.Mockito.never;
Ned Burns77050aa2019-10-17 21:55:24 -040033import static org.mockito.Mockito.spy;
34import static org.mockito.Mockito.verify;
35
36import android.testing.AndroidTestingRunner;
37import android.testing.TestableLooper;
38import android.util.ArrayMap;
Ned Burns77050aa2019-10-17 21:55:24 -040039
40import androidx.test.filters.SmallTest;
41
Beverlyb6f4dc22020-01-10 14:58:20 -050042import com.android.systemui.DumpController;
Ned Burns77050aa2019-10-17 21:55:24 -040043import com.android.systemui.SysuiTestCase;
Ned Burnsc0cd2832020-01-10 00:37:03 -050044import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener;
Ned Burns77050aa2019-10-17 21:55:24 -040045import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
46import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
47import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
Ned Burns7d2e9c12020-01-21 17:47:16 -050048import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger;
Ned Burns77050aa2019-10-17 21:55:24 -040049import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
50import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
51import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
Beverly48220972020-01-13 16:55:38 -050052import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
Ned Burns012048d2020-01-08 19:57:30 -050053import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
Ned Burns77050aa2019-10-17 21:55:24 -040054import com.android.systemui.util.Assert;
55import com.android.systemui.util.time.FakeSystemClock;
56
57import org.junit.Before;
58import org.junit.Test;
59import org.junit.runner.RunWith;
60import org.mockito.ArgumentCaptor;
61import org.mockito.Captor;
62import org.mockito.InOrder;
63import org.mockito.Mock;
64import org.mockito.Mockito;
65import org.mockito.MockitoAnnotations;
66import org.mockito.Spy;
67
68import java.util.ArrayList;
69import java.util.Arrays;
70import java.util.Collections;
71import java.util.List;
72import java.util.Map;
Daulet Zhanguzind0549ae2020-01-03 11:08:54 +000073import java.util.Objects;
Ned Burns77050aa2019-10-17 21:55:24 -040074import java.util.stream.Collectors;
75
76@SmallTest
77@RunWith(AndroidTestingRunner.class)
78@TestableLooper.RunWithLooper
Ned Burnsc0cd2832020-01-10 00:37:03 -050079public class ShadeListBuilderTest extends SysuiTestCase {
Ned Burns77050aa2019-10-17 21:55:24 -040080
Ned Burnsc0cd2832020-01-10 00:37:03 -050081 private ShadeListBuilder mListBuilder;
Ned Burns77050aa2019-10-17 21:55:24 -040082 private FakeSystemClock mSystemClock = new FakeSystemClock();
83
Ned Burns7d2e9c12020-01-21 17:47:16 -050084 @Mock private ShadeListBuilderLogger mLogger;
Ned Burns77050aa2019-10-17 21:55:24 -040085 @Mock private NotifCollection mNotifCollection;
86 @Spy private OnBeforeTransformGroupsListener mOnBeforeTransformGroupsListener;
87 @Spy private OnBeforeSortListener mOnBeforeSortListener;
88 @Spy private OnBeforeRenderListListener mOnBeforeRenderListListener;
89 @Spy private OnRenderListListener mOnRenderListListener = list -> mBuiltList = list;
90
91 @Captor private ArgumentCaptor<CollectionReadyForBuildListener> mBuildListenerCaptor;
92
93 private CollectionReadyForBuildListener mReadyForBuildListener;
94 private List<NotificationEntryBuilder> mPendingSet = new ArrayList<>();
95 private List<NotificationEntry> mEntrySet = new ArrayList<>();
96 private List<ListEntry> mBuiltList;
97
98 private Map<String, Integer> mNextIdMap = new ArrayMap<>();
99 private int mNextRank = 0;
100
101 @Before
102 public void setUp() {
103 MockitoAnnotations.initMocks(this);
104 Assert.sMainLooper = TestableLooper.get(this).getLooper();
105
Ned Burns7d2e9c12020-01-21 17:47:16 -0500106 mListBuilder = new ShadeListBuilder(mSystemClock, mLogger, mock(DumpController.class));
Ned Burns77050aa2019-10-17 21:55:24 -0400107 mListBuilder.setOnRenderListListener(mOnRenderListListener);
108
109 mListBuilder.attach(mNotifCollection);
110
111 Mockito.verify(mNotifCollection).setBuildListener(mBuildListenerCaptor.capture());
Daulet Zhanguzind0549ae2020-01-03 11:08:54 +0000112 mReadyForBuildListener = Objects.requireNonNull(mBuildListenerCaptor.getValue());
Ned Burns77050aa2019-10-17 21:55:24 -0400113 }
114
115 @Test
116 public void testNotifsAreSortedByRankAndWhen() {
117 // GIVEN a simple pipeline
118
119 // WHEN a series of notifs with jumbled ranks are added
120 addNotif(0, PACKAGE_1).setRank(2);
121 addNotif(1, PACKAGE_2).setRank(4).modifyNotification(mContext).setWhen(22);
122 addNotif(2, PACKAGE_3).setRank(4).modifyNotification(mContext).setWhen(33);
123 addNotif(3, PACKAGE_3).setRank(3);
124 addNotif(4, PACKAGE_5).setRank(4).modifyNotification(mContext).setWhen(11);
125 addNotif(5, PACKAGE_3).setRank(1);
126 addNotif(6, PACKAGE_1).setRank(0);
127 dispatchBuild();
128
Ned Burns5d1f59e2019-11-14 15:55:23 -0500129 // The final output is sorted based first by rank and then by when
Ned Burns77050aa2019-10-17 21:55:24 -0400130 verifyBuiltList(
131 notif(6),
132 notif(5),
133 notif(0),
134 notif(3),
135 notif(2),
136 notif(1),
137 notif(4)
138 );
139 }
140
141 @Test
142 public void testNotifsAreGrouped() {
143 // GIVEN a simple pipeline
144
145 // WHEN a group is added
146 addGroupChild(0, PACKAGE_1, GROUP_1);
147 addGroupChild(1, PACKAGE_1, GROUP_1);
148 addGroupChild(2, PACKAGE_1, GROUP_1);
149 addGroupSummary(3, PACKAGE_1, GROUP_1);
150 dispatchBuild();
151
152 // THEN the notifs are grouped together
153 verifyBuiltList(
154 group(
155 summary(3),
156 child(0),
157 child(1),
158 child(2)
159 )
160 );
161 }
162
163 @Test
164 public void testNotifsWithDifferentGroupKeysAreGrouped() {
165 // GIVEN a simple pipeline
166
167 // WHEN a package posts two different groups
168 addGroupChild(0, PACKAGE_1, GROUP_1);
169 addGroupChild(1, PACKAGE_1, GROUP_2);
170 addGroupSummary(2, PACKAGE_1, GROUP_2);
171 addGroupChild(3, PACKAGE_1, GROUP_2);
172 addGroupChild(4, PACKAGE_1, GROUP_1);
173 addGroupChild(5, PACKAGE_1, GROUP_2);
174 addGroupChild(6, PACKAGE_1, GROUP_1);
175 addGroupSummary(7, PACKAGE_1, GROUP_1);
176 dispatchBuild();
177
178 // THEN the groups are separated separately
179 verifyBuiltList(
180 group(
181 summary(2),
182 child(1),
183 child(3),
184 child(5)
185 ),
186 group(
187 summary(7),
188 child(0),
189 child(4),
190 child(6)
191 )
192 );
193 }
194
195 @Test
196 public void testNotifsNotifChildrenAreSorted() {
197 // GIVEN a simple pipeline
198
199 // WHEN a group is added
200 addGroupChild(0, PACKAGE_1, GROUP_1).setRank(4);
201 addGroupChild(1, PACKAGE_1, GROUP_1).setRank(2)
202 .modifyNotification(mContext).setWhen(11);
203 addGroupChild(2, PACKAGE_1, GROUP_1).setRank(1);
204 addGroupChild(3, PACKAGE_1, GROUP_1).setRank(2)
205 .modifyNotification(mContext).setWhen(33);
206 addGroupChild(4, PACKAGE_1, GROUP_1).setRank(2)
207 .modifyNotification(mContext).setWhen(22);
208 addGroupChild(5, PACKAGE_1, GROUP_1).setRank(0);
209 addGroupSummary(6, PACKAGE_1, GROUP_1).setRank(3);
210 dispatchBuild();
211
Ned Burns5d1f59e2019-11-14 15:55:23 -0500212 // THEN the children are sorted by rank and when
Ned Burns77050aa2019-10-17 21:55:24 -0400213 verifyBuiltList(
214 group(
215 summary(6),
216 child(5),
217 child(2),
218 child(3),
219 child(4),
220 child(1),
221 child(0)
222 )
223 );
224 }
225
226 @Test
227 public void testDuplicateGroupSummariesAreDiscarded() {
228 // GIVEN a simple pipeline
229
230 // WHEN a group with multiple summaries is added
231 addNotif(0, PACKAGE_3);
232 addGroupChild(1, PACKAGE_1, GROUP_1);
233 addGroupChild(2, PACKAGE_1, GROUP_1);
234 addGroupSummary(3, PACKAGE_1, GROUP_1).setPostTime(22);
235 addGroupSummary(4, PACKAGE_1, GROUP_1).setPostTime(33);
236 addNotif(5, PACKAGE_2);
237 addGroupSummary(6, PACKAGE_1, GROUP_1).setPostTime(11);
238 addGroupChild(7, PACKAGE_1, GROUP_1);
239 dispatchBuild();
240
241 // THEN only most recent summary is used
242 verifyBuiltList(
243 notif(0),
244 group(
245 summary(4),
246 child(1),
247 child(2),
248 child(7)
249 ),
250 notif(5)
251 );
252
253 // THEN the extra summaries have their parents set to null
254 assertNull(mEntrySet.get(3).getParent());
255 assertNull(mEntrySet.get(6).getParent());
256 }
257
258 @Test
259 public void testGroupsWithNoSummaryAreUngrouped() {
260 // GIVEN a group with no summary
261 addNotif(0, PACKAGE_2);
262 addGroupChild(1, PACKAGE_4, GROUP_2);
263 addGroupChild(2, PACKAGE_4, GROUP_2);
264 addGroupChild(3, PACKAGE_4, GROUP_2);
265 addGroupChild(4, PACKAGE_4, GROUP_2);
266
267 // WHEN we build the list
268 dispatchBuild();
269
270 // THEN the children aren't grouped
271 verifyBuiltList(
272 notif(0),
273 notif(1),
274 notif(2),
275 notif(3),
276 notif(4)
277 );
278 }
279
280 @Test
281 public void testGroupsWithNoChildrenAreUngrouped() {
282 // GIVEN a group with a summary but no children
283 addGroupSummary(0, PACKAGE_5, GROUP_1);
284 addNotif(1, PACKAGE_5);
285 addNotif(2, PACKAGE_1);
286
287 // WHEN we build the list
288 dispatchBuild();
289
290 // THEN the summary isn't grouped but is still added to the final list
291 verifyBuiltList(
292 notif(0),
293 notif(1),
294 notif(2)
295 );
296 }
297
298 @Test
299 public void testGroupsWithTooFewChildrenAreSplitUp() {
300 // GIVEN a group with one child
301 addGroupChild(0, PACKAGE_2, GROUP_1);
302 addGroupSummary(1, PACKAGE_2, GROUP_1);
303
304 // WHEN we build the list
305 dispatchBuild();
306
307 // THEN the child is added at top level and the summary is discarded
308 verifyBuiltList(
309 notif(0)
310 );
311
312 assertNull(mEntrySet.get(1).getParent());
313 }
314
315 @Test
316 public void testGroupsWhoLoseChildrenMidPipelineAreSplitUp() {
317 // GIVEN a group with two children
318 addGroupChild(0, PACKAGE_2, GROUP_1);
319 addGroupSummary(1, PACKAGE_2, GROUP_1);
320 addGroupChild(2, PACKAGE_2, GROUP_1);
321
322 // GIVEN a promoter that will promote one of children to top level
323 mListBuilder.addPromoter(new IdPromoter(0));
324
325 // WHEN we build the list
326 dispatchBuild();
327
328 // THEN both children end up at top level (because group is now too small)
329 verifyBuiltList(
330 notif(0),
331 notif(2)
332 );
333
334 // THEN the summary is discarded
335 assertNull(mEntrySet.get(1).getParent());
336 }
337
338 @Test
339 public void testPreviousParentsAreSetProperly() {
340 // GIVEN a notification that is initially added to the list
341 PackageFilter filter = new PackageFilter(PACKAGE_2);
342 filter.setEnabled(false);
Beverly142c5732019-12-11 14:26:49 -0500343 mListBuilder.addPreGroupFilter(filter);
Ned Burns77050aa2019-10-17 21:55:24 -0400344
345 addNotif(0, PACKAGE_1);
346 addNotif(1, PACKAGE_2);
347 addNotif(2, PACKAGE_3);
348 dispatchBuild();
349
350 // WHEN it is suddenly filtered out
351 filter.setEnabled(true);
352 dispatchBuild();
353
354 // THEN its previous parent indicates that it used to be added
355 assertNull(mEntrySet.get(1).getParent());
356 assertEquals(GroupEntry.ROOT_ENTRY, mEntrySet.get(1).getPreviousParent());
357 }
358
359 @Test
360 public void testThatAnnulledGroupsAndSummariesAreProperlyRolledBack() {
361 // GIVEN a registered transform groups listener
362 RecordingOnBeforeTransformGroupsListener listener =
363 new RecordingOnBeforeTransformGroupsListener();
364 mListBuilder.addOnBeforeTransformGroupsListener(listener);
365
366 // GIVEN a malformed group that will be dismantled
367 addGroupChild(0, PACKAGE_2, GROUP_1);
368 addGroupSummary(1, PACKAGE_2, GROUP_1);
369 addNotif(2, PACKAGE_1);
370
371 // WHEN we build the list
372 dispatchBuild();
373
374 // THEN only the child appears in the final list
375 verifyBuiltList(
376 notif(0),
377 notif(2)
378 );
379
Ned Burns77050aa2019-10-17 21:55:24 -0400380 // THEN the summary has a null parent and an unset firstAddedIteration
381 assertNull(mEntrySet.get(1).getParent());
382 assertEquals(-1, mEntrySet.get(1).mFirstAddedIteration);
383 }
384
385 @Test
Beverly142c5732019-12-11 14:26:49 -0500386 public void testPreGroupNotifsAreFiltered() {
387 // GIVEN a PreGroupNotifFilter and PreRenderFilter that filters out the same package
388 NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_2));
389 NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_2));
390 mListBuilder.addPreGroupFilter(preGroupFilter);
391 mListBuilder.addPreRenderFilter(preRenderFilter);
392
393 // WHEN the pipeline is kicked off on a list of notifs
394 addNotif(0, PACKAGE_1);
395 addNotif(1, PACKAGE_2);
396 addNotif(2, PACKAGE_3);
397 addNotif(3, PACKAGE_2);
398 dispatchBuild();
399
400 // THEN the preGroupFilter is called on each notif in the original set
401 verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
402 verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
403 verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
404 verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
405
406 // THEN the preRenderFilter is only called on the notifications not already filtered out
407 verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
408 verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
409 verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
410 verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
411
412 // THEN the final list doesn't contain any filtered-out notifs
413 verifyBuiltList(
414 notif(0),
415 notif(2)
416 );
417
418 // THEN each filtered notif records the NotifFilter that did it
419 assertEquals(preGroupFilter, mEntrySet.get(1).mExcludingFilter);
420 assertEquals(preGroupFilter, mEntrySet.get(3).mExcludingFilter);
421 }
422
423 @Test
424 public void testPreRenderNotifsAreFiltered() {
Ned Burns77050aa2019-10-17 21:55:24 -0400425 // GIVEN a NotifFilter that filters out a specific package
426 NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
Beverly142c5732019-12-11 14:26:49 -0500427 mListBuilder.addPreRenderFilter(filter1);
Ned Burns77050aa2019-10-17 21:55:24 -0400428
429 // WHEN the pipeline is kicked off on a list of notifs
430 addNotif(0, PACKAGE_1);
431 addNotif(1, PACKAGE_2);
432 addNotif(2, PACKAGE_3);
433 addNotif(3, PACKAGE_2);
434 dispatchBuild();
435
436 // THEN the filter is called on each notif in the original set
437 verify(filter1).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
438 verify(filter1).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
439 verify(filter1).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
440 verify(filter1).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
441
442 // THEN the final list doesn't contain any filtered-out notifs
443 verifyBuiltList(
444 notif(0),
445 notif(2)
446 );
447
448 // THEN each filtered notif records the filter that did it
449 assertEquals(filter1, mEntrySet.get(1).mExcludingFilter);
450 assertEquals(filter1, mEntrySet.get(3).mExcludingFilter);
451 }
452
453 @Test
Beverly48220972020-01-13 16:55:38 -0500454 public void testPreRenderNotifsFilteredBreakupGroups() {
455 final String filterTag = "FILTER_ME";
456 // GIVEN a NotifFilter that filters out notifications with a tag
457 NotifFilter filter1 = spy(new NotifFilterWithTag(filterTag));
458 mListBuilder.addPreRenderFilter(filter1);
459
460 // WHEN the pipeline is kicked off on a list of notifs
461 addGroupChildWithTag(0, PACKAGE_2, GROUP_1, filterTag);
462 addGroupChild(1, PACKAGE_2, GROUP_1);
463 addGroupSummary(2, PACKAGE_2, GROUP_1);
464 dispatchBuild();
465
466 // THEN the final list doesn't contain any filtered-out notifs
467 // and groups that are too small are broken up
468 verifyBuiltList(
469 notif(1)
470 );
471
472 // THEN each filtered notif records the filter that did it
473 assertEquals(filter1, mEntrySet.get(0).mExcludingFilter);
474 }
475
476 @Test
Ned Burns77050aa2019-10-17 21:55:24 -0400477 public void testNotifFiltersCanBePreempted() {
478 // GIVEN two notif filters
479 NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
480 NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5));
Beverly142c5732019-12-11 14:26:49 -0500481 mListBuilder.addPreGroupFilter(filter1);
482 mListBuilder.addPreGroupFilter(filter2);
Ned Burns77050aa2019-10-17 21:55:24 -0400483
484 // WHEN the pipeline is kicked off on a list of notifs
485 addNotif(0, PACKAGE_1);
486 addNotif(1, PACKAGE_2);
487 addNotif(2, PACKAGE_5);
488 dispatchBuild();
489
490 // THEN both filters are called on the first notif but the second filter is never called
491 // on the already-filtered second notif
492 verify(filter1).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
493 verify(filter1).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
494 verify(filter1).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
495 verify(filter2).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
496 verify(filter2).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
497
498 // THEN the final list doesn't contain any filtered-out notifs
499 verifyBuiltList(
500 notif(0)
501 );
502
503 // THEN each filtered notif records the filter that did it
504 assertEquals(filter1, mEntrySet.get(1).mExcludingFilter);
505 assertEquals(filter2, mEntrySet.get(2).mExcludingFilter);
506 }
507
508 @Test
509 public void testNotifsArePromoted() {
510 // GIVEN a NotifPromoter that promotes certain notif IDs
511 NotifPromoter promoter = spy(new IdPromoter(1, 2));
512 mListBuilder.addPromoter(promoter);
513
514 // WHEN the pipeline is kicked off
515 addNotif(0, PACKAGE_1);
516 addGroupChild(1, PACKAGE_2, GROUP_1);
517 addGroupChild(2, PACKAGE_2, GROUP_1);
518 addGroupChild(3, PACKAGE_2, GROUP_1);
519 addGroupChild(4, PACKAGE_2, GROUP_1);
520 addGroupSummary(5, PACKAGE_2, GROUP_1);
521 addNotif(6, PACKAGE_3);
522 dispatchBuild();
523
524 // THEN the filter is called on each group child
525 verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(1));
526 verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(2));
527 verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(3));
528 verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(4));
529
530 // THEN the final list contains the promoted entries at top level
531 verifyBuiltList(
532 notif(0),
533 notif(2),
534 notif(3),
535 group(
536 summary(5),
537 child(1),
538 child(4)),
539 notif(6)
540 );
541
542 // THEN each promoted notif records the promoter that did it
543 assertEquals(promoter, mEntrySet.get(2).mNotifPromoter);
544 assertEquals(promoter, mEntrySet.get(3).mNotifPromoter);
545 }
546
547 @Test
548 public void testNotifPromotersCanBePreempted() {
549 // GIVEN two notif promoters
550 NotifPromoter promoter1 = spy(new IdPromoter(1));
551 NotifPromoter promoter2 = spy(new IdPromoter(2));
552 mListBuilder.addPromoter(promoter1);
553 mListBuilder.addPromoter(promoter2);
554
555 // WHEN the pipeline is kicked off on some notifs and a group
556 addNotif(0, PACKAGE_1);
557 addGroupChild(1, PACKAGE_2, GROUP_1);
558 addGroupChild(2, PACKAGE_2, GROUP_1);
559 addGroupChild(3, PACKAGE_2, GROUP_1);
560 addGroupSummary(4, PACKAGE_2, GROUP_1);
561 addNotif(5, PACKAGE_3);
562 dispatchBuild();
563
Ned Burns77050aa2019-10-17 21:55:24 -0400564 // THEN both promoters are called on each child, except for children that a previous
565 // promoter has already promoted
566 verify(promoter1).shouldPromoteToTopLevel(mEntrySet.get(1));
567 verify(promoter1).shouldPromoteToTopLevel(mEntrySet.get(2));
568 verify(promoter1).shouldPromoteToTopLevel(mEntrySet.get(3));
569
570 verify(promoter2).shouldPromoteToTopLevel(mEntrySet.get(1));
571 verify(promoter2).shouldPromoteToTopLevel(mEntrySet.get(3));
572
573 // THEN each promoter is recorded on each notif it promoted
574 assertEquals(promoter1, mEntrySet.get(2).mNotifPromoter);
575 assertEquals(promoter2, mEntrySet.get(3).mNotifPromoter);
576 }
577
578 @Test
Beverly48220972020-01-13 16:55:38 -0500579 public void testNotifSections() {
580 // GIVEN a filter that removes all PACKAGE_4 notifs and sections that divide
Ned Burns77050aa2019-10-17 21:55:24 -0400581 // notifs based on package name
Beverly142c5732019-12-11 14:26:49 -0500582 mListBuilder.addPreGroupFilter(new PackageFilter(PACKAGE_4));
Beverly48220972020-01-13 16:55:38 -0500583 final NotifSection pkg1Section = spy(new PackageSection(PACKAGE_1));
584 final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2));
585 // NOTE: no package 3 section explicitly added, so notifs with package 3 will get set by
586 // ShadeListBuilder's sDefaultSection which will demote it to the last section
587 final NotifSection pkg4Section = spy(new PackageSection(PACKAGE_4));
588 final NotifSection pkg5Section = spy(new PackageSection(PACKAGE_5));
589 mListBuilder.setSections(Arrays.asList(pkg1Section, pkg2Section, pkg4Section, pkg5Section));
Ned Burns77050aa2019-10-17 21:55:24 -0400590
591 // WHEN we build a list with different packages
592 addNotif(0, PACKAGE_4);
593 addNotif(1, PACKAGE_2);
594 addNotif(2, PACKAGE_1);
595 addNotif(3, PACKAGE_3);
596 addGroupSummary(4, PACKAGE_2, GROUP_1);
597 addGroupChild(5, PACKAGE_2, GROUP_1);
598 addGroupChild(6, PACKAGE_2, GROUP_1);
599 addNotif(7, PACKAGE_1);
600 addNotif(8, PACKAGE_2);
601 addNotif(9, PACKAGE_5);
602 addNotif(10, PACKAGE_4);
603 dispatchBuild();
604
605 // THEN the list is sorted according to section
606 verifyBuiltList(
607 notif(2),
608 notif(7),
609 notif(1),
610 group(
611 summary(4),
612 child(5),
613 child(6)
614 ),
615 notif(8),
Beverly48220972020-01-13 16:55:38 -0500616 notif(9),
617 notif(3)
Ned Burns77050aa2019-10-17 21:55:24 -0400618 );
619
Beverly48220972020-01-13 16:55:38 -0500620 // THEN the first section (pkg1Section) is called on all top level elements (but
621 // no children and no entries that were filtered out)
622 verify(pkg1Section).isInSection(mEntrySet.get(1));
623 verify(pkg1Section).isInSection(mEntrySet.get(2));
624 verify(pkg1Section).isInSection(mEntrySet.get(3));
625 verify(pkg1Section).isInSection(mEntrySet.get(7));
626 verify(pkg1Section).isInSection(mEntrySet.get(8));
627 verify(pkg1Section).isInSection(mEntrySet.get(9));
628 verify(pkg1Section).isInSection(mBuiltList.get(3));
629
630 verify(pkg1Section, never()).isInSection(mEntrySet.get(0));
631 verify(pkg1Section, never()).isInSection(mEntrySet.get(4));
632 verify(pkg1Section, never()).isInSection(mEntrySet.get(5));
633 verify(pkg1Section, never()).isInSection(mEntrySet.get(6));
634 verify(pkg1Section, never()).isInSection(mEntrySet.get(10));
635
636 // THEN the last section (pkg5Section) is not called on any of the entries that were
637 // filtered or already in a section
638 verify(pkg5Section, never()).isInSection(mEntrySet.get(0));
639 verify(pkg5Section, never()).isInSection(mEntrySet.get(1));
640 verify(pkg5Section, never()).isInSection(mEntrySet.get(2));
641 verify(pkg5Section, never()).isInSection(mEntrySet.get(4));
642 verify(pkg5Section, never()).isInSection(mEntrySet.get(5));
643 verify(pkg5Section, never()).isInSection(mEntrySet.get(6));
644 verify(pkg5Section, never()).isInSection(mEntrySet.get(7));
645 verify(pkg5Section, never()).isInSection(mEntrySet.get(8));
646 verify(pkg5Section, never()).isInSection(mEntrySet.get(10));
647
648 verify(pkg5Section).isInSection(mEntrySet.get(3));
649 verify(pkg5Section).isInSection(mEntrySet.get(9));
650
651 // THEN the correct section is assigned for entries in pkg1Section
652 assertEquals(pkg1Section, mEntrySet.get(2).mNotifSection);
653 assertEquals(0, mEntrySet.get(2).getSection());
654 assertEquals(pkg1Section, mEntrySet.get(7).mNotifSection);
655 assertEquals(0, mEntrySet.get(7).getSection());
656
657 // THEN the correct section is assigned for entries in pkg2Section
658 assertEquals(pkg2Section, mEntrySet.get(1).mNotifSection);
659 assertEquals(1, mEntrySet.get(1).getSection());
660 assertEquals(pkg2Section, mEntrySet.get(8).mNotifSection);
661 assertEquals(1, mEntrySet.get(8).getSection());
662 assertEquals(pkg2Section, mBuiltList.get(3).mNotifSection);
663 assertEquals(1, mBuiltList.get(3).getSection());
664
665 // THEN no section was assigned to entries in pkg4Section (since they were filtered)
666 assertEquals(null, mEntrySet.get(0).mNotifSection);
667 assertEquals(-1, mEntrySet.get(0).getSection());
668 assertEquals(null, mEntrySet.get(10).mNotifSection);
669 assertEquals(-1, mEntrySet.get(10).getSection());
670
671
672 // THEN the correct section is assigned for entries in pkg5Section
673 assertEquals(pkg5Section, mEntrySet.get(9).mNotifSection);
674 assertEquals(3, mEntrySet.get(9).getSection());
675
676 // THEN the children entries are assigned the same section as its parent
677 assertEquals(mBuiltList.get(3).mNotifSection, child(5).entry.mNotifSection);
678 assertEquals(mBuiltList.get(3).getSection(), child(5).entry.getSection());
679 assertEquals(mBuiltList.get(3).mNotifSection, child(6).entry.mNotifSection);
680 assertEquals(mBuiltList.get(3).getSection(), child(6).entry.getSection());
681 }
682
683 @Test
684 public void testNotifUsesDefaultSection() {
685 // GIVEN a Section for Package2
686 final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2));
687 mListBuilder.setSections(Arrays.asList(pkg2Section));
688
689 // WHEN we build a list with pkg1 and pkg2 packages
690 addNotif(0, PACKAGE_1);
691 addNotif(1, PACKAGE_2);
692 dispatchBuild();
693
694 // THEN the list is sorted according to section
695 verifyBuiltList(
696 notif(1),
697 notif(0)
698 );
699
700 // THEN the entry that didn't have an explicit section gets assigned the DefaultSection
701 assertEquals(1, notif(0).entry.getSection());
702 assertNotNull(notif(0).entry.mNotifSection);
Ned Burns77050aa2019-10-17 21:55:24 -0400703 }
704
705 @Test
706 public void testThatNotifComparatorsAreCalled() {
707 // GIVEN a set of comparators that care about specific packages
708 mListBuilder.setComparators(Arrays.asList(
709 new HypeComparator(PACKAGE_4),
710 new HypeComparator(PACKAGE_1, PACKAGE_3),
711 new HypeComparator(PACKAGE_2)
712 ));
713
714 // WHEN the pipeline is kicked off on a bunch of notifications
715 addNotif(0, PACKAGE_1);
716 addNotif(1, PACKAGE_5);
717 addNotif(2, PACKAGE_3);
718 addNotif(3, PACKAGE_4);
719 addNotif(4, PACKAGE_2);
720 dispatchBuild();
721
722 // THEN the notifs are sorted according to the hierarchy of comparators
723 verifyBuiltList(
724 notif(3),
725 notif(0),
726 notif(2),
727 notif(4),
728 notif(1)
729 );
730 }
731
732 @Test
733 public void testListenersAndPluggablesAreFiredInOrder() {
734 // GIVEN a bunch of registered listeners and pluggables
Beverly142c5732019-12-11 14:26:49 -0500735 NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1));
Ned Burns77050aa2019-10-17 21:55:24 -0400736 NotifPromoter promoter = spy(new IdPromoter(3));
Beverly48220972020-01-13 16:55:38 -0500737 NotifSection section = spy(new PackageSection(PACKAGE_1));
Ned Burns77050aa2019-10-17 21:55:24 -0400738 NotifComparator comparator = spy(new HypeComparator(PACKAGE_4));
Beverly142c5732019-12-11 14:26:49 -0500739 NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5));
740 mListBuilder.addPreGroupFilter(preGroupFilter);
Ned Burns77050aa2019-10-17 21:55:24 -0400741 mListBuilder.addOnBeforeTransformGroupsListener(mOnBeforeTransformGroupsListener);
742 mListBuilder.addPromoter(promoter);
743 mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener);
744 mListBuilder.setComparators(Collections.singletonList(comparator));
Beverly48220972020-01-13 16:55:38 -0500745 mListBuilder.setSections(Arrays.asList(section));
Ned Burns77050aa2019-10-17 21:55:24 -0400746 mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener);
Beverly142c5732019-12-11 14:26:49 -0500747 mListBuilder.addPreRenderFilter(preRenderFilter);
Ned Burns77050aa2019-10-17 21:55:24 -0400748
749 // WHEN a few new notifs are added
750 addNotif(0, PACKAGE_1);
751 addGroupSummary(1, PACKAGE_2, GROUP_1);
752 addGroupChild(2, PACKAGE_2, GROUP_1);
753 addGroupChild(3, PACKAGE_2, GROUP_1);
754 addNotif(4, PACKAGE_5);
755 addNotif(5, PACKAGE_5);
756 addNotif(6, PACKAGE_4);
757 dispatchBuild();
758
759 // THEN the pluggables and listeners are called in order
760 InOrder inOrder = inOrder(
Beverly142c5732019-12-11 14:26:49 -0500761 preGroupFilter,
Ned Burns77050aa2019-10-17 21:55:24 -0400762 mOnBeforeTransformGroupsListener,
763 promoter,
764 mOnBeforeSortListener,
Beverly48220972020-01-13 16:55:38 -0500765 section,
Ned Burns77050aa2019-10-17 21:55:24 -0400766 comparator,
Beverly142c5732019-12-11 14:26:49 -0500767 preRenderFilter,
Ned Burns77050aa2019-10-17 21:55:24 -0400768 mOnBeforeRenderListListener,
769 mOnRenderListListener);
770
Beverly142c5732019-12-11 14:26:49 -0500771 inOrder.verify(preGroupFilter, atLeastOnce())
Ned Burns77050aa2019-10-17 21:55:24 -0400772 .shouldFilterOut(any(NotificationEntry.class), anyLong());
773 inOrder.verify(mOnBeforeTransformGroupsListener)
Beverly142c5732019-12-11 14:26:49 -0500774 .onBeforeTransformGroups(anyList());
Ned Burns77050aa2019-10-17 21:55:24 -0400775 inOrder.verify(promoter, atLeastOnce())
776 .shouldPromoteToTopLevel(any(NotificationEntry.class));
777 inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList());
Beverly48220972020-01-13 16:55:38 -0500778 inOrder.verify(section, atLeastOnce()).isInSection(any(ListEntry.class));
Ned Burns77050aa2019-10-17 21:55:24 -0400779 inOrder.verify(comparator, atLeastOnce())
780 .compare(any(ListEntry.class), any(ListEntry.class));
Beverly142c5732019-12-11 14:26:49 -0500781 inOrder.verify(preRenderFilter, atLeastOnce())
782 .shouldFilterOut(any(NotificationEntry.class), anyLong());
Ned Burns77050aa2019-10-17 21:55:24 -0400783 inOrder.verify(mOnBeforeRenderListListener).onBeforeRenderList(anyList());
784 inOrder.verify(mOnRenderListListener).onRenderList(anyList());
785 }
786
787 @Test
788 public void testThatPluggableInvalidationsTriggersRerun() {
789 // GIVEN a variety of pluggables
790 NotifFilter packageFilter = new PackageFilter(PACKAGE_1);
791 NotifPromoter idPromoter = new IdPromoter(4);
Beverly48220972020-01-13 16:55:38 -0500792 NotifSection section = new PackageSection(PACKAGE_1);
Ned Burns77050aa2019-10-17 21:55:24 -0400793 NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
794
Beverly142c5732019-12-11 14:26:49 -0500795 mListBuilder.addPreGroupFilter(packageFilter);
Ned Burns77050aa2019-10-17 21:55:24 -0400796 mListBuilder.addPromoter(idPromoter);
Beverly48220972020-01-13 16:55:38 -0500797 mListBuilder.setSections(Arrays.asList(section));
Ned Burns77050aa2019-10-17 21:55:24 -0400798 mListBuilder.setComparators(Collections.singletonList(hypeComparator));
799
800 // GIVEN a set of random notifs
801 addNotif(0, PACKAGE_1);
802 addNotif(1, PACKAGE_2);
803 addNotif(2, PACKAGE_3);
804 dispatchBuild();
805
806 // WHEN each pluggable is invalidated THEN the list is re-rendered
807
808 clearInvocations(mOnRenderListListener);
809 packageFilter.invalidateList();
810 verify(mOnRenderListListener).onRenderList(anyList());
811
812 clearInvocations(mOnRenderListListener);
813 idPromoter.invalidateList();
814 verify(mOnRenderListListener).onRenderList(anyList());
815
816 clearInvocations(mOnRenderListListener);
Beverly48220972020-01-13 16:55:38 -0500817 section.invalidateList();
Ned Burns77050aa2019-10-17 21:55:24 -0400818 verify(mOnRenderListListener).onRenderList(anyList());
819
820 clearInvocations(mOnRenderListListener);
821 hypeComparator.invalidateList();
822 verify(mOnRenderListListener).onRenderList(anyList());
823 }
824
825 @Test
826 public void testNotifFiltersAreAllSentTheSameNow() {
827 // GIVEN three notif filters
828 NotifFilter filter1 = spy(new PackageFilter(PACKAGE_5));
829 NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5));
830 NotifFilter filter3 = spy(new PackageFilter(PACKAGE_5));
Beverly142c5732019-12-11 14:26:49 -0500831 mListBuilder.addPreGroupFilter(filter1);
832 mListBuilder.addPreGroupFilter(filter2);
833 mListBuilder.addPreGroupFilter(filter3);
Ned Burns77050aa2019-10-17 21:55:24 -0400834
835 // GIVEN the SystemClock is set to a particular time:
Ned Burns582ee232019-12-19 16:04:35 -0500836 mSystemClock.setUptimeMillis(10047);
Ned Burns77050aa2019-10-17 21:55:24 -0400837
838 // WHEN the pipeline is kicked off on a list of notifs
839 addNotif(0, PACKAGE_1);
840 addNotif(1, PACKAGE_2);
841 dispatchBuild();
842
843 // THEN the value of `now` is the same for all calls to shouldFilterOut
Ned Burns582ee232019-12-19 16:04:35 -0500844 verify(filter1).shouldFilterOut(mEntrySet.get(0), 10047);
845 verify(filter2).shouldFilterOut(mEntrySet.get(0), 10047);
846 verify(filter3).shouldFilterOut(mEntrySet.get(0), 10047);
847 verify(filter1).shouldFilterOut(mEntrySet.get(1), 10047);
848 verify(filter2).shouldFilterOut(mEntrySet.get(1), 10047);
849 verify(filter3).shouldFilterOut(mEntrySet.get(1), 10047);
Ned Burns77050aa2019-10-17 21:55:24 -0400850 }
851
852 @Test
Beverly142c5732019-12-11 14:26:49 -0500853 public void testGroupTransformEntries() {
Ned Burns77050aa2019-10-17 21:55:24 -0400854 // GIVEN a registered OnBeforeTransformGroupsListener
855 RecordingOnBeforeTransformGroupsListener listener =
Beverly97532972020-01-28 10:40:48 -0500856 new RecordingOnBeforeTransformGroupsListener();
Ned Burns77050aa2019-10-17 21:55:24 -0400857 mListBuilder.addOnBeforeTransformGroupsListener(listener);
858
Beverly142c5732019-12-11 14:26:49 -0500859 // GIVEN some new notifs
Ned Burns77050aa2019-10-17 21:55:24 -0400860 addNotif(0, PACKAGE_1);
861 addGroupChild(1, PACKAGE_2, GROUP_1);
862 addGroupSummary(2, PACKAGE_2, GROUP_1);
863 addGroupChild(3, PACKAGE_2, GROUP_1);
864 addNotif(4, PACKAGE_3);
865 addGroupChild(5, PACKAGE_2, GROUP_1);
866
867 // WHEN we run the pipeline
868 dispatchBuild();
869
870 verifyBuiltList(
871 notif(0),
872 group(
873 summary(2),
874 child(1),
875 child(3),
876 child(5)
877 ),
878 notif(4)
879 );
880
881 // THEN all the new notifs, including the new GroupEntry, are passed to the listener
Beverly97532972020-01-28 10:40:48 -0500882 assertEquals(
Ned Burns77050aa2019-10-17 21:55:24 -0400883 Arrays.asList(
884 mEntrySet.get(0),
885 mBuiltList.get(1),
Beverly97532972020-01-28 10:40:48 -0500886 mEntrySet.get(4)),
887 listener.mEntriesReceived
Ned Burns77050aa2019-10-17 21:55:24 -0400888 );
889 }
890
891 @Test
Beverly142c5732019-12-11 14:26:49 -0500892 public void testGroupTransformEntriesOnSecondRun() {
Ned Burns77050aa2019-10-17 21:55:24 -0400893 // GIVEN a registered OnBeforeTransformGroupsListener
894 RecordingOnBeforeTransformGroupsListener listener =
895 spy(new RecordingOnBeforeTransformGroupsListener());
896 mListBuilder.addOnBeforeTransformGroupsListener(listener);
897
Beverly142c5732019-12-11 14:26:49 -0500898 // GIVEN some notifs that have already been added (two of which are in malformed groups)
Ned Burns77050aa2019-10-17 21:55:24 -0400899 addNotif(0, PACKAGE_1);
900 addGroupChild(1, PACKAGE_2, GROUP_1);
901 addGroupChild(2, PACKAGE_3, GROUP_2);
902
903 dispatchBuild();
904 clearInvocations(listener);
905
906 // WHEN we run the pipeline
907 addGroupSummary(3, PACKAGE_2, GROUP_1);
908 addGroupChild(4, PACKAGE_3, GROUP_2);
909 addGroupSummary(5, PACKAGE_3, GROUP_2);
910 addGroupChild(6, PACKAGE_3, GROUP_2);
911 addNotif(7, PACKAGE_2);
912
913 dispatchBuild();
914
915 verifyBuiltList(
916 notif(0),
917 notif(1),
918 group(
919 summary(5),
920 child(2),
921 child(4),
922 child(6)
923 ),
924 notif(7)
925 );
926
927 // THEN all the new notifs, including the new GroupEntry, are passed to the listener
Beverly97532972020-01-28 10:40:48 -0500928 assertEquals(
929 Arrays.asList(
930 mEntrySet.get(0),
931 mBuiltList.get(2),
932 mEntrySet.get(7),
933 mEntrySet.get(1)),
934 listener.mEntriesReceived
935 );
936 }
937
938 @Test
939 public void testDispatchListOnBeforeSort() {
940 // GIVEN a registered OnBeforeSortListener
941 RecordingOnBeforeSortListener listener =
942 new RecordingOnBeforeSortListener();
943 mListBuilder.addOnBeforeSortListener(listener);
944 mListBuilder.setComparators(Arrays.asList(new HypeComparator(PACKAGE_3)));
945
946 // GIVEN some new notifs out of order
947 addNotif(0, PACKAGE_1);
948 addNotif(1, PACKAGE_2);
949 addNotif(2, PACKAGE_3);
950
951 // WHEN we run the pipeline
952 dispatchBuild();
953
954 // THEN all the new notifs are passed to the listener out of order
955 assertEquals(
Ned Burns77050aa2019-10-17 21:55:24 -0400956 Arrays.asList(
957 mEntrySet.get(0),
958 mEntrySet.get(1),
Beverly97532972020-01-28 10:40:48 -0500959 mEntrySet.get(2)),
960 listener.mEntriesReceived
961 );
962
963 // THEN the final list is in order
964 verifyBuiltList(
965 notif(2),
966 notif(0),
967 notif(1)
968 );
969 }
970
971 @Test
972 public void testDispatchListOnBeforeRender() {
973 // GIVEN a registered OnBeforeRenderList
974 RecordingOnBeforeRenderistener listener =
975 new RecordingOnBeforeRenderistener();
976 mListBuilder.addOnBeforeRenderListListener(listener);
977
978 // GIVEN some new notifs out of order
979 addNotif(0, PACKAGE_1);
980 addNotif(1, PACKAGE_2);
981 addNotif(2, PACKAGE_3);
982
983 // WHEN we run the pipeline
984 dispatchBuild();
985
986 // THEN all the new notifs are passed to the listener
987 assertEquals(
988 Arrays.asList(
989 mEntrySet.get(0),
990 mEntrySet.get(1),
991 mEntrySet.get(2)),
992 listener.mEntriesReceived
Ned Burns77050aa2019-10-17 21:55:24 -0400993 );
994 }
995
996 @Test
997 public void testAnnulledGroupsHaveParentSetProperly() {
998 // GIVEN a list containing a small group that's already been built once
999 addGroupChild(0, PACKAGE_2, GROUP_2);
1000 addGroupSummary(1, PACKAGE_2, GROUP_2);
1001 addGroupChild(2, PACKAGE_2, GROUP_2);
1002 dispatchBuild();
1003
1004 verifyBuiltList(
1005 group(
1006 summary(1),
1007 child(0),
1008 child(2)
1009 )
1010 );
1011 GroupEntry group = (GroupEntry) mBuiltList.get(0);
1012
1013 // WHEN a child is removed such that the group is no longer big enough
1014 mEntrySet.remove(2);
1015 dispatchBuild();
1016
1017 // THEN the group is annulled and its parent is set back to null
1018 verifyBuiltList(
1019 notif(0)
1020 );
1021 assertNull(group.getParent());
Ned Burns5d1f59e2019-11-14 15:55:23 -05001022
1023 // but its previous parent indicates that it was added in the previous iteration
1024 assertEquals(GroupEntry.ROOT_ENTRY, group.getPreviousParent());
Ned Burns77050aa2019-10-17 21:55:24 -04001025 }
1026
1027 @Test(expected = IllegalStateException.class)
Beverly142c5732019-12-11 14:26:49 -05001028 public void testOutOfOrderPreGroupFilterInvalidationThrows() {
1029 // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage
Ned Burns77050aa2019-10-17 21:55:24 -04001030 NotifFilter filter = new PackageFilter(PACKAGE_5);
Beverly142c5732019-12-11 14:26:49 -05001031 OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
1032 mListBuilder.addPreGroupFilter(filter);
Ned Burns77050aa2019-10-17 21:55:24 -04001033 mListBuilder.addOnBeforeTransformGroupsListener(listener);
1034
1035 // WHEN we try to run the pipeline and the filter is invalidated
1036 addNotif(0, PACKAGE_1);
1037 dispatchBuild();
1038
Beverly142c5732019-12-11 14:26:49 -05001039 // THEN an exception is thrown
Ned Burns77050aa2019-10-17 21:55:24 -04001040 }
1041
1042 @Test(expected = IllegalStateException.class)
1043 public void testOutOfOrderPrompterInvalidationThrows() {
Ned Burns5d1f59e2019-11-14 15:55:23 -05001044 // GIVEN a NotifPromoter that gets invalidated during the sorting stage
Ned Burns77050aa2019-10-17 21:55:24 -04001045 NotifPromoter promoter = new IdPromoter(47);
1046 OnBeforeSortListener listener =
1047 (list) -> promoter.invalidateList();
1048 mListBuilder.addPromoter(promoter);
1049 mListBuilder.addOnBeforeSortListener(listener);
1050
Ned Burns5d1f59e2019-11-14 15:55:23 -05001051 // WHEN we try to run the pipeline and the promoter is invalidated
Ned Burns77050aa2019-10-17 21:55:24 -04001052 addNotif(0, PACKAGE_1);
1053 dispatchBuild();
1054
Beverly142c5732019-12-11 14:26:49 -05001055 // THEN an exception is thrown
Ned Burns77050aa2019-10-17 21:55:24 -04001056 }
1057
1058 @Test(expected = IllegalStateException.class)
1059 public void testOutOfOrderComparatorInvalidationThrows() {
Ned Burns5d1f59e2019-11-14 15:55:23 -05001060 // GIVEN a NotifComparator that gets invalidated during the finalizing stage
Ned Burns77050aa2019-10-17 21:55:24 -04001061 NotifComparator comparator = new HypeComparator(PACKAGE_5);
1062 OnBeforeRenderListListener listener =
1063 (list) -> comparator.invalidateList();
1064 mListBuilder.setComparators(Collections.singletonList(comparator));
1065 mListBuilder.addOnBeforeRenderListListener(listener);
1066
Ned Burns5d1f59e2019-11-14 15:55:23 -05001067 // WHEN we try to run the pipeline and the comparator is invalidated
Ned Burns77050aa2019-10-17 21:55:24 -04001068 addNotif(0, PACKAGE_1);
1069 dispatchBuild();
1070
Beverly142c5732019-12-11 14:26:49 -05001071 // THEN an exception is thrown
1072 }
1073
1074 @Test(expected = IllegalStateException.class)
1075 public void testOutOfOrderPreRenderFilterInvalidationThrows() {
1076 // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage
1077 NotifFilter filter = new PackageFilter(PACKAGE_5);
1078 OnBeforeRenderListListener listener = (list) -> filter.invalidateList();
1079 mListBuilder.addPreRenderFilter(filter);
1080 mListBuilder.addOnBeforeRenderListListener(listener);
1081
1082 // WHEN we try to run the pipeline and the PreRenderFilter is invalidated
1083 addNotif(0, PACKAGE_1);
1084 dispatchBuild();
1085
1086 // THEN an exception is thrown
1087 }
1088
1089 @Test
1090 public void testInOrderPreRenderFilter() {
1091 // GIVEN a PreRenderFilter that gets invalidated during the grouping stage
1092 NotifFilter filter = new PackageFilter(PACKAGE_5);
1093 OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
1094 mListBuilder.addPreRenderFilter(filter);
1095 mListBuilder.addOnBeforeTransformGroupsListener(listener);
1096
1097 // WHEN we try to run the pipeline and the filter is invalidated
1098 addNotif(0, PACKAGE_1);
1099 dispatchBuild();
1100
1101 // THEN no exception thrown
Ned Burns77050aa2019-10-17 21:55:24 -04001102 }
1103
1104 /**
1105 * Adds a notif to the collection that will be passed to the list builder when
1106 * {@link #dispatchBuild()}s is called.
1107 *
1108 * @param index Index of this notification in the set. This must be the current size of the set.
1109 * it exists to improve readability of the resulting code, since later tests will
1110 * have to refer to notifs by index.
1111 * @param packageId Package that the notif should be posted under
1112 * @return A NotificationEntryBuilder that can be used to further modify the notif. Do not call
1113 * build() on the builder; that will be done on the next dispatchBuild().
1114 */
1115 private NotificationEntryBuilder addNotif(int index, String packageId) {
1116 final NotificationEntryBuilder builder = new NotificationEntryBuilder()
1117 .setPkg(packageId)
1118 .setId(nextId(packageId))
1119 .setRank(nextRank());
1120
1121 builder.modifyNotification(mContext)
1122 .setContentTitle("Top level singleton")
1123 .setChannelId("test_channel");
1124
1125 assertEquals(mEntrySet.size() + mPendingSet.size(), index);
1126 mPendingSet.add(builder);
1127 return builder;
1128 }
1129
1130 /** Same behavior as {@link #addNotif(int, String)}. */
1131 private NotificationEntryBuilder addGroupSummary(int index, String packageId, String groupId) {
1132 final NotificationEntryBuilder builder = new NotificationEntryBuilder()
1133 .setPkg(packageId)
1134 .setId(nextId(packageId))
1135 .setRank(nextRank());
1136
1137 builder.modifyNotification(mContext)
1138 .setChannelId("test_channel")
1139 .setContentTitle("Group summary")
1140 .setGroup(groupId)
1141 .setGroupSummary(true);
1142
1143 assertEquals(mEntrySet.size() + mPendingSet.size(), index);
1144 mPendingSet.add(builder);
1145 return builder;
1146 }
1147
Beverly48220972020-01-13 16:55:38 -05001148 private NotificationEntryBuilder addGroupChildWithTag(int index, String packageId,
1149 String groupId, String tag) {
Ned Burns77050aa2019-10-17 21:55:24 -04001150 final NotificationEntryBuilder builder = new NotificationEntryBuilder()
Beverly48220972020-01-13 16:55:38 -05001151 .setTag(tag)
Ned Burns77050aa2019-10-17 21:55:24 -04001152 .setPkg(packageId)
1153 .setId(nextId(packageId))
1154 .setRank(nextRank());
1155
1156 builder.modifyNotification(mContext)
1157 .setChannelId("test_channel")
1158 .setContentTitle("Group child")
1159 .setGroup(groupId);
1160
1161 assertEquals(mEntrySet.size() + mPendingSet.size(), index);
1162 mPendingSet.add(builder);
1163 return builder;
1164 }
1165
Beverly48220972020-01-13 16:55:38 -05001166 /** Same behavior as {@link #addNotif(int, String)}. */
1167 private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) {
1168 return addGroupChildWithTag(index, packageId, groupId, null);
1169 }
1170
Ned Burns77050aa2019-10-17 21:55:24 -04001171 private int nextId(String packageName) {
1172 Integer nextId = mNextIdMap.get(packageName);
1173 if (nextId == null) {
1174 nextId = 0;
1175 }
1176 mNextIdMap.put(packageName, nextId + 1);
1177 return nextId;
1178 }
1179
1180 private int nextRank() {
1181 int nextRank = mNextRank;
1182 mNextRank++;
1183 return nextRank;
1184 }
1185
1186 private void dispatchBuild() {
1187 if (mPendingSet.size() > 0) {
1188 for (NotificationEntryBuilder builder : mPendingSet) {
1189 mEntrySet.add(builder.build());
1190 }
1191 mPendingSet.clear();
1192 }
1193
Ned Burns77050aa2019-10-17 21:55:24 -04001194 mReadyForBuildListener.onBuildList(mEntrySet);
1195 }
1196
1197 private void verifyBuiltList(ExpectedEntry ...expectedEntries) {
1198 try {
1199 assertEquals(
1200 "List is the wrong length",
1201 expectedEntries.length,
1202 mBuiltList.size());
1203
1204 for (int i = 0; i < expectedEntries.length; i++) {
1205 ListEntry outEntry = mBuiltList.get(i);
1206 ExpectedEntry expectedEntry = expectedEntries[i];
1207
1208 if (expectedEntry instanceof ExpectedNotif) {
1209 assertEquals(
1210 "Entry " + i + " isn't a NotifEntry",
1211 NotificationEntry.class,
1212 outEntry.getClass());
1213 assertEquals(
1214 "Entry " + i + " doesn't match expected value.",
1215 ((ExpectedNotif) expectedEntry).entry, outEntry);
1216 } else {
1217 ExpectedGroup cmpGroup = (ExpectedGroup) expectedEntry;
1218
1219 assertEquals(
1220 "Entry " + i + " isn't a GroupEntry",
1221 GroupEntry.class,
1222 outEntry.getClass());
1223
1224 GroupEntry outGroup = (GroupEntry) outEntry;
1225
1226 assertEquals(
1227 "Summary notif for entry " + i
1228 + " doesn't match expected value",
1229 cmpGroup.summary,
1230 outGroup.getSummary());
1231 assertEquals(
1232 "Summary notif for entry " + i
1233 + " doesn't have proper parent",
1234 outGroup,
1235 outGroup.getSummary().getParent());
1236
1237 assertEquals("Children for entry " + i,
1238 cmpGroup.children,
1239 outGroup.getChildren());
1240
1241 for (int j = 0; j < outGroup.getChildren().size(); j++) {
1242 NotificationEntry child = outGroup.getChildren().get(j);
1243 assertEquals(
1244 "Child " + j + " for entry " + i
1245 + " doesn't have proper parent",
1246 outGroup,
1247 child.getParent());
1248 }
1249 }
1250 }
1251 } catch (AssertionError err) {
1252 throw new AssertionError(
Beverlyb6f4dc22020-01-10 14:58:20 -05001253 "List under test failed verification:\n" + dumpTree(mBuiltList,
1254 true, ""), err);
Ned Burns77050aa2019-10-17 21:55:24 -04001255 }
1256 }
1257
1258 private ExpectedNotif notif(int index) {
1259 return new ExpectedNotif(mEntrySet.get(index));
1260 }
1261
1262 private ExpectedGroup group(ExpectedSummary summary, ExpectedChild...children) {
1263 return new ExpectedGroup(
1264 summary.entry,
1265 Arrays.stream(children)
1266 .map(child -> child.entry)
1267 .collect(Collectors.toList()));
1268 }
1269
1270 private ExpectedSummary summary(int index) {
1271 return new ExpectedSummary(mEntrySet.get(index));
1272 }
1273
1274 private ExpectedChild child(int index) {
1275 return new ExpectedChild(mEntrySet.get(index));
1276 }
1277
1278 private abstract static class ExpectedEntry {
1279 }
1280
1281 private static class ExpectedNotif extends ExpectedEntry {
1282 public final NotificationEntry entry;
1283
1284 private ExpectedNotif(NotificationEntry entry) {
1285 this.entry = entry;
1286 }
1287 }
1288
1289 private static class ExpectedGroup extends ExpectedEntry {
1290 public final NotificationEntry summary;
1291 public final List<NotificationEntry> children;
1292
1293 private ExpectedGroup(
1294 NotificationEntry summary,
1295 List<NotificationEntry> children) {
1296 this.summary = summary;
1297 this.children = children;
1298 }
1299 }
1300
1301 private static class ExpectedSummary {
1302 public final NotificationEntry entry;
1303
1304 private ExpectedSummary(NotificationEntry entry) {
1305 this.entry = entry;
1306 }
1307 }
1308
1309 private static class ExpectedChild {
1310 public final NotificationEntry entry;
1311
1312 private ExpectedChild(NotificationEntry entry) {
1313 this.entry = entry;
1314 }
1315 }
1316
1317 /** Filters out notifs from a particular package */
1318 private static class PackageFilter extends NotifFilter {
1319 private final String mPackageName;
1320
1321 private boolean mEnabled = true;
1322
1323 PackageFilter(String packageName) {
1324 super("PackageFilter");
1325
1326 mPackageName = packageName;
1327 }
1328
1329 @Override
1330 public boolean shouldFilterOut(NotificationEntry entry, long now) {
1331 return mEnabled && entry.getSbn().getPackageName().equals(mPackageName);
1332 }
1333
1334 public void setEnabled(boolean enabled) {
1335 mEnabled = enabled;
1336 }
1337 }
1338
Beverly48220972020-01-13 16:55:38 -05001339 /** Filters out notifications with a particular tag */
1340 private static class NotifFilterWithTag extends NotifFilter {
1341 private final String mTag;
1342
1343 NotifFilterWithTag(String tag) {
1344 super("NotifFilterWithTag_" + tag);
1345 mTag = tag;
1346 }
1347
1348 @Override
1349 public boolean shouldFilterOut(NotificationEntry entry, long now) {
1350 return Objects.equals(entry.getSbn().getTag(), mTag);
1351 }
1352 }
1353
Ned Burns77050aa2019-10-17 21:55:24 -04001354 /** Promotes notifs with particular IDs */
1355 private static class IdPromoter extends NotifPromoter {
1356 private final List<Integer> mIds;
1357
1358 IdPromoter(Integer... ids) {
1359 super("IdPromoter");
1360 mIds = Arrays.asList(ids);
1361 }
1362
1363 @Override
1364 public boolean shouldPromoteToTopLevel(NotificationEntry child) {
1365 return mIds.contains(child.getSbn().getId());
1366 }
1367 }
1368
1369 /** Sorts specific notifs above all others. */
1370 private static class HypeComparator extends NotifComparator {
1371
1372 private final List<String> mPreferredPackages;
1373
1374 HypeComparator(String ...preferredPackages) {
1375 super("HypeComparator");
1376 mPreferredPackages = Arrays.asList(preferredPackages);
1377 }
1378
1379 @Override
1380 public int compare(ListEntry o1, ListEntry o2) {
1381 boolean contains1 = mPreferredPackages.contains(
1382 o1.getRepresentativeEntry().getSbn().getPackageName());
1383 boolean contains2 = mPreferredPackages.contains(
1384 o2.getRepresentativeEntry().getSbn().getPackageName());
1385
1386 return Boolean.compare(contains2, contains1);
1387 }
1388 }
1389
Beverly48220972020-01-13 16:55:38 -05001390 /** Represents a section for the passed pkg */
1391 private static class PackageSection extends NotifSection {
1392 private final String mPackage;
Ned Burns77050aa2019-10-17 21:55:24 -04001393
Beverly48220972020-01-13 16:55:38 -05001394 PackageSection(String pkg) {
1395 super("PackageSection_" + pkg);
1396 mPackage = pkg;
Ned Burns77050aa2019-10-17 21:55:24 -04001397 }
1398
1399 @Override
Beverly48220972020-01-13 16:55:38 -05001400 public boolean isInSection(ListEntry entry) {
1401 return entry.getRepresentativeEntry().getSbn().getPackageName().equals(mPackage);
Ned Burns77050aa2019-10-17 21:55:24 -04001402 }
1403 }
1404
1405 private static class RecordingOnBeforeTransformGroupsListener
1406 implements OnBeforeTransformGroupsListener {
Beverly97532972020-01-28 10:40:48 -05001407 List<ListEntry> mEntriesReceived;
Ned Burns77050aa2019-10-17 21:55:24 -04001408
1409 @Override
Beverly97532972020-01-28 10:40:48 -05001410 public void onBeforeTransformGroups(List<ListEntry> list) {
1411 mEntriesReceived = new ArrayList<>(list);
1412 }
1413 }
1414
1415 private static class RecordingOnBeforeSortListener
1416 implements OnBeforeSortListener {
1417 List<ListEntry> mEntriesReceived;
1418
1419 @Override
1420 public void onBeforeSort(List<ListEntry> list) {
1421 mEntriesReceived = new ArrayList<>(list);
1422 }
1423 }
1424
1425 private static class RecordingOnBeforeRenderistener
1426 implements OnBeforeRenderListListener {
1427 List<ListEntry> mEntriesReceived;
1428
1429 @Override
1430 public void onBeforeRenderList(List<ListEntry> list) {
1431 mEntriesReceived = new ArrayList<>(list);
1432 }
Ned Burns77050aa2019-10-17 21:55:24 -04001433 }
1434
1435 private static final String PACKAGE_1 = "com.test1";
1436 private static final String PACKAGE_2 = "com.test2";
1437 private static final String PACKAGE_3 = "org.test3";
1438 private static final String PACKAGE_4 = "com.test4";
1439 private static final String PACKAGE_5 = "com.test5";
1440
1441 private static final String GROUP_1 = "group_1";
1442 private static final String GROUP_2 = "group_2";
1443}