Merge "Worked around a bug where the a group child could be leaked" into oc-dr1-dev
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index e5b1afe..ef4f419 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -89,6 +89,7 @@
         private int mCachedContrastColor = COLOR_INVALID;
         private int mCachedContrastColorIsFor = COLOR_INVALID;
         private InflationTask mRunningTask = null;
+        private Throwable mDebugThrowable;
 
         public Entry(StatusBarNotification n) {
             this.key = n.getKey();
@@ -249,6 +250,19 @@
         public InflationTask getRunningTask() {
             return mRunningTask;
         }
+
+        /**
+         * Set a throwable that is used for debugging
+         *
+         * @param debugThrowable the throwable to save
+         */
+        public void setDebugThrowable(Throwable debugThrowable) {
+            mDebugThrowable = debugThrowable;
+        }
+
+        public Throwable getDebugThrowable() {
+            return mDebugThrowable;
+        }
     }
 
     private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index dd84dea..b75c7e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -18,6 +18,7 @@
 
 import android.service.notification.StatusBarNotification;
 import android.support.annotation.Nullable;
+import android.util.Log;
 
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
@@ -29,7 +30,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 
@@ -38,6 +38,7 @@
  */
 public class NotificationGroupManager implements OnHeadsUpChangedListener {
 
+    private static final String TAG = "NotificationGroupManager";
     private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
     private OnGroupChangeListener mListener;
     private int mBarState = -1;
@@ -96,7 +97,7 @@
             return;
         }
         if (isGroupChild(sbn)) {
-            group.children.remove(removed);
+            group.children.remove(removed.key);
         } else {
             group.summary = null;
         }
@@ -109,6 +110,9 @@
     }
 
     public void onEntryAdded(final NotificationData.Entry added) {
+        if (added.row.isRemoved()) {
+            added.setDebugThrowable(new Throwable());
+        }
         final StatusBarNotification sbn = added.notification;
         boolean isGroupChild = isGroupChild(sbn);
         String groupKey = getGroupKey(sbn);
@@ -118,15 +122,25 @@
             mGroupMap.put(groupKey, group);
         }
         if (isGroupChild) {
-            group.children.add(added);
+            NotificationData.Entry existing = group.children.get(added.key);
+            if (existing != null && existing != added) {
+                Throwable existingThrowable = existing.getDebugThrowable();
+                Log.wtf(TAG, "Inconsistent entries found with the same key " + added.key
+                        + "existing removed: " + existing.row.isRemoved()
+                        + (existingThrowable != null
+                                ? Log.getStackTraceString(existingThrowable) + "\n": "")
+                        + " added removed" + added.row.isRemoved()
+                        , new Throwable());
+            }
+            group.children.put(added.key, added);
             updateSuppression(group);
         } else {
             group.summary = added;
             group.expanded = added.row.areChildrenExpanded();
             updateSuppression(group);
             if (!group.children.isEmpty()) {
-                HashSet<NotificationData.Entry> childrenCopy =
-                        (HashSet<NotificationData.Entry>) group.children.clone();
+                ArrayList<NotificationData.Entry> childrenCopy
+                        = new ArrayList<>(group.children.values());
                 for (NotificationData.Entry child : childrenCopy) {
                     onEntryBecomingChild(child);
                 }
@@ -410,7 +424,8 @@
         // The parent of a suppressed group got huned, lets hun the child!
         NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
         if (notificationGroup != null) {
-            Iterator<NotificationData.Entry> iterator = notificationGroup.children.iterator();
+            Iterator<NotificationData.Entry> iterator
+                    = notificationGroup.children.values().iterator();
             NotificationData.Entry child = iterator.hasNext() ? iterator.next() : null;
             if (child == null) {
                 child = getIsolatedChild(sbn.getGroupKey());
@@ -463,7 +478,7 @@
     }
 
     public static class NotificationGroup {
-        public final HashSet<NotificationData.Entry> children = new HashSet<>();
+        public final HashMap<String, NotificationData.Entry> children = new HashMap<>();
         public NotificationData.Entry summary;
         public boolean expanded;
         /**
@@ -474,10 +489,16 @@
         @Override
         public String toString() {
             String result = "    summary:\n      "
-                    + (summary != null ? summary.notification : "null");
+                    + (summary != null ? summary.notification : "null")
+                    + (summary != null && summary.getDebugThrowable() != null
+                            ? Log.getStackTraceString(summary.getDebugThrowable())
+                            : "");
             result += "\n    children size: " + children.size();
-            for (NotificationData.Entry child : children) {
-                result += "\n      " + child.notification;
+            for (NotificationData.Entry child : children.values()) {
+                result += "\n      " + child.notification
+                + (child.getDebugThrowable() != null
+                        ? Log.getStackTraceString(child.getDebugThrowable())
+                        : "");
             }
             return result;
         }