Deferring all app updates until the pending executor is complete.

We were only deferring full apps binds and not partial updates which could cause
the model to go out of sync with Launcher.

Bug: 72051234
Change-Id: I20db0e86aadd1e6a518237026f6dfb03e469eb87
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5a1143fc..4d820bc 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -150,7 +150,6 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.Executor;
 
 /**
  * Default launcher application.
@@ -1166,7 +1165,7 @@
 
     public void updateIconBadges(final Set<PackageUserKey> updatedBadges) {
         mWorkspace.updateIconBadges(updatedBadges);
-        mAppsView.updateIconBadges(updatedBadges);
+        mAppsView.getAppsStore().updateIconBadges(updatedBadges);
 
         PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(Launcher.this);
         if (popup != null) {
@@ -2527,6 +2526,11 @@
             mPendingExecutor.markCompleted();
         }
         mPendingExecutor = executor;
+        if (!isInState(ALL_APPS)) {
+            mAppsView.getAppsStore().setDeferUpdates(true);
+            mPendingExecutor.execute(() -> mAppsView.getAppsStore().setDeferUpdates(false));
+        }
+
         executor.attachTo(this);
     }
 
@@ -2588,30 +2592,14 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     public void bindAllApplications(ArrayList<AppInfo> apps) {
-        if (mAppsView != null) {
-            Executor pendingExecutor = getPendingExecutor();
-            if (pendingExecutor != null && !isInState(ALL_APPS)) {
-                // Wait until the fade in animation has finished before setting all apps list.
-                pendingExecutor.execute(() -> bindAllApplications(apps));
-                return;
-            }
+        mAppsView.getAppsStore().setApps(apps);
 
-            mAppsView.setApps(apps);
-        }
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.bindAllApplications(apps);
         }
     }
 
     /**
-     * Returns an Executor that will run after the launcher is first drawn (including after the
-     * initial fade in animation). Returns null if the first draw has already occurred.
-     */
-    public @Nullable Executor getPendingExecutor() {
-        return mPendingExecutor != null && mPendingExecutor.canQueue() ? mPendingExecutor : null;
-    }
-
-    /**
      * Copies LauncherModel's map of activities to shortcut ids to Launcher's. This is necessary
      * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
      */
@@ -2627,16 +2615,12 @@
      */
     @Override
     public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps) {
-        if (mAppsView != null) {
-            mAppsView.addOrUpdateApps(apps);
-        }
+        mAppsView.getAppsStore().addOrUpdateApps(apps);
     }
 
     @Override
     public void bindPromiseAppProgressUpdated(PromiseAppInfo app) {
-        if (mAppsView != null) {
-            mAppsView.updatePromiseAppProgress(app);
-        }
+        mAppsView.getAppsStore().updatePromiseAppProgress(app);
     }
 
     @Override
@@ -2682,10 +2666,7 @@
 
     @Override
     public void bindAppInfosRemoved(final ArrayList<AppInfo> appInfos) {
-        // Update AllApps
-        if (mAppsView != null) {
-            mAppsView.removeApps(appInfos);
-        }
+        mAppsView.getAppsStore().removeApps(appInfos);
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index de08eb6..d4277db 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -116,6 +116,8 @@
         mAH = new AdapterHolder[2];
         mAH[AdapterHolder.MAIN] = new AdapterHolder(false /* isWork */);
         mAH[AdapterHolder.WORK] = new AdapterHolder(true /* isWork */);
+
+        mAllAppsStore.addUpdateListener(this::onAppsUpdated);
     }
 
     @Override
@@ -150,43 +152,25 @@
         }
     }
 
+    private void onAppsUpdated() {
+        if (FeatureFlags.ALL_APPS_TABS_ENABLED) {
+            boolean hasWorkApps = false;
+            for (AppInfo app : mAllAppsStore.getApps()) {
+                if (mWorkMatcher.matches(app, null)) {
+                    hasWorkApps = true;
+                    break;
+                }
+            }
+            rebindAdapters(hasWorkApps);
+        }
+    }
+
     @Override
     public void setPressedIcon(BubbleTextView icon, Bitmap background) {
         mTouchFeedbackView.setPressedIcon(icon, background);
     }
 
     /**
-     * Sets the current set of apps.
-     */
-    public void setApps(List<AppInfo> apps) {
-        boolean hasWorkProfileApp = hasWorkProfileApp(apps);
-        rebindAdapters(hasWorkProfileApp);
-        mAllAppsStore.setApps(apps);
-    }
-
-    /**
-     * Adds or updates existing apps in the list
-     */
-    public void addOrUpdateApps(List<AppInfo> apps) {
-        mAllAppsStore.addOrUpdateApps(apps);
-    }
-
-    /**
-     * Removes some apps from the list.
-     */
-    public void removeApps(List<AppInfo> apps) {
-        mAllAppsStore.removeApps(apps);
-    }
-
-    public void updatePromiseAppProgress(PromiseAppInfo app) {
-        mAllAppsStore.updateAllIcons((child) -> {
-            if (child.getTag() == app) {
-                child.applyProgressLevel(app.level);
-            }
-        });
-    }
-
-    /**
      * Returns whether the view itself will handle the touch event or not.
      */
     public boolean shouldContainerScroll(MotionEvent ev) {
@@ -324,18 +308,6 @@
         InsettableFrameLayout.dispatchInsets(this, insets);
     }
 
-    public void updateIconBadges(Set<PackageUserKey> updatedBadges) {
-        PackageUserKey tempKey = new PackageUserKey(null, null);
-        mAllAppsStore.updateAllIcons((child) -> {
-            if (child.getTag() instanceof ItemInfo) {
-                ItemInfo info = (ItemInfo) child.getTag();
-                if (tempKey.updateFromItemInfo(info) && updatedBadges.contains(tempKey)) {
-                    child.applyBadgeState(info, true /* animate */);
-                }
-            }
-        });
-    }
-
     public SpringAnimationHandler getSpringAnimationHandler() {
         return mUsingTabs ? null : mAH[AdapterHolder.MAIN].animationHandler;
     }
@@ -371,17 +343,6 @@
         applyTouchDelegate();
     }
 
-    private boolean hasWorkProfileApp(List<AppInfo> apps) {
-        if (FeatureFlags.ALL_APPS_TABS_ENABLED) {
-            for (AppInfo app : apps) {
-                if (mWorkMatcher.matches(app, null)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     private void replaceRVContainer(boolean showTabs) {
         for (int i = 0; i < mAH.length; i++) {
             if (mAH[i].recyclerView != null) {
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 846b6a9..dc34892 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -20,22 +20,30 @@
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.PromiseAppInfo;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Set;
 
 /**
  * A utility class to maintain the collection of all apps.
  */
 public class AllAppsStore {
 
+    private PackageUserKey mTempKey = new PackageUserKey(null, null);
     private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
     private final List<OnUpdateListener> mUpdateListeners = new ArrayList<>();
     private final ArrayList<ViewGroup> mIconContainers = new ArrayList<>();
 
+    private boolean mDeferUpdates = false;
+    private boolean mUpdatePending = false;
+
     public Collection<AppInfo> getApps() {
         return mComponentToAppMap.values();
     }
@@ -52,6 +60,17 @@
         return mComponentToAppMap.get(key);
     }
 
+    public void setDeferUpdates(boolean deferUpdates) {
+        if (mDeferUpdates != deferUpdates) {
+            mDeferUpdates = deferUpdates;
+
+            if (!mDeferUpdates && mUpdatePending) {
+                notifyUpdate();
+                mUpdatePending = false;
+            }
+        }
+    }
+
     /**
      * Adds or updates existing apps in the list
      */
@@ -74,6 +93,10 @@
 
 
     private void notifyUpdate() {
+        if (mDeferUpdates) {
+            mUpdatePending = true;
+            return;
+        }
         int count = mUpdateListeners.size();
         for (int i = 0; i < count; i++) {
             mUpdateListeners.get(i).onAppsUpdated();
@@ -98,7 +121,26 @@
         mIconContainers.remove(container);
     }
 
-    public void updateAllIcons(IconAction action) {
+    public void updateIconBadges(Set<PackageUserKey> updatedBadges) {
+        updateAllIcons((child) -> {
+            if (child.getTag() instanceof ItemInfo) {
+                ItemInfo info = (ItemInfo) child.getTag();
+                if (mTempKey.updateFromItemInfo(info) && updatedBadges.contains(mTempKey)) {
+                    child.applyBadgeState(info, true /* animate */);
+                }
+            }
+        });
+    }
+
+    public void updatePromiseAppProgress(PromiseAppInfo app) {
+        updateAllIcons((child) -> {
+            if (child.getTag() == app) {
+                child.applyProgressLevel(app.level);
+            }
+        });
+    }
+
+    private void updateAllIcons(IconAction action) {
         for (int i = mIconContainers.size() - 1; i >= 0; i--) {
             ViewGroup parent = mIconContainers.get(i);
             int childCount = parent.getChildCount();