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 package com.android.systemui.statusbar.notification.collection 17 18 import com.android.systemui.dagger.SysUISingleton 19 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener 20 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener 21 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener 22 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener 23 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener 24 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener 25 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener 26 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator 27 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifBundler 28 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator 29 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter 30 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter 31 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner 32 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager 33 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection 34 import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater 35 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener 36 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor 37 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender 38 import com.android.systemui.statusbar.notification.collection.render.RenderStageManager 39 import javax.inject.Inject 40 41 /** 42 * The system that constructs the "shade list", the filtered, grouped, and sorted list of 43 * notifications that are currently being displayed to the user in the notification shade. 44 * 45 * The pipeline proceeds through a series of stages in order to produce the final list (see below). 46 * Each stage exposes hooks and listeners to allow other code to participate. 47 * 48 * This list differs from the canonical one we receive from system server in a few ways: 49 * - Filtered: Some notifications are filtered out. For example, we filter out notifications whose 50 * views haven't been inflated yet. We also filter out some notifications if we're on the lock 51 * screen and notifications for other users. To participate, see 52 * [.addPreGroupFilter] and similar methods. 53 * - Grouped: Notifications that are part of the same group are clustered together into a single 54 * GroupEntry. These groups are then transformed in order to remove children or completely split 55 * them apart. To participate, see [.addPromoter]. 56 * - Sorted: All top-level notifications are sorted. To participate, see 57 * [.setSections] and [.setComparators] 58 * 59 * The exact order of all hooks is as follows: 60 * 0. Collection listeners are fired ([.addCollectionListener]). 61 * 1. Pre-group filters are fired on each notification ([.addPreGroupFilter]). 62 * 2. Initial grouping is performed (NotificationEntries will have their parents set 63 * appropriately). 64 * 3. OnBeforeTransformGroupListeners are fired ([.addOnBeforeTransformGroupsListener]) 65 * 4. NotifPromoters are called on each notification with a parent ([.addPromoter]) 66 * 5. OnBeforeSortListeners are fired ([.addOnBeforeSortListener]) 67 * 6. Top-level entries are assigned sections by NotifSections ([.setSections]) 68 * 7. Top-level entries within the same section are sorted by NotifComparators ([.setComparators]) 69 * 8. OnBeforeFinalizeFilterListeners are fired ([.addOnBeforeFinalizeFilterListener]) 70 * 9. Finalize filters are fired on each notification ([.addFinalizeFilter]) 71 * 10. OnBeforeRenderListListeners are fired ([.addOnBeforeRenderListListener]) 72 * 11. The list is handed off to the view layer to be rendered 73 * 12. OnAfterRenderListListeners are fired ([.addOnAfterRenderListListener]) 74 * 13. OnAfterRenderGroupListeners are fired ([.addOnAfterRenderGroupListener]) 75 * 13. OnAfterRenderEntryListeners are fired ([.addOnAfterRenderEntryListener]) 76 */ 77 @SysUISingleton 78 class NotifPipeline @Inject constructor( 79 private val mNotifCollection: NotifCollection, 80 private val mShadeListBuilder: ShadeListBuilder, 81 private val mRenderStageManager: RenderStageManager 82 ) : CommonNotifCollection { 83 /** 84 * Returns the list of all known notifications, i.e. the notifications that are currently posted 85 * to the phone. In general, this tracks closely to the list maintained by NotificationManager, 86 * but it can diverge slightly due to lifetime extenders. 87 * 88 * The returned collection is read-only, unsorted, unfiltered, and ungrouped. 89 */ getAllNotifsnull90 override fun getAllNotifs(): Collection<NotificationEntry> { 91 return mNotifCollection.allNotifs 92 } 93 addCollectionListenernull94 override fun addCollectionListener(listener: NotifCollectionListener) { 95 mNotifCollection.addCollectionListener(listener) 96 } 97 removeCollectionListenernull98 override fun removeCollectionListener(listener: NotifCollectionListener) { 99 mNotifCollection.removeCollectionListener(listener) 100 } 101 102 /** 103 * Returns the NotificationEntry associated with [key]. 104 */ getEntrynull105 override fun getEntry(key: String): NotificationEntry? { 106 return mNotifCollection.getEntry(key) 107 } 108 109 /** 110 * Registers a lifetime extender. Lifetime extenders can cause notifications that have been 111 * dismissed or retracted by system server to be temporarily retained in the collection. 112 */ addNotificationLifetimeExtendernull113 fun addNotificationLifetimeExtender(extender: NotifLifetimeExtender) { 114 mNotifCollection.addNotificationLifetimeExtender(extender) 115 } 116 117 /** 118 * Registers a dismiss interceptor. Dismiss interceptors can cause notifications that have been 119 * dismissed by the user to be retained (won't send a dismissal to system server). 120 */ addNotificationDismissInterceptornull121 fun addNotificationDismissInterceptor(interceptor: NotifDismissInterceptor) { 122 mNotifCollection.addNotificationDismissInterceptor(interceptor) 123 } 124 125 /** 126 * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters 127 * are called on each notification in the order that they were registered. If any filter 128 * returns true, the notification is removed from the pipeline (and no other filters are 129 * called on that notif). 130 */ addPreGroupFilternull131 fun addPreGroupFilter(filter: NotifFilter) { 132 mShadeListBuilder.addPreGroupFilter(filter) 133 } 134 135 /** 136 * Called after notifications have been filtered and after the initial grouping has been 137 * performed but before NotifPromoters have had a chance to promote children out of groups. 138 */ addOnBeforeTransformGroupsListenernull139 fun addOnBeforeTransformGroupsListener(listener: OnBeforeTransformGroupsListener) { 140 mShadeListBuilder.addOnBeforeTransformGroupsListener(listener) 141 } 142 143 /** 144 * Registers a promoter with the pipeline. Promoters are able to promote child notifications to 145 * top-level, i.e. move a notification that would be a child of a group and make it appear 146 * ungrouped. Promoters are called on each child notification in the order that they are 147 * registered. If any promoter returns true, the notification is removed from the group (and no 148 * other promoters are called on it). 149 */ addPromoternull150 fun addPromoter(promoter: NotifPromoter) { 151 mShadeListBuilder.addPromoter(promoter) 152 } 153 154 /** 155 * Called after notifs have been filtered and groups have been determined but before sections 156 * have been determined or the notifs have been sorted. 157 */ addOnBeforeSortListenernull158 fun addOnBeforeSortListener(listener: OnBeforeSortListener) { 159 mShadeListBuilder.addOnBeforeSortListener(listener) 160 } 161 162 /** 163 * Sections that are used to sort top-level entries. If two entries have the same section, 164 * NotifComparators are consulted. Sections from this list are called in order for each 165 * notification passed through the pipeline. The first NotifSection to return true for 166 * [NotifSectioner.isInSection] sets the entry as part of its Section. 167 */ setSectionsnull168 fun setSections(sections: List<NotifSectioner>) { 169 mShadeListBuilder.setSectioners(sections) 170 } 171 172 /** 173 * NotifBundler that is used to determine whether a notification should be bundled according to 174 * classification. 175 */ setNotifBundlernull176 fun setNotifBundler(bundler: NotifBundler) { 177 mShadeListBuilder.setBundler(bundler) 178 } 179 180 /** 181 * StabilityManager that is used to determine whether to suppress group and section changes. 182 * This should only be set once. 183 */ setVisualStabilityManagernull184 fun setVisualStabilityManager(notifStabilityManager: NotifStabilityManager) { 185 mShadeListBuilder.setNotifStabilityManager(notifStabilityManager) 186 } 187 188 /** 189 * Comparators that are used to sort top-level entries that share the same section. The 190 * comparators are executed in order until one of them returns a non-zero result. If all return 191 * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when). 192 */ setComparatorsnull193 fun setComparators(comparators: List<NotifComparator>) { 194 mShadeListBuilder.setComparators(comparators) 195 } 196 197 /** 198 * Called after notifs have been filtered once, grouped, and sorted but before the final 199 * filtering. 200 */ addOnBeforeFinalizeFilterListenernull201 fun addOnBeforeFinalizeFilterListener(listener: OnBeforeFinalizeFilterListener) { 202 mShadeListBuilder.addOnBeforeFinalizeFilterListener(listener) 203 } 204 205 /** 206 * Registers a filter with the pipeline to filter right before rendering the list (after 207 * pre-group filtering, grouping, promoting and sorting occurs). Filters are 208 * called on each notification in the order that they were registered. If any filter returns 209 * true, the notification is removed from the pipeline (and no other filters are called on that 210 * notif). 211 */ addFinalizeFilternull212 fun addFinalizeFilter(filter: NotifFilter) { 213 mShadeListBuilder.addFinalizeFilter(filter) 214 } 215 216 /** 217 * Called at the end of the pipeline after the notif list has been finalized but before it has 218 * been handed off to the view layer. 219 */ addOnBeforeRenderListListenernull220 fun addOnBeforeRenderListListener(listener: OnBeforeRenderListListener) { 221 mShadeListBuilder.addOnBeforeRenderListListener(listener) 222 } 223 224 /** Registers an invalidator that can be used to invalidate the entire notif list. */ addPreRenderInvalidatornull225 fun addPreRenderInvalidator(invalidator: Invalidator) { 226 mShadeListBuilder.addPreRenderInvalidator(invalidator) 227 } 228 229 /** 230 * Called at the end of the pipeline after the notif list has been handed off to the view layer. 231 */ addOnAfterRenderListListenernull232 fun addOnAfterRenderListListener(listener: OnAfterRenderListListener) { 233 mRenderStageManager.addOnAfterRenderListListener(listener) 234 } 235 236 /** 237 * Called at the end of the pipeline after a group has been handed off to the view layer. 238 */ addOnAfterRenderGroupListenernull239 fun addOnAfterRenderGroupListener(listener: OnAfterRenderGroupListener) { 240 mRenderStageManager.addOnAfterRenderGroupListener(listener) 241 } 242 243 /** 244 * Called at the end of the pipeline after an entry has been handed off to the view layer. 245 * This will be called for every top level entry, every group summary, and every group child. 246 */ addOnAfterRenderEntryListenernull247 fun addOnAfterRenderEntryListener(listener: OnAfterRenderEntryListener) { 248 mRenderStageManager.addOnAfterRenderEntryListener(listener) 249 } 250 251 /** 252 * Get an object which can be used to update a notification (internally to the pipeline) 253 * in response to a user action. 254 * 255 * @param name the name of the component that will update notifiations 256 * @return an updater 257 */ getInternalNotifUpdaternull258 fun getInternalNotifUpdater(name: String?): InternalNotifUpdater { 259 return mNotifCollection.getInternalNotifUpdater(name) 260 } 261 } 262