• 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