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