NotificationManagerService.java revision 6d51571835737c7502a2e111ee9dc2527ebad984
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.ActivityManager;
24import android.app.ActivityManagerNative;
25import android.app.AppGlobals;
26import android.app.IActivityManager;
27import android.app.INotificationManager;
28import android.app.ITransientNotification;
29import android.app.Notification;
30import android.app.PendingIntent;
31import android.app.StatusBarManager;
32import android.content.BroadcastReceiver;
33import android.content.ContentResolver;
34import android.content.Context;
35import android.content.Intent;
36import android.content.IntentFilter;
37import android.content.pm.ApplicationInfo;
38import android.content.pm.PackageManager;
39import android.content.pm.PackageManager.NameNotFoundException;
40import android.content.res.Resources;
41import android.database.ContentObserver;
42import android.media.AudioManager;
43import android.media.IAudioService;
44import android.media.IRingtonePlayer;
45import android.net.Uri;
46import android.os.Binder;
47import android.os.Handler;
48import android.os.IBinder;
49import android.os.Message;
50import android.os.Process;
51import android.os.RemoteException;
52import android.os.ServiceManager;
53import android.os.UserHandle;
54import android.os.Vibrator;
55import android.provider.Settings;
56import android.service.dreams.IDreamManager;
57import android.telephony.TelephonyManager;
58import android.text.TextUtils;
59import android.util.AtomicFile;
60import android.util.EventLog;
61import android.util.Log;
62import android.util.Slog;
63import android.util.Xml;
64import android.view.accessibility.AccessibilityEvent;
65import android.view.accessibility.AccessibilityManager;
66import android.widget.RemoteViews;
67import android.widget.Toast;
68
69import com.android.internal.statusbar.StatusBarNotification;
70import com.android.internal.util.FastXmlSerializer;
71
72import org.xmlpull.v1.XmlPullParser;
73import org.xmlpull.v1.XmlPullParserException;
74import org.xmlpull.v1.XmlSerializer;
75
76import java.io.File;
77import java.io.FileDescriptor;
78import java.io.FileInputStream;
79import java.io.FileNotFoundException;
80import java.io.FileOutputStream;
81import java.io.IOException;
82import java.io.PrintWriter;
83import java.util.ArrayList;
84import java.util.Arrays;
85import java.util.HashSet;
86
87import libcore.io.IoUtils;
88
89
90/** {@hide} */
91public class NotificationManagerService extends INotificationManager.Stub
92{
93    private static final String TAG = "NotificationService";
94    private static final boolean DBG = false;
95
96    private static final int MAX_PACKAGE_NOTIFICATIONS = 50;
97
98    // message codes
99    private static final int MESSAGE_TIMEOUT = 2;
100
101    private static final int LONG_DELAY = 3500; // 3.5 seconds
102    private static final int SHORT_DELAY = 2000; // 2 seconds
103
104    private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
105
106    private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
107    private static final boolean SCORE_ONGOING_HIGHER = false;
108
109    private static final int JUNK_SCORE = -1000;
110    private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10;
111    private static final int SCORE_DISPLAY_THRESHOLD = Notification.PRIORITY_MIN * NOTIFICATION_PRIORITY_MULTIPLIER;
112
113    private static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
114    private static final boolean ENABLE_BLOCKED_TOASTS = true;
115
116    final Context mContext;
117    final IActivityManager mAm;
118    final IBinder mForegroundToken = new Binder();
119
120    private WorkerHandler mHandler;
121    private StatusBarManagerService mStatusBar;
122    private LightsService.Light mNotificationLight;
123    private LightsService.Light mAttentionLight;
124
125    private int mDefaultNotificationColor;
126    private int mDefaultNotificationLedOn;
127    private int mDefaultNotificationLedOff;
128
129    private boolean mSystemReady;
130    private int mDisabledNotifications;
131
132    private NotificationRecord mSoundNotification;
133    private NotificationRecord mVibrateNotification;
134
135    private IAudioService mAudioService;
136    private Vibrator mVibrator;
137
138    // for enabling and disabling notification pulse behavior
139    private boolean mScreenOn = true;
140    private boolean mInCall = false;
141    private boolean mNotificationPulseEnabled;
142
143    private final ArrayList<NotificationRecord> mNotificationList =
144            new ArrayList<NotificationRecord>();
145
146    private ArrayList<ToastRecord> mToastQueue;
147
148    private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
149    private NotificationRecord mLedNotification;
150
151    // Notification control database. For now just contains disabled packages.
152    private AtomicFile mPolicyFile;
153    private HashSet<String> mBlockedPackages = new HashSet<String>();
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 int userId;
322        final Notification notification;
323        final int score;
324        IBinder statusBarKey;
325
326        NotificationRecord(String pkg, String tag, int id, int uid, int initialPid,
327                int userId, int score, Notification notification)
328        {
329            this.pkg = pkg;
330            this.tag = tag;
331            this.id = id;
332            this.uid = uid;
333            this.initialPid = initialPid;
334            this.userId = userId;
335            this.score = score;
336            this.notification = notification;
337        }
338
339        void dump(PrintWriter pw, String prefix, Context baseContext) {
340            pw.println(prefix + this);
341            pw.println(prefix + "  icon=0x" + Integer.toHexString(notification.icon)
342                    + " / " + idDebugString(baseContext, this.pkg, notification.icon));
343            pw.println(prefix + "  pri=" + notification.priority);
344            pw.println(prefix + "  score=" + this.score);
345            pw.println(prefix + "  contentIntent=" + notification.contentIntent);
346            pw.println(prefix + "  deleteIntent=" + notification.deleteIntent);
347            pw.println(prefix + "  tickerText=" + notification.tickerText);
348            pw.println(prefix + "  contentView=" + notification.contentView);
349            pw.println(prefix + "  uid=" + uid + " userId=" + userId);
350            pw.println(prefix + "  defaults=0x" + Integer.toHexString(notification.defaults));
351            pw.println(prefix + "  flags=0x" + Integer.toHexString(notification.flags));
352            pw.println(prefix + "  sound=" + notification.sound);
353            pw.println(prefix + "  vibrate=" + Arrays.toString(notification.vibrate));
354            pw.println(prefix + "  ledARGB=0x" + Integer.toHexString(notification.ledARGB)
355                    + " ledOnMS=" + notification.ledOnMS
356                    + " ledOffMS=" + notification.ledOffMS);
357        }
358
359        @Override
360        public final String toString()
361        {
362            return "NotificationRecord{"
363                + Integer.toHexString(System.identityHashCode(this))
364                + " pkg=" + pkg
365                + " id=" + Integer.toHexString(id)
366                + " tag=" + tag
367                + " score=" + score
368                + "}";
369        }
370    }
371
372    private static final class ToastRecord
373    {
374        final int pid;
375        final String pkg;
376        final ITransientNotification callback;
377        int duration;
378
379        ToastRecord(int pid, String pkg, ITransientNotification callback, int duration)
380        {
381            this.pid = pid;
382            this.pkg = pkg;
383            this.callback = callback;
384            this.duration = duration;
385        }
386
387        void update(int duration) {
388            this.duration = duration;
389        }
390
391        void dump(PrintWriter pw, String prefix) {
392            pw.println(prefix + this);
393        }
394
395        @Override
396        public final String toString()
397        {
398            return "ToastRecord{"
399                + Integer.toHexString(System.identityHashCode(this))
400                + " pkg=" + pkg
401                + " callback=" + callback
402                + " duration=" + duration;
403        }
404    }
405
406    private StatusBarManagerService.NotificationCallbacks mNotificationCallbacks
407            = new StatusBarManagerService.NotificationCallbacks() {
408
409        public void onSetDisabled(int status) {
410            synchronized (mNotificationList) {
411                mDisabledNotifications = status;
412                if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
413                    // cancel whatever's going on
414                    long identity = Binder.clearCallingIdentity();
415                    try {
416                        final IRingtonePlayer player = mAudioService.getRingtonePlayer();
417                        if (player != null) {
418                            player.stopAsync();
419                        }
420                    } catch (RemoteException e) {
421                    } finally {
422                        Binder.restoreCallingIdentity(identity);
423                    }
424
425                    identity = Binder.clearCallingIdentity();
426                    try {
427                        mVibrator.cancel();
428                    } finally {
429                        Binder.restoreCallingIdentity(identity);
430                    }
431                }
432            }
433        }
434
435        public void onClearAll() {
436            // XXX to be totally correct, the caller should tell us which user
437            // this is for.
438            cancelAll(ActivityManager.getCurrentUser());
439        }
440
441        public void onNotificationClick(String pkg, String tag, int id) {
442            // XXX to be totally correct, the caller should tell us which user
443            // this is for.
444            cancelNotification(pkg, tag, id, Notification.FLAG_AUTO_CANCEL,
445                    Notification.FLAG_FOREGROUND_SERVICE, false,
446                    ActivityManager.getCurrentUser());
447        }
448
449        public void onNotificationClear(String pkg, String tag, int id) {
450            // XXX to be totally correct, the caller should tell us which user
451            // this is for.
452            cancelNotification(pkg, tag, id, 0,
453                Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
454                true, ActivityManager.getCurrentUser());
455        }
456
457        public void onPanelRevealed() {
458            synchronized (mNotificationList) {
459                // sound
460                mSoundNotification = null;
461
462                long identity = Binder.clearCallingIdentity();
463                try {
464                    final IRingtonePlayer player = mAudioService.getRingtonePlayer();
465                    if (player != null) {
466                        player.stopAsync();
467                    }
468                } catch (RemoteException e) {
469                } finally {
470                    Binder.restoreCallingIdentity(identity);
471                }
472
473                // vibrate
474                mVibrateNotification = null;
475                identity = Binder.clearCallingIdentity();
476                try {
477                    mVibrator.cancel();
478                } finally {
479                    Binder.restoreCallingIdentity(identity);
480                }
481
482                // light
483                mLights.clear();
484                mLedNotification = null;
485                updateLightsLocked();
486            }
487        }
488
489        public void onNotificationError(String pkg, String tag, int id,
490                int uid, int initialPid, String message) {
491            Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id
492                    + "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")");
493            // XXX to be totally correct, the caller should tell us which user
494            // this is for.
495            cancelNotification(pkg, tag, id, 0, 0, false, UserHandle.getUserId(uid));
496            long ident = Binder.clearCallingIdentity();
497            try {
498                ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg,
499                        "Bad notification posted from package " + pkg
500                        + ": " + message);
501            } catch (RemoteException e) {
502            }
503            Binder.restoreCallingIdentity(ident);
504        }
505    };
506
507    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
508        @Override
509        public void onReceive(Context context, Intent intent) {
510            String action = intent.getAction();
511
512            boolean queryRestart = false;
513            boolean packageChanged = false;
514
515            if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
516                    || action.equals(Intent.ACTION_PACKAGE_RESTARTED)
517                    || (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED))
518                    || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
519                    || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
520                String pkgList[] = null;
521                if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
522                    pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
523                } else if (queryRestart) {
524                    pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
525                } else {
526                    Uri uri = intent.getData();
527                    if (uri == null) {
528                        return;
529                    }
530                    String pkgName = uri.getSchemeSpecificPart();
531                    if (pkgName == null) {
532                        return;
533                    }
534                    if (packageChanged) {
535                        // We cancel notifications for packages which have just been disabled
536                        final int enabled = mContext.getPackageManager()
537                                .getApplicationEnabledSetting(pkgName);
538                        if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
539                                || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
540                            return;
541                        }
542                    }
543                    pkgList = new String[]{pkgName};
544                }
545                if (pkgList != null && (pkgList.length > 0)) {
546                    for (String pkgName : pkgList) {
547                        cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart,
548                                UserHandle.USER_ALL);
549                    }
550                }
551            } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
552                // Keep track of screen on/off state, but do not turn off the notification light
553                // until user passes through the lock screen or views the notification.
554                mScreenOn = true;
555            } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
556                mScreenOn = false;
557            } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
558                mInCall = (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(
559                        TelephonyManager.EXTRA_STATE_OFFHOOK));
560                updateNotificationPulse();
561            } else if (action.equals(Intent.ACTION_USER_STOPPED)) {
562                int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
563                if (userHandle >= 0) {
564                    cancelAllNotificationsInt(null, 0, 0, true, userHandle);
565                }
566            } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
567                // turn off LED when user passes through lock screen
568                mNotificationLight.turnOff();
569            }
570        }
571    };
572
573    class SettingsObserver extends ContentObserver {
574        SettingsObserver(Handler handler) {
575            super(handler);
576        }
577
578        void observe() {
579            ContentResolver resolver = mContext.getContentResolver();
580            resolver.registerContentObserver(Settings.System.getUriFor(
581                    Settings.System.NOTIFICATION_LIGHT_PULSE), false, this);
582            update();
583        }
584
585        @Override public void onChange(boolean selfChange) {
586            update();
587        }
588
589        public void update() {
590            ContentResolver resolver = mContext.getContentResolver();
591            boolean pulseEnabled = Settings.System.getInt(resolver,
592                        Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
593            if (mNotificationPulseEnabled != pulseEnabled) {
594                mNotificationPulseEnabled = pulseEnabled;
595                updateNotificationPulse();
596            }
597        }
598    }
599
600    NotificationManagerService(Context context, StatusBarManagerService statusBar,
601            LightsService lights)
602    {
603        super();
604        mContext = context;
605        mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
606        mAm = ActivityManagerNative.getDefault();
607        mToastQueue = new ArrayList<ToastRecord>();
608        mHandler = new WorkerHandler();
609
610        loadBlockDb();
611
612        mStatusBar = statusBar;
613        statusBar.setNotificationCallbacks(mNotificationCallbacks);
614
615        mNotificationLight = lights.getLight(LightsService.LIGHT_ID_NOTIFICATIONS);
616        mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION);
617
618        Resources resources = mContext.getResources();
619        mDefaultNotificationColor = resources.getColor(
620                com.android.internal.R.color.config_defaultNotificationColor);
621        mDefaultNotificationLedOn = resources.getInteger(
622                com.android.internal.R.integer.config_defaultNotificationLedOn);
623        mDefaultNotificationLedOff = resources.getInteger(
624                com.android.internal.R.integer.config_defaultNotificationLedOff);
625
626        // Don't start allowing notifications until the setup wizard has run once.
627        // After that, including subsequent boots, init with notifications turned on.
628        // This works on the first boot because the setup wizard will toggle this
629        // flag at least once and we'll go back to 0 after that.
630        if (0 == Settings.Secure.getInt(mContext.getContentResolver(),
631                    Settings.Secure.DEVICE_PROVISIONED, 0)) {
632            mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
633        }
634
635        // register for various Intents
636        IntentFilter filter = new IntentFilter();
637        filter.addAction(Intent.ACTION_SCREEN_ON);
638        filter.addAction(Intent.ACTION_SCREEN_OFF);
639        filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
640        filter.addAction(Intent.ACTION_USER_PRESENT);
641        filter.addAction(Intent.ACTION_USER_STOPPED);
642        mContext.registerReceiver(mIntentReceiver, filter);
643        IntentFilter pkgFilter = new IntentFilter();
644        pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
645        pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
646        pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
647        pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
648        pkgFilter.addDataScheme("package");
649        mContext.registerReceiver(mIntentReceiver, pkgFilter);
650        IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
651        mContext.registerReceiver(mIntentReceiver, sdFilter);
652
653        SettingsObserver observer = new SettingsObserver(mHandler);
654        observer.observe();
655    }
656
657    void systemReady() {
658        mAudioService = IAudioService.Stub.asInterface(
659                ServiceManager.getService(Context.AUDIO_SERVICE));
660
661        // no beeping until we're basically done booting
662        mSystemReady = true;
663    }
664
665    // Toasts
666    // ============================================================================
667    public void enqueueToast(String pkg, ITransientNotification callback, int duration)
668    {
669        if (DBG) Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration);
670
671        if (pkg == null || callback == null) {
672            Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
673            return ;
674        }
675
676        final boolean isSystemToast = ("android".equals(pkg));
677
678        if (ENABLE_BLOCKED_TOASTS && !isSystemToast && !areNotificationsEnabledForPackageInt(pkg)) {
679            Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
680            return;
681        }
682
683        synchronized (mToastQueue) {
684            int callingPid = Binder.getCallingPid();
685            long callingId = Binder.clearCallingIdentity();
686            try {
687                ToastRecord record;
688                int index = indexOfToastLocked(pkg, callback);
689                // If it's already in the queue, we update it in place, we don't
690                // move it to the end of the queue.
691                if (index >= 0) {
692                    record = mToastQueue.get(index);
693                    record.update(duration);
694                } else {
695                    // Limit the number of toasts that any given package except the android
696                    // package can enqueue.  Prevents DOS attacks and deals with leaks.
697                    if (!isSystemToast) {
698                        int count = 0;
699                        final int N = mToastQueue.size();
700                        for (int i=0; i<N; i++) {
701                             final ToastRecord r = mToastQueue.get(i);
702                             if (r.pkg.equals(pkg)) {
703                                 count++;
704                                 if (count >= MAX_PACKAGE_NOTIFICATIONS) {
705                                     Slog.e(TAG, "Package has already posted " + count
706                                            + " toasts. Not showing more. Package=" + pkg);
707                                     return;
708                                 }
709                             }
710                        }
711                    }
712
713                    record = new ToastRecord(callingPid, pkg, callback, duration);
714                    mToastQueue.add(record);
715                    index = mToastQueue.size() - 1;
716                    keepProcessAliveLocked(callingPid);
717                }
718                // If it's at index 0, it's the current toast.  It doesn't matter if it's
719                // new or just been updated.  Call back and tell it to show itself.
720                // If the callback fails, this will remove it from the list, so don't
721                // assume that it's valid after this.
722                if (index == 0) {
723                    showNextToastLocked();
724                }
725            } finally {
726                Binder.restoreCallingIdentity(callingId);
727            }
728        }
729    }
730
731    public void cancelToast(String pkg, ITransientNotification callback) {
732        Slog.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback);
733
734        if (pkg == null || callback == null) {
735            Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback);
736            return ;
737        }
738
739        synchronized (mToastQueue) {
740            long callingId = Binder.clearCallingIdentity();
741            try {
742                int index = indexOfToastLocked(pkg, callback);
743                if (index >= 0) {
744                    cancelToastLocked(index);
745                } else {
746                    Slog.w(TAG, "Toast already cancelled. pkg=" + pkg + " callback=" + callback);
747                }
748            } finally {
749                Binder.restoreCallingIdentity(callingId);
750            }
751        }
752    }
753
754    private void showNextToastLocked() {
755        ToastRecord record = mToastQueue.get(0);
756        while (record != null) {
757            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
758            try {
759                record.callback.show();
760                scheduleTimeoutLocked(record, false);
761                return;
762            } catch (RemoteException e) {
763                Slog.w(TAG, "Object died trying to show notification " + record.callback
764                        + " in package " + record.pkg);
765                // remove it from the list and let the process die
766                int index = mToastQueue.indexOf(record);
767                if (index >= 0) {
768                    mToastQueue.remove(index);
769                }
770                keepProcessAliveLocked(record.pid);
771                if (mToastQueue.size() > 0) {
772                    record = mToastQueue.get(0);
773                } else {
774                    record = null;
775                }
776            }
777        }
778    }
779
780    private void cancelToastLocked(int index) {
781        ToastRecord record = mToastQueue.get(index);
782        try {
783            record.callback.hide();
784        } catch (RemoteException e) {
785            Slog.w(TAG, "Object died trying to hide notification " + record.callback
786                    + " in package " + record.pkg);
787            // don't worry about this, we're about to remove it from
788            // the list anyway
789        }
790        mToastQueue.remove(index);
791        keepProcessAliveLocked(record.pid);
792        if (mToastQueue.size() > 0) {
793            // Show the next one. If the callback fails, this will remove
794            // it from the list, so don't assume that the list hasn't changed
795            // after this point.
796            showNextToastLocked();
797        }
798    }
799
800    private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)
801    {
802        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
803        long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
804        mHandler.removeCallbacksAndMessages(r);
805        mHandler.sendMessageDelayed(m, delay);
806    }
807
808    private void handleTimeout(ToastRecord record)
809    {
810        if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
811        synchronized (mToastQueue) {
812            int index = indexOfToastLocked(record.pkg, record.callback);
813            if (index >= 0) {
814                cancelToastLocked(index);
815            }
816        }
817    }
818
819    // lock on mToastQueue
820    private int indexOfToastLocked(String pkg, ITransientNotification callback)
821    {
822        IBinder cbak = callback.asBinder();
823        ArrayList<ToastRecord> list = mToastQueue;
824        int len = list.size();
825        for (int i=0; i<len; i++) {
826            ToastRecord r = list.get(i);
827            if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
828                return i;
829            }
830        }
831        return -1;
832    }
833
834    // lock on mToastQueue
835    private void keepProcessAliveLocked(int pid)
836    {
837        int toastCount = 0; // toasts from this pid
838        ArrayList<ToastRecord> list = mToastQueue;
839        int N = list.size();
840        for (int i=0; i<N; i++) {
841            ToastRecord r = list.get(i);
842            if (r.pid == pid) {
843                toastCount++;
844            }
845        }
846        try {
847            mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
848        } catch (RemoteException e) {
849            // Shouldn't happen.
850        }
851    }
852
853    private final class WorkerHandler extends Handler
854    {
855        @Override
856        public void handleMessage(Message msg)
857        {
858            switch (msg.what)
859            {
860                case MESSAGE_TIMEOUT:
861                    handleTimeout((ToastRecord)msg.obj);
862                    break;
863            }
864        }
865    }
866
867
868    // Notifications
869    // ============================================================================
870    public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification,
871            int[] idOut, int userId)
872    {
873        enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(),
874                tag, id, notification, idOut, userId);
875    }
876
877    private final static int clamp(int x, int low, int high) {
878        return (x < low) ? low : ((x > high) ? high : x);
879    }
880
881    // Not exposed via Binder; for system use only (otherwise malicious apps could spoof the
882    // uid/pid of another application)
883    public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
884            String tag, int id, Notification notification, int[] idOut, int userId)
885    {
886        if (DBG) {
887            Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
888        }
889        checkCallerIsSystemOrSameApp(pkg);
890        final boolean isSystemNotification = ("android".equals(pkg));
891
892        userId = ActivityManager.handleIncomingUser(callingPid,
893                callingUid, userId, false, true, "enqueueNotification", pkg);
894
895        // Limit the number of notifications that any given package except the android
896        // package can enqueue.  Prevents DOS attacks and deals with leaks.
897        if (!isSystemNotification) {
898            synchronized (mNotificationList) {
899                int count = 0;
900                final int N = mNotificationList.size();
901                for (int i=0; i<N; i++) {
902                    final NotificationRecord r = mNotificationList.get(i);
903                    if (r.pkg.equals(pkg)) {
904                        count++;
905                        if (count >= MAX_PACKAGE_NOTIFICATIONS) {
906                            Slog.e(TAG, "Package has already posted " + count
907                                    + " notifications.  Not showing more.  package=" + pkg);
908                            return;
909                        }
910                    }
911                }
912            }
913        }
914
915        // This conditional is a dirty hack to limit the logging done on
916        //     behalf of the download manager without affecting other apps.
917        if (!pkg.equals("com.android.providers.downloads")
918                || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
919            EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag,
920                    notification.toString());
921        }
922
923        if (pkg == null || notification == null) {
924            throw new IllegalArgumentException("null not allowed: pkg=" + pkg
925                    + " id=" + id + " notification=" + notification);
926        }
927        if (notification.icon != 0) {
928            if (notification.contentView == null) {
929                throw new IllegalArgumentException("contentView required: pkg=" + pkg
930                        + " id=" + id + " notification=" + notification);
931            }
932        }
933
934        // === Scoring ===
935
936        // 0. Sanitize inputs
937        notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, Notification.PRIORITY_MAX);
938        // Migrate notification flags to scores
939        if (0 != (notification.flags & Notification.FLAG_HIGH_PRIORITY)) {
940            if (notification.priority < Notification.PRIORITY_MAX) notification.priority = Notification.PRIORITY_MAX;
941        } else if (SCORE_ONGOING_HIGHER && 0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) {
942            if (notification.priority < Notification.PRIORITY_HIGH) notification.priority = Notification.PRIORITY_HIGH;
943        }
944
945        // 1. initial score: buckets of 10, around the app
946        int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20]
947
948        // 2. Consult external heuristics (TBD)
949
950        // 3. Apply local rules
951
952        // blocked apps
953        if (ENABLE_BLOCKED_NOTIFICATIONS && !isSystemNotification && !areNotificationsEnabledForPackageInt(pkg)) {
954            score = JUNK_SCORE;
955            Slog.e(TAG, "Suppressing notification from package " + pkg + " by user request.");
956        }
957
958        if (DBG) {
959            Slog.v(TAG, "Assigned score=" + score + " to " + notification);
960        }
961
962        if (score < SCORE_DISPLAY_THRESHOLD) {
963            // Notification will be blocked because the score is too low.
964            return;
965        }
966
967        synchronized (mNotificationList) {
968            NotificationRecord r = new NotificationRecord(pkg, tag, id,
969                    callingUid, callingPid, userId,
970                    score,
971                    notification);
972            NotificationRecord old = null;
973
974            int index = indexOfNotificationLocked(pkg, tag, id, userId);
975            if (index < 0) {
976                mNotificationList.add(r);
977            } else {
978                old = mNotificationList.remove(index);
979                mNotificationList.add(index, r);
980                // Make sure we don't lose the foreground service state.
981                if (old != null) {
982                    notification.flags |=
983                        old.notification.flags&Notification.FLAG_FOREGROUND_SERVICE;
984                }
985            }
986
987            // Ensure if this is a foreground service that the proper additional
988            // flags are set.
989            if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) {
990                notification.flags |= Notification.FLAG_ONGOING_EVENT
991                        | Notification.FLAG_NO_CLEAR;
992            }
993
994            if (notification.icon != 0) {
995                final UserHandle user = new UserHandle(userId);
996                final StatusBarNotification n = new StatusBarNotification(
997                        pkg, id, tag, r.uid, r.initialPid, score, notification, user);
998                if (old != null && old.statusBarKey != null) {
999                    r.statusBarKey = old.statusBarKey;
1000                    long identity = Binder.clearCallingIdentity();
1001                    try {
1002                        mStatusBar.updateNotification(r.statusBarKey, n);
1003                    }
1004                    finally {
1005                        Binder.restoreCallingIdentity(identity);
1006                    }
1007                } else {
1008                    long identity = Binder.clearCallingIdentity();
1009                    try {
1010                        r.statusBarKey = mStatusBar.addNotification(n);
1011                        if ((n.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
1012                            mAttentionLight.pulse();
1013                        }
1014                    }
1015                    finally {
1016                        Binder.restoreCallingIdentity(identity);
1017                    }
1018                }
1019                sendAccessibilityEvent(notification, pkg);
1020            } else {
1021                Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
1022                if (old != null && old.statusBarKey != null) {
1023                    long identity = Binder.clearCallingIdentity();
1024                    try {
1025                        mStatusBar.removeNotification(old.statusBarKey);
1026                    }
1027                    finally {
1028                        Binder.restoreCallingIdentity(identity);
1029                    }
1030                }
1031            }
1032
1033            // If we're not supposed to beep, vibrate, etc. then don't.
1034            if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
1035                    && (!(old != null
1036                        && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
1037                    && (r.userId == UserHandle.USER_ALL || r.userId == userId)
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, int userId) {
1198        EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, tag,
1199                mustHaveFlags, mustNotHaveFlags);
1200
1201        synchronized (mNotificationList) {
1202            int index = indexOfNotificationLocked(pkg, tag, id, userId);
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, int userId) {
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 (userId != UserHandle.USER_ALL && r.userId != userId) {
1236                    continue;
1237                }
1238                if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
1239                    continue;
1240                }
1241                if ((r.notification.flags & mustNotHaveFlags) != 0) {
1242                    continue;
1243                }
1244                if (!r.pkg.equals(pkg)) {
1245                    continue;
1246                }
1247                canceledSomething = true;
1248                if (!doit) {
1249                    return true;
1250                }
1251                mNotificationList.remove(i);
1252                cancelNotificationLocked(r, false);
1253            }
1254            if (canceledSomething) {
1255                updateLightsLocked();
1256            }
1257            return canceledSomething;
1258        }
1259    }
1260
1261    public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) {
1262        checkCallerIsSystemOrSameApp(pkg);
1263        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
1264                Binder.getCallingUid(), userId, false, true, "cancelNotificationWithTag", pkg);
1265        // Don't allow client applications to cancel foreground service notis.
1266        cancelNotification(pkg, tag, id, 0,
1267                Binder.getCallingUid() == Process.SYSTEM_UID
1268                ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId);
1269    }
1270
1271    public void cancelAllNotifications(String pkg, int userId) {
1272        checkCallerIsSystemOrSameApp(pkg);
1273
1274        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
1275                Binder.getCallingUid(), userId, true, true, "cancelAllNotifications", pkg);
1276
1277        // Calling from user space, don't allow the canceling of actively
1278        // running foreground services.
1279        cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId);
1280    }
1281
1282    void checkCallerIsSystem() {
1283        int uid = Binder.getCallingUid();
1284        if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) {
1285            return;
1286        }
1287        throw new SecurityException("Disallowed call for uid " + uid);
1288    }
1289
1290    void checkCallerIsSystemOrSameApp(String pkg) {
1291        int uid = Binder.getCallingUid();
1292        if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) {
1293            return;
1294        }
1295        try {
1296            ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
1297                    pkg, 0, UserHandle.getCallingUserId());
1298            if (!UserHandle.isSameApp(ai.uid, uid)) {
1299                throw new SecurityException("Calling uid " + uid + " gave package"
1300                        + pkg + " which is owned by uid " + ai.uid);
1301            }
1302        } catch (RemoteException re) {
1303            throw new SecurityException("Unknown package " + pkg + "\n" + re);
1304        }
1305    }
1306
1307    void cancelAll(int userId) {
1308        synchronized (mNotificationList) {
1309            final int N = mNotificationList.size();
1310            for (int i=N-1; i>=0; i--) {
1311                NotificationRecord r = mNotificationList.get(i);
1312
1313                if (r.userId != userId) {
1314                    continue;
1315                }
1316
1317                if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
1318                                | Notification.FLAG_NO_CLEAR)) == 0) {
1319                    mNotificationList.remove(i);
1320                    cancelNotificationLocked(r, true);
1321                }
1322            }
1323
1324            updateLightsLocked();
1325        }
1326    }
1327
1328    // lock on mNotificationList
1329    private void updateLightsLocked()
1330    {
1331        // handle notification lights
1332        if (mLedNotification == null) {
1333            // get next notification, if any
1334            int n = mLights.size();
1335            if (n > 0) {
1336                mLedNotification = mLights.get(n-1);
1337            }
1338        }
1339
1340        // Don't flash while we are in a call or screen is on
1341        if (mLedNotification == null || mInCall || mScreenOn) {
1342            mNotificationLight.turnOff();
1343        } else {
1344            int ledARGB = mLedNotification.notification.ledARGB;
1345            int ledOnMS = mLedNotification.notification.ledOnMS;
1346            int ledOffMS = mLedNotification.notification.ledOffMS;
1347            if ((mLedNotification.notification.defaults & Notification.DEFAULT_LIGHTS) != 0) {
1348                ledARGB = mDefaultNotificationColor;
1349                ledOnMS = mDefaultNotificationLedOn;
1350                ledOffMS = mDefaultNotificationLedOff;
1351            }
1352            if (mNotificationPulseEnabled) {
1353                // pulse repeatedly
1354                mNotificationLight.setFlashing(ledARGB, LightsService.LIGHT_FLASH_TIMED,
1355                        ledOnMS, ledOffMS);
1356            }
1357        }
1358    }
1359
1360    // lock on mNotificationList
1361    private int indexOfNotificationLocked(String pkg, String tag, int id, int userId)
1362    {
1363        ArrayList<NotificationRecord> list = mNotificationList;
1364        final int len = list.size();
1365        for (int i=0; i<len; i++) {
1366            NotificationRecord r = list.get(i);
1367            if (r.userId != userId || r.id != id) {
1368                continue;
1369            }
1370            if (tag == null) {
1371                if (r.tag != null) {
1372                    continue;
1373                }
1374            } else {
1375                if (!tag.equals(r.tag)) {
1376                    continue;
1377                }
1378            }
1379            if (r.pkg.equals(pkg)) {
1380                return i;
1381            }
1382        }
1383        return -1;
1384    }
1385
1386    private void updateNotificationPulse() {
1387        synchronized (mNotificationList) {
1388            updateLightsLocked();
1389        }
1390    }
1391
1392    // ======================================================================
1393    @Override
1394    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1395        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1396                != PackageManager.PERMISSION_GRANTED) {
1397            pw.println("Permission Denial: can't dump NotificationManager from from pid="
1398                    + Binder.getCallingPid()
1399                    + ", uid=" + Binder.getCallingUid());
1400            return;
1401        }
1402
1403        pw.println("Current Notification Manager state:");
1404
1405        int N;
1406
1407        synchronized (mToastQueue) {
1408            N = mToastQueue.size();
1409            if (N > 0) {
1410                pw.println("  Toast Queue:");
1411                for (int i=0; i<N; i++) {
1412                    mToastQueue.get(i).dump(pw, "    ");
1413                }
1414                pw.println("  ");
1415            }
1416
1417        }
1418
1419        synchronized (mNotificationList) {
1420            N = mNotificationList.size();
1421            if (N > 0) {
1422                pw.println("  Notification List:");
1423                for (int i=0; i<N; i++) {
1424                    mNotificationList.get(i).dump(pw, "    ", mContext);
1425                }
1426                pw.println("  ");
1427            }
1428
1429            N = mLights.size();
1430            if (N > 0) {
1431                pw.println("  Lights List:");
1432                for (int i=0; i<N; i++) {
1433                    mLights.get(i).dump(pw, "    ", mContext);
1434                }
1435                pw.println("  ");
1436            }
1437
1438            pw.println("  mSoundNotification=" + mSoundNotification);
1439            pw.println("  mVibrateNotification=" + mVibrateNotification);
1440            pw.println("  mDisabledNotifications=0x" + Integer.toHexString(mDisabledNotifications));
1441            pw.println("  mSystemReady=" + mSystemReady);
1442        }
1443    }
1444}
1445