summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/NotificationManagerService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com/android/server/NotificationManagerService.java')
-rw-r--r--services/java/com/android/server/NotificationManagerService.java368
1 files changed, 314 insertions, 54 deletions
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 44d730c..cfb892f 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -26,16 +26,17 @@ import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
-import android.app.INotificationListener;
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;
@@ -57,6 +58,9 @@ 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;
@@ -68,8 +72,6 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
-import com.android.internal.statusbar.StatusBarNotification;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -121,6 +123,8 @@ public class NotificationManagerService extends INotificationManager.Stub
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;
@@ -163,8 +167,18 @@ public class NotificationManagerService extends INotificationManager.Stub
private final AppOpsManager mAppOps;
- private ArrayList<NotificationListenerInfo> mListeners = new ArrayList<NotificationListenerInfo>();
- private ArrayList<String> mEnabledListenersForCurrentUser = new ArrayList<String>();
+ // 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;
@@ -181,27 +195,42 @@ public class NotificationManagerService extends INotificationManager.Stub
private class NotificationListenerInfo implements DeathRecipient {
INotificationListener listener;
- String pkg;
+ ComponentName component;
int userid;
boolean isSystem;
+ ServiceConnection connection;
- public NotificationListenerInfo(INotificationListener listener, String pkg, int userid,
- boolean isSystem) {
+ public NotificationListenerInfo(INotificationListener listener, ComponentName component,
+ int userid, boolean isSystem) {
this.listener = listener;
- this.pkg = pkg;
+ 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 (!(isSystem || isEnabledForUser(nid))) return false;
+ 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;
+ if (!enabledAndUserMatches(sbn)) {
+ return;
+ }
try {
listener.onNotificationPosted(sbn);
} catch (RemoteException ex) {
@@ -220,15 +249,17 @@ public class NotificationManagerService extends INotificationManager.Stub
@Override
public void binderDied() {
- unregisterListener(this.listener, this.userid);
+ if (connection == null) {
+ // This is not a service; it won't be recreated. We can give up this connection.
+ unregisterListener(this.listener, this.userid);
+ }
}
/** convenience method for looking in mEnabledListenersForCurrentUser */
- public boolean isEnabledForUser(int userid) {
- for (int i=0; i<mEnabledListenersForCurrentUser.size(); i++) {
- if (this.pkg.equals(mEnabledListenersForCurrentUser.get(i))) return true;
- }
- return false;
+ public boolean isEnabledForCurrentUser() {
+ if (this.isSystem) return true;
+ if (this.connection == null) return false;
+ return mEnabledListenersForCurrentUser.contains(this.component);
}
}
@@ -434,6 +465,12 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
+ /**
+ * 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,
@@ -456,6 +493,12 @@ public class NotificationManagerService extends INotificationManager.Stub
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,
@@ -474,27 +517,76 @@ public class NotificationManagerService extends INotificationManager.Stub
return tmp;
}
- boolean packageCanTapNotificationsForUser(final int uid, final String pkg) {
- // Make sure the package and uid match, and that the package is allowed access
- return (AppOpsManager.MODE_ALLOWED
- == mAppOps.checkOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, pkg));
+ /**
+ * 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() {
+ String flat = Settings.Secure.getString(
+ mContext.getContentResolver(),
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+
+ NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()];
+ final ArrayList<ComponentName> toAdd;
+ final int currentUser = ActivityManager.getCurrentUser();
+
+ 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 String pkg, final int userid) {
- // ensure system or allowed pkg
- int uid = Binder.getCallingUid();
- boolean isSystem = (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0);
- if (!(isSystem || packageCanTapNotificationsForUser(uid, pkg))) {
- throw new SecurityException("Package " + pkg
- + " may not listen for notifications");
- }
+ final ComponentName component, final int userid) {
+ checkCallerIsSystem();
synchronized (mNotificationList) {
try {
NotificationListenerInfo info
- = new NotificationListenerInfo(listener, pkg, userid, isSystem);
+ = new NotificationListenerInfo(listener, component, userid, true);
listener.asBinder().linkToDeath(info, 0);
mListeners.add(info);
} catch (RemoteException e) {
@@ -503,6 +595,90 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
+ /**
+ * 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,
+ com.android.internal.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;
+ }
+ }
+ }
+
+ /**
+ * Remove a listener binder directly
+ */
@Override
public void unregisterListener(INotificationListener listener, int userid) {
// no need to check permissions; if your listener binder is in the list,
@@ -513,12 +689,39 @@ public class NotificationManagerService extends INotificationManager.Stub
for (int i=N-1; i>=0; i--) {
final NotificationListenerInfo info = mListeners.get(i);
if (info.listener == listener && info.userid == userid) {
- mListeners.remove(listener);
+ mListeners.remove(i);
+ if (info.connection != null) {
+ mContext.unbindService(info.connection);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 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) {
+ mContext.unbindService(info.connection);
+ }
}
}
}
}
+ /**
+ * asynchronously notify all listeners about a new notification
+ */
private void notifyPostedLocked(NotificationRecord n) {
final StatusBarNotification sbn = n.sbn;
for (final NotificationListenerInfo info : mListeners) {
@@ -530,6 +733,9 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
+ /**
+ * asynchronously notify all listeners about a removed notification
+ */
private void notifyRemovedLocked(NotificationRecord n) {
final StatusBarNotification sbn = n.sbn;
for (final NotificationListenerInfo info : mListeners) {
@@ -541,6 +747,57 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
+ // -- 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 clearAllNotificationsFromListener(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 clearNotificationFromListener(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);
+ }
+ }
+
+ // -- end of listener APIs --
+
public static final class NotificationRecord
{
final StatusBarNotification sbn;
@@ -759,12 +1016,23 @@ public class NotificationManagerService extends INotificationManager.Stub
}
pkgList = new String[]{pkgName};
}
+
+ boolean anyListenersInvolved = false;
if (pkgList != null && (pkgList.length > 0)) {
for (String pkgName : pkgList) {
cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart,
UserHandle.USER_ALL);
+ if (mEnabledListenerPackageNames.contains(pkgName)) {
+ anyListenersInvolved = true;
+ }
}
}
+
+ if (anyListenersInvolved) {
+ // 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.
@@ -795,7 +1063,7 @@ public class NotificationManagerService extends INotificationManager.Stub
= Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
private final Uri ENABLED_NOTIFICATION_LISTENERS_URI
- = Settings.System.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+ = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
SettingsObserver(Handler handler) {
super(handler);
@@ -804,9 +1072,9 @@ public class NotificationManagerService extends INotificationManager.Stub
void observe() {
ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
- false, this);
+ false, this, UserHandle.USER_ALL);
resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
- false, this);
+ false, this, UserHandle.USER_ALL);
update(null);
}
@@ -825,19 +1093,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) {
- String pkglist = Settings.Secure.getString(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
- mEnabledListenersForCurrentUser.clear();
- if (pkglist != null) {
- String[] pkgs = pkglist.split(";");
- for (int i=0; i<pkgs.length; i++) {
- final String pkg = pkgs[i];
- if (pkg != null && ! "".equals(pkg)) {
- mEnabledListenersForCurrentUser.add(pkgs[i]);
- }
- }
- }
+ rebindListenerServices();
}
}
}
@@ -956,6 +1212,9 @@ public class NotificationManagerService extends INotificationManager.Stub
// no beeping until we're basically done booting
mSystemReady = true;
+
+ // make sure our listener services are properly bound
+ rebindListenerServices();
}
// Toasts
@@ -1781,16 +2040,17 @@ public class NotificationManagerService extends INotificationManager.Stub
pw.println("Current Notification Manager state:");
- pw.print(" Enabled listeners: [");
- for (String pkg : mEnabledListenersForCurrentUser) {
- pw.print(" " + pkg);
+ pw.println(" Listeners (" + mEnabledListenersForCurrentUser.size()
+ + ") enabled for current user:");
+ for (ComponentName cmpt : mEnabledListenersForCurrentUser) {
+ pw.println(" " + cmpt);
}
- pw.println(" ]");
- pw.println(" Live listeners:");
+ pw.println(" Live listeners (" + mListeners.size() + "):");
for (NotificationListenerInfo info : mListeners) {
- pw.println(" " + info.pkg + " (user " + info.userid + "): " + info.listener
- + (info.isSystem?" SYSTEM":""));
+ pw.println(" " + info.component
+ + " (user " + info.userid + "): " + info.listener
+ + (info.isSystem?" SYSTEM":""));
}
int N;