From 44fa2940cd8b055e94dd6e6e6bb60df2eb79c48a Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Fri, 29 Nov 2019 21:47:29 -0800 Subject: [PATCH] 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 --- .../launcher3/config/FeatureFlags.java | 4 + .../graphics/LauncherPreviewRenderer.java | 120 +++++++++++++++--- .../launcher3/model/BaseLoaderResults.java | 95 +------------- .../android/launcher3/model/LoaderTask.java | 2 +- .../android/launcher3/model/ModelUtils.java | 112 ++++++++++++++++ 5 files changed, 227 insertions(+), 106 deletions(-) create mode 100644 src/com/android/launcher3/model/ModelUtils.java diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index ae30380c78..1ae30b7d8f 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -126,6 +126,10 @@ public final class FeatureFlags { 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 2badb6e2f3..0c5535ffe0 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.EXACTLY; 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.DeviceProfile; 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.config.FeatureFlags; 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 @@ public class LauncherPreviewRenderer implements Callable { } 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 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 currentWorkspaceItems = new ArrayList<>(); + ArrayList 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 @@ public class LauncherPreviewRenderer implements Callable { } } + private static class WorkspaceItemsInfoFetcher implements Callable>, + LauncherModel.ModelUpdateTask { + + private final FutureTask> 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 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 a00a6bd3a5..76c2951310 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.ItemInfo; 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 @@ public abstract class BaseLoaderResults { 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 @@ public abstract class BaseLoaderResults { } } - - /** Filters the set of items who are directly or indirectly (via another container) on the - * specified screen. */ - public static void filterCurrentWorkspaceItems(int currentScreenId, - ArrayList allWorkspaceItems, - ArrayList currentScreenItems, - ArrayList otherScreenItems) { - // Purge any null ItemInfos - Iterator 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 workspaceItems) { - final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile(); - final int screenCols = profile.numColumns; - final int screenCellCount = profile.numColumns * profile.numRows; - Collections.sort(workspaceItems, new Comparator() { - @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 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 2754cfc6b8..cc994df82e 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -19,7 +19,7 @@ package com.android.launcher3.model; 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 0000000000..628dd95c41 --- /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 void filterCurrentWorkspaceItems(int currentScreenId, + ArrayList allWorkspaceItems, + ArrayList currentScreenItems, + ArrayList otherScreenItems) { + // Purge any null ItemInfos + Iterator 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 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); + } + }); + } +}