diff options
6 files changed, 180 insertions, 32 deletions
diff --git a/api/current.txt b/api/current.txt index faeab39..190397b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -24317,8 +24317,12 @@ package android.service.notification { ctor public NotificationListenerService(); method public final void cancelAllNotifications(); method public final void cancelNotification(java.lang.String, java.lang.String, int); + method public final void cancelNotifications(java.lang.String[]); + method public java.lang.String[] getActiveNotificationKeys(); method public android.service.notification.StatusBarNotification[] getActiveNotifications(); + method public android.service.notification.StatusBarNotification[] getActiveNotifications(java.lang.String[]); method public android.os.IBinder onBind(android.content.Intent); + method public void onListenerConnected(java.lang.String[]); method public abstract void onNotificationPosted(android.service.notification.StatusBarNotification); method public abstract void onNotificationRemoved(android.service.notification.StatusBarNotification); field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService"; @@ -24330,6 +24334,7 @@ package android.service.notification { method public android.service.notification.StatusBarNotification clone(); method public int describeContents(); method public int getId(); + method public java.lang.String getKey(); method public android.app.Notification getNotification(); method public java.lang.String getPackageName(); method public long getPostTime(); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 9f933ca..9911467 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -45,7 +45,8 @@ interface INotificationManager void unregisterListener(in INotificationListener listener, int userid); void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id); - void cancelAllNotificationsFromListener(in INotificationListener token); + void cancelNotificationsFromListener(in INotificationListener token, in String[] keys); - StatusBarNotification[] getActiveNotificationsFromListener(in INotificationListener token); + StatusBarNotification[] getActiveNotificationsFromListener(in INotificationListener token, in String[] keys); + String[] getActiveNotificationKeysFromListener(in INotificationListener token); }
\ No newline at end of file diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl index 425fdc1..d4b29d8 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -21,6 +21,7 @@ import android.service.notification.StatusBarNotification; /** @hide */ oneway interface INotificationListener { + void onListenerConnected(in String[] notificationKeys); void onNotificationPosted(in StatusBarNotification notification); void onNotificationRemoved(in StatusBarNotification notification); }
\ No newline at end of file diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index cf862b8..050e1a0 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -83,6 +83,17 @@ public abstract class NotificationListenerService extends Service { */ public abstract void onNotificationRemoved(StatusBarNotification sbn); + /** + * Implement this method to learn about when the listener is enabled and connected to + * the notification manager. You are safe to call {@link #getActiveNotifications(String[]) + * at this time. + * + * @param notificationKeys The notification keys for all currently posted notifications. + */ + public void onListenerConnected(String[] notificationKeys) { + // optional + } + private final INotificationManager getNotificationInterface() { if (mNoMan == null) { mNoMan = INotificationManager.Stub.asInterface( @@ -132,9 +143,23 @@ public abstract class NotificationListenerService extends Service { * {@see #cancelNotification(String, String, int)} */ public final void cancelAllNotifications() { + cancelNotifications(null /*all*/); + } + + /** + * Inform the notification manager about dismissal of specific notifications. + * <p> + * Use this if your listener has a user interface that allows the user to dismiss + * multiple notifications at once. + * + * @param keys Notifications to dismiss, or {@code null} to dismiss all. + * + * {@see #cancelNotification(String, String, int)} + */ + public final void cancelNotifications(String[] keys) { if (!isBound()) return; try { - getNotificationInterface().cancelAllNotificationsFromListener(mWrapper); + getNotificationInterface().cancelNotificationsFromListener(mWrapper, keys); } catch (android.os.RemoteException ex) { Log.v(TAG, "Unable to contact notification manager", ex); } @@ -142,14 +167,43 @@ public abstract class NotificationListenerService extends Service { /** * Request the list of outstanding notifications (that is, those that are visible to the - * current user). Useful when starting up and you don't know what's already been posted. + * current user). Useful when you don't know what's already been posted. * * @return An array of active notifications. */ public StatusBarNotification[] getActiveNotifications() { + return getActiveNotifications(null /*all*/); + } + + /** + * Request the list of outstanding notifications (that is, those that are visible to the + * current user). Useful when you don't know what's already been posted. + * + * @param keys A specific list of notification keys, or {@code null} for all. + * @return An array of active notifications. + */ + public StatusBarNotification[] getActiveNotifications(String[] keys) { + if (!isBound()) return null; + try { + return getNotificationInterface().getActiveNotificationsFromListener(mWrapper, keys); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + } + return null; + } + + /** + * Request the list of outstanding notification keys(that is, those that are visible to the + * current user). You can use the notification keys for subsequent retrieval via + * {@link #getActiveNotifications(String[]) or dismissal via + * {@link #cancelNotifications(String[]). + * + * @return An array of active notification keys. + */ + public String[] getActiveNotificationKeys() { if (!isBound()) return null; try { - return getNotificationInterface().getActiveNotificationsFromListener(mWrapper); + return getNotificationInterface().getActiveNotificationKeysFromListener(mWrapper); } catch (android.os.RemoteException ex) { Log.v(TAG, "Unable to contact notification manager", ex); } @@ -189,5 +243,13 @@ public abstract class NotificationListenerService extends Service { Log.w(TAG, "Error running onNotificationRemoved", t); } } + @Override + public void onListenerConnected(String[] notificationKeys) { + try { + NotificationListenerService.this.onListenerConnected(notificationKeys); + } catch (Throwable t) { + Log.w(TAG, "Error running onListenerConnected", t); + } + } } } diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index b5b9e14..7f15ab8 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -29,6 +29,7 @@ public class StatusBarNotification implements Parcelable { private final String pkg; private final int id; private final String tag; + private final String key; private final int uid; private final String basePkg; @@ -68,8 +69,8 @@ public class StatusBarNotification implements Parcelable { this.notification = notification; this.user = user; this.notification.setUser(user); - this.postTime = postTime; + this.key = key(); } public StatusBarNotification(Parcel in) { @@ -88,6 +89,11 @@ public class StatusBarNotification implements Parcelable { this.user = UserHandle.readFromParcel(in); this.notification.setUser(this.user); this.postTime = in.readLong(); + this.key = key(); + } + + private String key() { + return pkg + '|' + basePkg + '|' + id + '|' + tag + '|' + uid; } public void writeToParcel(Parcel out, int flags) { @@ -148,9 +154,9 @@ public class StatusBarNotification implements Parcelable { @Override public String toString() { return String.format( - "StatusBarNotification(pkg=%s user=%s id=%d tag=%s score=%d: %s)", + "StatusBarNotification(pkg=%s user=%s id=%d tag=%s score=%d key=%s: %s)", this.pkg, this.user, this.id, this.tag, - this.score, this.notification); + this.score, this.key, this.notification); } /** Convenience method to check the notification's flags for @@ -230,4 +236,11 @@ public class StatusBarNotification implements Parcelable { public int getScore() { return score; } + + /** + * A unique instance key for this notification record. + */ + public String getKey() { + return key; + } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e25e42c..ccd6f60 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -65,8 +65,8 @@ import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.AtomicFile; -import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -76,7 +76,6 @@ import android.view.accessibility.AccessibilityManager; import android.widget.Toast; import com.android.internal.R; - import com.android.internal.notification.NotificationScorer; import com.android.server.EventLogTags; import com.android.server.statusbar.StatusBarManagerInternal; @@ -169,7 +168,8 @@ public class NotificationManagerService extends SystemService { // used as a mutex for access to all active notifications & listeners final ArrayList<NotificationRecord> mNotificationList = new ArrayList<NotificationRecord>(); - + final ArrayMap<String, NotificationRecord> mNotificationsByKey = + new ArrayMap<String, NotificationRecord>(); final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>(); ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>(); @@ -661,6 +661,7 @@ public class NotificationManagerService extends SystemService { @Override public void onServiceConnected(ComponentName name, IBinder service) { + boolean added = false; synchronized (mNotificationList) { mServicesBinding.remove(servicesBindingTag); try { @@ -669,11 +670,20 @@ public class NotificationManagerService extends SystemService { = new NotificationListenerInfo( mListener, name, userid, this); service.linkToDeath(info, 0); - mListeners.add(info); + added = mListeners.add(info); } catch (RemoteException e) { // already dead } } + if (added) { + final String[] keys = + getActiveNotificationKeysFromListener(mListener); + try { + mListener.onListenerConnected(keys); + } catch (RemoteException e) { + // we tried + } + } } @Override @@ -799,6 +809,7 @@ public class NotificationManagerService extends SystemService { 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 + " key=" + sbn.getKey()); pw.println(prefix + " contentIntent=" + notification.contentIntent); pw.println(prefix + " deleteIntent=" + notification.deleteIntent); pw.println(prefix + " tickerText=" + notification.tickerText); @@ -855,10 +866,11 @@ public class NotificationManagerService extends SystemService { @Override public final String toString() { return String.format( - "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d: %s)", + "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d key=%s: %s)", System.identityHashCode(this), this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(), - this.sbn.getTag(), this.sbn.getScore(), this.sbn.getNotification()); + this.sbn.getTag(), this.sbn.getScore(), this.sbn.getKey(), + this.sbn.getNotification()); } } @@ -1554,21 +1566,40 @@ public class NotificationManagerService extends SystemService { * @param token The binder for the listener, to check that the caller is allowed */ @Override - public void cancelAllNotificationsFromListener(INotificationListener token) { + public void cancelNotificationsFromListener(INotificationListener token, String[] keys) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); long identity = Binder.clearCallingIdentity(); try { synchronized (mNotificationList) { - NotificationListenerInfo info = checkListenerTokenLocked(token); - cancelAllLocked(callingUid, callingPid, info.userid, - REASON_LISTENER_CANCEL_ALL, info); + final NotificationListenerInfo info = checkListenerTokenLocked(token); + if (keys != null) { + final int N = keys.length; + for (int i = 0; i < N; i++) { + NotificationRecord r = mNotificationsByKey.get(keys[i]); + if (r != null) { + cancelNotificationFromListenerLocked(info, callingUid, callingPid, + r.sbn.getPackageName(), r.sbn.getTag(), r.sbn.getId()); + } + } + } else { + cancelAllLocked(callingUid, callingPid, info.userid, + REASON_LISTENER_CANCEL_ALL, info); + } } } finally { Binder.restoreCallingIdentity(identity); } } + private void cancelNotificationFromListenerLocked(NotificationListenerInfo info, + int callingUid, int callingPid, String pkg, String tag, int id) { + cancelNotification(callingUid, callingPid, pkg, tag, id, 0, + Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE, + true, + info.userid, REASON_LISTENER_CANCEL, info); + } + /** * Allow an INotificationListener to simulate clearing (dismissing) a single notification. * @@ -1584,11 +1615,9 @@ public class NotificationManagerService extends SystemService { long identity = Binder.clearCallingIdentity(); try { synchronized (mNotificationList) { - NotificationListenerInfo info = checkListenerTokenLocked(token); - cancelNotification(callingUid, callingPid, pkg, tag, id, 0, - Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE, - true, - info.userid, REASON_LISTENER_CANCEL, info); + final NotificationListenerInfo info = checkListenerTokenLocked(token); + cancelNotificationFromListenerLocked(info, callingUid, callingPid, + pkg, tag, id); } } finally { Binder.restoreCallingIdentity(identity); @@ -1604,15 +1633,26 @@ public class NotificationManagerService extends SystemService { */ @Override public StatusBarNotification[] getActiveNotificationsFromListener( - INotificationListener token) { + INotificationListener token, String[] keys) { synchronized (mNotificationList) { - NotificationListenerInfo info = checkListenerTokenLocked(token); - ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>(); - 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); + final NotificationListenerInfo info = checkListenerTokenLocked(token); + final ArrayList<StatusBarNotification> list + = new ArrayList<StatusBarNotification>(); + if (keys == null) { + 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); + } + } + } else { + final int N = keys.length; + for (int i=0; i<N; i++) { + NotificationRecord r = mNotificationsByKey.get(keys[i]); + if (r != null && info.enabledAndUserMatches(r.sbn)) { + list.add(r.sbn); + } } } return list.toArray(new StatusBarNotification[list.size()]); @@ -1620,6 +1660,11 @@ public class NotificationManagerService extends SystemService { } @Override + public String[] getActiveNotificationKeysFromListener(INotificationListener token) { + return NotificationManagerService.this.getActiveNotificationKeysFromListener(token); + } + + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -1633,6 +1678,21 @@ public class NotificationManagerService extends SystemService { } }; + private String[] getActiveNotificationKeysFromListener(INotificationListener token) { + synchronized (mNotificationList) { + final NotificationListenerInfo info = checkListenerTokenLocked(token); + final ArrayList<String> keys = new ArrayList<String>(); + final int N = mNotificationList.size(); + for (int i=0; i<N; i++) { + final StatusBarNotification sbn = mNotificationList.get(i).sbn; + if (info.enabledAndUserMatches(sbn)) { + keys.add(sbn.getKey()); + } + } + return keys.toArray(new String[keys.size()]); + } + } + void dumpImpl(PrintWriter pw) { pw.println("Current Notification Manager state:"); @@ -1854,6 +1914,10 @@ public class NotificationManagerService extends SystemService { old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE; } } + if (old != null) { + mNotificationsByKey.remove(old.sbn.getKey()); + } + mNotificationsByKey.put(n.getKey(), r); // Ensure if this is a foreground service that the proper additional // flags are set. @@ -2331,6 +2395,7 @@ public class NotificationManagerService extends SystemService { } mNotificationList.remove(index); + mNotificationsByKey.remove(r.sbn.getKey()); cancelNotificationLocked(r, sendDelete); updateLightsLocked(); @@ -2403,6 +2468,7 @@ public class NotificationManagerService extends SystemService { return true; } mNotificationList.remove(i); + mNotificationsByKey.remove(r.sbn.getKey()); cancelNotificationLocked(r, false); } if (canceledSomething) { @@ -2458,7 +2524,6 @@ public class NotificationManagerService extends SystemService { final int N = mNotificationList.size(); for (int i=N-1; i>=0; i--) { NotificationRecord r = mNotificationList.get(i); - if (!notificationMatchesUserIdOrRelated(r, userId)) { continue; } @@ -2466,6 +2531,7 @@ public class NotificationManagerService extends SystemService { if ((r.getFlags() & (Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR)) == 0) { mNotificationList.remove(i); + mNotificationsByKey.remove(r.sbn.getKey()); cancelNotificationLocked(r, true); } } |