New API to request a list of current notifications.

The ACCESS_NOTIFICATIONS permission is signature|system only.

Change-Id: I41338230aee9611117cbdac251c1b6b6c3cebf00
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 241a9ae..34708ab 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -40,6 +40,10 @@
     public static final int MODE_IGNORED = 1;
     public static final int MODE_ERRORED = 2;
 
+    // when adding one of these:
+    //  - increment _NUM_OP
+    //  - add rows to sOpToSwitch, sOpNames, sOpPerms
+    //  - add descriptive strings to Settings/res/values/arrays.xml
     public static final int OP_NONE = -1;
     public static final int OP_COARSE_LOCATION = 0;
     public static final int OP_FINE_LOCATION = 1;
@@ -66,8 +70,9 @@
     public static final int OP_WRITE_ICC_SMS = 22;
     public static final int OP_WRITE_SETTINGS = 23;
     public static final int OP_SYSTEM_ALERT_WINDOW = 24;
+    public static final int OP_ACCESS_NOTIFICATIONS = 25;
     /** @hide */
-    public static final int _NUM_OP = 25;
+    public static final int _NUM_OP = 26;
 
     /**
      * This maps each operation to the operation that serves as the
@@ -103,6 +108,7 @@
             OP_WRITE_SMS,
             OP_WRITE_SETTINGS,
             OP_SYSTEM_ALERT_WINDOW,
+            OP_ACCESS_NOTIFICATIONS,
     };
 
     /**
@@ -135,6 +141,7 @@
             "WRITE_ICC_SMS",
             "WRITE_SETTINGS",
             "SYSTEM_ALERT_WINDOW",
+            "ACCESS_NOTIFICATIONS",
     };
 
     /**
@@ -167,6 +174,7 @@
             android.Manifest.permission.WRITE_SMS,
             android.Manifest.permission.WRITE_SETTINGS,
             android.Manifest.permission.SYSTEM_ALERT_WINDOW,
+            android.Manifest.permission.ACCESS_NOTIFICATIONS,
     };
 
     public static int opToSwitch(int op) {
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index d400eba..bb10f62 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -21,6 +21,8 @@
 import android.app.Notification;
 import android.content.Intent;
 
+import com.android.internal.statusbar.StatusBarNotification;
+
 /** {@hide} */
 interface INotificationManager
 {
@@ -34,5 +36,7 @@
 
     void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
     boolean areNotificationsEnabledForPackage(String pkg, int uid);
+
+    StatusBarNotification[] getActiveNotifications(String callingPkg);
 }
 
diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.java b/core/java/com/android/internal/statusbar/StatusBarNotification.java
index a91aa3c..23e87fc 100644
--- a/core/java/com/android/internal/statusbar/StatusBarNotification.java
+++ b/core/java/com/android/internal/statusbar/StatusBarNotification.java
@@ -21,23 +21,13 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 
-/*
-boolean clearable = !n.ongoingEvent && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0);
-
-
-// TODO: make this restriction do something smarter like never fill
-// more than two screens.  "Why would anyone need more than 80 characters." :-/
-final int maxTickerLen = 80;
-if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) {
-    truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen);
-}
-*/
-
 /**
- * Class encapsulating a Notification. Sent by the NotificationManagerService to the IStatusBar (in System UI).
+ * Class encapsulating a Notification. Sent by the NotificationManagerService to clients including
+ * the IStatusBar (in System UI).
  */
 public class StatusBarNotification implements Parcelable {
     public final String pkg;
+    public final String basePkg;
     public final int id;
     public final String tag;
     public final int uid;
@@ -47,6 +37,7 @@
     public final Notification notification;
     public final int score;
     public final UserHandle user;
+    public final long postTime;
 
     /** This is temporarily needed for the JB MR1 PDK. */
     @Deprecated
@@ -57,10 +48,23 @@
 
     public StatusBarNotification(String pkg, int id, String tag, int uid, int initialPid, int score,
             Notification notification, UserHandle user) {
+        this(pkg, null, id, tag, uid, initialPid, score, notification, user);
+    }
+
+    public StatusBarNotification(String pkg, String basePkg, int id, String tag, int uid,
+            int initialPid, int score, Notification notification, UserHandle user) {
+        this(pkg, basePkg, id, tag, uid, initialPid, score, notification, user,
+                System.currentTimeMillis());
+    }
+
+    public StatusBarNotification(String pkg, String basePkg, int id, String tag, int uid,
+            int initialPid, int score, Notification notification, UserHandle user,
+            long postTime) {
         if (pkg == null) throw new NullPointerException();
         if (notification == null) throw new NullPointerException();
 
         this.pkg = pkg;
+        this.basePkg = pkg;
         this.id = id;
         this.tag = tag;
         this.uid = uid;
@@ -69,10 +73,13 @@
         this.notification = notification;
         this.user = user;
         this.notification.setUser(user);
+
+        this.postTime = postTime;
     }
 
     public StatusBarNotification(Parcel in) {
         this.pkg = in.readString();
+        this.basePkg = in.readString();
         this.id = in.readInt();
         if (in.readInt() != 0) {
             this.tag = in.readString();
@@ -84,11 +91,13 @@
         this.score = in.readInt();
         this.notification = new Notification(in);
         this.user = UserHandle.readFromParcel(in);
-        this.notification.setUser(user);
+        this.notification.setUser(this.user);
+        this.postTime = in.readLong();
     }
 
     public void writeToParcel(Parcel out, int flags) {
         out.writeString(this.pkg);
+        out.writeString(this.basePkg);
         out.writeInt(this.id);
         if (this.tag != null) {
             out.writeInt(1);
@@ -101,6 +110,8 @@
         out.writeInt(this.score);
         this.notification.writeToParcel(out, flags);
         user.writeToParcel(out, flags);
+
+        out.writeLong(this.postTime);
     }
 
     public int describeContents() {
@@ -123,14 +134,17 @@
 
     @Override
     public StatusBarNotification clone() {
-        return new StatusBarNotification(this.pkg, this.id, this.tag, this.uid, this.initialPid,
-                this.score, this.notification.clone(), this.user);
+        return new StatusBarNotification(this.pkg, this.basePkg,
+                this.id, this.tag, this.uid, this.initialPid,
+                this.score, this.notification.clone(), this.user, this.postTime);
     }
 
     @Override
     public String toString() {
-        return "StatusBarNotification(pkg=" + pkg + " id=" + id + " tag=" + tag + " score=" + score
-                + " notn=" + notification + " user=" + user + ")";
+        return String.format(
+                "StatusBarNotification(pkg=%s user=%s id=%d tag=%s score=%d: %s)",
+                this.pkg, this.user, this.id, this.tag,
+                this.score, this.notification);
     }
 
     public boolean isOngoing() {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5783bf6..5d0614c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2175,6 +2175,14 @@
         android:description="@string/permdesc_updateLock"
         android:protectionLevel="signatureOrSystem" />
 
+    <!-- Allows an application to read the current set of notifications, including
+         any metadata and intents attached.
+         @hide -->
+    <permission android:name="android.permission.ACCESS_NOTIFICATIONS"
+        android:label="@string/permlab_accessNotifications"
+        android:description="@string/permdesc_accessNotifications"
+        android:protectionLevel="signature|system" />
+
     <!-- The system process is explicitly the only one allowed to launch the
          confirmation UI for full backup/restore -->
     <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 22f4e2e..00c6f6d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1806,6 +1806,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_modifyNetworkAccounting">Allows the app to modify how network usage is accounted against apps. Not for use by normal apps.</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_accessNotifications">access notifications</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_accessNotifications">Allows the app to retrieve, examine, and clear notifications, including those posted by other apps.</string>
+
     <!-- Policy administration -->
 
     <!-- Title of policy access to limiting the user's password choices -->
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index d121653..1a2c3de 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -49,6 +49,8 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -77,9 +79,12 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
 
 import libcore.io.IoUtils;
 
@@ -169,6 +174,71 @@
     private static final String TAG_PACKAGE = "package";
     private static final String ATTR_NAME = "name";
 
+    private static class Archive {
+        static final int BUFFER_SIZE = 1000;
+        ArrayDeque<StatusBarNotification> mBuffer = new ArrayDeque<StatusBarNotification>(BUFFER_SIZE);
+
+        public Archive() {
+
+        }
+        public void record(StatusBarNotification nr) {
+            if (mBuffer.size() == BUFFER_SIZE) {
+                mBuffer.removeFirst();
+            }
+            mBuffer.addLast(nr);
+        }
+
+        public void clear() {
+            mBuffer.clear();
+        }
+
+        public Iterator<StatusBarNotification> descendingIterator() {
+            return mBuffer.descendingIterator();
+        }
+        public Iterator<StatusBarNotification> ascendingIterator() {
+            return mBuffer.iterator();
+        }
+        public Iterator<StatusBarNotification> filter(
+                final Iterator<StatusBarNotification> iter, final String pkg, final int userId) {
+            return new Iterator<StatusBarNotification>() {
+                StatusBarNotification mNext = findNext();
+
+                private StatusBarNotification findNext() {
+                    while (iter.hasNext()) {
+                        StatusBarNotification nr = iter.next();
+                        if ((pkg == null || nr.pkg == pkg)
+                                && (userId == UserHandle.USER_ALL || nr.getUserId() == userId)) {
+                            return nr;
+                        }
+                    }
+                    return null;
+                }
+
+                @Override
+                public boolean hasNext() {
+                    return mNext == null;
+                }
+
+                @Override
+                public StatusBarNotification next() {
+                    StatusBarNotification next = mNext;
+                    if (next == null) {
+                        throw new NoSuchElementException();
+                    }
+                    mNext = findNext();
+                    return next;
+                }
+
+                @Override
+                public void remove() {
+                    iter.remove();
+                }
+            };
+        }
+    }
+
+    Archive mArchive = new Archive();
+
     private void loadBlockDb() {
         synchronized(mBlockedPackages) {
             if (mPolicyFile == null) {
@@ -275,44 +345,53 @@
         }
     }
 
-    private static final class NotificationRecord
+    public StatusBarNotification[] getActiveNotifications(String callingPkg) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS,
+                "NotificationManagerService");
+
+        StatusBarNotification[] tmp = null;
+        int userId = UserHandle.getCallingUserId();
+        int uid = Binder.getCallingUid();
+
+        if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
+                == AppOpsManager.MODE_ALLOWED) {
+            synchronized (mNotificationList) {
+                tmp = new StatusBarNotification[mNotificationList.size()];
+                final int N = mNotificationList.size();
+                for (int i=0; i<N; i++) {
+                    tmp[i] = mNotificationList.get(i).sbn;
+                }
+            }
+        }
+        return tmp;
+    }
+
+    public static final class NotificationRecord
     {
-        final String pkg;
-        final String basePkg;
-        final String tag;
-        final int id;
-        final int uid;
-        final int initialPid;
-        final int userId;
-        final Notification notification;
-        final int score;
+        final StatusBarNotification sbn;
         IBinder statusBarKey;
 
-        NotificationRecord(String pkg, String basePkg, String tag, int id, int uid, int initialPid,
-                int userId, int score, Notification notification)
+        NotificationRecord(StatusBarNotification sbn)
         {
-            this.pkg = pkg;
-            this.basePkg = basePkg;
-            this.tag = tag;
-            this.id = id;
-            this.uid = uid;
-            this.initialPid = initialPid;
-            this.userId = userId;
-            this.score = score;
-            this.notification = notification;
+            this.sbn = sbn;
         }
 
+        public Notification getNotification() { return sbn.notification; }
+        public int getFlags() { return sbn.notification.flags; }
+        public int getUserId() { return sbn.getUserId(); }
+
         void dump(PrintWriter pw, String prefix, Context baseContext) {
+            final Notification notification = sbn.notification;
             pw.println(prefix + this);
             pw.println(prefix + "  icon=0x" + Integer.toHexString(notification.icon)
-                    + " / " + idDebugString(baseContext, this.pkg, notification.icon));
+                    + " / " + idDebugString(baseContext, this.sbn.pkg, notification.icon));
             pw.println(prefix + "  pri=" + notification.priority);
-            pw.println(prefix + "  score=" + this.score);
+            pw.println(prefix + "  score=" + this.sbn.score);
             pw.println(prefix + "  contentIntent=" + notification.contentIntent);
             pw.println(prefix + "  deleteIntent=" + notification.deleteIntent);
             pw.println(prefix + "  tickerText=" + notification.tickerText);
             pw.println(prefix + "  contentView=" + notification.contentView);
-            pw.println(prefix + "  uid=" + uid + " userId=" + userId);
+            pw.println(prefix + "  uid=" + this.sbn.uid + " userId=" + this.sbn.getUserId());
             pw.println(prefix + "  defaults=0x" + Integer.toHexString(notification.defaults));
             pw.println(prefix + "  flags=0x" + Integer.toHexString(notification.flags));
             pw.println(prefix + "  sound=" + notification.sound);
@@ -323,15 +402,12 @@
         }
 
         @Override
-        public final String toString()
-        {
-            return "NotificationRecord{"
-                + Integer.toHexString(System.identityHashCode(this))
-                + " pkg=" + pkg
-                + " id=" + Integer.toHexString(id)
-                + " tag=" + tag 
-                + " score=" + score
-                + "}";
+        public final String toString() {
+            return String.format(
+                    "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d: %s)",
+                    System.identityHashCode(this),
+                    this.sbn.pkg, this.sbn.user, this.sbn.id, this.sbn.tag,
+                    this.sbn.score, this.sbn.notification);
         }
     }
 
@@ -916,7 +992,7 @@
                 final int N = mNotificationList.size();
                 for (int i=0; i<N; i++) {
                     final NotificationRecord r = mNotificationList.get(i);
-                    if (r.pkg.equals(pkg) && r.userId == userId) {
+                    if (r.sbn.pkg.equals(pkg) && r.sbn.getUserId() == userId) {
                         count++;
                         if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                             Slog.e(TAG, "Package has already posted " + count
@@ -986,10 +1062,9 @@
         final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD);
 
         synchronized (mNotificationList) {
-            NotificationRecord r = new NotificationRecord(pkg, basePkg, tag, id,
-                    callingUid, callingPid, userId,
-                    score,
-                    notification);
+            final StatusBarNotification n = new StatusBarNotification(
+                    pkg, id, tag, callingUid, callingPid, score, notification, user);
+            NotificationRecord r = new NotificationRecord(n);
             NotificationRecord old = null;
 
             int index = indexOfNotificationLocked(pkg, tag, id, userId);
@@ -1001,7 +1076,7 @@
                 // Make sure we don't lose the foreground service state.
                 if (old != null) {
                     notification.flags |=
-                        old.notification.flags&Notification.FLAG_FOREGROUND_SERVICE;
+                        old.getNotification().flags&Notification.FLAG_FOREGROUND_SERVICE;
                 }
             }
 
@@ -1021,8 +1096,6 @@
             }
 
             if (notification.icon != 0) {
-                final StatusBarNotification n = new StatusBarNotification(
-                        pkg, id, tag, r.uid, r.initialPid, score, notification, user);
                 if (old != null && old.statusBarKey != null) {
                     r.statusBarKey = old.statusBarKey;
                     long identity = Binder.clearCallingIdentity();
@@ -1049,6 +1122,9 @@
                 if (currentUser == userId) {
                     sendAccessibilityEvent(notification, pkg);
                 }
+
+                // finally, keep some of this information around for later use
+                mArchive.record(n);
             } else {
                 Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
                 if (old != null && old.statusBarKey != null) {
@@ -1060,14 +1136,15 @@
                         Binder.restoreCallingIdentity(identity);
                     }
                 }
+                return; // do not play sounds, show lights, etc. for invalid notifications
             }
 
             // If we're not supposed to beep, vibrate, etc. then don't.
             if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
                     && (!(old != null
                         && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
-                    && (r.userId == UserHandle.USER_ALL ||
-                        (r.userId == userId && r.userId == currentUser))
+                    && (r.getUserId() == UserHandle.USER_ALL ||
+                        (r.getUserId() == userId && r.getUserId() == currentUser))
                     && canInterrupt
                     && mSystemReady) {
 
@@ -1143,7 +1220,7 @@
                         // does not have the VIBRATE permission.
                         long identity = Binder.clearCallingIdentity();
                         try {
-                            mVibrator.vibrate(r.uid, r.basePkg,
+                            mVibrator.vibrate(r.sbn.uid, r.sbn.basePkg,
                                 useDefaultVibrate ? mDefaultVibrationPattern
                                     : mFallbackVibrationPattern,
                                 ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
@@ -1152,14 +1229,12 @@
                         }
                     } else if (notification.vibrate.length > 1) {
                         // If you want your own vibration pattern, you need the VIBRATE permission
-                        mVibrator.vibrate(r.uid, r.basePkg, notification.vibrate,
+                        mVibrator.vibrate(r.sbn.uid, r.sbn.basePkg, notification.vibrate,
                             ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
                     }
                 }
             }
 
-            // this option doesn't shut off the lights
-
             // light
             // the most recent thing gets the light
             mLights.remove(old);
@@ -1174,7 +1249,7 @@
                 updateLightsLocked();
             } else {
                 if (old != null
-                        && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
+                        && ((old.getFlags() & Notification.FLAG_SHOW_LIGHTS) != 0)) {
                     updateLightsLocked();
                 }
             }
@@ -1205,19 +1280,19 @@
     private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete) {
         // tell the app
         if (sendDelete) {
-            if (r.notification.deleteIntent != null) {
+            if (r.getNotification().deleteIntent != null) {
                 try {
-                    r.notification.deleteIntent.send();
+                    r.getNotification().deleteIntent.send();
                 } catch (PendingIntent.CanceledException ex) {
                     // do nothing - there's no relevant way to recover, and
                     //     no reason to let this propagate
-                    Slog.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
+                    Slog.w(TAG, "canceled PendingIntent for " + r.sbn.pkg, ex);
                 }
             }
         }
 
         // status bar
-        if (r.notification.icon != 0) {
+        if (r.getNotification().icon != 0) {
             long identity = Binder.clearCallingIdentity();
             try {
                 mStatusBar.removeNotification(r.statusBarKey);
@@ -1276,10 +1351,10 @@
             if (index >= 0) {
                 NotificationRecord r = mNotificationList.get(index);
 
-                if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
+                if ((r.getNotification().flags & mustHaveFlags) != mustHaveFlags) {
                     return;
                 }
-                if ((r.notification.flags & mustNotHaveFlags) != 0) {
+                if ((r.getNotification().flags & mustNotHaveFlags) != 0) {
                     return;
                 }
 
@@ -1300,9 +1375,9 @@
                 // looking for USER_ALL notifications? match everything
                    userId == UserHandle.USER_ALL
                 // a notification sent to USER_ALL matches any query
-                || r.userId == UserHandle.USER_ALL
+                || r.getUserId() == UserHandle.USER_ALL
                 // an exact user match
-                || r.userId == userId;
+                || r.getUserId() == userId;
     }
 
     /**
@@ -1323,16 +1398,16 @@
                     continue;
                 }
                 // Don't remove notifications to all, if there's no package name specified
-                if (r.userId == UserHandle.USER_ALL && pkg == null) {
+                if (r.getUserId() == UserHandle.USER_ALL && pkg == null) {
                     continue;
                 }
-                if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
+                if ((r.getFlags() & mustHaveFlags) != mustHaveFlags) {
                     continue;
                 }
-                if ((r.notification.flags & mustNotHaveFlags) != 0) {
+                if ((r.getFlags() & mustNotHaveFlags) != 0) {
                     continue;
                 }
-                if (pkg != null && !r.pkg.equals(pkg)) {
+                if (pkg != null && !r.sbn.pkg.equals(pkg)) {
                     continue;
                 }
                 canceledSomething = true;
@@ -1405,7 +1480,7 @@
                     continue;
                 }
 
-                if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
+                if ((r.getFlags() & (Notification.FLAG_ONGOING_EVENT
                                 | Notification.FLAG_NO_CLEAR)) == 0) {
                     mNotificationList.remove(i);
                     cancelNotificationLocked(r, true);
@@ -1432,10 +1507,11 @@
         if (mLedNotification == null || mInCall || mScreenOn) {
             mNotificationLight.turnOff();
         } else {
-            int ledARGB = mLedNotification.notification.ledARGB;
-            int ledOnMS = mLedNotification.notification.ledOnMS;
-            int ledOffMS = mLedNotification.notification.ledOffMS;
-            if ((mLedNotification.notification.defaults & Notification.DEFAULT_LIGHTS) != 0) {
+            final Notification ledno = mLedNotification.sbn.notification;
+            int ledARGB = ledno.ledARGB;
+            int ledOnMS = ledno.ledOnMS;
+            int ledOffMS = ledno.ledOffMS;
+            if ((ledno.defaults & Notification.DEFAULT_LIGHTS) != 0) {
                 ledARGB = mDefaultNotificationColor;
                 ledOnMS = mDefaultNotificationLedOn;
                 ledOffMS = mDefaultNotificationLedOff;
@@ -1455,19 +1531,19 @@
         final int len = list.size();
         for (int i=0; i<len; i++) {
             NotificationRecord r = list.get(i);
-            if (!notificationMatchesUserId(r, userId) || r.id != id) {
+            if (!notificationMatchesUserId(r, userId) || r.sbn.id != id) {
                 continue;
             }
             if (tag == null) {
-                if (r.tag != null) {
+                if (r.sbn.tag != null) {
                     continue;
                 }
             } else {
-                if (!tag.equals(r.tag)) {
+                if (!tag.equals(r.sbn.tag)) {
                     continue;
                 }
             }
-            if (r.pkg.equals(pkg)) {
+            if (r.sbn.pkg.equals(pkg)) {
                 return i;
             }
         }