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