Moving ShortcutInfo creation in InstallShortcutReceiver to background thread

> Creating shortcut info requires iconCache access

Bug: 21325319
Change-Id: I3317d8b6824aa05b836f3ed3626f169d4d34f783
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 056facb..d2f25a4 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -33,6 +33,8 @@
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.Thunk;
 
 import org.json.JSONException;
@@ -44,6 +46,7 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 
 public class InstallShortcutReceiver extends BroadcastReceiver {
@@ -76,11 +79,7 @@
             String encoded = info.encodeToString();
             if (encoded != null) {
                 Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
-                if (strings == null) {
-                    strings = new HashSet<String>(1);
-                } else {
-                    strings = new HashSet<String>(strings);
-                }
+                strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1);
                 strings.add(encoded);
                 sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
             }
@@ -115,16 +114,15 @@
         }
     }
 
-    private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(
-            SharedPreferences sharedPrefs, Context context) {
+    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 new ArrayList<PendingInstallShortcutInfo>();
+                return infos;
             }
-            ArrayList<PendingInstallShortcutInfo> infos =
-                new ArrayList<PendingInstallShortcutInfo>();
             for (String encoded : strings) {
                 PendingInstallShortcutInfo info = decode(encoded, context);
                 if (info != null) {
@@ -212,36 +210,12 @@
         mUseInstallQueue = false;
         flushInstallQueue(context);
     }
+
     static void flushInstallQueue(Context context) {
-        SharedPreferences sp = Utilities.getPrefs(context);
-        ArrayList<PendingInstallShortcutInfo> installQueue = getAndClearInstallQueue(sp, context);
-        if (!installQueue.isEmpty()) {
-            Iterator<PendingInstallShortcutInfo> iter = installQueue.iterator();
-            ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>();
-            while (iter.hasNext()) {
-                final PendingInstallShortcutInfo pendingInfo = iter.next();
-
-                // If the intent specifies a package, make sure the package exists
-                String packageName = pendingInfo.getTargetPackage();
-                if (!TextUtils.isEmpty(packageName)) {
-                    UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
-                    if (!LauncherAppsCompat.getInstance(context)
-                            .isPackageEnabledForProfile(packageName, myUserHandle)) {
-                        if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
-                                + pendingInfo.launchIntent);
-                        continue;
-                    }
-                }
-
-                // Generate a shortcut info to add into the model
-                addShortcuts.add(pendingInfo.getShortcutInfo());
-            }
-
-            // Add the new apps to the model and bind them
-            if (!addShortcuts.isEmpty()) {
-                LauncherAppState app = LauncherAppState.getInstance();
-                app.getModel().addAndBindAddedWorkspaceItems(addShortcuts);
-            }
+        ArrayList<PendingInstallShortcutInfo> items = getAndClearInstallQueue(context);
+        if (!items.isEmpty()) {
+            LauncherAppState.getInstance().getModel().addAndBindAddedWorkspaceItems(
+                    new LazyShortcutsProvider(context.getApplicationContext(), items));
         }
     }
 
@@ -445,4 +419,40 @@
         // Ignore any conflicts in the label name, as that can change based on locale.
         return new PendingInstallShortcutInfo(info, original.mContext);
     }
+
+    private static class LazyShortcutsProvider extends Provider<List<ItemInfo>> {
+
+        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<ItemInfo> get() {
+            Preconditions.assertNonUiThread();
+            ArrayList<ItemInfo> 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 = pendingInfo.getTargetPackage();
+                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.getShortcutInfo());
+            }
+            return installQueue;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index c70a475..3daa2c3 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -84,6 +84,7 @@
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 
@@ -261,9 +262,16 @@
     /**
      * 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(
-            final ArrayList<? extends ItemInfo> workspaceApps) {
-        enqueueModelUpdateTask(new AddWorkspaceItemsTask(workspaceApps));
+            Provider<List<ItemInfo>> appsProvider) {
+        enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider));
     }
 
     /**
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 986e163..97335cb 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -33,26 +33,29 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.Provider;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Task to add auto-created workspace items.
  */
 public class AddWorkspaceItemsTask extends ExtendedModelTask {
 
-    private final ArrayList<? extends ItemInfo> mWorkspaceApps;
+    private final Provider<List<ItemInfo>> mAppsProvider;
 
     /**
-     * @param workspaceApps items to add on the workspace
+     * @param appsProvider items to add on the workspace
      */
-    public AddWorkspaceItemsTask(ArrayList<? extends ItemInfo> workspaceApps) {
-        mWorkspaceApps = workspaceApps;
+    public AddWorkspaceItemsTask(Provider<List<ItemInfo>> appsProvider) {
+        mAppsProvider = appsProvider;
     }
 
     @Override
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-        if (mWorkspaceApps.isEmpty()) {
+        List<ItemInfo> workspaceApps = mAppsProvider.get();
+        if (workspaceApps.isEmpty()) {
             return;
         }
         Context context = app.getContext();
@@ -65,7 +68,7 @@
         // called.
         ArrayList<Long> workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context);
         synchronized(dataModel) {
-            for (ItemInfo item : mWorkspaceApps) {
+            for (ItemInfo item : workspaceApps) {
                 if (item instanceof ShortcutInfo) {
                     // Short-circuit this logic if the icon exists somewhere on the workspace
                     if (shortcutExists(dataModel, item.getIntent(), item.user)) {
@@ -258,5 +261,4 @@
         }
         return occupied.findVacantCell(xy, spanX, spanY);
     }
-
 }
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index 78b7a3e..817a38a 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -121,7 +121,7 @@
             // getting filled with the managed user apps, when it start with a fresh DB (or after
             // a very long time).
             if (userAppsExisted && !homescreenApps.isEmpty()) {
-                mModel.addAndBindAddedWorkspaceItems(homescreenApps);
+                mModel.addAndBindAddedWorkspaceItems(new ArrayList<ItemInfo>(homescreenApps));
             }
         }
 
@@ -173,7 +173,7 @@
                 }
 
                 // Add the item to home screen and DB. This also generates an item id synchronously.
-                ArrayList<ItemInfo> itemList = new ArrayList<ItemInfo>(1);
+                ArrayList<ItemInfo> itemList = new ArrayList<>(1);
                 itemList.add(workFolder);
                 mModel.addAndBindAddedWorkspaceItems(itemList);
                 mPrefs.edit().putLong(folderIdKey, workFolder.id).apply();
diff --git a/src/com/android/launcher3/util/Provider.java b/src/com/android/launcher3/util/Provider.java
new file mode 100644
index 0000000..1cdd8d6
--- /dev/null
+++ b/src/com/android/launcher3/util/Provider.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/**
+ * Utility class to allow lazy initialization of objects.
+ */
+public abstract class Provider<T> {
+
+    /**
+     * Initializes and returns the object. This may contain expensive operations not suitable
+     * to UI thread.
+     */
+    public abstract T get();
+
+    public static <T> Provider<T> of (final T value) {
+        return new Provider<T>() {
+            @Override
+            public T get() {
+                return value;
+            }
+        };
+    }
+}
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
index ecb3782..b2f0cbb 100644
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -15,6 +15,7 @@
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.Provider;
 
 import org.mockito.ArgumentCaptor;
 
@@ -48,8 +49,8 @@
         idp.numRows = 5;
     }
 
-    private <T extends ItemInfo> AddWorkspaceItemsTask newTask(T... items) {
-        return new AddWorkspaceItemsTask(new ArrayList<>(Arrays.asList(items))) {
+    private AddWorkspaceItemsTask newTask(ItemInfo... items) {
+        return new AddWorkspaceItemsTask(Provider.of(Arrays.asList(items))) {
 
             @Override
             protected void addItemToDatabase(Context context, ItemInfo item,