From a4294297d46cc0b9f45897bc688c267502cce3ef Mon Sep 17 00:00:00 2001 From: John Spurlock Date: Mon, 24 Mar 2014 18:02:32 -0400 Subject: Enhance the NotificationListenerService api. Add to NotificationListenerService: - getActiveNotificationKeys() : String[] - getActiveNotifications(String[] keys) : StatusBarNotification[] - cancelNotifications(String[] keys) : void - onListenerConnected(String[] keys) : void Add to StatusBarNotification: - getKey() : String Bug:13562828 Change-Id: Idd9605dad8f942d68b4b32ab9625faf6c8642db9 --- api/current.txt | 5 + core/java/android/app/INotificationManager.aidl | 5 +- .../notification/INotificationListener.aidl | 1 + .../notification/NotificationListenerService.java | 68 +++++++++++- .../notification/StatusBarNotification.java | 19 +++- .../notification/NotificationManagerService.java | 114 ++++++++++++++++----- 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. + *

+ * 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 mNotificationList = new ArrayList(); - + final ArrayMap mNotificationsByKey = + new ArrayMap(); final ArrayList mToastQueue = new ArrayList(); ArrayList mLights = new ArrayList(); @@ -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 list = new ArrayList(); - final int N = mNotificationList.size(); - for (int i=0; i list + = new ArrayList(); + if (keys == null) { + final int N = mNotificationList.size(); + for (int i=0; i keys = new ArrayList(); + final int N = mNotificationList.size(); + for (int i=0; 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); } } -- cgit v1.1