Automatically adding managed profile shortcuts to homescreen.

When the managed profile is created, a "Work" folder is created
and added to the homescreen. All work profile apps are added to this
folder and icons for subsequent installs (withing a fixed time frame)
are automatically added to this folder.
If this folder get deleted or the time-frame expires, icon for any new
install is placed on the homescreen.

Bug: 17410319
Change-Id: I49f4e437707d5eabe4eec85320765bf6ba7fde97
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 3240cbf..80b1564 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -37,6 +37,11 @@
     public static final int FLAG_ITEMS_SORTED = 0x00000001;
 
     /**
+     * It is a work folder
+     */
+    public static final int FLAG_WORK_FOLDER = 0x00000002;
+
+    /**
      * Whether this folder has been opened
      */
     boolean opened;
@@ -46,11 +51,11 @@
     /**
      * The apps and shortcuts
      */
-    ArrayList<ShortcutInfo> contents = new ArrayList<ShortcutInfo>();
+    public ArrayList<ShortcutInfo> contents = new ArrayList<ShortcutInfo>();
 
     ArrayList<FolderListener> listeners = new ArrayList<FolderListener>();
 
-    FolderInfo() {
+    public FolderInfo() {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
         user = UserHandleCompat.myUserHandle();
     }
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 5422de9..0c69154 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -210,7 +210,7 @@
             // Add the new apps to the model and bind them
             if (!addShortcuts.isEmpty()) {
                 LauncherAppState app = LauncherAppState.getInstance();
-                app.getModel().addAndBindAddedWorkspaceApps(context, addShortcuts);
+                app.getModel().addAndBindAddedWorkspaceItems(context, addShortcuts);
             }
         }
     }
@@ -352,16 +352,7 @@
 
         public ShortcutInfo getShortcutInfo() {
             if (activityInfo != null) {
-                final ShortcutInfo info = new ShortcutInfo();
-                info.user = user;
-                info.title = label;
-                info.contentDescription = label;
-                info.customIcon = false;
-                info.intent = launchIntent;
-                info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-                info.flags = AppInfo.initFlags(activityInfo);
-                info.firstInstallTime = activityInfo.getFirstInstallTime();
-                return info;
+                return ShortcutInfo.fromActivityInfo(activityInfo, mContext);
             } else {
                 return LauncherAppState.getInstance().getModel().infoFromShortcutIntent(mContext, data);
             }
diff --git a/src/com/android/launcher3/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/LauncherAccessibilityDelegate.java
index 42f1914..cfc1bd9 100644
--- a/src/com/android/launcher3/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/LauncherAccessibilityDelegate.java
@@ -124,7 +124,7 @@
                 mLauncher.showWorkspace(true, new Runnable() {
                     @Override
                     public void run() {
-                        mLauncher.getModel().addAndBindAddedWorkspaceApps(
+                        mLauncher.getModel().addAndBindAddedWorkspaceItems(
                                 mLauncher, addShortcuts, screenProvider, 0, true);
                         announceConfirmation(R.string.item_added_to_workspace);
                     }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 3bd3850..6e77d06 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -149,7 +149,7 @@
         return mIconCache;
     }
 
-    LauncherModel getModel() {
+    public LauncherModel getModel() {
         return mModel;
     }
 
diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java
index 699cb37..9dd8dc5 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -21,6 +21,7 @@
     public static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs";
     public static final String WALLPAPER_CROP_PREFERENCES_KEY =
             "com.android.launcher3.WallpaperCropActivity";
+    public static final String MANAGED_USER_PREFERENCES_KEY = "com.android.launcher3.managedusers.prefs";
 
     public static final String WALLPAPER_IMAGES_DB = "saved_wallpaper_images.db";
     public static final String WIDGET_PREVIEWS_DB = "widgetpreviews.db";
@@ -35,6 +36,7 @@
             WALLPAPER_CROP_PREFERENCES_KEY + XML,
             WALLPAPER_IMAGES_DB,
             WIDGET_PREVIEWS_DB,
+            MANAGED_USER_PREFERENCES_KEY,
             APP_ICONS_DB));
 
     // TODO: Delete these files on upgrade
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 98ba09b..37f1ea8 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -59,6 +59,7 @@
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.util.Thunk;
 
 import java.lang.ref.WeakReference;
@@ -87,7 +88,6 @@
     static final boolean DEBUG_LOADERS = false;
     private static final boolean DEBUG_RECEIVER = false;
     private static final boolean REMOVE_UNRESTORED_ICONS = true;
-    private static final boolean ADD_MANAGED_PROFILE_SHORTCUTS = false;
 
     static final String TAG = "Launcher.Model";
 
@@ -107,11 +107,6 @@
     @Thunk LoaderTask mLoaderTask;
     @Thunk boolean mIsLoaderTaskRunning;
 
-    /**
-     * Maintain a set of packages per user, for which we added a shortcut on the workspace.
-     */
-    private static final String INSTALLED_SHORTCUTS_SET_PREFIX = "installed_shortcuts_set_for_user_";
-
     // Specific runnable types that are run on the main thread deferred handler, this allows us to
     // clear all queued binding runnables when the Launcher activity is destroyed.
     private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
@@ -338,9 +333,9 @@
         runOnWorkerThread(r);
     }
 
-    public void addAndBindAddedWorkspaceApps(final Context context,
+    public void addAndBindAddedWorkspaceItems(final Context context,
             final ArrayList<ItemInfo> workspaceApps) {
-        addAndBindAddedWorkspaceApps(context, workspaceApps,
+        addAndBindAddedWorkspaceItems(context, workspaceApps,
                 new ScreenPosProvider() {
 
                     @Override
@@ -518,7 +513,7 @@
      * @param fallbackStartScreen the screen to start search for empty space if
      * preferredScreen is not available.
      */
-    public void addAndBindAddedWorkspaceApps(final Context context,
+    public void addAndBindAddedWorkspaceItems(final Context context,
             final ArrayList<ItemInfo> workspaceApps,
             final ScreenPosProvider preferredScreen,
             final int fallbackStartScreen,
@@ -539,7 +534,7 @@
                 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
                 synchronized(sBgLock) {
                     for (ItemInfo item : workspaceApps) {
-                        if (!allowDuplicate) {
+                        if (!allowDuplicate && item instanceof ShortcutInfo) {
                             // Short-circuit this logic if the icon exists somewhere on the workspace
                             if (shortcutExists(context, item.title.toString(),
                                     item.getIntent(), item.user)) {
@@ -554,21 +549,21 @@
                         long screenId = coords.first;
                         int[] cordinates = coords.second;
 
-                        ShortcutInfo shortcutInfo;
-                        if (item instanceof ShortcutInfo) {
-                            shortcutInfo = (ShortcutInfo) item;
+                        ItemInfo itemInfo;
+                        if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
+                            itemInfo = item;
                         } else if (item instanceof AppInfo) {
-                            shortcutInfo = ((AppInfo) item).makeShortcut();
+                            itemInfo = ((AppInfo) item).makeShortcut();
                         } else {
                             throw new RuntimeException("Unexpected info type");
                         }
 
                         // Add the shortcut to the db
-                        addItemToDatabase(context, shortcutInfo,
+                        addItemToDatabase(context, itemInfo,
                                 LauncherSettings.Favorites.CONTAINER_DESKTOP,
                                 screenId, cordinates[0], cordinates[1]);
                         // Save the ShortcutInfo for binding in the workspace
-                        addedShortcutsFinal.add(shortcutInfo);
+                        addedShortcutsFinal.add(itemInfo);
                     }
                 }
 
@@ -993,7 +988,7 @@
      * Add an item to the database in a specified container. Sets the container, screen, cellX and
      * cellY fields of the item. Also assigns an ID to the item.
      */
-    static void addItemToDatabase(Context context, final ItemInfo item, final long container,
+    public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
             final long screenId, final int cellX, final int cellY) {
         item.container = container;
         item.cellX = cellX;
@@ -1097,7 +1092,6 @@
      */
     static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
         final ContentResolver cr = context.getContentResolver();
-
         Runnable r = new Runnable() {
             public void run() {
                 for (ItemInfo item : items) {
@@ -2845,23 +2839,11 @@
                     mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
                 }
 
-                if (ADD_MANAGED_PROFILE_SHORTCUTS && !user.equals(UserHandleCompat.myUserHandle())) {
-                    // Add shortcuts for packages which were installed while launcher was dead.
-                    String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
-                            + mUserManager.getSerialNumberForUser(user);
-                    Set<String> packagesAdded = prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET);
-                    HashSet<String> newPackageSet = new HashSet<String>();
-
-                    for (LauncherActivityInfoCompat info : apps) {
-                        String packageName = info.getComponentName().getPackageName();
-                        if (!packagesAdded.contains(packageName)
-                                && !newPackageSet.contains(packageName)) {
-                            InstallShortcutReceiver.queueInstallShortcut(info, mContext);
-                        }
-                        newPackageSet.add(packageName);
+                if (!user.equals(UserHandleCompat.myUserHandle())) {
+                    ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
+                    if (heuristic != null) {
+                        heuristic.processUserApps(apps);
                     }
-
-                    prefs.edit().putStringSet(shortcutsSetKey, newPackageSet).commit();
                 }
             }
             // Huh? Shouldn't this be inside the Runnable below?
@@ -2884,6 +2866,8 @@
                     }
                 }
             });
+            // Cleanup any data stored for a deleted user.
+            ManagedProfileHeuristic.processAllUsers(profiles, mContext);
 
             if (DEBUG_LOADERS) {
                 Log.d(TAG, "Icons processed in "
@@ -2971,38 +2955,19 @@
             final String[] packages = mPackages;
             final int N = packages.length;
             switch (mOp) {
-                case OP_ADD:
+                case OP_ADD: {
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
                         mIconCache.updateIconsForPkg(packages[i], mUser);
                         mBgAllAppsList.addPackage(context, packages[i], mUser);
                     }
 
-                    // Auto add shortcuts for added packages.
-                    if (ADD_MANAGED_PROFILE_SHORTCUTS
-                            && !UserHandleCompat.myUserHandle().equals(mUser)) {
-                        SharedPreferences prefs = context.getSharedPreferences(
-                                LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
-                        String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
-                                + mUserManager.getSerialNumberForUser(mUser);
-                        Set<String> shortcutSet = new HashSet<String>(
-                                prefs.getStringSet(shortcutsSetKey,Collections.EMPTY_SET));
-
-                        for (int i=0; i<N; i++) {
-                            if (!shortcutSet.contains(packages[i])) {
-                                shortcutSet.add(packages[i]);
-                                List<LauncherActivityInfoCompat> activities =
-                                        mLauncherApps.getActivityList(packages[i], mUser);
-                                if (activities != null && !activities.isEmpty()) {
-                                    InstallShortcutReceiver.queueInstallShortcut(
-                                            activities.get(0), context);
-                                }
-                            }
-                        }
-
-                        prefs.edit().putStringSet(shortcutsSetKey, shortcutSet).commit();
+                    ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
+                    if (heuristic != null) {
+                        heuristic.processPackageAdd(mPackages);
                     }
                     break;
+                }
                 case OP_UPDATE:
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
@@ -3011,25 +2976,17 @@
                         mApp.getWidgetCache().removePackage(packages[i], mUser);
                     }
                     break;
-                case OP_REMOVE:
-                    // Remove the packageName for the set of auto-installed shortcuts. This
-                    // will ensure that the shortcut when the app is installed again.
-                    if (ADD_MANAGED_PROFILE_SHORTCUTS
-                            && !UserHandleCompat.myUserHandle().equals(mUser)) {
-                        SharedPreferences prefs = context.getSharedPreferences(
-                                LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
-                        String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX
-                                + mUserManager.getSerialNumberForUser(mUser);
-                        HashSet<String> shortcutSet = new HashSet<String>(
-                                prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET));
-                        shortcutSet.removeAll(Arrays.asList(mPackages));
-                        prefs.edit().putStringSet(shortcutsSetKey, shortcutSet).commit();
+                case OP_REMOVE: {
+                    ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
+                    if (heuristic != null) {
+                        heuristic.processPackageRemoved(mPackages);
                     }
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
                         mIconCache.removeIconsForPkg(packages[i], mUser);
                     }
                     // Fall through
+                }
                 case OP_UNAVAILABLE:
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
@@ -3677,4 +3634,13 @@
     public Callbacks getCallback() {
         return mCallbacks != null ? mCallbacks.get() : null;
     }
+
+    /**
+     * @return {@link FolderInfo} if its already loaded.
+     */
+    public FolderInfo findFolderById(Long folderId) {
+        synchronized (sBgLock) {
+            return sBgFolders.get(folderId);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 9f7da6c..5bef845 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -24,7 +24,9 @@
 import android.util.Log;
 
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -274,5 +276,19 @@
     public boolean shouldUseLowResIcon() {
         return usingLowResIcon && container >= 0 && rank >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
     }
+
+    public static ShortcutInfo fromActivityInfo(LauncherActivityInfoCompat info, Context context) {
+        final ShortcutInfo shortcut = new ShortcutInfo();
+        shortcut.user = info.getUser();
+        shortcut.title = info.getLabel().toString();
+        shortcut.contentDescription = UserManagerCompat.getInstance(context)
+                .getBadgedLabelForUser(info.getLabel(), info.getUser());
+        shortcut.customIcon = false;
+        shortcut.intent = AppInfo.makeLaunchIntent(context, info, info.getUser());
+        shortcut.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+        shortcut.flags = AppInfo.initFlags(info);
+        shortcut.firstInstallTime = info.getFirstInstallTime();
+        return shortcut;
+    }
 }
 
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index 1374b4e..a79d946 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -43,4 +43,5 @@
     public abstract UserHandleCompat getUserForSerialNumber(long serialNumber);
     public abstract Drawable getBadgedDrawableForUser(Drawable unbadged, UserHandleCompat user);
     public abstract CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user);
+    public abstract long getUserCreationTime(UserHandleCompat user);
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java
index 32f972e..ffe698c 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatV16.java
@@ -48,4 +48,9 @@
     public CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user) {
         return label;
     }
+
+    @Override
+    public long getUserCreationTime(UserHandleCompat user) {
+        return 0;
+    }
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index 19eeabd..884d6fe 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -18,21 +18,27 @@
 package com.android.launcher3.compat;
 
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
-import android.os.UserManager;
+
+import com.android.launcher3.LauncherAppState;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 public class UserManagerCompatVL extends UserManagerCompatV17 {
+    private static final String USER_CREATION_TIME_KEY = "user_creation_time_";
+
     private final PackageManager mPm;
+    private final Context mContext;
 
     UserManagerCompatVL(Context context) {
         super(context);
         mPm = context.getPackageManager();
+        mContext = context;
     }
 
     @Override
@@ -61,5 +67,17 @@
         }
         return mPm.getUserBadgedLabel(label, user.getUser());
     }
+
+    @Override
+    public long getUserCreationTime(UserHandleCompat user) {
+        // TODO: Use system API once available.
+        SharedPreferences prefs = mContext.getSharedPreferences(
+                LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
+        String key = USER_CREATION_TIME_KEY + getSerialNumberForUser(user);
+        if (!prefs.contains(key)) {
+            prefs.edit().putLong(key, System.currentTimeMillis()).apply();
+        }
+        return prefs.getLong(key, 0);
+    }
 }
 
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
new file mode 100644
index 0000000..cefa71c
--- /dev/null
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -0,0 +1,277 @@
+package com.android.launcher3.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
+
+import com.android.launcher3.FolderInfo;
+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.ShortcutInfo;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Handles addition of app shortcuts for managed profiles.
+ * Methods of class should only be called on {@link LauncherModel#sWorkerThread}.
+ */
+public class ManagedProfileHeuristic {
+
+    private static final String TAG = "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_";
+
+    /**
+     * Duration (in milliseconds) for which app shortcuts will be added to work folder.
+     */
+    private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000;
+
+    public static ManagedProfileHeuristic get(Context context, UserHandleCompat user) {
+        if (Utilities.isLmpOrAbove() && !UserHandleCompat.myUserHandle().equals(user)) {
+            return new ManagedProfileHeuristic(context, user);
+        }
+        return null;
+    }
+
+    private final Context mContext;
+    private final UserHandleCompat mUser;
+    private final LauncherModel mModel;
+
+    private final SharedPreferences mPrefs;
+    private final long mUserSerial;
+    private final long mUserCreationTime;
+    private final String mPackageSetKey;
+
+    private ArrayList<ItemInfo> mHomescreenApps;
+    private ArrayList<ItemInfo> mWorkFolderApps;
+
+    private ManagedProfileHeuristic(Context context, UserHandleCompat user) {
+        mContext = context;
+        mUser = user;
+        mModel = LauncherAppState.getInstance().getModel();
+
+        UserManagerCompat userManager = UserManagerCompat.getInstance(context);
+        mUserSerial = userManager.getSerialNumberForUser(user);
+        mUserCreationTime = userManager.getUserCreationTime(user);
+        mPackageSetKey = INSTALLED_PACKAGES_PREFIX + mUserSerial;
+
+        mPrefs = mContext.getSharedPreferences(LauncherFiles.MANAGED_USER_PREFERENCES_KEY,
+                Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Checks the list of user apps and adds icons for newly installed apps on the homescreen or
+     * workfolder.
+     */
+    public void processUserApps(List<LauncherActivityInfoCompat> apps) {
+        mHomescreenApps = new ArrayList<ItemInfo>();
+        mWorkFolderApps = new ArrayList<ItemInfo>();
+        HashSet<String> packageSet = getPackageSet();
+        boolean newPackageAdded = false;
+
+        for (LauncherActivityInfoCompat info : apps) {
+            String packageName = info.getComponentName().getPackageName();
+            if (!packageSet.contains(packageName)) {
+                packageSet.add(packageName);
+                newPackageAdded = true;
+
+                try {
+                    PackageInfo pkgInfo = mContext.getPackageManager()
+                            .getPackageInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
+                    markForAddition(info, pkgInfo.firstInstallTime);
+                } catch (NameNotFoundException e) {
+                    Log.e(TAG, "Unknown package " + packageName, e);
+                }
+            }
+        }
+
+        if (newPackageAdded) {
+            mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
+            finalizeAdditions();
+        }
+    }
+
+    private void markForAddition(LauncherActivityInfoCompat info, long installTime) {
+        ArrayList<ItemInfo> targetList =
+                (installTime <= mUserCreationTime + AUTO_ADD_TO_FOLDER_DURATION) ?
+                        mWorkFolderApps : mHomescreenApps;
+        targetList.add(ShortcutInfo.fromActivityInfo(info, mContext));
+    }
+
+    /**
+     * Adds and binds shortcuts marked to be added to the work folder.
+     */
+    private void finalizeWorkFolder() {
+        if (mWorkFolderApps.isEmpty()) {
+            return;
+        }
+
+        // Try to get a work folder.
+        String folderIdKey = USER_FOLDER_ID_PREFIX + mUserSerial;
+        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.
+                mHomescreenApps.addAll(mWorkFolderApps);
+                return;
+            }
+            saveWorkFolderShortcuts(folderId, workFolder.contents.size());
+
+            final ArrayList<ItemInfo> shortcuts = mWorkFolderApps;
+            // FolderInfo could already be bound. We need to add shortcuts on the UI thread.
+            new MainThreadExecutor().execute(new Runnable() {
+
+                @Override
+                public void run() {
+                    for (ItemInfo info : shortcuts) {
+                        workFolder.add((ShortcutInfo) info);
+                    }
+                }
+            });
+        } 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 (ItemInfo info : mWorkFolderApps) {
+                workFolder.add((ShortcutInfo) info);
+            }
+
+            // Add the item to home screen and DB. This also generates an item id synchronously.
+            ArrayList<ItemInfo> itemList = new ArrayList<ItemInfo>(1);
+            itemList.add(workFolder);
+            mModel.addAndBindAddedWorkspaceItems(mContext, itemList);
+            mPrefs.edit().putLong(USER_FOLDER_ID_PREFIX + mUserSerial, workFolder.id).apply();
+
+            saveWorkFolderShortcuts(workFolder.id, 0);
+        }
+    }
+
+    /**
+     * Add work folder shortcuts to the DB.
+     */
+    private void saveWorkFolderShortcuts(long workFolderId, int startingRank) {
+        for (ItemInfo info : mWorkFolderApps) {
+            info.rank = startingRank++;
+            LauncherModel.addItemToDatabase(mContext, info, workFolderId, 0, 0, 0);
+        }
+    }
+
+    /**
+     * Adds and binds all shortcuts marked for addition.
+     */
+    private void finalizeAdditions() {
+        finalizeWorkFolder();
+
+        if (!mHomescreenApps.isEmpty()) {
+            mModel.addAndBindAddedWorkspaceItems(mContext, mHomescreenApps);
+        }
+    }
+
+    /**
+     * Updates the list of installed apps and adds any new icons on homescreen or work folder.
+     */
+    public void processPackageAdd(String[] packages) {
+        mHomescreenApps = new ArrayList<ItemInfo>();
+        mWorkFolderApps = new ArrayList<ItemInfo>();
+        HashSet<String> packageSet = getPackageSet();
+        boolean newPackageAdded = false;
+        long installTime = System.currentTimeMillis();
+        LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
+
+        for (String packageName : packages) {
+            if (!packageSet.contains(packageName)) {
+                packageSet.add(packageName);
+                newPackageAdded = true;
+
+                List<LauncherActivityInfoCompat> activities =
+                        launcherApps.getActivityList(packageName, mUser);
+                if (!activities.isEmpty()) {
+                    markForAddition(activities.get(0), installTime);
+                }
+            }
+        }
+
+        if (newPackageAdded) {
+            mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
+            finalizeAdditions();
+        }
+    }
+
+    /**
+     * Updates the list of installed packages for the user.
+     */
+    public void processPackageRemoved(String[] packages) {
+        HashSet<String> packageSet = getPackageSet();
+        boolean packageRemoved = false;
+
+        for (String packageName : packages) {
+            if (packageSet.remove(packageName)) {
+                packageRemoved = true;
+            }
+        }
+
+        if (packageRemoved) {
+            mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private HashSet<String> getPackageSet() {
+        return new HashSet<String>(mPrefs.getStringSet(mPackageSetKey, Collections.EMPTY_SET));
+    }
+
+    /**
+     * Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
+     */
+    public static void processAllUsers(List<UserHandleCompat> users, Context context) {
+        if (!Utilities.isLmpOrAbove()) {
+            return;
+        }
+        UserManagerCompat userManager = UserManagerCompat.getInstance(context);
+        HashSet<String> validKeys = new HashSet<String>();
+        for (UserHandleCompat user : users) {
+            addAllUserKeys(userManager.getSerialNumberForUser(user), validKeys);
+        }
+
+        SharedPreferences prefs = context.getSharedPreferences(
+                LauncherFiles.MANAGED_USER_PREFERENCES_KEY,
+                Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = prefs.edit();
+        for (String key : prefs.getAll().keySet()) {
+            if (!validKeys.contains(key)) {
+                editor.remove(key);
+            }
+        }
+        editor.apply();
+    }
+
+    private static void addAllUserKeys(long userSerial, HashSet<String> keysOut) {
+        keysOut.add(INSTALLED_PACKAGES_PREFIX + userSerial);
+        keysOut.add(USER_FOLDER_ID_PREFIX + userSerial);
+    }
+}