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