• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.coordinator;
18 
19 import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 
24 import com.android.systemui.plugins.statusbar.StatusBarStateController;
25 import com.android.systemui.statusbar.notification.collection.BundleEntry;
26 import com.android.systemui.statusbar.notification.collection.PipelineEntry;
27 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
28 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
29 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
30 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
31 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
32 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
33 import com.android.systemui.statusbar.notification.collection.render.NodeController;
34 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
35 import com.android.systemui.statusbar.notification.dagger.AlertingHeader;
36 import com.android.systemui.statusbar.notification.dagger.SilentHeader;
37 import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
38 
39 import java.util.List;
40 
41 import javax.inject.Inject;
42 
43 /**
44  * Filters out NotificationEntries based on its Ranking and dozing state.
45  * Assigns alerting / silent section based on the importance of the notification entry.
46  * We check the NotificationEntry's Ranking for:
47  * - whether the notification's app is suspended or hiding its notifications
48  * - whether DND settings are hiding notifications from ambient display or the notification list
49  */
50 @CoordinatorScope
51 public class RankingCoordinator implements Coordinator {
52     public static final boolean SHOW_ALL_SECTIONS = false;
53     private final StatusBarStateController mStatusBarStateController;
54     private final HighPriorityProvider mHighPriorityProvider;
55     private final NodeController mSilentNodeController;
56     private final SectionHeaderController mSilentHeaderController;
57     private final NodeController mAlertingHeaderController;
58     private boolean mHasSilentEntries;
59     private boolean mHasMinimizedEntries;
60 
61     @Inject
RankingCoordinator( StatusBarStateController statusBarStateController, HighPriorityProvider highPriorityProvider, @AlertingHeader NodeController alertingHeaderController, @SilentHeader SectionHeaderController silentHeaderController, @SilentHeader NodeController silentNodeController)62     public RankingCoordinator(
63             StatusBarStateController statusBarStateController,
64             HighPriorityProvider highPriorityProvider,
65             @AlertingHeader NodeController alertingHeaderController,
66             @SilentHeader SectionHeaderController silentHeaderController,
67             @SilentHeader NodeController silentNodeController) {
68         mStatusBarStateController = statusBarStateController;
69         mHighPriorityProvider = highPriorityProvider;
70         mAlertingHeaderController = alertingHeaderController;
71         mSilentNodeController = silentNodeController;
72         mSilentHeaderController = silentHeaderController;
73     }
74 
75     @Override
attach(NotifPipeline pipeline)76     public void attach(NotifPipeline pipeline) {
77         mStatusBarStateController.addCallback(mStatusBarStateCallback);
78 
79         pipeline.addPreGroupFilter(mSuspendedFilter);
80         if (com.android.systemui.Flags.notificationAmbientSuppressionAfterInflation()) {
81             pipeline.addPreGroupFilter(mDndPreGroupFilter);
82             pipeline.addFinalizeFilter(mDndVisualEffectsFilter);
83         } else {
84             pipeline.addPreGroupFilter(mDndVisualEffectsFilter);
85         }
86     }
87 
getAlertingSectioner()88     public NotifSectioner getAlertingSectioner() {
89         return mAlertingNotifSectioner;
90     }
91 
getSilentSectioner()92     public NotifSectioner getSilentSectioner() {
93         return mSilentNotifSectioner;
94     }
95 
getMinimizedSectioner()96     public NotifSectioner getMinimizedSectioner() {
97         return mMinimizedNotifSectioner;
98     }
99 
100     private final NotifSectioner mAlertingNotifSectioner = new NotifSectioner("Alerting",
101             NotificationPriorityBucketKt.BUCKET_ALERTING) {
102         @Override
103         public boolean isInSection(PipelineEntry entry) {
104             if (BundleUtil.Companion.isClassified(entry)) {
105                 return false;
106             }
107             return mHighPriorityProvider.isHighPriority(entry);
108         }
109 
110         @Nullable
111         @Override
112         public NodeController getHeaderNodeController() {
113             // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mAlertingHeaderController
114             if (SHOW_ALL_SECTIONS) {
115                 return mAlertingHeaderController;
116             }
117             return null;
118         }
119     };
120 
121     private final NotifSectioner mSilentNotifSectioner = new NotifSectioner("Silent",
122             NotificationPriorityBucketKt.BUCKET_SILENT) {
123         @Override
124         public boolean isInSection(PipelineEntry entry) {
125             if (entry instanceof BundleEntry) {
126                 return true;
127             }
128             if (BundleUtil.Companion.isClassified(entry)) {
129                 return false;
130             }
131             return !mHighPriorityProvider.isHighPriority(entry)
132                     && entry.getRepresentativeEntry() != null
133                     && !entry.getRepresentativeEntry().isAmbient();
134         }
135 
136         @Nullable
137         @Override
138         public NodeController getHeaderNodeController() {
139             return mSilentNodeController;
140         }
141 
142         @Nullable
143         @Override
144         public void onEntriesUpdated(@NonNull List<PipelineEntry> entries) {
145             mHasSilentEntries = false;
146             for (int i = 0; i < entries.size(); i++) {
147                 NotificationEntry notifEntry = entries.get(i).getRepresentativeEntry();
148                 if (notifEntry == null) {
149                     // TODO(b/395698521) Handle BundleEntry
150                     continue;
151                 }
152                 if (notifEntry.getSbn().isClearable()) {
153                     mHasSilentEntries = true;
154                     break;
155                 }
156             }
157             mSilentHeaderController.setClearSectionEnabled(
158                     mHasSilentEntries | mHasMinimizedEntries);
159         }
160     };
161 
162     private final NotifSectioner mMinimizedNotifSectioner = new NotifSectioner("Minimized",
163             NotificationPriorityBucketKt.BUCKET_SILENT) {
164         @Override
165         public boolean isInSection(PipelineEntry entry) {
166             if (BundleUtil.Companion.isClassified(entry)) {
167                 return false;
168             }
169             return !mHighPriorityProvider.isHighPriority(entry)
170                     && entry.getRepresentativeEntry() != null
171                     && entry.getRepresentativeEntry().isAmbient();
172         }
173 
174         @Nullable
175         @Override
176         public NodeController getHeaderNodeController() {
177             return mSilentNodeController;
178         }
179 
180         @Nullable
181         @Override
182         public void onEntriesUpdated(@NonNull List<PipelineEntry> entries) {
183             mHasMinimizedEntries = false;
184             for (int i = 0; i < entries.size(); i++) {
185                 NotificationEntry notifEntry = entries.get(i).getRepresentativeEntry();
186                 if (notifEntry == null) {
187                     // TODO(b/395698521) Handle BundleEntry
188                     continue;
189                 }
190                 if (notifEntry.getSbn().isClearable()) {
191                     mHasMinimizedEntries = true;
192                     break;
193                 }
194             }
195             mSilentHeaderController.setClearSectionEnabled(
196                     mHasSilentEntries | mHasMinimizedEntries);
197         }
198     };
199 
200     /**
201      * Checks whether to filter out the given notification based the notification's Ranking object.
202      * NotifListBuilder invalidates the notification list each time the ranking is updated,
203      * so we don't need to explicitly invalidate this filter on ranking update.
204      */
205     private final NotifFilter mSuspendedFilter = new NotifFilter("IsSuspendedFilter") {
206         @Override
207         public boolean shouldFilterOut(NotificationEntry entry, long now) {
208             return entry.getRanking().isSuspended();
209         }
210     };
211 
212     private final NotifFilter mDndVisualEffectsFilter = new NotifFilter(
213             "DndSuppressingVisualEffects") {
214         @Override
215         public boolean shouldFilterOut(NotificationEntry entry, long now) {
216             if ((mStatusBarStateController.isDozing()
217                     || mStatusBarStateController.getDozeAmount() == 1f)
218                     && entry.shouldSuppressAmbient()) {
219                 return true;
220             }
221 
222             return !mStatusBarStateController.isDozing() && entry.shouldSuppressNotificationList();
223         }
224     };
225 
226     private final NotifFilter mDndPreGroupFilter = new NotifFilter("DndPreGroupFilter") {
227         @Override
228         public boolean shouldFilterOut(NotificationEntry entry, long now) {
229             // Entries with both flags set should be suppressed ASAP regardless of dozing state.
230             // As a result of being doze-independent, they can also be suppressed early in the
231             // pipeline.
232             return entry.shouldSuppressNotificationList() && entry.shouldSuppressAmbient();
233         }
234     };
235 
236     private final StatusBarStateController.StateListener mStatusBarStateCallback =
237             new StatusBarStateController.StateListener() {
238                 private boolean mPrevDozeAmountIsOne = false;
239 
240                 @Override
241                 public void onDozeAmountChanged(float linear, float eased) {
242                     StatusBarStateController.StateListener.super.onDozeAmountChanged(linear, eased);
243 
244                     boolean dozeAmountIsOne = linear == 1f;
245                     if (mPrevDozeAmountIsOne != dozeAmountIsOne) {
246                         mDndVisualEffectsFilter.invalidateList("dozeAmount changed to "
247                                 + (dozeAmountIsOne ? "one" : "not one"));
248                         mPrevDozeAmountIsOne = dozeAmountIsOne;
249                     }
250                 }
251 
252                 @Override
253                 public void onDozingChanged(boolean isDozing) {
254                     mDndVisualEffectsFilter.invalidateList("onDozingChanged to " + isDozing);
255                 }
256             };
257 }
258