Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4822097e authored by Beverly's avatar Beverly
Browse files

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
parent b6f4dc22
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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();
+5 −1
Original line number Diff line number Diff line
@@ -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) {
+9 −10
Original line number Diff line number Diff line
@@ -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);
    }

    /**
+65 −22
Original line number Diff line number Diff line
@@ -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");
        }

    private static final NotifSection sDefaultSection =
            new NotifSection("DefaultSection") {
                @Override
        public int getSection(ListEntry entry) {
            return 0;
        }
                public boolean isInSection(ListEntry entry) {
                    return true;
                }
            };

    private static final String TAG = "NotifListBuilderImpl";

+5 −0
Original line number Diff line number Diff line
@@ -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;
    }
}
Loading