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