NotificationManagerServicepublic 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 void | cancelAll()
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 void | cancelAllNotifications(java.lang.String pkg)
cancelAllNotificationsInt(pkg, 0);
| private void | cancelAllNotificationsInt(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 void | cancelNotification(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 void | cancelNotification(java.lang.String pkg, int id)
cancelNotification(pkg, id, 0);
| private void | cancelNotificationLocked(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 void | cancelToast(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 void | cancelToastLocked(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 void | dump(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 void | enqueueNotification(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 void | enqueueToast(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 void | handleTimeout(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.String | idDebugString(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 int | indexOfNotificationLocked(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 int | indexOfToastLocked(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 void | keepProcessAliveLocked(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 void | scheduleTimeoutLocked(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 void | showNextToastLocked()
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 void | updateLights()
synchronized (mNotificationList) {
updateLightsLocked();
}
| private void | updateLightsLocked()
// 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);
}
|
|