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