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