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