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