| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.systemui.statusbar.notification.collection; |
| |
| import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyList; |
| import static org.mockito.ArgumentMatchers.anyLong; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.atLeastOnce; |
| import static org.mockito.Mockito.clearInvocations; |
| import static org.mockito.Mockito.inOrder; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.spy; |
| import static org.mockito.Mockito.verify; |
| |
| import android.testing.AndroidTestingRunner; |
| import android.testing.TestableLooper; |
| import android.util.ArrayMap; |
| |
| import androidx.test.filters.SmallTest; |
| |
| import com.android.systemui.SysuiTestCase; |
| import com.android.systemui.dump.DumpManager; |
| import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener; |
| import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; |
| import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; |
| import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; |
| import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger; |
| import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; |
| import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; |
| import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; |
| import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection; |
| import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; |
| import com.android.systemui.util.time.FakeSystemClock; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Captor; |
| import org.mockito.InOrder; |
| import org.mockito.Mock; |
| import org.mockito.Mockito; |
| import org.mockito.MockitoAnnotations; |
| import org.mockito.Spy; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.stream.Collectors; |
| |
| @SmallTest |
| @RunWith(AndroidTestingRunner.class) |
| @TestableLooper.RunWithLooper |
| public class ShadeListBuilderTest extends SysuiTestCase { |
| |
| private ShadeListBuilder mListBuilder; |
| private FakeSystemClock mSystemClock = new FakeSystemClock(); |
| |
| @Mock private ShadeListBuilderLogger mLogger; |
| @Mock private NotifCollection mNotifCollection; |
| @Spy private OnBeforeTransformGroupsListener mOnBeforeTransformGroupsListener; |
| @Spy private OnBeforeSortListener mOnBeforeSortListener; |
| @Spy private OnBeforeRenderListListener mOnBeforeRenderListListener; |
| @Spy private OnRenderListListener mOnRenderListListener = list -> mBuiltList = list; |
| |
| @Captor private ArgumentCaptor<CollectionReadyForBuildListener> mBuildListenerCaptor; |
| |
| private CollectionReadyForBuildListener mReadyForBuildListener; |
| private List<NotificationEntryBuilder> mPendingSet = new ArrayList<>(); |
| private List<NotificationEntry> mEntrySet = new ArrayList<>(); |
| private List<ListEntry> mBuiltList; |
| |
| private Map<String, Integer> mNextIdMap = new ArrayMap<>(); |
| private int mNextRank = 0; |
| |
| @Before |
| public void setUp() { |
| MockitoAnnotations.initMocks(this); |
| allowTestableLooperAsMainThread(); |
| |
| mListBuilder = new ShadeListBuilder(mSystemClock, mLogger, mock(DumpManager.class)); |
| mListBuilder.setOnRenderListListener(mOnRenderListListener); |
| |
| mListBuilder.attach(mNotifCollection); |
| |
| Mockito.verify(mNotifCollection).setBuildListener(mBuildListenerCaptor.capture()); |
| mReadyForBuildListener = Objects.requireNonNull(mBuildListenerCaptor.getValue()); |
| } |
| |
| @Test |
| public void testNotifsAreSortedByRankAndWhen() { |
| // GIVEN a simple pipeline |
| |
| // WHEN a series of notifs with jumbled ranks are added |
| addNotif(0, PACKAGE_1).setRank(2); |
| addNotif(1, PACKAGE_2).setRank(4).modifyNotification(mContext).setWhen(22); |
| addNotif(2, PACKAGE_3).setRank(4).modifyNotification(mContext).setWhen(33); |
| addNotif(3, PACKAGE_3).setRank(3); |
| addNotif(4, PACKAGE_5).setRank(4).modifyNotification(mContext).setWhen(11); |
| addNotif(5, PACKAGE_3).setRank(1); |
| addNotif(6, PACKAGE_1).setRank(0); |
| dispatchBuild(); |
| |
| // The final output is sorted based first by rank and then by when |
| verifyBuiltList( |
| notif(6), |
| notif(5), |
| notif(0), |
| notif(3), |
| notif(2), |
| notif(1), |
| notif(4) |
| ); |
| } |
| |
| @Test |
| public void testNotifsAreGrouped() { |
| // GIVEN a simple pipeline |
| |
| // WHEN a group is added |
| addGroupChild(0, PACKAGE_1, GROUP_1); |
| addGroupChild(1, PACKAGE_1, GROUP_1); |
| addGroupChild(2, PACKAGE_1, GROUP_1); |
| addGroupSummary(3, PACKAGE_1, GROUP_1); |
| dispatchBuild(); |
| |
| // THEN the notifs are grouped together |
| verifyBuiltList( |
| group( |
| summary(3), |
| child(0), |
| child(1), |
| child(2) |
| ) |
| ); |
| } |
| |
| @Test |
| public void testNotifsWithDifferentGroupKeysAreGrouped() { |
| // GIVEN a simple pipeline |
| |
| // WHEN a package posts two different groups |
| addGroupChild(0, PACKAGE_1, GROUP_1); |
| addGroupChild(1, PACKAGE_1, GROUP_2); |
| addGroupSummary(2, PACKAGE_1, GROUP_2); |
| addGroupChild(3, PACKAGE_1, GROUP_2); |
| addGroupChild(4, PACKAGE_1, GROUP_1); |
| addGroupChild(5, PACKAGE_1, GROUP_2); |
| addGroupChild(6, PACKAGE_1, GROUP_1); |
| addGroupSummary(7, PACKAGE_1, GROUP_1); |
| dispatchBuild(); |
| |
| // THEN the groups are separated separately |
| verifyBuiltList( |
| group( |
| summary(2), |
| child(1), |
| child(3), |
| child(5) |
| ), |
| group( |
| summary(7), |
| child(0), |
| child(4), |
| child(6) |
| ) |
| ); |
| } |
| |
| @Test |
| public void testNotifsNotifChildrenAreSorted() { |
| // GIVEN a simple pipeline |
| |
| // WHEN a group is added |
| addGroupChild(0, PACKAGE_1, GROUP_1).setRank(4); |
| addGroupChild(1, PACKAGE_1, GROUP_1).setRank(2) |
| .modifyNotification(mContext).setWhen(11); |
| addGroupChild(2, PACKAGE_1, GROUP_1).setRank(1); |
| addGroupChild(3, PACKAGE_1, GROUP_1).setRank(2) |
| .modifyNotification(mContext).setWhen(33); |
| addGroupChild(4, PACKAGE_1, GROUP_1).setRank(2) |
| .modifyNotification(mContext).setWhen(22); |
| addGroupChild(5, PACKAGE_1, GROUP_1).setRank(0); |
| addGroupSummary(6, PACKAGE_1, GROUP_1).setRank(3); |
| dispatchBuild(); |
| |
| // THEN the children are sorted by rank and when |
| verifyBuiltList( |
| group( |
| summary(6), |
| child(5), |
| child(2), |
| child(3), |
| child(4), |
| child(1), |
| child(0) |
| ) |
| ); |
| } |
| |
| @Test |
| public void testDuplicateGroupSummariesAreDiscarded() { |
| // GIVEN a simple pipeline |
| |
| // WHEN a group with multiple summaries is added |
| addNotif(0, PACKAGE_3); |
| addGroupChild(1, PACKAGE_1, GROUP_1); |
| addGroupChild(2, PACKAGE_1, GROUP_1); |
| addGroupSummary(3, PACKAGE_1, GROUP_1).setPostTime(22); |
| addGroupSummary(4, PACKAGE_1, GROUP_1).setPostTime(33); |
| addNotif(5, PACKAGE_2); |
| addGroupSummary(6, PACKAGE_1, GROUP_1).setPostTime(11); |
| addGroupChild(7, PACKAGE_1, GROUP_1); |
| dispatchBuild(); |
| |
| // THEN only most recent summary is used |
| verifyBuiltList( |
| notif(0), |
| group( |
| summary(4), |
| child(1), |
| child(2), |
| child(7) |
| ), |
| notif(5) |
| ); |
| |
| // THEN the extra summaries have their parents set to null |
| assertNull(mEntrySet.get(3).getParent()); |
| assertNull(mEntrySet.get(6).getParent()); |
| } |
| |
| @Test |
| public void testGroupsWithNoSummaryAreUngrouped() { |
| // GIVEN a group with no summary |
| addNotif(0, PACKAGE_2); |
| addGroupChild(1, PACKAGE_4, GROUP_2); |
| addGroupChild(2, PACKAGE_4, GROUP_2); |
| addGroupChild(3, PACKAGE_4, GROUP_2); |
| addGroupChild(4, PACKAGE_4, GROUP_2); |
| |
| // WHEN we build the list |
| dispatchBuild(); |
| |
| // THEN the children aren't grouped |
| verifyBuiltList( |
| notif(0), |
| notif(1), |
| notif(2), |
| notif(3), |
| notif(4) |
| ); |
| } |
| |
| @Test |
| public void testGroupsWithNoChildrenAreUngrouped() { |
| // GIVEN a group with a summary but no children |
| addGroupSummary(0, PACKAGE_5, GROUP_1); |
| addNotif(1, PACKAGE_5); |
| addNotif(2, PACKAGE_1); |
| |
| // WHEN we build the list |
| dispatchBuild(); |
| |
| // THEN the summary isn't grouped but is still added to the final list |
| verifyBuiltList( |
| notif(0), |
| notif(1), |
| notif(2) |
| ); |
| } |
| |
| @Test |
| public void testGroupsWithTooFewChildrenAreSplitUp() { |
| // GIVEN a group with one child |
| addGroupChild(0, PACKAGE_2, GROUP_1); |
| addGroupSummary(1, PACKAGE_2, GROUP_1); |
| |
| // WHEN we build the list |
| dispatchBuild(); |
| |
| // THEN the child is added at top level and the summary is discarded |
| verifyBuiltList( |
| notif(0) |
| ); |
| |
| assertNull(mEntrySet.get(1).getParent()); |
| } |
| |
| @Test |
| public void testGroupsWhoLoseChildrenMidPipelineAreSplitUp() { |
| // GIVEN a group with two children |
| addGroupChild(0, PACKAGE_2, GROUP_1); |
| addGroupSummary(1, PACKAGE_2, GROUP_1); |
| addGroupChild(2, PACKAGE_2, GROUP_1); |
| |
| // GIVEN a promoter that will promote one of children to top level |
| mListBuilder.addPromoter(new IdPromoter(0)); |
| |
| // WHEN we build the list |
| dispatchBuild(); |
| |
| // THEN both children end up at top level (because group is now too small) |
| verifyBuiltList( |
| notif(0), |
| notif(2) |
| ); |
| |
| // THEN the summary is discarded |
| assertNull(mEntrySet.get(1).getParent()); |
| } |
| |
| @Test |
| public void testPreviousParentsAreSetProperly() { |
| // GIVEN a notification that is initially added to the list |
| PackageFilter filter = new PackageFilter(PACKAGE_2); |
| filter.setEnabled(false); |
| mListBuilder.addPreGroupFilter(filter); |
| |
| addNotif(0, PACKAGE_1); |
| addNotif(1, PACKAGE_2); |
| addNotif(2, PACKAGE_3); |
| dispatchBuild(); |
| |
| // WHEN it is suddenly filtered out |
| filter.setEnabled(true); |
| dispatchBuild(); |
| |
| // THEN its previous parent indicates that it used to be added |
| assertNull(mEntrySet.get(1).getParent()); |
| assertEquals(GroupEntry.ROOT_ENTRY, mEntrySet.get(1).getPreviousParent()); |
| } |
| |
| @Test |
| public void testThatAnnulledGroupsAndSummariesAreProperlyRolledBack() { |
| // GIVEN a registered transform groups listener |
| RecordingOnBeforeTransformGroupsListener listener = |
| new RecordingOnBeforeTransformGroupsListener(); |
| mListBuilder.addOnBeforeTransformGroupsListener(listener); |
| |
| // GIVEN a malformed group that will be dismantled |
| addGroupChild(0, PACKAGE_2, GROUP_1); |
| addGroupSummary(1, PACKAGE_2, GROUP_1); |
| addNotif(2, PACKAGE_1); |
| |
| // WHEN we build the list |
| dispatchBuild(); |
| |
| // THEN only the child appears in the final list |
| verifyBuiltList( |
| notif(0), |
| notif(2) |
| ); |
| |
| // THEN the summary has a null parent and an unset firstAddedIteration |
| assertNull(mEntrySet.get(1).getParent()); |
| assertEquals(-1, mEntrySet.get(1).mFirstAddedIteration); |
| } |
| |
| @Test |
| public void testPreGroupNotifsAreFiltered() { |
| // GIVEN a PreGroupNotifFilter and PreRenderFilter that filters out the same package |
| NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_2)); |
| NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_2)); |
| mListBuilder.addPreGroupFilter(preGroupFilter); |
| mListBuilder.addPreRenderFilter(preRenderFilter); |
| |
| // WHEN the pipeline is kicked off on a list of notifs |
| addNotif(0, PACKAGE_1); |
| addNotif(1, PACKAGE_2); |
| addNotif(2, PACKAGE_3); |
| addNotif(3, PACKAGE_2); |
| dispatchBuild(); |
| |
| // THEN the preGroupFilter is called on each notif in the original set |
| verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong()); |
| verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(1)), anyLong()); |
| verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong()); |
| verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(3)), anyLong()); |
| |
| // THEN the preRenderFilter is only called on the notifications not already filtered out |
| verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong()); |
| verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(1)), anyLong()); |
| verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong()); |
| verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(3)), anyLong()); |
| |
| // THEN the final list doesn't contain any filtered-out notifs |
| verifyBuiltList( |
| notif(0), |
| notif(2) |
| ); |
| |
| // THEN each filtered notif records the NotifFilter that did it |
| assertEquals(preGroupFilter, mEntrySet.get(1).mExcludingFilter); |
| assertEquals(preGroupFilter, mEntrySet.get(3).mExcludingFilter); |
| } |
| |
| @Test |
| public void testPreRenderNotifsAreFiltered() { |
| // GIVEN a NotifFilter that filters out a specific package |
| NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2)); |
| mListBuilder.addPreRenderFilter(filter1); |
| |
| // WHEN the pipeline is kicked off on a list of notifs |
| addNotif(0, PACKAGE_1); |
| addNotif(1, PACKAGE_2); |
| addNotif(2, PACKAGE_3); |
| addNotif(3, PACKAGE_2); |
| dispatchBuild(); |
| |
| // THEN the filter is called on each notif in the original set |
| verify(filter1).shouldFilterOut(eq(mEntrySet.get(0)), anyLong()); |
| verify(filter1).shouldFilterOut(eq(mEntrySet.get(1)), anyLong()); |
| verify(filter1).shouldFilterOut(eq(mEntrySet.get(2)), anyLong()); |
| verify(filter1).shouldFilterOut(eq(mEntrySet.get(3)), anyLong()); |
| |
| // THEN the final list doesn't contain any filtered-out notifs |
| verifyBuiltList( |
| notif(0), |
| notif(2) |
| ); |
| |
| // THEN each filtered notif records the filter that did it |
| assertEquals(filter1, mEntrySet.get(1).mExcludingFilter); |
| assertEquals(filter1, mEntrySet.get(3).mExcludingFilter); |
| } |
| |
| @Test |
| public void testPreRenderNotifsFilteredBreakupGroups() { |
| final String filterTag = "FILTER_ME"; |
| // GIVEN a NotifFilter that filters out notifications with a tag |
| NotifFilter filter1 = spy(new NotifFilterWithTag(filterTag)); |
| mListBuilder.addPreRenderFilter(filter1); |
| |
| // WHEN the pipeline is kicked off on a list of notifs |
| addGroupChildWithTag(0, PACKAGE_2, GROUP_1, filterTag); |
| addGroupChild(1, PACKAGE_2, GROUP_1); |
| addGroupSummary(2, PACKAGE_2, GROUP_1); |
| dispatchBuild(); |
| |
| // THEN the final list doesn't contain any filtered-out notifs |
| // and groups that are too small are broken up |
| verifyBuiltList( |
| notif(1) |
| ); |
| |
| // THEN each filtered notif records the filter that did it |
| assertEquals(filter1, mEntrySet.get(0).mExcludingFilter); |
| } |
| |
| @Test |
| public void testNotifFiltersCanBePreempted() { |
| // GIVEN two notif filters |
| NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2)); |
| NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5)); |
| mListBuilder.addPreGroupFilter(filter1); |
| mListBuilder.addPreGroupFilter(filter2); |
| |
| // WHEN the pipeline is kicked off on a list of notifs |
| addNotif(0, PACKAGE_1); |
| addNotif(1, PACKAGE_2); |
| addNotif(2, PACKAGE_5); |
| dispatchBuild(); |
| |
| // THEN both filters are called on the first notif but the second filter is never called |
| // on the already-filtered second notif |
| verify(filter1).shouldFilterOut(eq(mEntrySet.get(0)), anyLong()); |
| verify(filter1).shouldFilterOut(eq(mEntrySet.get(1)), anyLong()); |
| verify(filter1).shouldFilterOut(eq(mEntrySet.get(2)), anyLong()); |
| verify(filter2).shouldFilterOut(eq(mEntrySet.get(0)), anyLong()); |
| verify(filter2).shouldFilterOut(eq(mEntrySet.get(2)), anyLong()); |
| |
| // THEN the final list doesn't contain any filtered-out notifs |
| verifyBuiltList( |
| notif(0) |
| ); |
| |
| // THEN each filtered notif records the filter that did it |
| assertEquals(filter1, mEntrySet.get(1).mExcludingFilter); |
| assertEquals(filter2, mEntrySet.get(2).mExcludingFilter); |
| } |
| |
| @Test |
| public void testNotifsArePromoted() { |
| // GIVEN a NotifPromoter that promotes certain notif IDs |
| NotifPromoter promoter = spy(new IdPromoter(1, 2)); |
| mListBuilder.addPromoter(promoter); |
| |
| // WHEN the pipeline is kicked off |
| addNotif(0, PACKAGE_1); |
| addGroupChild(1, PACKAGE_2, GROUP_1); |
| addGroupChild(2, PACKAGE_2, GROUP_1); |
| addGroupChild(3, PACKAGE_2, GROUP_1); |
| addGroupChild(4, PACKAGE_2, GROUP_1); |
| addGroupSummary(5, PACKAGE_2, GROUP_1); |
| addNotif(6, PACKAGE_3); |
| dispatchBuild(); |
| |
| // THEN the filter is called on each group child |
| verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(1)); |
| verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(2)); |
| verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(3)); |
| verify(promoter).shouldPromoteToTopLevel(mEntrySet.get(4)); |
| |
| // THEN the final list contains the promoted entries at top level |
| verifyBuiltList( |
| notif(0), |
| notif(2), |
| notif(3), |
| group( |
| summary(5), |
| child(1), |
| child(4)), |
| notif(6) |
| ); |
| |
| // THEN each promoted notif records the promoter that did it |
| assertEquals(promoter, mEntrySet.get(2).mNotifPromoter); |
| assertEquals(promoter, mEntrySet.get(3).mNotifPromoter); |
| } |
| |
| @Test |
| public void testNotifPromotersCanBePreempted() { |
| // GIVEN two notif promoters |
| NotifPromoter promoter1 = spy(new IdPromoter(1)); |
| NotifPromoter promoter2 = spy(new IdPromoter(2)); |
| mListBuilder.addPromoter(promoter1); |
| mListBuilder.addPromoter(promoter2); |
| |
| // WHEN the pipeline is kicked off on some notifs and a group |
| addNotif(0, PACKAGE_1); |
| addGroupChild(1, PACKAGE_2, GROUP_1); |
| addGroupChild(2, PACKAGE_2, GROUP_1); |
| addGroupChild(3, PACKAGE_2, GROUP_1); |
| addGroupSummary(4, PACKAGE_2, GROUP_1); |
| addNotif(5, PACKAGE_3); |
| dispatchBuild(); |
| |
| // THEN both promoters are called on each child, except for children that a previous |
| // promoter has already promoted |
| verify(promoter1).shouldPromoteToTopLevel(mEntrySet.get(1)); |
| verify(promoter1).shouldPromoteToTopLevel(mEntrySet.get(2)); |
| verify(promoter1).shouldPromoteToTopLevel(mEntrySet.get(3)); |
| |
| verify(promoter2).shouldPromoteToTopLevel(mEntrySet.get(1)); |
| verify(promoter2).shouldPromoteToTopLevel(mEntrySet.get(3)); |
| |
| // THEN each promoter is recorded on each notif it promoted |
| assertEquals(promoter1, mEntrySet.get(2).mNotifPromoter); |
| assertEquals(promoter2, mEntrySet.get(3).mNotifPromoter); |
| } |
| |
| @Test |
| public void testNotifSections() { |
| // GIVEN a filter that removes all PACKAGE_4 notifs and sections that divide |
| // notifs based on package name |
| mListBuilder.addPreGroupFilter(new PackageFilter(PACKAGE_4)); |
| final NotifSection pkg1Section = spy(new PackageSection(PACKAGE_1)); |
| final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2)); |
| // NOTE: no package 3 section explicitly added, so notifs with package 3 will get set by |
| // ShadeListBuilder's sDefaultSection which will demote it to the last section |
| final NotifSection pkg4Section = spy(new PackageSection(PACKAGE_4)); |
| final NotifSection pkg5Section = spy(new PackageSection(PACKAGE_5)); |
| mListBuilder.setSections(Arrays.asList(pkg1Section, pkg2Section, pkg4Section, pkg5Section)); |
| |
| // WHEN we build a list with different packages |
| addNotif(0, PACKAGE_4); |
| addNotif(1, PACKAGE_2); |
| addNotif(2, PACKAGE_1); |
| addNotif(3, PACKAGE_3); |
| addGroupSummary(4, PACKAGE_2, GROUP_1); |
| addGroupChild(5, PACKAGE_2, GROUP_1); |
| addGroupChild(6, PACKAGE_2, GROUP_1); |
| addNotif(7, PACKAGE_1); |
| addNotif(8, PACKAGE_2); |
| addNotif(9, PACKAGE_5); |
| addNotif(10, PACKAGE_4); |
| dispatchBuild(); |
| |
| // THEN the list is sorted according to section |
| verifyBuiltList( |
| notif(2), |
| notif(7), |
| notif(1), |
| group( |
| summary(4), |
| child(5), |
| child(6) |
| ), |
| notif(8), |
| notif(9), |
| notif(3) |
| ); |
| |
| // THEN the first section (pkg1Section) is called on all top level elements (but |
| // no children and no entries that were filtered out) |
| verify(pkg1Section).isInSection(mEntrySet.get(1)); |
| verify(pkg1Section).isInSection(mEntrySet.get(2)); |
| verify(pkg1Section).isInSection(mEntrySet.get(3)); |
| verify(pkg1Section).isInSection(mEntrySet.get(7)); |
| verify(pkg1Section).isInSection(mEntrySet.get(8)); |
| verify(pkg1Section).isInSection(mEntrySet.get(9)); |
| verify(pkg1Section).isInSection(mBuiltList.get(3)); |
| |
| verify(pkg1Section, never()).isInSection(mEntrySet.get(0)); |
| verify(pkg1Section, never()).isInSection(mEntrySet.get(4)); |
| verify(pkg1Section, never()).isInSection(mEntrySet.get(5)); |
| verify(pkg1Section, never()).isInSection(mEntrySet.get(6)); |
| verify(pkg1Section, never()).isInSection(mEntrySet.get(10)); |
| |
| // THEN the last section (pkg5Section) is not called on any of the entries that were |
| // filtered or already in a section |
| verify(pkg5Section, never()).isInSection(mEntrySet.get(0)); |
| verify(pkg5Section, never()).isInSection(mEntrySet.get(1)); |
| verify(pkg5Section, never()).isInSection(mEntrySet.get(2)); |
| verify(pkg5Section, never()).isInSection(mEntrySet.get(4)); |
| verify(pkg5Section, never()).isInSection(mEntrySet.get(5)); |
| verify(pkg5Section, never()).isInSection(mEntrySet.get(6)); |
| verify(pkg5Section, never()).isInSection(mEntrySet.get(7)); |
| verify(pkg5Section, never()).isInSection(mEntrySet.get(8)); |
| verify(pkg5Section, never()).isInSection(mEntrySet.get(10)); |
| |
| verify(pkg5Section).isInSection(mEntrySet.get(3)); |
| verify(pkg5Section).isInSection(mEntrySet.get(9)); |
| |
| // THEN the correct section is assigned for entries in pkg1Section |
| assertEquals(pkg1Section, mEntrySet.get(2).mNotifSection); |
| assertEquals(0, mEntrySet.get(2).getSection()); |
| assertEquals(pkg1Section, mEntrySet.get(7).mNotifSection); |
| assertEquals(0, mEntrySet.get(7).getSection()); |
| |
| // THEN the correct section is assigned for entries in pkg2Section |
| assertEquals(pkg2Section, mEntrySet.get(1).mNotifSection); |
| assertEquals(1, mEntrySet.get(1).getSection()); |
| assertEquals(pkg2Section, mEntrySet.get(8).mNotifSection); |
| assertEquals(1, mEntrySet.get(8).getSection()); |
| assertEquals(pkg2Section, mBuiltList.get(3).mNotifSection); |
| assertEquals(1, mBuiltList.get(3).getSection()); |
| |
| // THEN no section was assigned to entries in pkg4Section (since they were filtered) |
| assertEquals(null, mEntrySet.get(0).mNotifSection); |
| assertEquals(-1, mEntrySet.get(0).getSection()); |
| assertEquals(null, mEntrySet.get(10).mNotifSection); |
| assertEquals(-1, mEntrySet.get(10).getSection()); |
| |
| |
| // THEN the correct section is assigned for entries in pkg5Section |
| assertEquals(pkg5Section, mEntrySet.get(9).mNotifSection); |
| assertEquals(3, mEntrySet.get(9).getSection()); |
| |
| // THEN the children entries are assigned the same section as its parent |
| assertEquals(mBuiltList.get(3).mNotifSection, child(5).entry.mNotifSection); |
| assertEquals(mBuiltList.get(3).getSection(), child(5).entry.getSection()); |
| assertEquals(mBuiltList.get(3).mNotifSection, child(6).entry.mNotifSection); |
| assertEquals(mBuiltList.get(3).getSection(), child(6).entry.getSection()); |
| } |
| |
| @Test |
| public void testNotifUsesDefaultSection() { |
| // GIVEN a Section for Package2 |
| final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2)); |
| mListBuilder.setSections(Arrays.asList(pkg2Section)); |
| |
| // WHEN we build a list with pkg1 and pkg2 packages |
| addNotif(0, PACKAGE_1); |
| addNotif(1, PACKAGE_2); |
| dispatchBuild(); |
| |
| // THEN the list is sorted according to section |
| verifyBuiltList( |
| notif(1), |
| notif(0) |
| ); |
| |
| // THEN the entry that didn't have an explicit section gets assigned the DefaultSection |
| assertEquals(1, notif(0).entry.getSection()); |
| assertNotNull(notif(0).entry.mNotifSection); |
| } |
| |
| @Test |
| public void testThatNotifComparatorsAreCalled() { |
| // GIVEN a set of comparators that care about specific packages |
| mListBuilder.setComparators(Arrays.asList( |
| new HypeComparator(PACKAGE_4), |
| new HypeComparator(PACKAGE_1, PACKAGE_3), |
| new HypeComparator(PACKAGE_2) |
| )); |
| |
| // WHEN the pipeline is kicked off on a bunch of notifications |
| addNotif(0, PACKAGE_1); |
| addNotif(1, PACKAGE_5); |
| addNotif(2, PACKAGE_3); |
| addNotif(3, PACKAGE_4); |
| addNotif(4, PACKAGE_2); |
| dispatchBuild(); |
| |
| // THEN the notifs are sorted according to the hierarchy of comparators |
| verifyBuiltList( |
| notif(3), |
| notif(0), |
| notif(2), |
| notif(4), |
| notif(1) |
| ); |
| } |
| |
| @Test |
| public void testListenersAndPluggablesAreFiredInOrder() { |
| // GIVEN a bunch of registered listeners and pluggables |
| NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1)); |
| NotifPromoter promoter = spy(new IdPromoter(3)); |
| NotifSection section = spy(new PackageSection(PACKAGE_1)); |
| NotifComparator comparator = spy(new HypeComparator(PACKAGE_4)); |
| NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5)); |
| mListBuilder.addPreGroupFilter(preGroupFilter); |
| mListBuilder.addOnBeforeTransformGroupsListener(mOnBeforeTransformGroupsListener); |
| mListBuilder.addPromoter(promoter); |
| mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener); |
| mListBuilder.setComparators(Collections.singletonList(comparator)); |
| mListBuilder.setSections(Arrays.asList(section)); |
| mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener); |
| mListBuilder.addPreRenderFilter(preRenderFilter); |
| |
| // WHEN a few new notifs are added |
| addNotif(0, PACKAGE_1); |
| addGroupSummary(1, PACKAGE_2, GROUP_1); |
| addGroupChild(2, PACKAGE_2, GROUP_1); |
| addGroupChild(3, PACKAGE_2, GROUP_1); |
| addNotif(4, PACKAGE_5); |
| addNotif(5, PACKAGE_5); |
| addNotif(6, PACKAGE_4); |
| dispatchBuild(); |
| |
| // THEN the pluggables and listeners are called in order |
| InOrder inOrder = inOrder( |
| preGroupFilter, |
| mOnBeforeTransformGroupsListener, |
| promoter, |
| mOnBeforeSortListener, |
| section, |
| comparator, |
| preRenderFilter, |
| mOnBeforeRenderListListener, |
| mOnRenderListListener); |
| |
| inOrder.verify(preGroupFilter, atLeastOnce()) |
| .shouldFilterOut(any(NotificationEntry.class), anyLong()); |
| inOrder.verify(mOnBeforeTransformGroupsListener) |
| .onBeforeTransformGroups(anyList()); |
| inOrder.verify(promoter, atLeastOnce()) |
| .shouldPromoteToTopLevel(any(NotificationEntry.class)); |
| inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList()); |
| inOrder.verify(section, atLeastOnce()).isInSection(any(ListEntry.class)); |
| inOrder.verify(comparator, atLeastOnce()) |
| .compare(any(ListEntry.class), any(ListEntry.class)); |
| inOrder.verify(preRenderFilter, atLeastOnce()) |
| .shouldFilterOut(any(NotificationEntry.class), anyLong()); |
| inOrder.verify(mOnBeforeRenderListListener).onBeforeRenderList(anyList()); |
| inOrder.verify(mOnRenderListListener).onRenderList(anyList()); |
| } |
| |
| @Test |
| public void testThatPluggableInvalidationsTriggersRerun() { |
| // GIVEN a variety of pluggables |
| NotifFilter packageFilter = new PackageFilter(PACKAGE_1); |
| NotifPromoter idPromoter = new IdPromoter(4); |
| NotifSection section = new PackageSection(PACKAGE_1); |
| NotifComparator hypeComparator = new HypeComparator(PACKAGE_2); |
| |
| mListBuilder.addPreGroupFilter(packageFilter); |
| mListBuilder.addPromoter(idPromoter); |
| mListBuilder.setSections(Arrays.asList(section)); |
| mListBuilder.setComparators(Collections.singletonList(hypeComparator)); |
| |
| // GIVEN a set of random notifs |
| addNotif(0, PACKAGE_1); |
| addNotif(1, PACKAGE_2); |
| addNotif(2, PACKAGE_3); |
| dispatchBuild(); |
| |
| // WHEN each pluggable is invalidated THEN the list is re-rendered |
| |
| clearInvocations(mOnRenderListListener); |
| packageFilter.invalidateList(); |
| verify(mOnRenderListListener).onRenderList(anyList()); |
| |
| clearInvocations(mOnRenderListListener); |
| idPromoter.invalidateList(); |
| verify(mOnRenderListListener).onRenderList(anyList()); |
| |
| clearInvocations(mOnRenderListListener); |
| section.invalidateList(); |
| verify(mOnRenderListListener).onRenderList(anyList()); |
| |
| clearInvocations(mOnRenderListListener); |
| hypeComparator.invalidateList(); |
| verify(mOnRenderListListener).onRenderList(anyList()); |
| } |
| |
| @Test |
| public void testNotifFiltersAreAllSentTheSameNow() { |
| // GIVEN three notif filters |
| NotifFilter filter1 = spy(new PackageFilter(PACKAGE_5)); |
| NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5)); |
| NotifFilter filter3 = spy(new PackageFilter(PACKAGE_5)); |
| mListBuilder.addPreGroupFilter(filter1); |
| mListBuilder.addPreGroupFilter(filter2); |
| mListBuilder.addPreGroupFilter(filter3); |
| |
| // GIVEN the SystemClock is set to a particular time: |
| mSystemClock.setUptimeMillis(10047); |
| |
| // WHEN the pipeline is kicked off on a list of notifs |
| addNotif(0, PACKAGE_1); |
| addNotif(1, PACKAGE_2); |
| dispatchBuild(); |
| |
| // THEN the value of `now` is the same for all calls to shouldFilterOut |
| verify(filter1).shouldFilterOut(mEntrySet.get(0), 10047); |
| verify(filter2).shouldFilterOut(mEntrySet.get(0), 10047); |
| verify(filter3).shouldFilterOut(mEntrySet.get(0), 10047); |
| verify(filter1).shouldFilterOut(mEntrySet.get(1), 10047); |
| verify(filter2).shouldFilterOut(mEntrySet.get(1), 10047); |
| verify(filter3).shouldFilterOut(mEntrySet.get(1), 10047); |
| } |
| |
| @Test |
| public void testGroupTransformEntries() { |
| // GIVEN a registered OnBeforeTransformGroupsListener |
| RecordingOnBeforeTransformGroupsListener listener = |
| new RecordingOnBeforeTransformGroupsListener(); |
| mListBuilder.addOnBeforeTransformGroupsListener(listener); |
| |
| // GIVEN some new notifs |
| addNotif(0, PACKAGE_1); |
| addGroupChild(1, PACKAGE_2, GROUP_1); |
| addGroupSummary(2, PACKAGE_2, GROUP_1); |
| addGroupChild(3, PACKAGE_2, GROUP_1); |
| addNotif(4, PACKAGE_3); |
| addGroupChild(5, PACKAGE_2, GROUP_1); |
| |
| // WHEN we run the pipeline |
| dispatchBuild(); |
| |
| verifyBuiltList( |
| notif(0), |
| group( |
| summary(2), |
| child(1), |
| child(3), |
| child(5) |
| ), |
| notif(4) |
| ); |
| |
| // THEN all the new notifs, including the new GroupEntry, are passed to the listener |
| assertEquals( |
| Arrays.asList( |
| mEntrySet.get(0), |
| mBuiltList.get(1), |
| mEntrySet.get(4)), |
| listener.mEntriesReceived |
| ); |
| } |
| |
| @Test |
| public void testGroupTransformEntriesOnSecondRun() { |
| // GIVEN a registered OnBeforeTransformGroupsListener |
| RecordingOnBeforeTransformGroupsListener listener = |
| spy(new RecordingOnBeforeTransformGroupsListener()); |
| mListBuilder.addOnBeforeTransformGroupsListener(listener); |
| |
| // GIVEN some notifs that have already been added (two of which are in malformed groups) |
| addNotif(0, PACKAGE_1); |
| addGroupChild(1, PACKAGE_2, GROUP_1); |
| addGroupChild(2, PACKAGE_3, GROUP_2); |
| |
| dispatchBuild(); |
| clearInvocations(listener); |
| |
| // WHEN we run the pipeline |
| addGroupSummary(3, PACKAGE_2, GROUP_1); |
| addGroupChild(4, PACKAGE_3, GROUP_2); |
| addGroupSummary(5, PACKAGE_3, GROUP_2); |
| addGroupChild(6, PACKAGE_3, GROUP_2); |
| addNotif(7, PACKAGE_2); |
| |
| dispatchBuild(); |
| |
| verifyBuiltList( |
| notif(0), |
| notif(1), |
| group( |
| summary(5), |
| child(2), |
| child(4), |
| child(6) |
| ), |
| notif(7) |
| ); |
| |
| // THEN all the new notifs, including the new GroupEntry, are passed to the listener |
| assertEquals( |
| Arrays.asList( |
| mEntrySet.get(0), |
| mBuiltList.get(2), |
| mEntrySet.get(7), |
| mEntrySet.get(1)), |
| listener.mEntriesReceived |
| ); |
| } |
| |
| @Test |
| public void testDispatchListOnBeforeSort() { |
| // GIVEN a registered OnBeforeSortListener |
| RecordingOnBeforeSortListener listener = |
| new RecordingOnBeforeSortListener(); |
| mListBuilder.addOnBeforeSortListener(listener); |
| mListBuilder.setComparators(Arrays.asList(new HypeComparator(PACKAGE_3))); |
| |
| // GIVEN some new notifs out of order |
| addNotif(0, PACKAGE_1); |
| addNotif(1, PACKAGE_2); |
| addNotif(2, PACKAGE_3); |
| |
| // WHEN we run the pipeline |
| dispatchBuild(); |
| |
| // THEN all the new notifs are passed to the listener out of order |
| assertEquals( |
| Arrays.asList( |
| mEntrySet.get(0), |
| mEntrySet.get(1), |
| mEntrySet.get(2)), |
| listener.mEntriesReceived |
| ); |
| |
| // THEN the final list is in order |
| verifyBuiltList( |
| notif(2), |
| notif(0), |
| notif(1) |
| ); |
| } |
| |
| @Test |
| public void testDispatchListOnBeforeRender() { |
| // GIVEN a registered OnBeforeRenderList |
| RecordingOnBeforeRenderistener listener = |
| new RecordingOnBeforeRenderistener(); |
| mListBuilder.addOnBeforeRenderListListener(listener); |
| |
| // GIVEN some new notifs out of order |
| addNotif(0, PACKAGE_1); |
| addNotif(1, PACKAGE_2); |
| addNotif(2, PACKAGE_3); |
| |
| // WHEN we run the pipeline |
| dispatchBuild(); |
| |
| // THEN all the new notifs are passed to the listener |
| assertEquals( |
| Arrays.asList( |
| mEntrySet.get(0), |
| mEntrySet.get(1), |
| mEntrySet.get(2)), |
| listener.mEntriesReceived |
| ); |
| } |
| |
| @Test |
| public void testAnnulledGroupsHaveParentSetProperly() { |
| // GIVEN a list containing a small group that's already been built once |
| addGroupChild(0, PACKAGE_2, GROUP_2); |
| addGroupSummary(1, PACKAGE_2, GROUP_2); |
| addGroupChild(2, PACKAGE_2, GROUP_2); |
| dispatchBuild(); |
| |
| verifyBuiltList( |
| group( |
| summary(1), |
| child(0), |
| child(2) |
| ) |
| ); |
| GroupEntry group = (GroupEntry) mBuiltList.get(0); |
| |
| // WHEN a child is removed such that the group is no longer big enough |
| mEntrySet.remove(2); |
| dispatchBuild(); |
| |
| // THEN the group is annulled and its parent is set back to null |
| verifyBuiltList( |
| notif(0) |
| ); |
| assertNull(group.getParent()); |
| |
| // but its previous parent indicates that it was added in the previous iteration |
| assertEquals(GroupEntry.ROOT_ENTRY, group.getPreviousParent()); |
| } |
| |
| @Test(expected = IllegalStateException.class) |
| public void testOutOfOrderPreGroupFilterInvalidationThrows() { |
| // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage |
| NotifFilter filter = new PackageFilter(PACKAGE_5); |
| OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList(); |
| mListBuilder.addPreGroupFilter(filter); |
| mListBuilder.addOnBeforeTransformGroupsListener(listener); |
| |
| // WHEN we try to run the pipeline and the filter is invalidated |
| addNotif(0, PACKAGE_1); |
| dispatchBuild(); |
| |
| // THEN an exception is thrown |
| } |
| |
| @Test(expected = IllegalStateException.class) |
| public void testOutOfOrderPrompterInvalidationThrows() { |
| // GIVEN a NotifPromoter that gets invalidated during the sorting stage |
| NotifPromoter promoter = new IdPromoter(47); |
| OnBeforeSortListener listener = |
| (list) -> promoter.invalidateList(); |
| mListBuilder.addPromoter(promoter); |
| mListBuilder.addOnBeforeSortListener(listener); |
| |
| // WHEN we try to run the pipeline and the promoter is invalidated |
| addNotif(0, PACKAGE_1); |
| dispatchBuild(); |
| |
| // THEN an exception is thrown |
| } |
| |
| @Test(expected = IllegalStateException.class) |
| public void testOutOfOrderComparatorInvalidationThrows() { |
| // GIVEN a NotifComparator that gets invalidated during the finalizing stage |
| NotifComparator comparator = new HypeComparator(PACKAGE_5); |
| OnBeforeRenderListListener listener = |
| (list) -> comparator.invalidateList(); |
| mListBuilder.setComparators(Collections.singletonList(comparator)); |
| mListBuilder.addOnBeforeRenderListListener(listener); |
| |
| // WHEN we try to run the pipeline and the comparator is invalidated |
| addNotif(0, PACKAGE_1); |
| dispatchBuild(); |
| |
| // THEN an exception is thrown |
| } |
| |
| @Test(expected = IllegalStateException.class) |
| public void testOutOfOrderPreRenderFilterInvalidationThrows() { |
| // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage |
| NotifFilter filter = new PackageFilter(PACKAGE_5); |
| OnBeforeRenderListListener listener = (list) -> filter.invalidateList(); |
| mListBuilder.addPreRenderFilter(filter); |
| mListBuilder.addOnBeforeRenderListListener(listener); |
| |
| // WHEN we try to run the pipeline and the PreRenderFilter is invalidated |
| addNotif(0, PACKAGE_1); |
| dispatchBuild(); |
| |
| // THEN an exception is thrown |
| } |
| |
| @Test |
| public void testInOrderPreRenderFilter() { |
| // GIVEN a PreRenderFilter that gets invalidated during the grouping stage |
| NotifFilter filter = new PackageFilter(PACKAGE_5); |
| OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList(); |
| mListBuilder.addPreRenderFilter(filter); |
| mListBuilder.addOnBeforeTransformGroupsListener(listener); |
| |
| // WHEN we try to run the pipeline and the filter is invalidated |
| addNotif(0, PACKAGE_1); |
| dispatchBuild(); |
| |
| // THEN no exception thrown |
| } |
| |
| /** |
| * Adds a notif to the collection that will be passed to the list builder when |
| * {@link #dispatchBuild()}s is called. |
| * |
| * @param index Index of this notification in the set. This must be the current size of the set. |
| * it exists to improve readability of the resulting code, since later tests will |
| * have to refer to notifs by index. |
| * @param packageId Package that the notif should be posted under |
| * @return A NotificationEntryBuilder that can be used to further modify the notif. Do not call |
| * build() on the builder; that will be done on the next dispatchBuild(). |
| */ |
| private NotificationEntryBuilder addNotif(int index, String packageId) { |
| final NotificationEntryBuilder builder = new NotificationEntryBuilder() |
| .setPkg(packageId) |
| .setId(nextId(packageId)) |
| .setRank(nextRank()); |
| |
| builder.modifyNotification(mContext) |
| .setContentTitle("Top level singleton") |
| .setChannelId("test_channel"); |
| |
| assertEquals(mEntrySet.size() + mPendingSet.size(), index); |
| mPendingSet.add(builder); |
| return builder; |
| } |
| |
| /** Same behavior as {@link #addNotif(int, String)}. */ |
| private NotificationEntryBuilder addGroupSummary(int index, String packageId, String groupId) { |
| final NotificationEntryBuilder builder = new NotificationEntryBuilder() |
| .setPkg(packageId) |
| .setId(nextId(packageId)) |
| .setRank(nextRank()); |
| |
| builder.modifyNotification(mContext) |
| .setChannelId("test_channel") |
| .setContentTitle("Group summary") |
| .setGroup(groupId) |
| .setGroupSummary(true); |
| |
| assertEquals(mEntrySet.size() + mPendingSet.size(), index); |
| mPendingSet.add(builder); |
| return builder; |
| } |
| |
| private NotificationEntryBuilder addGroupChildWithTag(int index, String packageId, |
| String groupId, String tag) { |
| final NotificationEntryBuilder builder = new NotificationEntryBuilder() |
| .setTag(tag) |
| .setPkg(packageId) |
| .setId(nextId(packageId)) |
| .setRank(nextRank()); |
| |
| builder.modifyNotification(mContext) |
| .setChannelId("test_channel") |
| .setContentTitle("Group child") |
| .setGroup(groupId); |
| |
| assertEquals(mEntrySet.size() + mPendingSet.size(), index); |
| mPendingSet.add(builder); |
| return builder; |
| } |
| |
| /** Same behavior as {@link #addNotif(int, String)}. */ |
| private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) { |
| return addGroupChildWithTag(index, packageId, groupId, null); |
| } |
| |
| private int nextId(String packageName) { |
| Integer nextId = mNextIdMap.get(packageName); |
| if (nextId == null) { |
| nextId = 0; |
| } |
| mNextIdMap.put(packageName, nextId + 1); |
| return nextId; |
| } |
| |
| private int nextRank() { |
| int nextRank = mNextRank; |
| mNextRank++; |
| return nextRank; |
| } |
| |
| private void dispatchBuild() { |
| if (mPendingSet.size() > 0) { |
| for (NotificationEntryBuilder builder : mPendingSet) { |
| mEntrySet.add(builder.build()); |
| } |
| mPendingSet.clear(); |
| } |
| |
| mReadyForBuildListener.onBuildList(mEntrySet); |
| } |
| |
| private void verifyBuiltList(ExpectedEntry ...expectedEntries) { |
| try { |
| assertEquals( |
| "List is the wrong length", |
| expectedEntries.length, |
| mBuiltList.size()); |
| |
| for (int i = 0; i < expectedEntries.length; i++) { |
| ListEntry outEntry = mBuiltList.get(i); |
| ExpectedEntry expectedEntry = expectedEntries[i]; |
| |
| if (expectedEntry instanceof ExpectedNotif) { |
| assertEquals( |
| "Entry " + i + " isn't a NotifEntry", |
| NotificationEntry.class, |
| outEntry.getClass()); |
| assertEquals( |
| "Entry " + i + " doesn't match expected value.", |
| ((ExpectedNotif) expectedEntry).entry, outEntry); |
| } else { |
| ExpectedGroup cmpGroup = (ExpectedGroup) expectedEntry; |
| |
| assertEquals( |
| "Entry " + i + " isn't a GroupEntry", |
| GroupEntry.class, |
| outEntry.getClass()); |
| |
| GroupEntry outGroup = (GroupEntry) outEntry; |
| |
| assertEquals( |
| "Summary notif for entry " + i |
| + " doesn't match expected value", |
| cmpGroup.summary, |
| outGroup.getSummary()); |
| assertEquals( |
| "Summary notif for entry " + i |
| + " doesn't have proper parent", |
| outGroup, |
| outGroup.getSummary().getParent()); |
| |
| assertEquals("Children for entry " + i, |
| cmpGroup.children, |
| outGroup.getChildren()); |
| |
| for (int j = 0; j < outGroup.getChildren().size(); j++) { |
| NotificationEntry child = outGroup.getChildren().get(j); |
| assertEquals( |
| "Child " + j + " for entry " + i |
| + " doesn't have proper parent", |
| outGroup, |
| child.getParent()); |
| } |
| } |
| } |
| } catch (AssertionError err) { |
| throw new AssertionError( |
| "List under test failed verification:\n" + dumpTree(mBuiltList, |
| true, ""), err); |
| } |
| } |
| |
| private ExpectedNotif notif(int index) { |
| return new ExpectedNotif(mEntrySet.get(index)); |
| } |
| |
| private ExpectedGroup group(ExpectedSummary summary, ExpectedChild...children) { |
| return new ExpectedGroup( |
| summary.entry, |
| Arrays.stream(children) |
| .map(child -> child.entry) |
| .collect(Collectors.toList())); |
| } |
| |
| private ExpectedSummary summary(int index) { |
| return new ExpectedSummary(mEntrySet.get(index)); |
| } |
| |
| private ExpectedChild child(int index) { |
| return new ExpectedChild(mEntrySet.get(index)); |
| } |
| |
| private abstract static class ExpectedEntry { |
| } |
| |
| private static class ExpectedNotif extends ExpectedEntry { |
| public final NotificationEntry entry; |
| |
| private ExpectedNotif(NotificationEntry entry) { |
| this.entry = entry; |
| } |
| } |
| |
| private static class ExpectedGroup extends ExpectedEntry { |
| public final NotificationEntry summary; |
| public final List<NotificationEntry> children; |
| |
| private ExpectedGroup( |
| NotificationEntry summary, |
| List<NotificationEntry> children) { |
| this.summary = summary; |
| this.children = children; |
| } |
| } |
| |
| private static class ExpectedSummary { |
| public final NotificationEntry entry; |
| |
| private ExpectedSummary(NotificationEntry entry) { |
| this.entry = entry; |
| } |
| } |
| |
| private static class ExpectedChild { |
| public final NotificationEntry entry; |
| |
| private ExpectedChild(NotificationEntry entry) { |
| this.entry = entry; |
| } |
| } |
| |
| /** Filters out notifs from a particular package */ |
| private static class PackageFilter extends NotifFilter { |
| private final String mPackageName; |
| |
| private boolean mEnabled = true; |
| |
| PackageFilter(String packageName) { |
| super("PackageFilter"); |
| |
| mPackageName = packageName; |
| } |
| |
| @Override |
| public boolean shouldFilterOut(NotificationEntry entry, long now) { |
| return mEnabled && entry.getSbn().getPackageName().equals(mPackageName); |
| } |
| |
| public void setEnabled(boolean enabled) { |
| mEnabled = enabled; |
| } |
| } |
| |
| /** Filters out notifications with a particular tag */ |
| private static class NotifFilterWithTag extends NotifFilter { |
| private final String mTag; |
| |
| NotifFilterWithTag(String tag) { |
| super("NotifFilterWithTag_" + tag); |
| mTag = tag; |
| } |
| |
| @Override |
| public boolean shouldFilterOut(NotificationEntry entry, long now) { |
| return Objects.equals(entry.getSbn().getTag(), mTag); |
| } |
| } |
| |
| /** Promotes notifs with particular IDs */ |
| private static class IdPromoter extends NotifPromoter { |
| private final List<Integer> mIds; |
| |
| IdPromoter(Integer... ids) { |
| super("IdPromoter"); |
| mIds = Arrays.asList(ids); |
| } |
| |
| @Override |
| public boolean shouldPromoteToTopLevel(NotificationEntry child) { |
| return mIds.contains(child.getSbn().getId()); |
| } |
| } |
| |
| /** Sorts specific notifs above all others. */ |
| private static class HypeComparator extends NotifComparator { |
| |
| private final List<String> mPreferredPackages; |
| |
| HypeComparator(String ...preferredPackages) { |
| super("HypeComparator"); |
| mPreferredPackages = Arrays.asList(preferredPackages); |
| } |
| |
| @Override |
| public int compare(ListEntry o1, ListEntry o2) { |
| boolean contains1 = mPreferredPackages.contains( |
| o1.getRepresentativeEntry().getSbn().getPackageName()); |
| boolean contains2 = mPreferredPackages.contains( |
| o2.getRepresentativeEntry().getSbn().getPackageName()); |
| |
| return Boolean.compare(contains2, contains1); |
| } |
| } |
| |
| /** Represents a section for the passed pkg */ |
| private static class PackageSection extends NotifSection { |
| private final String mPackage; |
| |
| PackageSection(String pkg) { |
| super("PackageSection_" + pkg); |
| mPackage = pkg; |
| } |
| |
| @Override |
| public boolean isInSection(ListEntry entry) { |
| return entry.getRepresentativeEntry().getSbn().getPackageName().equals(mPackage); |
| } |
| } |
| |
| private static class RecordingOnBeforeTransformGroupsListener |
| implements OnBeforeTransformGroupsListener { |
| List<ListEntry> mEntriesReceived; |
| |
| @Override |
| public void onBeforeTransformGroups(List<ListEntry> list) { |
| mEntriesReceived = new ArrayList<>(list); |
| } |
| } |
| |
| private static class RecordingOnBeforeSortListener |
| implements OnBeforeSortListener { |
| List<ListEntry> mEntriesReceived; |
| |
| @Override |
| public void onBeforeSort(List<ListEntry> list) { |
| mEntriesReceived = new ArrayList<>(list); |
| } |
| } |
| |
| private static class RecordingOnBeforeRenderistener |
| implements OnBeforeRenderListListener { |
| List<ListEntry> mEntriesReceived; |
| |
| @Override |
| public void onBeforeRenderList(List<ListEntry> list) { |
| mEntriesReceived = new ArrayList<>(list); |
| } |
| } |
| |
| private static final String PACKAGE_1 = "com.test1"; |
| private static final String PACKAGE_2 = "com.test2"; |
| private static final String PACKAGE_3 = "org.test3"; |
| private static final String PACKAGE_4 = "com.test4"; |
| private static final String PACKAGE_5 = "com.test5"; |
| |
| private static final String GROUP_1 = "group_1"; |
| private static final String GROUP_2 = "group_2"; |
| } |