NotificationManagerService.java revision 39606a007a5b1309dd000234f2b8cf156c49fd0f
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;
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.ActivityManagerNative;
24import android.app.IActivityManager;
25import android.app.INotificationManager;
26import android.app.ITransientNotification;
27import android.app.Notification;
28import android.app.PendingIntent;
29import android.app.StatusBarManager;
30import android.content.BroadcastReceiver;
31import android.content.ContentResolver;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.pm.ApplicationInfo;
36import android.content.pm.PackageManager;
37import android.content.pm.PackageManager.NameNotFoundException;
38import android.content.res.Resources;
39import android.database.ContentObserver;
40import android.media.AudioManager;
41import android.media.IAudioService;
42import android.media.IRingtonePlayer;
43import android.net.Uri;
44import android.os.Binder;
45import android.os.Handler;
46import android.os.IBinder;
47import android.os.Message;
48import android.os.Process;
49import android.os.RemoteException;
50import android.os.ServiceManager;
51import android.os.UserId;
52import android.os.Vibrator;
53import android.provider.Settings;
54import android.telephony.TelephonyManager;
55import android.text.TextUtils;
56import android.util.AtomicFile;
57import android.util.EventLog;
58import android.util.Log;
59import android.util.Slog;
60import android.util.Xml;
61import android.view.accessibility.AccessibilityEvent;
62import android.view.accessibility.AccessibilityManager;
63import android.widget.Toast;
64
65import com.android.internal.statusbar.StatusBarNotification;
66import com.android.internal.util.FastXmlSerializer;
67
68import org.xmlpull.v1.XmlPullParser;
69import org.xmlpull.v1.XmlPullParserException;
70import org.xmlpull.v1.XmlSerializer;
71
72import java.io.File;
73import java.io.FileDescriptor;
74import java.io.FileInputStream;
75import java.io.FileNotFoundException;
76import java.io.FileOutputStream;
77import java.io.IOException;
78import java.io.PrintWriter;
79import java.util.ArrayList;
80import java.util.Arrays;
81import java.util.HashSet;
82
83import libcore.io.IoUtils;
84
85
86/** {@hide} */
87public class NotificationManagerService extends INotificationManager.Stub
88{
89    private static final String TAG = "NotificationService";
90    private static final boolean DBG = false;
91
92    private static final int MAX_PACKAGE_NOTIFICATIONS = 50;
93
94    // message codes
95    private static final int MESSAGE_TIMEOUT = 2;
96
97    private static final int LONG_DELAY = 3500; // 3.5 seconds
98    private static final int SHORT_DELAY = 2000; // 2 seconds
99
100    private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
101
102    private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
103    private static final boolean SCORE_ONGOING_HIGHER = false;
104
105    private static final int JUNK_SCORE = -1000;
106    private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10;
107    private static final int SCORE_DISPLAY_THRESHOLD = Notification.PRIORITY_MIN * NOTIFICATION_PRIORITY_MULTIPLIER;
108
109    private static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
110    private static final boolean ENABLE_BLOCKED_TOASTS = true;
111
112    final Context mContext;
113    final IActivityManager mAm;
114    final IBinder mForegroundToken = new Binder();
115
116    private WorkerHandler mHandler;
117    private StatusBarManagerService mStatusBar;
118    private LightsService.Light mNotificationLight;
119    private LightsService.Light mAttentionLight;
120
121    private int mDefaultNotificationColor;
122    private int mDefaultNotificationLedOn;
123    private int mDefaultNotificationLedOff;
124
125    private boolean mSystemReady;
126    private int mDisabledNotifications;
127
128    private NotificationRecord mSoundNotification;
129    private NotificationRecord mVibrateNotification;
130
131    private IAudioService mAudioService;
132    private Vibrator mVibrator;
133
134    // for enabling and disabling notification pulse behavior
135    private boolean mScreenOn = true;
136    private boolean mInCall = false;
137    private boolean mNotificationPulseEnabled;
138
139    private final ArrayList<NotificationRecord> mNotificationList =
140            new ArrayList<NotificationRecord>();
141
142    private ArrayList<ToastRecord> mToastQueue;
143
144    private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
145    private NotificationRecord mLedNotification;
146
147    // Notification control database. For now just contains disabled packages.
148    private AtomicFile mPolicyFile;
149    private HashSet<String> mBlockedPackages = new HashSet<String>();
150
151    private static final int DB_VERSION = 1;
152
153    private static final String TAG_BODY = "notification-policy";
154    private static final String ATTR_VERSION = "version";
155
156    private static final String TAG_BLOCKED_PKGS = "blocked-packages";
157    private static final String TAG_PACKAGE = "package";
158    private static final String ATTR_NAME = "name";
159
160    private void loadBlockDb() {
161        synchronized(mBlockedPackages) {
162            if (mPolicyFile == null) {
163                File dir = new File("/data/system");
164                mPolicyFile = new AtomicFile(new File(dir, "notification_policy.xml"));
165
166                mBlockedPackages.clear();
167
168                FileInputStream infile = null;
169                try {
170                    infile = mPolicyFile.openRead();
171                    final XmlPullParser parser = Xml.newPullParser();
172                    parser.setInput(infile, null);
173
174                    int type;
175                    String tag;
176                    int version = DB_VERSION;
177                    while ((type = parser.next()) != END_DOCUMENT) {
178                        tag = parser.getName();
179                        if (type == START_TAG) {
180                            if (TAG_BODY.equals(tag)) {
181                                version = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));
182                            } else if (TAG_BLOCKED_PKGS.equals(tag)) {
183                                while ((type = parser.next()) != END_DOCUMENT) {
184                                    tag = parser.getName();
185                                    if (TAG_PACKAGE.equals(tag)) {
186                                        mBlockedPackages.add(parser.getAttributeValue(null, ATTR_NAME));
187                                    } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
188                                        break;
189                                    }
190                                }
191                            }
192                        }
193                    }
194                } catch (FileNotFoundException e) {
195                    // No data yet
196                } catch (IOException e) {
197                    Log.wtf(TAG, "Unable to read blocked notifications database", e);
198                } catch (NumberFormatException e) {
199                    Log.wtf(TAG, "Unable to parse blocked notifications database", e);
200                } catch (XmlPullParserException e) {
201                    Log.wtf(TAG, "Unable to parse blocked notifications database", e);
202                } finally {
203                    IoUtils.closeQuietly(infile);
204                }
205            }
206        }
207    }
208
209    private void writeBlockDb() {
210        synchronized(mBlockedPackages) {
211            FileOutputStream outfile = null;
212            try {
213                outfile = mPolicyFile.startWrite();
214
215                XmlSerializer out = new FastXmlSerializer();
216                out.setOutput(outfile, "utf-8");
217
218                out.startDocument(null, true);
219
220                out.startTag(null, TAG_BODY); {
221                    out.attribute(null, ATTR_VERSION, String.valueOf(DB_VERSION));
222                    out.startTag(null, TAG_BLOCKED_PKGS); {
223                        // write all known network policies
224                        for (String pkg : mBlockedPackages) {
225                            out.startTag(null, TAG_PACKAGE); {
226                                out.attribute(null, ATTR_NAME, pkg);
227                            } out.endTag(null, TAG_PACKAGE);
228                        }
229                    } out.endTag(null, TAG_BLOCKED_PKGS);
230                } out.endTag(null, TAG_BODY);
231
232                out.endDocument();
233
234                mPolicyFile.finishWrite(outfile);
235            } catch (IOException e) {
236                if (outfile != null) {
237                    mPolicyFile.failWrite(outfile);
238                }
239            }
240        }
241    }
242
243    public boolean areNotificationsEnabledForPackage(String pkg) {
244        checkCallerIsSystem();
245        return areNotificationsEnabledForPackageInt(pkg);
246    }
247
248    // Unchecked. Not exposed via Binder, but can be called in the course of enqueue*().
249    private boolean areNotificationsEnabledForPackageInt(String pkg) {
250        final boolean enabled = !mBlockedPackages.contains(pkg);
251        if (DBG) {
252            Slog.v(TAG, "notifications are " + (enabled?"en":"dis") + "abled for " + pkg);
253        }
254        return enabled;
255    }
256
257    public void setNotificationsEnabledForPackage(String pkg, boolean enabled) {
258        checkCallerIsSystem();
259        if (DBG) {
260            Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
261        }
262        if (enabled) {
263            mBlockedPackages.remove(pkg);
264        } else {
265            mBlockedPackages.add(pkg);
266
267            // Now, cancel any outstanding notifications that are part of a just-disabled app
268            if (ENABLE_BLOCKED_NOTIFICATIONS) {
269                synchronized (mNotificationList) {
270                    final int N = mNotificationList.size();
271                    for (int i=0; i<N; i++) {
272                        final NotificationRecord r = mNotificationList.get(i);
273                        if (r.pkg.equals(pkg)) {
274                            cancelNotificationLocked(r, false);
275                        }
276                    }
277                }
278            }
279            // Don't bother canceling toasts, they'll go away soon enough.
280        }
281        writeBlockDb();
282    }
283
284
285    private static String idDebugString(Context baseContext, String packageName, int id) {
286        Context c = null;
287
288        if (packageName != null) {
289            try {
290                c = baseContext.createPackageContext(packageName, 0);
291            } catch (NameNotFoundException e) {
292                c = baseContext;
293            }
294        } else {
295            c = baseContext;
296        }
297
298        String pkg;
299        String type;
300        String name;
301
302        Resources r = c.getResources();
303        try {
304            return r.getResourceName(id);
305        } catch (Resources.NotFoundException e) {
306            return "<name unknown>";
307        }
308    }
309
310    private static final class NotificationRecord
311    {
312        final String pkg;
313        final String tag;
314        final int id;
315        final int uid;
316        final int initialPid;
317        final Notification notification;
318        final int score;
319        IBinder statusBarKey;
320
321        NotificationRecord(String pkg, String tag, int id, int uid, int initialPid, int score, Notification notification)
322        {
323            this.pkg = pkg;
324            this.tag = tag;
325            this.id = id;
326            this.uid = uid;
327            this.initialPid = initialPid;
328            this.score = score;
329            this.notification = notification;
330        }
331
332        void dump(PrintWriter pw, String prefix, Context baseContext) {
333            pw.println(prefix + this);
334            pw.println(prefix + "  icon=0x" + Integer.toHexString(notification.icon)
335                    + " / " + idDebugString(baseContext, this.pkg, notification.icon));
336            pw.println(prefix + "  pri=" + notification.priority);
337            pw.println(prefix + "  score=" + this.score);
338            pw.println(prefix + "  contentIntent=" + notification.contentIntent);
339            pw.println(prefix + "  deleteIntent=" + notification.deleteIntent);
340            pw.println(prefix + "  tickerText=" + notification.tickerText);
341            pw.println(prefix + "  contentView=" + notification.contentView);
342            pw.println(prefix + "  uid=" + uid);
343            pw.println(prefix + "  defaults=0x" + Integer.toHexString(notification.defaults));
344            pw.println(prefix + "  flags=0x" + Integer.toHexString(notification.flags));
345            pw.println(prefix + "  sound=" + notification.sound);
346            pw.println(prefix + "  vibrate=" + Arrays.toString(notification.vibrate));
347            pw.println(prefix + "  ledARGB=0x" + Integer.toHexString(notification.ledARGB)
348                    + " ledOnMS=" + notification.ledOnMS
349                    + " ledOffMS=" + notification.ledOffMS);
350        }
351
352        @Override
353        public final String toString()
354        {
355            return "NotificationRecord{"
356                + Integer.toHexString(System.identityHashCode(this))
357                + " pkg=" + pkg
358                + " id=" + Integer.toHexString(id)
359                + " tag=" + tag
360                + " score=" + score
361                + "}";
362        }
363    }
364
365    private static final class ToastRecord
366    {
367        final int pid;
368        final String pkg;
369        final ITransientNotification callback;
370        int duration;
371
372        ToastRecord(int pid, String pkg, ITransientNotification callback, int duration)
373        {
374            this.pid = pid;
375            this.pkg = pkg;
376            this.callback = callback;
377            this.duration = duration;
378        }
379
380        void update(int duration) {
381            this.duration = duration;
382        }
383
384        void dump(PrintWriter pw, String prefix) {
385            pw.println(prefix + this);
386        }
387
388        @Override
389        public final String toString()
390        {
391            return "ToastRecord{"
392                + Integer.toHexString(System.identityHashCode(this))
393                + " pkg=" + pkg
394                + " callback=" + callback
395                + " duration=" + duration;
396        }
397    }
398
399    private StatusBarManagerService.NotificationCallbacks mNotificationCallbacks
400            = new StatusBarManagerService.NotificationCallbacks() {
401
402        public void onSetDisabled(int status) {
403            synchronized (mNotificationList) {
404                mDisabledNotifications = status;
405                if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
406                    // cancel whatever's going on
407                    long identity = Binder.clearCallingIdentity();
408                    try {
409                        final IRingtonePlayer player = mAudioService.getRingtonePlayer();
410                        if (player != null) {
411                            player.stopAsync();
412                        }
413                    } catch (RemoteException e) {
414                    } finally {
415                        Binder.restoreCallingIdentity(identity);
416                    }
417
418                    identity = Binder.clearCallingIdentity();
419                    try {
420                        mVibrator.cancel();
421                    } finally {
422                        Binder.restoreCallingIdentity(identity);
423                    }
424                }
425            }
426        }
427
428        public void onClearAll() {
429            cancelAll();
430        }
431
432        public void onNotificationClick(String pkg, String tag, int id) {
433            cancelNotification(pkg, tag, id, Notification.FLAG_AUTO_CANCEL,
434                    Notification.FLAG_FOREGROUND_SERVICE, false);
435        }
436
437        public void onNotificationClear(String pkg, String tag, int id) {
438            cancelNotification(pkg, tag, id, 0,
439                Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
440                true);
441        }
442
443        public void onPanelRevealed() {
444            synchronized (mNotificationList) {
445                // sound
446                mSoundNotification = null;
447
448                long identity = Binder.clearCallingIdentity();
449                try {
450                    final IRingtonePlayer player = mAudioService.getRingtonePlayer();
451                    if (player != null) {
452                        player.stopAsync();
453                    }
454                } catch (RemoteException e) {
455                } finally {
456                    Binder.restoreCallingIdentity(identity);
457                }
458
459                // vibrate
460                mVibrateNotification = null;
461                identity = Binder.clearCallingIdentity();
462                try {
463                    mVibrator.cancel();
464                } finally {
465                    Binder.restoreCallingIdentity(identity);
466                }
467
468                // light
469                mLights.clear();
470                mLedNotification = null;
471                updateLightsLocked();
472            }
473        }
474
475        public void onNotificationError(String pkg, String tag, int id,
476                int uid, int initialPid, String message) {
477            Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id
478                    + "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")");
479            cancelNotification(pkg, tag, id, 0, 0, false);
480            long ident = Binder.clearCallingIdentity();
481            try {
482                ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg,
483                        "Bad notification posted from package " + pkg
484                        + ": " + message);
485            } catch (RemoteException e) {
486            }
487            Binder.restoreCallingIdentity(ident);
488        }
489    };
490
491    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
492        @Override
493        public void onReceive(Context context, Intent intent) {
494            String action = intent.getAction();
495
496            boolean queryRestart = false;
497            boolean packageChanged = false;
498
499            if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
500                    || action.equals(Intent.ACTION_PACKAGE_RESTARTED)
501                    || (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED))
502                    || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
503                    || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
504                String pkgList[] = null;
505                if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
506                    pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
507                } else if (queryRestart) {
508                    pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
509                } else {
510                    Uri uri = intent.getData();
511                    if (uri == null) {
512                        return;
513                    }
514                    String pkgName = uri.getSchemeSpecificPart();
515                    if (pkgName == null) {
516                        return;
517                    }
518                    if (packageChanged) {
519                        // We cancel notifications for packages which have just been disabled
520                        final int enabled = mContext.getPackageManager()
521                                .getApplicationEnabledSetting(pkgName);
522                        if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
523                                || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
524                            return;
525                        }
526                    }
527                    pkgList = new String[]{pkgName};
528                }
529                if (pkgList != null && (pkgList.length > 0)) {
530                    for (String pkgName : pkgList) {
531                        cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart);
532                    }
533                }
534            } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
535                // Keep track of screen on/off state, but do not turn off the notification light
536                // until user passes through the lock screen or views the notification.
537                mScreenOn = true;
538            } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
539                mScreenOn = false;
540            } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
541                mInCall = (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(
542                        TelephonyManager.EXTRA_STATE_OFFHOOK));
543                updateNotificationPulse();
544            } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
545                // turn off LED when user passes through lock screen
546                mNotificationLight.turnOff();
547            }
548        }
549    };
550
551    class SettingsObserver extends ContentObserver {
552        SettingsObserver(Handler handler) {
553            super(handler);
554        }
555
556        void observe() {
557            ContentResolver resolver = mContext.getContentResolver();
558            resolver.registerContentObserver(Settings.System.getUriFor(
559                    Settings.System.NOTIFICATION_LIGHT_PULSE), false, this);
560            update();
561        }
562
563        @Override public void onChange(boolean selfChange) {
564            update();
565        }
566
567        public void update() {
568            ContentResolver resolver = mContext.getContentResolver();
569            boolean pulseEnabled = Settings.System.getInt(resolver,
570                        Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
571            if (mNotificationPulseEnabled != pulseEnabled) {
572                mNotificationPulseEnabled = pulseEnabled;
573                updateNotificationPulse();
574            }
575        }
576    }
577
578    NotificationManagerService(Context context, StatusBarManagerService statusBar,
579            LightsService lights)
580    {
581        super();
582        mContext = context;
583        mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
584        mAm = ActivityManagerNative.getDefault();
585        mToastQueue = new ArrayList<ToastRecord>();
586        mHandler = new WorkerHandler();
587
588        loadBlockDb();
589
590        mStatusBar = statusBar;
591        statusBar.setNotificationCallbacks(mNotificationCallbacks);
592
593        mNotificationLight = lights.getLight(LightsService.LIGHT_ID_NOTIFICATIONS);
594        mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION);
595
596        Resources resources = mContext.getResources();
597        mDefaultNotificationColor = resources.getColor(
598                com.android.internal.R.color.config_defaultNotificationColor);
599        mDefaultNotificationLedOn = resources.getInteger(
600                com.android.internal.R.integer.config_defaultNotificationLedOn);
601        mDefaultNotificationLedOff = resources.getInteger(
602                com.android.internal.R.integer.config_defaultNotificationLedOff);
603
604        // Don't start allowing notifications until the setup wizard has run once.
605        // After that, including subsequent boots, init with notifications turned on.
606        // This works on the first boot because the setup wizard will toggle this
607        // flag at least once and we'll go back to 0 after that.
608        if (0 == Settings.Secure.getInt(mContext.getContentResolver(),
609                    Settings.Secure.DEVICE_PROVISIONED, 0)) {
610            mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
611        }
612
613        // register for various Intents
614        IntentFilter filter = new IntentFilter();
615        filter.addAction(Intent.ACTION_SCREEN_ON);
616        filter.addAction(Intent.ACTION_SCREEN_OFF);
617        filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
618        filter.addAction(Intent.ACTION_USER_PRESENT);
619        mContext.registerReceiver(mIntentReceiver, filter);
620        IntentFilter pkgFilter = new IntentFilter();
621        pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
622        pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
623        pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
624        pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
625        pkgFilter.addDataScheme("package");
626        mContext.registerReceiver(mIntentReceiver, pkgFilter);
627        IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
628        mContext.registerReceiver(mIntentReceiver, sdFilter);
629
630        SettingsObserver observer = new SettingsObserver(mHandler);
631        observer.observe();
632    }
633
634    void systemReady() {
635        mAudioService = IAudioService.Stub.asInterface(
636                ServiceManager.getService(Context.AUDIO_SERVICE));
637
638        // no beeping until we're basically done booting
639        mSystemReady = true;
640    }
641
642    // Toasts
643    // ============================================================================
644    public void enqueueToast(String pkg, ITransientNotification callback, int duration)
645    {
646        if (DBG) Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration);
647
648        if (pkg == null || callback == null) {
649            Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
650            return ;
651        }
652
653        final boolean isSystemToast = ("android".equals(pkg));
654
655        if (ENABLE_BLOCKED_TOASTS && !isSystemToast && !areNotificationsEnabledForPackageInt(pkg)) {
656            Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
657            return;
658        }
659
660        synchronized (mToastQueue) {
661            int callingPid = Binder.getCallingPid();
662            long callingId = Binder.clearCallingIdentity();
663            try {
664                ToastRecord record;
665                int index = indexOfToastLocked(pkg, callback);
666                // If it's already in the queue, we update it in place, we don't
667                // move it to the end of the queue.
668                if (index >= 0) {
669                    record = mToastQueue.get(index);
670                    record.update(duration);
671                } else {
672                    // Limit the number of toasts that any given package except the android
673                    // package can enqueue.  Prevents DOS attacks and deals with leaks.
674                    if (!isSystemToast) {
675                        int count = 0;
676                        final int N = mToastQueue.size();
677                        for (int i=0; i<N; i++) {
678                             final ToastRecord r = mToastQueue.get(i);
679                             if (r.pkg.equals(pkg)) {
680                                 count++;
681                                 if (count >= MAX_PACKAGE_NOTIFICATIONS) {
682                                     Slog.e(TAG, "Package has already posted " + count
683                                            + " toasts. Not showing more. Package=" + pkg);
684                                     return;
685                                 }
686                             }
687                        }
688                    }
689
690                    record = new ToastRecord(callingPid, pkg, callback, duration);
691                    mToastQueue.add(record);
692                    index = mToastQueue.size() - 1;
693                    keepProcessAliveLocked(callingPid);
694                }
695                // If it's at index 0, it's the current toast.  It doesn't matter if it's
696                // new or just been updated.  Call back and tell it to show itself.
697                // If the callback fails, this will remove it from the list, so don't
698                // assume that it's valid after this.
699                if (index == 0) {
700                    showNextToastLocked();
701                }
702            } finally {
703                Binder.restoreCallingIdentity(callingId);
704            }
705        }
706    }
707
708    public void cancelToast(String pkg, ITransientNotification callback) {
709        Slog.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback);
710
711        if (pkg == null || callback == null) {
712            Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback);
713            return ;
714        }
715
716        synchronized (mToastQueue) {
717            long callingId = Binder.clearCallingIdentity();
718            try {
719                int index = indexOfToastLocked(pkg, callback);
720                if (index >= 0) {
721                    cancelToastLocked(index);
722                } else {
723                    Slog.w(TAG, "Toast already cancelled. pkg=" + pkg + " callback=" + callback);
724                }
725            } finally {
726                Binder.restoreCallingIdentity(callingId);
727            }
728        }
729    }
730
731    private void showNextToastLocked() {
732        ToastRecord record = mToastQueue.get(0);
733        while (record != null) {
734            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
735            try {
736                record.callback.show();
737                scheduleTimeoutLocked(record, false);
738                return;
739            } catch (RemoteException e) {
740                Slog.w(TAG, "Object died trying to show notification " + record.callback
741                        + " in package " + record.pkg);
742                // remove it from the list and let the process die
743                int index = mToastQueue.indexOf(record);
744                if (index >= 0) {
745                    mToastQueue.remove(index);
746                }
747                keepProcessAliveLocked(record.pid);
748                if (mToastQueue.size() > 0) {
749                    record = mToastQueue.get(0);
750                } else {
751                    record = null;
752                }
753            }
754        }
755    }
756
757    private void cancelToastLocked(int index) {
758        ToastRecord record = mToastQueue.get(index);
759        try {
760            record.callback.hide();
761        } catch (RemoteException e) {
762            Slog.w(TAG, "Object died trying to hide notification " + record.callback
763                    + " in package " + record.pkg);
764            // don't worry about this, we're about to remove it from
765            // the list anyway
766        }
767        mToastQueue.remove(index);
768        keepProcessAliveLocked(record.pid);
769        if (mToastQueue.size() > 0) {
770            // Show the next one. If the callback fails, this will remove
771            // it from the list, so don't assume that the list hasn't changed
772            // after this point.
773            showNextToastLocked();
774        }
775    }
776
777    private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)
778    {
779        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
780        long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
781        mHandler.removeCallbacksAndMessages(r);
782        mHandler.sendMessageDelayed(m, delay);
783    }
784
785    private void handleTimeout(ToastRecord record)
786    {
787        if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
788        synchronized (mToastQueue) {
789            int index = indexOfToastLocked(record.pkg, record.callback);
790            if (index >= 0) {
791                cancelToastLocked(index);
792            }
793        }
794    }
795
796    // lock on mToastQueue
797    private int indexOfToastLocked(String pkg, ITransientNotification callback)
798    {
799        IBinder cbak = callback.asBinder();
800        ArrayList<ToastRecord> list = mToastQueue;
801        int len = list.size();
802        for (int i=0; i<len; i++) {
803            ToastRecord r = list.get(i);
804            if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
805                return i;
806            }
807        }
808        return -1;
809    }
810
811    // lock on mToastQueue
812    private void keepProcessAliveLocked(int pid)
813    {
814        int toastCount = 0; // toasts from this pid
815        ArrayList<ToastRecord> list = mToastQueue;
816        int N = list.size();
817        for (int i=0; i<N; i++) {
818            ToastRecord r = list.get(i);
819            if (r.pid == pid) {
820                toastCount++;
821            }
822        }
823        try {
824            mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
825        } catch (RemoteException e) {
826            // Shouldn't happen.
827        }
828    }
829
830    private final class WorkerHandler extends Handler
831    {
832        @Override
833        public void handleMessage(Message msg)
834        {
835            switch (msg.what)
836            {
837                case MESSAGE_TIMEOUT:
838                    handleTimeout((ToastRecord)msg.obj);
839                    break;
840            }
841        }
842    }
843
844
845    // Notifications
846    // ============================================================================
847    @Deprecated
848    public void enqueueNotification(String pkg, int id, Notification notification, int[] idOut)
849    {
850        enqueueNotificationWithTag(pkg, null /* tag */, id, notification, idOut);
851    }
852
853    public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification,
854            int[] idOut)
855    {
856        enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(),
857                tag, id, notification, idOut);
858    }
859
860    private final static int clamp(int x, int low, int high) {
861        return (x < low) ? low : ((x > high) ? high : x);
862    }
863
864
865    // Not exposed via Binder; for system use only (otherwise malicious apps could spoof the
866    // uid/pid of another application)
867    public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
868            String tag, int id, Notification notification, int[] idOut)
869    {
870        if (DBG) {
871            Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
872        }
873        checkCallerIsSystemOrSameApp(pkg);
874        final boolean isSystemNotification = ("android".equals(pkg));
875
876        // Limit the number of notifications that any given package except the android
877        // package can enqueue.  Prevents DOS attacks and deals with leaks.
878        if (!isSystemNotification) {
879            synchronized (mNotificationList) {
880                int count = 0;
881                final int N = mNotificationList.size();
882                for (int i=0; i<N; i++) {
883                    final NotificationRecord r = mNotificationList.get(i);
884                    if (r.pkg.equals(pkg)) {
885                        count++;
886                        if (count >= MAX_PACKAGE_NOTIFICATIONS) {
887                            Slog.e(TAG, "Package has already posted " + count
888                                    + " notifications.  Not showing more.  package=" + pkg);
889                            return;
890                        }
891                    }
892                }
893            }
894        }
895
896        // This conditional is a dirty hack to limit the logging done on
897        //     behalf of the download manager without affecting other apps.
898        if (!pkg.equals("com.android.providers.downloads")
899                || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
900            EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag,
901                    notification.toString());
902        }
903
904        if (pkg == null || notification == null) {
905            throw new IllegalArgumentException("null not allowed: pkg=" + pkg
906                    + " id=" + id + " notification=" + notification);
907        }
908        if (notification.icon != 0) {
909            if (notification.contentView == null) {
910                throw new IllegalArgumentException("contentView required: pkg=" + pkg
911                        + " id=" + id + " notification=" + notification);
912            }
913        }
914
915        // === Scoring ===
916
917        // 0. Sanitize inputs
918        notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, Notification.PRIORITY_MAX);
919        // Migrate notification flags to scores
920        if (0 != (notification.flags & Notification.FLAG_HIGH_PRIORITY)) {
921            if (notification.priority < Notification.PRIORITY_MAX) notification.priority = Notification.PRIORITY_MAX;
922        } else if (SCORE_ONGOING_HIGHER && 0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) {
923            if (notification.priority < Notification.PRIORITY_HIGH) notification.priority = Notification.PRIORITY_HIGH;
924        }
925
926        // 1. initial score: buckets of 10, around the app
927        int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20]
928
929        // 2. Consult external heuristics (TBD)
930
931        // 3. Apply local rules
932
933        // blocked apps
934        if (ENABLE_BLOCKED_NOTIFICATIONS && !isSystemNotification && !areNotificationsEnabledForPackageInt(pkg)) {
935            score = JUNK_SCORE;
936            Slog.e(TAG, "Suppressing notification from package " + pkg + " by user request.");
937        }
938
939        if (DBG) {
940            Slog.v(TAG, "Assigned score=" + score + " to " + notification);
941        }
942
943        if (score < SCORE_DISPLAY_THRESHOLD) {
944            // Notification will be blocked because the score is too low.
945            return;
946        }
947
948        synchronized (mNotificationList) {
949            NotificationRecord r = new NotificationRecord(pkg, tag, id,
950                    callingUid, callingPid,
951                    score,
952                    notification);
953            NotificationRecord old = null;
954
955            int index = indexOfNotificationLocked(pkg, tag, id);
956            if (index < 0) {
957                mNotificationList.add(r);
958            } else {
959                old = mNotificationList.remove(index);
960                mNotificationList.add(index, r);
961                // Make sure we don't lose the foreground service state.
962                if (old != null) {
963                    notification.flags |=
964                        old.notification.flags&Notification.FLAG_FOREGROUND_SERVICE;
965                }
966            }
967
968            // Ensure if this is a foreground service that the proper additional
969            // flags are set.
970            if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) {
971                notification.flags |= Notification.FLAG_ONGOING_EVENT
972                        | Notification.FLAG_NO_CLEAR;
973            }
974
975            if (notification.icon != 0) {
976                StatusBarNotification n = new StatusBarNotification(pkg, id, tag,
977                        r.uid, r.initialPid, score, notification);
978                if (old != null && old.statusBarKey != null) {
979                    r.statusBarKey = old.statusBarKey;
980                    long identity = Binder.clearCallingIdentity();
981                    try {
982                        mStatusBar.updateNotification(r.statusBarKey, n);
983                    }
984                    finally {
985                        Binder.restoreCallingIdentity(identity);
986                    }
987                } else {
988                    long identity = Binder.clearCallingIdentity();
989                    try {
990                        r.statusBarKey = mStatusBar.addNotification(n);
991                        if ((n.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
992                            mAttentionLight.pulse();
993                        }
994                    }
995                    finally {
996                        Binder.restoreCallingIdentity(identity);
997                    }
998                }
999                sendAccessibilityEvent(notification, pkg);
1000            } else {
1001                Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
1002                if (old != null && old.statusBarKey != null) {
1003                    long identity = Binder.clearCallingIdentity();
1004                    try {
1005                        mStatusBar.removeNotification(old.statusBarKey);
1006                    }
1007                    finally {
1008                        Binder.restoreCallingIdentity(identity);
1009                    }
1010                }
1011            }
1012
1013            // If we're not supposed to beep, vibrate, etc. then don't.
1014            if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
1015                    && (!(old != null
1016                        && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
1017                    && mSystemReady) {
1018
1019                final AudioManager audioManager = (AudioManager) mContext
1020                .getSystemService(Context.AUDIO_SERVICE);
1021                // sound
1022                final boolean useDefaultSound =
1023                    (notification.defaults & Notification.DEFAULT_SOUND) != 0;
1024                if (useDefaultSound || notification.sound != null) {
1025                    Uri uri;
1026                    if (useDefaultSound) {
1027                        uri = Settings.System.DEFAULT_NOTIFICATION_URI;
1028                    } else {
1029                        uri = notification.sound;
1030                    }
1031                    boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
1032                    int audioStreamType;
1033                    if (notification.audioStreamType >= 0) {
1034                        audioStreamType = notification.audioStreamType;
1035                    } else {
1036                        audioStreamType = DEFAULT_STREAM_TYPE;
1037                    }
1038                    mSoundNotification = r;
1039                    // do not play notifications if stream volume is 0
1040                    // (typically because ringer mode is silent).
1041                    if (audioManager.getStreamVolume(audioStreamType) != 0) {
1042                        final long identity = Binder.clearCallingIdentity();
1043                        try {
1044                            final IRingtonePlayer player = mAudioService.getRingtonePlayer();
1045                            if (player != null) {
1046                                player.playAsync(uri, looping, audioStreamType);
1047                            }
1048                        } catch (RemoteException e) {
1049                        } finally {
1050                            Binder.restoreCallingIdentity(identity);
1051                        }
1052                    }
1053                }
1054
1055                // vibrate
1056                final boolean useDefaultVibrate =
1057                    (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
1058                if ((useDefaultVibrate || notification.vibrate != null)
1059                        && !(audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT)) {
1060                    mVibrateNotification = r;
1061
1062                    mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN
1063                                                        : notification.vibrate,
1064                              ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
1065                }
1066            }
1067
1068            // this option doesn't shut off the lights
1069
1070            // light
1071            // the most recent thing gets the light
1072            mLights.remove(old);
1073            if (mLedNotification == old) {
1074                mLedNotification = null;
1075            }
1076            //Slog.i(TAG, "notification.lights="
1077            //        + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
1078            if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
1079                mLights.add(r);
1080                updateLightsLocked();
1081            } else {
1082                if (old != null
1083                        && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
1084                    updateLightsLocked();
1085                }
1086            }
1087        }
1088
1089        idOut[0] = id;
1090    }
1091
1092    private void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
1093        AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
1094        if (!manager.isEnabled()) {
1095            return;
1096        }
1097
1098        AccessibilityEvent event =
1099            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
1100        event.setPackageName(packageName);
1101        event.setClassName(Notification.class.getName());
1102        event.setParcelableData(notification);
1103        CharSequence tickerText = notification.tickerText;
1104        if (!TextUtils.isEmpty(tickerText)) {
1105            event.getText().add(tickerText);
1106        }
1107
1108        manager.sendAccessibilityEvent(event);
1109    }
1110
1111    private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete) {
1112        // tell the app
1113        if (sendDelete) {
1114            if (r.notification.deleteIntent != null) {
1115                try {
1116                    r.notification.deleteIntent.send();
1117                } catch (PendingIntent.CanceledException ex) {
1118                    // do nothing - there's no relevant way to recover, and
1119                    //     no reason to let this propagate
1120                    Slog.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
1121                }
1122            }
1123        }
1124
1125        // status bar
1126        if (r.notification.icon != 0) {
1127            long identity = Binder.clearCallingIdentity();
1128            try {
1129                mStatusBar.removeNotification(r.statusBarKey);
1130            }
1131            finally {
1132                Binder.restoreCallingIdentity(identity);
1133            }
1134            r.statusBarKey = null;
1135        }
1136
1137        // sound
1138        if (mSoundNotification == r) {
1139            mSoundNotification = null;
1140            final long identity = Binder.clearCallingIdentity();
1141            try {
1142                final IRingtonePlayer player = mAudioService.getRingtonePlayer();
1143                if (player != null) {
1144                    player.stopAsync();
1145                }
1146            } catch (RemoteException e) {
1147            } finally {
1148                Binder.restoreCallingIdentity(identity);
1149            }
1150        }
1151
1152        // vibrate
1153        if (mVibrateNotification == r) {
1154            mVibrateNotification = null;
1155            long identity = Binder.clearCallingIdentity();
1156            try {
1157                mVibrator.cancel();
1158            }
1159            finally {
1160                Binder.restoreCallingIdentity(identity);
1161            }
1162        }
1163
1164        // light
1165        mLights.remove(r);
1166        if (mLedNotification == r) {
1167            mLedNotification = null;
1168        }
1169    }
1170
1171    /**
1172     * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}
1173     * and none of the {@code mustNotHaveFlags}.
1174     */
1175    private void cancelNotification(String pkg, String tag, int id, int mustHaveFlags,
1176            int mustNotHaveFlags, boolean sendDelete) {
1177        EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, tag,
1178                mustHaveFlags, mustNotHaveFlags);
1179
1180        synchronized (mNotificationList) {
1181            int index = indexOfNotificationLocked(pkg, tag, id);
1182            if (index >= 0) {
1183                NotificationRecord r = mNotificationList.get(index);
1184
1185                if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
1186                    return;
1187                }
1188                if ((r.notification.flags & mustNotHaveFlags) != 0) {
1189                    return;
1190                }
1191
1192                mNotificationList.remove(index);
1193
1194                cancelNotificationLocked(r, sendDelete);
1195                updateLightsLocked();
1196            }
1197        }
1198    }
1199
1200    /**
1201     * Cancels all notifications from a given package that have all of the
1202     * {@code mustHaveFlags}.
1203     */
1204    boolean cancelAllNotificationsInt(String pkg, int mustHaveFlags,
1205            int mustNotHaveFlags, boolean doit) {
1206        EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL_ALL, pkg, mustHaveFlags,
1207                mustNotHaveFlags);
1208
1209        synchronized (mNotificationList) {
1210            final int N = mNotificationList.size();
1211            boolean canceledSomething = false;
1212            for (int i = N-1; i >= 0; --i) {
1213                NotificationRecord r = mNotificationList.get(i);
1214                if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
1215                    continue;
1216                }
1217                if ((r.notification.flags & mustNotHaveFlags) != 0) {
1218                    continue;
1219                }
1220                if (!r.pkg.equals(pkg)) {
1221                    continue;
1222                }
1223                canceledSomething = true;
1224                if (!doit) {
1225                    return true;
1226                }
1227                mNotificationList.remove(i);
1228                cancelNotificationLocked(r, false);
1229            }
1230            if (canceledSomething) {
1231                updateLightsLocked();
1232            }
1233            return canceledSomething;
1234        }
1235    }
1236
1237    @Deprecated
1238    public void cancelNotification(String pkg, int id) {
1239        cancelNotificationWithTag(pkg, null /* tag */, id);
1240    }
1241
1242    public void cancelNotificationWithTag(String pkg, String tag, int id) {
1243        checkCallerIsSystemOrSameApp(pkg);
1244        // Don't allow client applications to cancel foreground service notis.
1245        cancelNotification(pkg, tag, id, 0,
1246                Binder.getCallingUid() == Process.SYSTEM_UID
1247                ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false);
1248    }
1249
1250    public void cancelAllNotifications(String pkg) {
1251        checkCallerIsSystemOrSameApp(pkg);
1252
1253        // Calling from user space, don't allow the canceling of actively
1254        // running foreground services.
1255        cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true);
1256    }
1257
1258    void checkCallerIsSystem() {
1259        int uid = Binder.getCallingUid();
1260        if (uid == Process.SYSTEM_UID || uid == 0) {
1261            return;
1262        }
1263        throw new SecurityException("Disallowed call for uid " + uid);
1264    }
1265
1266    void checkCallerIsSystemOrSameApp(String pkg) {
1267        int uid = Binder.getCallingUid();
1268        if (uid == Process.SYSTEM_UID || uid == 0) {
1269            return;
1270        }
1271        try {
1272            ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(
1273                    pkg, 0);
1274            if (!UserId.isSameApp(ai.uid, uid)) {
1275                throw new SecurityException("Calling uid " + uid + " gave package"
1276                        + pkg + " which is owned by uid " + ai.uid);
1277            }
1278        } catch (PackageManager.NameNotFoundException e) {
1279            throw new SecurityException("Unknown package " + pkg);
1280        }
1281    }
1282
1283    void cancelAll() {
1284        synchronized (mNotificationList) {
1285            final int N = mNotificationList.size();
1286            for (int i=N-1; i>=0; i--) {
1287                NotificationRecord r = mNotificationList.get(i);
1288
1289                if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
1290                                | Notification.FLAG_NO_CLEAR)) == 0) {
1291                    mNotificationList.remove(i);
1292                    cancelNotificationLocked(r, true);
1293                }
1294            }
1295
1296            updateLightsLocked();
1297        }
1298    }
1299
1300    // lock on mNotificationList
1301    private void updateLightsLocked()
1302    {
1303        // handle notification lights
1304        if (mLedNotification == null) {
1305            // get next notification, if any
1306            int n = mLights.size();
1307            if (n > 0) {
1308                mLedNotification = mLights.get(n-1);
1309            }
1310        }
1311
1312        // Don't flash while we are in a call or screen is on
1313        if (mLedNotification == null || mInCall || mScreenOn) {
1314            mNotificationLight.turnOff();
1315        } else {
1316            int ledARGB = mLedNotification.notification.ledARGB;
1317            int ledOnMS = mLedNotification.notification.ledOnMS;
1318            int ledOffMS = mLedNotification.notification.ledOffMS;
1319            if ((mLedNotification.notification.defaults & Notification.DEFAULT_LIGHTS) != 0) {
1320                ledARGB = mDefaultNotificationColor;
1321                ledOnMS = mDefaultNotificationLedOn;
1322                ledOffMS = mDefaultNotificationLedOff;
1323            }
1324            if (mNotificationPulseEnabled) {
1325                // pulse repeatedly
1326                mNotificationLight.setFlashing(ledARGB, LightsService.LIGHT_FLASH_TIMED,
1327                        ledOnMS, ledOffMS);
1328            }
1329        }
1330    }
1331
1332    // lock on mNotificationList
1333    private int indexOfNotificationLocked(String pkg, String tag, int id)
1334    {
1335        ArrayList<NotificationRecord> list = mNotificationList;
1336        final int len = list.size();
1337        for (int i=0; i<len; i++) {
1338            NotificationRecord r = list.get(i);
1339            if (tag == null) {
1340                if (r.tag != null) {
1341                    continue;
1342                }
1343            } else {
1344                if (!tag.equals(r.tag)) {
1345                    continue;
1346                }
1347            }
1348            if (r.id == id && r.pkg.equals(pkg)) {
1349                return i;
1350            }
1351        }
1352        return -1;
1353    }
1354
1355    private void updateNotificationPulse() {
1356        synchronized (mNotificationList) {
1357            updateLightsLocked();
1358        }
1359    }
1360
1361    // ======================================================================
1362    @Override
1363    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1364        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1365                != PackageManager.PERMISSION_GRANTED) {
1366            pw.println("Permission Denial: can't dump NotificationManager from from pid="
1367                    + Binder.getCallingPid()
1368                    + ", uid=" + Binder.getCallingUid());
1369            return;
1370        }
1371
1372        pw.println("Current Notification Manager state:");
1373
1374        int N;
1375
1376        synchronized (mToastQueue) {
1377            N = mToastQueue.size();
1378            if (N > 0) {
1379                pw.println("  Toast Queue:");
1380                for (int i=0; i<N; i++) {
1381                    mToastQueue.get(i).dump(pw, "    ");
1382                }
1383                pw.println("  ");
1384            }
1385
1386        }
1387
1388        synchronized (mNotificationList) {
1389            N = mNotificationList.size();
1390            if (N > 0) {
1391                pw.println("  Notification List:");
1392                for (int i=0; i<N; i++) {
1393                    mNotificationList.get(i).dump(pw, "    ", mContext);
1394                }
1395                pw.println("  ");
1396            }
1397
1398            N = mLights.size();
1399            if (N > 0) {
1400                pw.println("  Lights List:");
1401                for (int i=0; i<N; i++) {
1402                    mLights.get(i).dump(pw, "    ", mContext);
1403                }
1404                pw.println("  ");
1405            }
1406
1407            pw.println("  mSoundNotification=" + mSoundNotification);
1408            pw.println("  mVibrateNotification=" + mVibrateNotification);
1409            pw.println("  mDisabledNotifications=0x" + Integer.toHexString(mDisabledNotifications));
1410            pw.println("  mSystemReady=" + mSystemReady);
1411        }
1412    }
1413}
1414