Home | History | Annotate | Download | only in statusbar
      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 
    129         public Entry(StatusBarNotification n) {
    130             this.key = n.getKey();
    131             this.notification = n;
    132         }
    133 
    134         public void setInterruption() {
    135             interruption = true;
    136         }
    137 
    138         public boolean hasInterrupted() {
    139             return interruption;
    140         }
    141 
    142         /**
    143          * Resets the notification entry to be re-used.
    144          */
    145         public void reset() {
    146             if (row != null) {
    147                 row.reset();
    148             }
    149         }
    150 
    151         public View getExpandedContentView() {
    152             return row.getPrivateLayout().getExpandedChild();
    153         }
    154 
    155         public View getPublicContentView() {
    156             return row.getPublicLayout().getContractedChild();
    157         }
    158 
    159         public void notifyFullScreenIntentLaunched() {
    160             setInterruption();
    161             lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
    162         }
    163 
    164         public boolean hasJustLaunchedFullScreenIntent() {
    165             return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
    166         }
    167 
    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          */
    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 
    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          */
    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 
    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          */
    267         public void abortTask() {
    268             if (mRunningTask != null) {
    269                 mRunningTask.abort();
    270                 mRunningTask = null;
    271             }
    272         }
    273 
    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 
    284         public void onInflationTaskFinished() {
    285            mRunningTask = null;
    286         }
    287 
    288         @VisibleForTesting
    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          */
    298         public void setDebugThrowable(Throwable debugThrowable) {
    299             mDebugThrowable = debugThrowable;
    300         }
    301 
    302         public Throwable getDebugThrowable() {
    303             return mDebugThrowable;
    304         }
    305 
    306         public void onRemoteInputInserted() {
    307             lastRemoteInputSent = NOT_LAUNCHED_YET;
    308             remoteInputTextWhenReset = null;
    309         }
    310 
    311         public void setHasSentReply() {
    312             hasSentReply = true;
    313         }
    314 
    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 
    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 
    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      */
    429     public ArrayList<Entry> getActiveNotifications() {
    430         return mSortedAndFiltered;
    431     }
    432 
    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 
    450     public Entry get(String key) {
    451         return mEntries.get(key);
    452     }
    453 
    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 
    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 
    474     public void updateRanking(RankingMap ranking) {
    475         updateRankingAndSort(ranking);
    476     }
    477 
    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 
    496     public boolean isAmbient(String key) {
    497         if (mRankingMap != null) {
    498             getRanking(key, mTmpRanking);
    499             return mTmpRanking.isAmbient();
    500         }
    501         return false;
    502     }
    503 
    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 
    512     public boolean shouldSuppressFullScreenIntent(Entry entry) {
    513         return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
    514     }
    515 
    516     public boolean shouldSuppressPeek(Entry entry) {
    517         return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_PEEK);
    518     }
    519 
    520     public boolean shouldSuppressStatusBar(Entry entry) {
    521         return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_STATUS_BAR);
    522     }
    523 
    524     public boolean shouldSuppressAmbient(Entry entry) {
    525         return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_AMBIENT);
    526     }
    527 
    528     public boolean shouldSuppressNotificationList(Entry entry) {
    529         return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_NOTIFICATION_LIST);
    530     }
    531 
    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 
    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      */
    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 
    578     private boolean isCategory(String category, Notification n) {
    579         return Objects.equals(n.category, category);
    580     }
    581 
    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 
    590     public String getOverrideGroupKey(String key) {
    591         if (mRankingMap != null) {
    592             getRanking(key, mTmpRanking);
    593             return mTmpRanking.getOverrideGroupKey();
    594         }
    595          return null;
    596     }
    597 
    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 
    606     public NotificationChannel getChannel(String key) {
    607         if (mRankingMap != null) {
    608             getRanking(key, mTmpRanking);
    609             return mTmpRanking.getChannel();
    610         }
    611         return null;
    612     }
    613 
    614     public int getRank(String key) {
    615         if (mRankingMap != null) {
    616             getRanking(key, mTmpRanking);
    617             return mTmpRanking.getRank();
    618         }
    619         return 0;
    620     }
    621 
    622     public boolean shouldHide(String key) {
    623         if (mRankingMap != null) {
    624             getRanking(key, mTmpRanking);
    625             return mTmpRanking.isSuspended();
    626         }
    627         return false;
    628     }
    629 
    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
    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.
    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      */
    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).
    748     public static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
    749         return showNotificationEvenIfUnprovisioned(AppGlobals.getPackageManager(), sbn);
    750     }
    751 
    752     @VisibleForTesting
    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 
    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 
    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 
    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 
    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 {
    814         public boolean isSecurelyLocked(int userId);
    815         public boolean shouldHideNotifications(int userid);
    816         public boolean shouldHideNotifications(String key);
    817         public boolean isDeviceProvisioned();
    818         public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
    819         public String getCurrentMediaNotificationKey();
    820         public NotificationGroupManager getGroupManager();
    821         /**
    822          * @return true iff the device is dozing
    823          */
    824         boolean isDozing();
    825     }
    826 }
    827