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