diff options
Diffstat (limited to 'services/java/com/android/server/NotificationManagerService.java')
-rw-r--r-- | services/java/com/android/server/NotificationManagerService.java | 2379 |
1 files changed, 0 insertions, 2379 deletions
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java deleted file mode 100644 index dedc9bd..0000000 --- a/services/java/com/android/server/NotificationManagerService.java +++ /dev/null @@ -1,2379 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; -import static org.xmlpull.v1.XmlPullParser.END_TAG; -import static org.xmlpull.v1.XmlPullParser.START_TAG; - -import android.app.ActivityManager; -import android.app.ActivityManagerNative; -import android.app.AppGlobals; -import android.app.AppOpsManager; -import android.app.IActivityManager; -import android.app.INotificationManager; -import android.app.ITransientNotification; -import android.app.Notification; -import android.app.PendingIntent; -import android.app.StatusBarManager; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.graphics.Bitmap; -import android.media.AudioManager; -import android.media.IAudioService; -import android.media.IRingtonePlayer; -import android.net.Uri; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.Process; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.UserHandle; -import android.os.UserManager; -import android.os.Vibrator; -import android.provider.Settings; -import android.service.notification.INotificationListener; -import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.AtomicFile; -import android.util.EventLog; -import android.util.Log; -import android.util.Slog; -import android.util.Xml; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.widget.Toast; - -import com.android.internal.R; - -import com.android.internal.notification.NotificationScorer; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; -import java.lang.reflect.Array; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Set; - -import libcore.io.IoUtils; - - -/** {@hide} */ -public class NotificationManagerService extends INotificationManager.Stub -{ - private static final String TAG = "NotificationService"; - private static final boolean DBG = false; - - private static final int MAX_PACKAGE_NOTIFICATIONS = 50; - - // message codes - private static final int MESSAGE_TIMEOUT = 2; - - private static final int LONG_DELAY = 3500; // 3.5 seconds - private static final int SHORT_DELAY = 2000; // 2 seconds - - private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; - private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps - - private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION; - private static final boolean SCORE_ONGOING_HIGHER = false; - - private static final int JUNK_SCORE = -1000; - private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; - private static final int SCORE_DISPLAY_THRESHOLD = Notification.PRIORITY_MIN * NOTIFICATION_PRIORITY_MULTIPLIER; - - // Notifications with scores below this will not interrupt the user, either via LED or - // sound or vibration - private static final int SCORE_INTERRUPTION_THRESHOLD = - Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER; - - private static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true; - private static final boolean ENABLE_BLOCKED_TOASTS = true; - - private static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":"; - - final Context mContext; - final IActivityManager mAm; - final UserManager mUserManager; - final IBinder mForegroundToken = new Binder(); - - private WorkerHandler mHandler; - private StatusBarManagerService mStatusBar; - private LightsService.Light mNotificationLight; - private LightsService.Light mAttentionLight; - - private int mDefaultNotificationColor; - private int mDefaultNotificationLedOn; - private int mDefaultNotificationLedOff; - - private long[] mDefaultVibrationPattern; - private long[] mFallbackVibrationPattern; - - private boolean mSystemReady; - private int mDisabledNotifications; - - private NotificationRecord mSoundNotification; - private NotificationRecord mVibrateNotification; - - private IAudioService mAudioService; - private Vibrator mVibrator; - - // for enabling and disabling notification pulse behavior - private boolean mScreenOn = true; - private boolean mInCall = false; - private boolean mNotificationPulseEnabled; - - // used as a mutex for access to all active notifications & listeners - private final ArrayList<NotificationRecord> mNotificationList = - new ArrayList<NotificationRecord>(); - - private ArrayList<ToastRecord> mToastQueue; - - private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>(); - private NotificationRecord mLedNotification; - - private final AppOpsManager mAppOps; - - // contains connections to all connected listeners, including app services - // and system listeners - private ArrayList<NotificationListenerInfo> mListeners - = new ArrayList<NotificationListenerInfo>(); - // things that will be put into mListeners as soon as they're ready - private ArrayList<String> mServicesBinding = new ArrayList<String>(); - // lists the component names of all enabled (and therefore connected) listener - // app services for the current user only - private HashSet<ComponentName> mEnabledListenersForCurrentUser - = new HashSet<ComponentName>(); - // Just the packages from mEnabledListenersForCurrentUser - private HashSet<String> mEnabledListenerPackageNames = new HashSet<String>(); - - // Notification control database. For now just contains disabled packages. - private AtomicFile mPolicyFile; - private HashSet<String> mBlockedPackages = new HashSet<String>(); - - private static final int DB_VERSION = 1; - - private static final String TAG_BODY = "notification-policy"; - private static final String ATTR_VERSION = "version"; - - private static final String TAG_BLOCKED_PKGS = "blocked-packages"; - private static final String TAG_PACKAGE = "package"; - private static final String ATTR_NAME = "name"; - - private final ArrayList<NotificationScorer> mScorers = new ArrayList<NotificationScorer>(); - - private class NotificationListenerInfo implements DeathRecipient { - INotificationListener listener; - ComponentName component; - int userid; - boolean isSystem; - ServiceConnection connection; - - public NotificationListenerInfo(INotificationListener listener, ComponentName component, - int userid, boolean isSystem) { - this.listener = listener; - this.component = component; - this.userid = userid; - this.isSystem = isSystem; - this.connection = null; - } - - public NotificationListenerInfo(INotificationListener listener, ComponentName component, - int userid, ServiceConnection connection) { - this.listener = listener; - this.component = component; - this.userid = userid; - this.isSystem = false; - this.connection = connection; - } - - boolean enabledAndUserMatches(StatusBarNotification sbn) { - final int nid = sbn.getUserId(); - if (!isEnabledForCurrentUser()) { - return false; - } - if (this.userid == UserHandle.USER_ALL) return true; - return (nid == UserHandle.USER_ALL || nid == this.userid); - } - - public void notifyPostedIfUserMatch(StatusBarNotification sbn) { - if (!enabledAndUserMatches(sbn)) { - return; - } - try { - listener.onNotificationPosted(sbn); - } catch (RemoteException ex) { - Log.e(TAG, "unable to notify listener (posted): " + listener, ex); - } - } - - public void notifyRemovedIfUserMatch(StatusBarNotification sbn) { - if (!enabledAndUserMatches(sbn)) return; - try { - listener.onNotificationRemoved(sbn); - } catch (RemoteException ex) { - Log.e(TAG, "unable to notify listener (removed): " + listener, ex); - } - } - - @Override - public void binderDied() { - // Remove the listener, but don't unbind from the service. The system will bring the - // service back up, and the onServiceConnected handler will readd the listener with the - // new binding. If this isn't a bound service, and is just a registered - // INotificationListener, just removing it from the list is all we need to do anyway. - removeListenerImpl(this.listener, this.userid); - } - - /** convenience method for looking in mEnabledListenersForCurrentUser */ - public boolean isEnabledForCurrentUser() { - if (this.isSystem) return true; - if (this.connection == null) return false; - return mEnabledListenersForCurrentUser.contains(this.component); - } - } - - private static class Archive { - static final int BUFFER_SIZE = 250; - ArrayDeque<StatusBarNotification> mBuffer = new ArrayDeque<StatusBarNotification>(BUFFER_SIZE); - - public Archive() { - } - - public String toString() { - final StringBuilder sb = new StringBuilder(); - final int N = mBuffer.size(); - sb.append("Archive ("); - sb.append(N); - sb.append(" notification"); - sb.append((N==1)?")":"s)"); - return sb.toString(); - } - - public void record(StatusBarNotification nr) { - if (mBuffer.size() == BUFFER_SIZE) { - mBuffer.removeFirst(); - } - - // We don't want to store the heavy bits of the notification in the archive, - // but other clients in the system process might be using the object, so we - // store a (lightened) copy. - mBuffer.addLast(nr.cloneLight()); - } - - - public void clear() { - mBuffer.clear(); - } - - public Iterator<StatusBarNotification> descendingIterator() { - return mBuffer.descendingIterator(); - } - public Iterator<StatusBarNotification> ascendingIterator() { - return mBuffer.iterator(); - } - public Iterator<StatusBarNotification> filter( - final Iterator<StatusBarNotification> iter, final String pkg, final int userId) { - return new Iterator<StatusBarNotification>() { - StatusBarNotification mNext = findNext(); - - private StatusBarNotification findNext() { - while (iter.hasNext()) { - StatusBarNotification nr = iter.next(); - if ((pkg == null || nr.getPackageName() == pkg) - && (userId == UserHandle.USER_ALL || nr.getUserId() == userId)) { - return nr; - } - } - return null; - } - - @Override - public boolean hasNext() { - return mNext == null; - } - - @Override - public StatusBarNotification next() { - StatusBarNotification next = mNext; - if (next == null) { - throw new NoSuchElementException(); - } - mNext = findNext(); - return next; - } - - @Override - public void remove() { - iter.remove(); - } - }; - } - - public StatusBarNotification[] getArray(int count) { - if (count == 0) count = Archive.BUFFER_SIZE; - final StatusBarNotification[] a - = new StatusBarNotification[Math.min(count, mBuffer.size())]; - Iterator<StatusBarNotification> iter = descendingIterator(); - int i=0; - while (iter.hasNext() && i < count) { - a[i++] = iter.next(); - } - return a; - } - - public StatusBarNotification[] getArray(int count, String pkg, int userId) { - if (count == 0) count = Archive.BUFFER_SIZE; - final StatusBarNotification[] a - = new StatusBarNotification[Math.min(count, mBuffer.size())]; - Iterator<StatusBarNotification> iter = filter(descendingIterator(), pkg, userId); - int i=0; - while (iter.hasNext() && i < count) { - a[i++] = iter.next(); - } - return a; - } - - } - - Archive mArchive = new Archive(); - - private void loadBlockDb() { - synchronized(mBlockedPackages) { - if (mPolicyFile == null) { - File dir = new File("/data/system"); - mPolicyFile = new AtomicFile(new File(dir, "notification_policy.xml")); - - mBlockedPackages.clear(); - - FileInputStream infile = null; - try { - infile = mPolicyFile.openRead(); - final XmlPullParser parser = Xml.newPullParser(); - parser.setInput(infile, null); - - int type; - String tag; - int version = DB_VERSION; - while ((type = parser.next()) != END_DOCUMENT) { - tag = parser.getName(); - if (type == START_TAG) { - if (TAG_BODY.equals(tag)) { - version = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION)); - } else if (TAG_BLOCKED_PKGS.equals(tag)) { - while ((type = parser.next()) != END_DOCUMENT) { - tag = parser.getName(); - if (TAG_PACKAGE.equals(tag)) { - mBlockedPackages.add(parser.getAttributeValue(null, ATTR_NAME)); - } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) { - break; - } - } - } - } - } - } catch (FileNotFoundException e) { - // No data yet - } catch (IOException e) { - Log.wtf(TAG, "Unable to read blocked notifications database", e); - } catch (NumberFormatException e) { - Log.wtf(TAG, "Unable to parse blocked notifications database", e); - } catch (XmlPullParserException e) { - Log.wtf(TAG, "Unable to parse blocked notifications database", e); - } finally { - IoUtils.closeQuietly(infile); - } - } - } - } - - /** - * Use this when you just want to know if notifications are OK for this package. - */ - public boolean areNotificationsEnabledForPackage(String pkg, int uid) { - checkCallerIsSystem(); - return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg) - == AppOpsManager.MODE_ALLOWED); - } - - /** Use this when you actually want to post a notification or toast. - * - * Unchecked. Not exposed via Binder, but can be called in the course of enqueue*(). - */ - private boolean noteNotificationOp(String pkg, int uid) { - if (mAppOps.noteOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg) - != AppOpsManager.MODE_ALLOWED) { - Slog.v(TAG, "notifications are disabled by AppOps for " + pkg); - return false; - } - return true; - } - - public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) { - checkCallerIsSystem(); - - Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg); - - mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg, - enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); - - // Now, cancel any outstanding notifications that are part of a just-disabled app - if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) { - cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid)); - } - } - - - private static String idDebugString(Context baseContext, 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>"; - } - } - - /** - * System-only API for getting a list of current (i.e. not cleared) notifications. - * - * Requires ACCESS_NOTIFICATIONS which is signature|system. - */ - @Override - public StatusBarNotification[] getActiveNotifications(String callingPkg) { - // enforce() will ensure the calling uid has the correct permission - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS, - "NotificationManagerService.getActiveNotifications"); - - StatusBarNotification[] tmp = null; - int uid = Binder.getCallingUid(); - - // noteOp will check to make sure the callingPkg matches the uid - if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg) - == AppOpsManager.MODE_ALLOWED) { - synchronized (mNotificationList) { - tmp = new StatusBarNotification[mNotificationList.size()]; - final int N = mNotificationList.size(); - for (int i=0; i<N; i++) { - tmp[i] = mNotificationList.get(i).sbn; - } - } - } - return tmp; - } - - /** - * System-only API for getting a list of recent (cleared, no longer shown) notifications. - * - * Requires ACCESS_NOTIFICATIONS which is signature|system. - */ - @Override - public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count) { - // enforce() will ensure the calling uid has the correct permission - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS, - "NotificationManagerService.getHistoricalNotifications"); - - StatusBarNotification[] tmp = null; - int uid = Binder.getCallingUid(); - - // noteOp will check to make sure the callingPkg matches the uid - if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg) - == AppOpsManager.MODE_ALLOWED) { - synchronized (mArchive) { - tmp = mArchive.getArray(count); - } - } - return tmp; - } - - /** - * Remove notification access for any services that no longer exist. - */ - void disableNonexistentListeners() { - int currentUser = ActivityManager.getCurrentUser(); - String flatIn = Settings.Secure.getStringForUser( - mContext.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - currentUser); - if (!TextUtils.isEmpty(flatIn)) { - if (DBG) Slog.v(TAG, "flat before: " + flatIn); - PackageManager pm = mContext.getPackageManager(); - List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser( - new Intent(NotificationListenerService.SERVICE_INTERFACE), - PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, - currentUser); - - Set<ComponentName> installed = new HashSet<ComponentName>(); - for (int i = 0, count = installedServices.size(); i < count; i++) { - ResolveInfo resolveInfo = installedServices.get(i); - ServiceInfo info = resolveInfo.serviceInfo; - - if (!android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals( - info.permission)) { - Slog.w(TAG, "Skipping notification listener service " - + info.packageName + "/" + info.name - + ": it does not require the permission " - + android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE); - continue; - } - installed.add(new ComponentName(info.packageName, info.name)); - } - - String flatOut = ""; - if (!installed.isEmpty()) { - String[] enabled = flatIn.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); - ArrayList<String> remaining = new ArrayList<String>(enabled.length); - for (int i = 0; i < enabled.length; i++) { - ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]); - if (installed.contains(enabledComponent)) { - remaining.add(enabled[i]); - } - } - flatOut = TextUtils.join(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR, remaining); - } - if (DBG) Slog.v(TAG, "flat after: " + flatOut); - if (!flatIn.equals(flatOut)) { - Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - flatOut, currentUser); - } - } - } - - /** - * Called whenever packages change, the user switches, or ENABLED_NOTIFICATION_LISTENERS - * is altered. (For example in response to USER_SWITCHED in our broadcast receiver) - */ - void rebindListenerServices() { - final int currentUser = ActivityManager.getCurrentUser(); - String flat = Settings.Secure.getStringForUser( - mContext.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - currentUser); - - NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()]; - final ArrayList<ComponentName> toAdd; - - synchronized (mNotificationList) { - // unbind and remove all existing listeners - toRemove = mListeners.toArray(toRemove); - - toAdd = new ArrayList<ComponentName>(); - final HashSet<ComponentName> newEnabled = new HashSet<ComponentName>(); - final HashSet<String> newPackages = new HashSet<String>(); - - // decode the list of components - if (flat != null) { - String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); - for (int i=0; i<components.length; i++) { - final ComponentName component - = ComponentName.unflattenFromString(components[i]); - if (component != null) { - newEnabled.add(component); - toAdd.add(component); - newPackages.add(component.getPackageName()); - } - } - - mEnabledListenersForCurrentUser = newEnabled; - mEnabledListenerPackageNames = newPackages; - } - } - - for (NotificationListenerInfo info : toRemove) { - final ComponentName component = info.component; - final int oldUser = info.userid; - Slog.v(TAG, "disabling notification listener for user " + oldUser + ": " + component); - unregisterListenerService(component, info.userid); - } - - final int N = toAdd.size(); - for (int i=0; i<N; i++) { - final ComponentName component = toAdd.get(i); - Slog.v(TAG, "enabling notification listener for user " + currentUser + ": " - + component); - registerListenerService(component, currentUser); - } - } - - /** - * Register a listener binder directly with the notification manager. - * - * Only works with system callers. Apps should extend - * {@link android.service.notification.NotificationListenerService}. - */ - @Override - public void registerListener(final INotificationListener listener, - final ComponentName component, final int userid) { - checkCallerIsSystem(); - - synchronized (mNotificationList) { - try { - NotificationListenerInfo info - = new NotificationListenerInfo(listener, component, userid, true); - listener.asBinder().linkToDeath(info, 0); - mListeners.add(info); - } catch (RemoteException e) { - // already dead - } - } - } - - /** - * Version of registerListener that takes the name of a - * {@link android.service.notification.NotificationListenerService} to bind to. - * - * This is the mechanism by which third parties may subscribe to notifications. - */ - private void registerListenerService(final ComponentName name, final int userid) { - checkCallerIsSystem(); - - if (DBG) Slog.v(TAG, "registerListenerService: " + name + " u=" + userid); - - synchronized (mNotificationList) { - final String servicesBindingTag = name.toString() + "/" + userid; - if (mServicesBinding.contains(servicesBindingTag)) { - // stop registering this thing already! we're working on it - return; - } - mServicesBinding.add(servicesBindingTag); - - final int N = mListeners.size(); - for (int i=N-1; i>=0; i--) { - final NotificationListenerInfo info = mListeners.get(i); - if (name.equals(info.component) - && info.userid == userid) { - // cut old connections - if (DBG) Slog.v(TAG, " disconnecting old listener: " + info.listener); - mListeners.remove(i); - if (info.connection != null) { - mContext.unbindService(info.connection); - } - } - } - - Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE); - intent.setComponent(name); - - intent.putExtra(Intent.EXTRA_CLIENT_LABEL, - R.string.notification_listener_binding_label); - intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( - mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0)); - - try { - if (DBG) Slog.v(TAG, "binding: " + intent); - if (!mContext.bindServiceAsUser(intent, - new ServiceConnection() { - INotificationListener mListener; - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - synchronized (mNotificationList) { - mServicesBinding.remove(servicesBindingTag); - try { - mListener = INotificationListener.Stub.asInterface(service); - NotificationListenerInfo info = new NotificationListenerInfo( - mListener, name, userid, this); - service.linkToDeath(info, 0); - mListeners.add(info); - } catch (RemoteException e) { - // already dead - } - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - Slog.v(TAG, "notification listener connection lost: " + name); - } - }, - Context.BIND_AUTO_CREATE, - new UserHandle(userid))) - { - mServicesBinding.remove(servicesBindingTag); - Slog.w(TAG, "Unable to bind listener service: " + intent); - return; - } - } catch (SecurityException ex) { - Slog.e(TAG, "Unable to bind listener service: " + intent, ex); - return; - } - } - } - - /** - * Removes a listener from the list and unbinds from its service. - */ - public void unregisterListener(final INotificationListener listener, final int userid) { - if (listener == null) return; - - NotificationListenerInfo info = removeListenerImpl(listener, userid); - if (info != null && info.connection != null) { - mContext.unbindService(info.connection); - } - } - - /** - * Removes a listener from the list but does not unbind from the listener's service. - * - * @return the removed listener. - */ - NotificationListenerInfo removeListenerImpl( - final INotificationListener listener, final int userid) { - NotificationListenerInfo listenerInfo = null; - synchronized (mNotificationList) { - final int N = mListeners.size(); - for (int i=N-1; i>=0; i--) { - final NotificationListenerInfo info = mListeners.get(i); - if (info.listener.asBinder() == listener.asBinder() - && info.userid == userid) { - listenerInfo = mListeners.remove(i); - } - } - } - return listenerInfo; - } - - /** - * Remove a listener service for the given user by ComponentName - */ - private void unregisterListenerService(ComponentName name, int userid) { - checkCallerIsSystem(); - - synchronized (mNotificationList) { - final int N = mListeners.size(); - for (int i=N-1; i>=0; i--) { - final NotificationListenerInfo info = mListeners.get(i); - if (name.equals(info.component) - && info.userid == userid) { - mListeners.remove(i); - if (info.connection != null) { - try { - mContext.unbindService(info.connection); - } catch (IllegalArgumentException ex) { - // something happened to the service: we think we have a connection - // but it's bogus. - Slog.e(TAG, "Listener " + name + " could not be unbound: " + ex); - } - } - } - } - } - } - - /** - * asynchronously notify all listeners about a new notification - */ - private void notifyPostedLocked(NotificationRecord n) { - // make a copy in case changes are made to the underlying Notification object - final StatusBarNotification sbn = n.sbn.clone(); - for (final NotificationListenerInfo info : mListeners) { - mHandler.post(new Runnable() { - @Override - public void run() { - info.notifyPostedIfUserMatch(sbn); - }}); - } - } - - /** - * asynchronously notify all listeners about a removed notification - */ - private void notifyRemovedLocked(NotificationRecord n) { - // make a copy in case changes are made to the underlying Notification object - // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the notification - final StatusBarNotification sbn_light = n.sbn.cloneLight(); - - for (final NotificationListenerInfo info : mListeners) { - mHandler.post(new Runnable() { - @Override - public void run() { - info.notifyRemovedIfUserMatch(sbn_light); - }}); - } - } - - // -- APIs to support listeners clicking/clearing notifications -- - - private NotificationListenerInfo checkListenerToken(INotificationListener listener) { - final IBinder token = listener.asBinder(); - final int N = mListeners.size(); - for (int i=0; i<N; i++) { - final NotificationListenerInfo info = mListeners.get(i); - if (info.listener.asBinder() == token) return info; - } - throw new SecurityException("Disallowed call from unknown listener: " + listener); - } - - /** - * Allow an INotificationListener to simulate a "clear all" operation. - * - * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onClearAllNotifications} - * - * @param token The binder for the listener, to check that the caller is allowed - */ - public void cancelAllNotificationsFromListener(INotificationListener token) { - NotificationListenerInfo info = checkListenerToken(token); - long identity = Binder.clearCallingIdentity(); - try { - cancelAll(info.userid); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Allow an INotificationListener to simulate clearing (dismissing) a single notification. - * - * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear} - * - * @param token The binder for the listener, to check that the caller is allowed - */ - public void cancelNotificationFromListener(INotificationListener token, String pkg, String tag, int id) { - NotificationListenerInfo info = checkListenerToken(token); - long identity = Binder.clearCallingIdentity(); - try { - cancelNotification(pkg, tag, id, 0, - Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE, - true, - info.userid); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - /** - * Allow an INotificationListener to request the list of outstanding notifications seen by - * the current user. Useful when starting up, after which point the listener callbacks should - * be used. - * - * @param token The binder for the listener, to check that the caller is allowed - */ - public StatusBarNotification[] getActiveNotificationsFromListener(INotificationListener token) { - NotificationListenerInfo info = checkListenerToken(token); - - StatusBarNotification[] result = new StatusBarNotification[0]; - ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>(); - synchronized (mNotificationList) { - final int N = mNotificationList.size(); - for (int i=0; i<N; i++) { - StatusBarNotification sbn = mNotificationList.get(i).sbn; - if (info.enabledAndUserMatches(sbn)) { - list.add(sbn); - } - } - } - return list.toArray(result); - } - - // -- end of listener APIs -- - - public static final class NotificationRecord - { - final StatusBarNotification sbn; - IBinder statusBarKey; - - NotificationRecord(StatusBarNotification sbn) - { - this.sbn = sbn; - } - - public Notification getNotification() { return sbn.getNotification(); } - public int getFlags() { return sbn.getNotification().flags; } - public int getUserId() { return sbn.getUserId(); } - - void dump(PrintWriter pw, String prefix, Context baseContext) { - final Notification notification = sbn.getNotification(); - pw.println(prefix + this); - pw.println(prefix + " uid=" + sbn.getUid() + " userId=" + sbn.getUserId()); - pw.println(prefix + " icon=0x" + Integer.toHexString(notification.icon) - + " / " + idDebugString(baseContext, sbn.getPackageName(), notification.icon)); - pw.println(prefix + " pri=" + notification.priority + " score=" + sbn.getScore()); - pw.println(prefix + " contentIntent=" + notification.contentIntent); - pw.println(prefix + " deleteIntent=" + notification.deleteIntent); - pw.println(prefix + " tickerText=" + notification.tickerText); - pw.println(prefix + " contentView=" + notification.contentView); - pw.println(prefix + String.format(" defaults=0x%08x flags=0x%08x", - notification.defaults, notification.flags)); - pw.println(prefix + " sound=" + notification.sound); - pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate)); - pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d", - notification.ledARGB, notification.ledOnMS, notification.ledOffMS)); - if (notification.actions != null && notification.actions.length > 0) { - pw.println(prefix + " actions={"); - final int N = notification.actions.length; - for (int i=0; i<N; i++) { - final Notification.Action action = notification.actions[i]; - pw.println(String.format("%s [%d] \"%s\" -> %s", - prefix, - i, - action.title, - action.actionIntent.toString() - )); - } - pw.println(prefix + " }"); - } - if (notification.extras != null && notification.extras.size() > 0) { - pw.println(prefix + " extras={"); - for (String key : notification.extras.keySet()) { - pw.print(prefix + " " + key + "="); - Object val = notification.extras.get(key); - if (val == null) { - pw.println("null"); - } else { - pw.print(val.toString()); - if (val instanceof Bitmap) { - pw.print(String.format(" (%dx%d)", - ((Bitmap) val).getWidth(), - ((Bitmap) val).getHeight())); - } else if (val.getClass().isArray()) { - pw.println(" {"); - final int N = Array.getLength(val); - for (int i=0; i<N; i++) { - if (i > 0) pw.println(","); - pw.print(prefix + " " + Array.get(val, i)); - } - pw.print("\n" + prefix + " }"); - } - pw.println(); - } - } - pw.println(prefix + " }"); - } - } - - @Override - public final String toString() { - return String.format( - "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d: %s)", - System.identityHashCode(this), - this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(), this.sbn.getTag(), - this.sbn.getScore(), this.sbn.getNotification()); - } - } - - private static final class ToastRecord - { - final int pid; - final String pkg; - final ITransientNotification callback; - int duration; - - ToastRecord(int pid, String pkg, ITransientNotification callback, int duration) - { - this.pid = pid; - this.pkg = pkg; - this.callback = callback; - this.duration = duration; - } - - void update(int duration) { - this.duration = duration; - } - - void dump(PrintWriter pw, String prefix) { - pw.println(prefix + this); - } - - @Override - public final String toString() - { - return "ToastRecord{" - + Integer.toHexString(System.identityHashCode(this)) - + " pkg=" + pkg - + " callback=" + callback - + " duration=" + duration; - } - } - - private StatusBarManagerService.NotificationCallbacks mNotificationCallbacks - = new StatusBarManagerService.NotificationCallbacks() { - - public void onSetDisabled(int status) { - synchronized (mNotificationList) { - mDisabledNotifications = status; - if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) { - // cancel whatever's going on - long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioService.getRingtonePlayer(); - if (player != null) { - player.stopAsync(); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); - } - - identity = Binder.clearCallingIdentity(); - try { - mVibrator.cancel(); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - } - - public void onClearAll() { - // XXX to be totally correct, the caller should tell us which user - // this is for. - cancelAll(ActivityManager.getCurrentUser()); - } - - public void onNotificationClick(String pkg, String tag, int id) { - // XXX to be totally correct, the caller should tell us which user - // this is for. - cancelNotification(pkg, tag, id, Notification.FLAG_AUTO_CANCEL, - Notification.FLAG_FOREGROUND_SERVICE, false, - ActivityManager.getCurrentUser()); - } - - public void onNotificationClear(String pkg, String tag, int id) { - // XXX to be totally correct, the caller should tell us which user - // this is for. - cancelNotification(pkg, tag, id, 0, - Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE, - true, ActivityManager.getCurrentUser()); - } - - public void onPanelRevealed() { - synchronized (mNotificationList) { - // sound - mSoundNotification = null; - - long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioService.getRingtonePlayer(); - if (player != null) { - player.stopAsync(); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); - } - - // vibrate - mVibrateNotification = null; - identity = Binder.clearCallingIdentity(); - try { - mVibrator.cancel(); - } finally { - Binder.restoreCallingIdentity(identity); - } - - // light - mLights.clear(); - mLedNotification = null; - updateLightsLocked(); - } - } - - public void onNotificationError(String pkg, String tag, int id, - int uid, int initialPid, String message) { - Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id - + "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")"); - // XXX to be totally correct, the caller should tell us which user - // this is for. - cancelNotification(pkg, tag, id, 0, 0, false, UserHandle.getUserId(uid)); - long ident = Binder.clearCallingIdentity(); - try { - ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg, - "Bad notification posted from package " + pkg - + ": " + message); - } catch (RemoteException e) { - } - Binder.restoreCallingIdentity(ident); - } - }; - - private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - - boolean queryRestart = false; - boolean queryRemove = false; - boolean packageChanged = false; - boolean cancelNotifications = true; - - if (action.equals(Intent.ACTION_PACKAGE_ADDED) - || (queryRemove=action.equals(Intent.ACTION_PACKAGE_REMOVED)) - || action.equals(Intent.ACTION_PACKAGE_RESTARTED) - || (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED)) - || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART)) - || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { - String pkgList[] = null; - boolean queryReplace = queryRemove && - intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); - if (DBG) Slog.i(TAG, "queryReplace=" + queryReplace); - if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - } else if (queryRestart) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); - } else { - Uri uri = intent.getData(); - if (uri == null) { - return; - } - String pkgName = uri.getSchemeSpecificPart(); - if (pkgName == null) { - return; - } - if (packageChanged) { - // We cancel notifications for packages which have just been disabled - try { - final int enabled = mContext.getPackageManager() - .getApplicationEnabledSetting(pkgName); - if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED - || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { - cancelNotifications = false; - } - } catch (IllegalArgumentException e) { - // Package doesn't exist; probably racing with uninstall. - // cancelNotifications is already true, so nothing to do here. - if (DBG) { - Slog.i(TAG, "Exception trying to look up app enabled setting", e); - } - } - } - pkgList = new String[]{pkgName}; - } - - boolean anyListenersInvolved = false; - if (pkgList != null && (pkgList.length > 0)) { - for (String pkgName : pkgList) { - if (cancelNotifications) { - cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart, - UserHandle.USER_ALL); - } - if (mEnabledListenerPackageNames.contains(pkgName)) { - anyListenersInvolved = true; - } - } - } - - if (anyListenersInvolved) { - // if we're not replacing a package, clean up orphaned bits - if (!queryReplace) { - disableNonexistentListeners(); - } - // make sure we're still bound to any of our - // listeners who may have just upgraded - rebindListenerServices(); - } - } else if (action.equals(Intent.ACTION_SCREEN_ON)) { - // Keep track of screen on/off state, but do not turn off the notification light - // until user passes through the lock screen or views the notification. - mScreenOn = true; - } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { - mScreenOn = false; - } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) { - mInCall = (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals( - TelephonyManager.EXTRA_STATE_OFFHOOK)); - updateNotificationPulse(); - } else if (action.equals(Intent.ACTION_USER_STOPPED)) { - int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (userHandle >= 0) { - cancelAllNotificationsInt(null, 0, 0, true, userHandle); - } - } else if (action.equals(Intent.ACTION_USER_PRESENT)) { - // turn off LED when user passes through lock screen - mNotificationLight.turnOff(); - } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { - // reload per-user settings - mSettingsObserver.update(null); - } - } - }; - - class SettingsObserver extends ContentObserver { - private final Uri NOTIFICATION_LIGHT_PULSE_URI - = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE); - - private final Uri ENABLED_NOTIFICATION_LISTENERS_URI - = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); - - SettingsObserver(Handler handler) { - super(handler); - } - - void observe() { - ContentResolver resolver = mContext.getContentResolver(); - resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI, - false, this, UserHandle.USER_ALL); - resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI, - false, this, UserHandle.USER_ALL); - update(null); - } - - @Override public void onChange(boolean selfChange, Uri uri) { - update(uri); - } - - public void update(Uri uri) { - ContentResolver resolver = mContext.getContentResolver(); - if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) { - boolean pulseEnabled = Settings.System.getInt(resolver, - Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0; - if (mNotificationPulseEnabled != pulseEnabled) { - mNotificationPulseEnabled = pulseEnabled; - updateNotificationPulse(); - } - } - if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) { - rebindListenerServices(); - } - } - } - - private SettingsObserver mSettingsObserver; - - static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) { - int[] ar = r.getIntArray(resid); - if (ar == null) { - return def; - } - final int len = ar.length > maxlen ? maxlen : ar.length; - long[] out = new long[len]; - for (int i=0; i<len; i++) { - out[i] = ar[i]; - } - return out; - } - - NotificationManagerService(Context context, StatusBarManagerService statusBar, - LightsService lights) - { - super(); - mContext = context; - mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE); - mAm = ActivityManagerNative.getDefault(); - mUserManager = (UserManager)context.getSystemService(Context.USER_SERVICE); - mToastQueue = new ArrayList<ToastRecord>(); - mHandler = new WorkerHandler(); - - mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); - - importOldBlockDb(); - - mStatusBar = statusBar; - statusBar.setNotificationCallbacks(mNotificationCallbacks); - - mNotificationLight = lights.getLight(LightsService.LIGHT_ID_NOTIFICATIONS); - mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION); - - Resources resources = mContext.getResources(); - mDefaultNotificationColor = resources.getColor( - R.color.config_defaultNotificationColor); - mDefaultNotificationLedOn = resources.getInteger( - R.integer.config_defaultNotificationLedOn); - mDefaultNotificationLedOff = resources.getInteger( - R.integer.config_defaultNotificationLedOff); - - mDefaultVibrationPattern = getLongArray(resources, - R.array.config_defaultNotificationVibePattern, - VIBRATE_PATTERN_MAXLEN, - DEFAULT_VIBRATE_PATTERN); - - mFallbackVibrationPattern = getLongArray(resources, - R.array.config_notificationFallbackVibePattern, - VIBRATE_PATTERN_MAXLEN, - DEFAULT_VIBRATE_PATTERN); - - // Don't start allowing notifications until the setup wizard has run once. - // After that, including subsequent boots, init with notifications turned on. - // This works on the first boot because the setup wizard will toggle this - // flag at least once and we'll go back to 0 after that. - if (0 == Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 0)) { - mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS; - } - - // register for various Intents - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); - filter.addAction(Intent.ACTION_USER_PRESENT); - filter.addAction(Intent.ACTION_USER_STOPPED); - filter.addAction(Intent.ACTION_USER_SWITCHED); - mContext.registerReceiver(mIntentReceiver, filter); - IntentFilter pkgFilter = new IntentFilter(); - pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); - pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); - pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); - pkgFilter.addDataScheme("package"); - mContext.registerReceiver(mIntentReceiver, pkgFilter); - IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - mContext.registerReceiver(mIntentReceiver, sdFilter); - - mSettingsObserver = new SettingsObserver(mHandler); - mSettingsObserver.observe(); - - // spin up NotificationScorers - String[] notificationScorerNames = resources.getStringArray( - R.array.config_notificationScorers); - for (String scorerName : notificationScorerNames) { - try { - Class<?> scorerClass = mContext.getClassLoader().loadClass(scorerName); - NotificationScorer scorer = (NotificationScorer) scorerClass.newInstance(); - scorer.initialize(mContext); - mScorers.add(scorer); - } catch (ClassNotFoundException e) { - Slog.w(TAG, "Couldn't find scorer " + scorerName + ".", e); - } catch (InstantiationException e) { - Slog.w(TAG, "Couldn't instantiate scorer " + scorerName + ".", e); - } catch (IllegalAccessException e) { - Slog.w(TAG, "Problem accessing scorer " + scorerName + ".", e); - } - } - } - - /** - * Read the old XML-based app block database and import those blockages into the AppOps system. - */ - private void importOldBlockDb() { - loadBlockDb(); - - PackageManager pm = mContext.getPackageManager(); - for (String pkg : mBlockedPackages) { - PackageInfo info = null; - try { - info = pm.getPackageInfo(pkg, 0); - setNotificationsEnabledForPackage(pkg, info.applicationInfo.uid, false); - } catch (NameNotFoundException e) { - // forget you - } - } - mBlockedPackages.clear(); - if (mPolicyFile != null) { - mPolicyFile.delete(); - } - } - - void systemReady() { - mAudioService = IAudioService.Stub.asInterface( - ServiceManager.getService(Context.AUDIO_SERVICE)); - - // no beeping until we're basically done booting - mSystemReady = true; - - // make sure our listener services are properly bound - rebindListenerServices(); - } - - // Toasts - // ============================================================================ - public void enqueueToast(String pkg, ITransientNotification callback, int duration) - { - if (DBG) Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration); - - if (pkg == null || callback == null) { - Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback); - return ; - } - - final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg)); - - if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) { - if (!isSystemToast) { - Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request."); - 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 { - // Limit the number of toasts that any given package except the android - // package can enqueue. Prevents DOS attacks and deals with leaks. - if (!isSystemToast) { - int count = 0; - final int N = mToastQueue.size(); - for (int i=0; i<N; i++) { - final ToastRecord r = mToastQueue.get(i); - if (r.pkg.equals(pkg)) { - count++; - if (count >= MAX_PACKAGE_NOTIFICATIONS) { - Slog.e(TAG, "Package has already posted " + count - + " toasts. Not showing more. Package=" + pkg); - return; - } - } - } - } - - 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); - } - } - } - - public void cancelToast(String pkg, ITransientNotification callback) { - Slog.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback); - - if (pkg == null || callback == null) { - Slog.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 { - Slog.w(TAG, "Toast already cancelled. pkg=" + pkg + " callback=" + callback); - } - } finally { - Binder.restoreCallingIdentity(callingId); - } - } - } - - private void showNextToastLocked() { - ToastRecord record = mToastQueue.get(0); - while (record != null) { - if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback); - try { - record.callback.show(); - scheduleTimeoutLocked(record); - return; - } catch (RemoteException e) { - Slog.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 cancelToastLocked(int index) { - ToastRecord record = mToastQueue.get(index); - try { - record.callback.hide(); - } catch (RemoteException e) { - Slog.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(); - } - } - - private void scheduleTimeoutLocked(ToastRecord r) - { - mHandler.removeCallbacksAndMessages(r); - Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r); - long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; - mHandler.sendMessageDelayed(m, delay); - } - - private void handleTimeout(ToastRecord record) - { - if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback); - synchronized (mToastQueue) { - int index = indexOfToastLocked(record.pkg, record.callback); - if (index >= 0) { - cancelToastLocked(index); - } - } - } - - // lock on mToastQueue - private int indexOfToastLocked(String pkg, 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; - } - - // lock on mToastQueue - 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 final class WorkerHandler extends Handler - { - @Override - public void handleMessage(Message msg) - { - switch (msg.what) - { - case MESSAGE_TIMEOUT: - handleTimeout((ToastRecord)msg.obj); - break; - } - } - } - - - // Notifications - // ============================================================================ - public void enqueueNotificationWithTag(String pkg, String basePkg, String tag, int id, - Notification notification, int[] idOut, int userId) - { - enqueueNotificationInternal(pkg, basePkg, Binder.getCallingUid(), Binder.getCallingPid(), - tag, id, notification, idOut, userId); - } - - private final static int clamp(int x, int low, int high) { - return (x < low) ? low : ((x > high) ? high : x); - } - - // Not exposed via Binder; for system use only (otherwise malicious apps could spoof the - // uid/pid of another application) - - public void enqueueNotificationInternal(final String pkg, String basePkg, final int callingUid, - final int callingPid, final String tag, final int id, final Notification notification, - int[] idOut, int incomingUserId) - { - if (DBG) { - Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); - } - checkCallerIsSystemOrSameApp(pkg); - final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg)); - - final int userId = ActivityManager.handleIncomingUser(callingPid, - callingUid, incomingUserId, true, false, "enqueueNotification", pkg); - final UserHandle user = new UserHandle(userId); - - // Limit the number of notifications that any given package except the android - // package can enqueue. Prevents DOS attacks and deals with leaks. - if (!isSystemNotification) { - synchronized (mNotificationList) { - int count = 0; - final int N = mNotificationList.size(); - for (int i=0; i<N; i++) { - final NotificationRecord r = mNotificationList.get(i); - if (r.sbn.getPackageName().equals(pkg) && r.sbn.getUserId() == userId) { - count++; - if (count >= MAX_PACKAGE_NOTIFICATIONS) { - Slog.e(TAG, "Package has already posted " + count - + " notifications. Not showing more. package=" + pkg); - return; - } - } - } - } - } - - // 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(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag, userId, - 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); - } - } - - mHandler.post(new Runnable() { - @Override - public void run() { - - // === Scoring === - - // 0. Sanitize inputs - notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, - Notification.PRIORITY_MAX); - // Migrate notification flags to scores - if (0 != (notification.flags & Notification.FLAG_HIGH_PRIORITY)) { - if (notification.priority < Notification.PRIORITY_MAX) { - notification.priority = Notification.PRIORITY_MAX; - } - } else if (SCORE_ONGOING_HIGHER && - 0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) { - if (notification.priority < Notification.PRIORITY_HIGH) { - notification.priority = Notification.PRIORITY_HIGH; - } - } - - // 1. initial score: buckets of 10, around the app - int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20] - - // 2. Consult external heuristics (TBD) - - // 3. Apply local rules - - int initialScore = score; - if (!mScorers.isEmpty()) { - if (DBG) Slog.v(TAG, "Initial score is " + score + "."); - for (NotificationScorer scorer : mScorers) { - try { - score = scorer.getScore(notification, score); - } catch (Throwable t) { - Slog.w(TAG, "Scorer threw on .getScore.", t); - } - } - if (DBG) Slog.v(TAG, "Final score is " + score + "."); - } - - // add extra to indicate score modified by NotificationScorer - notification.extras.putBoolean(Notification.EXTRA_SCORE_MODIFIED, - score != initialScore); - - // blocked apps - if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) { - if (!isSystemNotification) { - score = JUNK_SCORE; - Slog.e(TAG, "Suppressing notification from package " + pkg - + " by user request."); - } - } - - if (DBG) { - Slog.v(TAG, "Assigned score=" + score + " to " + notification); - } - - if (score < SCORE_DISPLAY_THRESHOLD) { - // Notification will be blocked because the score is too low. - return; - } - - // Should this notification make noise, vibe, or use the LED? - final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD); - - synchronized (mNotificationList) { - final StatusBarNotification n = new StatusBarNotification( - pkg, id, tag, callingUid, callingPid, score, notification, user); - NotificationRecord r = new NotificationRecord(n); - NotificationRecord old = null; - - int index = indexOfNotificationLocked(pkg, tag, id, userId); - if (index < 0) { - mNotificationList.add(r); - } else { - old = mNotificationList.remove(index); - mNotificationList.add(index, r); - // Make sure we don't lose the foreground service state. - if (old != null) { - notification.flags |= - old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE; - } - } - - // Ensure if this is a foreground service that the proper additional - // flags are set. - if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) { - notification.flags |= Notification.FLAG_ONGOING_EVENT - | Notification.FLAG_NO_CLEAR; - } - - final int currentUser; - final long token = Binder.clearCallingIdentity(); - try { - currentUser = ActivityManager.getCurrentUser(); - } finally { - Binder.restoreCallingIdentity(token); - } - - if (notification.icon != 0) { - if (old != null && old.statusBarKey != null) { - r.statusBarKey = old.statusBarKey; - long identity = Binder.clearCallingIdentity(); - try { - mStatusBar.updateNotification(r.statusBarKey, n); - } - finally { - Binder.restoreCallingIdentity(identity); - } - } else { - long identity = Binder.clearCallingIdentity(); - try { - r.statusBarKey = mStatusBar.addNotification(n); - if ((n.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0 - && canInterrupt) { - mAttentionLight.pulse(); - } - } - finally { - Binder.restoreCallingIdentity(identity); - } - } - // Send accessibility events only for the current user. - if (currentUser == userId) { - sendAccessibilityEvent(notification, pkg); - } - - notifyPostedLocked(r); - } else { - Slog.e(TAG, "Not posting notification with icon==0: " + notification); - if (old != null && old.statusBarKey != null) { - long identity = Binder.clearCallingIdentity(); - try { - mStatusBar.removeNotification(old.statusBarKey); - } - finally { - Binder.restoreCallingIdentity(identity); - } - - notifyRemovedLocked(r); - } - // ATTENTION: in a future release we will bail out here - // so that we do not play sounds, show lights, etc. for invalid notifications - Slog.e(TAG, "WARNING: In a future release this will crash the app: " - + n.getPackageName()); - } - - // 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 )) - && (r.getUserId() == UserHandle.USER_ALL || - (r.getUserId() == userId && r.getUserId() == currentUser)) - && canInterrupt - && mSystemReady) { - - final AudioManager audioManager = (AudioManager) mContext - .getSystemService(Context.AUDIO_SERVICE); - - // sound - - // should we use the default notification sound? (indicated either by - // DEFAULT_SOUND or because notification.sound is pointing at - // Settings.System.NOTIFICATION_SOUND) - final boolean useDefaultSound = - (notification.defaults & Notification.DEFAULT_SOUND) != 0 || - Settings.System.DEFAULT_NOTIFICATION_URI - .equals(notification.sound); - - Uri soundUri = null; - boolean hasValidSound = false; - - if (useDefaultSound) { - soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; - - // check to see if the default notification sound is silent - ContentResolver resolver = mContext.getContentResolver(); - hasValidSound = Settings.System.getString(resolver, - Settings.System.NOTIFICATION_SOUND) != null; - } else if (notification.sound != null) { - soundUri = notification.sound; - hasValidSound = (soundUri != null); - } - - if (hasValidSound) { - boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0; - int audioStreamType; - if (notification.audioStreamType >= 0) { - audioStreamType = notification.audioStreamType; - } else { - audioStreamType = DEFAULT_STREAM_TYPE; - } - mSoundNotification = r; - // do not play notifications if stream volume is 0 (typically because - // ringer mode is silent) or if there is a user of exclusive audio focus - if ((audioManager.getStreamVolume(audioStreamType) != 0) - && !audioManager.isAudioFocusExclusive()) { - final long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioService.getRingtonePlayer(); - if (player != null) { - player.playAsync(soundUri, user, looping, audioStreamType); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - - // vibrate - // Does the notification want to specify its own vibration? - final boolean hasCustomVibrate = notification.vibrate != null; - - // new in 4.2: if there was supposed to be a sound and we're in vibrate - // mode, and no other vibration is specified, we fall back to vibration - final boolean convertSoundToVibration = - !hasCustomVibrate - && hasValidSound - && (audioManager.getRingerMode() - == AudioManager.RINGER_MODE_VIBRATE); - - // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback. - final boolean useDefaultVibrate = - (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; - - if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate) - && !(audioManager.getRingerMode() - == AudioManager.RINGER_MODE_SILENT)) { - mVibrateNotification = r; - - if (useDefaultVibrate || convertSoundToVibration) { - // Escalate privileges so we can use the vibrator even if the - // notifying app does not have the VIBRATE permission. - long identity = Binder.clearCallingIdentity(); - try { - mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(), - useDefaultVibrate ? mDefaultVibrationPattern - : mFallbackVibrationPattern, - ((notification.flags & Notification.FLAG_INSISTENT) != 0) - ? 0: -1); - } finally { - Binder.restoreCallingIdentity(identity); - } - } else if (notification.vibrate.length > 1) { - // If you want your own vibration pattern, you need the VIBRATE - // permission - mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(), - notification.vibrate, - ((notification.flags & Notification.FLAG_INSISTENT) != 0) - ? 0: -1); - } - } - } - - // light - // the most recent thing gets the light - mLights.remove(old); - if (mLedNotification == old) { - mLedNotification = null; - } - //Slog.i(TAG, "notification.lights=" - // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) - // != 0)); - if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 - && canInterrupt) { - mLights.add(r); - updateLightsLocked(); - } else { - if (old != null - && ((old.getFlags() & Notification.FLAG_SHOW_LIGHTS) != 0)) { - updateLightsLocked(); - } - } - } - } - }); - - idOut[0] = id; - } - - private void sendAccessibilityEvent(Notification notification, CharSequence packageName) { - AccessibilityManager manager = AccessibilityManager.getInstance(mContext); - if (!manager.isEnabled()) { - return; - } - - AccessibilityEvent event = - AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); - event.setPackageName(packageName); - event.setClassName(Notification.class.getName()); - event.setParcelableData(notification); - CharSequence tickerText = notification.tickerText; - if (!TextUtils.isEmpty(tickerText)) { - event.getText().add(tickerText); - } - - manager.sendAccessibilityEvent(event); - } - - private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete) { - // tell the app - if (sendDelete) { - if (r.getNotification().deleteIntent != null) { - try { - r.getNotification().deleteIntent.send(); - } catch (PendingIntent.CanceledException ex) { - // do nothing - there's no relevant way to recover, and - // no reason to let this propagate - Slog.w(TAG, "canceled PendingIntent for " + r.sbn.getPackageName(), ex); - } - } - } - - // status bar - if (r.getNotification().icon != 0) { - long identity = Binder.clearCallingIdentity(); - try { - mStatusBar.removeNotification(r.statusBarKey); - } - finally { - Binder.restoreCallingIdentity(identity); - } - r.statusBarKey = null; - notifyRemovedLocked(r); - } - - // sound - if (mSoundNotification == r) { - mSoundNotification = null; - final long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioService.getRingtonePlayer(); - if (player != null) { - player.stopAsync(); - } - } catch (RemoteException e) { - } 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; - } - - // Save it for users of getHistoricalNotifications() - mArchive.record(r.sbn); - } - - /** - * Cancels a notification ONLY if it has all of the {@code mustHaveFlags} - * and none of the {@code mustNotHaveFlags}. - */ - private void cancelNotification(final String pkg, final String tag, final int id, - final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete, - final int userId) { - // In enqueueNotificationInternal notifications are added by scheduling the - // work on the worker handler. Hence, we also schedule the cancel on this - // handler to avoid a scenario where an add notification call followed by a - // remove notification call ends up in not removing the notification. - mHandler.post(new Runnable() { - @Override - public void run() { - EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, tag, userId, - mustHaveFlags, mustNotHaveFlags); - - synchronized (mNotificationList) { - int index = indexOfNotificationLocked(pkg, tag, id, userId); - if (index >= 0) { - NotificationRecord r = mNotificationList.get(index); - - if ((r.getNotification().flags & mustHaveFlags) != mustHaveFlags) { - return; - } - if ((r.getNotification().flags & mustNotHaveFlags) != 0) { - return; - } - - mNotificationList.remove(index); - - cancelNotificationLocked(r, sendDelete); - updateLightsLocked(); - } - } - } - }); - } - - /** - * Determine whether the userId applies to the notification in question, either because - * they match exactly, or one of them is USER_ALL (which is treated as a wildcard). - */ - private boolean notificationMatchesUserId(NotificationRecord r, int userId) { - return - // looking for USER_ALL notifications? match everything - userId == UserHandle.USER_ALL - // a notification sent to USER_ALL matches any query - || r.getUserId() == UserHandle.USER_ALL - // an exact user match - || r.getUserId() == userId; - } - - /** - * Cancels all notifications from a given package that have all of the - * {@code mustHaveFlags}. - */ - boolean cancelAllNotificationsInt(String pkg, int mustHaveFlags, - int mustNotHaveFlags, boolean doit, int userId) { - EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL_ALL, pkg, userId, - mustHaveFlags, mustNotHaveFlags); - - synchronized (mNotificationList) { - final int N = mNotificationList.size(); - boolean canceledSomething = false; - for (int i = N-1; i >= 0; --i) { - NotificationRecord r = mNotificationList.get(i); - if (!notificationMatchesUserId(r, userId)) { - continue; - } - // Don't remove notifications to all, if there's no package name specified - if (r.getUserId() == UserHandle.USER_ALL && pkg == null) { - continue; - } - if ((r.getFlags() & mustHaveFlags) != mustHaveFlags) { - continue; - } - if ((r.getFlags() & mustNotHaveFlags) != 0) { - continue; - } - if (pkg != null && !r.sbn.getPackageName().equals(pkg)) { - continue; - } - canceledSomething = true; - if (!doit) { - return true; - } - mNotificationList.remove(i); - cancelNotificationLocked(r, false); - } - if (canceledSomething) { - updateLightsLocked(); - } - return canceledSomething; - } - } - - public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) { - checkCallerIsSystemOrSameApp(pkg); - userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), - Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg); - // Don't allow client applications to cancel foreground service notis. - cancelNotification(pkg, tag, id, 0, - Binder.getCallingUid() == Process.SYSTEM_UID - ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId); - } - - public void cancelAllNotifications(String pkg, int userId) { - checkCallerIsSystemOrSameApp(pkg); - - userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), - Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg); - - // Calling from user space, don't allow the canceling of actively - // running foreground services. - cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId); - } - - // Return true if the UID is a system or phone UID and therefore should not have - // any notifications or toasts blocked. - boolean isUidSystem(int uid) { - final int appid = UserHandle.getAppId(uid); - return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0); - } - - // same as isUidSystem(int, int) for the Binder caller's UID. - boolean isCallerSystem() { - return isUidSystem(Binder.getCallingUid()); - } - - void checkCallerIsSystem() { - if (isCallerSystem()) { - return; - } - throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid()); - } - - void checkCallerIsSystemOrSameApp(String pkg) { - if (isCallerSystem()) { - return; - } - final int uid = Binder.getCallingUid(); - try { - ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo( - pkg, 0, UserHandle.getCallingUserId()); - if (!UserHandle.isSameApp(ai.uid, uid)) { - throw new SecurityException("Calling uid " + uid + " gave package" - + pkg + " which is owned by uid " + ai.uid); - } - } catch (RemoteException re) { - throw new SecurityException("Unknown package " + pkg + "\n" + re); - } - } - - void cancelAll(int userId) { - synchronized (mNotificationList) { - final int N = mNotificationList.size(); - for (int i=N-1; i>=0; i--) { - NotificationRecord r = mNotificationList.get(i); - - if (!notificationMatchesUserId(r, userId)) { - continue; - } - - if ((r.getFlags() & (Notification.FLAG_ONGOING_EVENT - | Notification.FLAG_NO_CLEAR)) == 0) { - mNotificationList.remove(i); - cancelNotificationLocked(r, true); - } - } - - updateLightsLocked(); - } - } - - // lock on mNotificationList - private void updateLightsLocked() - { - // handle notification lights - if (mLedNotification == null) { - // get next notification, if any - int n = mLights.size(); - if (n > 0) { - mLedNotification = mLights.get(n-1); - } - } - - // Don't flash while we are in a call or screen is on - if (mLedNotification == null || mInCall || mScreenOn) { - mNotificationLight.turnOff(); - } else { - final Notification ledno = mLedNotification.sbn.getNotification(); - int ledARGB = ledno.ledARGB; - int ledOnMS = ledno.ledOnMS; - int ledOffMS = ledno.ledOffMS; - if ((ledno.defaults & Notification.DEFAULT_LIGHTS) != 0) { - ledARGB = mDefaultNotificationColor; - ledOnMS = mDefaultNotificationLedOn; - ledOffMS = mDefaultNotificationLedOff; - } - if (mNotificationPulseEnabled) { - // pulse repeatedly - mNotificationLight.setFlashing(ledARGB, LightsService.LIGHT_FLASH_TIMED, - ledOnMS, ledOffMS); - } - } - } - - // lock on mNotificationList - private int indexOfNotificationLocked(String pkg, String tag, int id, int userId) - { - ArrayList<NotificationRecord> list = mNotificationList; - final int len = list.size(); - for (int i=0; i<len; i++) { - NotificationRecord r = list.get(i); - if (!notificationMatchesUserId(r, userId) || r.sbn.getId() != id) { - continue; - } - if (tag == null) { - if (r.sbn.getTag() != null) { - continue; - } - } else { - if (!tag.equals(r.sbn.getTag())) { - continue; - } - } - if (r.sbn.getPackageName().equals(pkg)) { - return i; - } - } - return -1; - } - - private void updateNotificationPulse() { - synchronized (mNotificationList) { - updateLightsLocked(); - } - } - - // ====================================================================== - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, 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:"); - - pw.println(" Listeners (" + mEnabledListenersForCurrentUser.size() - + ") enabled for current user:"); - for (ComponentName cmpt : mEnabledListenersForCurrentUser) { - pw.println(" " + cmpt); - } - - pw.println(" Live listeners (" + mListeners.size() + "):"); - for (NotificationListenerInfo info : mListeners) { - pw.println(" " + info.component - + " (user " + info.userid + "): " + info.listener - + (info.isSystem?" SYSTEM":"")); - } - - 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++) { - pw.println(" " + mLights.get(i)); - } - pw.println(" "); - } - - pw.println(" mSoundNotification=" + mSoundNotification); - pw.println(" mVibrateNotification=" + mVibrateNotification); - pw.println(" mDisabledNotifications=0x" + Integer.toHexString(mDisabledNotifications)); - pw.println(" mSystemReady=" + mSystemReady); - pw.println(" mArchive=" + mArchive.toString()); - Iterator<StatusBarNotification> iter = mArchive.descendingIterator(); - int i=0; - while (iter.hasNext()) { - pw.println(" " + iter.next()); - if (++i >= 5) { - if (iter.hasNext()) pw.println(" ..."); - break; - } - } - - } - } -} |