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