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