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