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