1 /* 2 * Copyright (C) 2008 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; 18 19 import android.app.Notification; 20 import android.os.SystemClock; 21 import android.service.notification.NotificationListenerService; 22 import android.service.notification.NotificationListenerService.Ranking; 23 import android.service.notification.NotificationListenerService.RankingMap; 24 import android.service.notification.StatusBarNotification; 25 import android.util.ArrayMap; 26 import android.view.View; 27 28 import com.android.systemui.statusbar.phone.NotificationGroupManager; 29 import com.android.systemui.statusbar.policy.HeadsUpManager; 30 31 import java.io.PrintWriter; 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.Comparator; 35 36 /** 37 * The list of currently displaying notifications. 38 */ 39 public class NotificationData { 40 41 private final Environment mEnvironment; 42 private HeadsUpManager mHeadsUpManager; 43 44 public static final class Entry { 45 private static final long LAUNCH_COOLDOWN = 2000; 46 private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN; 47 public String key; 48 public StatusBarNotification notification; 49 public StatusBarIconView icon; 50 public ExpandableNotificationRow row; // the outer expanded view 51 private boolean interruption; 52 public boolean autoRedacted; // whether the redacted notification was generated by us 53 public boolean legacy; // whether the notification has a legacy, dark background 54 public int targetSdk; 55 private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET; 56 Entry(StatusBarNotification n, StatusBarIconView ic)57 public Entry(StatusBarNotification n, StatusBarIconView ic) { 58 this.key = n.getKey(); 59 this.notification = n; 60 this.icon = ic; 61 } 62 setInterruption()63 public void setInterruption() { 64 interruption = true; 65 } 66 hasInterrupted()67 public boolean hasInterrupted() { 68 return interruption; 69 } 70 71 /** 72 * Resets the notification entry to be re-used. 73 */ reset()74 public void reset() { 75 // NOTE: Icon needs to be preserved for now. 76 // We should fix this at some point. 77 autoRedacted = false; 78 legacy = false; 79 lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET; 80 if (row != null) { 81 row.reset(); 82 } 83 } 84 getContentView()85 public View getContentView() { 86 return row.getPrivateLayout().getContractedChild(); 87 } 88 getExpandedContentView()89 public View getExpandedContentView() { 90 return row.getPrivateLayout().getExpandedChild(); 91 } 92 getHeadsUpContentView()93 public View getHeadsUpContentView() { 94 return row.getPrivateLayout().getHeadsUpChild(); 95 } 96 getPublicContentView()97 public View getPublicContentView() { 98 return row.getPublicLayout().getContractedChild(); 99 } 100 notifyFullScreenIntentLaunched()101 public void notifyFullScreenIntentLaunched() { 102 lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime(); 103 } 104 hasJustLaunchedFullScreenIntent()105 public boolean hasJustLaunchedFullScreenIntent() { 106 return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN; 107 } 108 } 109 110 private final ArrayMap<String, Entry> mEntries = new ArrayMap<>(); 111 private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>(); 112 113 private NotificationGroupManager mGroupManager; 114 115 private RankingMap mRankingMap; 116 private final Ranking mTmpRanking = new Ranking(); 117 setHeadsUpManager(HeadsUpManager headsUpManager)118 public void setHeadsUpManager(HeadsUpManager headsUpManager) { 119 mHeadsUpManager = headsUpManager; 120 } 121 122 private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() { 123 private final Ranking mRankingA = new Ranking(); 124 private final Ranking mRankingB = new Ranking(); 125 126 @Override 127 public int compare(Entry a, Entry b) { 128 final StatusBarNotification na = a.notification; 129 final StatusBarNotification nb = b.notification; 130 final int aPriority = na.getNotification().priority; 131 final int bPriority = nb.getNotification().priority; 132 133 String mediaNotification = mEnvironment.getCurrentMediaNotificationKey(); 134 135 // PRIORITY_MIN media streams are allowed to drift to the bottom 136 final boolean aMedia = a.key.equals(mediaNotification) 137 && aPriority > Notification.PRIORITY_MIN; 138 final boolean bMedia = b.key.equals(mediaNotification) 139 && bPriority > Notification.PRIORITY_MIN; 140 141 boolean aSystemMax = aPriority >= Notification.PRIORITY_MAX && 142 isSystemNotification(na); 143 boolean bSystemMax = bPriority >= Notification.PRIORITY_MAX && 144 isSystemNotification(nb); 145 int d = nb.getScore() - na.getScore(); 146 147 boolean isHeadsUp = a.row.isHeadsUp(); 148 if (isHeadsUp != b.row.isHeadsUp()) { 149 return isHeadsUp ? -1 : 1; 150 } else if (isHeadsUp) { 151 // Provide consistent ranking with headsUpManager 152 return mHeadsUpManager.compare(a, b); 153 } else if (aMedia != bMedia) { 154 // Upsort current media notification. 155 return aMedia ? -1 : 1; 156 } else if (aSystemMax != bSystemMax) { 157 // Upsort PRIORITY_MAX system notifications 158 return aSystemMax ? -1 : 1; 159 } else if (mRankingMap != null) { 160 // RankingMap as received from NoMan 161 mRankingMap.getRanking(a.key, mRankingA); 162 mRankingMap.getRanking(b.key, mRankingB); 163 return mRankingA.getRank() - mRankingB.getRank(); 164 } if (d != 0) { 165 return d; 166 } else { 167 return (int) (nb.getNotification().when - na.getNotification().when); 168 } 169 } 170 }; 171 NotificationData(Environment environment)172 public NotificationData(Environment environment) { 173 mEnvironment = environment; 174 mGroupManager = environment.getGroupManager(); 175 } 176 177 /** 178 * Returns the sorted list of active notifications (depending on {@link Environment} 179 * 180 * <p> 181 * This call doesn't update the list of active notifications. Call {@link #filterAndSort()} 182 * when the environment changes. 183 * <p> 184 * Don't hold on to or modify the returned list. 185 */ getActiveNotifications()186 public ArrayList<Entry> getActiveNotifications() { 187 return mSortedAndFiltered; 188 } 189 get(String key)190 public Entry get(String key) { 191 return mEntries.get(key); 192 } 193 add(Entry entry, RankingMap ranking)194 public void add(Entry entry, RankingMap ranking) { 195 mEntries.put(entry.notification.getKey(), entry); 196 updateRankingAndSort(ranking); 197 mGroupManager.onEntryAdded(entry); 198 } 199 remove(String key, RankingMap ranking)200 public Entry remove(String key, RankingMap ranking) { 201 Entry removed = mEntries.remove(key); 202 if (removed == null) return null; 203 updateRankingAndSort(ranking); 204 mGroupManager.onEntryRemoved(removed); 205 return removed; 206 } 207 updateRanking(RankingMap ranking)208 public void updateRanking(RankingMap ranking) { 209 updateRankingAndSort(ranking); 210 } 211 isAmbient(String key)212 public boolean isAmbient(String key) { 213 if (mRankingMap != null) { 214 mRankingMap.getRanking(key, mTmpRanking); 215 return mTmpRanking.isAmbient(); 216 } 217 return false; 218 } 219 getVisibilityOverride(String key)220 public int getVisibilityOverride(String key) { 221 if (mRankingMap != null) { 222 mRankingMap.getRanking(key, mTmpRanking); 223 return mTmpRanking.getVisibilityOverride(); 224 } 225 return NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE; 226 } 227 updateRankingAndSort(RankingMap ranking)228 private void updateRankingAndSort(RankingMap ranking) { 229 if (ranking != null) { 230 mRankingMap = ranking; 231 } 232 filterAndSort(); 233 } 234 235 // TODO: This should not be public. Instead the Environment should notify this class when 236 // anything changed, and this class should call back the UI so it updates itself. filterAndSort()237 public void filterAndSort() { 238 mSortedAndFiltered.clear(); 239 240 final int N = mEntries.size(); 241 for (int i = 0; i < N; i++) { 242 Entry entry = mEntries.valueAt(i); 243 StatusBarNotification sbn = entry.notification; 244 245 if (shouldFilterOut(sbn)) { 246 continue; 247 } 248 249 mSortedAndFiltered.add(entry); 250 } 251 252 Collections.sort(mSortedAndFiltered, mRankingComparator); 253 } 254 shouldFilterOut(StatusBarNotification sbn)255 boolean shouldFilterOut(StatusBarNotification sbn) { 256 if (!(mEnvironment.isDeviceProvisioned() || 257 showNotificationEvenIfUnprovisioned(sbn))) { 258 return true; 259 } 260 261 if (!mEnvironment.isNotificationForCurrentProfiles(sbn)) { 262 return true; 263 } 264 265 if (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET && 266 mEnvironment.shouldHideSensitiveContents(sbn.getUserId())) { 267 return true; 268 } 269 270 if (!BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS 271 && mGroupManager.isChildInGroupWithSummary(sbn)) { 272 return true; 273 } 274 return false; 275 } 276 277 /** 278 * Return whether there are any clearable notifications (that aren't errors). 279 */ hasActiveClearableNotifications()280 public boolean hasActiveClearableNotifications() { 281 for (Entry e : mSortedAndFiltered) { 282 if (e.getContentView() != null) { // the view successfully inflated 283 if (e.notification.isClearable()) { 284 return true; 285 } 286 } 287 } 288 return false; 289 } 290 291 // Q: What kinds of notifications should show during setup? 292 // A: Almost none! Only things coming from the system (package is "android") that also 293 // have special "kind" tags marking them as relevant for setup (see below). showNotificationEvenIfUnprovisioned(StatusBarNotification sbn)294 public static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) { 295 return "android".equals(sbn.getPackageName()) 296 && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP); 297 } 298 dump(PrintWriter pw, String indent)299 public void dump(PrintWriter pw, String indent) { 300 int N = mSortedAndFiltered.size(); 301 pw.print(indent); 302 pw.println("active notifications: " + N); 303 int active; 304 for (active = 0; active < N; active++) { 305 NotificationData.Entry e = mSortedAndFiltered.get(active); 306 dumpEntry(pw, indent, active, e); 307 } 308 309 int M = mEntries.size(); 310 pw.print(indent); 311 pw.println("inactive notifications: " + (M - active)); 312 int inactiveCount = 0; 313 for (int i = 0; i < M; i++) { 314 Entry entry = mEntries.valueAt(i); 315 if (!mSortedAndFiltered.contains(entry)) { 316 dumpEntry(pw, indent, inactiveCount, entry); 317 inactiveCount++; 318 } 319 } 320 } 321 dumpEntry(PrintWriter pw, String indent, int i, Entry e)322 private void dumpEntry(PrintWriter pw, String indent, int i, Entry e) { 323 pw.print(indent); 324 pw.println(" [" + i + "] key=" + e.key + " icon=" + e.icon); 325 StatusBarNotification n = e.notification; 326 pw.print(indent); 327 pw.println(" pkg=" + n.getPackageName() + " id=" + n.getId() + " score=" + 328 n.getScore()); 329 pw.print(indent); 330 pw.println(" notification=" + n.getNotification()); 331 pw.print(indent); 332 pw.println(" tickerText=\"" + n.getNotification().tickerText + "\""); 333 } 334 isSystemNotification(StatusBarNotification sbn)335 private static boolean isSystemNotification(StatusBarNotification sbn) { 336 String sbnPackage = sbn.getPackageName(); 337 return "android".equals(sbnPackage) || "com.android.systemui".equals(sbnPackage); 338 } 339 340 /** 341 * Provides access to keyguard state and user settings dependent data. 342 */ 343 public interface Environment { shouldHideSensitiveContents(int userid)344 public boolean shouldHideSensitiveContents(int userid); isDeviceProvisioned()345 public boolean isDeviceProvisioned(); isNotificationForCurrentProfiles(StatusBarNotification sbn)346 public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn); getCurrentMediaNotificationKey()347 public String getCurrentMediaNotificationKey(); getGroupManager()348 public NotificationGroupManager getGroupManager(); 349 } 350 } 351