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