Separating apps list callback from model callback and simplifying various events

Bug: 137568159
Change-Id: I4d8bb5c01cabe642731ebd3f57fe15bdc5a569b7
diff --git a/go/src/com/android/launcher3/model/LoaderResults.java b/go/src/com/android/launcher3/model/LoaderResults.java
index b82f362..26c3313 100644
--- a/go/src/com/android/launcher3/model/LoaderResults.java
+++ b/go/src/com/android/launcher3/model/LoaderResults.java
@@ -16,9 +16,8 @@
 
 package com.android.launcher3.model;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 
 import java.lang.ref.WeakReference;
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
index cb5cbdd..623f1ff 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -279,7 +279,7 @@
     }
 
     private List<ItemInfoWithIcon> processPredictedAppComponents(List<ComponentKeyMapper> components) {
-        if (getAppsStore().getApps().isEmpty()) {
+        if (getAppsStore().getApps().length == 0) {
             // Apps have not been bound yet.
             return Collections.emptyList();
         }
diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index ab39274..bc936b7 100644
--- a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -15,14 +15,13 @@
 import android.os.Process;
 import android.os.UserHandle;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppFilter;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.icons.IconCache;
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index d884049..2910525 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -29,11 +29,19 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageManagerHelper;
 
+import java.util.Comparator;
+
 /**
  * Represents an app in AllAppsView.
  */
 public class AppInfo extends ItemInfoWithIcon {
 
+    public static AppInfo[] EMPTY_ARRAY = new AppInfo[0];
+    public static Comparator<AppInfo> COMPONENT_KEY_COMPARATOR = (a, b) -> {
+        int uc = a.user.hashCode() - b.user.hashCode();
+        return uc != 0 ? uc : a.componentName.compareTo(b.componentName);
+    };
+
     /**
      * The intent used to start the application.
      */
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 40d7668..c221a2d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -105,6 +105,7 @@
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate;
 import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.popup.PopupContainerWithArrow;
@@ -112,7 +113,6 @@
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.states.InternalStateHandler;
 import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -163,7 +163,7 @@
  * Default launcher application.
  */
 public class Launcher extends BaseDraggingActivity implements LauncherExterns,
-        LauncherModel.Callbacks, LauncherProviderChangeListener, UserEventDelegate,
+        Callbacks, LauncherProviderChangeListener, UserEventDelegate,
         InvariantDeviceProfile.OnIDPChangeListener {
     public static final String TAG = "Launcher";
     static final boolean LOGD = false;
@@ -2335,7 +2335,7 @@
      *
      * Implementation of the method from LauncherModel.Callbacks.
      */
-    public void bindAllApplications(ArrayList<AppInfo> apps) {
+    public void bindAllApplications(AppInfo[] apps) {
         mAppsView.getAppsStore().setApps(apps);
     }
 
@@ -2348,16 +2348,6 @@
         mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
     }
 
-    /**
-     * A package was updated.
-     *
-     * Implementation of the method from LauncherModel.Callbacks.
-     */
-    @Override
-    public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps) {
-        mAppsView.getAppsStore().addOrUpdateApps(apps);
-    }
-
     @Override
     public void bindPromiseAppProgressUpdated(PromiseAppInfo app) {
         mAppsView.getAppsStore().updatePromiseAppProgress(app);
@@ -2405,11 +2395,6 @@
     }
 
     @Override
-    public void bindAppInfosRemoved(final ArrayList<AppInfo> appInfos) {
-        mAppsView.getAppsStore().removeApps(appInfos);
-    }
-
-    @Override
     public void bindAllWidgets(final ArrayList<WidgetListRowEntry> allWidgets) {
         mPopupDataProvider.setAllWidgets(allWidgets);
     }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index d79f5d5..eed2377 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -38,8 +38,10 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.AddWorkspaceItemsTask;
+import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BaseModelUpdateTask;
 import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.CacheDataUpdatedTask;
 import com.android.launcher3.model.LoaderResults;
 import com.android.launcher3.model.LoaderTask;
@@ -49,20 +51,14 @@
 import com.android.launcher3.model.ShortcutsChangedTask;
 import com.android.launcher3.model.UserLockStateChangedTask;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.util.ViewOnDrawExecutor;
-import com.android.launcher3.widget.WidgetListRowEntry;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.CancellationException;
@@ -133,33 +129,6 @@
         }
     };
 
-    public interface Callbacks {
-        public void rebindModel();
-
-        public int getCurrentWorkspaceScreen();
-        public void clearPendingBinds();
-        public void startBinding();
-        public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
-        public void bindScreens(IntArray orderedScreenIds);
-        public void finishFirstPageBind(ViewOnDrawExecutor executor);
-        public void finishBindingItems(int pageBoundFirst);
-        public void bindAllApplications(ArrayList<AppInfo> apps);
-        public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps);
-        public void preAddApps();
-        public void bindAppsAdded(IntArray newScreens,
-                ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
-        public void bindPromiseAppProgressUpdated(PromiseAppInfo app);
-        public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated);
-        public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
-        public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
-        public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
-        public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
-        public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
-        public void onPageBoundSynchronously(int page);
-        public void executeOnNextDraw(ViewOnDrawExecutor executor);
-        public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
-    }
-
     LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
@@ -411,16 +380,7 @@
             @Override
             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
                 apps.addPromiseApp(app.getContext(), sessionInfo);
-                if (!apps.added.isEmpty()) {
-                    final ArrayList<AppInfo> arrayList = new ArrayList<>(apps.added);
-                    apps.added.clear();
-                    scheduleCallbackTask(new CallbackTask() {
-                        @Override
-                        public void execute(Callbacks callbacks) {
-                            callbacks.bindAppsAddedOrUpdated(arrayList);
-                        }
-                    });
-                }
+                bindApplicationsIfNeeded();
             }
         });
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 160042e..1ff5029 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR;
+import static com.android.launcher3.AppInfo.EMPTY_ARRAY;
+
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -26,8 +29,7 @@
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -45,27 +47,33 @@
     public static final int DEFER_UPDATES_TEST = 1 << 2;
 
     private PackageUserKey mTempKey = new PackageUserKey(null, null);
-    private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
+    private AppInfo mTempInfo = new AppInfo();
+
+    private AppInfo[] mApps = EMPTY_ARRAY;
+
     private final List<OnUpdateListener> mUpdateListeners = new ArrayList<>();
     private final ArrayList<ViewGroup> mIconContainers = new ArrayList<>();
 
     private int mDeferUpdatesFlags = 0;
     private boolean mUpdatePending = false;
 
-    public Collection<AppInfo> getApps() {
-        return mComponentToAppMap.values();
+    public AppInfo[] getApps() {
+        return mApps;
     }
 
     /**
      * Sets the current set of apps.
      */
-    public void setApps(List<AppInfo> apps) {
-        mComponentToAppMap.clear();
-        addOrUpdateApps(apps);
+    public void setApps(AppInfo[] apps) {
+        mApps = apps;
+        notifyUpdate();
     }
 
     public AppInfo getApp(ComponentKey key) {
-        return mComponentToAppMap.get(key);
+        mTempInfo.componentName = key.componentName;
+        mTempInfo.user = key.user;
+        int index = Arrays.binarySearch(mApps, mTempInfo, COMPONENT_KEY_COMPARATOR);
+        return index < 0 ? null : mApps[index];
     }
 
     public void enableDeferUpdates(int flag) {
@@ -84,27 +92,6 @@
         return mDeferUpdatesFlags;
     }
 
-    /**
-     * Adds or updates existing apps in the list
-     */
-    public void addOrUpdateApps(List<AppInfo> apps) {
-        for (AppInfo app : apps) {
-            mComponentToAppMap.put(app.toComponentKey(), app);
-        }
-        notifyUpdate();
-    }
-
-    /**
-     * Removes some apps from the list.
-     */
-    public void removeApps(List<AppInfo> apps) {
-        for (AppInfo app : apps) {
-            mComponentToAppMap.remove(app.toComponentKey());
-        }
-        notifyUpdate();
-    }
-
-
     private void notifyUpdate() {
         if (mDeferUpdatesFlags != 0) {
             mUpdatePending = true;
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 8c264c1..935326d 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -20,7 +20,6 @@
 import android.util.LongSparseArray;
 import android.util.Pair;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InvariantDeviceProfile;
@@ -28,7 +27,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.util.GridOccupancy;
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
similarity index 73%
rename from src/com/android/launcher3/AllAppsList.java
rename to src/com/android/launcher3/model/AllAppsList.java
index 733f295..2fddbaa 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.model;
+
+import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR;
+import static com.android.launcher3.AppInfo.EMPTY_ARRAY;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -24,15 +27,22 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import com.android.launcher3.AppFilter;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.PromiseAppInfo;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.SafeCloseable;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.function.Consumer;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -42,23 +52,22 @@
  * Stores the list of all applications for the all apps view.
  */
 public class AllAppsList {
+
     private static final String TAG = "AllAppsList";
+    private static final Consumer<AppInfo> NO_OP_CONSUMER = a -> { };
+
 
     public static final int DEFAULT_APPLICATIONS_NUMBER = 42;
 
     /** The list off all apps. */
     public final ArrayList<AppInfo> data = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER);
-    /** The list of apps that have been added since the last notify() call. */
-    public ArrayList<AppInfo> added = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER);
-    /** The list of apps that have been removed since the last notify() call. */
-    public ArrayList<AppInfo> removed = new ArrayList<>();
-    /** The list of apps that have been modified since the last notify() call. */
-    public ArrayList<AppInfo> modified = new ArrayList<>();
 
     private IconCache mIconCache;
-
     private AppFilter mAppFilter;
 
+    private boolean mDataChanged = false;
+    private Consumer<AppInfo> mRemoveListener = NO_OP_CONSUMER;
+
     /**
      * Boring constructor.
      */
@@ -68,6 +77,15 @@
     }
 
     /**
+     * Returns true if there have been any changes since last call.
+     */
+    public boolean getAndResetChangeFlag() {
+        boolean result = mDataChanged;
+        mDataChanged = false;
+        return result;
+    }
+
+    /**
      * Add the supplied ApplicationInfo objects to the list, and enqueue it into the
      * list to broadcast when notify() is called.
      *
@@ -83,7 +101,7 @@
         mIconCache.getTitleAndIcon(info, activityInfo, true /* useLowResIcon */);
 
         data.add(info);
-        added.add(info);
+        mDataChanged = true;
     }
 
     public void addPromiseApp(Context context,
@@ -95,30 +113,41 @@
             PromiseAppInfo info = new PromiseAppInfo(installInfo);
             mIconCache.getTitleAndIcon(info, info.usingLowResIcon());
             data.add(info);
-            added.add(info);
+            mDataChanged = true;
         }
     }
 
-    public void removePromiseApp(AppInfo appInfo) {
-        // the <em>removed</em> list is handled by the caller
-        // so not adding it here
-        data.remove(appInfo);
+    public PromiseAppInfo updatePromiseInstallInfo(PackageInstallInfo installInfo) {
+        UserHandle user = Process.myUserHandle();
+        for (int i=0; i < data.size(); i++) {
+            final AppInfo appInfo = data.get(i);
+            final ComponentName tgtComp = appInfo.getTargetComponent();
+            if (tgtComp != null && tgtComp.getPackageName().equals(installInfo.packageName)
+                    && appInfo.user.equals(user)
+                    && appInfo instanceof PromiseAppInfo) {
+                final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo;
+                if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLING) {
+                    promiseAppInfo.level = installInfo.progress;
+                    return promiseAppInfo;
+                } else if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
+                    removeApp(i);
+                }
+            }
+        }
+        return null;
+    }
+
+    private void removeApp(int index) {
+        AppInfo removed = data.remove(index);
+        if (removed != null) {
+            mDataChanged = true;
+            mRemoveListener.accept(removed);
+        }
     }
 
     public void clear() {
         data.clear();
-        // TODO: do we clear these too?
-        added.clear();
-        removed.clear();
-        modified.clear();
-    }
-
-    public int size() {
-        return data.size();
-    }
-
-    public AppInfo get(int index) {
-        return data.get(index);
+        mDataChanged = false;
     }
 
     /**
@@ -142,8 +171,7 @@
         for (int i = data.size() - 1; i >= 0; i--) {
             AppInfo info = data.get(i);
             if (info.user.equals(user) && packageName.equals(info.componentName.getPackageName())) {
-                removed.add(info);
-                data.remove(i);
+                removeApp(i);
             }
         }
     }
@@ -157,17 +185,16 @@
             AppInfo info = data.get(i);
             if (matcher.matches(info, info.componentName)) {
                 info.runtimeStatusFlags = op.apply(info.runtimeStatusFlags);
-                modified.add(info);
+                mDataChanged = true;
             }
         }
     }
 
-    public void updateIconsAndLabels(HashSet<String> packages, UserHandle user,
-            ArrayList<AppInfo> outUpdates) {
+    public void updateIconsAndLabels(HashSet<String> packages, UserHandle user) {
         for (AppInfo info : data) {
             if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) {
                 mIconCache.updateTitleAndIcon(info);
-                outUpdates.add(info);
+                mDataChanged = true;
             }
         }
     }
@@ -188,8 +215,7 @@
                         && packageName.equals(applicationInfo.componentName.getPackageName())) {
                     if (!findActivity(matches, applicationInfo.componentName)) {
                         Log.w(TAG, "Changing shortcut target due to app component name change.");
-                        removed.add(applicationInfo);
-                        data.remove(i);
+                        removeApp(i);
                     }
                 }
             }
@@ -202,7 +228,7 @@
                     add(new AppInfo(context, info, user), info);
                 } else {
                     mIconCache.getTitleAndIcon(applicationInfo, info, true /* useLowResIcon */);
-                    modified.add(applicationInfo);
+                    mDataChanged = true;
                 }
             }
         } else {
@@ -211,15 +237,13 @@
                 final AppInfo applicationInfo = data.get(i);
                 if (user.equals(applicationInfo.user)
                         && packageName.equals(applicationInfo.componentName.getPackageName())) {
-                    removed.add(applicationInfo);
                     mIconCache.remove(applicationInfo.componentName, user);
-                    data.remove(i);
+                    removeApp(i);
                 }
             }
         }
     }
 
-
     /**
      * Returns whether <em>apps</em> contains <em>component</em>.
      */
@@ -247,4 +271,16 @@
         }
         return null;
     }
+
+    public AppInfo[] copyData() {
+        AppInfo[] result = data.toArray(EMPTY_ARRAY);
+        Arrays.sort(result, COMPONENT_KEY_COMPARATOR);
+        return result;
+    }
+
+    public SafeCloseable trackRemoves(Consumer<AppInfo> removeListener) {
+        mRemoveListener = removeListener;
+
+        return () -> mRemoveListener = NO_OP_CONSUMER;
+    }
 }
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 97cf267..018f93a 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -19,14 +19,13 @@
 import android.os.Looper;
 import android.util.Log;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.PagedView;
@@ -279,9 +278,8 @@
 
     public void bindAllApps() {
         // shallow copy
-        @SuppressWarnings("unchecked")
-        ArrayList<AppInfo> list = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
-        executeCallbacksTask(c -> c.bindAllApplications(list), mUiExecutor);
+        AppInfo[] apps = mBgAllAppsList.copyData();
+        executeCallbacksTask(c -> c.bindAllApplications(apps), mUiExecutor);
     }
 
     public abstract void bindWidgets();
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index eea3d8c..e12633b 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -17,12 +17,12 @@
 
 import android.util.Log;
 
-import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -30,6 +30,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -95,12 +96,7 @@
 
     public void bindUpdatedWorkspaceItems(final ArrayList<WorkspaceItemInfo> updatedShortcuts) {
         if (!updatedShortcuts.isEmpty()) {
-            scheduleCallbackTask(new CallbackTask() {
-                @Override
-                public void execute(Callbacks callbacks) {
-                    callbacks.bindWorkspaceItemsChanged(updatedShortcuts);
-                }
-            });
+            scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(updatedShortcuts));
         }
     }
 
@@ -113,23 +109,20 @@
     public void bindUpdatedWidgets(BgDataModel dataModel) {
         final ArrayList<WidgetListRowEntry> widgets =
                 dataModel.widgetsModel.getWidgetsList(mApp.getContext());
-        scheduleCallbackTask(new CallbackTask() {
-            @Override
-            public void execute(Callbacks callbacks) {
-                callbacks.bindAllWidgets(widgets);
-            }
-        });
+        scheduleCallbackTask(c -> c.bindAllWidgets(widgets));
     }
 
     public void deleteAndBindComponentsRemoved(final ItemInfoMatcher matcher) {
         getModelWriter().deleteItemsFromDatabase(matcher);
 
         // Call the components-removed callback
-        scheduleCallbackTask(new CallbackTask() {
-            @Override
-            public void execute(Callbacks callbacks) {
-                callbacks.bindWorkspaceComponentsRemoved(matcher);
-            }
-        });
+        scheduleCallbackTask(c -> c.bindWorkspaceComponentsRemoved(matcher));
+    }
+
+    public void bindApplicationsIfNeeded() {
+        if (mAllAppsList.getAndResetChangeFlag()) {
+            AppInfo[] apps = mAllAppsList.copyData();
+            scheduleCallbackTask(c -> c.bindAllApplications(apps));
+        }
     }
 }
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 8f0cd08..0e20270 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -22,11 +22,13 @@
 import android.util.Log;
 import android.util.MutableInt;
 
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.PromiseAppInfo;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
@@ -40,6 +42,10 @@
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.widget.WidgetListRowEntry;
+
 import com.google.protobuf.nano.MessageNano;
 
 import java.io.FileDescriptor;
@@ -49,6 +55,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -391,4 +398,30 @@
             }
         }
     }
+
+    public interface Callbacks {
+        void rebindModel();
+
+        int getCurrentWorkspaceScreen();
+        void clearPendingBinds();
+        void startBinding();
+        void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
+        void bindScreens(IntArray orderedScreenIds);
+        void finishFirstPageBind(ViewOnDrawExecutor executor);
+        void finishBindingItems(int pageBoundFirst);
+        void preAddApps();
+        void bindAppsAdded(IntArray newScreens,
+                ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
+        void bindPromiseAppProgressUpdated(PromiseAppInfo app);
+        void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated);
+        void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
+        void bindRestoreItemsChange(HashSet<ItemInfo> updates);
+        void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
+        void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
+        void onPageBoundSynchronously(int page);
+        void executeOnNextDraw(ViewOnDrawExecutor executor);
+        void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
+
+        void bindAllApplications(AppInfo[] apps);
+    }
 }
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index 7852444..c1c8be3 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -18,14 +18,10 @@
 import android.content.ComponentName;
 import android.os.UserHandle;
 
-import com.android.launcher3.AllAppsList;
-import com.android.launcher3.AppInfo;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
 import com.android.launcher3.LauncherSettings;
 
 import java.util.ArrayList;
@@ -53,9 +49,9 @@
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
         IconCache iconCache = app.getIconCache();
 
-        final ArrayList<AppInfo> updatedApps = new ArrayList<>();
 
         ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>();
+
         synchronized (dataModel) {
             for (ItemInfo info : dataModel.itemsIdMap) {
                 if (info instanceof WorkspaceItemInfo && mUser.equals(info.user)) {
@@ -69,18 +65,10 @@
                     }
                 }
             }
-            apps.updateIconsAndLabels(mPackages, mUser, updatedApps);
+            apps.updateIconsAndLabels(mPackages, mUser);
         }
         bindUpdatedWorkspaceItems(updatedShortcuts);
-
-        if (!updatedApps.isEmpty()) {
-            scheduleCallbackTask(new CallbackTask() {
-                @Override
-                public void execute(Callbacks callbacks) {
-                    callbacks.bindAppsAddedOrUpdated(updatedApps);
-                }
-            });
-        }
+        bindApplicationsIfNeeded();
     }
 
     public boolean isValidShortcut(WorkspaceItemInfo si) {
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 6c33946..19eaadb 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -40,7 +40,6 @@
 import android.util.LongSparseArray;
 import android.util.MutableInt;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InstallShortcutReceiver;
@@ -831,7 +830,7 @@
             }
         }
 
-        mBgAllAppsList.added = new ArrayList<>();
+        mBgAllAppsList.getAndResetChangeFlag();
         return allActivityList;
     }
 
diff --git a/src/com/android/launcher3/model/ModelPreload.java b/src/com/android/launcher3/model/ModelPreload.java
index b353810..2bd6cd4 100644
--- a/src/com/android/launcher3/model/ModelPreload.java
+++ b/src/com/android/launcher3/model/ModelPreload.java
@@ -18,7 +18,6 @@
 import android.content.Context;
 import android.util.Log;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 4ce2f4b..d7ab0dd 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -31,7 +31,7 @@
 import com.android.launcher3.LauncherAppWidgetHost;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 5f6d128..e43412d 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -20,20 +20,18 @@
 import android.content.pm.PackageManager;
 import android.os.Process;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.PromiseAppInfo;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.util.InstantAppResolver;
 
-import java.util.ArrayList;
 import java.util.HashSet;
 
 /**
@@ -66,41 +64,11 @@
         }
 
         synchronized (apps) {
-            PromiseAppInfo updated = null;
-            final ArrayList<AppInfo> removed = new ArrayList<>();
-            for (int i=0; i < apps.size(); i++) {
-                final AppInfo appInfo = apps.get(i);
-                final ComponentName tgtComp = appInfo.getTargetComponent();
-                if (tgtComp != null && tgtComp.getPackageName().equals(mInstallInfo.packageName)) {
-                    if (appInfo instanceof PromiseAppInfo) {
-                        final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo;
-                        if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLING) {
-                            promiseAppInfo.level = mInstallInfo.progress;
-                            updated = promiseAppInfo;
-                        } else if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) {
-                            apps.removePromiseApp(appInfo);
-                            removed.add(appInfo);
-                        }
-                    }
-                }
-            }
+            PromiseAppInfo updated = apps.updatePromiseInstallInfo(mInstallInfo);
             if (updated != null) {
-                final PromiseAppInfo updatedPromiseApp = updated;
-                scheduleCallbackTask(new CallbackTask() {
-                    @Override
-                    public void execute(Callbacks callbacks) {
-                        callbacks.bindPromiseAppProgressUpdated(updatedPromiseApp);
-                    }
-                });
+                scheduleCallbackTask(c -> c.bindPromiseAppProgressUpdated(updated));
             }
-            if (!removed.isEmpty()) {
-                scheduleCallbackTask(new CallbackTask() {
-                    @Override
-                    public void execute(Callbacks callbacks) {
-                        callbacks.bindAppInfosRemoved(removed);
-                    }
-                });
-            }
+            bindApplicationsIfNeeded();
         }
 
         synchronized (dataModel) {
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index c37ed99..17a9a02 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -23,23 +23,19 @@
 import android.os.UserHandle;
 import android.util.Log;
 
-import com.android.launcher3.AllAppsList;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
-import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.SessionCommitReceiver;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
@@ -48,6 +44,7 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.SafeCloseable;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -93,6 +90,8 @@
         FlagOp flagOp = FlagOp.NO_OP;
         final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
         ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageSet, mUser);
+        final HashSet<ComponentName> removedComponents = new HashSet<>();
+
         switch (mOp) {
             case OP_ADD: {
                 for (int i = 0; i < N; i++) {
@@ -112,11 +111,14 @@
                 break;
             }
             case OP_UPDATE:
-                for (int i = 0; i < N; i++) {
-                    if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
-                    iconCache.updateIconsForPkg(packages[i], mUser);
-                    appsList.updatePackage(context, packages[i], mUser);
-                    app.getWidgetCache().removePackage(packages[i], mUser);
+                try (SafeCloseable t =
+                             appsList.trackRemoves(a -> removedComponents.add(a.componentName))) {
+                    for (int i = 0; i < N; i++) {
+                        if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
+                        iconCache.updateIconsForPkg(packages[i], mUser);
+                        appsList.updatePackage(context, packages[i], mUser);
+                        app.getWidgetCache().removePackage(packages[i], mUser);
+                    }
                 }
                 // Since package was just updated, the target must be available now.
                 flagOp = FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
@@ -153,23 +155,7 @@
                 break;
         }
 
-        final ArrayList<AppInfo> addedOrModified = new ArrayList<>();
-        addedOrModified.addAll(appsList.added);
-        appsList.added.clear();
-        addedOrModified.addAll(appsList.modified);
-        appsList.modified.clear();
-        if (!addedOrModified.isEmpty()) {
-            scheduleCallbackTask((callbacks) -> callbacks.bindAppsAddedOrUpdated(addedOrModified));
-        }
-
-        final ArrayList<AppInfo> removedApps = new ArrayList<>(appsList.removed);
-        appsList.removed.clear();
-        final HashSet<ComponentName> removedComponents = new HashSet<>();
-        if (mOp == OP_UPDATE) {
-            for (AppInfo ai : removedApps) {
-                removedComponents.add(ai.componentName);
-            }
-        }
+        bindApplicationsIfNeeded();
 
         final IntSparseArrayMap<Boolean> removedShortcuts = new IntSparseArrayMap<>();
 
@@ -296,12 +282,7 @@
             }
 
             if (!widgets.isEmpty()) {
-                scheduleCallbackTask(new CallbackTask() {
-                    @Override
-                    public void execute(Callbacks callbacks) {
-                        callbacks.bindWidgetsRestored(widgets);
-                    }
-                });
+                scheduleCallbackTask(c -> c.bindWidgetsRestored(widgets));
             }
         }
 
@@ -332,16 +313,6 @@
             InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
         }
 
-        if (!removedApps.isEmpty()) {
-            // Remove corresponding apps from All-Apps
-            scheduleCallbackTask(new CallbackTask() {
-                @Override
-                public void execute(Callbacks callbacks) {
-                    callbacks.bindAppInfosRemoved(removedApps);
-                }
-            });
-        }
-
         if (Utilities.ATLEAST_OREO && mOp == OP_ADD) {
             // Load widgets for the new package. Changes due to app updates are handled through
             // AppWidgetHost events, this is just to initialize the long-press options.
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 8528228..c3cd9d0 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -19,7 +19,6 @@
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 50fff26..4b773d7 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -21,7 +21,6 @@
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
index c6370c5..446d4f8 100644
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -22,7 +22,7 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.MainThreadExecutor;
 
 import java.lang.ref.WeakReference;
diff --git a/src/com/android/launcher3/util/SafeCloseable.java b/src/com/android/launcher3/util/SafeCloseable.java
new file mode 100644
index 0000000..ba8ee04
--- /dev/null
+++ b/src/com/android/launcher3/util/SafeCloseable.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+/**
+ * Extension of closeable which does not throw an exception
+ */
+public interface SafeCloseable extends AutoCloseable {
+
+    @Override
+    void close();
+}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
index 1710aef..789bfd8 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
@@ -16,9 +16,8 @@
 
 package com.android.launcher3.model;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.widget.WidgetListRowEntry;