Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 44fa2940 authored by Tracy Zhou's avatar Tracy Zhou
Browse files

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
parent ebc50f30
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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) {
+105 −15
Original line number Diff line number Diff line
@@ -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,6 +258,43 @@ public class LauncherPreviewRenderer implements Callable<Bitmap> {
        }

        private void renderScreenShot(Canvas canvas) {
            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;
                }

                // 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);
@@ -248,7 +302,6 @@ public class LauncherPreviewRenderer implements Callable<Bitmap> {
                    info.screenId = i;
                    inflateAndAddIcon(info);
                }

                // Add workspace icons
                for (int i = 0; i < mIdp.numColumns; i++) {
                    WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
@@ -258,6 +311,7 @@ public class LauncherPreviewRenderer implements Callable<Bitmap> {
                    info.cellY = mIdp.numRows - 1;
                    inflateAndAddIcon(info);
                }
            }

            // Add first page QSB
            if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
@@ -286,6 +340,42 @@ public class LauncherPreviewRenderer implements Callable<Bitmap> {
        }
    }

    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);
+5 −90
Original line number Diff line number Diff line
@@ -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 <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
+1 −1
Original line number Diff line number Diff line
@@ -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;

+112 −0
Original line number Diff line number Diff line
/*
 * 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);
            }
        });
    }
}