Merge "Override displacement for transient taskbar instead of mCurrentShift" into tm-qpr-dev
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index c0b6657..50f1236 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -292,10 +292,14 @@
     <!-- An additional touch slop to prevent x-axis movement during the swipe up to show taskbar -->
     <dimen name="transient_taskbar_clamped_offset_bound">16dp</dimen>
     <!-- Taskbar swipe up thresholds -->
+    <dimen name="taskbar_nav_threshold">40dp</dimen>
     <dimen name="taskbar_app_window_threshold">150dp</dimen>
     <dimen name="taskbar_home_overview_threshold">225dp</dimen>
     <dimen name="taskbar_catch_up_threshold">300dp</dimen>
-    <dimen name="taskbar_nav_threshold">40dp</dimen>
+
+    <dimen name="taskbar_nav_threshold_v2">30dp</dimen>
+    <dimen name="taskbar_app_window_threshold_v2">100dp</dimen>
+    <dimen name="taskbar_home_overview_threshold_v2">200dp</dimen>
 
     <!--  Taskbar 3 button spacing  -->
     <dimen name="taskbar_button_space_inbetween">24dp</dimen>
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 1bc00be..9aedbf8 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_REVISED_THRESHOLDS;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
@@ -292,7 +293,6 @@
 
     private boolean mWasLauncherAlreadyVisible;
 
-    private boolean mPassedOverviewThreshold;
     private boolean mGestureStarted;
     private boolean mLogDirectionUpOrLeft = true;
     private PointF mDownPos;
@@ -319,8 +319,8 @@
     private final int mTaskbarCatchUpThreshold;
     private boolean mTaskbarAlreadyOpen;
     private final boolean mIsTransientTaskbar;
-    // Only used when mIsTransientTaskbar is true.
-    private boolean mHasReachedHomeOverviewThreshold;
+    // May be set to false when mIsTransientTaskbar is true.
+    private boolean mCanSlowSwipeGoHome = true;
 
     public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
@@ -345,7 +345,9 @@
 
         Resources res = context.getResources();
         mTaskbarAppWindowThreshold = res
-                .getDimensionPixelSize(R.dimen.taskbar_app_window_threshold);
+                .getDimensionPixelSize(ENABLE_TASKBAR_REVISED_THRESHOLDS.get()
+                        ? R.dimen.taskbar_app_window_threshold_v2
+                        : R.dimen.taskbar_app_window_threshold);
         mTaskbarCatchUpThreshold = res.getDimensionPixelSize(R.dimen.taskbar_catch_up_threshold);
         mIsTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
 
@@ -811,14 +813,6 @@
     @UiThread
     @Override
     public void updateFinalShift() {
-        final boolean passed = hasReachedHomeOverviewThreshold();
-        if (passed != mPassedOverviewThreshold) {
-            mPassedOverviewThreshold = passed;
-            if (mDeviceState.isTwoButtonNavMode() && !mGestureState.isHandlingAtomicEvent()) {
-                performHapticFeedback();
-            }
-        }
-
         updateSysUiFlags(mCurrentShift.value);
         applyScrollAndTransform();
 
@@ -903,8 +897,6 @@
         mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
                 this::startInterceptingTouchesForGesture);
         mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
-
-        mPassedOverviewThreshold = false;
     }
 
     @Override
@@ -1132,20 +1124,11 @@
             return willGoToNewTask || isCenteredOnNewTask ? NEW_TASK : LAST_TASK;
         }
 
-        if (!mDeviceState.isFullyGesturalNavMode()) {
-            return (!hasReachedHomeOverviewThreshold() && willGoToNewTask) ? NEW_TASK : RECENTS;
-        }
         return willGoToNewTask ? NEW_TASK : HOME;
     }
 
     private GestureEndTarget calculateEndTargetForNonFling(PointF velocity) {
         final boolean isScrollingToNewTask = isScrollingToNewTask();
-        final boolean reachedHomeOverviewThreshold = hasReachedHomeOverviewThreshold();
-        if (!mDeviceState.isFullyGesturalNavMode()) {
-            return reachedHomeOverviewThreshold && mGestureStarted
-                    ? RECENTS
-                    : (isScrollingToNewTask ? NEW_TASK : LAST_TASK);
-        }
 
         // Fully gestural mode.
         final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources()
@@ -1158,10 +1141,8 @@
             return RECENTS;
         } else if (isScrollingToNewTask) {
             return NEW_TASK;
-        } else if (reachedHomeOverviewThreshold) {
-            return HOME;
         }
-        return LAST_TASK;
+        return velocity.y < 0 && mCanSlowSwipeGoHome ? HOME : LAST_TASK;
     }
 
     private boolean isScrollingToNewTask() {
@@ -1178,21 +1159,15 @@
     }
 
     /**
-     * Sets whether the current swipe has reached the threshold where if user lets go they would
-     * go to either the home state or overview state.
+     * Sets whether a slow swipe can go to the HOME end target when the user lets go. A slow swipe
+     * for this purpose must meet two criteria:
+     *   1) y-velocity is less than quickstep_fling_threshold_speed
+     *   AND
+     *   2) motion pause has not been detected (possibly because
+     *   {@link MotionPauseDetector#setDisallowPause} has been called with disallowPause == true)
      */
-    public void setHasReachedHomeOverviewThreshold(boolean hasReachedHomeOverviewThreshold) {
-        mHasReachedHomeOverviewThreshold = hasReachedHomeOverviewThreshold;
-    }
-
-    /**
-     * Returns true iff swipe has reached the overview threshold.
-     */
-    public boolean hasReachedHomeOverviewThreshold() {
-        if (mIsTransientTaskbar) {
-            return mHasReachedHomeOverviewThreshold;
-        }
-        return mCurrentShift.value > MIN_PROGRESS_FOR_OVERVIEW;
+    public void setCanSlowSwipeGoHome(boolean canSlowSwipeGoHome) {
+        mCanSlowSwipeGoHome = canSlowSwipeGoHome;
     }
 
     @UiThread
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index f842fd0..bf666ea 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.PagedView.DEBUG_FAILED_QUICKSWITCH;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_REVISED_THRESHOLDS;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS;
 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
@@ -164,7 +165,9 @@
         mTaskbarAlreadyOpen = controller != null && !controller.isTaskbarStashed();
         mIsTransientTaskbar = DisplayController.isTransientTaskbar(base);
         mTaskbarHomeOverviewThreshold = base.getResources()
-                .getDimensionPixelSize(R.dimen.taskbar_home_overview_threshold);
+                .getDimensionPixelSize(ENABLE_TASKBAR_REVISED_THRESHOLDS.get()
+                        ? R.dimen.taskbar_home_overview_threshold_v2
+                        : R.dimen.taskbar_home_overview_threshold);
 
         boolean continuingPreviousGesture = mTaskAnimationManager.isRecentsAnimationRunning();
         mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
@@ -340,8 +343,8 @@
                         boolean minSwipeMet = upDist >= mMotionPauseMinDisplacement;
                         if (mIsTransientTaskbar) {
                             minSwipeMet = upDist >= mTaskbarHomeOverviewThreshold;
-                            mInteractionHandler.setHasReachedHomeOverviewThreshold(minSwipeMet);
                         }
+                        mInteractionHandler.setCanSlowSwipeGoHome(minSwipeMet);
                         mMotionPauseDetector.setDisallowPause(!minSwipeMet
                                 || isLikelyToStartNewTask);
                         mMotionPauseDetector.addPosition(ev);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
index b7f2022..3a09490 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
@@ -18,6 +18,7 @@
 import static android.view.MotionEvent.INVALID_POINTER_ID;
 
 import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_REVISED_THRESHOLDS;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TOUCHING;
 
 import android.content.Context;
@@ -72,7 +73,9 @@
 
         Resources res = context.getResources();
         mUnstashArea = res.getDimensionPixelSize(R.dimen.taskbar_unstash_input_area);
-        int taskbarThreshold = res.getDimensionPixelSize(R.dimen.taskbar_nav_threshold);
+        int taskbarThreshold = res.getDimensionPixelSize(ENABLE_TASKBAR_REVISED_THRESHOLDS.get()
+                ? R.dimen.taskbar_nav_threshold_v2
+                : R.dimen.taskbar_nav_threshold);
         int screenHeight = taskbarActivityContext.getDeviceProfile().heightPx;
         mTaskbarThresholdY = screenHeight - taskbarThreshold;
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 50b7563..db604f2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -388,6 +388,9 @@
     <dimen name="taskbar_app_window_threshold">0dp</dimen>
     <dimen name="taskbar_home_overview_threshold">0dp</dimen>
     <dimen name="taskbar_catch_up_threshold">0dp</dimen>
+    <dimen name="taskbar_nav_threshold_v2">0dp</dimen>
+    <dimen name="taskbar_app_window_threshold_v2">0dp</dimen>
+    <dimen name="taskbar_home_overview_threshold_v2">0dp</dimen>
 
     <!-- Size of the maximum radius for the enforced rounded rectangles. -->
     <dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 191d063..4f2f093 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1074,13 +1074,14 @@
         mXDown = ev.getX();
         mYDown = ev.getY();
         if (mFirstPagePinnedItem != null) {
-            mTempFXY[0] = mXDown + getScrollX();
-            mTempFXY[1] = mYDown + getScrollY();
-            Utilities.mapCoordInSelfToDescendant(mFirstPagePinnedItem, this, mTempFXY);
-            mIsEventOverFirstPagePinnedItem = mFirstPagePinnedItem.getLeft() <= mTempFXY[0]
-                    && mFirstPagePinnedItem.getRight() >= mTempFXY[0]
-                    && mFirstPagePinnedItem.getTop() <= mTempFXY[1]
-                    && mFirstPagePinnedItem.getBottom() >= mTempFXY[1];
+            final float[] tempFXY = new float[2];
+            tempFXY[0] = mXDown;
+            tempFXY[1] = mYDown;
+            Utilities.mapCoordInSelfToDescendant(mFirstPagePinnedItem, this, tempFXY);
+            mIsEventOverFirstPagePinnedItem = mFirstPagePinnedItem.getLeft() <= tempFXY[0]
+                    && mFirstPagePinnedItem.getRight() >= tempFXY[0]
+                    && mFirstPagePinnedItem.getTop() <= tempFXY[1]
+                    && mFirstPagePinnedItem.getBottom() >= tempFXY[1];
         } else {
             mIsEventOverFirstPagePinnedItem = false;
         }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 819463d..d6d468e 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -79,10 +79,6 @@
     public static final BooleanFlag PROMISE_APPS_IN_ALL_APPS = getDebugFlag(
             "PROMISE_APPS_IN_ALL_APPS", false, "Add promise icon in all-apps");
 
-    // TODO: b/206508141: Long pressing on some icons on home screen cause launcher to crash.
-    public static final BooleanFlag ENABLE_LOCAL_COLOR_POPUPS = getDebugFlag(
-            "ENABLE_LOCAL_COLOR_POPUPS", false, "Enable local color extraction for popups.");
-
     public static final BooleanFlag KEYGUARD_ANIMATION = getDebugFlag(
             "KEYGUARD_ANIMATION", false, "Enable animation for keyguard going away on wallpaper");
 
@@ -326,6 +322,10 @@
             "HOME_GARDENING_WORKSPACE_BUTTONS", false,
             "Change workspace edit buttons to reflect home gardening");
 
+    public static final BooleanFlag ENABLE_TASKBAR_REVISED_THRESHOLDS = getDebugFlag(
+            "ENABLE_TASKBAR_REVISED_THRESHOLDS", false,
+            "Uses revised thresholds for transient taskbar.");
+
     public static final BooleanFlag ENABLE_TRANSIENT_TASKBAR = getDebugFlag(
             "ENABLE_TRANSIENT_TASKBAR", false, "Enables transient taskbar.");
 
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 287b976..85c0a7a 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -50,7 +50,7 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
 import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.model.GridSizeMigrationTaskV2;
+import com.android.launcher3.model.GridSizeMigrationUtil;
 import com.android.launcher3.model.LoaderTask;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.RunnableList;
@@ -241,10 +241,10 @@
 
     @WorkerThread
     private boolean doGridMigrationIfNecessary() {
-        if (!GridSizeMigrationTaskV2.needsToMigrate(mContext, mIdp)) {
+        if (!GridSizeMigrationUtil.needsToMigrate(mContext, mIdp)) {
             return false;
         }
-        return GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp);
+        return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, mIdp);
     }
 
     @UiThread
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
similarity index 75%
rename from src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
rename to src/com/android/launcher3/model/GridSizeMigrationUtil.java
index 341372e..d63408b 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -32,7 +32,6 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
@@ -64,42 +63,13 @@
  * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
  * result of restoring from a larger device or device density change.
  */
-public class GridSizeMigrationTaskV2 {
+public class GridSizeMigrationUtil {
 
-    private static final String TAG = "GridSizeMigrationTaskV2";
+    private static final String TAG = "GridSizeMigrationUtil";
     private static final boolean DEBUG = false;
 
-    private final Context mContext;
-    private final SQLiteDatabase mDb;
-    private final DbReader mSrcReader;
-    private final DbReader mDestReader;
-
-    private final List<DbEntry> mHotseatItems;
-    private final List<DbEntry> mWorkspaceItems;
-
-    private final List<DbEntry> mHotseatDiff;
-    private final List<DbEntry> mWorkspaceDiff;
-
-    private final int mDestHotseatSize;
-    private final int mTrgX, mTrgY;
-
-    @VisibleForTesting
-    protected GridSizeMigrationTaskV2(Context context, SQLiteDatabase db, DbReader srcReader,
-            DbReader destReader, int destHotseatSize, Point targetSize) {
-        mContext = context;
-        mDb = db;
-        mSrcReader = srcReader;
-        mDestReader = destReader;
-
-        mHotseatItems = destReader.loadHotseatEntries();
-        mWorkspaceItems = destReader.loadAllWorkspaceEntries();
-
-        mHotseatDiff = calcDiff(mSrcReader.loadHotseatEntries(), mHotseatItems);
-        mWorkspaceDiff = calcDiff(mSrcReader.loadAllWorkspaceEntries(), mWorkspaceItems);
-        mDestHotseatSize = destHotseatSize;
-
-        mTrgX = targetSize.x;
-        mTrgY = targetSize.y;
+    private GridSizeMigrationUtil() {
+        // Util class should not be instantiated
     }
 
     /**
@@ -187,9 +157,8 @@
                     context, validPackages);
 
             Point targetSize = new Point(destDeviceState.getColumns(), destDeviceState.getRows());
-            GridSizeMigrationTaskV2 task = new GridSizeMigrationTaskV2(context, t.getDb(),
-                    srcReader, destReader, destDeviceState.getNumHotseat(), targetSize);
-            task.migrate(srcDeviceState, destDeviceState);
+            migrate(context, t.getDb(), srcReader, destReader, destDeviceState.getNumHotseat(),
+                    targetSize, srcDeviceState, destDeviceState);
 
             if (!migrateForPreview) {
                 dropTable(t.getDb(), LauncherSettings.Favorites.TMP_TABLE);
@@ -212,25 +181,39 @@
         }
     }
 
-    @VisibleForTesting
-    protected boolean migrate(DeviceGridState srcDeviceState, DeviceGridState destDeviceState) {
-        if (mHotseatDiff.isEmpty() && mWorkspaceDiff.isEmpty()) {
+    public static boolean migrate(
+            @NonNull final Context context, @NonNull final SQLiteDatabase db,
+            @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
+            final int destHotseatSize, @NonNull final Point targetSize,
+            @NonNull final DeviceGridState srcDeviceState,
+            @NonNull final DeviceGridState destDeviceState) {
+
+        final List<DbEntry> hotseatItems = destReader.loadHotseatEntries();
+        final List<DbEntry> workspaceItems = destReader.loadAllWorkspaceEntries();
+        final List<DbEntry> hotseatDiff =
+                calcDiff(srcReader.loadHotseatEntries(), hotseatItems);
+        final List<DbEntry> workspaceDiff =
+                calcDiff(srcReader.loadAllWorkspaceEntries(), workspaceItems);
+
+        final int trgX = targetSize.x;
+        final int trgY = targetSize.y;
+
+        if (hotseatDiff.isEmpty() && workspaceDiff.isEmpty()) {
             return false;
         }
 
         // Sort the items by the reading order.
-        Collections.sort(mHotseatDiff);
-        Collections.sort(mWorkspaceDiff);
+        Collections.sort(hotseatDiff);
+        Collections.sort(workspaceDiff);
 
         // Migrate hotseat
-        HotseatPlacementSolution hotseatSolution = new HotseatPlacementSolution(mDb, mSrcReader,
-                mDestReader, mContext, mDestHotseatSize, mHotseatItems, mHotseatDiff);
-        hotseatSolution.find();
+        solveHotseatPlacement(db, srcReader,
+                destReader, context, destHotseatSize, hotseatItems, hotseatDiff);
 
         // Migrate workspace.
         // First we create a collection of the screens
         List<Integer> screens = new ArrayList<>();
-        for (int screenId = 0; screenId <= mDestReader.mLastScreenId; screenId++) {
+        for (int screenId = 0; screenId <= destReader.mLastScreenId; screenId++) {
             screens.add(screenId);
         }
 
@@ -245,22 +228,19 @@
             if (DEBUG) {
                 Log.d(TAG, "Migrating " + screenId);
             }
-            GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
-                    mDestReader, mContext, screenId, mTrgX, mTrgY, mWorkspaceDiff, false);
-            workspaceSolution.find();
-            if (mWorkspaceDiff.isEmpty()) {
+            solveGridPlacement(db, srcReader,
+                    destReader, context, screenId, trgX, trgY, workspaceDiff, false);
+            if (workspaceDiff.isEmpty()) {
                 break;
             }
         }
 
         // In case the new grid is smaller, there might be some leftover items that don't fit on
         // any of the screens, in this case we add them to new screens until all of them are placed.
-        int screenId = mDestReader.mLastScreenId + 1;
-        while (!mWorkspaceDiff.isEmpty()) {
-            GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
-                    mDestReader, mContext, screenId, mTrgX, mTrgY, mWorkspaceDiff,
-                    preservePages);
-            workspaceSolution.find();
+        int screenId = destReader.mLastScreenId + 1;
+        while (!workspaceDiff.isEmpty()) {
+            solveGridPlacement(db, srcReader,
+                    destReader, context, screenId, trgX, trgY, workspaceDiff, preservePages);
             screenId++;
         }
 
@@ -365,144 +345,88 @@
         return validPackages;
     }
 
-    protected static class GridPlacementSolution {
-
-        private final SQLiteDatabase mDb;
-        private final DbReader mSrcReader;
-        private final DbReader mDestReader;
-        private final Context mContext;
-        private final GridOccupancy mOccupied;
-        private final int mScreenId;
-        private final int mTrgX;
-        private final int mTrgY;
-        private final List<DbEntry> mSortedItemsToPlace;
-        private final boolean mMatchingScreenIdOnly;
-
-        private int mNextStartX;
-        private int mNextStartY;
-
-        GridPlacementSolution(SQLiteDatabase db, DbReader srcReader, DbReader destReader,
-                Context context, int screenId, int trgX, int trgY, List<DbEntry> sortedItemsToPlace,
-                boolean matchingScreenIdOnly) {
-            mDb = db;
-            mSrcReader = srcReader;
-            mDestReader = destReader;
-            mContext = context;
-            mOccupied = new GridOccupancy(trgX, trgY);
-            mScreenId = screenId;
-            mTrgX = trgX;
-            mTrgY = trgY;
-            mNextStartX = 0;
-            mNextStartY = mScreenId == 0 && FeatureFlags.QSB_ON_FIRST_SCREEN
-                    ? 1 /* smartspace */ : 0;
-            List<DbEntry> existedEntries = mDestReader.mWorkspaceEntriesByScreenId.get(screenId);
-            if (existedEntries != null) {
-                for (DbEntry entry : existedEntries) {
-                    mOccupied.markCells(entry, true);
-                }
-            }
-            mSortedItemsToPlace = sortedItemsToPlace;
-            mMatchingScreenIdOnly = matchingScreenIdOnly;
-        }
-
-        public void find() {
-            Iterator<DbEntry> iterator = mSortedItemsToPlace.iterator();
-            while (iterator.hasNext()) {
-                final DbEntry entry = iterator.next();
-                if (mMatchingScreenIdOnly && entry.screenId < mScreenId) continue;
-                if (mMatchingScreenIdOnly && entry.screenId > mScreenId) break;
-                if (entry.minSpanX > mTrgX || entry.minSpanY > mTrgY) {
-                    iterator.remove();
-                    continue;
-                }
-                if (findPlacement(entry)) {
-                    insertEntryInDb(mDb, mContext, entry, mSrcReader.mTableName,
-                            mDestReader.mTableName);
-                    iterator.remove();
-                }
+    private static void solveGridPlacement(@NonNull final SQLiteDatabase db,
+            @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
+            @NonNull final Context context, final int screenId, final int trgX, final int trgY,
+            @NonNull final List<DbEntry> sortedItemsToPlace, final boolean matchingScreenIdOnly) {
+        final GridOccupancy occupied = new GridOccupancy(trgX, trgY);
+        final Point trg = new Point(trgX, trgY);
+        final Point next = new Point(0, screenId == 0 && FeatureFlags.QSB_ON_FIRST_SCREEN
+                ? 1 /* smartspace */ : 0);
+        List<DbEntry> existedEntries = destReader.mWorkspaceEntriesByScreenId.get(screenId);
+        if (existedEntries != null) {
+            for (DbEntry entry : existedEntries) {
+                occupied.markCells(entry, true);
             }
         }
-
-        /**
-         * Search for the next possible placement of an icon. (mNextStartX, mNextStartY) serves as
-         * a memoization of last placement, we can start our search for next placement from there
-         * to speed up the search.
-         */
-        private boolean findPlacement(DbEntry entry) {
-            for (int y = mNextStartY; y <  mTrgY; y++) {
-                for (int x = mNextStartX; x < mTrgX; x++) {
-                    boolean fits = mOccupied.isRegionVacant(x, y, entry.spanX, entry.spanY);
-                    boolean minFits = mOccupied.isRegionVacant(x, y, entry.minSpanX,
-                            entry.minSpanY);
-                    if (minFits) {
-                        entry.spanX = entry.minSpanX;
-                        entry.spanY = entry.minSpanY;
-                    }
-                    if (fits || minFits) {
-                        entry.screenId = mScreenId;
-                        entry.cellX = x;
-                        entry.cellY = y;
-                        mOccupied.markCells(entry, true);
-                        mNextStartX = x + entry.spanX;
-                        mNextStartY = y;
-                        return true;
-                    }
-                }
-                mNextStartX = 0;
+        Iterator<DbEntry> iterator = sortedItemsToPlace.iterator();
+        while (iterator.hasNext()) {
+            final DbEntry entry = iterator.next();
+            if (matchingScreenIdOnly && entry.screenId < screenId) continue;
+            if (matchingScreenIdOnly && entry.screenId > screenId) break;
+            if (entry.minSpanX > trgX || entry.minSpanY > trgY) {
+                iterator.remove();
+                continue;
             }
-            return false;
+            if (findPlacementForEntry(entry, next, trg, occupied, screenId)) {
+                insertEntryInDb(db, context, entry, srcReader.mTableName, destReader.mTableName);
+                iterator.remove();
+            }
         }
     }
 
-    protected static class HotseatPlacementSolution {
-
-        private final SQLiteDatabase mDb;
-        private final DbReader mSrcReader;
-        private final DbReader mDestReader;
-        private final Context mContext;
-        private final HotseatOccupancy mOccupied;
-        private final List<DbEntry> mItemsToPlace;
-
-        HotseatPlacementSolution(SQLiteDatabase db, DbReader srcReader, DbReader destReader,
-                Context context, int hotseatSize, List<DbEntry> placedHotseatItems,
-                List<DbEntry> itemsToPlace) {
-            mDb = db;
-            mSrcReader = srcReader;
-            mDestReader = destReader;
-            mContext = context;
-            mOccupied = new HotseatOccupancy(hotseatSize);
-            for (DbEntry entry : placedHotseatItems) {
-                mOccupied.markCells(entry, true);
-            }
-            mItemsToPlace = itemsToPlace;
-        }
-
-        public void find() {
-            for (int i = 0; i < mOccupied.mCells.length; i++) {
-                if (!mOccupied.mCells[i] && !mItemsToPlace.isEmpty()) {
-                    DbEntry entry = mItemsToPlace.remove(0);
-                    entry.screenId = i;
-                    // These values does not affect the item position, but we should set them
-                    // to something other than -1.
-                    entry.cellX = i;
-                    entry.cellY = 0;
-                    insertEntryInDb(mDb, mContext, entry, mSrcReader.mTableName,
-                            mDestReader.mTableName);
-                    mOccupied.markCells(entry, true);
+    /**
+     * Search for the next possible placement of an icon. (mNextStartX, mNextStartY) serves as
+     * a memoization of last placement, we can start our search for next placement from there
+     * to speed up the search.
+     */
+    private static boolean findPlacementForEntry(@NonNull final DbEntry entry,
+            @NonNull final Point next, @NonNull final Point trg,
+            @NonNull final GridOccupancy occupied, final int screenId) {
+        for (int y = next.y; y <  trg.y; y++) {
+            for (int x = next.x; x < trg.x; x++) {
+                boolean fits = occupied.isRegionVacant(x, y, entry.spanX, entry.spanY);
+                boolean minFits = occupied.isRegionVacant(x, y, entry.minSpanX,
+                        entry.minSpanY);
+                if (minFits) {
+                    entry.spanX = entry.minSpanX;
+                    entry.spanY = entry.minSpanY;
+                }
+                if (fits || minFits) {
+                    entry.screenId = screenId;
+                    entry.cellX = x;
+                    entry.cellY = y;
+                    occupied.markCells(entry, true);
+                    next.set(x + entry.spanX, y);
+                    return true;
                 }
             }
+            next.set(0, next.y);
+        }
+        return false;
+    }
+
+    private static void solveHotseatPlacement(@NonNull final SQLiteDatabase db,
+            @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
+            @NonNull final Context context, final int hotseatSize,
+            @NonNull final  List<DbEntry> placedHotseatItems,
+            @NonNull final List<DbEntry> itemsToPlace) {
+
+        final boolean[] occupied = new boolean[hotseatSize];
+        for (DbEntry entry : placedHotseatItems) {
+            occupied[entry.screenId] = true;
         }
 
-        private class HotseatOccupancy {
-
-            private final boolean[] mCells;
-
-            private HotseatOccupancy(int hotseatSize) {
-                mCells = new boolean[hotseatSize];
-            }
-
-            private void markCells(ItemInfo item, boolean value) {
-                mCells[item.screenId] = value;
+        for (int i = 0; i < occupied.length; i++) {
+            if (!occupied[i] && !itemsToPlace.isEmpty()) {
+                DbEntry entry = itemsToPlace.remove(0);
+                entry.screenId = i;
+                // These values does not affect the item position, but we should set them
+                // to something other than -1.
+                entry.cellX = i;
+                entry.cellY = 0;
+                insertEntryInDb(db, context, entry, srcReader.mTableName, destReader.mTableName);
+                occupied[entry.screenId] = true;
             }
         }
     }
@@ -515,8 +439,6 @@
         private final Set<String> mValidPackages;
         private int mLastScreenId = -1;
 
-        private final ArrayList<DbEntry> mHotseatEntries = new ArrayList<>();
-        private final ArrayList<DbEntry> mWorkspaceEntries = new ArrayList<>();
         private final Map<Integer, ArrayList<DbEntry>> mWorkspaceEntriesByScreenId =
                 new ArrayMap<>();
 
@@ -528,7 +450,8 @@
             mValidPackages = validPackages;
         }
 
-        protected ArrayList<DbEntry> loadHotseatEntries() {
+        protected List<DbEntry> loadHotseatEntries() {
+            final List<DbEntry> hotseatEntries = new ArrayList<>();
             Cursor c = queryWorkspace(
                     new String[]{
                             LauncherSettings.Favorites._ID,                  // 0
@@ -577,14 +500,15 @@
                     entriesToRemove.add(entry.id);
                     continue;
                 }
-                mHotseatEntries.add(entry);
+                hotseatEntries.add(entry);
             }
             removeEntryFromDb(mDb, mTableName, entriesToRemove);
             c.close();
-            return mHotseatEntries;
+            return hotseatEntries;
         }
 
-        protected ArrayList<DbEntry> loadAllWorkspaceEntries() {
+        protected List<DbEntry> loadAllWorkspaceEntries() {
+            final List<DbEntry> workspaceEntries = new ArrayList<>();
             Cursor c = queryWorkspace(
                     new String[]{
                             LauncherSettings.Favorites._ID,                  // 0
@@ -599,10 +523,6 @@
                             LauncherSettings.Favorites.APPWIDGET_ID},        // 9
                         LauncherSettings.Favorites.CONTAINER + " = "
                             + LauncherSettings.Favorites.CONTAINER_DESKTOP);
-            return loadWorkspaceEntries(c);
-        }
-
-        private ArrayList<DbEntry> loadWorkspaceEntries(Cursor c) {
             final int indexId = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
             final int indexItemType = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
             final int indexScreen = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
@@ -678,7 +598,7 @@
                     entriesToRemove.add(entry.id);
                     continue;
                 }
-                mWorkspaceEntries.add(entry);
+                workspaceEntries.add(entry);
                 if (!mWorkspaceEntriesByScreenId.containsKey(entry.screenId)) {
                     mWorkspaceEntriesByScreenId.put(entry.screenId, new ArrayList<>());
                 }
@@ -686,7 +606,7 @@
             }
             removeEntryFromDb(mDb, mTableName, entriesToRemove);
             c.close();
-            return mWorkspaceEntries;
+            return workspaceEntries;
         }
 
         private int getFolderItemsCount(DbEntry entry) {
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index b644b6b..1d6971e 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -349,7 +349,7 @@
         final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
 
         boolean clearDb = false;
-        if (!GridSizeMigrationTaskV2.migrateGridIfNeeded(context)) {
+        if (!GridSizeMigrationUtil.migrateGridIfNeeded(context)) {
             // Migration failed. Clear workspace.
             clearDb = true;
         }
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 196cc56..9a745ab 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -21,14 +21,12 @@
 import static com.android.launcher3.anim.Interpolators.ACCELERATED_EASE;
 import static com.android.launcher3.anim.Interpolators.DECELERATED_EASE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_LOCAL_COLOR_POPUPS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Color;
@@ -36,15 +34,12 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
-import android.os.Build;
 import android.util.AttributeSet;
 import android.util.Pair;
-import android.util.SparseIntArray;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
@@ -52,21 +47,17 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.launcher3.widget.LocalColorExtractor;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.List;
 
 /**
  * A container for shortcuts to deep links and notifications associated with an app.
@@ -89,10 +80,6 @@
     protected int CLOSE_CHILD_FADE_START_DELAY = 0;
     protected int CLOSE_CHILD_FADE_DURATION = 140;
 
-    // Index used to get background color when using local wallpaper color extraction,
-    private static final int DARK_COLOR_EXTRACTION_INDEX = android.R.color.system_neutral2_800;
-    private static final int LIGHT_COLOR_EXTRACTION_INDEX = android.R.color.system_accent2_50;
-
     protected final Rect mTempRect = new Rect();
 
     protected final LayoutInflater mInflater;
@@ -124,10 +111,8 @@
 
     // The rect string of the view that the arrow is attached to, in screen reference frame.
     protected int mArrowColor;
-    protected final List<LocalColorExtractor> mColorExtractors;
 
     protected final float mElevation;
-    private final int mBackgroundColor;
 
     private final String mIterateChildrenTag;
 
@@ -140,8 +125,8 @@
         mActivityContext = ActivityContext.lookupContext(context);
         mIsRtl = Utilities.isRtl(getResources());
 
-        mBackgroundColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
-        mArrowColor = mBackgroundColor;
+        int backgroundColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
+        mArrowColor = backgroundColor;
         mElevation = getResources().getDimension(R.dimen.deep_shortcuts_elevation);
 
         // Initialize arrow view
@@ -158,25 +143,18 @@
 
         int smallerRadius = resources.getDimensionPixelSize(R.dimen.popup_smaller_radius);
         mRoundedTop = new GradientDrawable();
-        mRoundedTop.setColor(mBackgroundColor);
+        mRoundedTop.setColor(backgroundColor);
         mRoundedTop.setCornerRadii(new float[] { mOutlineRadius, mOutlineRadius, mOutlineRadius,
                 mOutlineRadius, smallerRadius, smallerRadius, smallerRadius, smallerRadius});
 
         mRoundedBottom = new GradientDrawable();
-        mRoundedBottom.setColor(mBackgroundColor);
+        mRoundedBottom.setColor(backgroundColor);
         mRoundedBottom.setCornerRadii(new float[] { smallerRadius, smallerRadius, smallerRadius,
                 smallerRadius, mOutlineRadius, mOutlineRadius, mOutlineRadius, mOutlineRadius});
 
         mIterateChildrenTag = getContext().getString(R.string.popup_container_iterate_children);
 
-        boolean shouldUseColorExtraction = mActivityContext.shouldUseColorExtractionForPopup();
-        if (shouldUseColorExtraction && Utilities.ATLEAST_S && ENABLE_LOCAL_COLOR_POPUPS.get()) {
-            mColorExtractors = new ArrayList<>();
-        } else {
-            mColorExtractors = null;
-        }
-
-        if (shouldUseColorExtraction) {
+        if (mActivityContext.shouldUseColorExtractionForPopup()) {
             mColorIds = new int[]{R.color.popup_shade_first, R.color.popup_shade_second,
                     R.color.popup_shade_third};
         } else {
@@ -220,11 +198,6 @@
     }
 
     /**
-     * Called when all view inflation and reordering in complete.
-     */
-    protected void onInflationComplete(boolean isReversed) { }
-
-    /**
      * Set the margins and radius of backgrounds after views are properly ordered.
      */
     public void assignMarginsAndBackgrounds(ViewGroup viewGroup) {
@@ -271,13 +244,9 @@
                     backgroundColor = colors[numVisibleChild % colors.length];
                 }
 
-                if (!ENABLE_LOCAL_COLOR_POPUPS.get()) {
-                    // Arrow color matches the first child or the last child.
-                    if (!mIsAboveIcon && numVisibleChild == 0 && viewGroup == this) {
-                        mArrowColor = backgroundColor;
-                    } else if (mIsAboveIcon) {
-                        mArrowColor = backgroundColor;
-                    }
+                // Arrow color matches the first child or the last child.
+                if (mIsAboveIcon || (numVisibleChild == 0 && viewGroup == this)) {
+                    mArrowColor = backgroundColor;
                 }
 
                 if (view instanceof ViewGroup && mIterateChildrenTag.equals(view.getTag())) {
@@ -301,10 +270,7 @@
                     }
                 }
 
-                if (!ENABLE_LOCAL_COLOR_POPUPS.get()) {
-                    setChildColor(view, backgroundColor, colorAnimator);
-                }
-
+                setChildColor(view, backgroundColor, colorAnimator);
                 numVisibleChild++;
             }
         }
@@ -320,85 +286,6 @@
         return view instanceof DeepShortcutView;
     }
 
-    @TargetApi(Build.VERSION_CODES.S)
-    private int getExtractedColor(SparseIntArray colors) {
-        int index = Utilities.isDarkTheme(getContext())
-                ? DARK_COLOR_EXTRACTION_INDEX
-                : LIGHT_COLOR_EXTRACTION_INDEX;
-        return colors.get(index, mBackgroundColor);
-    }
-
-    protected void addPreDrawForColorExtraction(Launcher launcher) {
-        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
-            @Override
-            public boolean onPreDraw() {
-                getViewTreeObserver().removeOnPreDrawListener(this);
-                initColorExtractionLocations(launcher);
-                return true;
-            }
-        });
-    }
-
-    /**
-     * Returns list of child views that will receive local color extraction treatment.
-     * Note: Order should match the view hierarchy.
-     */
-    protected List<View> getChildrenForColorExtraction() {
-        return Collections.emptyList();
-    }
-
-    private void initColorExtractionLocations(Launcher launcher) {
-        if (mColorExtractors == null) {
-            return;
-        }
-        Workspace<?> workspace = launcher.getWorkspace();
-        if (workspace == null) {
-            return;
-        }
-
-        boolean firstVisibleChild = true;
-        int screenId = workspace.getScreenIdForPageIndex(workspace.getCurrentPage());
-        DragLayer dragLayer = launcher.getDragLayer();
-
-        final View[] viewAlignedWithArrow = new View[1];
-
-        // Order matters here, since we need the arrow to match the color of its adjacent view.
-        for (final View view : getChildrenForColorExtraction()) {
-            if (view != null && view.getVisibility() == VISIBLE) {
-                Rect pos = new Rect();
-                dragLayer.getDescendantRectRelativeToSelf(view, pos);
-                if (!pos.isEmpty()) {
-                    LocalColorExtractor extractor = LocalColorExtractor.newInstance(launcher);
-                    extractor.setWorkspaceLocation(pos, dragLayer, screenId);
-                    extractor.setListener(extractedColors -> {
-                        AnimatorSet colors = new AnimatorSet();
-                        int newColor = getExtractedColor(extractedColors);
-                        setChildColor(view, newColor, colors);
-                        int numChildren = view instanceof ViewGroup
-                                ? ((ViewGroup) view).getChildCount() : 0;
-                        for (int i = 0; i < numChildren; ++i) {
-                            View childView = ((ViewGroup) view).getChildAt(i);
-                            setChildColor(childView, newColor, colors);
-                        }
-                        if (viewAlignedWithArrow[0] == view) {
-                            mArrowColor = newColor;
-                            updateArrowColor();
-                        }
-                        colors.setDuration(150);
-                        view.post(colors::start);
-                    });
-                    mColorExtractors.add(extractor);
-
-                    if (mIsAboveIcon || firstVisibleChild) {
-                        viewAlignedWithArrow[0] = view;
-                    }
-                    firstVisibleChild = false;
-                }
-            }
-        }
-
-    }
-
     /**
      * Sets the background color of the child.
      */
@@ -425,7 +312,6 @@
         if (reverseOrder) {
             reverseOrder(viewsToFlip);
         }
-        onInflationComplete(reverseOrder);
         assignMarginsAndBackgrounds(this);
         if (shouldAddArrow()) {
             addArrow();
@@ -438,7 +324,6 @@
      */
     public void show() {
         setupForDisplay();
-        onInflationComplete(false);
         assignMarginsAndBackgrounds(this);
         if (shouldAddArrow()) {
             addArrow();
@@ -819,9 +704,6 @@
         if (mOnCloseCallback != null) {
             mOnCloseCallback.run();
         }
-        if (mColorExtractors != null) {
-            mColorExtractors.forEach(e -> e.setListener(null));
-        }
     }
 
     /**
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 8e7a10c..4da588e 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -69,7 +69,6 @@
 import com.android.launcher3.views.BaseDragLayer;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -236,13 +235,6 @@
         mPopupItemDragHandler = new LauncherPopupItemDragHandler(launcher, this);
         mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(launcher);
         launcher.getDragController().addDragListener(this);
-        addPreDrawForColorExtraction(launcher);
-    }
-
-    @Override
-    protected List<View> getChildrenForColorExtraction() {
-        return Arrays.asList(mSystemShortcutContainer, mWidgetContainer, mDeepShortcutContainer,
-                mNotificationContainer);
     }
 
     private void initializeSystemShortcuts(List<SystemShortcut> shortcuts) {
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index b3c376f..622516f 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -139,14 +139,13 @@
         mTargetRect.roundOut(outPos);
     }
 
-    public static OptionsPopupView show(
-            Launcher launcher, RectF targetRect, List<OptionItem> items, boolean shouldAddArrow) {
+    public static OptionsPopupView show(AppLauncher launcher, RectF targetRect,
+            List<OptionItem> items, boolean shouldAddArrow) {
         return show(launcher, targetRect, items, shouldAddArrow, 0 /* width */);
     }
 
-    public static OptionsPopupView show(
-            Launcher launcher, RectF targetRect, List<OptionItem> items, boolean shouldAddArrow,
-            int width) {
+    public static OptionsPopupView show(AppLauncher launcher, RectF targetRect,
+            List<OptionItem> items, boolean shouldAddArrow, int width) {
         OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater()
                 .inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false);
         popup.mTargetRect = targetRect;
@@ -165,21 +164,10 @@
             popup.mItemMap.put(view, item);
         }
 
-        popup.addPreDrawForColorExtraction(launcher);
         popup.show();
         return popup;
     }
 
-    @Override
-    protected List<View> getChildrenForColorExtraction() {
-        int childCount = getChildCount();
-        ArrayList<View> children = new ArrayList<>(childCount);
-        for (int i = 0; i < childCount; ++i) {
-            children.add(getChildAt(i));
-        }
-        return children;
-    }
-
     /**
      * Returns the list of supported actions
      */
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.kt b/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
similarity index 89%
rename from tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.kt
rename to tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
index 90d7b43..85d7bf9 100644
--- a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.kt
+++ b/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
@@ -26,7 +26,7 @@
 import com.android.launcher3.LauncherFiles
 import com.android.launcher3.LauncherSettings.Favorites.*
 import com.android.launcher3.config.FeatureFlags
-import com.android.launcher3.model.GridSizeMigrationTaskV2.DbReader
+import com.android.launcher3.model.GridSizeMigrationUtil.DbReader
 import com.android.launcher3.pm.UserCache
 import com.android.launcher3.provider.LauncherDbUtils
 import com.android.launcher3.util.LauncherModelHelper
@@ -37,10 +37,10 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-/** Unit tests for [GridSizeMigrationTaskV2]  */
+/** Unit tests for [GridSizeMigrationUtil]  */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class GridSizeMigrationTaskV2Test {
+class GridSizeMigrationUtilTest {
     private lateinit var modelHelper: LauncherModelHelper
     private lateinit var context: Context
     private lateinit var db: SQLiteDatabase
@@ -122,15 +122,16 @@
         idp.numRows = 4
         val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
         val destReader = DbReader(db, TABLE_NAME, context, validPackages)
-        val task = GridSizeMigrationTaskV2(
-            context,
-            db,
-            srcReader,
-            destReader,
-            idp.numDatabaseHotseatIcons,
-            Point(idp.numColumns, idp.numRows)
+        GridSizeMigrationUtil.migrate(
+                context,
+                db,
+                srcReader,
+                destReader,
+                idp.numDatabaseHotseatIcons,
+                Point(idp.numColumns, idp.numRows),
+                DeviceGridState(context),
+                DeviceGridState(idp)
         )
-        task.migrate(DeviceGridState(context), DeviceGridState(idp))
 
         // Check hotseat items
         var c = context.contentResolver.query(
@@ -207,15 +208,16 @@
         idp.numRows = 4
         val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
         val destReader = DbReader(db, TABLE_NAME, context, validPackages)
-        val task = GridSizeMigrationTaskV2(
-            context,
-            db,
-            srcReader,
-            destReader,
-            idp.numDatabaseHotseatIcons,
-            Point(idp.numColumns, idp.numRows)
+        GridSizeMigrationUtil.migrate(
+                context,
+                db,
+                srcReader,
+                destReader,
+                idp.numDatabaseHotseatIcons,
+                Point(idp.numColumns, idp.numRows),
+                DeviceGridState(context),
+                DeviceGridState(idp)
         )
-        task.migrate(DeviceGridState(context), DeviceGridState(idp))
 
         // Check hotseat items
         val c = context.contentResolver.query(
@@ -262,15 +264,16 @@
         idp.numRows = 4
         val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
         val destReader = DbReader(db, TABLE_NAME, context, validPackages)
-        val task = GridSizeMigrationTaskV2(
-            context,
-            db,
-            srcReader,
-            destReader,
-            idp.numDatabaseHotseatIcons,
-            Point(idp.numColumns, idp.numRows)
+        GridSizeMigrationUtil.migrate(
+                context,
+                db,
+                srcReader,
+                destReader,
+                idp.numDatabaseHotseatIcons,
+                Point(idp.numColumns, idp.numRows),
+                DeviceGridState(context),
+                DeviceGridState(idp)
         )
-        task.migrate(DeviceGridState(context), DeviceGridState(idp))
 
         // Check hotseat items
         val c = context.contentResolver.query(
@@ -327,15 +330,16 @@
 
         val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
         val destReader = DbReader(db, TABLE_NAME, context, validPackages)
-        val task = GridSizeMigrationTaskV2(
-            context,
-            db,
-            srcReader,
-            destReader,
-            idp.numDatabaseHotseatIcons,
-            Point(idp.numColumns, idp.numRows)
+        GridSizeMigrationUtil.migrate(
+                context,
+                db,
+                srcReader,
+                destReader,
+                idp.numDatabaseHotseatIcons,
+                Point(idp.numColumns, idp.numRows),
+                DeviceGridState(context),
+                DeviceGridState(idp)
         )
-        task.migrate(DeviceGridState(context), DeviceGridState(idp))
 
         // Get workspace items
         val c = context.contentResolver.query(
@@ -387,15 +391,16 @@
         idp.numRows = 5
         val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
         val destReader = DbReader(db, TABLE_NAME, context, validPackages)
-        val task = GridSizeMigrationTaskV2(
-            context,
-            db,
-            srcReader,
-            destReader,
-            idp.numDatabaseHotseatIcons,
-            Point(idp.numColumns, idp.numRows)
+        GridSizeMigrationUtil.migrate(
+                context,
+                db,
+                srcReader,
+                destReader,
+                idp.numDatabaseHotseatIcons,
+                Point(idp.numColumns, idp.numRows),
+                DeviceGridState(context),
+                DeviceGridState(idp)
         )
-        task.migrate(DeviceGridState(context), DeviceGridState(idp))
 
         // Get workspace items
         val c = context.contentResolver.query(
@@ -448,15 +453,16 @@
         idp.numRows = 4
         val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
         val destReader = DbReader(db, TABLE_NAME, context, validPackages)
-        val task = GridSizeMigrationTaskV2(
-            context,
-            db,
-            srcReader,
-            destReader,
-            idp.numDatabaseHotseatIcons,
-            Point(idp.numColumns, idp.numRows)
+        GridSizeMigrationUtil.migrate(
+                context,
+                db,
+                srcReader,
+                destReader,
+                idp.numDatabaseHotseatIcons,
+                Point(idp.numColumns, idp.numRows),
+                DeviceGridState(context),
+                DeviceGridState(idp)
         )
-        task.migrate(DeviceGridState(context), DeviceGridState(idp))
 
         // Get workspace items
         val c = context.contentResolver.query(