Rework Notification Sectioning

To create a notification section:
- Override Coordinator#getSection to return your desired NotifSection
- Ensure your new section is added in the correct order to
NotifCoordinators.mOrderedSections

Test: atest SystemUiTests
Change-Id: Ia3f83f4b662cdfb198981e94fba67f97eab767b8
This commit is contained in:
Beverly
2020-01-13 16:55:38 -05:00
parent b6f4dc2245
commit 4822097e1c
9 changed files with 283 additions and 88 deletions

View File

@@ -99,6 +99,13 @@ public class ListDumper {
.append(")");
}
if (entry.mNotifSection != null) {
sb.append(" sectionIndex=")
.append(entry.getSection())
.append(" sectionName=")
.append(entry.mNotifSection.getName());
}
if (includeRecordKeeping) {
NotificationEntry notifEntry = entry.getRepresentativeEntry();
StringBuilder rksb = new StringBuilder();

View File

@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.collection;
import android.annotation.Nullable;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
/**
* Abstract superclass for top-level entries, i.e. things that can appear in the final notification
* list shown to users. In practice, this means either GroupEntries or NotificationEntries.
@@ -27,7 +29,9 @@ public abstract class ListEntry {
@Nullable private GroupEntry mParent;
@Nullable private GroupEntry mPreviousParent;
private int mSection;
@Nullable NotifSection mNotifSection;
private int mSection = -1;
int mFirstAddedIteration = -1;
ListEntry(String key) {

View File

@@ -22,7 +22,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
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.SectionsProvider;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
@@ -48,7 +48,7 @@ import javax.inject.Singleton;
* GroupEntry. These groups are then transformed in order to remove children or completely split
* them apart. To participate, see {@link #addPromoter}.
* - Sorted: All top-level notifications are sorted. To participate, see
* {@link #setSectionsProvider} and {@link #setComparators}
* {@link #setSections} and {@link #setComparators}
*
* The exact order of all hooks is as follows:
* 0. Collection listeners are fired ({@link #addCollectionListener}).
@@ -58,7 +58,7 @@ import javax.inject.Singleton;
* 3. OnBeforeTransformGroupListeners are fired ({@link #addOnBeforeTransformGroupsListener})
* 4. NotifPromoters are called on each notification with a parent ({@link #addPromoter})
* 5. OnBeforeSortListeners are fired ({@link #addOnBeforeSortListener})
* 6. SectionsProvider is called on each top-level entry in the list ({@link #setSectionsProvider})
* 6. Top-level entries are assigned sections by NotifSections ({@link #setSections})
* 7. Top-level entries within the same section are sorted by NotifComparators
* ({@link #setComparators})
* 8. Pre-render filters are fired on each notification ({@link #addPreRenderFilter})
@@ -142,14 +142,13 @@ public class NotifPipeline {
}
/**
* Assigns sections to each top-level entry, where a section is simply an integer. Sections are
* the primary metric by which top-level entries are sorted; NotifComparators are only consulted
* when two entries are in the same section. The pipeline doesn't assign any particular meaning
* to section IDs -- from it's perspective they're just numbers and it sorts them by a simple
* numerical comparison.
* Sections that are used to sort top-level entries. If two entries have the same section,
* NotifComparators are consulted. Sections from this list are called in order for each
* notification passed through the pipeline. The first NotifSection to return true for
* {@link NotifSection#isInSection(ListEntry)} sets the entry as part of its Section.
*/
public void setSectionsProvider(SectionsProvider provider) {
mShadeListBuilder.setSectionsProvider(provider);
public void setSections(List<NotifSection> sections) {
mShadeListBuilder.setSections(sections);
}
/**

View File

@@ -30,6 +30,7 @@ import static com.android.systemui.statusbar.notification.collection.listbuilder
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.util.ArrayMap;
import android.util.Pair;
import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
@@ -40,7 +41,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.Pipeli
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.SectionsProvider;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.statusbar.notification.logging.NotifEvent;
import com.android.systemui.statusbar.notification.logging.NotifLog;
@@ -82,7 +83,7 @@ public class ShadeListBuilder implements Dumpable {
private final List<NotifPromoter> mNotifPromoters = new ArrayList<>();
private final List<NotifFilter> mNotifPreRenderFilters = new ArrayList<>();
private final List<NotifComparator> mNotifComparators = new ArrayList<>();
private SectionsProvider mSectionsProvider = new DefaultSectionsProvider();
private final List<NotifSection> mNotifSections = new ArrayList<>();
private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
new ArrayList<>();
@@ -170,12 +171,15 @@ public class ShadeListBuilder implements Dumpable {
promoter.setInvalidationListener(this::onPromoterInvalidated);
}
void setSectionsProvider(SectionsProvider provider) {
void setSections(List<NotifSection> sections) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
mSectionsProvider = provider;
provider.setInvalidationListener(this::onSectionsProviderInvalidated);
mNotifSections.clear();
for (NotifSection section : sections) {
mNotifSections.add(section);
section.setInvalidationListener(this::onNotifSectionInvalidated);
}
}
void setComparators(List<NotifComparator> comparators) {
@@ -230,12 +234,12 @@ public class ShadeListBuilder implements Dumpable {
rebuildListIfBefore(STATE_TRANSFORMING);
}
private void onSectionsProviderInvalidated(SectionsProvider provider) {
private void onNotifSectionInvalidated(NotifSection section) {
Assert.isMainThread();
mNotifLog.log(NotifEvent.SECTIONS_PROVIDER_INVALIDATED, String.format(
"Sections provider \"%s\" invalidated; pipeline state is %d",
provider.getName(),
mNotifLog.log(NotifEvent.SECTION_INVALIDATED, String.format(
"Section \"%s\" invalidated; pipeline state is %d",
section.getName(),
mPipelineState.getState()));
rebuildListIfBefore(STATE_SORTING);
@@ -318,7 +322,7 @@ public class ShadeListBuilder implements Dumpable {
sortList();
// Step 6: Filter out entries after pre-group filtering, grouping, promoting and sorting
// Now filters can see grouping information to determine whether to filter or not
// Now filters can see grouping information to determine whether to filter or not.
mPipelineState.incrementTo(STATE_PRE_RENDER_FILTERING);
filterNotifs(mNotifList, mNewNotifList, mNotifPreRenderFilters);
applyNewNotifList();
@@ -580,6 +584,8 @@ public class ShadeListBuilder implements Dumpable {
* filtered out during any of the filtering steps.
*/
private void annulAddition(ListEntry entry) {
entry.setSection(-1);
entry.mNotifSection = null;
entry.setParent(null);
if (entry.mFirstAddedIteration == mIterationCount) {
entry.mFirstAddedIteration = -1;
@@ -589,11 +595,12 @@ public class ShadeListBuilder implements Dumpable {
private void sortList() {
// Assign sections to top-level elements and sort their children
for (ListEntry entry : mNotifList) {
entry.setSection(mSectionsProvider.getSection(entry));
Pair<NotifSection, Integer> sectionWithIndex = applySections(entry);
if (entry instanceof GroupEntry) {
GroupEntry parent = (GroupEntry) entry;
for (NotificationEntry child : parent.getChildren()) {
child.setSection(0);
child.mNotifSection = sectionWithIndex.first;
child.setSection(sectionWithIndex.second);
}
parent.sortChildren(sChildComparator);
}
@@ -754,6 +761,45 @@ public class ShadeListBuilder implements Dumpable {
return null;
}
private Pair<NotifSection, Integer> applySections(ListEntry entry) {
final Pair<NotifSection, Integer> sectionWithIndex = findSection(entry);
final NotifSection section = sectionWithIndex.first;
final Integer sectionIndex = sectionWithIndex.second;
if (section != entry.mNotifSection) {
if (entry.mNotifSection == null) {
mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format(
"%s: sectioned by '%s' [index=%d].",
entry.getKey(),
section.getName(),
sectionIndex));
} else {
mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format(
"%s: section changed: '%s' [index=%d] -> '%s [index=%d]'.",
entry.getKey(),
entry.mNotifSection,
entry.getSection(),
section,
sectionIndex));
}
entry.mNotifSection = section;
entry.setSection(sectionIndex);
}
return sectionWithIndex;
}
private Pair<NotifSection, Integer> findSection(ListEntry entry) {
for (int i = 0; i < mNotifSections.size(); i++) {
NotifSection sectioner = mNotifSections.get(i);
if (sectioner.isInSection(entry)) {
return new Pair<>(sectioner, i);
}
}
return new Pair<>(sDefaultSection, mNotifSections.size());
}
private void rebuildListIfBefore(@PipelineState.StateName int state) {
mPipelineState.requireIsBefore(state);
if (mPipelineState.is(STATE_IDLE)) {
@@ -803,16 +849,13 @@ public class ShadeListBuilder implements Dumpable {
void onRenderList(List<ListEntry> entries);
}
private static class DefaultSectionsProvider extends SectionsProvider {
DefaultSectionsProvider() {
super("DefaultSectionsProvider");
}
@Override
public int getSection(ListEntry entry) {
return 0;
}
}
private static final NotifSection sDefaultSection =
new NotifSection("DefaultSection") {
@Override
public boolean isInSection(ListEntry entry) {
return true;
}
};
private static final String TAG = "NotifListBuilderImpl";

View File

@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
/**
@@ -28,4 +29,8 @@ public interface Coordinator {
* Coordinators should register their listeners and {@link Pluggable}s to the pipeline.
*/
void attach(NotifPipeline pipeline);
default NotifSection getSection() {
return null;
}
}

View File

@@ -16,9 +16,11 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
@@ -39,18 +41,21 @@ import javax.inject.Singleton;
public class NotifCoordinators implements Dumpable {
private static final String TAG = "NotifCoordinators";
private final List<Coordinator> mCoordinators = new ArrayList<>();
private final List<NotifSection> mOrderedSections = new ArrayList<>();
/**
* Creates all the coordinators.
*/
@Inject
public NotifCoordinators(
DumpController dumpController,
FeatureFlags featureFlags,
KeyguardCoordinator keyguardCoordinator,
RankingCoordinator rankingCoordinator,
ForegroundCoordinator foregroundCoordinator,
DeviceProvisionedCoordinator deviceProvisionedCoordinator,
PreparationCoordinator preparationCoordinator) {
dumpController.registerDumpable(TAG, this);
mCoordinators.add(keyguardCoordinator);
mCoordinators.add(rankingCoordinator);
mCoordinators.add(foregroundCoordinator);
@@ -59,6 +64,13 @@ public class NotifCoordinators implements Dumpable {
mCoordinators.add(preparationCoordinator);
}
// TODO: add new Coordinators here! (b/145134683, b/112656837)
// TODO: add the sections in a particular ORDER (HeadsUp < People < Alerting)
for (Coordinator c : mCoordinators) {
if (c.getSection() != null) {
mOrderedSections.add(c.getSection());
}
}
}
/**
@@ -69,6 +81,8 @@ public class NotifCoordinators implements Dumpable {
for (Coordinator c : mCoordinators) {
c.attach(pipeline);
}
pipeline.setSections(mOrderedSections);
}
@Override
@@ -78,5 +92,9 @@ public class NotifCoordinators implements Dumpable {
for (Coordinator c : mCoordinators) {
pw.println("\t" + c.getClass());
}
for (NotifSection s : mOrderedSections) {
pw.println("\t" + s.getName());
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 The Android Open Source Project
* Copyright (C) 2020 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.
@@ -17,21 +17,21 @@
package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
/**
* Interface for sorting notifications into "sections", such as a heads-upping section, people
* section, alerting section, silent section, etc.
* Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSections}.
*/
public abstract class SectionsProvider extends Pluggable<SectionsProvider> {
protected SectionsProvider(String name) {
public abstract class NotifSection extends Pluggable<NotifSection> {
protected NotifSection(String name) {
super(name);
}
/**
* Returns the section that this entry belongs to. A section can be any non-negative integer.
* When entries are sorted, they are first sorted by section and then by any remainining
* comparators.
* If returns true, this notification is considered within this section.
* Sectioning is performed on each top level notification each time the pipeline is run.
* However, this doesn't necessarily mean that your section will get called on each top-level
* notification. The first section to return true determines the section of the notification.
*/
public abstract int getSection(ListEntry entry);
public abstract boolean isInSection(ListEntry entry);
}

View File

@@ -94,7 +94,7 @@ public class NotifEvent extends RichEvent {
LIST_BUILD_COMPLETE,
PRE_GROUP_FILTER_INVALIDATED,
PROMOTER_INVALIDATED,
SECTIONS_PROVIDER_INVALIDATED,
SECTION_INVALIDATED,
COMPARATOR_INVALIDATED,
PARENT_CHANGED,
FILTER_CHANGED,
@@ -132,12 +132,13 @@ public class NotifEvent extends RichEvent {
"ListBuildComplete",
"FilterInvalidated",
"PromoterInvalidated",
"SectionsProviderInvalidated",
"SectionInvalidated",
"ComparatorInvalidated",
"ParentChanged",
"FilterChanged",
"PromoterChanged",
"FinalFilterInvalidated",
"SectionerChanged",
// NEM event labels:
"NotifAdded",
@@ -170,13 +171,14 @@ public class NotifEvent extends RichEvent {
public static final int LIST_BUILD_COMPLETE = 4;
public static final int PRE_GROUP_FILTER_INVALIDATED = 5;
public static final int PROMOTER_INVALIDATED = 6;
public static final int SECTIONS_PROVIDER_INVALIDATED = 7;
public static final int SECTION_INVALIDATED = 7;
public static final int COMPARATOR_INVALIDATED = 8;
public static final int PARENT_CHANGED = 9;
public static final int FILTER_CHANGED = 10;
public static final int PROMOTER_CHANGED = 11;
public static final int PRE_RENDER_FILTER_INVALIDATED = 12;
private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 13;
public static final int SECTION_CHANGED = 13;
private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 14;
/**
* Events related to {@link NotificationEntryManager}

View File

@@ -19,6 +19,7 @@ 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;
@@ -47,7 +48,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
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.SectionsProvider;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.util.Assert;
@@ -449,6 +450,29 @@ public class ShadeListBuilderTest extends SysuiTestCase {
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
@@ -552,12 +576,17 @@ public class ShadeListBuilderTest extends SysuiTestCase {
}
@Test
public void testNotifsAreSectioned() {
// GIVEN a filter that removes all PACKAGE_4 notifs and a SectionsProvider that divides
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 SectionsProvider sectionsProvider = spy(new PackageSectioner());
mListBuilder.setSectionsProvider(sectionsProvider);
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);
@@ -584,19 +613,93 @@ public class ShadeListBuilderTest extends SysuiTestCase {
child(6)
),
notif(8),
notif(3),
notif(9)
notif(9),
notif(3)
);
// THEN the sections provider is called on all top level elements (but no children and no
// entries that were filtered out)
verify(sectionsProvider).getSection(mEntrySet.get(1));
verify(sectionsProvider).getSection(mEntrySet.get(2));
verify(sectionsProvider).getSection(mEntrySet.get(3));
verify(sectionsProvider).getSection(mEntrySet.get(7));
verify(sectionsProvider).getSection(mEntrySet.get(8));
verify(sectionsProvider).getSection(mEntrySet.get(9));
verify(sectionsProvider).getSection(mBuiltList.get(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
@@ -631,7 +734,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
// GIVEN a bunch of registered listeners and pluggables
NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1));
NotifPromoter promoter = spy(new IdPromoter(3));
PackageSectioner sectioner = spy(new PackageSectioner());
NotifSection section = spy(new PackageSection(PACKAGE_1));
NotifComparator comparator = spy(new HypeComparator(PACKAGE_4));
NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5));
mListBuilder.addPreGroupFilter(preGroupFilter);
@@ -639,7 +742,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
mListBuilder.addPromoter(promoter);
mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener);
mListBuilder.setComparators(Collections.singletonList(comparator));
mListBuilder.setSectionsProvider(sectioner);
mListBuilder.setSections(Arrays.asList(section));
mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener);
mListBuilder.addPreRenderFilter(preRenderFilter);
@@ -659,7 +762,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
mOnBeforeTransformGroupsListener,
promoter,
mOnBeforeSortListener,
sectioner,
section,
comparator,
preRenderFilter,
mOnBeforeRenderListListener,
@@ -672,7 +775,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
inOrder.verify(promoter, atLeastOnce())
.shouldPromoteToTopLevel(any(NotificationEntry.class));
inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList());
inOrder.verify(sectioner, atLeastOnce()).getSection(any(ListEntry.class));
inOrder.verify(section, atLeastOnce()).isInSection(any(ListEntry.class));
inOrder.verify(comparator, atLeastOnce())
.compare(any(ListEntry.class), any(ListEntry.class));
inOrder.verify(preRenderFilter, atLeastOnce())
@@ -686,12 +789,12 @@ public class ShadeListBuilderTest extends SysuiTestCase {
// GIVEN a variety of pluggables
NotifFilter packageFilter = new PackageFilter(PACKAGE_1);
NotifPromoter idPromoter = new IdPromoter(4);
SectionsProvider sectionsProvider = new PackageSectioner();
NotifSection section = new PackageSection(PACKAGE_1);
NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
mListBuilder.addPreGroupFilter(packageFilter);
mListBuilder.addPromoter(idPromoter);
mListBuilder.setSectionsProvider(sectionsProvider);
mListBuilder.setSections(Arrays.asList(section));
mListBuilder.setComparators(Collections.singletonList(hypeComparator));
// GIVEN a set of random notifs
@@ -711,7 +814,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
sectionsProvider.invalidateList();
section.invalidateList();
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
@@ -984,9 +1087,10 @@ public class ShadeListBuilderTest extends SysuiTestCase {
return builder;
}
/** Same behavior as {@link #addNotif(int, String)}. */
private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) {
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());
@@ -1001,6 +1105,11 @@ public class ShadeListBuilderTest extends SysuiTestCase {
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) {
@@ -1169,6 +1278,21 @@ public class ShadeListBuilderTest extends SysuiTestCase {
}
}
/** 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;
@@ -1205,25 +1329,18 @@ public class ShadeListBuilderTest extends SysuiTestCase {
}
}
/** Sorts notifs into sections based on their package name */
private static class PackageSectioner extends SectionsProvider {
/** Represents a section for the passed pkg */
private static class PackageSection extends NotifSection {
private final String mPackage;
PackageSectioner() {
super("PackageSectioner");
PackageSection(String pkg) {
super("PackageSection_" + pkg);
mPackage = pkg;
}
@Override
public int getSection(ListEntry entry) {
switch (entry.getRepresentativeEntry().getSbn().getPackageName()) {
case PACKAGE_1:
return 1;
case PACKAGE_2:
return 2;
case PACKAGE_3:
return 3;
default:
return 4;
}
public boolean isInSection(ListEntry entry) {
return entry.getRepresentativeEntry().getSbn().getPackageName().equals(mPackage);
}
}