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