NotificationManagerService.java revision 524dc046b1225087f481878a5c3ae7f733c9f0e2
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 HardwareService mHardware;
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            HardwareService hardware)
367    {
368        super();
369        mContext = context;
370        mHardware = hardware;
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                        mHardware.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
708                final AudioManager audioManager = (AudioManager) mContext
709                .getSystemService(Context.AUDIO_SERVICE);
710                // sound
711                final boolean useDefaultSound =
712                    (notification.defaults & Notification.DEFAULT_SOUND) != 0;
713                if (useDefaultSound || notification.sound != null) {
714                    Uri uri;
715                    if (useDefaultSound) {
716                        uri = Settings.System.DEFAULT_NOTIFICATION_URI;
717                    } else {
718                        uri = notification.sound;
719                    }
720                    boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
721                    int audioStreamType;
722                    if (notification.audioStreamType >= 0) {
723                        audioStreamType = notification.audioStreamType;
724                    } else {
725                        audioStreamType = DEFAULT_STREAM_TYPE;
726                    }
727                    mSoundNotification = r;
728                    // do not play notifications if stream volume is 0
729                    // (typically because ringer mode is silent).
730                    if (audioManager.getStreamVolume(audioStreamType) != 0) {
731                        long identity = Binder.clearCallingIdentity();
732                        try {
733                            mSound.play(mContext, uri, looping, audioStreamType);
734                        }
735                        finally {
736                            Binder.restoreCallingIdentity(identity);
737                        }
738                    }
739                }
740
741                // vibrate
742                final boolean useDefaultVibrate =
743                    (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
744                if ((useDefaultVibrate || notification.vibrate != null)
745                        && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
746                    mVibrateNotification = r;
747
748                    mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN
749                                                        : notification.vibrate,
750                              ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
751                }
752            }
753
754            // this option doesn't shut off the lights
755
756            // light
757            // the most recent thing gets the light
758            mLights.remove(old);
759            if (mLedNotification == old) {
760                mLedNotification = null;
761            }
762            //Log.i(TAG, "notification.lights="
763            //        + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
764            if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
765                mLights.add(r);
766                updateLightsLocked();
767            } else {
768                if (old != null
769                        && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
770                    updateLightsLocked();
771                }
772            }
773        }
774
775        idOut[0] = id;
776    }
777
778    private void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
779        AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
780        if (!manager.isEnabled()) {
781            return;
782        }
783
784        AccessibilityEvent event =
785            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
786        event.setPackageName(packageName);
787        event.setClassName(Notification.class.getName());
788        event.setParcelableData(notification);
789        CharSequence tickerText = notification.tickerText;
790        if (!TextUtils.isEmpty(tickerText)) {
791            event.getText().add(tickerText);
792        }
793
794        manager.sendAccessibilityEvent(event);
795    }
796
797    private void cancelNotificationLocked(NotificationRecord r) {
798        // status bar
799        if (r.notification.icon != 0) {
800            long identity = Binder.clearCallingIdentity();
801            try {
802                mStatusBarService.removeIcon(r.statusBarKey);
803            }
804            finally {
805                Binder.restoreCallingIdentity(identity);
806            }
807            r.statusBarKey = null;
808        }
809
810        // sound
811        if (mSoundNotification == r) {
812            mSoundNotification = null;
813            long identity = Binder.clearCallingIdentity();
814            try {
815                mSound.stop();
816            }
817            finally {
818                Binder.restoreCallingIdentity(identity);
819            }
820        }
821
822        // vibrate
823        if (mVibrateNotification == r) {
824            mVibrateNotification = null;
825            long identity = Binder.clearCallingIdentity();
826            try {
827                mVibrator.cancel();
828            }
829            finally {
830                Binder.restoreCallingIdentity(identity);
831            }
832        }
833
834        // light
835        mLights.remove(r);
836        if (mLedNotification == r) {
837            mLedNotification = null;
838        }
839    }
840
841    /**
842     * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}
843     * and none of the {@code mustNotHaveFlags}.
844     */
845    private void cancelNotification(String pkg, String tag, int id, int mustHaveFlags,
846            int mustNotHaveFlags) {
847        EventLog.writeEvent(EVENT_LOG_CANCEL, pkg, id, mustHaveFlags);
848
849        synchronized (mNotificationList) {
850            int index = indexOfNotificationLocked(pkg, tag, id);
851            if (index >= 0) {
852                NotificationRecord r = mNotificationList.get(index);
853
854                if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
855                    return;
856                }
857                if ((r.notification.flags & mustNotHaveFlags) != 0) {
858                    return;
859                }
860
861                mNotificationList.remove(index);
862
863                cancelNotificationLocked(r);
864                updateLightsLocked();
865            }
866        }
867    }
868
869    /**
870     * Cancels all notifications from a given package that have all of the
871     * {@code mustHaveFlags}.
872     */
873    void cancelAllNotificationsInt(String pkg, int mustHaveFlags,
874            int mustNotHaveFlags) {
875        EventLog.writeEvent(EVENT_LOG_CANCEL_ALL, pkg, mustHaveFlags);
876
877        synchronized (mNotificationList) {
878            final int N = mNotificationList.size();
879            boolean canceledSomething = false;
880            for (int i = N-1; i >= 0; --i) {
881                NotificationRecord r = mNotificationList.get(i);
882                if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
883                    continue;
884                }
885                if ((r.notification.flags & mustNotHaveFlags) != 0) {
886                    continue;
887                }
888                if (!r.pkg.equals(pkg)) {
889                    continue;
890                }
891                mNotificationList.remove(i);
892                cancelNotificationLocked(r);
893                canceledSomething = true;
894            }
895            if (canceledSomething) {
896                updateLightsLocked();
897            }
898        }
899    }
900
901
902    public void cancelNotification(String pkg, int id) {
903        cancelNotificationWithTag(pkg, null /* tag */, id);
904    }
905
906    public void cancelNotificationWithTag(String pkg, String tag, int id) {
907        checkIncomingCall(pkg);
908        // Don't allow client applications to cancel foreground service notis.
909        cancelNotification(pkg, tag, id, 0,
910                Binder.getCallingUid() == Process.SYSTEM_UID
911                ? 0 : Notification.FLAG_FOREGROUND_SERVICE);
912    }
913
914    public void cancelAllNotifications(String pkg) {
915        checkIncomingCall(pkg);
916
917        // Calling from user space, don't allow the canceling of actively
918        // running foreground services.
919        cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE);
920    }
921
922    void checkIncomingCall(String pkg) {
923        int uid = Binder.getCallingUid();
924        if (uid == Process.SYSTEM_UID || uid == 0) {
925            return;
926        }
927        try {
928            ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(
929                    pkg, 0);
930            if (ai.uid != uid) {
931                throw new SecurityException("Calling uid " + uid + " gave package"
932                        + pkg + " which is owned by uid " + ai.uid);
933            }
934        } catch (PackageManager.NameNotFoundException e) {
935            throw new SecurityException("Unknown package " + pkg);
936        }
937    }
938
939    void cancelAll() {
940        synchronized (mNotificationList) {
941            final int N = mNotificationList.size();
942            for (int i=N-1; i>=0; i--) {
943                NotificationRecord r = mNotificationList.get(i);
944
945                if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
946                                | Notification.FLAG_NO_CLEAR)) == 0) {
947                    if (r.notification.deleteIntent != null) {
948                        try {
949                            r.notification.deleteIntent.send();
950                        } catch (PendingIntent.CanceledException ex) {
951                            // do nothing - there's no relevant way to recover, and
952                            //     no reason to let this propagate
953                            Log.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
954                        }
955                    }
956                    mNotificationList.remove(i);
957                    cancelNotificationLocked(r);
958                }
959            }
960
961            updateLightsLocked();
962        }
963    }
964
965    private void updateLights() {
966        synchronized (mNotificationList) {
967            updateLightsLocked();
968        }
969    }
970
971    // lock on mNotificationList
972    private void updateLightsLocked()
973    {
974        // Battery low always shows, other states only show if charging.
975        if (mBatteryLow) {
976            if (mBatteryCharging) {
977                mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
978                    BATTERY_LOW_ARGB);
979            } else {
980                // Flash when battery is low and not charging
981                mHardware.setLightFlashing_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
982                    BATTERY_LOW_ARGB, HardwareService.LIGHT_FLASH_TIMED,
983                    BATTERY_BLINK_ON, BATTERY_BLINK_OFF);
984            }
985        } else if (mBatteryCharging) {
986            if (mBatteryFull) {
987                mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
988                        BATTERY_FULL_ARGB);
989            } else {
990                mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
991                        BATTERY_MEDIUM_ARGB);
992            }
993        } else {
994            mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_BATTERY);
995        }
996
997        // handle notification lights
998        if (mLedNotification == null) {
999            // get next notification, if any
1000            int n = mLights.size();
1001            if (n > 0) {
1002                mLedNotification = mLights.get(n-1);
1003            }
1004        }
1005        if (mLedNotification == null) {
1006            mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_NOTIFICATIONS);
1007        } else {
1008            mHardware.setLightFlashing_UNCHECKED(
1009                    HardwareService.LIGHT_ID_NOTIFICATIONS,
1010                    mLedNotification.notification.ledARGB,
1011                    HardwareService.LIGHT_FLASH_TIMED,
1012                    mLedNotification.notification.ledOnMS,
1013                    mLedNotification.notification.ledOffMS);
1014        }
1015    }
1016
1017    // lock on mNotificationList
1018    private int indexOfNotificationLocked(String pkg, String tag, int id)
1019    {
1020        ArrayList<NotificationRecord> list = mNotificationList;
1021        final int len = list.size();
1022        for (int i=0; i<len; i++) {
1023            NotificationRecord r = list.get(i);
1024            if (tag == null) {
1025                if (r.tag != null) {
1026                    continue;
1027                }
1028            } else {
1029                if (!tag.equals(r.tag)) {
1030                    continue;
1031                }
1032            }
1033            if (r.id == id && r.pkg.equals(pkg)) {
1034                return i;
1035            }
1036        }
1037        return -1;
1038    }
1039
1040    // This is here instead of StatusBarPolicy because it is an important
1041    // security feature that we don't want people customizing the platform
1042    // to accidentally lose.
1043    private void updateAdbNotification() {
1044        if (mAdbEnabled && mUsbConnected) {
1045            if ("0".equals(SystemProperties.get("persist.adb.notify"))) {
1046                return;
1047            }
1048            if (!mAdbNotificationShown) {
1049                NotificationManager notificationManager = (NotificationManager) mContext
1050                        .getSystemService(Context.NOTIFICATION_SERVICE);
1051                if (notificationManager != null) {
1052                    Resources r = mContext.getResources();
1053                    CharSequence title = r.getText(
1054                            com.android.internal.R.string.adb_active_notification_title);
1055                    CharSequence message = r.getText(
1056                            com.android.internal.R.string.adb_active_notification_message);
1057
1058                    if (mAdbNotification == null) {
1059                        mAdbNotification = new Notification();
1060                        mAdbNotification.icon = com.android.internal.R.drawable.stat_sys_warning;
1061                        mAdbNotification.when = 0;
1062                        mAdbNotification.flags = Notification.FLAG_ONGOING_EVENT;
1063                        mAdbNotification.tickerText = title;
1064                        mAdbNotification.defaults |= Notification.DEFAULT_SOUND;
1065                    }
1066
1067                    Intent intent = new Intent(
1068                            Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
1069                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1070                            Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1071                    // Note: we are hard-coding the component because this is
1072                    // an important security UI that we don't want anyone
1073                    // intercepting.
1074                    intent.setComponent(new ComponentName("com.android.settings",
1075                            "com.android.settings.DevelopmentSettings"));
1076                    PendingIntent pi = PendingIntent.getActivity(mContext, 0,
1077                            intent, 0);
1078
1079                    mAdbNotification.setLatestEventInfo(mContext, title, message, pi);
1080
1081                    mAdbNotificationShown = true;
1082                    notificationManager.notify(
1083                            com.android.internal.R.string.adb_active_notification_title,
1084                            mAdbNotification);
1085                }
1086            }
1087
1088        } else if (mAdbNotificationShown) {
1089            NotificationManager notificationManager = (NotificationManager) mContext
1090                    .getSystemService(Context.NOTIFICATION_SERVICE);
1091            if (notificationManager != null) {
1092                mAdbNotificationShown = false;
1093                notificationManager.cancel(
1094                        com.android.internal.R.string.adb_active_notification_title);
1095            }
1096        }
1097    }
1098
1099    // ======================================================================
1100    @Override
1101    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1102        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1103                != PackageManager.PERMISSION_GRANTED) {
1104            pw.println("Permission Denial: can't dump NotificationManager from from pid="
1105                    + Binder.getCallingPid()
1106                    + ", uid=" + Binder.getCallingUid());
1107            return;
1108        }
1109
1110        pw.println("Current Notification Manager state:");
1111
1112        int N;
1113
1114        synchronized (mToastQueue) {
1115            N = mToastQueue.size();
1116            if (N > 0) {
1117                pw.println("  Toast Queue:");
1118                for (int i=0; i<N; i++) {
1119                    mToastQueue.get(i).dump(pw, "    ");
1120                }
1121                pw.println("  ");
1122            }
1123
1124        }
1125
1126        synchronized (mNotificationList) {
1127            N = mNotificationList.size();
1128            if (N > 0) {
1129                pw.println("  Notification List:");
1130                for (int i=0; i<N; i++) {
1131                    mNotificationList.get(i).dump(pw, "    ", mContext);
1132                }
1133                pw.println("  ");
1134            }
1135
1136            N = mLights.size();
1137            if (N > 0) {
1138                pw.println("  Lights List:");
1139                for (int i=0; i<N; i++) {
1140                    mLights.get(i).dump(pw, "    ", mContext);
1141                }
1142                pw.println("  ");
1143            }
1144
1145            pw.println("  mSoundNotification=" + mSoundNotification);
1146            pw.println("  mSound=" + mSound);
1147            pw.println("  mVibrateNotification=" + mVibrateNotification);
1148            pw.println("  mDisabledNotifications=0x" + Integer.toHexString(mDisabledNotifications));
1149            pw.println("  mSystemReady=" + mSystemReady);
1150        }
1151    }
1152}
1153