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