Moving install queue updates to worker thread.

This avoids acquiring a lock for upating the sharedPrefs during onResume
as all the logic runs on a single thread.

Bug: 67305604
Change-Id: I1bbea382da9fafb403b4e9508f393f78db28478d
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 0370777..df1eec6 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -27,7 +27,9 @@
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.UserHandle;
@@ -61,6 +63,9 @@
 
 public class InstallShortcutReceiver extends BroadcastReceiver {
 
+    private static final int MSG_ADD_TO_QUEUE = 1;
+    private static final int MSG_FLUSH_QUEUE = 2;
+
     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;
@@ -93,73 +98,98 @@
     public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
     public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
 
-    private static final Object sLock = new Object();
+    private static final Handler sHandler = new Handler(LauncherModel.getWorkerLooper()) {
 
-    private static void addToInstallQueue(
-            SharedPreferences sharedPrefs, PendingInstallShortcutInfo info) {
-        synchronized(sLock) {
-            String encoded = info.encodeToString();
-            if (encoded != null) {
-                Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
-                strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1);
-                strings.add(encoded);
-                sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ADD_TO_QUEUE: {
+                    Pair<Context, PendingInstallShortcutInfo> pair =
+                            (Pair<Context, PendingInstallShortcutInfo>) msg.obj;
+                    String encoded = pair.second.encodeToString();
+                    SharedPreferences prefs = Utilities.getPrefs(pair.first);
+                    Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
+                    strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1);
+                    strings.add(encoded);
+                    prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
+                    return;
+                }
+                case MSG_FLUSH_QUEUE: {
+                    Context context = (Context) msg.obj;
+                    LauncherModel model = LauncherAppState.getInstance(context).getModel();
+                    if (model.getCallback() == null) {
+                        // Launcher not loaded
+                        return;
+                    }
+
+                    ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
+                    SharedPreferences prefs = Utilities.getPrefs(context);
+                    Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
+                    if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
+                    if (strings == null) {
+                        return;
+                    }
+
+                    LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+                    for (String encoded : strings) {
+                        PendingInstallShortcutInfo info = decode(encoded, context);
+                        if (info == null) {
+                            continue;
+                        }
+
+                        String pkg = getIntentPackage(info.launchIntent);
+                        if (!TextUtils.isEmpty(pkg)
+                                && !launcherApps.isPackageEnabledForProfile(pkg, info.user)) {
+                            if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
+                                    + info.launchIntent);
+                            continue;
+                        }
+
+                        // Generate a shortcut info to add into the model
+                        installQueue.add(info.getItemInfo());
+                    }
+                    prefs.edit().remove(APPS_PENDING_INSTALL).apply();
+                    if (!installQueue.isEmpty()) {
+                        model.addAndBindAddedWorkspaceItems(installQueue);
+                    }
+                    return;
+                }
             }
         }
-    }
+    };
 
     public static void removeFromInstallQueue(Context context, HashSet<String> packageNames,
             UserHandle user) {
         if (packageNames.isEmpty()) {
             return;
         }
+        Preconditions.assertWorkerThread();
+
         SharedPreferences sp = Utilities.getPrefs(context);
-        synchronized(sLock) {
-            Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
-            if (DBG) {
-                Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
-                        + ", removing packages: " + packageNames);
-            }
-            if (Utilities.isEmpty(strings)) {
-                return;
-            }
-            Set<String> newStrings = new HashSet<>(strings);
-            Iterator<String> newStringsIter = newStrings.iterator();
-            while (newStringsIter.hasNext()) {
-                String encoded = newStringsIter.next();
-                try {
-                    Decoder decoder = new Decoder(encoded, context);
-                    if (packageNames.contains(getIntentPackage(decoder.launcherIntent)) &&
-                            user.equals(decoder.user)) {
-                        newStringsIter.remove();
-                    }
-                } catch (JSONException | URISyntaxException e) {
-                    Log.d(TAG, "Exception reading shortcut to add: " + e);
+        Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
+        if (DBG) {
+            Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
+                    + ", removing packages: " + packageNames);
+        }
+        if (Utilities.isEmpty(strings)) {
+            return;
+        }
+        Set<String> newStrings = new HashSet<>(strings);
+        Iterator<String> newStringsIter = newStrings.iterator();
+        while (newStringsIter.hasNext()) {
+            String encoded = newStringsIter.next();
+            try {
+                Decoder decoder = new Decoder(encoded, context);
+                if (packageNames.contains(getIntentPackage(decoder.launcherIntent)) &&
+                        user.equals(decoder.user)) {
                     newStringsIter.remove();
                 }
+            } catch (JSONException | URISyntaxException e) {
+                Log.d(TAG, "Exception reading shortcut to add: " + e);
+                newStringsIter.remove();
             }
-            sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
         }
-    }
-
-    private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(Context context) {
-        SharedPreferences sharedPrefs = Utilities.getPrefs(context);
-        synchronized(sLock) {
-            ArrayList<PendingInstallShortcutInfo> infos = new ArrayList<>();
-            Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
-            if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
-            if (strings == null) {
-                return infos;
-            }
-            for (String encoded : strings) {
-                PendingInstallShortcutInfo info = decode(encoded, context);
-                if (info != null) {
-                    infos.add(info);
-                }
-            }
-            sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet<String>()).apply();
-            return infos;
-        }
+        sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
     }
 
     public void onReceive(Context context, Intent data) {
@@ -256,7 +286,7 @@
 
     private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
         // Queue the item up for adding if launcher has not loaded properly yet
-        addToInstallQueue(Utilities.getPrefs(context), info);
+        Message.obtain(sHandler, MSG_ADD_TO_QUEUE, Pair.create(context, info)).sendToTarget();
         flushInstallQueue(context);
     }
 
@@ -269,17 +299,10 @@
     }
 
     static void flushInstallQueue(Context context) {
-        LauncherModel model = LauncherAppState.getInstance(context).getModel();
-        boolean launcherNotLoaded = model.getCallback() == null;
-        if (sInstallQueueDisabledFlags != 0 || launcherNotLoaded) {
+        if (sInstallQueueDisabledFlags != 0) {
             return;
         }
-
-        ArrayList<PendingInstallShortcutInfo> items = getAndClearInstallQueue(context);
-        if (!items.isEmpty()) {
-            model.addAndBindAddedWorkspaceItems(
-                    new LazyShortcutsProvider(context.getApplicationContext(), items));
-        }
+        Message.obtain(sHandler, MSG_FLUSH_QUEUE, context.getApplicationContext()).sendToTarget();
     }
 
     /**
@@ -601,42 +624,6 @@
         return new PendingInstallShortcutInfo(info, original.mContext);
     }
 
-    private static class LazyShortcutsProvider extends Provider<List<Pair<ItemInfo, Object>>> {
-
-        private final Context mContext;
-        private final ArrayList<PendingInstallShortcutInfo> mPendingItems;
-
-        public LazyShortcutsProvider(Context context, ArrayList<PendingInstallShortcutInfo> items) {
-            mContext = context;
-            mPendingItems = items;
-        }
-
-        /**
-         * This must be called on the background thread as this requires multiple calls to
-         * packageManager and icon cache.
-         */
-        @Override
-        public ArrayList<Pair<ItemInfo, Object>> get() {
-            Preconditions.assertNonUiThread();
-            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
-                String packageName = getIntentPackage(pendingInfo.launchIntent);
-                if (!TextUtils.isEmpty(packageName) && !launcherApps.isPackageEnabledForProfile(
-                        packageName, pendingInfo.user)) {
-                    if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
-                            + pendingInfo.launchIntent);
-                    continue;
-                }
-
-                // Generate a shortcut info to add into the model
-                installQueue.add(pendingInfo.getItemInfo());
-            }
-            return installQueue;
-        }
-    }
-
     private static ShortcutInfo createShortcutInfo(Intent data, LauncherAppState app) {
         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
         String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 74a5bac..3e22366 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -192,9 +192,8 @@
     /**
      * Adds the provided items to the workspace.
      */
-    public void addAndBindAddedWorkspaceItems(
-            Provider<List<Pair<ItemInfo, Object>>> appsProvider) {
-        enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider));
+    public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
+        enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
     }
 
     public ModelWriter getWriter(boolean hasVerticalHotseat) {
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 42926fa..a33a039 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -38,7 +38,6 @@
 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.List;
 
@@ -47,19 +46,18 @@
  */
 public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
 
-    private final Provider<List<Pair<ItemInfo, Object>>> mAppsProvider;
+    private final List<Pair<ItemInfo, Object>> mItemList;
 
     /**
-     * @param appsProvider items to add on the workspace
+     * @param itemList items to add on the workspace
      */
-    public AddWorkspaceItemsTask(Provider<List<Pair<ItemInfo, Object>>> appsProvider) {
-        mAppsProvider = appsProvider;
+    public AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList) {
+        mItemList = itemList;
     }
 
     @Override
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-        List<Pair<ItemInfo, Object>> workspaceApps = mAppsProvider.get();
-        if (workspaceApps.isEmpty()) {
+        if (mItemList.isEmpty()) {
             return;
         }
         Context context = app.getContext();
@@ -75,7 +73,7 @@
         synchronized(dataModel) {
 
             List<ItemInfo> filteredItems = new ArrayList<>();
-            for (Pair<ItemInfo, Object> entry : workspaceApps) {
+            for (Pair<ItemInfo, Object> entry : mItemList) {
                 ItemInfo item = entry.first;
                 if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
                         item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {