• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.content.Context;
21  import android.os.SystemClock;
22  import android.service.notification.NotificationListenerService;
23  import android.service.notification.NotificationListenerService.Ranking;
24  import android.service.notification.NotificationListenerService.RankingMap;
25  import android.service.notification.StatusBarNotification;
26  import android.util.ArrayMap;
27  import android.view.View;
28  import android.widget.RemoteViews;
29  
30  import com.android.systemui.statusbar.phone.NotificationGroupManager;
31  import com.android.systemui.statusbar.policy.HeadsUpManager;
32  
33  import java.io.PrintWriter;
34  import java.util.ArrayList;
35  import java.util.Collections;
36  import java.util.Comparator;
37  import java.util.Objects;
38  
39  /**
40   * The list of currently displaying notifications.
41   */
42  public class NotificationData {
43  
44      private final Environment mEnvironment;
45      private HeadsUpManager mHeadsUpManager;
46  
47      public static final class Entry {
48          private static final long LAUNCH_COOLDOWN = 2000;
49          private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
50          public String key;
51          public StatusBarNotification notification;
52          public StatusBarIconView icon;
53          public ExpandableNotificationRow row; // the outer expanded view
54          private boolean interruption;
55          public boolean autoRedacted; // whether the redacted notification was generated by us
56          public boolean legacy; // whether the notification has a legacy, dark background
57          public int targetSdk;
58          private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
59          public RemoteViews cachedContentView;
60          public RemoteViews cachedBigContentView;
61          public RemoteViews cachedHeadsUpContentView;
62          public RemoteViews cachedPublicContentView;
63          public CharSequence remoteInputText;
64  
Entry(StatusBarNotification n, StatusBarIconView ic)65          public Entry(StatusBarNotification n, StatusBarIconView ic) {
66              this.key = n.getKey();
67              this.notification = n;
68              this.icon = ic;
69          }
70  
setInterruption()71          public void setInterruption() {
72              interruption = true;
73          }
74  
hasInterrupted()75          public boolean hasInterrupted() {
76              return interruption;
77          }
78  
79          /**
80           * Resets the notification entry to be re-used.
81           */
reset()82          public void reset() {
83              // NOTE: Icon needs to be preserved for now.
84              // We should fix this at some point.
85              autoRedacted = false;
86              legacy = false;
87              lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
88              if (row != null) {
89                  row.reset();
90              }
91          }
92  
getContentView()93          public View getContentView() {
94              return row.getPrivateLayout().getContractedChild();
95          }
96  
getExpandedContentView()97          public View getExpandedContentView() {
98              return row.getPrivateLayout().getExpandedChild();
99          }
100  
getHeadsUpContentView()101          public View getHeadsUpContentView() {
102              return row.getPrivateLayout().getHeadsUpChild();
103          }
104  
getPublicContentView()105          public View getPublicContentView() {
106              return row.getPublicLayout().getContractedChild();
107          }
108  
cacheContentViews(Context ctx, Notification updatedNotification)109          public boolean cacheContentViews(Context ctx, Notification updatedNotification) {
110              boolean applyInPlace = false;
111              if (updatedNotification != null) {
112                  final Notification.Builder updatedNotificationBuilder
113                          = Notification.Builder.recoverBuilder(ctx, updatedNotification);
114                  final RemoteViews newContentView = updatedNotificationBuilder.createContentView();
115                  final RemoteViews newBigContentView =
116                          updatedNotificationBuilder.createBigContentView();
117                  final RemoteViews newHeadsUpContentView =
118                          updatedNotificationBuilder.createHeadsUpContentView();
119                  final RemoteViews newPublicNotification
120                          = updatedNotificationBuilder.makePublicContentView();
121  
122                  boolean sameCustomView = Objects.equals(
123                          notification.getNotification().extras.getBoolean(
124                                  Notification.EXTRA_CONTAINS_CUSTOM_VIEW),
125                          updatedNotification.extras.getBoolean(
126                                  Notification.EXTRA_CONTAINS_CUSTOM_VIEW));
127                  applyInPlace = compareRemoteViews(cachedContentView, newContentView)
128                          && compareRemoteViews(cachedBigContentView, newBigContentView)
129                          && compareRemoteViews(cachedHeadsUpContentView, newHeadsUpContentView)
130                          && compareRemoteViews(cachedPublicContentView, newPublicNotification)
131                          && sameCustomView;
132                  cachedPublicContentView = newPublicNotification;
133                  cachedHeadsUpContentView = newHeadsUpContentView;
134                  cachedBigContentView = newBigContentView;
135                  cachedContentView = newContentView;
136              } else {
137                  final Notification.Builder builder
138                          = Notification.Builder.recoverBuilder(ctx, notification.getNotification());
139  
140                  cachedContentView = builder.createContentView();
141                  cachedBigContentView = builder.createBigContentView();
142                  cachedHeadsUpContentView = builder.createHeadsUpContentView();
143                  cachedPublicContentView = builder.makePublicContentView();
144  
145                  applyInPlace = false;
146              }
147              return applyInPlace;
148          }
149  
150          // Returns true if the RemoteViews are the same.
compareRemoteViews(final RemoteViews a, final RemoteViews b)151          private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
152              return (a == null && b == null) ||
153                      (a != null && b != null
154                      && b.getPackage() != null
155                      && a.getPackage() != null
156                      && a.getPackage().equals(b.getPackage())
157                      && a.getLayoutId() == b.getLayoutId());
158          }
159  
notifyFullScreenIntentLaunched()160          public void notifyFullScreenIntentLaunched() {
161              lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
162          }
163  
hasJustLaunchedFullScreenIntent()164          public boolean hasJustLaunchedFullScreenIntent() {
165              return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
166          }
167      }
168  
169      private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
170      private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>();
171  
172      private NotificationGroupManager mGroupManager;
173  
174      private RankingMap mRankingMap;
175      private final Ranking mTmpRanking = new Ranking();
176  
setHeadsUpManager(HeadsUpManager headsUpManager)177      public void setHeadsUpManager(HeadsUpManager headsUpManager) {
178          mHeadsUpManager = headsUpManager;
179      }
180  
181      private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() {
182          private final Ranking mRankingA = new Ranking();
183          private final Ranking mRankingB = new Ranking();
184  
185          @Override
186          public int compare(Entry a, Entry b) {
187              final StatusBarNotification na = a.notification;
188              final StatusBarNotification nb = b.notification;
189              int aImportance = Ranking.IMPORTANCE_DEFAULT;
190              int bImportance = Ranking.IMPORTANCE_DEFAULT;
191              int aRank = 0;
192              int bRank = 0;
193  
194              if (mRankingMap != null) {
195                  // RankingMap as received from NoMan
196                  mRankingMap.getRanking(a.key, mRankingA);
197                  mRankingMap.getRanking(b.key, mRankingB);
198                  aImportance = mRankingA.getImportance();
199                  bImportance = mRankingB.getImportance();
200                  aRank = mRankingA.getRank();
201                  bRank = mRankingB.getRank();
202              }
203  
204              String mediaNotification = mEnvironment.getCurrentMediaNotificationKey();
205  
206              // IMPORTANCE_MIN media streams are allowed to drift to the bottom
207              final boolean aMedia = a.key.equals(mediaNotification)
208                      && aImportance > Ranking.IMPORTANCE_MIN;
209              final boolean bMedia = b.key.equals(mediaNotification)
210                      && bImportance > Ranking.IMPORTANCE_MIN;
211  
212              boolean aSystemMax = aImportance >= Ranking.IMPORTANCE_MAX &&
213                      isSystemNotification(na);
214              boolean bSystemMax = bImportance >= Ranking.IMPORTANCE_MAX &&
215                      isSystemNotification(nb);
216  
217              boolean isHeadsUp = a.row.isHeadsUp();
218              if (isHeadsUp != b.row.isHeadsUp()) {
219                  return isHeadsUp ? -1 : 1;
220              } else if (isHeadsUp) {
221                  // Provide consistent ranking with headsUpManager
222                  return mHeadsUpManager.compare(a, b);
223              } else if (aMedia != bMedia) {
224                  // Upsort current media notification.
225                  return aMedia ? -1 : 1;
226              } else if (aSystemMax != bSystemMax) {
227                  // Upsort PRIORITY_MAX system notifications
228                  return aSystemMax ? -1 : 1;
229              } else if (aRank != bRank) {
230                  return aRank - bRank;
231              } else {
232                  return (int) (nb.getNotification().when - na.getNotification().when);
233              }
234          }
235      };
236  
NotificationData(Environment environment)237      public NotificationData(Environment environment) {
238          mEnvironment = environment;
239          mGroupManager = environment.getGroupManager();
240      }
241  
242      /**
243       * Returns the sorted list of active notifications (depending on {@link Environment}
244       *
245       * <p>
246       * This call doesn't update the list of active notifications. Call {@link #filterAndSort()}
247       * when the environment changes.
248       * <p>
249       * Don't hold on to or modify the returned list.
250       */
getActiveNotifications()251      public ArrayList<Entry> getActiveNotifications() {
252          return mSortedAndFiltered;
253      }
254  
get(String key)255      public Entry get(String key) {
256          return mEntries.get(key);
257      }
258  
add(Entry entry, RankingMap ranking)259      public void add(Entry entry, RankingMap ranking) {
260          synchronized (mEntries) {
261              mEntries.put(entry.notification.getKey(), entry);
262          }
263          mGroupManager.onEntryAdded(entry);
264          updateRankingAndSort(ranking);
265      }
266  
remove(String key, RankingMap ranking)267      public Entry remove(String key, RankingMap ranking) {
268          Entry removed = null;
269          synchronized (mEntries) {
270              removed = mEntries.remove(key);
271          }
272          if (removed == null) return null;
273          mGroupManager.onEntryRemoved(removed);
274          updateRankingAndSort(ranking);
275          return removed;
276      }
277  
updateRanking(RankingMap ranking)278      public void updateRanking(RankingMap ranking) {
279          updateRankingAndSort(ranking);
280      }
281  
isAmbient(String key)282      public boolean isAmbient(String key) {
283          if (mRankingMap != null) {
284              mRankingMap.getRanking(key, mTmpRanking);
285              return mTmpRanking.isAmbient();
286          }
287          return false;
288      }
289  
getVisibilityOverride(String key)290      public int getVisibilityOverride(String key) {
291          if (mRankingMap != null) {
292              mRankingMap.getRanking(key, mTmpRanking);
293              return mTmpRanking.getVisibilityOverride();
294          }
295          return Ranking.VISIBILITY_NO_OVERRIDE;
296      }
297  
shouldSuppressScreenOff(String key)298      public boolean shouldSuppressScreenOff(String key) {
299          if (mRankingMap != null) {
300              mRankingMap.getRanking(key, mTmpRanking);
301              return (mTmpRanking.getSuppressedVisualEffects()
302                      & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0;
303          }
304          return false;
305      }
306  
shouldSuppressScreenOn(String key)307      public boolean shouldSuppressScreenOn(String key) {
308          if (mRankingMap != null) {
309              mRankingMap.getRanking(key, mTmpRanking);
310              return (mTmpRanking.getSuppressedVisualEffects()
311                      & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON) != 0;
312          }
313          return false;
314      }
315  
getImportance(String key)316      public int getImportance(String key) {
317          if (mRankingMap != null) {
318              mRankingMap.getRanking(key, mTmpRanking);
319              return mTmpRanking.getImportance();
320          }
321          return Ranking.IMPORTANCE_UNSPECIFIED;
322      }
323  
getOverrideGroupKey(String key)324      public String getOverrideGroupKey(String key) {
325          if (mRankingMap != null) {
326              mRankingMap.getRanking(key, mTmpRanking);
327              return mTmpRanking.getOverrideGroupKey();
328          }
329           return null;
330      }
331  
updateRankingAndSort(RankingMap ranking)332      private void updateRankingAndSort(RankingMap ranking) {
333          if (ranking != null) {
334              mRankingMap = ranking;
335              synchronized (mEntries) {
336                  final int N = mEntries.size();
337                  for (int i = 0; i < N; i++) {
338                      Entry entry = mEntries.valueAt(i);
339                      final StatusBarNotification oldSbn = entry.notification.clone();
340                      final String overrideGroupKey = getOverrideGroupKey(entry.key);
341                      if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) {
342                          entry.notification.setOverrideGroupKey(overrideGroupKey);
343                          mGroupManager.onEntryUpdated(entry, oldSbn);
344                      }
345                  }
346              }
347          }
348          filterAndSort();
349      }
350  
351      // TODO: This should not be public. Instead the Environment should notify this class when
352      // anything changed, and this class should call back the UI so it updates itself.
filterAndSort()353      public void filterAndSort() {
354          mSortedAndFiltered.clear();
355  
356          synchronized (mEntries) {
357              final int N = mEntries.size();
358              for (int i = 0; i < N; i++) {
359                  Entry entry = mEntries.valueAt(i);
360                  StatusBarNotification sbn = entry.notification;
361  
362                  if (shouldFilterOut(sbn)) {
363                      continue;
364                  }
365  
366                  mSortedAndFiltered.add(entry);
367              }
368          }
369  
370          Collections.sort(mSortedAndFiltered, mRankingComparator);
371      }
372  
shouldFilterOut(StatusBarNotification sbn)373      boolean shouldFilterOut(StatusBarNotification sbn) {
374          if (!(mEnvironment.isDeviceProvisioned() ||
375                  showNotificationEvenIfUnprovisioned(sbn))) {
376              return true;
377          }
378  
379          if (!mEnvironment.isNotificationForCurrentProfiles(sbn)) {
380              return true;
381          }
382  
383          if (mEnvironment.onSecureLockScreen() &&
384                  (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET
385                          || mEnvironment.shouldHideNotifications(sbn.getUserId())
386                          || mEnvironment.shouldHideNotifications(sbn.getKey()))) {
387              return true;
388          }
389  
390          if (!BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
391                  && mGroupManager.isChildInGroupWithSummary(sbn)) {
392              return true;
393          }
394          return false;
395      }
396  
397      // Q: What kinds of notifications should show during setup?
398      // A: Almost none! Only things coming from the system (package is "android") that also
399      // have special "kind" tags marking them as relevant for setup (see below).
showNotificationEvenIfUnprovisioned(StatusBarNotification sbn)400      public static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
401          return "android".equals(sbn.getPackageName())
402                  && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
403      }
404  
dump(PrintWriter pw, String indent)405      public void dump(PrintWriter pw, String indent) {
406          int N = mSortedAndFiltered.size();
407          pw.print(indent);
408          pw.println("active notifications: " + N);
409          int active;
410          for (active = 0; active < N; active++) {
411              NotificationData.Entry e = mSortedAndFiltered.get(active);
412              dumpEntry(pw, indent, active, e);
413          }
414          synchronized (mEntries) {
415              int M = mEntries.size();
416              pw.print(indent);
417              pw.println("inactive notifications: " + (M - active));
418              int inactiveCount = 0;
419              for (int i = 0; i < M; i++) {
420                  Entry entry = mEntries.valueAt(i);
421                  if (!mSortedAndFiltered.contains(entry)) {
422                      dumpEntry(pw, indent, inactiveCount, entry);
423                      inactiveCount++;
424                  }
425              }
426          }
427      }
428  
dumpEntry(PrintWriter pw, String indent, int i, Entry e)429      private void dumpEntry(PrintWriter pw, String indent, int i, Entry e) {
430          mRankingMap.getRanking(e.key, mTmpRanking);
431          pw.print(indent);
432          pw.println("  [" + i + "] key=" + e.key + " icon=" + e.icon);
433          StatusBarNotification n = e.notification;
434          pw.print(indent);
435          pw.println("      pkg=" + n.getPackageName() + " id=" + n.getId() + " importance=" +
436                  mTmpRanking.getImportance());
437          pw.print(indent);
438          pw.println("      notification=" + n.getNotification());
439          pw.print(indent);
440          pw.println("      tickerText=\"" + n.getNotification().tickerText + "\"");
441      }
442  
isSystemNotification(StatusBarNotification sbn)443      private static boolean isSystemNotification(StatusBarNotification sbn) {
444          String sbnPackage = sbn.getPackageName();
445          return "android".equals(sbnPackage) || "com.android.systemui".equals(sbnPackage);
446      }
447  
448      /**
449       * Provides access to keyguard state and user settings dependent data.
450       */
451      public interface Environment {
onSecureLockScreen()452          public boolean onSecureLockScreen();
shouldHideNotifications(int userid)453          public boolean shouldHideNotifications(int userid);
shouldHideNotifications(String key)454          public boolean shouldHideNotifications(String key);
isDeviceProvisioned()455          public boolean isDeviceProvisioned();
isNotificationForCurrentProfiles(StatusBarNotification sbn)456          public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
getCurrentMediaNotificationKey()457          public String getCurrentMediaNotificationKey();
getGroupManager()458          public NotificationGroupManager getGroupManager();
459      }
460  }
461