Simplifying logic for managed for icon addition
am: a474a9bcf5

Change-Id: I03970e8ce265dd62aaa0e568cb3836fb8170ef4a
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index ce85570..b136e7d 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -34,6 +34,7 @@
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
@@ -59,6 +60,16 @@
 import java.util.Set;
 
 public class InstallShortcutReceiver extends BroadcastReceiver {
+
+    public static final int FLAG_ACTIVITY_PAUSED = 1;
+    public static final int FLAG_LOADER_RUNNING = 2;
+    public static final int FLAG_DRAG_AND_DROP = 4;
+    public static final int FLAG_BULK_ADD = 4;
+
+    // Determines whether to defer installing shortcuts immediately until
+    // processAllPendingInstalls() is called.
+    private static int sInstallQueueDisabledFlags = 0;
+
     private static final String TAG = "InstallShortcutReceiver";
     private static final boolean DBG = false;
 
@@ -151,10 +162,6 @@
         }
     }
 
-    // Determines whether to defer installing shortcuts immediately until
-    // processAllPendingInstalls() is called.
-    private static boolean mUseInstallQueue = false;
-
     public void onReceive(Context context, Intent data) {
         if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
             return;
@@ -207,7 +214,7 @@
 
     public static ShortcutInfo fromShortcutIntent(Context context, Intent data) {
         PendingInstallShortcutInfo info = createPendingInfo(context, data);
-        return info == null ? null : (ShortcutInfo) info.getItemInfo();
+        return info == null ? null : (ShortcutInfo) info.getItemInfo().first;
     }
 
     public static void queueShortcut(ShortcutInfoCompat info, Context context) {
@@ -245,27 +252,28 @@
 
     private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
         // Queue the item up for adding if launcher has not loaded properly yet
-        LauncherAppState app = LauncherAppState.getInstance(context);
-        boolean launcherNotLoaded = app.getModel().getCallback() == null;
-
         addToInstallQueue(Utilities.getPrefs(context), info);
-        if (!mUseInstallQueue && !launcherNotLoaded) {
-            flushInstallQueue(context);
-        }
+        flushInstallQueue(context);
     }
 
-    static void enableInstallQueue() {
-        mUseInstallQueue = true;
+    public static void enableInstallQueue(int flag) {
+        sInstallQueueDisabledFlags |= flag;
     }
-    static void disableAndFlushInstallQueue(Context context) {
-        mUseInstallQueue = false;
+    public static void disableAndFlushInstallQueue(int flag, Context context) {
+        sInstallQueueDisabledFlags &= ~flag;
         flushInstallQueue(context);
     }
 
     static void flushInstallQueue(Context context) {
+        LauncherModel model = LauncherAppState.getInstance(context).getModel();
+        boolean launcherNotLoaded = model.getCallback() == null;
+        if (sInstallQueueDisabledFlags != 0 || launcherNotLoaded) {
+            return;
+        }
+
         ArrayList<PendingInstallShortcutInfo> items = getAndClearInstallQueue(context);
         if (!items.isEmpty()) {
-            LauncherAppState.getInstance(context).getModel().addAndBindAddedWorkspaceItems(
+            model.addAndBindAddedWorkspaceItems(
                     new LazyShortcutsProvider(context.getApplicationContext(), items));
         }
     }
@@ -439,7 +447,7 @@
             }
         }
 
-        public ItemInfo getItemInfo() {
+        public Pair<ItemInfo, Object> getItemInfo() {
             if (activityInfo != null) {
                 AppInfo appInfo = new AppInfo(mContext, activityInfo, user);
                 final LauncherAppState app = LauncherAppState.getInstance(mContext);
@@ -459,11 +467,11 @@
                         }
                     });
                 }
-                return si;
+                return Pair.create((ItemInfo) si, (Object) activityInfo);
             } else if (shortcutInfo != null) {
                 ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext);
                 si.iconBitmap = LauncherIcons.createShortcutIcon(shortcutInfo, mContext);
-                return si;
+                return Pair.create((ItemInfo) si, (Object) shortcutInfo);
             } else if (providerInfo != null) {
                 LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
                         .fromProviderInfo(mContext, providerInfo);
@@ -475,9 +483,10 @@
                 widgetInfo.minSpanY = info.minSpanY;
                 widgetInfo.spanX = Math.min(info.spanX, idp.numColumns);
                 widgetInfo.spanY = Math.min(info.spanY, idp.numRows);
-                return widgetInfo;
+                return Pair.create((ItemInfo) widgetInfo, (Object) providerInfo);
             } else {
-                return createShortcutInfo(data, LauncherAppState.getInstance(mContext));
+                ShortcutInfo si = createShortcutInfo(data, LauncherAppState.getInstance(mContext));
+                return Pair.create((ItemInfo) si, null);
             }
         }
 
@@ -588,7 +597,7 @@
         return new PendingInstallShortcutInfo(info, original.mContext);
     }
 
-    private static class LazyShortcutsProvider extends Provider<List<ItemInfo>> {
+    private static class LazyShortcutsProvider extends Provider<List<Pair<ItemInfo, Object>>> {
 
         private final Context mContext;
         private final ArrayList<PendingInstallShortcutInfo> mPendingItems;
@@ -603,9 +612,9 @@
          * packageManager and icon cache.
          */
         @Override
-        public ArrayList<ItemInfo> get() {
+        public ArrayList<Pair<ItemInfo, Object>> get() {
             Preconditions.assertNonUiThread();
-            ArrayList<ItemInfo> installQueue = new ArrayList<>();
+            ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
             LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
             for (PendingInstallShortcutInfo pendingInfo : mPendingItems) {
                 // If the intent specifies a package, make sure the package exists
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b63bbd5..b9b5610 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1036,13 +1036,12 @@
         updateInteraction(Workspace.State.NORMAL, mWorkspace.getState());
         mWorkspace.onResume();
 
-        if (!isWorkspaceLoading()) {
-            // Process any items that were added while Launcher was away.
-            InstallShortcutReceiver.disableAndFlushInstallQueue(this);
+        // Process any items that were added while Launcher was away.
+        InstallShortcutReceiver.disableAndFlushInstallQueue(
+                InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this);
 
-            // Refresh shortcuts if the permission changed.
-            mModel.refreshShortcutsIfRequired();
-        }
+        // Refresh shortcuts if the permission changed.
+        mModel.refreshShortcutsIfRequired();
 
         if (shouldShowDiscoveryBounce()) {
             mAllAppsController.showDiscoveryBounce();
@@ -1057,7 +1056,7 @@
     @Override
     protected void onPause() {
         // Ensure that items added to Launcher are queued until Launcher returns
-        InstallShortcutReceiver.enableInstallQueue();
+        InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED);
 
         super.onPause();
         mPaused = true;
@@ -3655,7 +3654,8 @@
             mPendingActivityResult = null;
         }
 
-        InstallShortcutReceiver.disableAndFlushInstallQueue(this);
+        InstallShortcutReceiver.disableAndFlushInstallQueue(
+                InstallShortcutReceiver.FLAG_LOADER_RUNNING, this);
 
         NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 12789c5..b5ca301 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -40,6 +40,7 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.MutableInt;
+import android.util.Pair;
 
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
@@ -138,12 +139,6 @@
         }
     }
 
-    /**
-     * Set of runnables to be called on the background thread after the workspace binding
-     * is complete.
-     */
-    static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
-
     @Thunk WeakReference<Callbacks> mCallbacks;
 
     // < only access in worker thread >
@@ -251,15 +246,8 @@
     /**
      * Adds the provided items to the workspace.
      */
-    public void addAndBindAddedWorkspaceItems(List<ItemInfo> workspaceApps) {
-        addAndBindAddedWorkspaceItems(Provider.of(workspaceApps));
-    }
-
-    /**
-     * Adds the provided items to the workspace.
-     */
     public void addAndBindAddedWorkspaceItems(
-            Provider<List<ItemInfo>> appsProvider) {
+            Provider<List<Pair<ItemInfo, Object>>> appsProvider) {
         enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider));
     }
 
@@ -529,7 +517,7 @@
      */
     public boolean startLoader(int synchronousBindPage) {
         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
-        InstallShortcutReceiver.enableInstallQueue();
+        InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
         synchronized (mLock) {
             // Don't bother to start the thread if we know it's not going to do anything
             if (mCallbacks != null && mCallbacks.get() != null) {
@@ -607,7 +595,6 @@
         private Context mContext;
         private int mPageToBindFirst;
 
-        @Thunk boolean mIsLoadingAndBindingWorkspace;
         private boolean mStopped;
 
         LoaderTask(Context context, int pageToBindFirst) {
@@ -675,8 +662,6 @@
             try {
                 long now = 0;
                 if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace");
-                // Set to false in bindWorkspace()
-                mIsLoadingAndBindingWorkspace = true;
                 loadWorkspace();
 
                 verifyNotStopped();
@@ -1584,18 +1569,6 @@
                         callbacks.finishBindingItems();
                     }
 
-                    mIsLoadingAndBindingWorkspace = false;
-
-                    // Run all the bind complete runnables after workspace is bound.
-                    if (!mBindCompleteRunnables.isEmpty()) {
-                        synchronized (mBindCompleteRunnables) {
-                            for (final Runnable r : mBindCompleteRunnables) {
-                                runOnWorkerThread(r);
-                            }
-                            mBindCompleteRunnables.clear();
-                        }
-                    }
-
                     // If we're profiling, ensure this is the last thing in the queue.
                     if (DEBUG_LOADERS) {
                         Log.d(TAG, "bound workspace in "
@@ -1710,31 +1683,7 @@
                     mBgAllAppsList.add(new AppInfo(app, user, quietMode), app);
                 }
 
-                final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
-                if (heuristic != null) {
-                    final Runnable r = new Runnable() {
-
-                        @Override
-                        public void run() {
-                            heuristic.processUserApps(apps);
-                        }
-                    };
-                    mUiExecutor.execute(new Runnable() {
-
-                                    @Override
-                                    public void run() {
-                                        // Check isLoadingWorkspace on the UI thread, as it is updated on
-                                        // the UI thread.
-                                        if (mIsLoadingAndBindingWorkspace) {
-                                            synchronized (mBindCompleteRunnables) {
-                                                mBindCompleteRunnables.add(r);
-                                            }
-                                        } else {
-                                            runOnWorkerThread(r);
-                                        }
-                                    }
-                                });
-                }
+                ManagedProfileHeuristic.onAllAppsLoaded(mContext, apps, user);
             }
 
             if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) {
@@ -1768,8 +1717,6 @@
                     }
                 }
             });
-            // Cleanup any data stored for a deleted user.
-            ManagedProfileHeuristic.processAllUsers(profiles, mContext);
             if (DEBUG_LOADERS) {
                 Log.d(TAG, "Icons processed in "
                         + (SystemClock.uptimeMillis() - loadTime) + "ms");
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index 61bcc17..8caba75 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -67,18 +67,19 @@
         SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
         UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
 
-        if (TextUtils.isEmpty(info.getAppPackageName()) ||
-                info.getInstallReason() != PackageManager.INSTALL_REASON_USER) {
-            return;
+        if (Process.myUserHandle().equals(user)) {
+            if (TextUtils.isEmpty(info.getAppPackageName()) ||
+                    info.getInstallReason() != PackageManager.INSTALL_REASON_USER) {
+                return;
+            }
         }
 
-        if (!Process.myUserHandle().equals(user)) {
-            // Managed profile is handled using ManagedProfileHeuristic
-            return;
-        }
+        queueAppIconAddition(context, info.getAppPackageName(), user);
+    }
 
+    public static void queueAppIconAddition(Context context, String packageName, UserHandle user) {
         List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(context)
-                .getActivityList(info.getAppPackageName(), user);
+                .getActivityList(packageName, user);
         if (activities == null || activities.isEmpty()) {
             // no activity found
             return;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index b3dd7ac..672203c 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -451,7 +451,7 @@
         mLauncher.lockScreenOrientation();
         mLauncher.onInteractionBegin();
         // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
-        InstallShortcutReceiver.enableInstallQueue();
+        InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_DRAG_AND_DROP);
 
         // Do not add a new page if it is a accessible drag which was not started by the workspace.
         // We do not support accessibility drag from other sources and instead provide a direct
@@ -504,7 +504,8 @@
         mLauncher.unlockScreenOrientation(false);
 
         // Re-enable any Un/InstallShortcutReceiver and now process any queued items
-        InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
+        InstallShortcutReceiver.disableAndFlushInstallQueue(
+                InstallShortcutReceiver.FLAG_DRAG_AND_DROP, getContext());
 
         mOutlineProvider = null;
         mDragInfo = null;
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index 45525f5..c7f88f6 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -22,8 +22,8 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
-import com.android.launcher3.Utilities;
 import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.ManagedProfileHeuristic;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -122,7 +122,7 @@
 
     @Override
     public long getUserCreationTime(UserHandle user) {
-        SharedPreferences prefs = Utilities.getPrefs(mContext);
+        SharedPreferences prefs = ManagedProfileHeuristic.prefs(mContext);
         String key = USER_CREATION_TIME_KEY + getSerialNumberForUser(user);
         if (!prefs.contains(key)) {
             prefs.edit().putLong(key, System.currentTimeMillis()).apply();
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 10fb582..2e8e15b 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -17,6 +17,8 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.os.Process;
 import android.os.UserHandle;
 import android.util.LongSparseArray;
 import android.util.Pair;
@@ -35,9 +37,11 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.ManagedProfileHeuristic.UserFolderInfo;
 import com.android.launcher3.util.Provider;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 
 /**
@@ -45,18 +49,18 @@
  */
 public class AddWorkspaceItemsTask extends ExtendedModelTask {
 
-    private final Provider<List<ItemInfo>> mAppsProvider;
+    private final Provider<List<Pair<ItemInfo, Object>>> mAppsProvider;
 
     /**
      * @param appsProvider items to add on the workspace
      */
-    public AddWorkspaceItemsTask(Provider<List<ItemInfo>> appsProvider) {
+    public AddWorkspaceItemsTask(Provider<List<Pair<ItemInfo, Object>>> appsProvider) {
         mAppsProvider = appsProvider;
     }
 
     @Override
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-        List<ItemInfo> workspaceApps = mAppsProvider.get();
+        List<Pair<ItemInfo, Object>> workspaceApps = mAppsProvider.get();
         if (workspaceApps.isEmpty()) {
             return;
         }
@@ -64,13 +68,17 @@
 
         final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
         final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<>();
+        HashMap<UserHandle, UserFolderInfo> userFolderMap = new HashMap<>();
 
         // Get the list of workspace screens.  We need to append to this list and
         // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
         // called.
         ArrayList<Long> workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context);
         synchronized(dataModel) {
-            for (ItemInfo item : workspaceApps) {
+
+            List<ItemInfo> filteredItems = new ArrayList<>();
+            for (Pair<ItemInfo, Object> entry : workspaceApps) {
+                ItemInfo item = entry.first;
                 if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
                         item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
                     // Short-circuit this logic if the icon exists somewhere on the workspace
@@ -79,6 +87,32 @@
                     }
                 }
 
+                if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+                    if (item instanceof AppInfo) {
+                        item = ((AppInfo) item).makeShortcut();
+                    }
+
+                    if (!Process.myUserHandle().equals(item.user)) {
+                        // Check if this belongs to a work folder.
+                        if (!(entry.second instanceof LauncherActivityInfo)) {
+                            continue;
+                        }
+
+                        UserFolderInfo userFolderInfo = userFolderMap.get(item.user);
+                        if (userFolderInfo == null) {
+                            userFolderInfo = new UserFolderInfo(context, item.user, dataModel);
+                            userFolderMap.put(item.user, userFolderInfo);
+                        }
+                        item = userFolderInfo.convertToWorkspaceItem(
+                                (ShortcutInfo) item, (LauncherActivityInfo) entry.second);
+                    }
+                }
+                if (item != null) {
+                    filteredItems.add(item);
+                }
+            }
+
+            for (ItemInfo item : filteredItems) {
                 // Find appropriate space for the item.
                 Pair<Long, int[]> coords = findSpaceForItem(app, dataModel, workspaceScreens,
                         addedWorkspaceScreensFinal, item.spanX, item.spanY);
@@ -130,6 +164,10 @@
                 }
             });
         }
+
+        for (UserFolderInfo userFolderInfo : userFolderMap.values()) {
+            userFolderInfo.applyPendingState(getModelWriter());
+        }
     }
 
     protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) {
@@ -276,4 +314,5 @@
         }
         return occupied.findVacantCell(xy, spanX, spanY);
     }
+
 }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index b58efb6..8380f01 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -35,6 +35,7 @@
 import com.android.launcher3.LauncherModel.Callbacks;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.SessionCommitReceiver;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.LauncherAppsCompat;
@@ -43,7 +44,6 @@
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
 
@@ -100,11 +100,11 @@
                         appsList.removePackage(packages[i], Process.myUserHandle());
                     }
                     appsList.addPackage(context, packages[i], mUser);
-                }
 
-                ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
-                if (heuristic != null) {
-                    heuristic.processPackageAdd(mPackages);
+                    // Automatically add homescreen icon for work profile apps for below O device.
+                    if (!Utilities.isAtLeastO() && !Process.myUserHandle().equals(mUser)) {
+                        SessionCommitReceiver.queueAppIconAddition(context, packages[i], mUser);
+                    }
                 }
                 break;
             }
@@ -119,10 +119,6 @@
                 flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
                 break;
             case OP_REMOVE: {
-                ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
-                if (heuristic != null) {
-                    heuristic.processPackageRemoved(mPackages);
-                }
                 for (int i = 0; i < N; i++) {
                     iconCache.removeIconsForPkg(packages[i], mUser);
                 }
diff --git a/src/com/android/launcher3/util/CachedPackageTracker.java b/src/com/android/launcher3/util/CachedPackageTracker.java
deleted file mode 100644
index 314b4c0..0000000
--- a/src/com/android/launcher3/util/CachedPackageTracker.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.pm.LauncherActivityInfo;
-import android.os.UserHandle;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.LauncherAppsCompat.OnAppsChangedCallbackCompat;
-import com.android.launcher3.compat.UserManagerCompat;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Utility class to track list of installed packages. It persists the list so that apps
- * installed/uninstalled while Launcher was dead can also be handled properly.
- */
-public abstract class CachedPackageTracker implements OnAppsChangedCallbackCompat {
-
-    protected static final String INSTALLED_PACKAGES_PREFIX = "installed_packages_for_user_";
-
-    protected final SharedPreferences mPrefs;
-    protected final UserManagerCompat mUserManager;
-    protected final LauncherAppsCompat mLauncherApps;
-
-    public CachedPackageTracker(Context context, String preferenceFileName) {
-        mPrefs = context.getSharedPreferences(preferenceFileName, Context.MODE_PRIVATE);
-        mUserManager = UserManagerCompat.getInstance(context);
-        mLauncherApps = LauncherAppsCompat.getInstance(context);
-    }
-
-    /**
-     * Checks the list of user apps, and generates package event accordingly.
-     * {@see #onLauncherAppsAdded}, {@see #onLauncherPackageRemoved}
-     */
-    public void processUserApps(List<LauncherActivityInfo> apps, UserHandle user) {
-        String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user);
-        HashSet<String> oldPackageSet = new HashSet<>();
-        final boolean userAppsExisted = getUserApps(oldPackageSet, prefKey);
-
-        HashSet<String> packagesRemoved = new HashSet<>(oldPackageSet);
-        HashSet<String> newPackageSet = new HashSet<>();
-        ArrayList<LauncherActivityInstallInfo> packagesAdded = new ArrayList<>();
-
-        for (LauncherActivityInfo info : apps) {
-            String packageName = info.getComponentName().getPackageName();
-            newPackageSet.add(packageName);
-            packagesRemoved.remove(packageName);
-
-            if (!oldPackageSet.contains(packageName)) {
-                oldPackageSet.add(packageName);
-                packagesAdded.add(new LauncherActivityInstallInfo(
-                        info, info.getFirstInstallTime()));
-            }
-        }
-
-        if (!packagesAdded.isEmpty() || !packagesRemoved.isEmpty()) {
-            mPrefs.edit().putStringSet(prefKey, newPackageSet).apply();
-
-            if (!packagesAdded.isEmpty()) {
-                Collections.sort(packagesAdded);
-                onLauncherAppsAdded(packagesAdded, user, userAppsExisted);
-            }
-
-            if (!packagesRemoved.isEmpty()) {
-                for (String pkg : packagesRemoved) {
-                    onLauncherPackageRemoved(pkg, user);
-                }
-            }
-        }
-    }
-
-    /**
-     * Reads the list of user apps which have already been processed.
-     * @return false if the list didn't exist, true otherwise
-     */
-    private boolean getUserApps(HashSet<String> outExistingApps, String prefKey) {
-        Set<String> userApps = mPrefs.getStringSet(prefKey, null);
-        if (userApps == null) {
-            return false;
-        } else {
-            outExistingApps.addAll(userApps);
-            return true;
-        }
-    }
-
-    @Override
-    public void onPackageRemoved(String packageName, UserHandle user) {
-        String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user);
-        HashSet<String> packageSet = new HashSet<>();
-        if (getUserApps(packageSet, prefKey) && packageSet.remove(packageName)) {
-            mPrefs.edit().putStringSet(prefKey, packageSet).apply();
-        }
-
-        onLauncherPackageRemoved(packageName, user);
-    }
-
-    @Override
-    public void onPackageAdded(String packageName, UserHandle user) {
-        String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user);
-        HashSet<String> packageSet = new HashSet<>();
-        final boolean userAppsExisted = getUserApps(packageSet, prefKey);
-        if (!packageSet.contains(packageName)) {
-            List<LauncherActivityInfo> activities =
-                    mLauncherApps.getActivityList(packageName, user);
-            if (!activities.isEmpty()) {
-                LauncherActivityInfo activityInfo = activities.get(0);
-
-                packageSet.add(packageName);
-                mPrefs.edit().putStringSet(prefKey, packageSet).apply();
-                onLauncherAppsAdded(Arrays.asList(
-                        new LauncherActivityInstallInfo(activityInfo, System.currentTimeMillis())),
-                        user, userAppsExisted);
-            }
-        }
-    }
-
-    @Override
-    public void onPackageChanged(String packageName, UserHandle user) { }
-
-    @Override
-    public void onPackagesAvailable(
-            String[] packageNames, UserHandle user, boolean replacing) { }
-
-    @Override
-    public void onPackagesUnavailable(
-            String[] packageNames, UserHandle user, boolean replacing) { }
-
-    @Override
-    public void onPackagesSuspended(String[] packageNames, UserHandle user) { }
-
-    @Override
-    public void onPackagesUnsuspended(String[] packageNames, UserHandle user) { }
-
-    /**
-     * Called when new launcher apps are added.
-     * @param apps list of newly added activities. Only one entry per package is sent.
-     * @param user the user for this event. All activities in {@param apps} will belong to
-     *             the same user.
-     * @param userAppsExisted false if the list was processed for the first time, like in case
-     *                        when Launcher was newly installed or a new user was added.
-     */
-    protected abstract void onLauncherAppsAdded(List<LauncherActivityInstallInfo> apps,
-            UserHandle user, boolean userAppsExisted);
-
-    /**
-     * Called when apps are removed from the system.
-     */
-    protected abstract void onLauncherPackageRemoved(String packageName, UserHandle user);
-
-    public static class LauncherActivityInstallInfo
-            implements Comparable<LauncherActivityInstallInfo> {
-        public final LauncherActivityInfo info;
-        public final long installTime;
-
-        public LauncherActivityInstallInfo(LauncherActivityInfo info, long installTime) {
-            this.info = info;
-            this.installTime = installTime;
-        }
-
-        @Override
-        public int compareTo(LauncherActivityInstallInfo another) {
-            return Utilities.longCompare(installTime, another.installTime);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index ce603c4..091dd84 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -19,23 +19,23 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.LauncherActivityInfo;
+import android.os.Handler;
 import android.os.Process;
 import android.os.UserHandle;
-import android.support.v4.os.BuildCompat;
 
-import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
-import com.android.launcher3.IconCache;
+import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherFiles;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.SessionCommitReceiver;
 import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.shortcuts.ShortcutInfoCompat;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.ModelWriter;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -47,11 +47,6 @@
  */
 public class ManagedProfileHeuristic {
 
-    /**
-     * Maintain a set of packages installed per user.
-     */
-    private static final String INSTALLED_PACKAGES_PREFIX = "installed_packages_for_user_";
-
     private static final String USER_FOLDER_ID_PREFIX = "user_folder_";
 
     /**
@@ -59,165 +54,154 @@
      */
     private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000;
 
-    public static ManagedProfileHeuristic get(Context context, UserHandle user) {
-        if (!Process.myUserHandle().equals(user)) {
-            return new ManagedProfileHeuristic(context, user);
-        }
-        return null;
-    }
-
-    private final Context mContext;
-    private final LauncherModel mModel;
-    private final UserHandle mUser;
-    private final IconCache mIconCache;
-    private final boolean mAddIconsToHomescreen;
-
-    private ManagedProfileHeuristic(Context context, UserHandle user) {
-        mContext = context;
-        mUser = user;
-        mModel = LauncherAppState.getInstance(context).getModel();
-        mIconCache = LauncherAppState.getInstance(context).getIconCache();
-        mAddIconsToHomescreen =
-                !BuildCompat.isAtLeastO() || SessionCommitReceiver.isEnabled(context);
-    }
-
-    public void processPackageRemoved(String[] packages) {
-        Preconditions.assertWorkerThread();
-        ManagedProfilePackageHandler handler = new ManagedProfilePackageHandler();
-        for (String pkg : packages) {
-            handler.onPackageRemoved(pkg, mUser);
-        }
-    }
-
-    public void processPackageAdd(String[] packages) {
-        Preconditions.assertWorkerThread();
-        ManagedProfilePackageHandler handler = new ManagedProfilePackageHandler();
-        for (String pkg : packages) {
-            handler.onPackageAdded(pkg, mUser);
-        }
-    }
-
-    public void processUserApps(List<LauncherActivityInfo> apps) {
-        Preconditions.assertWorkerThread();
-        new ManagedProfilePackageHandler().processUserApps(apps, mUser);
-    }
-
-    private class ManagedProfilePackageHandler extends CachedPackageTracker {
-
-        private ManagedProfilePackageHandler() {
-            super(mContext, LauncherFiles.MANAGED_USER_PREFERENCES_KEY);
+    public static void onAllAppsLoaded(final Context context,
+            List<LauncherActivityInfo> apps, UserHandle user) {
+        if (Process.myUserHandle().equals(user)) {
+            return;
         }
 
-        protected void onLauncherAppsAdded(
-                List<LauncherActivityInstallInfo> apps, UserHandle user, boolean userAppsExisted) {
-            ArrayList<ShortcutInfo> workFolderApps = new ArrayList<>();
-            ArrayList<ShortcutInfo> homescreenApps = new ArrayList<>();
+        UserFolderInfo ufi = new UserFolderInfo(context, user, null);
+        // We only handle folder creation once. Later icon additions are handled using package
+        // or session events.
+        if (ufi.folderAlreadyCreated) {
+            return;
+        }
 
-            int count = apps.size();
-            long folderCreationTime =
-                    mUserManager.getUserCreationTime(user) + AUTO_ADD_TO_FOLDER_DURATION;
+        if (Utilities.isAtLeastO() && !SessionCommitReceiver.isEnabled(context)) {
+            // Just mark the folder id preference to avoid new folder creation later.
+            ufi.prefs.edit().putLong(ufi.folderIdKey, ItemInfo.NO_ID).apply();
+            return;
+        }
 
-            boolean quietModeEnabled = UserManagerCompat.getInstance(mContext)
-                    .isQuietModeEnabled(user);
-            for (int i = 0; i < count; i++) {
-                LauncherActivityInstallInfo info = apps.get(i);
-                AppInfo appInfo = new AppInfo(info.info, user, quietModeEnabled);
-                mIconCache.getTitleAndIcon(appInfo, info.info, false /* useLowResIcon */);
-                ShortcutInfo si = appInfo.makeShortcut();
-                ((info.installTime <= folderCreationTime) ? workFolderApps : homescreenApps).add(si);
-            }
-
-            finalizeWorkFolder(user, workFolderApps, homescreenApps);
-
-            // Do not add shortcuts on the homescreen for the first time. This prevents the launcher
-            // getting filled with the managed user apps, when it start with a fresh DB (or after
-            // a very long time).
-            if (userAppsExisted && !homescreenApps.isEmpty() && mAddIconsToHomescreen) {
-                mModel.addAndBindAddedWorkspaceItems(new ArrayList<ItemInfo>(homescreenApps));
+        InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_BULK_ADD);
+        for (LauncherActivityInfo app : apps) {
+            // Queue all items which should go in the work folder.
+            if (app.getFirstInstallTime() < ufi.addIconToFolderTime) {
+                InstallShortcutReceiver.queueActivityInfo(app, context);
             }
         }
+        // Post the queue update on next frame, so that the loader gets finished.
+        new Handler(LauncherModel.getWorkerLooper()).post(new Runnable() {
+            @Override
+            public void run() {
+                InstallShortcutReceiver.disableAndFlushInstallQueue(
+                        InstallShortcutReceiver.FLAG_BULK_ADD, context);
+            }
+        });
+    }
 
-        @Override
-        protected void onLauncherPackageRemoved(String packageName, UserHandle user) {
+
+    /**
+     * Utility class to help workspace icon addition.
+     */
+    public static class UserFolderInfo {
+
+        final ArrayList<ShortcutInfo> pendingShortcuts = new ArrayList<>();
+
+        final UserHandle user;
+
+        final long userSerial;
+        // Time until which icons will be added to folder instead.
+        final long addIconToFolderTime;
+
+        final String folderIdKey;
+        final SharedPreferences prefs;
+
+        final boolean folderAlreadyCreated;
+        final FolderInfo folderInfo;
+
+        boolean folderPendingAddition;
+
+        public UserFolderInfo(Context context, UserHandle user, BgDataModel dataModel) {
+            this.user = user;
+
+            UserManagerCompat um = UserManagerCompat.getInstance(context);
+            userSerial = um.getSerialNumberForUser(user);
+            addIconToFolderTime = um.getUserCreationTime(user) + AUTO_ADD_TO_FOLDER_DURATION;
+
+            folderIdKey = USER_FOLDER_ID_PREFIX + userSerial;
+            prefs = prefs(context);
+
+            folderAlreadyCreated = prefs.contains(folderIdKey);
+            if (dataModel != null) {
+                if (folderAlreadyCreated) {
+                    long folderId = prefs.getLong(folderIdKey, ItemInfo.NO_ID);
+                    folderInfo = dataModel.folders.get(folderId);
+                } else {
+                    folderInfo = new FolderInfo();
+                    folderInfo.title = context.getText(R.string.work_folder_name);
+                    folderInfo.setOption(FolderInfo.FLAG_WORK_FOLDER, true, null);
+                    folderPendingAddition = true;
+                }
+            } else {
+                folderInfo = null;
+            }
         }
 
         /**
-         * Adds and binds shortcuts marked to be added to the work folder.
+         * Returns the ItemInfo which should be added to the workspace. In case the the provided
+         * {@link ShortcutInfo} or a wrapped {@link FolderInfo} or null.
          */
-        private void finalizeWorkFolder(
-                UserHandle user, final ArrayList<ShortcutInfo> workFolderApps,
-                ArrayList<ShortcutInfo> homescreenApps) {
-            if (workFolderApps.isEmpty()) {
+        public ItemInfo convertToWorkspaceItem(
+                ShortcutInfo shortcut, LauncherActivityInfo activityInfo) {
+            if (activityInfo.getFirstInstallTime() >= addIconToFolderTime) {
+                return shortcut;
+            }
+
+            if (folderAlreadyCreated) {
+                if (folderInfo == null) {
+                    // Work folder was deleted by user, add icon to home screen.
+                    return shortcut;
+                } else {
+                    // Add item to work folder instead. Nothing needs to be added
+                    // on the homescreen.
+                    pendingShortcuts.add(shortcut);
+                    return null;
+                }
+            }
+
+            pendingShortcuts.add(shortcut);
+            folderInfo.add(shortcut, false);
+            if (folderPendingAddition) {
+                folderPendingAddition = false;
+                return folderInfo;
+            } else {
+                // WorkFolder already requested to be added. Nothing new needs to be added.
+                return null;
+            }
+        }
+
+        public void applyPendingState(ModelWriter writer) {
+            if (folderInfo == null) {
                 return;
             }
-            // Try to get a work folder.
-            String folderIdKey = USER_FOLDER_ID_PREFIX + mUserManager.getSerialNumberForUser(user);
-            if (!mAddIconsToHomescreen) {
-                if (!mPrefs.contains(folderIdKey)) {
-                    // Just mark the folder id preference to avoid new folder creation later.
-                    mPrefs.edit().putLong(folderIdKey, -1).apply();
-                }
-                return;
+
+            int startingRank = 0;
+            if (folderAlreadyCreated) {
+                startingRank = folderInfo.contents.size();
             }
-            if (mPrefs.contains(folderIdKey)) {
-                long folderId = mPrefs.getLong(folderIdKey, 0);
-                final FolderInfo workFolder = mModel.findFolderById(folderId);
 
-                if (workFolder == null || !workFolder.hasOption(FolderInfo.FLAG_WORK_FOLDER)) {
-                    // Could not get a work folder. Add all the icons to homescreen.
-                    homescreenApps.addAll(0, workFolderApps);
-                    return;
-                }
-                saveWorkFolderShortcuts(folderId, workFolder.contents.size(), workFolderApps);
+            for (ShortcutInfo info : pendingShortcuts) {
+                info.rank = startingRank++;
+                writer.addItemToDatabase(info, folderInfo.id, 0, 0, 0);
+            }
 
+            if (folderAlreadyCreated) {
                 // FolderInfo could already be bound. We need to add shortcuts on the UI thread.
                 new MainThreadExecutor().execute(new Runnable() {
 
                     @Override
                     public void run() {
-                        workFolder.prepareAutoUpdate();
-                        for (ShortcutInfo info : workFolderApps) {
-                            workFolder.add(info, false);
+                        folderInfo.prepareAutoUpdate();
+                        for (ShortcutInfo info : pendingShortcuts) {
+                            folderInfo.add(info, false);
                         }
                     }
                 });
             } else {
-                // Create a new folder.
-                final FolderInfo workFolder = new FolderInfo();
-                workFolder.title = mContext.getText(R.string.work_folder_name);
-                workFolder.setOption(FolderInfo.FLAG_WORK_FOLDER, true, null);
-
-                // Add all shortcuts before adding it to the UI, as an empty folder might get deleted.
-                for (ShortcutInfo info : workFolderApps) {
-                    workFolder.add(info, false);
-                }
-
-                // Add the item to home screen and DB. This also generates an item id synchronously.
-                ArrayList<ItemInfo> itemList = new ArrayList<>(1);
-                itemList.add(workFolder);
-                mModel.addAndBindAddedWorkspaceItems(itemList);
-                mPrefs.edit().putLong(folderIdKey, workFolder.id).apply();
-
-                saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps);
+                prefs.edit().putLong(folderIdKey, folderInfo.id).apply();
             }
         }
-
-        @Override
-        public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts,
-                UserHandle user) {
-            // Do nothing
-        }
-    }
-
-    /**
-     * Add work folder shortcuts to the DB.
-     */
-    private void saveWorkFolderShortcuts(
-            long workFolderId, int startingRank, ArrayList<ShortcutInfo> workFolderApps) {
-        for (ItemInfo info : workFolderApps) {
-            info.rank = startingRank++;
-            mModel.getWriter(false).addItemToDatabase(info, workFolderId, 0, 0, 0);
-        }
     }
 
     /**
@@ -225,14 +209,12 @@
      */
     public static void processAllUsers(List<UserHandle> users, Context context) {
         UserManagerCompat userManager = UserManagerCompat.getInstance(context);
-        HashSet<String> validKeys = new HashSet<String>();
+        HashSet<String> validKeys = new HashSet<>();
         for (UserHandle user : users) {
-            addAllUserKeys(userManager.getSerialNumberForUser(user), validKeys);
+            validKeys.add(USER_FOLDER_ID_PREFIX + userManager.getSerialNumberForUser(user));
         }
 
-        SharedPreferences prefs = context.getSharedPreferences(
-                LauncherFiles.MANAGED_USER_PREFERENCES_KEY,
-                Context.MODE_PRIVATE);
+        SharedPreferences prefs = prefs(context);
         SharedPreferences.Editor editor = prefs.edit();
         for (String key : prefs.getAll().keySet()) {
             if (!validKeys.contains(key)) {
@@ -242,11 +224,6 @@
         editor.apply();
     }
 
-    private static void addAllUserKeys(long userSerial, HashSet<String> keysOut) {
-        keysOut.add(INSTALLED_PACKAGES_PREFIX + userSerial);
-        keysOut.add(USER_FOLDER_ID_PREFIX + userSerial);
-    }
-
     /**
      * For each user, if a work folder has not been created, mark it such that the folder will
      * never get created.
@@ -260,11 +237,8 @@
             if (myUser.equals(user)) {
                 continue;
             }
-
             if (prefs == null) {
-                prefs = context.getSharedPreferences(
-                        LauncherFiles.MANAGED_USER_PREFERENCES_KEY,
-                        Context.MODE_PRIVATE);
+                prefs = prefs(context);
             }
             String folderIdKey = USER_FOLDER_ID_PREFIX + userManager.getSerialNumberForUser(user);
             if (!prefs.contains(folderIdKey)) {
@@ -272,4 +246,9 @@
             }
         }
     }
+
+    public static SharedPreferences prefs(Context context) {
+        return context.getSharedPreferences(
+                LauncherFiles.MANAGED_USER_PREFERENCES_KEY, Context.MODE_PRIVATE);
+    }
 }
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
index 883be5a..4c80902 100644
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -21,6 +21,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.verify;
@@ -50,7 +51,11 @@
     }
 
     private AddWorkspaceItemsTask newTask(ItemInfo... items) {
-        return new AddWorkspaceItemsTask(Provider.of(Arrays.asList(items))) {
+        List<Pair<ItemInfo, Object>> list = new ArrayList<>();
+        for (ItemInfo item : items) {
+            list.add(Pair.create(item, null));
+        }
+        return new AddWorkspaceItemsTask(Provider.of(list)) {
 
             @Override
             protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) { }