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