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