Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2007 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.server;
     18 
     19 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
     20 import static org.xmlpull.v1.XmlPullParser.END_TAG;
     21 import static org.xmlpull.v1.XmlPullParser.START_TAG;
     22 
     23 import android.app.ActivityManager;
     24 import android.app.ActivityManagerNative;
     25 import android.app.AppGlobals;
     26 import android.app.AppOpsManager;
     27 import android.app.IActivityManager;
     28 import android.app.INotificationManager;
     29 import android.app.ITransientNotification;
     30 import android.app.Notification;
     31 import android.app.PendingIntent;
     32 import android.app.StatusBarManager;
     33 import android.content.BroadcastReceiver;
     34 import android.content.ComponentName;
     35 import android.content.ContentResolver;
     36 import android.content.Context;
     37 import android.content.Intent;
     38 import android.content.IntentFilter;
     39 import android.content.ServiceConnection;
     40 import android.content.pm.ApplicationInfo;
     41 import android.content.pm.PackageInfo;
     42 import android.content.pm.PackageManager;
     43 import android.content.pm.ResolveInfo;
     44 import android.content.pm.ServiceInfo;
     45 import android.content.pm.PackageManager.NameNotFoundException;
     46 import android.content.res.Resources;
     47 import android.database.ContentObserver;
     48 import android.graphics.Bitmap;
     49 import android.media.AudioManager;
     50 import android.media.IAudioService;
     51 import android.media.IRingtonePlayer;
     52 import android.net.Uri;
     53 import android.os.Binder;
     54 import android.os.Handler;
     55 import android.os.IBinder;
     56 import android.os.Message;
     57 import android.os.Process;
     58 import android.os.RemoteException;
     59 import android.os.ServiceManager;
     60 import android.os.UserHandle;
     61 import android.os.UserManager;
     62 import android.os.Vibrator;
     63 import android.provider.Settings;
     64 import android.service.notification.INotificationListener;
     65 import android.service.notification.NotificationListenerService;
     66 import android.service.notification.StatusBarNotification;
     67 import android.telephony.TelephonyManager;
     68 import android.text.TextUtils;
     69 import android.util.AtomicFile;
     70 import android.util.EventLog;
     71 import android.util.Log;
     72 import android.util.Slog;
     73 import android.util.Xml;
     74 import android.view.accessibility.AccessibilityEvent;
     75 import android.view.accessibility.AccessibilityManager;
     76 import android.widget.Toast;
     77 
     78 import com.android.internal.R;
     79 
     80 import com.android.internal.notification.NotificationScorer;
     81 import org.xmlpull.v1.XmlPullParser;
     82 import org.xmlpull.v1.XmlPullParserException;
     83 
     84 import java.io.File;
     85 import java.io.FileDescriptor;
     86 import java.io.FileInputStream;
     87 import java.io.FileNotFoundException;
     88 import java.io.IOException;
     89 import java.io.PrintWriter;
     90 import java.lang.reflect.Array;
     91 import java.util.ArrayDeque;
     92 import java.util.ArrayList;
     93 import java.util.Arrays;
     94 import java.util.HashSet;
     95 import java.util.Iterator;
     96 import java.util.List;
     97 import java.util.NoSuchElementException;
     98 import java.util.Set;
     99 
    100 import libcore.io.IoUtils;
    101 
    102 
    103 /** {@hide} */
    104 public class NotificationManagerService extends INotificationManager.Stub
    105 {
    106     private static final String TAG = "NotificationService";
    107     private static final boolean DBG = false;
    108 
    109     private static final int MAX_PACKAGE_NOTIFICATIONS = 50;
    110 
    111     // message codes
    112     private static final int MESSAGE_TIMEOUT = 2;
    113 
    114     private static final int LONG_DELAY = 3500; // 3.5 seconds
    115     private static final int SHORT_DELAY = 2000; // 2 seconds
    116 
    117     private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
    118     private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
    119 
    120     private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
    121     private static final boolean SCORE_ONGOING_HIGHER = false;
    122 
    123     private static final int JUNK_SCORE = -1000;
    124     private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10;
    125     private static final int SCORE_DISPLAY_THRESHOLD = Notification.PRIORITY_MIN * NOTIFICATION_PRIORITY_MULTIPLIER;
    126 
    127     // Notifications with scores below this will not interrupt the user, either via LED or
    128     // sound or vibration
    129     private static final int SCORE_INTERRUPTION_THRESHOLD =
    130             Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER;
    131 
    132     private static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
    133     private static final boolean ENABLE_BLOCKED_TOASTS = true;
    134 
    135     private static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":";
    136 
    137     final Context mContext;
    138     final IActivityManager mAm;
    139     final UserManager mUserManager;
    140     final IBinder mForegroundToken = new Binder();
    141 
    142     private WorkerHandler mHandler;
    143     private StatusBarManagerService mStatusBar;
    144     private LightsService.Light mNotificationLight;
    145     private LightsService.Light mAttentionLight;
    146 
    147     private int mDefaultNotificationColor;
    148     private int mDefaultNotificationLedOn;
    149     private int mDefaultNotificationLedOff;
    150 
    151     private long[] mDefaultVibrationPattern;
    152     private long[] mFallbackVibrationPattern;
    153 
    154     private boolean mSystemReady;
    155     private int mDisabledNotifications;
    156 
    157     private NotificationRecord mSoundNotification;
    158     private NotificationRecord mVibrateNotification;
    159 
    160     private IAudioService mAudioService;
    161     private Vibrator mVibrator;
    162 
    163     // for enabling and disabling notification pulse behavior
    164     private boolean mScreenOn = true;
    165     private boolean mInCall = false;
    166     private boolean mNotificationPulseEnabled;
    167 
    168     // used as a mutex for access to all active notifications & listeners
    169     private final ArrayList<NotificationRecord> mNotificationList =
    170             new ArrayList<NotificationRecord>();
    171 
    172     private ArrayList<ToastRecord> mToastQueue;
    173 
    174     private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
    175     private NotificationRecord mLedNotification;
    176 
    177     private final AppOpsManager mAppOps;
    178 
    179     // contains connections to all connected listeners, including app services
    180     // and system listeners
    181     private ArrayList<NotificationListenerInfo> mListeners
    182             = new ArrayList<NotificationListenerInfo>();
    183     // things that will be put into mListeners as soon as they're ready
    184     private ArrayList<String> mServicesBinding = new ArrayList<String>();
    185     // lists the component names of all enabled (and therefore connected) listener
    186     // app services for the current user only
    187     private HashSet<ComponentName> mEnabledListenersForCurrentUser
    188             = new HashSet<ComponentName>();
    189     // Just the packages from mEnabledListenersForCurrentUser
    190     private HashSet<String> mEnabledListenerPackageNames = new HashSet<String>();
    191 
    192     // Notification control database. For now just contains disabled packages.
    193     private AtomicFile mPolicyFile;
    194     private HashSet<String> mBlockedPackages = new HashSet<String>();
    195 
    196     private static final int DB_VERSION = 1;
    197 
    198     private static final String TAG_BODY = "notification-policy";
    199     private static final String ATTR_VERSION = "version";
    200 
    201     private static final String TAG_BLOCKED_PKGS = "blocked-packages";
    202     private static final String TAG_PACKAGE = "package";
    203     private static final String ATTR_NAME = "name";
    204 
    205     private final ArrayList<NotificationScorer> mScorers = new ArrayList<NotificationScorer>();
    206 
    207     private class NotificationListenerInfo implements DeathRecipient {
    208         INotificationListener listener;
    209         ComponentName component;
    210         int userid;
    211         boolean isSystem;
    212         ServiceConnection connection;
    213 
    214         public NotificationListenerInfo(INotificationListener listener, ComponentName component,
    215                 int userid, boolean isSystem) {
    216             this.listener = listener;
    217             this.component = component;
    218             this.userid = userid;
    219             this.isSystem = isSystem;
    220             this.connection = null;
    221         }
    222 
    223         public NotificationListenerInfo(INotificationListener listener, ComponentName component,
    224                 int userid, ServiceConnection connection) {
    225             this.listener = listener;
    226             this.component = component;
    227             this.userid = userid;
    228             this.isSystem = false;
    229             this.connection = connection;
    230         }
    231 
    232         boolean enabledAndUserMatches(StatusBarNotification sbn) {
    233             final int nid = sbn.getUserId();
    234             if (!isEnabledForCurrentUser()) {
    235                 return false;
    236             }
    237             if (this.userid == UserHandle.USER_ALL) return true;
    238             return (nid == UserHandle.USER_ALL || nid == this.userid);
    239         }
    240 
    241         public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
    242             if (!enabledAndUserMatches(sbn)) {
    243                 return;
    244             }
    245             try {
    246                 listener.onNotificationPosted(sbn);
    247             } catch (RemoteException ex) {
    248                 Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
    249             }
    250         }
    251 
    252         public void notifyRemovedIfUserMatch(StatusBarNotification sbn) {
    253             if (!enabledAndUserMatches(sbn)) return;
    254             try {
    255                 listener.onNotificationRemoved(sbn);
    256             } catch (RemoteException ex) {
    257                 Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
    258             }
    259         }
    260 
    261         @Override
    262         public void binderDied() {
    263             // Remove the listener, but don't unbind from the service. The system will bring the
    264             // service back up, and the onServiceConnected handler will readd the listener with the
    265             // new binding. If this isn't a bound service, and is just a registered
    266             // INotificationListener, just removing it from the list is all we need to do anyway.
    267             removeListenerImpl(this.listener, this.userid);
    268         }
    269 
    270         /** convenience method for looking in mEnabledListenersForCurrentUser */
    271         public boolean isEnabledForCurrentUser() {
    272             if (this.isSystem) return true;
    273             if (this.connection == null) return false;
    274             return mEnabledListenersForCurrentUser.contains(this.component);
    275         }
    276     }
    277 
    278     private static class Archive {
    279         static final int BUFFER_SIZE = 250;
    280         ArrayDeque<StatusBarNotification> mBuffer = new ArrayDeque<StatusBarNotification>(BUFFER_SIZE);
    281 
    282         public Archive() {
    283         }
    284 
    285         public String toString() {
    286             final StringBuilder sb = new StringBuilder();
    287             final int N = mBuffer.size();
    288             sb.append("Archive (");
    289             sb.append(N);
    290             sb.append(" notification");
    291             sb.append((N==1)?")":"s)");
    292             return sb.toString();
    293         }
    294 
    295         public void record(StatusBarNotification nr) {
    296             if (mBuffer.size() == BUFFER_SIZE) {
    297                 mBuffer.removeFirst();
    298             }
    299 
    300             // We don't want to store the heavy bits of the notification in the archive,
    301             // but other clients in the system process might be using the object, so we
    302             // store a (lightened) copy.
    303             mBuffer.addLast(nr.cloneLight());
    304         }
    305 
    306 
    307         public void clear() {
    308             mBuffer.clear();
    309         }
    310 
    311         public Iterator<StatusBarNotification> descendingIterator() {
    312             return mBuffer.descendingIterator();
    313         }
    314         public Iterator<StatusBarNotification> ascendingIterator() {
    315             return mBuffer.iterator();
    316         }
    317         public Iterator<StatusBarNotification> filter(
    318                 final Iterator<StatusBarNotification> iter, final String pkg, final int userId) {
    319             return new Iterator<StatusBarNotification>() {
    320                 StatusBarNotification mNext = findNext();
    321 
    322                 private StatusBarNotification findNext() {
    323                     while (iter.hasNext()) {
    324                         StatusBarNotification nr = iter.next();
    325                         if ((pkg == null || nr.getPackageName() == pkg)
    326                                 && (userId == UserHandle.USER_ALL || nr.getUserId() == userId)) {
    327                             return nr;
    328                         }
    329                     }
    330                     return null;
    331                 }
    332 
    333                 @Override
    334                 public boolean hasNext() {
    335                     return mNext == null;
    336                 }
    337 
    338                 @Override
    339                 public StatusBarNotification next() {
    340                     StatusBarNotification next = mNext;
    341                     if (next == null) {
    342                         throw new NoSuchElementException();
    343                     }
    344                     mNext = findNext();
    345                     return next;
    346                 }
    347 
    348                 @Override
    349                 public void remove() {
    350                     iter.remove();
    351                 }
    352             };
    353         }
    354 
    355         public StatusBarNotification[] getArray(int count) {
    356             if (count == 0) count = Archive.BUFFER_SIZE;
    357             final StatusBarNotification[] a
    358                     = new StatusBarNotification[Math.min(count, mBuffer.size())];
    359             Iterator<StatusBarNotification> iter = descendingIterator();
    360             int i=0;
    361             while (iter.hasNext() && i < count) {
    362                 a[i++] = iter.next();
    363             }
    364             return a;
    365         }
    366 
    367         public StatusBarNotification[] getArray(int count, String pkg, int userId) {
    368             if (count == 0) count = Archive.BUFFER_SIZE;
    369             final StatusBarNotification[] a
    370                     = new StatusBarNotification[Math.min(count, mBuffer.size())];
    371             Iterator<StatusBarNotification> iter = filter(descendingIterator(), pkg, userId);
    372             int i=0;
    373             while (iter.hasNext() && i < count) {
    374                 a[i++] = iter.next();
    375             }
    376             return a;
    377         }
    378 
    379     }
    380 
    381     Archive mArchive = new Archive();
    382 
    383     private void loadBlockDb() {
    384         synchronized(mBlockedPackages) {
    385             if (mPolicyFile == null) {
    386                 File dir = new File("/data/system");
    387                 mPolicyFile = new AtomicFile(new File(dir, "notification_policy.xml"));
    388 
    389                 mBlockedPackages.clear();
    390 
    391                 FileInputStream infile = null;
    392                 try {
    393                     infile = mPolicyFile.openRead();
    394                     final XmlPullParser parser = Xml.newPullParser();
    395                     parser.setInput(infile, null);
    396 
    397                     int type;
    398                     String tag;
    399                     int version = DB_VERSION;
    400                     while ((type = parser.next()) != END_DOCUMENT) {
    401                         tag = parser.getName();
    402                         if (type == START_TAG) {
    403                             if (TAG_BODY.equals(tag)) {
    404                                 version = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));
    405                             } else if (TAG_BLOCKED_PKGS.equals(tag)) {
    406                                 while ((type = parser.next()) != END_DOCUMENT) {
    407                                     tag = parser.getName();
    408                                     if (TAG_PACKAGE.equals(tag)) {
    409                                         mBlockedPackages.add(parser.getAttributeValue(null, ATTR_NAME));
    410                                     } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
    411                                         break;
    412                                     }
    413                                 }
    414                             }
    415                         }
    416                     }
    417                 } catch (FileNotFoundException e) {
    418                     // No data yet
    419                 } catch (IOException e) {
    420                     Log.wtf(TAG, "Unable to read blocked notifications database", e);
    421                 } catch (NumberFormatException e) {
    422                     Log.wtf(TAG, "Unable to parse blocked notifications database", e);
    423                 } catch (XmlPullParserException e) {
    424                     Log.wtf(TAG, "Unable to parse blocked notifications database", e);
    425                 } finally {
    426                     IoUtils.closeQuietly(infile);
    427                 }
    428             }
    429         }
    430     }
    431 
    432     /**
    433      * Use this when you just want to know if notifications are OK for this package.
    434      */
    435     public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
    436         checkCallerIsSystem();
    437         return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
    438                 == AppOpsManager.MODE_ALLOWED);
    439     }
    440 
    441     /** Use this when you actually want to post a notification or toast.
    442      *
    443      * Unchecked. Not exposed via Binder, but can be called in the course of enqueue*().
    444      */
    445     private boolean noteNotificationOp(String pkg, int uid) {
    446         if (mAppOps.noteOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
    447                 != AppOpsManager.MODE_ALLOWED) {
    448             Slog.v(TAG, "notifications are disabled by AppOps for " + pkg);
    449             return false;
    450         }
    451         return true;
    452     }
    453 
    454     public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
    455         checkCallerIsSystem();
    456 
    457         Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
    458 
    459         mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
    460                 enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
    461 
    462         // Now, cancel any outstanding notifications that are part of a just-disabled app
    463         if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
    464             cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid));
    465         }
    466     }
    467 
    468 
    469     private static String idDebugString(Context baseContext, String packageName, int id) {
    470         Context c = null;
    471 
    472         if (packageName != null) {
    473             try {
    474                 c = baseContext.createPackageContext(packageName, 0);
    475             } catch (NameNotFoundException e) {
    476                 c = baseContext;
    477             }
    478         } else {
    479             c = baseContext;
    480         }
    481 
    482         String pkg;
    483         String type;
    484         String name;
    485 
    486         Resources r = c.getResources();
    487         try {
    488             return r.getResourceName(id);
    489         } catch (Resources.NotFoundException e) {
    490             return "<name unknown>";
    491         }
    492     }
    493 
    494     /**
    495      * System-only API for getting a list of current (i.e. not cleared) notifications.
    496      *
    497      * Requires ACCESS_NOTIFICATIONS which is signature|system.
    498      */
    499     @Override
    500     public StatusBarNotification[] getActiveNotifications(String callingPkg) {
    501         // enforce() will ensure the calling uid has the correct permission
    502         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS,
    503                 "NotificationManagerService.getActiveNotifications");
    504 
    505         StatusBarNotification[] tmp = null;
    506         int uid = Binder.getCallingUid();
    507 
    508         // noteOp will check to make sure the callingPkg matches the uid
    509         if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
    510                 == AppOpsManager.MODE_ALLOWED) {
    511             synchronized (mNotificationList) {
    512                 tmp = new StatusBarNotification[mNotificationList.size()];
    513                 final int N = mNotificationList.size();
    514                 for (int i=0; i<N; i++) {
    515                     tmp[i] = mNotificationList.get(i).sbn;
    516                 }
    517             }
    518         }
    519         return tmp;
    520     }
    521 
    522     /**
    523      * System-only API for getting a list of recent (cleared, no longer shown) notifications.
    524      *
    525      * Requires ACCESS_NOTIFICATIONS which is signature|system.
    526      */
    527     @Override
    528     public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count) {
    529         // enforce() will ensure the calling uid has the correct permission
    530         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS,
    531                 "NotificationManagerService.getHistoricalNotifications");
    532 
    533         StatusBarNotification[] tmp = null;
    534         int uid = Binder.getCallingUid();
    535 
    536         // noteOp will check to make sure the callingPkg matches the uid
    537         if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
    538                 == AppOpsManager.MODE_ALLOWED) {
    539             synchronized (mArchive) {
    540                 tmp = mArchive.getArray(count);
    541             }
    542         }
    543         return tmp;
    544     }
    545 
    546     /**
    547      * Remove notification access for any services that no longer exist.
    548      */
    549     void disableNonexistentListeners() {
    550         int currentUser = ActivityManager.getCurrentUser();
    551         String flatIn = Settings.Secure.getStringForUser(
    552                 mContext.getContentResolver(),
    553                 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
    554                 currentUser);
    555         if (!TextUtils.isEmpty(flatIn)) {
    556             if (DBG) Slog.v(TAG, "flat before: " + flatIn);
    557             PackageManager pm = mContext.getPackageManager();
    558             List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
    559                     new Intent(NotificationListenerService.SERVICE_INTERFACE),
    560                     PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
    561                     currentUser);
    562 
    563             Set<ComponentName> installed = new HashSet<ComponentName>();
    564             for (int i = 0, count = installedServices.size(); i < count; i++) {
    565                 ResolveInfo resolveInfo = installedServices.get(i);
    566                 ServiceInfo info = resolveInfo.serviceInfo;
    567 
    568                 if (!android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals(
    569                                 info.permission)) {
    570                     Slog.w(TAG, "Skipping notification listener service "
    571                             + info.packageName + "/" + info.name
    572                             + ": it does not require the permission "
    573                             + android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE);
    574                     continue;
    575                 }
    576                 installed.add(new ComponentName(info.packageName, info.name));
    577             }
    578 
    579             String flatOut = "";
    580             if (!installed.isEmpty()) {
    581                 String[] enabled = flatIn.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
    582                 ArrayList<String> remaining = new ArrayList<String>(enabled.length);
    583                 for (int i = 0; i < enabled.length; i++) {
    584                     ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]);
    585                     if (installed.contains(enabledComponent)) {
    586                         remaining.add(enabled[i]);
    587                     }
    588                 }
    589                 flatOut = TextUtils.join(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR, remaining);
    590             }
    591             if (DBG) Slog.v(TAG, "flat after: " + flatOut);
    592             if (!flatIn.equals(flatOut)) {
    593                 Settings.Secure.putStringForUser(mContext.getContentResolver(),
    594                         Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
    595                         flatOut, currentUser);
    596             }
    597         }
    598     }
    599 
    600     /**
    601      * Called whenever packages change, the user switches, or ENABLED_NOTIFICATION_LISTENERS
    602      * is altered. (For example in response to USER_SWITCHED in our broadcast receiver)
    603      */
    604     void rebindListenerServices() {
    605         final int currentUser = ActivityManager.getCurrentUser();
    606         String flat = Settings.Secure.getStringForUser(
    607                 mContext.getContentResolver(),
    608                 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
    609                 currentUser);
    610 
    611         NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()];
    612         final ArrayList<ComponentName> toAdd;
    613 
    614         synchronized (mNotificationList) {
    615             // unbind and remove all existing listeners
    616             toRemove = mListeners.toArray(toRemove);
    617 
    618             toAdd = new ArrayList<ComponentName>();
    619             final HashSet<ComponentName> newEnabled = new HashSet<ComponentName>();
    620             final HashSet<String> newPackages = new HashSet<String>();
    621 
    622             // decode the list of components
    623             if (flat != null) {
    624                 String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
    625                 for (int i=0; i<components.length; i++) {
    626                     final ComponentName component
    627                             = ComponentName.unflattenFromString(components[i]);
    628                     if (component != null) {
    629                         newEnabled.add(component);
    630                         toAdd.add(component);
    631                         newPackages.add(component.getPackageName());
    632                     }
    633                 }
    634 
    635                 mEnabledListenersForCurrentUser = newEnabled;
    636                 mEnabledListenerPackageNames = newPackages;
    637             }
    638         }
    639 
    640         for (NotificationListenerInfo info : toRemove) {
    641             final ComponentName component = info.component;
    642             final int oldUser = info.userid;
    643             Slog.v(TAG, "disabling notification listener for user " + oldUser + ": " + component);
    644             unregisterListenerService(component, info.userid);
    645         }
    646 
    647         final int N = toAdd.size();
    648         for (int i=0; i<N; i++) {
    649             final ComponentName component = toAdd.get(i);
    650             Slog.v(TAG, "enabling notification listener for user " + currentUser + ": "
    651                     + component);
    652             registerListenerService(component, currentUser);
    653         }
    654     }
    655 
    656     /**
    657      * Register a listener binder directly with the notification manager.
    658      *
    659      * Only works with system callers. Apps should extend
    660      * {@link android.service.notification.NotificationListenerService}.
    661      */
    662     @Override
    663     public void registerListener(final INotificationListener listener,
    664             final ComponentName component, final int userid) {
    665         checkCallerIsSystem();
    666 
    667         synchronized (mNotificationList) {
    668             try {
    669                 NotificationListenerInfo info
    670                         = new NotificationListenerInfo(listener, component, userid, true);
    671                 listener.asBinder().linkToDeath(info, 0);
    672                 mListeners.add(info);
    673             } catch (RemoteException e) {
    674                 // already dead
    675             }
    676         }
    677     }
    678 
    679     /**
    680      * Version of registerListener that takes the name of a
    681      * {@link android.service.notification.NotificationListenerService} to bind to.
    682      *
    683      * This is the mechanism by which third parties may subscribe to notifications.
    684      */
    685     private void registerListenerService(final ComponentName name, final int userid) {
    686         checkCallerIsSystem();
    687 
    688         if (DBG) Slog.v(TAG, "registerListenerService: " + name + " u=" + userid);
    689 
    690         synchronized (mNotificationList) {
    691             final String servicesBindingTag = name.toString() + "/" + userid;
    692             if (mServicesBinding.contains(servicesBindingTag)) {
    693                 // stop registering this thing already! we're working on it
    694                 return;
    695             }
    696             mServicesBinding.add(servicesBindingTag);
    697 
    698             final int N = mListeners.size();
    699             for (int i=N-1; i>=0; i--) {
    700                 final NotificationListenerInfo info = mListeners.get(i);
    701                 if (name.equals(info.component)
    702                         && info.userid == userid) {
    703                     // cut old connections
    704                     if (DBG) Slog.v(TAG, "    disconnecting old listener: " + info.listener);
    705                     mListeners.remove(i);
    706                     if (info.connection != null) {
    707                         mContext.unbindService(info.connection);
    708                     }
    709                 }
    710             }
    711 
    712             Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
    713             intent.setComponent(name);
    714 
    715             intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
    716                     R.string.notification_listener_binding_label);
    717             intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
    718                     mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0));
    719 
    720             try {
    721                 if (DBG) Slog.v(TAG, "binding: " + intent);
    722                 if (!mContext.bindServiceAsUser(intent,
    723                         new ServiceConnection() {
    724                             INotificationListener mListener;
    725                             @Override
    726                             public void onServiceConnected(ComponentName name, IBinder service) {
    727                                 synchronized (mNotificationList) {
    728                                     mServicesBinding.remove(servicesBindingTag);
    729                                     try {
    730                                         mListener = INotificationListener.Stub.asInterface(service);
    731                                         NotificationListenerInfo info = new NotificationListenerInfo(
    732                                                 mListener, name, userid, this);
    733                                         service.linkToDeath(info, 0);
    734                                         mListeners.add(info);
    735                                     } catch (RemoteException e) {
    736                                         // already dead
    737                                     }
    738                                 }
    739                             }
    740 
    741                             @Override
    742                             public void onServiceDisconnected(ComponentName name) {
    743                                 Slog.v(TAG, "notification listener connection lost: " + name);
    744                             }
    745                         },
    746                         Context.BIND_AUTO_CREATE,
    747                         new UserHandle(userid)))
    748                 {
    749                     mServicesBinding.remove(servicesBindingTag);
    750                     Slog.w(TAG, "Unable to bind listener service: " + intent);
    751                     return;
    752                 }
    753             } catch (SecurityException ex) {
    754                 Slog.e(TAG, "Unable to bind listener service: " + intent, ex);
    755                 return;
    756             }
    757         }
    758     }
    759 
    760     /**
    761      * Removes a listener from the list and unbinds from its service.
    762      */
    763     public void unregisterListener(final INotificationListener listener, final int userid) {
    764         if (listener == null) return;
    765 
    766         NotificationListenerInfo info = removeListenerImpl(listener, userid);
    767         if (info != null && info.connection != null) {
    768             mContext.unbindService(info.connection);
    769         }
    770     }
    771 
    772     /**
    773      * Removes a listener from the list but does not unbind from the listener's service.
    774      *
    775      * @return the removed listener.
    776      */
    777     NotificationListenerInfo removeListenerImpl(
    778             final INotificationListener listener, final int userid) {
    779         NotificationListenerInfo listenerInfo = null;
    780         synchronized (mNotificationList) {
    781             final int N = mListeners.size();
    782             for (int i=N-1; i>=0; i--) {
    783                 final NotificationListenerInfo info = mListeners.get(i);
    784                 if (info.listener.asBinder() == listener.asBinder()
    785                         && info.userid == userid) {
    786                     listenerInfo = mListeners.remove(i);
    787                 }
    788             }
    789         }
    790         return listenerInfo;
    791     }
    792 
    793     /**
    794      * Remove a listener service for the given user by ComponentName
    795      */
    796     private void unregisterListenerService(ComponentName name, int userid) {
    797         checkCallerIsSystem();
    798 
    799         synchronized (mNotificationList) {
    800             final int N = mListeners.size();
    801             for (int i=N-1; i>=0; i--) {
    802                 final NotificationListenerInfo info = mListeners.get(i);
    803                 if (name.equals(info.component)
    804                         && info.userid == userid) {
    805                     mListeners.remove(i);
    806                     if (info.connection != null) {
    807                         try {
    808                             mContext.unbindService(info.connection);
    809                         } catch (IllegalArgumentException ex) {
    810                             // something happened to the service: we think we have a connection
    811                             // but it's bogus.
    812                             Slog.e(TAG, "Listener " + name + " could not be unbound: " + ex);
    813                         }
    814                     }
    815                 }
    816             }
    817         }
    818     }
    819 
    820     /**
    821      * asynchronously notify all listeners about a new notification
    822      */
    823     private void notifyPostedLocked(NotificationRecord n) {
    824         // make a copy in case changes are made to the underlying Notification object
    825         final StatusBarNotification sbn = n.sbn.clone();
    826         for (final NotificationListenerInfo info : mListeners) {
    827             mHandler.post(new Runnable() {
    828                 @Override
    829                 public void run() {
    830                     info.notifyPostedIfUserMatch(sbn);
    831                 }});
    832         }
    833     }
    834 
    835     /**
    836      * asynchronously notify all listeners about a removed notification
    837      */
    838     private void notifyRemovedLocked(NotificationRecord n) {
    839         // make a copy in case changes are made to the underlying Notification object
    840         // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the notification
    841         final StatusBarNotification sbn_light = n.sbn.cloneLight();
    842 
    843         for (final NotificationListenerInfo info : mListeners) {
    844             mHandler.post(new Runnable() {
    845                 @Override
    846                 public void run() {
    847                     info.notifyRemovedIfUserMatch(sbn_light);
    848                 }});
    849         }
    850     }
    851 
    852     // -- APIs to support listeners clicking/clearing notifications --
    853 
    854     private NotificationListenerInfo checkListenerToken(INotificationListener listener) {
    855         final IBinder token = listener.asBinder();
    856         final int N = mListeners.size();
    857         for (int i=0; i<N; i++) {
    858             final NotificationListenerInfo info = mListeners.get(i);
    859             if (info.listener.asBinder() == token) return info;
    860         }
    861         throw new SecurityException("Disallowed call from unknown listener: " + listener);
    862     }
    863 
    864     /**
    865      * Allow an INotificationListener to simulate a "clear all" operation.
    866      *
    867      * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onClearAllNotifications}
    868      *
    869      * @param token The binder for the listener, to check that the caller is allowed
    870      */
    871     public void cancelAllNotificationsFromListener(INotificationListener token) {
    872         NotificationListenerInfo info = checkListenerToken(token);
    873         long identity = Binder.clearCallingIdentity();
    874         try {
    875             cancelAll(info.userid);
    876         } finally {
    877             Binder.restoreCallingIdentity(identity);
    878         }
    879     }
    880 
    881     /**
    882      * Allow an INotificationListener to simulate clearing (dismissing) a single notification.
    883      *
    884      * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear}
    885      *
    886      * @param token The binder for the listener, to check that the caller is allowed
    887      */
    888     public void cancelNotificationFromListener(INotificationListener token, String pkg, String tag, int id) {
    889         NotificationListenerInfo info = checkListenerToken(token);
    890         long identity = Binder.clearCallingIdentity();
    891         try {
    892             cancelNotification(pkg, tag, id, 0,
    893                     Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
    894                     true,
    895                     info.userid);
    896         } finally {
    897             Binder.restoreCallingIdentity(identity);
    898         }
    899     }
    900 
    901     /**
    902      * Allow an INotificationListener to request the list of outstanding notifications seen by
    903      * the current user. Useful when starting up, after which point the listener callbacks should
    904      * be used.
    905      *
    906      * @param token The binder for the listener, to check that the caller is allowed
    907      */
    908     public StatusBarNotification[] getActiveNotificationsFromListener(INotificationListener token) {
    909         NotificationListenerInfo info = checkListenerToken(token);
    910 
    911         StatusBarNotification[] result = new StatusBarNotification[0];
    912         ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>();
    913         synchronized (mNotificationList) {
    914             final int N = mNotificationList.size();
    915             for (int i=0; i<N; i++) {
    916                 StatusBarNotification sbn = mNotificationList.get(i).sbn;
    917                 if (info.enabledAndUserMatches(sbn)) {
    918                     list.add(sbn);
    919                 }
    920             }
    921         }
    922         return list.toArray(result);
    923     }
    924 
    925     // -- end of listener APIs --
    926 
    927     public static final class NotificationRecord
    928     {
    929         final StatusBarNotification sbn;
    930         IBinder statusBarKey;
    931 
    932         NotificationRecord(StatusBarNotification sbn)
    933         {
    934             this.sbn = sbn;
    935         }
    936 
    937         public Notification getNotification() { return sbn.getNotification(); }
    938         public int getFlags() { return sbn.getNotification().flags; }
    939         public int getUserId() { return sbn.getUserId(); }
    940 
    941         void dump(PrintWriter pw, String prefix, Context baseContext) {
    942             final Notification notification = sbn.getNotification();
    943             pw.println(prefix + this);
    944             pw.println(prefix + "  uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
    945             pw.println(prefix + "  icon=0x" + Integer.toHexString(notification.icon)
    946                     + " / " + idDebugString(baseContext, sbn.getPackageName(), notification.icon));
    947             pw.println(prefix + "  pri=" + notification.priority + " score=" + sbn.getScore());
    948             pw.println(prefix + "  contentIntent=" + notification.contentIntent);
    949             pw.println(prefix + "  deleteIntent=" + notification.deleteIntent);
    950             pw.println(prefix + "  tickerText=" + notification.tickerText);
    951             pw.println(prefix + "  contentView=" + notification.contentView);
    952             pw.println(prefix + String.format("  defaults=0x%08x flags=0x%08x",
    953                     notification.defaults, notification.flags));
    954             pw.println(prefix + "  sound=" + notification.sound);
    955             pw.println(prefix + "  vibrate=" + Arrays.toString(notification.vibrate));
    956             pw.println(prefix + String.format("  led=0x%08x onMs=%d offMs=%d",
    957                     notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
    958             if (notification.actions != null && notification.actions.length > 0) {
    959                 pw.println(prefix + "  actions={");
    960                 final int N = notification.actions.length;
    961                 for (int i=0; i<N; i++) {
    962                     final Notification.Action action = notification.actions[i];
    963                     pw.println(String.format("%s    [%d] \"%s\" -> %s",
    964                             prefix,
    965                             i,
    966                             action.title,
    967                             action.actionIntent.toString()
    968                             ));
    969                 }
    970                 pw.println(prefix + "  }");
    971             }
    972             if (notification.extras != null && notification.extras.size() > 0) {
    973                 pw.println(prefix + "  extras={");
    974                 for (String key : notification.extras.keySet()) {
    975                     pw.print(prefix + "    " + key + "=");
    976                     Object val = notification.extras.get(key);
    977                     if (val == null) {
    978                         pw.println("null");
    979                     } else {
    980                         pw.print(val.toString());
    981                         if (val instanceof Bitmap) {
    982                             pw.print(String.format(" (%dx%d)",
    983                                     ((Bitmap) val).getWidth(),
    984                                     ((Bitmap) val).getHeight()));
    985                         } else if (val.getClass().isArray()) {
    986                             pw.println(" {");
    987                             final int N = Array.getLength(val);
    988                             for (int i=0; i<N; i++) {
    989                                 if (i > 0) pw.println(",");
    990                                 pw.print(prefix + "      " + Array.get(val, i));
    991                             }
    992                             pw.print("\n" + prefix + "    }");
    993                         }
    994                         pw.println();
    995                     }
    996                 }
    997                 pw.println(prefix + "  }");
    998             }
    999         }
   1000 
   1001         @Override
   1002         public final String toString() {
   1003             return String.format(
   1004                     "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d: %s)",
   1005                     System.identityHashCode(this),
   1006                     this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(), this.sbn.getTag(),
   1007                     this.sbn.getScore(), this.sbn.getNotification());
   1008         }
   1009     }
   1010 
   1011     private static final class ToastRecord
   1012     {
   1013         final int pid;
   1014         final String pkg;
   1015         final ITransientNotification callback;
   1016         int duration;
   1017 
   1018         ToastRecord(int pid, String pkg, ITransientNotification callback, int duration)
   1019         {
   1020             this.pid = pid;
   1021             this.pkg = pkg;
   1022             this.callback = callback;
   1023             this.duration = duration;
   1024         }
   1025 
   1026         void update(int duration) {
   1027             this.duration = duration;
   1028         }
   1029 
   1030         void dump(PrintWriter pw, String prefix) {
   1031             pw.println(prefix + this);
   1032         }
   1033 
   1034         @Override
   1035         public final String toString()
   1036         {
   1037             return "ToastRecord{"
   1038                 + Integer.toHexString(System.identityHashCode(this))
   1039                 + " pkg=" + pkg
   1040                 + " callback=" + callback
   1041                 + " duration=" + duration;
   1042         }
   1043     }
   1044 
   1045     private StatusBarManagerService.NotificationCallbacks mNotificationCallbacks
   1046             = new StatusBarManagerService.NotificationCallbacks() {
   1047 
   1048         public void onSetDisabled(int status) {
   1049             synchronized (mNotificationList) {
   1050                 mDisabledNotifications = status;
   1051                 if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
   1052                     // cancel whatever's going on
   1053                     long identity = Binder.clearCallingIdentity();
   1054                     try {
   1055                         final IRingtonePlayer player = mAudioService.getRingtonePlayer();
   1056                         if (player != null) {
   1057                             player.stopAsync();
   1058                         }
   1059                     } catch (RemoteException e) {
   1060                     } finally {
   1061                         Binder.restoreCallingIdentity(identity);
   1062                     }
   1063 
   1064                     identity = Binder.clearCallingIdentity();
   1065                     try {
   1066                         mVibrator.cancel();
   1067                     } finally {
   1068                         Binder.restoreCallingIdentity(identity);
   1069                     }
   1070                 }
   1071             }
   1072         }
   1073 
   1074         public void onClearAll() {
   1075             // XXX to be totally correct, the caller should tell us which user
   1076             // this is for.
   1077             cancelAll(ActivityManager.getCurrentUser());
   1078         }
   1079 
   1080         public void onNotificationClick(String pkg, String tag, int id) {
   1081             // XXX to be totally correct, the caller should tell us which user
   1082             // this is for.
   1083             cancelNotification(pkg, tag, id, Notification.FLAG_AUTO_CANCEL,
   1084                     Notification.FLAG_FOREGROUND_SERVICE, false,
   1085                     ActivityManager.getCurrentUser());
   1086         }
   1087 
   1088         public void onNotificationClear(String pkg, String tag, int id) {
   1089             // XXX to be totally correct, the caller should tell us which user
   1090             // this is for.
   1091             cancelNotification(pkg, tag, id, 0,
   1092                 Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
   1093                 true, ActivityManager.getCurrentUser());
   1094         }
   1095 
   1096         public void onPanelRevealed() {
   1097             synchronized (mNotificationList) {
   1098                 // sound
   1099                 mSoundNotification = null;
   1100 
   1101                 long identity = Binder.clearCallingIdentity();
   1102                 try {
   1103                     final IRingtonePlayer player = mAudioService.getRingtonePlayer();
   1104                     if (player != null) {
   1105                         player.stopAsync();
   1106                     }
   1107                 } catch (RemoteException e) {
   1108                 } finally {
   1109                     Binder.restoreCallingIdentity(identity);
   1110                 }
   1111 
   1112                 // vibrate
   1113                 mVibrateNotification = null;
   1114                 identity = Binder.clearCallingIdentity();
   1115                 try {
   1116                     mVibrator.cancel();
   1117                 } finally {
   1118                     Binder.restoreCallingIdentity(identity);
   1119                 }
   1120 
   1121                 // light
   1122                 mLights.clear();
   1123                 mLedNotification = null;
   1124                 updateLightsLocked();
   1125             }
   1126         }
   1127 
   1128         public void onNotificationError(String pkg, String tag, int id,
   1129                 int uid, int initialPid, String message) {
   1130             Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id
   1131                     + "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")");
   1132             // XXX to be totally correct, the caller should tell us which user
   1133             // this is for.
   1134             cancelNotification(pkg, tag, id, 0, 0, false, UserHandle.getUserId(uid));
   1135             long ident = Binder.clearCallingIdentity();
   1136             try {
   1137                 ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg,
   1138                         "Bad notification posted from package " + pkg
   1139                         + ": " + message);
   1140             } catch (RemoteException e) {
   1141             }
   1142             Binder.restoreCallingIdentity(ident);
   1143         }
   1144     };
   1145 
   1146     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
   1147         @Override
   1148         public void onReceive(Context context, Intent intent) {
   1149             String action = intent.getAction();
   1150 
   1151             boolean queryRestart = false;
   1152             boolean queryRemove = false;
   1153             boolean packageChanged = false;
   1154             boolean cancelNotifications = true;
   1155 
   1156             if (action.equals(Intent.ACTION_PACKAGE_ADDED)
   1157                     || (queryRemove=action.equals(Intent.ACTION_PACKAGE_REMOVED))
   1158                     || action.equals(Intent.ACTION_PACKAGE_RESTARTED)
   1159                     || (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED))
   1160                     || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
   1161                     || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
   1162                 String pkgList[] = null;
   1163                 boolean queryReplace = queryRemove &&
   1164                         intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
   1165                 if (DBG) Slog.i(TAG, "queryReplace=" + queryReplace);
   1166                 if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
   1167                     pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
   1168                 } else if (queryRestart) {
   1169                     pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
   1170                 } else {
   1171                     Uri uri = intent.getData();
   1172                     if (uri == null) {
   1173                         return;
   1174                     }
   1175                     String pkgName = uri.getSchemeSpecificPart();
   1176                     if (pkgName == null) {
   1177                         return;
   1178                     }
   1179                     if (packageChanged) {
   1180                         // We cancel notifications for packages which have just been disabled
   1181                         try {
   1182                             final int enabled = mContext.getPackageManager()
   1183                                     .getApplicationEnabledSetting(pkgName);
   1184                             if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
   1185                                     || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
   1186                                 cancelNotifications = false;
   1187                             }
   1188                         } catch (IllegalArgumentException e) {
   1189                             // Package doesn't exist; probably racing with uninstall.
   1190                             // cancelNotifications is already true, so nothing to do here.
   1191                             if (DBG) {
   1192                                 Slog.i(TAG, "Exception trying to look up app enabled setting", e);
   1193                             }
   1194                         }
   1195                     }
   1196                     pkgList = new String[]{pkgName};
   1197                 }
   1198 
   1199                 boolean anyListenersInvolved = false;
   1200                 if (pkgList != null && (pkgList.length > 0)) {
   1201                     for (String pkgName : pkgList) {
   1202                         if (cancelNotifications) {
   1203                             cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart,
   1204                                     UserHandle.USER_ALL);
   1205                         }
   1206                         if (mEnabledListenerPackageNames.contains(pkgName)) {
   1207                             anyListenersInvolved = true;
   1208                         }
   1209                     }
   1210                 }
   1211 
   1212                 if (anyListenersInvolved) {
   1213                     // if we're not replacing a package, clean up orphaned bits
   1214                     if (!queryReplace) {
   1215                         disableNonexistentListeners();
   1216                     }
   1217                     // make sure we're still bound to any of our
   1218                     // listeners who may have just upgraded
   1219                     rebindListenerServices();
   1220                 }
   1221             } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
   1222                 // Keep track of screen on/off state, but do not turn off the notification light
   1223                 // until user passes through the lock screen or views the notification.
   1224                 mScreenOn = true;
   1225             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
   1226                 mScreenOn = false;
   1227             } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
   1228                 mInCall = (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(
   1229                         TelephonyManager.EXTRA_STATE_OFFHOOK));
   1230                 updateNotificationPulse();
   1231             } else if (action.equals(Intent.ACTION_USER_STOPPED)) {
   1232                 int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
   1233                 if (userHandle >= 0) {
   1234                     cancelAllNotificationsInt(null, 0, 0, true, userHandle);
   1235                 }
   1236             } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
   1237                 // turn off LED when user passes through lock screen
   1238                 mNotificationLight.turnOff();
   1239             } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
   1240                 // reload per-user settings
   1241                 mSettingsObserver.update(null);
   1242             }
   1243         }
   1244     };
   1245 
   1246     class SettingsObserver extends ContentObserver {
   1247         private final Uri NOTIFICATION_LIGHT_PULSE_URI
   1248                 = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
   1249 
   1250         private final Uri ENABLED_NOTIFICATION_LISTENERS_URI
   1251                 = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
   1252 
   1253         SettingsObserver(Handler handler) {
   1254             super(handler);
   1255         }
   1256 
   1257         void observe() {
   1258             ContentResolver resolver = mContext.getContentResolver();
   1259             resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
   1260                     false, this, UserHandle.USER_ALL);
   1261             resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
   1262                     false, this, UserHandle.USER_ALL);
   1263             update(null);
   1264         }
   1265 
   1266         @Override public void onChange(boolean selfChange, Uri uri) {
   1267             update(uri);
   1268         }
   1269 
   1270         public void update(Uri uri) {
   1271             ContentResolver resolver = mContext.getContentResolver();
   1272             if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
   1273                 boolean pulseEnabled = Settings.System.getInt(resolver,
   1274                             Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
   1275                 if (mNotificationPulseEnabled != pulseEnabled) {
   1276                     mNotificationPulseEnabled = pulseEnabled;
   1277                     updateNotificationPulse();
   1278                 }
   1279             }
   1280             if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) {
   1281                 rebindListenerServices();
   1282             }
   1283         }
   1284     }
   1285 
   1286     private SettingsObserver mSettingsObserver;
   1287 
   1288     static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) {
   1289         int[] ar = r.getIntArray(resid);
   1290         if (ar == null) {
   1291             return def;
   1292         }
   1293         final int len = ar.length > maxlen ? maxlen : ar.length;
   1294         long[] out = new long[len];
   1295         for (int i=0; i<len; i++) {
   1296             out[i] = ar[i];
   1297         }
   1298         return out;
   1299     }
   1300 
   1301     NotificationManagerService(Context context, StatusBarManagerService statusBar,
   1302             LightsService lights)
   1303     {
   1304         super();
   1305         mContext = context;
   1306         mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
   1307         mAm = ActivityManagerNative.getDefault();
   1308         mUserManager = (UserManager)context.getSystemService(Context.USER_SERVICE);
   1309         mToastQueue = new ArrayList<ToastRecord>();
   1310         mHandler = new WorkerHandler();
   1311 
   1312         mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
   1313 
   1314         importOldBlockDb();
   1315 
   1316         mStatusBar = statusBar;
   1317         statusBar.setNotificationCallbacks(mNotificationCallbacks);
   1318 
   1319         mNotificationLight = lights.getLight(LightsService.LIGHT_ID_NOTIFICATIONS);
   1320         mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION);
   1321 
   1322         Resources resources = mContext.getResources();
   1323         mDefaultNotificationColor = resources.getColor(
   1324                 R.color.config_defaultNotificationColor);
   1325         mDefaultNotificationLedOn = resources.getInteger(
   1326                 R.integer.config_defaultNotificationLedOn);
   1327         mDefaultNotificationLedOff = resources.getInteger(
   1328                 R.integer.config_defaultNotificationLedOff);
   1329 
   1330         mDefaultVibrationPattern = getLongArray(resources,
   1331                 R.array.config_defaultNotificationVibePattern,
   1332                 VIBRATE_PATTERN_MAXLEN,
   1333                 DEFAULT_VIBRATE_PATTERN);
   1334 
   1335         mFallbackVibrationPattern = getLongArray(resources,
   1336                 R.array.config_notificationFallbackVibePattern,
   1337                 VIBRATE_PATTERN_MAXLEN,
   1338                 DEFAULT_VIBRATE_PATTERN);
   1339 
   1340         // Don't start allowing notifications until the setup wizard has run once.
   1341         // After that, including subsequent boots, init with notifications turned on.
   1342         // This works on the first boot because the setup wizard will toggle this
   1343         // flag at least once and we'll go back to 0 after that.
   1344         if (0 == Settings.Global.getInt(mContext.getContentResolver(),
   1345                     Settings.Global.DEVICE_PROVISIONED, 0)) {
   1346             mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
   1347         }
   1348 
   1349         // register for various Intents
   1350         IntentFilter filter = new IntentFilter();
   1351         filter.addAction(Intent.ACTION_SCREEN_ON);
   1352         filter.addAction(Intent.ACTION_SCREEN_OFF);
   1353         filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
   1354         filter.addAction(Intent.ACTION_USER_PRESENT);
   1355         filter.addAction(Intent.ACTION_USER_STOPPED);
   1356         filter.addAction(Intent.ACTION_USER_SWITCHED);
   1357         mContext.registerReceiver(mIntentReceiver, filter);
   1358         IntentFilter pkgFilter = new IntentFilter();
   1359         pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
   1360         pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
   1361         pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
   1362         pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
   1363         pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
   1364         pkgFilter.addDataScheme("package");
   1365         mContext.registerReceiver(mIntentReceiver, pkgFilter);
   1366         IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
   1367         mContext.registerReceiver(mIntentReceiver, sdFilter);
   1368 
   1369         mSettingsObserver = new SettingsObserver(mHandler);
   1370         mSettingsObserver.observe();
   1371 
   1372         // spin up NotificationScorers
   1373         String[] notificationScorerNames = resources.getStringArray(
   1374                 R.array.config_notificationScorers);
   1375         for (String scorerName : notificationScorerNames) {
   1376             try {
   1377                 Class<?> scorerClass = mContext.getClassLoader().loadClass(scorerName);
   1378                 NotificationScorer scorer = (NotificationScorer) scorerClass.newInstance();
   1379                 scorer.initialize(mContext);
   1380                 mScorers.add(scorer);
   1381             } catch (ClassNotFoundException e) {
   1382                 Slog.w(TAG, "Couldn't find scorer " + scorerName + ".", e);
   1383             } catch (InstantiationException e) {
   1384                 Slog.w(TAG, "Couldn't instantiate scorer " + scorerName + ".", e);
   1385             } catch (IllegalAccessException e) {
   1386                 Slog.w(TAG, "Problem accessing scorer " + scorerName + ".", e);
   1387             }
   1388         }
   1389     }
   1390 
   1391     /**
   1392      * Read the old XML-based app block database and import those blockages into the AppOps system.
   1393      */
   1394     private void importOldBlockDb() {
   1395         loadBlockDb();
   1396 
   1397         PackageManager pm = mContext.getPackageManager();
   1398         for (String pkg : mBlockedPackages) {
   1399             PackageInfo info = null;
   1400             try {
   1401                 info = pm.getPackageInfo(pkg, 0);
   1402                 setNotificationsEnabledForPackage(pkg, info.applicationInfo.uid, false);
   1403             } catch (NameNotFoundException e) {
   1404                 // forget you
   1405             }
   1406         }
   1407         mBlockedPackages.clear();
   1408         if (mPolicyFile != null) {
   1409             mPolicyFile.delete();
   1410         }
   1411     }
   1412 
   1413     void systemReady() {
   1414         mAudioService = IAudioService.Stub.asInterface(
   1415                 ServiceManager.getService(Context.AUDIO_SERVICE));
   1416 
   1417         // no beeping until we're basically done booting
   1418         mSystemReady = true;
   1419 
   1420         // make sure our listener services are properly bound
   1421         rebindListenerServices();
   1422     }
   1423 
   1424     // Toasts
   1425     // ============================================================================
   1426     public void enqueueToast(String pkg, ITransientNotification callback, int duration)
   1427     {
   1428         if (DBG) Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration);
   1429 
   1430         if (pkg == null || callback == null) {
   1431             Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
   1432             return ;
   1433         }
   1434 
   1435         final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
   1436 
   1437         if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {
   1438             if (!isSystemToast) {
   1439                 Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
   1440                 return;
   1441             }
   1442         }
   1443 
   1444         synchronized (mToastQueue) {
   1445             int callingPid = Binder.getCallingPid();
   1446             long callingId = Binder.clearCallingIdentity();
   1447             try {
   1448                 ToastRecord record;
   1449                 int index = indexOfToastLocked(pkg, callback);
   1450                 // If it's already in the queue, we update it in place, we don't
   1451                 // move it to the end of the queue.
   1452                 if (index >= 0) {
   1453                     record = mToastQueue.get(index);
   1454                     record.update(duration);
   1455                 } else {
   1456                     // Limit the number of toasts that any given package except the android
   1457                     // package can enqueue.  Prevents DOS attacks and deals with leaks.
   1458                     if (!isSystemToast) {
   1459                         int count = 0;
   1460                         final int N = mToastQueue.size();
   1461                         for (int i=0; i<N; i++) {
   1462                              final ToastRecord r = mToastQueue.get(i);
   1463                              if (r.pkg.equals(pkg)) {
   1464                                  count++;
   1465                                  if (count >= MAX_PACKAGE_NOTIFICATIONS) {
   1466                                      Slog.e(TAG, "Package has already posted " + count
   1467                                             + " toasts. Not showing more. Package=" + pkg);
   1468                                      return;
   1469                                  }
   1470                              }
   1471                         }
   1472                     }
   1473 
   1474                     record = new ToastRecord(callingPid, pkg, callback, duration);
   1475                     mToastQueue.add(record);
   1476                     index = mToastQueue.size() - 1;
   1477                     keepProcessAliveLocked(callingPid);
   1478                 }
   1479                 // If it's at index 0, it's the current toast.  It doesn't matter if it's
   1480                 // new or just been updated.  Call back and tell it to show itself.
   1481                 // If the callback fails, this will remove it from the list, so don't
   1482                 // assume that it's valid after this.
   1483                 if (index == 0) {
   1484                     showNextToastLocked();
   1485                 }
   1486             } finally {
   1487                 Binder.restoreCallingIdentity(callingId);
   1488             }
   1489         }
   1490     }
   1491 
   1492     public void cancelToast(String pkg, ITransientNotification callback) {
   1493         Slog.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback);
   1494 
   1495         if (pkg == null || callback == null) {
   1496             Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback);
   1497             return ;
   1498         }
   1499 
   1500         synchronized (mToastQueue) {
   1501             long callingId = Binder.clearCallingIdentity();
   1502             try {
   1503                 int index = indexOfToastLocked(pkg, callback);
   1504                 if (index >= 0) {
   1505                     cancelToastLocked(index);
   1506                 } else {
   1507                     Slog.w(TAG, "Toast already cancelled. pkg=" + pkg + " callback=" + callback);
   1508                 }
   1509             } finally {
   1510                 Binder.restoreCallingIdentity(callingId);
   1511             }
   1512         }
   1513     }
   1514 
   1515     private void showNextToastLocked() {
   1516         ToastRecord record = mToastQueue.get(0);
   1517         while (record != null) {
   1518             if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
   1519             try {
   1520                 record.callback.show();
   1521                 scheduleTimeoutLocked(record);
   1522                 return;
   1523             } catch (RemoteException e) {
   1524                 Slog.w(TAG, "Object died trying to show notification " + record.callback
   1525                         + " in package " + record.pkg);
   1526                 // remove it from the list and let the process die
   1527                 int index = mToastQueue.indexOf(record);
   1528                 if (index >= 0) {
   1529                     mToastQueue.remove(index);
   1530                 }
   1531                 keepProcessAliveLocked(record.pid);
   1532                 if (mToastQueue.size() > 0) {
   1533                     record = mToastQueue.get(0);
   1534                 } else {
   1535                     record = null;
   1536                 }
   1537             }
   1538         }
   1539     }
   1540 
   1541     private void cancelToastLocked(int index) {
   1542         ToastRecord record = mToastQueue.get(index);
   1543         try {
   1544             record.callback.hide();
   1545         } catch (RemoteException e) {
   1546             Slog.w(TAG, "Object died trying to hide notification " + record.callback
   1547                     + " in package " + record.pkg);
   1548             // don't worry about this, we're about to remove it from
   1549             // the list anyway
   1550         }
   1551         mToastQueue.remove(index);
   1552         keepProcessAliveLocked(record.pid);
   1553         if (mToastQueue.size() > 0) {
   1554             // Show the next one. If the callback fails, this will remove
   1555             // it from the list, so don't assume that the list hasn't changed
   1556             // after this point.
   1557             showNextToastLocked();
   1558         }
   1559     }
   1560 
   1561     private void scheduleTimeoutLocked(ToastRecord r)
   1562     {
   1563         mHandler.removeCallbacksAndMessages(r);
   1564         Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
   1565         long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
   1566         mHandler.sendMessageDelayed(m, delay);
   1567     }
   1568 
   1569     private void handleTimeout(ToastRecord record)
   1570     {
   1571         if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
   1572         synchronized (mToastQueue) {
   1573             int index = indexOfToastLocked(record.pkg, record.callback);
   1574             if (index >= 0) {
   1575                 cancelToastLocked(index);
   1576             }
   1577         }
   1578     }
   1579 
   1580     // lock on mToastQueue
   1581     private int indexOfToastLocked(String pkg, ITransientNotification callback)
   1582     {
   1583         IBinder cbak = callback.asBinder();
   1584         ArrayList<ToastRecord> list = mToastQueue;
   1585         int len = list.size();
   1586         for (int i=0; i<len; i++) {
   1587             ToastRecord r = list.get(i);
   1588             if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
   1589                 return i;
   1590             }
   1591         }
   1592         return -1;
   1593     }
   1594 
   1595     // lock on mToastQueue
   1596     private void keepProcessAliveLocked(int pid)
   1597     {
   1598         int toastCount = 0; // toasts from this pid
   1599         ArrayList<ToastRecord> list = mToastQueue;
   1600         int N = list.size();
   1601         for (int i=0; i<N; i++) {
   1602             ToastRecord r = list.get(i);
   1603             if (r.pid == pid) {
   1604                 toastCount++;
   1605             }
   1606         }
   1607         try {
   1608             mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
   1609         } catch (RemoteException e) {
   1610             // Shouldn't happen.
   1611         }
   1612     }
   1613 
   1614     private final class WorkerHandler extends Handler
   1615     {
   1616         @Override
   1617         public void handleMessage(Message msg)
   1618         {
   1619             switch (msg.what)
   1620             {
   1621                 case MESSAGE_TIMEOUT:
   1622                     handleTimeout((ToastRecord)msg.obj);
   1623                     break;
   1624             }
   1625         }
   1626     }
   1627 
   1628 
   1629     // Notifications
   1630     // ============================================================================
   1631     public void enqueueNotificationWithTag(String pkg, String basePkg, String tag, int id,
   1632             Notification notification, int[] idOut, int userId)
   1633     {
   1634         enqueueNotificationInternal(pkg, basePkg, Binder.getCallingUid(), Binder.getCallingPid(),
   1635                 tag, id, notification, idOut, userId);
   1636     }
   1637 
   1638     private final static int clamp(int x, int low, int high) {
   1639         return (x < low) ? low : ((x > high) ? high : x);
   1640     }
   1641 
   1642     // Not exposed via Binder; for system use only (otherwise malicious apps could spoof the
   1643     // uid/pid of another application)
   1644 
   1645     public void enqueueNotificationInternal(final String pkg, String basePkg, final int callingUid,
   1646             final int callingPid, final String tag, final int id, final Notification notification,
   1647             int[] idOut, int incomingUserId)
   1648     {
   1649         if (DBG) {
   1650             Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
   1651         }
   1652         checkCallerIsSystemOrSameApp(pkg);
   1653         final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));
   1654 
   1655         final int userId = ActivityManager.handleIncomingUser(callingPid,
   1656                 callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
   1657         final UserHandle user = new UserHandle(userId);
   1658 
   1659         // Limit the number of notifications that any given package except the android
   1660         // package can enqueue.  Prevents DOS attacks and deals with leaks.
   1661         if (!isSystemNotification) {
   1662             synchronized (mNotificationList) {
   1663                 int count = 0;
   1664                 final int N = mNotificationList.size();
   1665                 for (int i=0; i<N; i++) {
   1666                     final NotificationRecord r = mNotificationList.get(i);
   1667                     if (r.sbn.getPackageName().equals(pkg) && r.sbn.getUserId() == userId) {
   1668                         count++;
   1669                         if (count >= MAX_PACKAGE_NOTIFICATIONS) {
   1670                             Slog.e(TAG, "Package has already posted " + count
   1671                                     + " notifications.  Not showing more.  package=" + pkg);
   1672                             return;
   1673                         }
   1674                     }
   1675                 }
   1676             }
   1677         }
   1678 
   1679         // This conditional is a dirty hack to limit the logging done on
   1680         //     behalf of the download manager without affecting other apps.
   1681         if (!pkg.equals("com.android.providers.downloads")
   1682                 || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
   1683             EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag, userId,
   1684                     notification.toString());
   1685         }
   1686 
   1687         if (pkg == null || notification == null) {
   1688             throw new IllegalArgumentException("null not allowed: pkg=" + pkg
   1689                     + " id=" + id + " notification=" + notification);
   1690         }
   1691         if (notification.icon != 0) {
   1692             if (notification.contentView == null) {
   1693                 throw new IllegalArgumentException("contentView required: pkg=" + pkg
   1694                         + " id=" + id + " notification=" + notification);
   1695             }
   1696         }
   1697 
   1698         mHandler.post(new Runnable() {
   1699             @Override
   1700             public void run() {
   1701 
   1702                 // === Scoring ===
   1703 
   1704                 // 0. Sanitize inputs
   1705                 notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
   1706                         Notification.PRIORITY_MAX);
   1707                 // Migrate notification flags to scores
   1708                 if (0 != (notification.flags & Notification.FLAG_HIGH_PRIORITY)) {
   1709                     if (notification.priority < Notification.PRIORITY_MAX) {
   1710                         notification.priority = Notification.PRIORITY_MAX;
   1711                     }
   1712                 } else if (SCORE_ONGOING_HIGHER &&
   1713                         0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) {
   1714                     if (notification.priority < Notification.PRIORITY_HIGH) {
   1715                         notification.priority = Notification.PRIORITY_HIGH;
   1716                     }
   1717                 }
   1718 
   1719                 // 1. initial score: buckets of 10, around the app
   1720                 int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20]
   1721 
   1722                 // 2. Consult external heuristics (TBD)
   1723 
   1724                 // 3. Apply local rules
   1725 
   1726                 int initialScore = score;
   1727                 if (!mScorers.isEmpty()) {
   1728                     if (DBG) Slog.v(TAG, "Initial score is " + score + ".");
   1729                     for (NotificationScorer scorer : mScorers) {
   1730                         try {
   1731                             score = scorer.getScore(notification, score);
   1732                         } catch (Throwable t) {
   1733                             Slog.w(TAG, "Scorer threw on .getScore.", t);
   1734                         }
   1735                     }
   1736                     if (DBG) Slog.v(TAG, "Final score is " + score + ".");
   1737                 }
   1738 
   1739                 // add extra to indicate score modified by NotificationScorer
   1740                 notification.extras.putBoolean(Notification.EXTRA_SCORE_MODIFIED,
   1741                         score != initialScore);
   1742 
   1743                 // blocked apps
   1744                 if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) {
   1745                     if (!isSystemNotification) {
   1746                         score = JUNK_SCORE;
   1747                         Slog.e(TAG, "Suppressing notification from package " + pkg
   1748                                 + " by user request.");
   1749                     }
   1750                 }
   1751 
   1752                 if (DBG) {
   1753                     Slog.v(TAG, "Assigned score=" + score + " to " + notification);
   1754                 }
   1755 
   1756                 if (score < SCORE_DISPLAY_THRESHOLD) {
   1757                     // Notification will be blocked because the score is too low.
   1758                     return;
   1759                 }
   1760 
   1761                 // Should this notification make noise, vibe, or use the LED?
   1762                 final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD);
   1763 
   1764                 synchronized (mNotificationList) {
   1765                     final StatusBarNotification n = new StatusBarNotification(
   1766                             pkg, id, tag, callingUid, callingPid, score, notification, user);
   1767                     NotificationRecord r = new NotificationRecord(n);
   1768                     NotificationRecord old = null;
   1769 
   1770                     int index = indexOfNotificationLocked(pkg, tag, id, userId);
   1771                     if (index < 0) {
   1772                         mNotificationList.add(r);
   1773                     } else {
   1774                         old = mNotificationList.remove(index);
   1775                         mNotificationList.add(index, r);
   1776                         // Make sure we don't lose the foreground service state.
   1777                         if (old != null) {
   1778                             notification.flags |=
   1779                                 old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
   1780                         }
   1781                     }
   1782 
   1783                     // Ensure if this is a foreground service that the proper additional
   1784                     // flags are set.
   1785                     if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) {
   1786                         notification.flags |= Notification.FLAG_ONGOING_EVENT
   1787                                 | Notification.FLAG_NO_CLEAR;
   1788                     }
   1789 
   1790                     final int currentUser;
   1791                     final long token = Binder.clearCallingIdentity();
   1792                     try {
   1793                         currentUser = ActivityManager.getCurrentUser();
   1794                     } finally {
   1795                         Binder.restoreCallingIdentity(token);
   1796                     }
   1797 
   1798                     if (notification.icon != 0) {
   1799                         if (old != null && old.statusBarKey != null) {
   1800                             r.statusBarKey = old.statusBarKey;
   1801                             long identity = Binder.clearCallingIdentity();
   1802                             try {
   1803                                 mStatusBar.updateNotification(r.statusBarKey, n);
   1804                             }
   1805                             finally {
   1806                                 Binder.restoreCallingIdentity(identity);
   1807                             }
   1808                         } else {
   1809                             long identity = Binder.clearCallingIdentity();
   1810                             try {
   1811                                 r.statusBarKey = mStatusBar.addNotification(n);
   1812                                 if ((n.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0
   1813                                         && canInterrupt) {
   1814                                     mAttentionLight.pulse();
   1815                                 }
   1816                             }
   1817                             finally {
   1818                                 Binder.restoreCallingIdentity(identity);
   1819                             }
   1820                         }
   1821                         // Send accessibility events only for the current user.
   1822                         if (currentUser == userId) {
   1823                             sendAccessibilityEvent(notification, pkg);
   1824                         }
   1825 
   1826                         notifyPostedLocked(r);
   1827                     } else {
   1828                         Slog.e(TAG, "Not posting notification with icon==0: " + notification);
   1829                         if (old != null && old.statusBarKey != null) {
   1830                             long identity = Binder.clearCallingIdentity();
   1831                             try {
   1832                                 mStatusBar.removeNotification(old.statusBarKey);
   1833                             }
   1834                             finally {
   1835                                 Binder.restoreCallingIdentity(identity);
   1836                             }
   1837 
   1838                             notifyRemovedLocked(r);
   1839                         }
   1840                         // ATTENTION: in a future release we will bail out here
   1841                         // so that we do not play sounds, show lights, etc. for invalid notifications
   1842                         Slog.e(TAG, "WARNING: In a future release this will crash the app: "
   1843                                 + n.getPackageName());
   1844                     }
   1845 
   1846                     // If we're not supposed to beep, vibrate, etc. then don't.
   1847                     if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
   1848                             && (!(old != null
   1849                                 && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
   1850                             && (r.getUserId() == UserHandle.USER_ALL ||
   1851                                 (r.getUserId() == userId && r.getUserId() == currentUser))
   1852                             && canInterrupt
   1853                             && mSystemReady) {
   1854 
   1855                         final AudioManager audioManager = (AudioManager) mContext
   1856                         .getSystemService(Context.AUDIO_SERVICE);
   1857 
   1858                         // sound
   1859 
   1860                         // should we use the default notification sound? (indicated either by
   1861                         // DEFAULT_SOUND or because notification.sound is pointing at
   1862                         // Settings.System.NOTIFICATION_SOUND)
   1863                         final boolean useDefaultSound =
   1864                                (notification.defaults & Notification.DEFAULT_SOUND) != 0 ||
   1865                                        Settings.System.DEFAULT_NOTIFICATION_URI
   1866                                                .equals(notification.sound);
   1867 
   1868                         Uri soundUri = null;
   1869                         boolean hasValidSound = false;
   1870 
   1871                         if (useDefaultSound) {
   1872                             soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
   1873 
   1874                             // check to see if the default notification sound is silent
   1875                             ContentResolver resolver = mContext.getContentResolver();
   1876                             hasValidSound = Settings.System.getString(resolver,
   1877                                    Settings.System.NOTIFICATION_SOUND) != null;
   1878                         } else if (notification.sound != null) {
   1879                             soundUri = notification.sound;
   1880                             hasValidSound = (soundUri != null);
   1881                         }
   1882 
   1883                         if (hasValidSound) {
   1884                             boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
   1885                             int audioStreamType;
   1886                             if (notification.audioStreamType >= 0) {
   1887                                 audioStreamType = notification.audioStreamType;
   1888                             } else {
   1889                                 audioStreamType = DEFAULT_STREAM_TYPE;
   1890                             }
   1891                             mSoundNotification = r;
   1892                             // do not play notifications if stream volume is 0 (typically because
   1893                             // ringer mode is silent) or if there is a user of exclusive audio focus
   1894                             if ((audioManager.getStreamVolume(audioStreamType) != 0)
   1895                                     && !audioManager.isAudioFocusExclusive()) {
   1896                                 final long identity = Binder.clearCallingIdentity();
   1897                                 try {
   1898                                     final IRingtonePlayer player = mAudioService.getRingtonePlayer();
   1899                                     if (player != null) {
   1900                                         player.playAsync(soundUri, user, looping, audioStreamType);
   1901                                     }
   1902                                 } catch (RemoteException e) {
   1903                                 } finally {
   1904                                     Binder.restoreCallingIdentity(identity);
   1905                                 }
   1906                             }
   1907                         }
   1908 
   1909                         // vibrate
   1910                         // Does the notification want to specify its own vibration?
   1911                         final boolean hasCustomVibrate = notification.vibrate != null;
   1912 
   1913                         // new in 4.2: if there was supposed to be a sound and we're in vibrate
   1914                         // mode, and no other vibration is specified, we fall back to vibration
   1915                         final boolean convertSoundToVibration =
   1916                                    !hasCustomVibrate
   1917                                 && hasValidSound
   1918                                 && (audioManager.getRingerMode()
   1919                                            == AudioManager.RINGER_MODE_VIBRATE);
   1920 
   1921                         // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.
   1922                         final boolean useDefaultVibrate =
   1923                                 (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
   1924 
   1925                         if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate)
   1926                                 && !(audioManager.getRingerMode()
   1927                                         == AudioManager.RINGER_MODE_SILENT)) {
   1928                             mVibrateNotification = r;
   1929 
   1930                             if (useDefaultVibrate || convertSoundToVibration) {
   1931                                 // Escalate privileges so we can use the vibrator even if the
   1932                                 // notifying app does not have the VIBRATE permission.
   1933                                 long identity = Binder.clearCallingIdentity();
   1934                                 try {
   1935                                     mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(),
   1936                                         useDefaultVibrate ? mDefaultVibrationPattern
   1937                                             : mFallbackVibrationPattern,
   1938                                         ((notification.flags & Notification.FLAG_INSISTENT) != 0)
   1939                                                 ? 0: -1);
   1940                                 } finally {
   1941                                     Binder.restoreCallingIdentity(identity);
   1942                                 }
   1943                             } else if (notification.vibrate.length > 1) {
   1944                                 // If you want your own vibration pattern, you need the VIBRATE
   1945                                 // permission
   1946                                 mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(),
   1947                                         notification.vibrate,
   1948                                     ((notification.flags & Notification.FLAG_INSISTENT) != 0)
   1949                                             ? 0: -1);
   1950                             }
   1951                         }
   1952                     }
   1953 
   1954                     // light
   1955                     // the most recent thing gets the light
   1956                     mLights.remove(old);
   1957                     if (mLedNotification == old) {
   1958                         mLedNotification = null;
   1959                     }
   1960                     //Slog.i(TAG, "notification.lights="
   1961                     //        + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS)
   1962                     //                  != 0));
   1963                     if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0
   1964                             && canInterrupt) {
   1965                         mLights.add(r);
   1966                         updateLightsLocked();
   1967                     } else {
   1968                         if (old != null
   1969                                 && ((old.getFlags() & Notification.FLAG_SHOW_LIGHTS) != 0)) {
   1970                             updateLightsLocked();
   1971                         }
   1972                     }
   1973                 }
   1974             }
   1975         });
   1976 
   1977         idOut[0] = id;
   1978     }
   1979 
   1980     private void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
   1981         AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
   1982         if (!manager.isEnabled()) {
   1983             return;
   1984         }
   1985 
   1986         AccessibilityEvent event =
   1987             AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
   1988         event.setPackageName(packageName);
   1989         event.setClassName(Notification.class.getName());
   1990         event.setParcelableData(notification);
   1991         CharSequence tickerText = notification.tickerText;
   1992         if (!TextUtils.isEmpty(tickerText)) {
   1993             event.getText().add(tickerText);
   1994         }
   1995 
   1996         manager.sendAccessibilityEvent(event);
   1997     }
   1998 
   1999     private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete) {
   2000         // tell the app
   2001         if (sendDelete) {
   2002             if (r.getNotification().deleteIntent != null) {
   2003                 try {
   2004                     r.getNotification().deleteIntent.send();
   2005                 } catch (PendingIntent.CanceledException ex) {
   2006                     // do nothing - there's no relevant way to recover, and
   2007                     //     no reason to let this propagate
   2008                     Slog.w(TAG, "canceled PendingIntent for " + r.sbn.getPackageName(), ex);
   2009                 }
   2010             }
   2011         }
   2012 
   2013         // status bar
   2014         if (r.getNotification().icon != 0) {
   2015             long identity = Binder.clearCallingIdentity();
   2016             try {
   2017                 mStatusBar.removeNotification(r.statusBarKey);
   2018             }
   2019             finally {
   2020                 Binder.restoreCallingIdentity(identity);
   2021             }
   2022             r.statusBarKey = null;
   2023             notifyRemovedLocked(r);
   2024         }
   2025 
   2026         // sound
   2027         if (mSoundNotification == r) {
   2028             mSoundNotification = null;
   2029             final long identity = Binder.clearCallingIdentity();
   2030             try {
   2031                 final IRingtonePlayer player = mAudioService.getRingtonePlayer();
   2032                 if (player != null) {
   2033                     player.stopAsync();
   2034                 }
   2035             } catch (RemoteException e) {
   2036             } finally {
   2037                 Binder.restoreCallingIdentity(identity);
   2038             }
   2039         }
   2040 
   2041         // vibrate
   2042         if (mVibrateNotification == r) {
   2043             mVibrateNotification = null;
   2044             long identity = Binder.clearCallingIdentity();
   2045             try {
   2046                 mVibrator.cancel();
   2047             }
   2048             finally {
   2049                 Binder.restoreCallingIdentity(identity);
   2050             }
   2051         }
   2052 
   2053         // light
   2054         mLights.remove(r);
   2055         if (mLedNotification == r) {
   2056             mLedNotification = null;
   2057         }
   2058 
   2059         // Save it for users of getHistoricalNotifications()
   2060         mArchive.record(r.sbn);
   2061     }
   2062 
   2063     /**
   2064      * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}
   2065      * and none of the {@code mustNotHaveFlags}.
   2066      */
   2067     private void cancelNotification(final String pkg, final String tag, final int id,
   2068             final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete,
   2069             final int userId) {
   2070         // In enqueueNotificationInternal notifications are added by scheduling the
   2071         // work on the worker handler. Hence, we also schedule the cancel on this
   2072         // handler to avoid a scenario where an add notification call followed by a
   2073         // remove notification call ends up in not removing the notification.
   2074         mHandler.post(new Runnable() {
   2075             @Override
   2076             public void run() {
   2077                 EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, tag, userId,
   2078                         mustHaveFlags, mustNotHaveFlags);
   2079 
   2080                 synchronized (mNotificationList) {
   2081                     int index = indexOfNotificationLocked(pkg, tag, id, userId);
   2082                     if (index >= 0) {
   2083                         NotificationRecord r = mNotificationList.get(index);
   2084 
   2085                         if ((r.getNotification().flags & mustHaveFlags) != mustHaveFlags) {
   2086                             return;
   2087                         }
   2088                         if ((r.getNotification().flags & mustNotHaveFlags) != 0) {
   2089                             return;
   2090                         }
   2091 
   2092                         mNotificationList.remove(index);
   2093 
   2094                         cancelNotificationLocked(r, sendDelete);
   2095                         updateLightsLocked();
   2096                     }
   2097                 }
   2098             }
   2099         });
   2100     }
   2101 
   2102     /**
   2103      * Determine whether the userId applies to the notification in question, either because
   2104      * they match exactly, or one of them is USER_ALL (which is treated as a wildcard).
   2105      */
   2106     private boolean notificationMatchesUserId(NotificationRecord r, int userId) {
   2107         return
   2108                 // looking for USER_ALL notifications? match everything
   2109                    userId == UserHandle.USER_ALL
   2110                 // a notification sent to USER_ALL matches any query
   2111                 || r.getUserId() == UserHandle.USER_ALL
   2112                 // an exact user match
   2113                 || r.getUserId() == userId;
   2114     }
   2115 
   2116     /**
   2117      * Cancels all notifications from a given package that have all of the
   2118      * {@code mustHaveFlags}.
   2119      */
   2120     boolean cancelAllNotificationsInt(String pkg, int mustHaveFlags,
   2121             int mustNotHaveFlags, boolean doit, int userId) {
   2122         EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL_ALL, pkg, userId,
   2123                 mustHaveFlags, mustNotHaveFlags);
   2124 
   2125         synchronized (mNotificationList) {
   2126             final int N = mNotificationList.size();
   2127             boolean canceledSomething = false;
   2128             for (int i = N-1; i >= 0; --i) {
   2129                 NotificationRecord r = mNotificationList.get(i);
   2130                 if (!notificationMatchesUserId(r, userId)) {
   2131                     continue;
   2132                 }
   2133                 // Don't remove notifications to all, if there's no package name specified
   2134                 if (r.getUserId() == UserHandle.USER_ALL && pkg == null) {
   2135                     continue;
   2136                 }
   2137                 if ((r.getFlags() & mustHaveFlags) != mustHaveFlags) {
   2138                     continue;
   2139                 }
   2140                 if ((r.getFlags() & mustNotHaveFlags) != 0) {
   2141                     continue;
   2142                 }
   2143                 if (pkg != null && !r.sbn.getPackageName().equals(pkg)) {
   2144                     continue;
   2145                 }
   2146                 canceledSomething = true;
   2147                 if (!doit) {
   2148                     return true;
   2149                 }
   2150                 mNotificationList.remove(i);
   2151                 cancelNotificationLocked(r, false);
   2152             }
   2153             if (canceledSomething) {
   2154                 updateLightsLocked();
   2155             }
   2156             return canceledSomething;
   2157         }
   2158     }
   2159 
   2160     public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) {
   2161         checkCallerIsSystemOrSameApp(pkg);
   2162         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
   2163                 Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg);
   2164         // Don't allow client applications to cancel foreground service notis.
   2165         cancelNotification(pkg, tag, id, 0,
   2166                 Binder.getCallingUid() == Process.SYSTEM_UID
   2167                 ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId);
   2168     }
   2169 
   2170     public void cancelAllNotifications(String pkg, int userId) {
   2171         checkCallerIsSystemOrSameApp(pkg);
   2172 
   2173         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
   2174                 Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg);
   2175 
   2176         // Calling from user space, don't allow the canceling of actively
   2177         // running foreground services.
   2178         cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId);
   2179     }
   2180 
   2181     // Return true if the UID is a system or phone UID and therefore should not have
   2182     // any notifications or toasts blocked.
   2183     boolean isUidSystem(int uid) {
   2184         final int appid = UserHandle.getAppId(uid);
   2185         return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
   2186     }
   2187 
   2188     // same as isUidSystem(int, int) for the Binder caller's UID.
   2189     boolean isCallerSystem() {
   2190         return isUidSystem(Binder.getCallingUid());
   2191     }
   2192 
   2193     void checkCallerIsSystem() {
   2194         if (isCallerSystem()) {
   2195             return;
   2196         }
   2197         throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());
   2198     }
   2199 
   2200     void checkCallerIsSystemOrSameApp(String pkg) {
   2201         if (isCallerSystem()) {
   2202             return;
   2203         }
   2204         final int uid = Binder.getCallingUid();
   2205         try {
   2206             ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
   2207                     pkg, 0, UserHandle.getCallingUserId());
   2208             if (!UserHandle.isSameApp(ai.uid, uid)) {
   2209                 throw new SecurityException("Calling uid " + uid + " gave package"
   2210                         + pkg + " which is owned by uid " + ai.uid);
   2211             }
   2212         } catch (RemoteException re) {
   2213             throw new SecurityException("Unknown package " + pkg + "\n" + re);
   2214         }
   2215     }
   2216 
   2217     void cancelAll(int userId) {
   2218         synchronized (mNotificationList) {
   2219             final int N = mNotificationList.size();
   2220             for (int i=N-1; i>=0; i--) {
   2221                 NotificationRecord r = mNotificationList.get(i);
   2222 
   2223                 if (!notificationMatchesUserId(r, userId)) {
   2224                     continue;
   2225                 }
   2226 
   2227                 if ((r.getFlags() & (Notification.FLAG_ONGOING_EVENT
   2228                                 | Notification.FLAG_NO_CLEAR)) == 0) {
   2229                     mNotificationList.remove(i);
   2230                     cancelNotificationLocked(r, true);
   2231                 }
   2232             }
   2233 
   2234             updateLightsLocked();
   2235         }
   2236     }
   2237 
   2238     // lock on mNotificationList
   2239     private void updateLightsLocked()
   2240     {
   2241         // handle notification lights
   2242         if (mLedNotification == null) {
   2243             // get next notification, if any
   2244             int n = mLights.size();
   2245             if (n > 0) {
   2246                 mLedNotification = mLights.get(n-1);
   2247             }
   2248         }
   2249 
   2250         // Don't flash while we are in a call or screen is on
   2251         if (mLedNotification == null || mInCall || mScreenOn) {
   2252             mNotificationLight.turnOff();
   2253         } else {
   2254             final Notification ledno = mLedNotification.sbn.getNotification();
   2255             int ledARGB = ledno.ledARGB;
   2256             int ledOnMS = ledno.ledOnMS;
   2257             int ledOffMS = ledno.ledOffMS;
   2258             if ((ledno.defaults & Notification.DEFAULT_LIGHTS) != 0) {
   2259                 ledARGB = mDefaultNotificationColor;
   2260                 ledOnMS = mDefaultNotificationLedOn;
   2261                 ledOffMS = mDefaultNotificationLedOff;
   2262             }
   2263             if (mNotificationPulseEnabled) {
   2264                 // pulse repeatedly
   2265                 mNotificationLight.setFlashing(ledARGB, LightsService.LIGHT_FLASH_TIMED,
   2266                         ledOnMS, ledOffMS);
   2267             }
   2268         }
   2269     }
   2270 
   2271     // lock on mNotificationList
   2272     private int indexOfNotificationLocked(String pkg, String tag, int id, int userId)
   2273     {
   2274         ArrayList<NotificationRecord> list = mNotificationList;
   2275         final int len = list.size();
   2276         for (int i=0; i<len; i++) {
   2277             NotificationRecord r = list.get(i);
   2278             if (!notificationMatchesUserId(r, userId) || r.sbn.getId() != id) {
   2279                 continue;
   2280             }
   2281             if (tag == null) {
   2282                 if (r.sbn.getTag() != null) {
   2283                     continue;
   2284                 }
   2285             } else {
   2286                 if (!tag.equals(r.sbn.getTag())) {
   2287                     continue;
   2288                 }
   2289             }
   2290             if (r.sbn.getPackageName().equals(pkg)) {
   2291                 return i;
   2292             }
   2293         }
   2294         return -1;
   2295     }
   2296 
   2297     private void updateNotificationPulse() {
   2298         synchronized (mNotificationList) {
   2299             updateLightsLocked();
   2300         }
   2301     }
   2302 
   2303     // ======================================================================
   2304     @Override
   2305     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
   2306         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
   2307                 != PackageManager.PERMISSION_GRANTED) {
   2308             pw.println("Permission Denial: can't dump NotificationManager from from pid="
   2309                     + Binder.getCallingPid()
   2310                     + ", uid=" + Binder.getCallingUid());
   2311             return;
   2312         }
   2313 
   2314         pw.println("Current Notification Manager state:");
   2315 
   2316         pw.println("  Listeners (" + mEnabledListenersForCurrentUser.size()
   2317                 + ") enabled for current user:");
   2318         for (ComponentName cmpt : mEnabledListenersForCurrentUser) {
   2319             pw.println("    " + cmpt);
   2320         }
   2321 
   2322         pw.println("  Live listeners (" + mListeners.size() + "):");
   2323         for (NotificationListenerInfo info : mListeners) {
   2324             pw.println("    " + info.component
   2325                     + " (user " + info.userid + "): " + info.listener
   2326                     + (info.isSystem?" SYSTEM":""));
   2327         }
   2328 
   2329         int N;
   2330 
   2331         synchronized (mToastQueue) {
   2332             N = mToastQueue.size();
   2333             if (N > 0) {
   2334                 pw.println("  Toast Queue:");
   2335                 for (int i=0; i<N; i++) {
   2336                     mToastQueue.get(i).dump(pw, "    ");
   2337                 }
   2338                 pw.println("  ");
   2339             }
   2340 
   2341         }
   2342 
   2343         synchronized (mNotificationList) {
   2344             N = mNotificationList.size();
   2345             if (N > 0) {
   2346                 pw.println("  Notification List:");
   2347                 for (int i=0; i<N; i++) {
   2348                     mNotificationList.get(i).dump(pw, "    ", mContext);
   2349                 }
   2350                 pw.println("  ");
   2351             }
   2352 
   2353             N = mLights.size();
   2354             if (N > 0) {
   2355                 pw.println("  Lights List:");
   2356                 for (int i=0; i<N; i++) {
   2357                     pw.println("    " + mLights.get(i));
   2358                 }
   2359                 pw.println("  ");
   2360             }
   2361 
   2362             pw.println("  mSoundNotification=" + mSoundNotification);
   2363             pw.println("  mVibrateNotification=" + mVibrateNotification);
   2364             pw.println("  mDisabledNotifications=0x" + Integer.toHexString(mDisabledNotifications));
   2365             pw.println("  mSystemReady=" + mSystemReady);
   2366             pw.println("  mArchive=" + mArchive.toString());
   2367             Iterator<StatusBarNotification> iter = mArchive.descendingIterator();
   2368             int i=0;
   2369             while (iter.hasNext()) {
   2370                 pw.println("    " + iter.next());
   2371                 if (++i >= 5) {
   2372                     if (iter.hasNext()) pw.println("    ...");
   2373                     break;
   2374                 }
   2375             }
   2376 
   2377         }
   2378     }
   2379 }
   2380