Refactoring package tracking in managed profile heuristic into a
separate class

Change-Id: I4f346422e5c7f94f8559942e21aa01b5c96cd8be
diff --git a/src/com/android/launcher3/util/CachedPackageTracker.java b/src/com/android/launcher3/util/CachedPackageTracker.java
new file mode 100644
index 0000000..d55d573
--- /dev/null
+++ b/src/com/android/launcher3/util/CachedPackageTracker.java
@@ -0,0 +1,188 @@
+/*
+ * 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 com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.LauncherAppsCompat.OnAppsChangedCallbackCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+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<LauncherActivityInfoCompat> apps, UserHandleCompat 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 (LauncherActivityInfoCompat 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, UserHandleCompat 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, UserHandleCompat 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<LauncherActivityInfoCompat> activities =
+                    mLauncherApps.getActivityList(packageName, user);
+            if (!activities.isEmpty()) {
+                LauncherActivityInfoCompat 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, UserHandleCompat user) { }
+
+    @Override
+    public void onPackagesAvailable(
+            String[] packageNames, UserHandleCompat user, boolean replacing) { }
+
+    @Override
+    public void onPackagesUnavailable(
+            String[] packageNames, UserHandleCompat user, boolean replacing) { }
+
+    @Override
+    public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) { }
+
+    @Override
+    public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat 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,
+            UserHandleCompat user, boolean userAppsExisted);
+
+    /**
+     * Called when apps are removed from the system.
+     */
+    protected abstract void onLauncherPackageRemoved(String packageName, UserHandleCompat user);
+
+    protected static class LauncherActivityInstallInfo
+            implements Comparable<LauncherActivityInstallInfo> {
+        public final LauncherActivityInfoCompat info;
+        public final long installTime;
+
+        public LauncherActivityInstallInfo(LauncherActivityInfoCompat 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 3925c40..df23abe 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -16,14 +16,8 @@
 
 package com.android.launcher3.util;
 
-import android.annotation.TargetApi;
 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.os.Build;
-import android.util.Log;
 
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.ItemInfo;
@@ -35,27 +29,19 @@
 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.Comparator;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 /**
  * Handles addition of app shortcuts for managed profiles.
  * Methods of class should only be called on {@link LauncherModel#sWorkerThread}.
  */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class ManagedProfileHeuristic {
 
-    private static final String TAG = "ManagedProfileHeuristic";
-
     /**
      * Maintain a set of packages installed per user.
      */
@@ -76,228 +62,137 @@
     }
 
     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<ShortcutInfo> mHomescreenApps;
-    private ArrayList<ShortcutInfo> mWorkFolderApps;
-    private HashMap<ShortcutInfo, Long> mShortcutToInstallTimeMap;
+    private final UserHandleCompat mUser;
 
     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);
     }
 
-    private void initVars() {
-        mHomescreenApps = new ArrayList<>();
-        mWorkFolderApps = new ArrayList<>();
-        mShortcutToInstallTimeMap = new HashMap<>();
+    public void processPackageRemoved(String[] packages) {
+        Preconditions.assertWorkerThread();
+        ManagedProfilePackageHandler handler = new ManagedProfilePackageHandler();
+        for (String pkg : packages) {
+            handler.onPackageRemoved(pkg, mUser);
+        }
     }
 
-    /**
-     * Checks the list of user apps and adds icons for newly installed apps on the homescreen or
-     * workfolder.
-     */
+    public void processPackageAdd(String[] packages) {
+        Preconditions.assertWorkerThread();
+        ManagedProfilePackageHandler handler = new ManagedProfilePackageHandler();
+        for (String pkg : packages) {
+            handler.onPackageAdded(pkg, mUser);
+        }
+    }
+
     public void processUserApps(List<LauncherActivityInfoCompat> apps) {
-        initVars();
+        Preconditions.assertWorkerThread();
+        new ManagedProfilePackageHandler().processUserApps(apps, mUser);
+    }
 
-        HashSet<String> packageSet = new HashSet<>();
-        final boolean userAppsExisted = getUserApps(packageSet);
+    private class ManagedProfilePackageHandler extends CachedPackageTracker {
 
-        boolean newPackageAdded = false;
-        for (LauncherActivityInfoCompat info : apps) {
-            String packageName = info.getComponentName().getPackageName();
-            if (!packageSet.contains(packageName)) {
-                packageSet.add(packageName);
-                newPackageAdded = true;
-                markForAddition(info, info.getFirstInstallTime());
-            }
+        private ManagedProfilePackageHandler() {
+            super(mContext, LauncherFiles.MANAGED_USER_PREFERENCES_KEY);
         }
 
-        if (newPackageAdded) {
-            mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
+        protected void onLauncherAppsAdded(
+                List<LauncherActivityInstallInfo> apps, UserHandleCompat user, boolean userAppsExisted) {
+            ArrayList<ShortcutInfo> workFolderApps = new ArrayList<>();
+            ArrayList<ShortcutInfo> homescreenApps = new ArrayList<>();
+
+            int count = apps.size();
+            long folderCreationTime =
+                    mUserManager.getUserCreationTime(user) + AUTO_ADD_TO_FOLDER_DURATION;
+
+            for (int i = 0; i < count; i++) {
+                LauncherActivityInstallInfo info = apps.get(i);
+
+                ShortcutInfo si = ShortcutInfo.fromActivityInfo(info.info, mContext);
+                ((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).
-            finalizeAdditions(userAppsExisted);
-        }
-    }
-
-    private void markForAddition(LauncherActivityInfoCompat info, long installTime) {
-        ArrayList<ShortcutInfo> targetList =
-                (installTime <= mUserCreationTime + AUTO_ADD_TO_FOLDER_DURATION) ?
-                        mWorkFolderApps : mHomescreenApps;
-        ShortcutInfo si = ShortcutInfo.fromActivityInfo(info, mContext);
-        mShortcutToInstallTimeMap.put(si, installTime);
-        targetList.add(si);
-    }
-
-    private void sortList(ArrayList<ShortcutInfo> infos) {
-        Collections.sort(infos, new Comparator<ShortcutInfo>() {
-
-            @Override
-            public int compare(ShortcutInfo lhs, ShortcutInfo rhs) {
-                Long lhsTime = mShortcutToInstallTimeMap.get(lhs);
-                Long rhsTime = mShortcutToInstallTimeMap.get(rhs);
-                return Utilities.longCompare(lhsTime == null ? 0 : lhsTime,
-                        rhsTime == null ? 0 : rhsTime);
+            if (userAppsExisted && !homescreenApps.isEmpty()) {
+                mModel.addAndBindAddedWorkspaceItems(mContext, homescreenApps);
             }
-        });
-    }
-
-    /**
-     * Adds and binds shortcuts marked to be added to the work folder.
-     */
-    private void finalizeWorkFolder() {
-        if (mWorkFolderApps.isEmpty()) {
-            return;
         }
-        sortList(mWorkFolderApps);
 
-        // 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);
+        @Override
+        protected void onLauncherPackageRemoved(String packageName, UserHandleCompat user) {
+        }
 
-            if (workFolder == null || !workFolder.hasOption(FolderInfo.FLAG_WORK_FOLDER)) {
-                // Could not get a work folder. Add all the icons to homescreen.
-                mHomescreenApps.addAll(mWorkFolderApps);
+        /**
+         * Adds and binds shortcuts marked to be added to the work folder.
+         */
+        private void finalizeWorkFolder(
+                UserHandleCompat user, final ArrayList<ShortcutInfo> workFolderApps,
+                ArrayList<ShortcutInfo> homescreenApps) {
+            if (workFolderApps.isEmpty()) {
                 return;
             }
-            saveWorkFolderShortcuts(folderId, workFolder.contents.size());
+            // Try to get a work folder.
+            String folderIdKey = USER_FOLDER_ID_PREFIX + mUserManager.getSerialNumberForUser(user);
+            if (mPrefs.contains(folderIdKey)) {
+                long folderId = mPrefs.getLong(folderIdKey, 0);
+                final FolderInfo workFolder = mModel.findFolderById(folderId);
 
-            final ArrayList<ShortcutInfo> 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 (ShortcutInfo info : shortcuts) {
-                        workFolder.add(info, false);
-                    }
+                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;
                 }
-            });
-        } 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);
+                saveWorkFolderShortcuts(folderId, workFolder.contents.size(), workFolderApps);
 
-            // Add all shortcuts before adding it to the UI, as an empty folder might get deleted.
-            for (ShortcutInfo info : mWorkFolderApps) {
-                workFolder.add(info, false);
+                // FolderInfo could already be bound. We need to add shortcuts on the UI thread.
+                new MainThreadExecutor().execute(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        for (ShortcutInfo info : workFolderApps) {
+                            workFolder.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<ItemInfo>(1);
+                itemList.add(workFolder);
+                mModel.addAndBindAddedWorkspaceItems(mContext, itemList);
+                mPrefs.edit().putLong(folderIdKey, workFolder.id).apply();
+
+                saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps);
             }
-
-            // 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) {
+    private void saveWorkFolderShortcuts(
+            long workFolderId, int startingRank, ArrayList<ShortcutInfo> workFolderApps) {
+        for (ItemInfo info : workFolderApps) {
             info.rank = startingRank++;
             LauncherModel.addItemToDatabase(mContext, info, workFolderId, 0, 0, 0);
         }
     }
 
-    /**
-     * Adds and binds all shortcuts marked for addition.
-     */
-    private void finalizeAdditions(boolean addHomeScreenShortcuts) {
-        finalizeWorkFolder();
-
-        if (addHomeScreenShortcuts && !mHomescreenApps.isEmpty()) {
-            sortList(mHomescreenApps);
-            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) {
-        initVars();
-        HashSet<String> packageSet = new HashSet<>();
-        final boolean userAppsExisted = getUserApps(packageSet);
-
-        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(userAppsExisted);
-        }
-    }
-
-    /**
-     * Updates the list of installed packages for the user.
-     */
-    public void processPackageRemoved(String[] packages) {
-        HashSet<String> packageSet = new HashSet<String>();
-        getUserApps(packageSet);
-        boolean packageRemoved = false;
-
-        for (String packageName : packages) {
-            if (packageSet.remove(packageName)) {
-                packageRemoved = true;
-            }
-        }
-
-        if (packageRemoved) {
-            mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
-        }
-    }
-
-    /**
-     * 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) {
-        Set<String> userApps = mPrefs.getStringSet(mPackageSetKey, null);
-        if (userApps == null) {
-            return false;
-        } else {
-            outExistingApps.addAll(userApps);
-            return true;
-        }
-    }
 
     /**
      * Verifies that entries corresponding to {@param users} exist and removes all invalid entries.