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