Atomic updates, icon cleanup for overflow

Bug: 149918957
Fixes: 149801848
Test: manual - overflow has smoother animations
Test: manual - overflowing bubbles out of overflow works as intended

Change-Id: I9c0c5ea37c5638b5d4a8d979f280cd426f18c0c6
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 4d7eb75..df9effd 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -171,7 +171,7 @@
     private INotificationManager mINotificationManager;
 
     // Callback that updates BubbleOverflowActivity on data change.
-    @Nullable private Runnable mOverflowCallback = null;
+    @Nullable private BubbleData.Listener mOverflowListener = null;
 
     private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private IStatusBarService mBarService;
@@ -581,8 +581,8 @@
         mInflateSynchronously = inflateSynchronously;
     }
 
-    void setOverflowCallback(Runnable updateOverflow) {
-        mOverflowCallback = updateOverflow;
+    void setOverflowListener(BubbleData.Listener listener) {
+        mOverflowListener = listener;
     }
 
     /**
@@ -959,8 +959,8 @@
         @Override
         public void applyUpdate(BubbleData.Update update) {
             // Update bubbles in overflow.
-            if (mOverflowCallback != null) {
-                mOverflowCallback.run();
+            if (mOverflowListener != null) {
+                mOverflowListener.applyUpdate(update);
             }
 
             // Collapsing? Do this first before remaining steps.
@@ -984,7 +984,8 @@
                     if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey())
                         && (!bubble.showInShade()
                             || reason == DISMISS_NOTIF_CANCEL
-                            || reason == DISMISS_GROUP_CANCELLED)) {
+                            || reason == DISMISS_GROUP_CANCELLED
+                            || reason == DISMISS_OVERFLOW_MAX_REACHED)) {
                         // The bubble is now gone & the notification is hidden from the shade, so
                         // time to actually remove it
                         for (NotifCallback cb : mCallbacks) {
@@ -1051,9 +1052,6 @@
                     Log.d(TAG, BubbleDebugConfig.formatBubblesString(mStackView.getBubblesOnScreen(),
                             mStackView.getExpandedBubble()));
                 }
-                Log.d(TAG, "\n[BubbleData] overflow:");
-                Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getOverflowBubbles(),
-                        null) + "\n");
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 35a4811..16140f7 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -73,6 +73,8 @@
         @Nullable Bubble selectedBubble;
         @Nullable Bubble addedBubble;
         @Nullable Bubble updatedBubble;
+        @Nullable Bubble addedOverflowBubble;
+        @Nullable Bubble removedOverflowBubble;
         // Pair with Bubble and @DismissReason Integer
         final List<Pair<Bubble, Integer>> removedBubbles = new ArrayList<>();
 
@@ -91,10 +93,12 @@
                     || addedBubble != null
                     || updatedBubble != null
                     || !removedBubbles.isEmpty()
+                    || addedOverflowBubble != null
+                    || removedOverflowBubble != null
                     || orderChanged;
         }
 
-        void bubbleRemoved(Bubble bubbleToRemove, @DismissReason  int reason) {
+        void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) {
             removedBubbles.add(new Pair<>(bubbleToRemove, reason));
         }
     }
@@ -232,6 +236,7 @@
 
     private void moveOverflowBubbleToPending(Bubble b) {
         mOverflowBubbles.remove(b);
+        mStateChange.removedOverflowBubble = b;
         mPendingBubbles.add(b);
     }
 
@@ -439,8 +444,9 @@
                 if (DEBUG_BUBBLE_DATA) {
                     Log.d(TAG, "Cancel overflow bubble: " + b);
                 }
-                mStateChange.bubbleRemoved(b, reason);
                 mOverflowBubbles.remove(b);
+                mStateChange.bubbleRemoved(b, reason);
+                mStateChange.removedOverflowBubble = b;
             }
             return;
         }
@@ -481,6 +487,7 @@
             Log.d(TAG, "Overflowing: " + bubble);
         }
         mOverflowBubbles.add(0, bubble);
+        mStateChange.addedOverflowBubble = bubble;
         bubble.stopInflation();
         if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
             // Remove oldest bubble.
@@ -488,8 +495,9 @@
             if (DEBUG_BUBBLE_DATA) {
                 Log.d(TAG, "Overflow full. Remove: " + oldest);
             }
-            mStateChange.bubbleRemoved(oldest, BubbleController.DISMISS_OVERFLOW_MAX_REACHED);
             mOverflowBubbles.remove(oldest);
+            mStateChange.removedOverflowBubble = oldest;
+            mStateChange.bubbleRemoved(oldest, BubbleController.DISMISS_OVERFLOW_MAX_REACHED);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 37841f2..131ece1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -95,11 +95,12 @@
         mAdapter = new BubbleOverflowAdapter(mOverflowBubbles,
                 mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight);
         mRecyclerView.setAdapter(mAdapter);
-        onDataChanged(mBubbleController.getOverflowBubbles());
-        mBubbleController.setOverflowCallback(() -> {
-            onDataChanged(mBubbleController.getOverflowBubbles());
-        });
-        onThemeChanged();
+
+        mOverflowBubbles.addAll(mBubbleController.getOverflowBubbles());
+        mAdapter.notifyDataSetChanged();
+        setEmptyStateVisibility();
+
+        mBubbleController.setOverflowListener(mDataListener);
     }
 
     /**
@@ -126,6 +127,14 @@
         }
     }
 
+    void setEmptyStateVisibility() {
+        if (mOverflowBubbles.isEmpty()) {
+            mEmptyState.setVisibility(View.VISIBLE);
+        } else {
+            mEmptyState.setVisibility(View.GONE);
+        }
+    }
+
     void setBackgroundColor() {
         final TypedArray ta = getApplicationContext().obtainStyledAttributes(
                 new int[]{android.R.attr.colorBackgroundFloating});
@@ -134,22 +143,40 @@
         findViewById(android.R.id.content).setBackgroundColor(bgColor);
     }
 
-    void onDataChanged(List<Bubble> bubbles) {
-        mOverflowBubbles.clear();
-        mOverflowBubbles.addAll(bubbles);
-        mAdapter.notifyDataSetChanged();
+    private final BubbleData.Listener mDataListener = new BubbleData.Listener() {
 
-        if (mOverflowBubbles.isEmpty()) {
-            mEmptyState.setVisibility(View.VISIBLE);
-        } else {
-            mEmptyState.setVisibility(View.GONE);
-        }
+        @Override
+        public void applyUpdate(BubbleData.Update update) {
 
-        if (DEBUG_OVERFLOW) {
-            Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString(
-                    mOverflowBubbles, /*selected*/ null));
+            Bubble toRemove = update.removedOverflowBubble;
+            if (toRemove != null) {
+                if (DEBUG_OVERFLOW) {
+                    Log.d(TAG, "remove: " + toRemove);
+                }
+                toRemove.cleanupViews();
+                int i = mOverflowBubbles.indexOf(toRemove);
+                mOverflowBubbles.remove(toRemove);
+                mAdapter.notifyItemRemoved(i);
+            }
+
+            Bubble toAdd = update.addedOverflowBubble;
+            if (toAdd != null) {
+                if (DEBUG_OVERFLOW) {
+                    Log.d(TAG, "add: " + toAdd);
+                }
+                mOverflowBubbles.add(0, toAdd);
+                mAdapter.notifyItemInserted(0);
+            }
+
+            setEmptyStateVisibility();
+
+            if (DEBUG_OVERFLOW) {
+                Log.d(TAG, BubbleDebugConfig.formatBubblesString(
+                        mBubbleController.getOverflowBubbles(),
+                        null));
+            }
         }
-    }
+    };
 
     @Override
     public void onStart() {