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