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