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