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