FileDocCategorySizeDatePackage
NotificationManagerService.javaAPI DocAndroid 1.5 API34323Wed May 06 22:42:00 BST 2009com.android.server

NotificationManagerService

public class NotificationManagerService extends INotificationManager.Stub

Fields Summary
private static final String
TAG
private static final boolean
DBG
private static final int
MESSAGE_TIMEOUT
private static final int
LONG_DELAY
private static final int
SHORT_DELAY
private static final long[]
DEFAULT_VIBRATE_PATTERN
private static final int
DEFAULT_STREAM_TYPE
final android.content.Context
mContext
final android.app.IActivityManager
mAm
final android.os.IBinder
mForegroundToken
private WorkerHandler
mHandler
private com.android.server.status.StatusBarService
mStatusBarService
private HardwareService
mHardware
private NotificationRecord
mSoundNotification
private android.media.AsyncPlayer
mSound
private int
mDisabledNotifications
private NotificationRecord
mVibrateNotification
private android.os.Vibrator
mVibrator
private ArrayList
mNotificationList
private ArrayList
mToastQueue
private ArrayList
mLights
private boolean
mBatteryCharging
private boolean
mBatteryLow
private boolean
mBatteryFull
private NotificationRecord
mLedNotification
private static final int
BATTERY_LOW_ARGB
private static final int
BATTERY_MEDIUM_ARGB
private static final int
BATTERY_FULL_ARGB
private static final int
BATTERY_BLINK_ON
private static final int
BATTERY_BLINK_OFF
private static final int
EVENT_LOG_ENQUEUE
private static final int
EVENT_LOG_CANCEL
private static final int
EVENT_LOG_CANCEL_ALL
private StatusBarService.NotificationCallbacks
mNotificationCallbacks
private android.content.BroadcastReceiver
mIntentReceiver
Constructors Summary
NotificationManagerService(android.content.Context context, com.android.server.status.StatusBarService statusBar, HardwareService hardware)


       
             
    
        super();
        mContext = context;
        mHardware = hardware;
        mAm = ActivityManagerNative.getDefault();
        mSound = new AsyncPlayer(TAG);
        mSound.setUsesWakeLock(context);
        mToastQueue = new ArrayList<ToastRecord>();
        mNotificationList = new ArrayList<NotificationRecord>();
        mHandler = new WorkerHandler();
        mStatusBarService = statusBar;
        statusBar.setNotificationCallbacks(mNotificationCallbacks);

        // register for battery changed notifications
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
        mContext.registerReceiver(mIntentReceiver, filter);
    
Methods Summary
public voidcancelAll()

        synchronized (mNotificationList) {
            final int N = mNotificationList.size();
            for (int i=N-1; i>=0; i--) {
                NotificationRecord r = mNotificationList.get(i);

                if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
                                | Notification.FLAG_NO_CLEAR)) == 0) {
                    if (r.notification.deleteIntent != null) {
                        try {
                            r.notification.deleteIntent.send();
                        } catch (PendingIntent.CanceledException ex) {
                            // do nothing - there's no relevant way to recover, and
                            //     no reason to let this propagate
                            Log.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
                        }
                    }
                    mNotificationList.remove(i);
                    cancelNotificationLocked(r);
                }
            }

            updateLightsLocked();
        }
    
public voidcancelAllNotifications(java.lang.String pkg)

        cancelAllNotificationsInt(pkg, 0);
    
private voidcancelAllNotificationsInt(java.lang.String pkg, int mustHaveFlags)
Cancels all notifications from a given package that have all of the {@code mustHaveFlags}.

        EventLog.writeEvent(EVENT_LOG_CANCEL_ALL, pkg, mustHaveFlags);

        synchronized (mNotificationList) {
            final int N = mNotificationList.size();
            boolean canceledSomething = false;
            for (int i = N-1; i >= 0; --i) {
                NotificationRecord r = mNotificationList.get(i);
                if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
                    continue;
                }
                if (!r.pkg.equals(pkg)) {
                    continue;
                }
                mNotificationList.remove(i);
                cancelNotificationLocked(r);
                canceledSomething = true;
            }
            if (canceledSomething) {
                updateLightsLocked();
            }
        }
    
private voidcancelNotification(java.lang.String pkg, int id, int mustHaveFlags)
Cancels a notification ONLY if it has all of the {@code mustHaveFlags}.

        EventLog.writeEvent(EVENT_LOG_CANCEL, pkg, id, mustHaveFlags);

        synchronized (mNotificationList) {
            NotificationRecord r = null;

            int index = indexOfNotificationLocked(pkg, id);
            if (index >= 0) {
                r = mNotificationList.get(index);
                
                if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
                    return;
                }
                
                mNotificationList.remove(index);

                cancelNotificationLocked(r);
                updateLightsLocked();
            }
        }
    
public voidcancelNotification(java.lang.String pkg, int id)

        cancelNotification(pkg, id, 0);
    
private voidcancelNotificationLocked(com.android.server.NotificationManagerService$NotificationRecord r)

        // status bar
        if (r.notification.icon != 0) {
            long identity = Binder.clearCallingIdentity();
            try {
                mStatusBarService.removeIcon(r.statusBarKey);
            }
            finally {
                Binder.restoreCallingIdentity(identity);
            }
            r.statusBarKey = null;
        }

        // sound
        if (mSoundNotification == r) {
            mSoundNotification = null;
            long identity = Binder.clearCallingIdentity();
            try {
                mSound.stop();
            }
            finally {
                Binder.restoreCallingIdentity(identity);
            }
        }

        // vibrate
        if (mVibrateNotification == r) {
            mVibrateNotification = null;
            long identity = Binder.clearCallingIdentity();
            try {
                mVibrator.cancel();
            }
            finally {
                Binder.restoreCallingIdentity(identity);
            }
        }

        // light
        mLights.remove(r);
        if (mLedNotification == r) {
            mLedNotification = null;
        }
    
public voidcancelToast(java.lang.String pkg, android.app.ITransientNotification callback)

        Log.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback);

        if (pkg == null || callback == null) {
            Log.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback);
            return ;
        }

        synchronized (mToastQueue) {
            long callingId = Binder.clearCallingIdentity();
            try {
                int index = indexOfToastLocked(pkg, callback);
                if (index >= 0) {
                    cancelToastLocked(index);
                } else {
                    Log.w(TAG, "Toast already cancelled. pkg=" + pkg + " callback=" + callback);
                }
            } finally {
                Binder.restoreCallingIdentity(callingId);
            }
        }
    
private voidcancelToastLocked(int index)

        ToastRecord record = mToastQueue.get(index);
        try {
            record.callback.hide();
        } catch (RemoteException e) {
            Log.w(TAG, "Object died trying to hide notification " + record.callback
                    + " in package " + record.pkg);
            // don't worry about this, we're about to remove it from
            // the list anyway
        }
        mToastQueue.remove(index);
        keepProcessAliveLocked(record.pid);
        if (mToastQueue.size() > 0) {
            // Show the next one. If the callback fails, this will remove
            // it from the list, so don't assume that the list hasn't changed
            // after this point.
            showNextToastLocked();
        }
    
protected voiddump(java.io.FileDescriptor fd, java.io.PrintWriter pw, java.lang.String[] args)

        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                != PackageManager.PERMISSION_GRANTED) {
            pw.println("Permission Denial: can't dump NotificationManager from from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid());
            return;
        }
        
        pw.println("Current Notification Manager state:");

        int N;

        synchronized (mToastQueue) {
            N = mToastQueue.size();
            if (N > 0) {
                pw.println("  Toast Queue:");
                for (int i=0; i<N; i++) {
                    mToastQueue.get(i).dump(pw, "    ");
                }
                pw.println("  ");
            }
            
        }

        synchronized (mNotificationList) {
            N = mNotificationList.size();
            if (N > 0) {
                pw.println("  Notification List:");
                for (int i=0; i<N; i++) {
                    mNotificationList.get(i).dump(pw, "    ", mContext);
                }
                pw.println("  ");
            }
            
            N = mLights.size();
            if (N > 0) {
                pw.println("  Lights List:");
                for (int i=0; i<N; i++) {
                    mLights.get(i).dump(pw, "    ", mContext);
                }
                pw.println("  ");
            }
            
            pw.println("  mSoundNotification=" + mSoundNotification);
            pw.println("  mSound=" + mSound);
            pw.println("  mVibrateNotification=" + mVibrateNotification);
        }
    
public voidenqueueNotification(java.lang.String pkg, int id, android.app.Notification notification, int[] idOut)

        // This conditional is a dirty hack to limit the logging done on
        //     behalf of the download manager without affecting other apps.
        if (!pkg.equals("com.android.providers.downloads")
                || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
            EventLog.writeEvent(EVENT_LOG_ENQUEUE, pkg, id, notification.toString());
        }

        if (pkg == null || notification == null) {
            throw new IllegalArgumentException("null not allowed: pkg=" + pkg
                    + " id=" + id + " notification=" + notification);
        }
        if (notification.icon != 0) {
            if (notification.contentView == null) {
                throw new IllegalArgumentException("contentView required: pkg=" + pkg
                        + " id=" + id + " notification=" + notification);
            }
            if (notification.contentIntent == null) {
                throw new IllegalArgumentException("contentIntent required: pkg=" + pkg
                        + " id=" + id + " notification=" + notification);
            }
        }

        synchronized (mNotificationList) {
            NotificationRecord r = new NotificationRecord(pkg, id, notification);
            NotificationRecord old = null;

            int index = indexOfNotificationLocked(pkg, id);
            if (index < 0) {
                mNotificationList.add(r);
            } else {
                old = mNotificationList.remove(index);
                mNotificationList.add(index, r);
            }
            if (notification.icon != 0) {
                IconData icon = IconData.makeIcon(null, pkg, notification.icon,
                                                    notification.iconLevel,
                                                    notification.number);
                CharSequence truncatedTicker = notification.tickerText;
                
                // TODO: make this restriction do something smarter like never fill
                // more than two screens.  "Why would anyone need more than 80 characters." :-/
                final int maxTickerLen = 80;
                if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) {
                    truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen);
                }

                NotificationData n = new NotificationData();
                    n.id = id;
                    n.pkg = pkg;
                    n.when = notification.when;
                    n.tickerText = truncatedTicker;
                    n.ongoingEvent = (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
                    if (!n.ongoingEvent && (notification.flags & Notification.FLAG_NO_CLEAR) == 0) {
                        n.clearable = true;
                    }
                    n.contentView = notification.contentView;
                    n.contentIntent = notification.contentIntent;
                    n.deleteIntent = notification.deleteIntent;
                if (old != null && old.statusBarKey != null) {
                    r.statusBarKey = old.statusBarKey;
                    long identity = Binder.clearCallingIdentity();
                    try {
                        mStatusBarService.updateIcon(r.statusBarKey, icon, n);
                    }
                    finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                } else {
                    long identity = Binder.clearCallingIdentity();
                    try {
                        r.statusBarKey = mStatusBarService.addIcon(icon, n);
                        mHardware.pulseBreathingLight();
                    }
                    finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }
            } else {
                if (old != null && old.statusBarKey != null) {
                    long identity = Binder.clearCallingIdentity();
                    try {
                        mStatusBarService.removeIcon(old.statusBarKey);
                    }
                    finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }
            }

            // If we're not supposed to beep, vibrate, etc. then don't.
            if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
                    && (!(old != null
                        && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))) {
                // sound
                final boolean useDefaultSound =
                    (notification.defaults & Notification.DEFAULT_SOUND) != 0; 
                if (useDefaultSound || notification.sound != null) {
                    Uri uri;
                    if (useDefaultSound) {
                        uri = Settings.System.DEFAULT_NOTIFICATION_URI;
                    } else {
                        uri = notification.sound;
                    }
                    boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
                    int audioStreamType;
                    if (notification.audioStreamType >= 0) {
                        audioStreamType = notification.audioStreamType;
                    } else {
                        audioStreamType = DEFAULT_STREAM_TYPE;
                    }
                    mSoundNotification = r;
                    long identity = Binder.clearCallingIdentity();
                    try {
                        mSound.play(mContext, uri, looping, audioStreamType);
                    }
                    finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }

                // vibrate
                final AudioManager audioManager = (AudioManager) mContext
                        .getSystemService(Context.AUDIO_SERVICE);
                final boolean useDefaultVibrate =
                    (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; 
                if ((useDefaultVibrate || notification.vibrate != null)
                        && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
                    mVibrateNotification = r;

                    mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN 
                                                        : notification.vibrate,
                              ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
                }
            }

            // this option doesn't shut off the lights

            // light
            // the most recent thing gets the light
            mLights.remove(old);
            if (mLedNotification == old) {
                mLedNotification = null;
            }
            //Log.i(TAG, "notification.lights="
            //        + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
            if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
                mLights.add(r);
                updateLightsLocked();
            } else {
                if (old != null
                        && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
                    updateLightsLocked();
                }
            }
        }

        idOut[0] = id;
    
public voidenqueueToast(java.lang.String pkg, android.app.ITransientNotification callback, int duration)

        Log.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration);

        if (pkg == null || callback == null) {
            Log.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
            return ;
        }

        synchronized (mToastQueue) {
            int callingPid = Binder.getCallingPid();
            long callingId = Binder.clearCallingIdentity();
            try {
                ToastRecord record;
                int index = indexOfToastLocked(pkg, callback);
                // If it's already in the queue, we update it in place, we don't
                // move it to the end of the queue.
                if (index >= 0) {
                    record = mToastQueue.get(index);
                    record.update(duration);
                } else {
                    record = new ToastRecord(callingPid, pkg, callback, duration);
                    mToastQueue.add(record);
                    index = mToastQueue.size() - 1;
                    keepProcessAliveLocked(callingPid);
                }
                // If it's at index 0, it's the current toast.  It doesn't matter if it's
                // new or just been updated.  Call back and tell it to show itself.
                // If the callback fails, this will remove it from the list, so don't
                // assume that it's valid after this.
                if (index == 0) {
                    showNextToastLocked();
                }
            } finally {
                Binder.restoreCallingIdentity(callingId);
            }
        }
    
private voidhandleTimeout(com.android.server.NotificationManagerService$ToastRecord record)

        if (DBG) Log.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
        synchronized (mToastQueue) {
            int index = indexOfToastLocked(record.pkg, record.callback);
            if (index >= 0) {
                cancelToastLocked(index);
            }
        }
    
private static java.lang.StringidDebugString(android.content.Context baseContext, java.lang.String packageName, int id)


             
        Context c = null;

        if (packageName != null) {
            try {
                c = baseContext.createPackageContext(packageName, 0);
            } catch (NameNotFoundException e) {
                c = baseContext;
            }
        } else {
            c = baseContext;
        }

        String pkg;
        String type;
        String name;

        Resources r = c.getResources();
        try {
            return r.getResourceName(id);
        } catch (Resources.NotFoundException e) {
            return "<name unknown>";
        }
    
private intindexOfNotificationLocked(java.lang.String pkg, int id)

        ArrayList<NotificationRecord> list = mNotificationList;
        final int len = list.size();
        for (int i=0; i<len; i++) {
            NotificationRecord r = list.get(i);
            if (r.id == id && r.pkg.equals(pkg)) {
                return i;
            }
        }
        return -1;
    
private intindexOfToastLocked(java.lang.String pkg, android.app.ITransientNotification callback)

        IBinder cbak = callback.asBinder();
        ArrayList<ToastRecord> list = mToastQueue;
        int len = list.size();
        for (int i=0; i<len; i++) {
            ToastRecord r = list.get(i);
            if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
                return i;
            }
        }
        return -1;
    
private voidkeepProcessAliveLocked(int pid)

        int toastCount = 0; // toasts from this pid
        ArrayList<ToastRecord> list = mToastQueue;
        int N = list.size();
        for (int i=0; i<N; i++) {
            ToastRecord r = list.get(i);
            if (r.pid == pid) {
                toastCount++;
            }
        }
        try {
            mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
        } catch (RemoteException e) {
            // Shouldn't happen.
        }
    
private voidscheduleTimeoutLocked(com.android.server.NotificationManagerService$ToastRecord r, boolean immediate)

        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
        long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
        mHandler.removeCallbacksAndMessages(r);
        mHandler.sendMessageDelayed(m, delay);
    
private voidshowNextToastLocked()

        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            if (DBG) Log.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
            try {
                record.callback.show();
                scheduleTimeoutLocked(record, false);
                return;
            } catch (RemoteException e) {
                Log.w(TAG, "Object died trying to show notification " + record.callback
                        + " in package " + record.pkg);
                // remove it from the list and let the process die
                int index = mToastQueue.indexOf(record);
                if (index >= 0) {
                    mToastQueue.remove(index);
                }
                keepProcessAliveLocked(record.pid);
                if (mToastQueue.size() > 0) {
                    record = mToastQueue.get(0);
                } else {
                    record = null;
                }
            }
        }
    
private voidupdateLights()

        synchronized (mNotificationList) {
            updateLightsLocked();
        }
    
private voidupdateLightsLocked()

        // Battery low always shows, other states only show if charging.
        if (mBatteryLow) {
            mHardware.setLightFlashing_UNCHECKED(HardwareService.LIGHT_ID_BATTERY, BATTERY_LOW_ARGB,
                    HardwareService.LIGHT_FLASH_TIMED, BATTERY_BLINK_ON, BATTERY_BLINK_OFF);
        } else if (mBatteryCharging) {
            if (mBatteryFull) {
                mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
                        BATTERY_FULL_ARGB);
            } else {
                mHardware.setLightColor_UNCHECKED(HardwareService.LIGHT_ID_BATTERY,
                        BATTERY_MEDIUM_ARGB);
            }
        } else {
            mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_BATTERY);
        }

        // handle notification lights
        if (mLedNotification == null) {
            // get next notification, if any
            int n = mLights.size();
            if (n > 0) {
                mLedNotification = mLights.get(n-1);
            }
        }
        if (mLedNotification == null) {
            mHardware.setLightOff_UNCHECKED(HardwareService.LIGHT_ID_NOTIFICATIONS);
        } else {
            mHardware.setLightFlashing_UNCHECKED(
                    HardwareService.LIGHT_ID_NOTIFICATIONS,
                    mLedNotification.notification.ledARGB,
                    HardwareService.LIGHT_FLASH_TIMED,
                    mLedNotification.notification.ledOnMS,
                    mLedNotification.notification.ledOffMS);
        }