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