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:
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user