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