From a263e4e438746f91fb78857bd569ba4f796a346d Mon Sep 17 00:00:00 2001 From: Kenny Guy Date: Mon, 3 Mar 2014 18:24:03 +0000 Subject: [PATCH] Pipe notifications from related users to listeners. For Listeners built against L or greater Send notifications from related users to listeners. Return notifications from related users getAllActiveNotifications Cancel notifications from related users in cancelAllNotifications Deprecate StatusBarNotification.getUserId() and expose getUser() as APIs should use UserHandles. Deprecate cancelNotification that takes package, id and tag in favour of one that takes key. Fix bug that notifications from related users didn't trigger sounds. Change-Id: I1b1c20c9f305b8f3c4047bc5720d8e99cdedfe70 --- api/current.txt | 6 +- .../notification/NotificationListenerService.java | 35 +++- .../notification/StatusBarNotification.java | 7 +- .../notification/NotificationManagerService.java | 206 ++++++++++++++------- 4 files changed, 187 insertions(+), 67 deletions(-) diff --git a/api/current.txt b/api/current.txt index 7fed429d773c..0d73a2c415aa 100644 --- a/api/current.txt +++ b/api/current.txt @@ -24348,7 +24348,8 @@ package android.service.notification { public abstract class NotificationListenerService extends android.app.Service { ctor public NotificationListenerService(); method public final void cancelAllNotifications(); - method public final void cancelNotification(java.lang.String, java.lang.String, int); + method public final deprecated void cancelNotification(java.lang.String, java.lang.String, int); + method public final void cancelNotification(java.lang.String); method public final void cancelNotifications(java.lang.String[]); method public java.lang.String[] getActiveNotificationKeys(); method public android.service.notification.StatusBarNotification[] getActiveNotifications(); @@ -24371,7 +24372,8 @@ package android.service.notification { method public java.lang.String getPackageName(); method public long getPostTime(); method public java.lang.String getTag(); - method public int getUserId(); + method public android.os.UserHandle getUser(); + method public deprecated int getUserId(); method public boolean isClearable(); method public boolean isOngoing(); method public void writeToParcel(android.os.Parcel, int); diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 050e1a0e5286..3673f034e4aa 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.Log; /** @@ -121,11 +122,43 @@ public abstract class NotificationListenerService extends Service { * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}. * @param id ID of the notification as specified by the notifying app in * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}. + *

+ * @deprecated Use {@link #cancelNotification(String key)} + * instead. Beginning with {@link android.os.Build.VERSION_CODES#L} this method will no longer + * cancel the notification. It will continue to cancel the notification for applications + * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#L}. */ public final void cancelNotification(String pkg, String tag, int id) { if (!isBound()) return; try { - getNotificationInterface().cancelNotificationFromListener(mWrapper, pkg, tag, id); + getNotificationInterface().cancelNotificationFromListener( + mWrapper, pkg, tag, id); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + } + } + + /** + * Inform the notification manager about dismissal of a single notification. + *

+ * Use this if your listener has a user interface that allows the user to dismiss individual + * notifications, similar to the behavior of Android's status bar and notification panel. + * It should be called after the user dismisses a single notification using your UI; + * upon being informed, the notification manager will actually remove the notification + * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. + *

+ * Note: If your listener allows the user to fire a notification's + * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call + * this method at that time if the Notification in question has the + * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set. + *

+ * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}. + */ + public final void cancelNotification(String key) { + if (!isBound()) return; + try { + getNotificationInterface().cancelNotificationsFromListener(mWrapper, + new String[] {key}); } catch (android.os.RemoteException ex) { Log.v(TAG, "Unable to contact notification manager", ex); } diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index 7f15ab85261a..0f74169591f8 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -175,7 +175,11 @@ public class StatusBarNotification implements Parcelable { && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0); } - /** Returns a userHandle for the instance of the app that posted this notification. */ + /** + * Returns a userHandle for the instance of the app that posted this notification. + * + * @deprecated Use {@link #getUser()} instead. + */ public int getUserId() { return this.user.getIdentifier(); } @@ -219,7 +223,6 @@ public class StatusBarNotification implements Parcelable { /** * The {@link android.os.UserHandle} for whom this notification is intended. - * @hide */ public UserHandle getUser() { return user; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 3b6d2886df7f..6c50a7fd2bdb 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -51,6 +51,7 @@ import android.media.AudioManager; import android.media.IRingtonePlayer; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -185,10 +186,10 @@ public class NotificationManagerService extends SystemService { // things that will be put into mListeners as soon as they're ready private ArrayList mServicesBinding = new ArrayList(); // lists the component names of all enabled (and therefore connected) listener - // app services for the current user only - private HashSet mEnabledListenersForCurrentUser + // app services for current profiles. + private HashSet mEnabledListenersForCurrentProfiles = new HashSet(); - // Just the packages from mEnabledListenersForCurrentUser + // Just the packages from mEnabledListenersForCurrentProfiles private HashSet mEnabledListenerPackageNames = new HashSet(); // Notification control database. For now just contains disabled packages. @@ -242,32 +243,40 @@ public class NotificationManagerService extends SystemService { int userid; boolean isSystem; ServiceConnection connection; + int targetSdkVersion; public NotificationListenerInfo(INotificationListener listener, ComponentName component, - int userid, boolean isSystem) { + int userid, boolean isSystem, int targetSdkVersion) { this.listener = listener; this.component = component; this.userid = userid; this.isSystem = isSystem; this.connection = null; + this.targetSdkVersion = targetSdkVersion; } public NotificationListenerInfo(INotificationListener listener, ComponentName component, - int userid, ServiceConnection connection) { + int userid, ServiceConnection connection, int targetSdkVersion) { this.listener = listener; this.component = component; this.userid = userid; this.isSystem = false; this.connection = connection; + this.targetSdkVersion = targetSdkVersion; } boolean enabledAndUserMatches(StatusBarNotification sbn) { final int nid = sbn.getUserId(); - if (!isEnabledForCurrentUser()) { + if (!isEnabledForCurrentProfiles()) { return false; } if (this.userid == UserHandle.USER_ALL) return true; - return (nid == UserHandle.USER_ALL || nid == this.userid); + if (nid == UserHandle.USER_ALL || nid == this.userid) return true; + return supportsProfiles() && isCurrentProfile(nid); + } + + boolean supportsProfiles() { + return targetSdkVersion >= Build.VERSION_CODES.L; } public void notifyPostedIfUserMatch(StatusBarNotification sbn) { @@ -299,11 +308,11 @@ public class NotificationManagerService extends SystemService { removeListenerImpl(this.listener, this.userid); } - /** convenience method for looking in mEnabledListenersForCurrentUser */ - public boolean isEnabledForCurrentUser() { + /** convenience method for looking in mEnabledListenersForCurrentProfiles */ + public boolean isEnabledForCurrentProfiles() { if (this.isSystem) return true; if (this.connection == null) return false; - return mEnabledListenersForCurrentUser.contains(this.component); + return mEnabledListenersForCurrentProfiles.contains(this.component); } } @@ -506,18 +515,25 @@ public class NotificationManagerService extends SystemService { * Remove notification access for any services that no longer exist. */ void disableNonexistentListeners() { - int currentUser = ActivityManager.getCurrentUser(); + int[] userIds = getCurrentProfileIds(); + final int N = userIds.length; + for (int i = 0 ; i < N; ++i) { + disableNonexistentListeners(userIds[i]); + } + } + + void disableNonexistentListeners(int userId) { String flatIn = Settings.Secure.getStringForUser( getContext().getContentResolver(), Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - currentUser); + userId); if (!TextUtils.isEmpty(flatIn)) { if (DBG) Slog.v(TAG, "flat before: " + flatIn); PackageManager pm = getContext().getPackageManager(); List installedServices = pm.queryIntentServicesAsUser( new Intent(NotificationListenerService.SERVICE_INTERFACE), PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, - currentUser); + userId); Set installed = new HashSet(); for (int i = 0, count = installedServices.size(); i < count; i++) { @@ -551,7 +567,7 @@ public class NotificationManagerService extends SystemService { if (!flatIn.equals(flatOut)) { Settings.Secure.putStringForUser(getContext().getContentResolver(), Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - flatOut, currentUser); + flatOut, userId); } } } @@ -561,54 +577,70 @@ public class NotificationManagerService extends SystemService { * 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( - getContext().getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - currentUser); + final int[] userIds = getCurrentProfileIds(); + final int nUserIds = userIds.length; + + final SparseArray flat = new SparseArray(); + + for (int i = 0; i < nUserIds; ++i) { + flat.put(userIds[i], Settings.Secure.getStringForUser( + getContext().getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + userIds[i])); + } NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()]; - final ArrayList toAdd; + final SparseArray> toAdd + = new SparseArray>(); synchronized (mNotificationList) { // unbind and remove all existing listeners toRemove = mListeners.toArray(toRemove); - toAdd = new ArrayList(); final HashSet newEnabled = new HashSet(); final HashSet newPackages = new HashSet(); - // decode the list of components - if (flat != null) { - String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); - for (int i=0; i add = new ArrayList(); + toAdd.put(userIds[i], add); + + // decode the list of components + String toDecode = flat.get(userIds[i]); + if (toDecode != null) { + String[] components = toDecode.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); + for (int j = 0; j < components.length; j++) { + final ComponentName component + = ComponentName.unflattenFromString(components[j]); + if (component != null) { + newEnabled.add(component); + add.add(component); + newPackages.add(component.getPackageName()); + } } - } - mEnabledListenersForCurrentUser = newEnabled; - mEnabledListenerPackageNames = newPackages; + } } + mEnabledListenersForCurrentProfiles = 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); + Slog.v(TAG, "disabling notification listener for user " + + oldUser + ": " + component); unregisterListenerService(component, info.userid); } - final int N = toAdd.size(); - for (int i=0; i add = toAdd.get(userIds[i]); + final int N = add.size(); + for (int j = 0; j < N; j++) { + final ComponentName component = add.get(j); + Slog.v(TAG, "enabling notification listener for user " + userIds[i] + ": " + + component); + registerListenerService(component, userIds[i]); + } } } @@ -656,6 +688,16 @@ public class NotificationManagerService extends SystemService { getContext(), 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0); intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent); + ApplicationInfo appInfo = null; + try { + appInfo = getContext().getPackageManager().getApplicationInfo( + name.getPackageName(), 0); + } catch (NameNotFoundException e) { + // Ignore if the package doesn't exist we won't be able to bind to the service. + } + final int targetSdkVersion = + appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE; + try { if (DBG) Slog.v(TAG, "binding: " + intent); if (!getContext().bindServiceAsUser(intent, @@ -671,7 +713,8 @@ public class NotificationManagerService extends SystemService { mListener = INotificationListener.Stub.asInterface(service); NotificationListenerInfo info = new NotificationListenerInfo( - mListener, name, userid, this); + mListener, name, userid, this, + targetSdkVersion); service.linkToDeath(info, 0); added = mListeners.add(info); } catch (RemoteException e) { @@ -945,7 +988,8 @@ public class NotificationManagerService extends SystemService { @Override public void onClearAll(int callingUid, int callingPid, int userId) { synchronized (mNotificationList) { - cancelAllLocked(callingUid, callingPid, userId, REASON_DELEGATE_CANCEL_ALL, null); + cancelAllLocked(callingUid, callingPid, userId, REASON_DELEGATE_CANCEL_ALL, null, + /*includeCurrentProfiles*/ true); } } @@ -1239,6 +1283,8 @@ public class NotificationManagerService extends SystemService { } updateZenMode(); + updateCurrentProfilesCache(getContext()); + // register for various Intents IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_ON); @@ -1582,14 +1628,21 @@ public class NotificationManagerService extends SystemService { final int N = keys.length; for (int i = 0; i < N; i++) { NotificationRecord r = mNotificationsByKey.get(keys[i]); + final int userId = r.sbn.getUserId(); + if (userId != info.userid && userId != UserHandle.USER_ALL && + !isCurrentProfile(userId)) { + throw new SecurityException("Disallowed call from listener: " + + info.listener); + } if (r != null) { cancelNotificationFromListenerLocked(info, callingUid, callingPid, - r.sbn.getPackageName(), r.sbn.getTag(), r.sbn.getId()); + r.sbn.getPackageName(), r.sbn.getTag(), r.sbn.getId(), + userId); } } } else { cancelAllLocked(callingUid, callingPid, info.userid, - REASON_LISTENER_CANCEL_ALL, info); + REASON_LISTENER_CANCEL_ALL, info, info.supportsProfiles()); } } } finally { @@ -1598,11 +1651,11 @@ public class NotificationManagerService extends SystemService { } private void cancelNotificationFromListenerLocked(NotificationListenerInfo info, - int callingUid, int callingPid, String pkg, String tag, int id) { + int callingUid, int callingPid, String pkg, String tag, int id, int userId) { cancelNotification(callingUid, callingPid, pkg, tag, id, 0, Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE, true, - info.userid, REASON_LISTENER_CANCEL, info); + userId, REASON_LISTENER_CANCEL, info); } /** @@ -1621,8 +1674,14 @@ public class NotificationManagerService extends SystemService { try { synchronized (mNotificationList) { final NotificationListenerInfo info = checkListenerTokenLocked(token); - cancelNotificationFromListenerLocked(info, callingUid, callingPid, - pkg, tag, id); + if (info.supportsProfiles()) { + Log.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) " + + "from " + info.component + + " use cancelNotification(key) instead."); + } else { + cancelNotificationFromListenerLocked(info, callingUid, callingPid, + pkg, tag, id, info.userid); + } } } finally { Binder.restoreCallingIdentity(identity); @@ -1701,9 +1760,9 @@ public class NotificationManagerService extends SystemService { void dumpImpl(PrintWriter pw) { pw.println("Current Notification Manager state:"); - pw.println(" Listeners (" + mEnabledListenersForCurrentUser.size() - + ") enabled for current user:"); - for (ComponentName cmpt : mEnabledListenersForCurrentUser) { + pw.println(" Listeners (" + mEnabledListenersForCurrentProfiles.size() + + ") enabled for current profiles:"); + for (ComponentName cmpt : mEnabledListenersForCurrentProfiles) { pw.println(" " + cmpt); } @@ -1995,7 +2054,8 @@ public class NotificationManagerService extends SystemService { && (!(old != null && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) && (r.getUserId() == UserHandle.USER_ALL || - (r.getUserId() == userId && r.getUserId() == currentUser)) + (r.getUserId() == userId && r.getUserId() == currentUser) || + isCurrentProfile(r.getUserId())) && canInterrupt && mSystemReady && mAudioManager != null) { @@ -2131,7 +2191,8 @@ public class NotificationManagerService extends SystemService { synchronized (mNotificationList) { try { NotificationListenerInfo info - = new NotificationListenerInfo(listener, component, userid, true); + = new NotificationListenerInfo(listener, component, userid, + /*isSystem*/ true, Build.VERSION_CODES.L); listener.asBinder().linkToDeath(info, 0); mListeners.add(info); } catch (RemoteException e) { @@ -2461,10 +2522,8 @@ public class NotificationManagerService extends SystemService { * because it matches one of the users profiles. */ private boolean notificationMatchesCurrentProfiles(NotificationRecord r, int userId) { - synchronized (mCurrentProfiles) { - return notificationMatchesUserId(r, userId) - || mCurrentProfiles.get(r.getUserId()) != null; - } + return notificationMatchesUserId(r, userId) + || isCurrentProfile(r.getUserId()); } /** @@ -2553,7 +2612,7 @@ public class NotificationManagerService extends SystemService { } void cancelAllLocked(int callingUid, int callingPid, int userId, int reason, - NotificationListenerInfo listener) { + NotificationListenerInfo listener, boolean includeCurrentProfiles) { EventLogTags.writeNotificationCancelAll(callingUid, callingPid, null, userId, 0, 0, reason, listener == null ? null : listener.component.toShortString()); @@ -2561,8 +2620,14 @@ public class NotificationManagerService extends SystemService { final int N = mNotificationList.size(); for (int i=N-1; i>=0; i--) { NotificationRecord r = mNotificationList.get(i); - if (!notificationMatchesCurrentProfiles(r, userId)) { - continue; + if (includeCurrentProfiles) { + if (!notificationMatchesCurrentProfiles(r, userId)) { + continue; + } + } else { + if (!notificationMatchesUserId(r, userId)) { + continue; + } } if ((r.getFlags() & (Notification.FLAG_ONGOING_EVENT @@ -2678,8 +2743,8 @@ public class NotificationManagerService extends SystemService { private void updateCurrentProfilesCache(Context context) { UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); - int currentUserId = ActivityManager.getCurrentUser(); if (userManager != null) { + int currentUserId = ActivityManager.getCurrentUser(); List profiles = userManager.getProfiles(currentUserId); synchronized (mCurrentProfiles) { mCurrentProfiles.clear(); @@ -2690,6 +2755,23 @@ public class NotificationManagerService extends SystemService { } } + private int[] getCurrentProfileIds() { + synchronized (mCurrentProfiles) { + int[] users = new int[mCurrentProfiles.size()]; + final int N = mCurrentProfiles.size(); + for (int i = 0; i < N; ++i) { + users[i] = mCurrentProfiles.keyAt(i); + } + return users; + } + } + + private boolean isCurrentProfile(int userId) { + synchronized (mCurrentProfiles) { + return mCurrentProfiles.get(userId) != null; + } + } + private boolean isCall(String pkg, Notification n) { return CALL_PACKAGES.contains(pkg); } -- 2.11.0