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