Merge "Fix concurrency issues when parceling StatusBarNotifications." into jb-mr2-dev
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8d994c4..2e328b2 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -635,11 +635,16 @@
     @Override
     public Notification clone() {
         Notification that = new Notification();
-        cloneInto(that);
+        cloneInto(that, true);
         return that;
     }
 
-    private void cloneInto(Notification that) {
+    /**
+     * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
+     * of this into that.
+     * @hide
+     */
+    public void cloneInto(Notification that, boolean heavy) {
         that.when = this.when;
         that.icon = this.icon;
         that.number = this.number;
@@ -652,13 +657,13 @@
         if (this.tickerText != null) {
             that.tickerText = this.tickerText.toString();
         }
-        if (this.tickerView != null) {
+        if (heavy && this.tickerView != null) {
             that.tickerView = this.tickerView.clone();
         }
-        if (this.contentView != null) {
+        if (heavy && this.contentView != null) {
             that.contentView = this.contentView.clone();
         }
-        if (this.largeIcon != null) {
+        if (heavy && this.largeIcon != null) {
             that.largeIcon = Bitmap.createBitmap(this.largeIcon);
         }
         that.iconLevel = this.iconLevel;
@@ -690,7 +695,6 @@
 
         if (this.extras != null) {
             that.extras = new Bundle(this.extras);
-
         }
 
         if (this.actions != null) {
@@ -700,9 +704,30 @@
             }
         }
 
-        if (this.bigContentView != null) {
+        if (heavy && this.bigContentView != null) {
             that.bigContentView = this.bigContentView.clone();
         }
+
+        if (!heavy) {
+            that.lightenPayload(); // will clean out extras
+        }
+    }
+
+    /**
+     * Removes heavyweight parts of the Notification object for archival or for sending to
+     * listeners when the full contents are not necessary.
+     * @hide
+     */
+    public final void lightenPayload() {
+        tickerView = null;
+        contentView = null;
+        bigContentView = null;
+        largeIcon = null;
+        if (extras != null) {
+            extras.remove(Notification.EXTRA_LARGE_ICON);
+            extras.remove(Notification.EXTRA_LARGE_ICON_BIG);
+            extras.remove(Notification.EXTRA_PICTURE);
+        }
     }
 
     public int describeContents() {
@@ -1706,7 +1731,7 @@
          * @hide
          */
         public Notification buildInto(Notification n) {
-            build().cloneInto(n);
+            build().cloneInto(n, true);
             return n;
         }
     }
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 86bab2a..8b72ca9 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -55,10 +55,17 @@
      * <P>
      * This might occur because the user has dismissed the notification using system UI (or another
      * notification listener) or because the app has withdrawn the notification.
+     * <P>
+     * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
+     * {@link StatusBarNotification#notification} member may be missing some heavyweight
+     * fields such as {@link android.app.Notification#contentView} and
+     * {@link android.app.Notification#largeIcon}. However, all other fields on
+     * {@link StatusBarNotification}, sufficient to match this call with a prior call to
+     * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
      *
-     * @param sbn A data structure encapsulating the original {@link android.app.Notification}
-     *            object as well as its identifying information (tag and id) and source
-     *            (package name).
+     * @param sbn A data structure encapsulating at least the original information (tag and id)
+     *            and source (package name) used to post the {@link android.app.Notification} that
+     *            was just removed.
      */
     public abstract void onNotificationRemoved(StatusBarNotification sbn);
 
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index ef5f8c4..006518c 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -152,6 +152,17 @@
         }
     };
 
+    /**
+     * @hide
+     */
+    public StatusBarNotification cloneLight() {
+        final Notification no = new Notification();
+        this.notification.cloneInto(no, false); // light copy
+        return new StatusBarNotification(this.pkg, this.basePkg,
+                this.id, this.tag, this.uid, this.initialPid,
+                this.score, no, this.user, this.postTime);
+    }
+
     @Override
     public StatusBarNotification clone() {
         return new StatusBarNotification(this.pkg, this.basePkg,
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 9a93f63..f0257ffe 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -237,7 +237,7 @@
             try {
                 listener.onNotificationPosted(sbn);
             } catch (RemoteException ex) {
-                // not there?
+                Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
             }
         }
 
@@ -246,7 +246,7 @@
             try {
                 listener.onNotificationRemoved(sbn);
             } catch (RemoteException ex) {
-                // not there?
+                Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
             }
         }
 
@@ -285,14 +285,7 @@
 
         public void record(StatusBarNotification nr) {
             // Nuke heavy parts of notification before storing in archive
-            nr.notification.tickerView = null;
-            nr.notification.contentView = null;
-            nr.notification.bigContentView = null;
-            nr.notification.largeIcon = null;
-            final Bundle extras = nr.notification.extras;
-            extras.remove(Notification.EXTRA_LARGE_ICON);
-            extras.remove(Notification.EXTRA_LARGE_ICON_BIG);
-            extras.remove(Notification.EXTRA_PICTURE);
+            nr.notification.lightenPayload();
 
             if (mBuffer.size() == BUFFER_SIZE) {
                 mBuffer.removeFirst();
@@ -746,7 +739,8 @@
      * asynchronously notify all listeners about a new notification
      */
     private void notifyPostedLocked(NotificationRecord n) {
-        final StatusBarNotification sbn = n.sbn;
+        // make a copy in case changes are made to the underlying Notification object
+        final StatusBarNotification sbn = n.sbn.clone();
         for (final NotificationListenerInfo info : mListeners) {
             mHandler.post(new Runnable() {
                 @Override
@@ -760,12 +754,15 @@
      * asynchronously notify all listeners about a removed notification
      */
     private void notifyRemovedLocked(NotificationRecord n) {
-        final StatusBarNotification sbn = n.sbn;
+        // make a copy in case changes are made to the underlying Notification object
+        // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the notification
+        final StatusBarNotification sbn_light = n.sbn.cloneLight();
+
         for (final NotificationListenerInfo info : mListeners) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    info.notifyRemovedIfUserMatch(sbn);
+                    info.notifyRemovedIfUserMatch(sbn_light);
                 }});
         }
     }