Deduping multiple prediction UI update calls

Bug: 179445858
Test: Manual
Change-Id: I6ad86af247a6d94dcaf45206b3d7fb8c44c602d6
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 5e81fba..0156e8f 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -74,11 +74,13 @@
     private static final int FLAG_UPDATE_PAUSED = 1 << 0;
     private static final int FLAG_DRAG_IN_PROGRESS = 1 << 1;
     private static final int FLAG_FILL_IN_PROGRESS = 1 << 2;
+    private static final int FLAG_REMOVING_PREDICTED_ICON = 1 << 3;
 
     private int mHotSeatItemsCount;
 
     private QuickstepLauncher mLauncher;
     private final Hotseat mHotseat;
+    private final Runnable mUpdateFillIfNotLoading = this::updateFillIfNotLoading;
 
     private List<ItemInfo> mPredictedItems = Collections.emptyList();
 
@@ -132,7 +134,8 @@
     private void onHotseatHierarchyChanged() {
         if (mPauseFlags == 0 && !mLauncher.isWorkspaceLoading()) {
             // Post update after a single frame to avoid layout within layout
-            MAIN_EXECUTOR.getHandler().post(this::updateFillIfNotLoading);
+            MAIN_EXECUTOR.getHandler().removeCallbacks(mUpdateFillIfNotLoading);
+            MAIN_EXECUTOR.getHandler().post(mUpdateFillIfNotLoading);
         }
     }
 
@@ -374,7 +377,7 @@
                 continue;
             }
             if (dragObject.dragSource == this && icon.equals(dragObject.originalView)) {
-                mHotseat.removeView(icon);
+                removeIconWithoutNotify(icon);
                 continue;
             }
             int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
@@ -386,7 +389,7 @@
                 @Override
                 public void onAnimationSuccess(Animator animator) {
                     if (icon.getParent() != null) {
-                        mHotseat.removeView(icon);
+                        removeIconWithoutNotify(icon);
                     }
                 }
             });
@@ -395,6 +398,17 @@
         mIconRemoveAnimators.start();
     }
 
+    /**
+     * Removes icon while suppressing any extra tasks performed on view-hierarchy changes.
+     * This avoids recursive/redundant updates as the control updates the UI anyway after
+     * it's animation.
+     */
+    private void removeIconWithoutNotify(PredictedAppIcon icon) {
+        mPauseFlags |= FLAG_REMOVING_PREDICTED_ICON;
+        mHotseat.removeView(icon);
+        mPauseFlags &= ~FLAG_REMOVING_PREDICTED_ICON;
+    }
+
     @Override
     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
         removePredictedApps(mOutlineDrawings, dragObject);