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