OSDN Git Service

Enhance the NotificationListenerService api.
authorJohn Spurlock <jspurlock@google.com>
Mon, 24 Mar 2014 22:02:32 +0000 (18:02 -0400)
committerJohn Spurlock <jspurlock@google.com>
Fri, 28 Mar 2014 12:47:01 +0000 (08:47 -0400)
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
core/java/android/app/INotificationManager.aidl
core/java/android/service/notification/INotificationListener.aidl
core/java/android/service/notification/NotificationListenerService.java
core/java/android/service/notification/StatusBarNotification.java
services/core/java/com/android/server/notification/NotificationManagerService.java

index faeab39..190397b 100644 (file)
@@ -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();
index 9f933ca..9911467 100644 (file)
@@ -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
index 425fdc1..d4b29d8 100644 (file)
@@ -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
index cf862b8..050e1a0 100644 (file)
@@ -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);
+            }
+        }
     }
 }
index b5b9e14..7f15ab8 100644 (file)
@@ -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;
+    }
 }
index e25e42c..ccd6f60 100644 (file)
@@ -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);
             }
         }