1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar.notification.collection;
18 
19 import android.os.Handler;
20 
21 import androidx.annotation.Nullable;
22 
23 import com.android.systemui.dagger.SysUISingleton;
24 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
25 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
26 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
27 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
28 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
29 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
30 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
31 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
32 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
33 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
34 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
35 import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater;
36 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
37 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
38 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
39 
40 import java.util.Collection;
41 import java.util.List;
42 
43 import javax.inject.Inject;
44 
45 /**
46  * The system that constructs the "shade list", the filtered, grouped, and sorted list of
47  * notifications that are currently being displayed to the user in the notification shade.
48  *
49  * The pipeline proceeds through a series of stages in order to produce the final list (see below).
50  * Each stage exposes hooks and listeners to allow other code to participate.
51  *
52  * This list differs from the canonical one we receive from system server in a few ways:
53  * - Filtered: Some notifications are filtered out. For example, we filter out notifications whose
54  *   views haven't been inflated yet. We also filter out some notifications if we're on the lock
55  *   screen and notifications for other users. So participate, see
56  *   {@link #addPreGroupFilter} and similar methods.
57  * - Grouped: Notifications that are part of the same group are clustered together into a single
58  *   GroupEntry. These groups are then transformed in order to remove children or completely split
59  *   them apart. To participate, see {@link #addPromoter}.
60  * - Sorted: All top-level notifications are sorted. To participate, see
61  *   {@link #setSections} and {@link #setComparators}
62  *
63  * The exact order of all hooks is as follows:
64  *  0. Collection listeners are fired ({@link #addCollectionListener}).
65  *  1. Pre-group filters are fired on each notification ({@link #addPreGroupFilter}).
66  *  2. Initial grouping is performed (NotificationEntries will have their parents set
67  *     appropriately).
68  *  3. OnBeforeTransformGroupListeners are fired ({@link #addOnBeforeTransformGroupsListener})
69  *  4. NotifPromoters are called on each notification with a parent ({@link #addPromoter})
70  *  5. OnBeforeSortListeners are fired ({@link #addOnBeforeSortListener})
71  *  6. Top-level entries are assigned sections by NotifSections ({@link #setSections})
72  *  7. Top-level entries within the same section are sorted by NotifComparators
73  *     ({@link #setComparators})
74  *  8. Finalize filters are fired on each notification ({@link #addFinalizeFilter})
75  *  9. OnBeforeRenderListListeners are fired ({@link #addOnBeforeRenderListListener})
76  *  9. The list is handed off to the view layer to be rendered
77  */
78 @SysUISingleton
79 public class NotifPipeline implements CommonNotifCollection {
80     private final NotifCollection mNotifCollection;
81     private final ShadeListBuilder mShadeListBuilder;
82 
83     @Inject
NotifPipeline( NotifCollection notifCollection, ShadeListBuilder shadeListBuilder)84     public NotifPipeline(
85             NotifCollection notifCollection,
86             ShadeListBuilder shadeListBuilder) {
87         mNotifCollection = notifCollection;
88         mShadeListBuilder = shadeListBuilder;
89     }
90 
91     /**
92      * Returns the list of all known notifications, i.e. the notifications that are currently posted
93      * to the phone. In general, this tracks closely to the list maintained by NotificationManager,
94      * but it can diverge slightly due to lifetime extenders.
95      *
96      * The returned collection is read-only, unsorted, unfiltered, and ungrouped.
97      */
98     @Override
getAllNotifs()99     public Collection<NotificationEntry> getAllNotifs() {
100         return mNotifCollection.getAllNotifs();
101     }
102 
103     @Override
addCollectionListener(NotifCollectionListener listener)104     public void addCollectionListener(NotifCollectionListener listener) {
105         mNotifCollection.addCollectionListener(listener);
106     }
107 
108     /**
109      * Returns the NotificationEntry associated with [key].
110      */
111     @Nullable
getEntry(String key)112     public NotificationEntry getEntry(String key) {
113         return mNotifCollection.getEntry(key);
114     }
115 
116     /**
117      * Registers a lifetime extender. Lifetime extenders can cause notifications that have been
118      * dismissed or retracted by system server to be temporarily retained in the collection.
119      */
addNotificationLifetimeExtender(NotifLifetimeExtender extender)120     public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
121         mNotifCollection.addNotificationLifetimeExtender(extender);
122     }
123 
124     /**
125      * Registers a dismiss interceptor. Dismiss interceptors can cause notifications that have been
126      * dismissed by the user to be retained (won't send a dismissal to system server).
127      */
addNotificationDismissInterceptor(NotifDismissInterceptor interceptor)128     public void addNotificationDismissInterceptor(NotifDismissInterceptor interceptor) {
129         mNotifCollection.addNotificationDismissInterceptor(interceptor);
130     }
131 
132     /**
133      * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters
134      * are called on each notification in the order that they were registered. If any filter
135      * returns true, the notification is removed from the pipeline (and no other filters are
136      * called on that notif).
137      */
addPreGroupFilter(NotifFilter filter)138     public void addPreGroupFilter(NotifFilter filter) {
139         mShadeListBuilder.addPreGroupFilter(filter);
140     }
141 
142     /**
143      * Called after notifications have been filtered and after the initial grouping has been
144      * performed but before NotifPromoters have had a chance to promote children out of groups.
145      */
addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener)146     public void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) {
147         mShadeListBuilder.addOnBeforeTransformGroupsListener(listener);
148     }
149 
150     /**
151      * Registers a promoter with the pipeline. Promoters are able to promote child notifications to
152      * top-level, i.e. move a notification that would be a child of a group and make it appear
153      * ungrouped. Promoters are called on each child notification in the order that they are
154      * registered. If any promoter returns true, the notification is removed from the group (and no
155      * other promoters are called on it).
156      */
addPromoter(NotifPromoter promoter)157     public void addPromoter(NotifPromoter promoter) {
158         mShadeListBuilder.addPromoter(promoter);
159     }
160 
161     /**
162      * Called after notifs have been filtered and groups have been determined but before sections
163      * have been determined or the notifs have been sorted.
164      */
addOnBeforeSortListener(OnBeforeSortListener listener)165     public void addOnBeforeSortListener(OnBeforeSortListener listener) {
166         mShadeListBuilder.addOnBeforeSortListener(listener);
167     }
168 
169     /**
170      * Sections that are used to sort top-level entries.  If two entries have the same section,
171      * NotifComparators are consulted. Sections from this list are called in order for each
172      * notification passed through the pipeline. The first NotifSection to return true for
173      * {@link NotifSectioner#isInSection(ListEntry)} sets the entry as part of its Section.
174      */
setSections(List<NotifSectioner> sections)175     public void setSections(List<NotifSectioner> sections) {
176         mShadeListBuilder.setSectioners(sections);
177     }
178 
179     /**
180      * StabilityManager that is used to determine whether to suppress group and section changes.
181      * This should only be set once.
182      */
setVisualStabilityManager(NotifStabilityManager notifStabilityManager)183     public void setVisualStabilityManager(NotifStabilityManager notifStabilityManager) {
184         mShadeListBuilder.setNotifStabilityManager(notifStabilityManager);
185     }
186 
187     /**
188      * Comparators that are used to sort top-level entries that share the same section. The
189      * comparators are executed in order until one of them returns a non-zero result. If all return
190      * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when).
191      */
setComparators(List<NotifComparator> comparators)192     public void setComparators(List<NotifComparator> comparators) {
193         mShadeListBuilder.setComparators(comparators);
194     }
195 
196     /**
197      * Called after notifs have been filtered once, grouped, and sorted but before the final
198      * filtering.
199      */
addOnBeforeFinalizeFilterListener(OnBeforeFinalizeFilterListener listener)200     public void addOnBeforeFinalizeFilterListener(OnBeforeFinalizeFilterListener listener) {
201         mShadeListBuilder.addOnBeforeFinalizeFilterListener(listener);
202     }
203 
204     /**
205      * Registers a filter with the pipeline to filter right before rendering the list (after
206      * pre-group filtering, grouping, promoting and sorting occurs). Filters are
207      * called on each notification in the order that they were registered. If any filter returns
208      * true, the notification is removed from the pipeline (and no other filters are called on that
209      * notif).
210      */
addFinalizeFilter(NotifFilter filter)211     public void addFinalizeFilter(NotifFilter filter) {
212         mShadeListBuilder.addFinalizeFilter(filter);
213     }
214 
215     /**
216      * Called at the end of the pipeline after the notif list has been finalized but before it has
217      * been handed off to the view layer.
218      */
addOnBeforeRenderListListener(OnBeforeRenderListListener listener)219     public void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
220         mShadeListBuilder.addOnBeforeRenderListListener(listener);
221     }
222 
223     /** Registers an invalidator that can be used to invalidate the entire notif list. */
addPreRenderInvalidator(Invalidator invalidator)224     public void addPreRenderInvalidator(Invalidator invalidator) {
225         mShadeListBuilder.addPreRenderInvalidator(invalidator);
226     }
227 
228     /**
229      * Get an object which can be used to update a notification (internally to the pipeline)
230      * in response to a user action.
231      *
232      * @param name the name of the component that will update notifiations
233      * @return an updater
234      */
getInternalNotifUpdater(String name)235     public InternalNotifUpdater getInternalNotifUpdater(String name) {
236         return mNotifCollection.getInternalNotifUpdater(name);
237     }
238 
239     /**
240      * Returns a read-only view in to the current shade list, i.e. the list of notifications that
241      * are currently present in the shade. If this method is called during pipeline execution it
242      * will return the current state of the list, which will likely be only partially-generated.
243      */
getShadeList()244     public List<ListEntry> getShadeList() {
245         return mShadeListBuilder.getShadeList();
246     }
247 
248     /**
249      * Returns the number of notifications currently shown in the shade. This includes all
250      * children and summary notifications. If this method is called during pipeline execution it
251      * will return the number of notifications in its current state, which will likely be only
252      * partially-generated.
253      */
getShadeListCount()254     public int getShadeListCount() {
255         final List<ListEntry> entries = getShadeList();
256         int numNotifs = 0;
257         for (int i = 0; i < entries.size(); i++) {
258             final ListEntry entry = entries.get(i);
259             if (entry instanceof GroupEntry) {
260                 final GroupEntry parentEntry = (GroupEntry) entry;
261                 numNotifs++; // include the summary in the count
262                 numNotifs += parentEntry.getChildren().size();
263             } else {
264                 numNotifs++;
265             }
266         }
267 
268         return numNotifs;
269     }
270 }
271