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