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