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