Render user's actual workspace in ThemePicker preview (Part 1)

This change takes care of icon rendering. Further work still needs to be done for folders and widgets.

Test: Go to grid options, choose a different grid option, and see user's workspace rendered in the preview
Bug: 144052839
Change-Id: I696153dec1d1f08c5ac82d0c75be5740325c0fc4
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index ae30380..1ae30b7 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -126,6 +126,10 @@
     public static final TogglableFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = new TogglableFlag(
             "ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache");
 
+    public static final TogglableFlag ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER = new TogglableFlag(
+            "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", false,
+            "Show launcher preview in grid picker");
+
     public static void initialize(Context context) {
         // Avoid the disk read for user builds
         if (Utilities.IS_DEBUG_DEVICE) {
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 2badb6e..0c5535f 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -19,6 +19,10 @@
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 import static android.view.View.VISIBLE;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER;
+import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
+
 import android.annotation.TargetApi;
 import android.app.Fragment;
 import android.content.Context;
@@ -48,6 +52,10 @@
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -58,11 +66,20 @@
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.model.AllAppsList;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.LoaderResults;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
 
+import java.util.ArrayList;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
@@ -241,22 +258,59 @@
         }
 
         private void renderScreenShot(Canvas canvas) {
-            // Add hotseat icons
-            for (int i = 0; i < mIdp.numHotseatIcons; i++) {
-                WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
-                info.container = Favorites.CONTAINER_HOTSEAT;
-                info.screenId = i;
-                inflateAndAddIcon(info);
-            }
+            if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
+                final LauncherModel launcherModel = LauncherAppState.getInstance(
+                        mContext).getModel();
+                final WorkspaceItemsInfoFetcher fetcher = new WorkspaceItemsInfoFetcher();
+                launcherModel.enqueueModelUpdateTask(fetcher);
+                ArrayList<ItemInfo> workspaceItems;
+                try {
+                    workspaceItems = fetcher.mTask.get(5, TimeUnit.SECONDS);
+                } catch (InterruptedException | ExecutionException | TimeoutException e) {
+                    Log.d(TAG, "Error fetching workspace items info", e);
+                    return;
+                }
 
-            // Add workspace icons
-            for (int i = 0; i < mIdp.numColumns; i++) {
-                WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
-                info.container = Favorites.CONTAINER_DESKTOP;
-                info.screenId = 0;
-                info.cellX = i;
-                info.cellY = mIdp.numRows - 1;
-                inflateAndAddIcon(info);
+                // Separate the items that are on the current screen, and all the other remaining
+                // items
+                ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
+                ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
+
+                filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceItems,
+                        currentWorkspaceItems, otherWorkspaceItems);
+                sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
+
+                for (ItemInfo itemInfo : currentWorkspaceItems) {
+                    switch (itemInfo.itemType) {
+                        case Favorites.ITEM_TYPE_APPLICATION:
+                        case Favorites.ITEM_TYPE_SHORTCUT:
+                        case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                            inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
+                            break;
+                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                            // TODO: for folder implementation here.
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            } else {
+                // Add hotseat icons
+                for (int i = 0; i < mIdp.numHotseatIcons; i++) {
+                    WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
+                    info.container = Favorites.CONTAINER_HOTSEAT;
+                    info.screenId = i;
+                    inflateAndAddIcon(info);
+                }
+                // Add workspace icons
+                for (int i = 0; i < mIdp.numColumns; i++) {
+                    WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
+                    info.container = Favorites.CONTAINER_DESKTOP;
+                    info.screenId = 0;
+                    info.cellX = i;
+                    info.cellY = mIdp.numRows - 1;
+                    inflateAndAddIcon(info);
+                }
             }
 
             // Add first page QSB
@@ -286,6 +340,42 @@
         }
     }
 
+    private static class WorkspaceItemsInfoFetcher implements Callable<ArrayList<ItemInfo>>,
+            LauncherModel.ModelUpdateTask {
+
+        private final FutureTask<ArrayList<ItemInfo>> mTask = new FutureTask<>(this);
+
+        private LauncherAppState mApp;
+        private LauncherModel mModel;
+        private BgDataModel mBgDataModel;
+        private AllAppsList mAllAppsList;
+
+        @Override
+        public void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel,
+                AllAppsList allAppsList, Executor uiExecutor) {
+            mApp = app;
+            mModel = model;
+            mBgDataModel = dataModel;
+            mAllAppsList = allAppsList;
+        }
+
+        @Override
+        public void run() {
+            mTask.run();
+        }
+
+        @Override
+        public ArrayList<ItemInfo> call() throws Exception {
+            if (!mModel.isModelLoaded()) {
+                Log.d(TAG, "Workspace not loaded, loading now");
+                mModel.startLoaderForResults(
+                        new LoaderResults(mApp, mBgDataModel, mAllAppsList, 0, null));
+                return new ArrayList<>();
+            }
+            return mBgDataModel.workspaceItems;
+        }
+    }
+
     private static void measureView(View view, int width, int height) {
         view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
         view.layout(0, 0, width, height);
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index a00a6bd..76c2951 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.os.Looper;
@@ -27,20 +29,15 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.PagedView;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LooperIdleLock;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
 import java.util.concurrent.Executor;
 
 /**
@@ -123,8 +120,9 @@
                 otherWorkspaceItems);
         filterCurrentWorkspaceItems(currentScreenId, appWidgets, currentAppWidgets,
                 otherAppWidgets);
-        sortWorkspaceItemsSpatially(currentWorkspaceItems);
-        sortWorkspaceItemsSpatially(otherWorkspaceItems);
+        final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
+        sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
+        sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);
 
         // Tell the workspace that we're about to start binding items
         executeCallbacksTask(c -> {
@@ -169,89 +167,6 @@
         }
     }
 
-
-    /** Filters the set of items who are directly or indirectly (via another container) on the
-     * specified screen. */
-    public static <T extends ItemInfo> void filterCurrentWorkspaceItems(int currentScreenId,
-            ArrayList<T> allWorkspaceItems,
-            ArrayList<T> currentScreenItems,
-            ArrayList<T> otherScreenItems) {
-        // Purge any null ItemInfos
-        Iterator<T> iter = allWorkspaceItems.iterator();
-        while (iter.hasNext()) {
-            ItemInfo i = iter.next();
-            if (i == null) {
-                iter.remove();
-            }
-        }
-
-        // Order the set of items by their containers first, this allows use to walk through the
-        // list sequentially, build up a list of containers that are in the specified screen,
-        // as well as all items in those containers.
-        IntSet itemsOnScreen = new IntSet();
-        Collections.sort(allWorkspaceItems,
-                (lhs, rhs) -> Integer.compare(lhs.container, rhs.container));
-
-        for (T info : allWorkspaceItems) {
-            if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                if (info.screenId == currentScreenId) {
-                    currentScreenItems.add(info);
-                    itemsOnScreen.add(info.id);
-                } else {
-                    otherScreenItems.add(info);
-                }
-            } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                currentScreenItems.add(info);
-                itemsOnScreen.add(info.id);
-            } else {
-                if (itemsOnScreen.contains(info.container)) {
-                    currentScreenItems.add(info);
-                    itemsOnScreen.add(info.id);
-                } else {
-                    otherScreenItems.add(info);
-                }
-            }
-        }
-    }
-
-    /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
-     * right) */
-    protected void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
-        final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile();
-        final int screenCols = profile.numColumns;
-        final int screenCellCount = profile.numColumns * profile.numRows;
-        Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
-            @Override
-            public int compare(ItemInfo lhs, ItemInfo rhs) {
-                if (lhs.container == rhs.container) {
-                    // Within containers, order by their spatial position in that container
-                    switch (lhs.container) {
-                        case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
-                            int lr = (lhs.screenId * screenCellCount +
-                                    lhs.cellY * screenCols + lhs.cellX);
-                            int rr = (rhs.screenId * screenCellCount +
-                                    rhs.cellY * screenCols + rhs.cellX);
-                            return Integer.compare(lr, rr);
-                        }
-                        case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
-                            // We currently use the screen id as the rank
-                            return Integer.compare(lhs.screenId, rhs.screenId);
-                        }
-                        default:
-                            if (FeatureFlags.IS_DOGFOOD_BUILD) {
-                                throw new RuntimeException("Unexpected container type when " +
-                                        "sorting workspace items.");
-                            }
-                            return 0;
-                    }
-                } else {
-                    // Between containers, order by hotseat, desktop
-                    return Integer.compare(lhs.container, rhs.container);
-                }
-            }
-        });
-    }
-
     protected void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems,
             final Executor executor) {
         // Bind the workspace items
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 2754cfc..cc994df 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
-import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.PackageManagerHelper.isSystemApp;
 
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
new file mode 100644
index 0000000..628dd95
--- /dev/null
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2019 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.model;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.IntSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * Utils class for {@link com.android.launcher3.LauncherModel}.
+ */
+public class ModelUtils {
+
+    /**
+     * Filters the set of items who are directly or indirectly (via another container) on the
+     * specified screen.
+     */
+    public static <T extends ItemInfo> void filterCurrentWorkspaceItems(int currentScreenId,
+            ArrayList<T> allWorkspaceItems,
+            ArrayList<T> currentScreenItems,
+            ArrayList<T> otherScreenItems) {
+        // Purge any null ItemInfos
+        Iterator<T> iter = allWorkspaceItems.iterator();
+        while (iter.hasNext()) {
+            ItemInfo i = iter.next();
+            if (i == null) {
+                iter.remove();
+            }
+        }
+        // Order the set of items by their containers first, this allows use to walk through the
+        // list sequentially, build up a list of containers that are in the specified screen,
+        // as well as all items in those containers.
+        IntSet itemsOnScreen = new IntSet();
+        Collections.sort(allWorkspaceItems,
+                (lhs, rhs) -> Integer.compare(lhs.container, rhs.container));
+        for (T info : allWorkspaceItems) {
+            if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                if (info.screenId == currentScreenId) {
+                    currentScreenItems.add(info);
+                    itemsOnScreen.add(info.id);
+                } else {
+                    otherScreenItems.add(info);
+                }
+            } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                currentScreenItems.add(info);
+                itemsOnScreen.add(info.id);
+            } else {
+                if (itemsOnScreen.contains(info.container)) {
+                    currentScreenItems.add(info);
+                    itemsOnScreen.add(info.id);
+                } else {
+                    otherScreenItems.add(info);
+                }
+            }
+        }
+    }
+
+    /**
+     * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right)
+     */
+    public static void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile,
+            ArrayList<ItemInfo> workspaceItems) {
+        final int screenCols = profile.numColumns;
+        final int screenCellCount = profile.numColumns * profile.numRows;
+        Collections.sort(workspaceItems, (lhs, rhs) -> {
+            if (lhs.container == rhs.container) {
+                // Within containers, order by their spatial position in that container
+                switch (lhs.container) {
+                    case LauncherSettings.Favorites.CONTAINER_DESKTOP: {
+                        int lr = (lhs.screenId * screenCellCount + lhs.cellY * screenCols
+                                + lhs.cellX);
+                        int rr = (rhs.screenId * screenCellCount + +rhs.cellY * screenCols
+                                + rhs.cellX);
+                        return Integer.compare(lr, rr);
+                    }
+                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT: {
+                        // We currently use the screen id as the rank
+                        return Integer.compare(lhs.screenId, rhs.screenId);
+                    }
+                    default:
+                        if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                            throw new RuntimeException(
+                                    "Unexpected container type when sorting workspace items.");
+                        }
+                        return 0;
+                }
+            } else {
+                // Between containers, order by hotseat, desktop
+                return Integer.compare(lhs.container, rhs.container);
+            }
+        });
+    }
+}