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