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 static android.app.Notification.CATEGORY_ALARM;
20 import static android.app.Notification.CATEGORY_CALL;
21 import static android.app.Notification.CATEGORY_EVENT;
22 import static android.app.Notification.CATEGORY_MESSAGE;
23 import static android.app.Notification.CATEGORY_REMINDER;
24 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
25 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
26 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
27 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
28 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
29 
30 import android.Manifest;
31 import android.app.AppGlobals;
32 import android.app.Notification;
33 import android.app.NotificationChannel;
34 import android.app.NotificationManager;
35 import android.app.Person;
36 import android.content.Context;
37 import android.content.pm.IPackageManager;
38 import android.content.pm.PackageManager;
39 import android.graphics.drawable.Icon;
40 import android.os.Bundle;
41 import android.os.Parcelable;
42 import android.os.RemoteException;
43 import android.os.SystemClock;
44 import android.service.notification.NotificationListenerService.Ranking;
45 import android.service.notification.NotificationListenerService.RankingMap;
46 import android.service.notification.SnoozeCriterion;
47 import android.service.notification.StatusBarNotification;
48 import android.util.ArrayMap;
49 import android.util.ArraySet;
50 import android.view.View;
51 import android.widget.ImageView;
52 import android.widget.RemoteViews;
53 
54 import com.android.internal.annotations.VisibleForTesting;
55 import com.android.internal.statusbar.StatusBarIcon;
56 import com.android.internal.util.ArrayUtils;
57 import com.android.internal.util.NotificationColorUtil;
58 import com.android.systemui.Dependency;
59 import com.android.systemui.ForegroundServiceController;
60 import com.android.systemui.statusbar.notification.InflationException;
61 import com.android.systemui.statusbar.phone.NotificationGroupManager;
62 import com.android.systemui.statusbar.phone.StatusBar;
63 import com.android.systemui.statusbar.policy.HeadsUpManager;
64 import com.android.systemui.statusbar.policy.ZenModeController;
65 
66 import java.io.PrintWriter;
67 import java.util.ArrayList;
68 import java.util.Collections;
69 import java.util.Comparator;
70 import java.util.List;
71 import java.util.Objects;
72 
73 /**
74  * The list of currently displaying notifications.
75  */
76 public class NotificationData {
77 
78     private final Environment mEnvironment;
79     private HeadsUpManager mHeadsUpManager;
80 
81     final ZenModeController mZen = Dependency.get(ZenModeController.class);
82     final ForegroundServiceController mFsc = Dependency.get(ForegroundServiceController.class);
83 
84     public static final class Entry {
85         private static final long LAUNCH_COOLDOWN = 2000;
86         private static final long REMOTE_INPUT_COOLDOWN = 500;
87         private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
88         private static final int COLOR_INVALID = 1;
89         public String key;
90         public StatusBarNotification notification;
91         public NotificationChannel channel;
92         public StatusBarIconView icon;
93         public StatusBarIconView expandedIcon;
94         public ExpandableNotificationRow row; // the outer expanded view
95         private boolean interruption;
96         public boolean autoRedacted; // whether the redacted notification was generated by us
97         public int targetSdk;
98         private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
99         public RemoteViews cachedContentView;
100         public RemoteViews cachedBigContentView;
101         public RemoteViews cachedHeadsUpContentView;
102         public RemoteViews cachedPublicContentView;
103         public RemoteViews cachedAmbientContentView;
104         public CharSequence remoteInputText;
105         public List<SnoozeCriterion> snoozeCriteria;
106         public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL;
107 
108         private int mCachedContrastColor = COLOR_INVALID;
109         private int mCachedContrastColorIsFor = COLOR_INVALID;
110         private InflationTask mRunningTask = null;
111         private Throwable mDebugThrowable;
112         public CharSequence remoteInputTextWhenReset;
113         public long lastRemoteInputSent = NOT_LAUNCHED_YET;
114         public ArraySet<Integer> mActiveAppOps = new ArraySet<>(3);
115         public CharSequence headsUpStatusBarText;
116         public CharSequence headsUpStatusBarTextPublic;
117         /**
118          * Whether or not this row represents a system notification. Note that if this is
119          * {@code null}, that means we were either unable to retrieve the info or have yet to
120          * retrieve the info.
121          */
122         public Boolean mIsSystemNotification;
123 
124         /**
125          * Has the user sent a reply through this Notification.
126          */
127         private boolean hasSentReply;
128 
Entry(StatusBarNotification n)129         public Entry(StatusBarNotification n) {
130             this.key = n.getKey();
131             this.notification = n;
132         }
133 
setInterruption()134         public void setInterruption() {
135             interruption = true;
136         }
137 
hasInterrupted()138         public boolean hasInterrupted() {
139             return interruption;
140         }
141 
142         /**
143          * Resets the notification entry to be re-used.
144          */
reset()145         public void reset() {
146             if (row != null) {
147                 row.reset();
148             }
149         }
150 
getExpandedContentView()151         public View getExpandedContentView() {
152             return row.getPrivateLayout().getExpandedChild();
153         }
154 
getPublicContentView()155         public View getPublicContentView() {
156             return row.getPublicLayout().getContractedChild();
157         }
158 
notifyFullScreenIntentLaunched()159         public void notifyFullScreenIntentLaunched() {
160             setInterruption();
161             lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
162         }
163 
hasJustLaunchedFullScreenIntent()164         public boolean hasJustLaunchedFullScreenIntent() {
165             return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
166         }
167 
hasJustSentRemoteInput()168         public boolean hasJustSentRemoteInput() {
169             return SystemClock.elapsedRealtime() < lastRemoteInputSent + REMOTE_INPUT_COOLDOWN;
170         }
171 
172         /**
173          * Create the icons for a notification
174          * @param context the context to create the icons with
175          * @param sbn the notification
176          * @throws InflationException
177          */
createIcons(Context context, StatusBarNotification sbn)178         public void createIcons(Context context, StatusBarNotification sbn)
179                 throws InflationException {
180             Notification n = sbn.getNotification();
181             final Icon smallIcon = n.getSmallIcon();
182             if (smallIcon == null) {
183                 throw new InflationException("No small icon in notification from "
184                         + sbn.getPackageName());
185             }
186 
187             // Construct the icon.
188             icon = new StatusBarIconView(context,
189                     sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
190             icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
191 
192             // Construct the expanded icon.
193             expandedIcon = new StatusBarIconView(context,
194                     sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
195             expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
196             final StatusBarIcon ic = new StatusBarIcon(
197                     sbn.getUser(),
198                     sbn.getPackageName(),
199                     smallIcon,
200                     n.iconLevel,
201                     n.number,
202                     StatusBarIconView.contentDescForNotification(context, n));
203             if (!icon.set(ic) || !expandedIcon.set(ic)) {
204                 icon = null;
205                 expandedIcon = null;
206                 throw new InflationException("Couldn't create icon: " + ic);
207             }
208             expandedIcon.setVisibility(View.INVISIBLE);
209             expandedIcon.setOnVisibilityChangedListener(
210                     newVisibility -> {
211                         if (row != null) {
212                             row.setIconsVisible(newVisibility != View.VISIBLE);
213                         }
214                     });
215         }
216 
setIconTag(int key, Object tag)217         public void setIconTag(int key, Object tag) {
218             if (icon != null) {
219                 icon.setTag(key, tag);
220                 expandedIcon.setTag(key, tag);
221             }
222         }
223 
224         /**
225          * Update the notification icons.
226          * @param context the context to create the icons with.
227          * @param sbn the notification to read the icon from.
228          * @throws InflationException
229          */
updateIcons(Context context, StatusBarNotification sbn)230         public void updateIcons(Context context, StatusBarNotification sbn)
231                 throws InflationException {
232             if (icon != null) {
233                 // Update the icon
234                 Notification n = sbn.getNotification();
235                 final StatusBarIcon ic = new StatusBarIcon(
236                         notification.getUser(),
237                         notification.getPackageName(),
238                         n.getSmallIcon(),
239                         n.iconLevel,
240                         n.number,
241                         StatusBarIconView.contentDescForNotification(context, n));
242                 icon.setNotification(sbn);
243                 expandedIcon.setNotification(sbn);
244                 if (!icon.set(ic) || !expandedIcon.set(ic)) {
245                     throw new InflationException("Couldn't update icon: " + ic);
246                 }
247             }
248         }
249 
getContrastedColor(Context context, boolean isLowPriority, int backgroundColor)250         public int getContrastedColor(Context context, boolean isLowPriority,
251                 int backgroundColor) {
252             int rawColor = isLowPriority ? Notification.COLOR_DEFAULT :
253                     notification.getNotification().color;
254             if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
255                 return mCachedContrastColor;
256             }
257             final int contrasted = NotificationColorUtil.resolveContrastColor(context, rawColor,
258                     backgroundColor);
259             mCachedContrastColorIsFor = rawColor;
260             mCachedContrastColor = contrasted;
261             return mCachedContrastColor;
262         }
263 
264         /**
265          * Abort all existing inflation tasks
266          */
abortTask()267         public void abortTask() {
268             if (mRunningTask != null) {
269                 mRunningTask.abort();
270                 mRunningTask = null;
271             }
272         }
273 
setInflationTask(InflationTask abortableTask)274         public void setInflationTask(InflationTask abortableTask) {
275             // abort any existing inflation
276             InflationTask existing = mRunningTask;
277             abortTask();
278             mRunningTask = abortableTask;
279             if (existing != null && mRunningTask != null) {
280                 mRunningTask.supersedeTask(existing);
281             }
282         }
283 
onInflationTaskFinished()284         public void onInflationTaskFinished() {
285            mRunningTask = null;
286         }
287 
288         @VisibleForTesting
getRunningTask()289         public InflationTask getRunningTask() {
290             return mRunningTask;
291         }
292 
293         /**
294          * Set a throwable that is used for debugging
295          *
296          * @param debugThrowable the throwable to save
297          */
setDebugThrowable(Throwable debugThrowable)298         public void setDebugThrowable(Throwable debugThrowable) {
299             mDebugThrowable = debugThrowable;
300         }
301 
getDebugThrowable()302         public Throwable getDebugThrowable() {
303             return mDebugThrowable;
304         }
305 
onRemoteInputInserted()306         public void onRemoteInputInserted() {
307             lastRemoteInputSent = NOT_LAUNCHED_YET;
308             remoteInputTextWhenReset = null;
309         }
310 
setHasSentReply()311         public void setHasSentReply() {
312             hasSentReply = true;
313         }
314 
isLastMessageFromReply()315         public boolean isLastMessageFromReply() {
316             if (!hasSentReply) {
317                 return false;
318             }
319             Bundle extras = notification.getNotification().extras;
320             CharSequence[] replyTexts = extras.getCharSequenceArray(
321                     Notification.EXTRA_REMOTE_INPUT_HISTORY);
322             if (!ArrayUtils.isEmpty(replyTexts)) {
323                 return true;
324             }
325             Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
326             if (messages != null && messages.length > 0) {
327                 Parcelable message = messages[messages.length - 1];
328                 if (message instanceof Bundle) {
329                     Notification.MessagingStyle.Message lastMessage =
330                             Notification.MessagingStyle.Message.getMessageFromBundle(
331                                     (Bundle) message);
332                     if (lastMessage != null) {
333                         Person senderPerson = lastMessage.getSenderPerson();
334                         if (senderPerson == null) {
335                             return true;
336                         }
337                         Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
338                         return Objects.equals(user, senderPerson);
339                     }
340                 }
341             }
342             return false;
343         }
344     }
345 
346     private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
347     private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>();
348     private final ArrayList<Entry> mFilteredForUser = new ArrayList<>();
349 
350     private NotificationGroupManager mGroupManager;
351 
352     private RankingMap mRankingMap;
353     private final Ranking mTmpRanking = new Ranking();
354 
setHeadsUpManager(HeadsUpManager headsUpManager)355     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
356         mHeadsUpManager = headsUpManager;
357     }
358 
359     private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() {
360         private final Ranking mRankingA = new Ranking();
361         private final Ranking mRankingB = new Ranking();
362 
363         @Override
364         public int compare(Entry a, Entry b) {
365             final StatusBarNotification na = a.notification;
366             final StatusBarNotification nb = b.notification;
367             int aImportance = NotificationManager.IMPORTANCE_DEFAULT;
368             int bImportance = NotificationManager.IMPORTANCE_DEFAULT;
369             int aRank = 0;
370             int bRank = 0;
371 
372             if (mRankingMap != null) {
373                 // RankingMap as received from NoMan
374                 getRanking(a.key, mRankingA);
375                 getRanking(b.key, mRankingB);
376                 aImportance = mRankingA.getImportance();
377                 bImportance = mRankingB.getImportance();
378                 aRank = mRankingA.getRank();
379                 bRank = mRankingB.getRank();
380             }
381 
382             String mediaNotification = mEnvironment.getCurrentMediaNotificationKey();
383 
384             // IMPORTANCE_MIN media streams are allowed to drift to the bottom
385             final boolean aMedia = a.key.equals(mediaNotification)
386                     && aImportance > NotificationManager.IMPORTANCE_MIN;
387             final boolean bMedia = b.key.equals(mediaNotification)
388                     && bImportance > NotificationManager.IMPORTANCE_MIN;
389 
390             boolean aSystemMax = aImportance >= NotificationManager.IMPORTANCE_HIGH &&
391                     isSystemNotification(na);
392             boolean bSystemMax = bImportance >= NotificationManager.IMPORTANCE_HIGH &&
393                     isSystemNotification(nb);
394 
395             boolean isHeadsUp = a.row.isHeadsUp();
396             if (isHeadsUp != b.row.isHeadsUp()) {
397                 return isHeadsUp ? -1 : 1;
398             } else if (isHeadsUp) {
399                 // Provide consistent ranking with headsUpManager
400                 return mHeadsUpManager.compare(a, b);
401             } else if (aMedia != bMedia) {
402                 // Upsort current media notification.
403                 return aMedia ? -1 : 1;
404             } else if (aSystemMax != bSystemMax) {
405                 // Upsort PRIORITY_MAX system notifications
406                 return aSystemMax ? -1 : 1;
407             } else if (aRank != bRank) {
408                 return aRank - bRank;
409             } else {
410                 return Long.compare(nb.getNotification().when, na.getNotification().when);
411             }
412         }
413     };
414 
NotificationData(Environment environment)415     public NotificationData(Environment environment) {
416         mEnvironment = environment;
417         mGroupManager = environment.getGroupManager();
418     }
419 
420     /**
421      * Returns the sorted list of active notifications (depending on {@link Environment}
422      *
423      * <p>
424      * This call doesn't update the list of active notifications. Call {@link #filterAndSort()}
425      * when the environment changes.
426      * <p>
427      * Don't hold on to or modify the returned list.
428      */
getActiveNotifications()429     public ArrayList<Entry> getActiveNotifications() {
430         return mSortedAndFiltered;
431     }
432 
getNotificationsForCurrentUser()433     public ArrayList<Entry> getNotificationsForCurrentUser() {
434         mFilteredForUser.clear();
435 
436         synchronized (mEntries) {
437             final int N = mEntries.size();
438             for (int i = 0; i < N; i++) {
439                 Entry entry = mEntries.valueAt(i);
440                 final StatusBarNotification sbn = entry.notification;
441                 if (!mEnvironment.isNotificationForCurrentProfiles(sbn)) {
442                     continue;
443                 }
444                 mFilteredForUser.add(entry);
445             }
446         }
447         return mFilteredForUser;
448     }
449 
get(String key)450     public Entry get(String key) {
451         return mEntries.get(key);
452     }
453 
add(Entry entry)454     public void add(Entry entry) {
455         synchronized (mEntries) {
456             mEntries.put(entry.notification.getKey(), entry);
457         }
458         mGroupManager.onEntryAdded(entry);
459 
460         updateRankingAndSort(mRankingMap);
461     }
462 
remove(String key, RankingMap ranking)463     public Entry remove(String key, RankingMap ranking) {
464         Entry removed = null;
465         synchronized (mEntries) {
466             removed = mEntries.remove(key);
467         }
468         if (removed == null) return null;
469         mGroupManager.onEntryRemoved(removed);
470         updateRankingAndSort(ranking);
471         return removed;
472     }
473 
updateRanking(RankingMap ranking)474     public void updateRanking(RankingMap ranking) {
475         updateRankingAndSort(ranking);
476     }
477 
updateAppOp(int appOp, int uid, String pkg, String key, boolean showIcon)478     public void updateAppOp(int appOp, int uid, String pkg, String key, boolean showIcon) {
479         synchronized (mEntries) {
480             final int N = mEntries.size();
481             for (int i = 0; i < N; i++) {
482                 Entry entry = mEntries.valueAt(i);
483                 if (uid == entry.notification.getUid()
484                         && pkg.equals(entry.notification.getPackageName())
485                         && key.equals(entry.key)) {
486                     if (showIcon) {
487                         entry.mActiveAppOps.add(appOp);
488                     } else {
489                         entry.mActiveAppOps.remove(appOp);
490                     }
491                 }
492             }
493         }
494     }
495 
isAmbient(String key)496     public boolean isAmbient(String key) {
497         if (mRankingMap != null) {
498             getRanking(key, mTmpRanking);
499             return mTmpRanking.isAmbient();
500         }
501         return false;
502     }
503 
getVisibilityOverride(String key)504     public int getVisibilityOverride(String key) {
505         if (mRankingMap != null) {
506             getRanking(key, mTmpRanking);
507             return mTmpRanking.getVisibilityOverride();
508         }
509         return Ranking.VISIBILITY_NO_OVERRIDE;
510     }
511 
shouldSuppressFullScreenIntent(Entry entry)512     public boolean shouldSuppressFullScreenIntent(Entry entry) {
513         return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
514     }
515 
shouldSuppressPeek(Entry entry)516     public boolean shouldSuppressPeek(Entry entry) {
517         return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_PEEK);
518     }
519 
shouldSuppressStatusBar(Entry entry)520     public boolean shouldSuppressStatusBar(Entry entry) {
521         return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_STATUS_BAR);
522     }
523 
shouldSuppressAmbient(Entry entry)524     public boolean shouldSuppressAmbient(Entry entry) {
525         return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_AMBIENT);
526     }
527 
shouldSuppressNotificationList(Entry entry)528     public boolean shouldSuppressNotificationList(Entry entry) {
529         return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_NOTIFICATION_LIST);
530     }
531 
shouldSuppressVisualEffect(Entry entry, int effect)532     private boolean shouldSuppressVisualEffect(Entry entry, int effect) {
533         if (isExemptFromDndVisualSuppression(entry)) {
534             return false;
535         }
536         String key = entry.key;
537         if (mRankingMap != null) {
538             getRanking(key, mTmpRanking);
539             return (mTmpRanking.getSuppressedVisualEffects() & effect) != 0;
540         }
541         return false;
542     }
543 
isExemptFromDndVisualSuppression(Entry entry)544     protected boolean isExemptFromDndVisualSuppression(Entry entry) {
545         if (isNotificationBlockedByPolicy(entry.notification.getNotification())) {
546             return false;
547         }
548 
549         if ((entry.notification.getNotification().flags
550                 & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
551             return true;
552         }
553         if (entry.notification.getNotification().isMediaNotification()) {
554             return true;
555         }
556         if (entry.mIsSystemNotification != null && entry.mIsSystemNotification) {
557             return true;
558         }
559         return false;
560     }
561 
562     /**
563      * Categories that are explicitly called out on DND settings screens are always blocked, if
564      * DND has flagged them, even if they are foreground or system notifications that might
565      * otherwise visually bypass DND.
566      */
isNotificationBlockedByPolicy(Notification n)567     protected boolean isNotificationBlockedByPolicy(Notification n) {
568         if (isCategory(CATEGORY_CALL, n)
569                 || isCategory(CATEGORY_MESSAGE, n)
570                 || isCategory(CATEGORY_ALARM, n)
571                 || isCategory(CATEGORY_EVENT, n)
572                 || isCategory(CATEGORY_REMINDER, n)) {
573             return true;
574         }
575         return false;
576     }
577 
isCategory(String category, Notification n)578     private boolean isCategory(String category, Notification n) {
579         return Objects.equals(n.category, category);
580     }
581 
getImportance(String key)582     public int getImportance(String key) {
583         if (mRankingMap != null) {
584             getRanking(key, mTmpRanking);
585             return mTmpRanking.getImportance();
586         }
587         return NotificationManager.IMPORTANCE_UNSPECIFIED;
588     }
589 
getOverrideGroupKey(String key)590     public String getOverrideGroupKey(String key) {
591         if (mRankingMap != null) {
592             getRanking(key, mTmpRanking);
593             return mTmpRanking.getOverrideGroupKey();
594         }
595          return null;
596     }
597 
getSnoozeCriteria(String key)598     public List<SnoozeCriterion> getSnoozeCriteria(String key) {
599         if (mRankingMap != null) {
600             getRanking(key, mTmpRanking);
601             return mTmpRanking.getSnoozeCriteria();
602         }
603         return null;
604     }
605 
getChannel(String key)606     public NotificationChannel getChannel(String key) {
607         if (mRankingMap != null) {
608             getRanking(key, mTmpRanking);
609             return mTmpRanking.getChannel();
610         }
611         return null;
612     }
613 
getRank(String key)614     public int getRank(String key) {
615         if (mRankingMap != null) {
616             getRanking(key, mTmpRanking);
617             return mTmpRanking.getRank();
618         }
619         return 0;
620     }
621 
shouldHide(String key)622     public boolean shouldHide(String key) {
623         if (mRankingMap != null) {
624             getRanking(key, mTmpRanking);
625             return mTmpRanking.isSuspended();
626         }
627         return false;
628     }
629 
updateRankingAndSort(RankingMap ranking)630     private void updateRankingAndSort(RankingMap ranking) {
631         if (ranking != null) {
632             mRankingMap = ranking;
633             synchronized (mEntries) {
634                 final int N = mEntries.size();
635                 for (int i = 0; i < N; i++) {
636                     Entry entry = mEntries.valueAt(i);
637                     if (!getRanking(entry.key, mTmpRanking)) {
638                         continue;
639                     }
640                     final StatusBarNotification oldSbn = entry.notification.cloneLight();
641                     final String overrideGroupKey = getOverrideGroupKey(entry.key);
642                     if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) {
643                         entry.notification.setOverrideGroupKey(overrideGroupKey);
644                         mGroupManager.onEntryUpdated(entry, oldSbn);
645                     }
646                     entry.channel = getChannel(entry.key);
647                     entry.snoozeCriteria = getSnoozeCriteria(entry.key);
648                     entry.userSentiment = mTmpRanking.getUserSentiment();
649                 }
650             }
651         }
652         filterAndSort();
653     }
654 
655     /**
656      * Get the ranking from the current ranking map.
657      *
658      * @param key the key to look up
659      * @param outRanking the ranking to populate
660      *
661      * @return {@code true} if the ranking was properly obtained.
662      */
663     @VisibleForTesting
getRanking(String key, Ranking outRanking)664     protected boolean getRanking(String key, Ranking outRanking) {
665         return mRankingMap.getRanking(key, outRanking);
666     }
667 
668     // TODO: This should not be public. Instead the Environment should notify this class when
669     // anything changed, and this class should call back the UI so it updates itself.
filterAndSort()670     public void filterAndSort() {
671         mSortedAndFiltered.clear();
672 
673         synchronized (mEntries) {
674             final int N = mEntries.size();
675             for (int i = 0; i < N; i++) {
676                 Entry entry = mEntries.valueAt(i);
677 
678                 if (shouldFilterOut(entry)) {
679                     continue;
680                 }
681 
682                 mSortedAndFiltered.add(entry);
683             }
684         }
685 
686         Collections.sort(mSortedAndFiltered, mRankingComparator);
687     }
688 
689     /**
690      * @return true if this notification should NOT be shown right now
691      */
shouldFilterOut(Entry entry)692     public boolean shouldFilterOut(Entry entry) {
693         final StatusBarNotification sbn = entry.notification;
694         if (!(mEnvironment.isDeviceProvisioned() ||
695                 showNotificationEvenIfUnprovisioned(sbn))) {
696             return true;
697         }
698 
699         if (!mEnvironment.isNotificationForCurrentProfiles(sbn)) {
700             return true;
701         }
702 
703         if (mEnvironment.isSecurelyLocked(sbn.getUserId()) &&
704                 (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET
705                         || mEnvironment.shouldHideNotifications(sbn.getUserId())
706                         || mEnvironment.shouldHideNotifications(sbn.getKey()))) {
707             return true;
708         }
709 
710         if (mEnvironment.isDozing() && shouldSuppressAmbient(entry)) {
711             return true;
712         }
713 
714         if (!mEnvironment.isDozing() && shouldSuppressNotificationList(entry)) {
715             return true;
716         }
717 
718         if (shouldHide(sbn.getKey())) {
719             return true;
720         }
721 
722         if (!StatusBar.ENABLE_CHILD_NOTIFICATIONS
723                 && mGroupManager.isChildInGroupWithSummary(sbn)) {
724             return true;
725         }
726 
727         if (mFsc.isDungeonNotification(sbn) && !mFsc.isDungeonNeededForUser(sbn.getUserId())) {
728             // this is a foreground-service disclosure for a user that does not need to show one
729             return true;
730         }
731         if (mFsc.isSystemAlertNotification(sbn)) {
732             final String[] apps = sbn.getNotification().extras.getStringArray(
733                     Notification.EXTRA_FOREGROUND_APPS);
734             if (apps != null && apps.length >= 1) {
735                 if (!mFsc.isSystemAlertWarningNeeded(sbn.getUserId(), apps[0])) {
736                     return true;
737                 }
738             }
739         }
740 
741         return false;
742     }
743 
744     // Q: What kinds of notifications should show during setup?
745     // A: Almost none! Only things coming from packages with permission
746     // android.permission.NOTIFICATION_DURING_SETUP that also have special "kind" tags marking them
747     // as relevant for setup (see below).
showNotificationEvenIfUnprovisioned(StatusBarNotification sbn)748     public static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
749         return showNotificationEvenIfUnprovisioned(AppGlobals.getPackageManager(), sbn);
750     }
751 
752     @VisibleForTesting
showNotificationEvenIfUnprovisioned(IPackageManager packageManager, StatusBarNotification sbn)753     static boolean showNotificationEvenIfUnprovisioned(IPackageManager packageManager,
754             StatusBarNotification sbn) {
755         return checkUidPermission(packageManager, Manifest.permission.NOTIFICATION_DURING_SETUP,
756                 sbn.getUid()) == PackageManager.PERMISSION_GRANTED
757                 && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
758     }
759 
checkUidPermission(IPackageManager packageManager, String permission, int uid)760     private static int checkUidPermission(IPackageManager packageManager, String permission,
761             int uid) {
762         try {
763             return packageManager.checkUidPermission(permission, uid);
764         } catch (RemoteException e) {
765             throw e.rethrowFromSystemServer();
766         }
767     }
768 
dump(PrintWriter pw, String indent)769     public void dump(PrintWriter pw, String indent) {
770         int N = mSortedAndFiltered.size();
771         pw.print(indent);
772         pw.println("active notifications: " + N);
773         int active;
774         for (active = 0; active < N; active++) {
775             NotificationData.Entry e = mSortedAndFiltered.get(active);
776             dumpEntry(pw, indent, active, e);
777         }
778         synchronized (mEntries) {
779             int M = mEntries.size();
780             pw.print(indent);
781             pw.println("inactive notifications: " + (M - active));
782             int inactiveCount = 0;
783             for (int i = 0; i < M; i++) {
784                 Entry entry = mEntries.valueAt(i);
785                 if (!mSortedAndFiltered.contains(entry)) {
786                     dumpEntry(pw, indent, inactiveCount, entry);
787                     inactiveCount++;
788                 }
789             }
790         }
791     }
792 
dumpEntry(PrintWriter pw, String indent, int i, Entry e)793     private void dumpEntry(PrintWriter pw, String indent, int i, Entry e) {
794         getRanking(e.key, mTmpRanking);
795         pw.print(indent);
796         pw.println("  [" + i + "] key=" + e.key + " icon=" + e.icon);
797         StatusBarNotification n = e.notification;
798         pw.print(indent);
799         pw.println("      pkg=" + n.getPackageName() + " id=" + n.getId() + " importance=" +
800                 mTmpRanking.getImportance());
801         pw.print(indent);
802         pw.println("      notification=" + n.getNotification());
803     }
804 
isSystemNotification(StatusBarNotification sbn)805     private static boolean isSystemNotification(StatusBarNotification sbn) {
806         String sbnPackage = sbn.getPackageName();
807         return "android".equals(sbnPackage) || "com.android.systemui".equals(sbnPackage);
808     }
809 
810     /**
811      * Provides access to keyguard state and user settings dependent data.
812      */
813     public interface Environment {
isSecurelyLocked(int userId)814         public boolean isSecurelyLocked(int userId);
shouldHideNotifications(int userid)815         public boolean shouldHideNotifications(int userid);
shouldHideNotifications(String key)816         public boolean shouldHideNotifications(String key);
isDeviceProvisioned()817         public boolean isDeviceProvisioned();
isNotificationForCurrentProfiles(StatusBarNotification sbn)818         public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
getCurrentMediaNotificationKey()819         public String getCurrentMediaNotificationKey();
getGroupManager()820         public NotificationGroupManager getGroupManager();
821         /**
822          * @return true iff the device is dozing
823          */
isDozing()824         boolean isDozing();
825     }
826 }
827