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