Merge "Add predictions row to taskbar all apps."
diff --git a/Android.bp b/Android.bp
index 7c78ba8..f5a38b4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -36,6 +36,7 @@
         "tests/tapl/**/*.java",
         "src/com/android/launcher3/ResourceUtils.java",
         "src/com/android/launcher3/testing/TestProtocol.java",
+        "src/com/android/launcher3/testing/*Request.java",
     ],
     resource_dirs: [ ],
     manifest: "tests/tapl/AndroidManifest.xml",
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index 0f61d14..02206c0 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -27,6 +27,7 @@
 import android.view.View;
 
 import androidx.annotation.Keep;
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
@@ -124,7 +125,7 @@
     }
 
     @Override
-    public Bundle call(String method, String arg) {
+    public Bundle call(String method, String arg, @Nullable Bundle extras) {
         final Bundle response = new Bundle();
         switch (method) {
             case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
@@ -219,7 +220,7 @@
             }
 
             default:
-                return super.call(method, arg);
+                return super.call(method, arg, extras);
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 650d4ce..7ab59b8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -176,7 +176,7 @@
 
         boolean isThreeButtonNav = mContext.isThreeButtonNav();
         mIsImeRenderingNavButtons =
-                InputMethodService.canImeRenderGesturalNavButtons() && mContext.isGestureNav();
+                InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
         if (!mIsImeRenderingNavButtons) {
             // IME switcher
             View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 70f0780..fe091ef 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -123,6 +123,7 @@
     private int mLastRequestedNonFullscreenHeight;
 
     private final SysUINavigationMode.Mode mNavMode;
+    private final boolean mImeDrawsImeNavBar;
     private final ViewCache mViewCache = new ViewCache();
 
     private final boolean mIsSafeModeEnabled;
@@ -144,6 +145,7 @@
         mOnboardingPrefs = new OnboardingPrefs<>(this, Utilities.getPrefs(this));
 
         mNavMode = SysUINavigationMode.getMode(windowContext);
+        mImeDrawsImeNavBar = SysUINavigationMode.getImeDrawsImeNavBar(windowContext);
         mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
                 () -> getPackageManager().isSafeMode());
         mIsUserSetupComplete = SettingsCache.INSTANCE.get(this).getValue(
@@ -282,6 +284,10 @@
         return mNavMode == Mode.NO_BUTTON;
     }
 
+    public boolean imeDrawsImeNavBar() {
+        return mImeDrawsImeNavBar;
+    }
+
     public int getLeftCornerRadius() {
         return mLeftCorner == null ? 0 : mLeftCorner.getRadius();
     }
@@ -758,6 +764,8 @@
         pw.println(String.format(
                 "%s\tmNavMode=%s", prefix, mNavMode));
         pw.println(String.format(
+                "%s\tmImeDrawsImeNavBar=%b", prefix, mImeDrawsImeNavBar));
+        pw.println(String.format(
                 "%s\tmIsUserSetupComplete=%b", prefix, mIsUserSetupComplete));
         pw.println(String.format(
                 "%s\tmWindowLayoutParams.height=%dpx", prefix, mWindowLayoutParams.height));
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index ae2583b..4c570f1 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -5,6 +5,8 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.testing.TestInformationHandler;
 import com.android.launcher3.testing.TestProtocol;
@@ -21,7 +23,7 @@
     }
 
     @Override
-    public Bundle call(String method, String arg) {
+    public Bundle call(String method, String arg, @Nullable Bundle extras) {
         final Bundle response = new Bundle();
         switch (method) {
             case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
@@ -82,7 +84,7 @@
             }
         }
 
-        return super.call(method, arg);
+        return super.call(method, arg, extras);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index 53db4cc..406414c 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -60,17 +60,22 @@
         return INSTANCE.get(context).getMode();
     }
 
+    public static boolean getImeDrawsImeNavBar(Context context) {
+        return INSTANCE.get(context).getImeDrawsImeNavBar();
+    }
+
     public static final MainThreadInitializedObject<SysUINavigationMode> INSTANCE =
             new MainThreadInitializedObject<>(SysUINavigationMode::new);
 
     private static final String TAG = "SysUINavigationMode";
     private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
-    private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
-            "config_navBarInteractionMode";
+    private static final String NAV_BAR_INTERACTION_MODE_RES_NAME = "config_navBarInteractionMode";
+    private static final String IME_DRAWS_IME_NAV_BAR_RES_NAME = "config_imeDrawsImeNavBar";
     private static final String TARGET_OVERLAY_PACKAGE = "android";
 
     private final Context mContext;
     private Mode mMode;
+    private boolean mImeDrawsImeNavBar;
 
     private int mNavBarGesturalHeight;
     private int mNavBarLargerGesturalHeight;
@@ -135,6 +140,8 @@
         mNavBarLargerGesturalHeight = ResourceUtils.getDimenByName(
                 ResourceUtils.NAVBAR_BOTTOM_GESTURE_LARGER_SIZE, mContext.getResources(),
                 mNavBarGesturalHeight);
+        mImeDrawsImeNavBar = ResourceUtils.getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME,
+                mContext.getResources(), false);
 
         if (modeInt == INVALID_RESOURCE_HANDLE) {
             Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
@@ -167,9 +174,14 @@
         return mMode;
     }
 
+    public boolean getImeDrawsImeNavBar() {
+        return mImeDrawsImeNavBar;
+    }
+
     public void dump(PrintWriter pw) {
         pw.println("SysUINavigationMode:");
         pw.println("  mode=" + mMode.name());
+        pw.println("  mImeDrawsImeNavBar=:" + mImeDrawsImeNavBar);
         pw.println("  mNavBarGesturalHeight=:" + mNavBarGesturalHeight);
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
index 6b6bd6a..333df10 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -15,9 +15,14 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_UNFOLD_ANIMATION;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.Utilities.comp;
 
 import android.annotation.Nullable;
+import android.util.FloatProperty;
+import android.util.MathUtils;
+import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
@@ -39,6 +44,8 @@
     // Percentage of the width of the quick search bar that will be reduced
     // from the both sides of the bar when progress is 0
     private static final float MAX_WIDTH_INSET_FRACTION = 0.15f;
+    private static final FloatProperty<View> UNFOLD_SCALE_PROPERTY =
+            SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_UNFOLD_ANIMATION);
 
     private final Launcher mLauncher;
 
@@ -62,6 +69,8 @@
         // Animated in all orientations
         mProgressProvider.addCallback(new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
                 windowManager));
+        mProgressProvider
+                .addCallback(new LauncherScaleAnimationListener());
 
         // Animated only in natural orientation
         mNaturalOrientationProgressProvider
@@ -120,4 +129,26 @@
             }
         }
     }
+
+    private class LauncherScaleAnimationListener implements TransitionProgressListener {
+
+        @Override
+        public void onTransitionStarted() {
+        }
+
+        @Override
+        public void onTransitionFinished() {
+            setScale(1);
+        }
+
+        @Override
+        public void onTransitionProgress(float progress) {
+            setScale(MathUtils.constrainedMap(0.85f, 1, 0, 1, progress));
+        }
+
+        private void setScale(float value) {
+            UNFOLD_SCALE_PROPERTY.setValue(mLauncher.getWorkspace(), value);
+            UNFOLD_SCALE_PROPERTY.setValue(mLauncher.getHotseat(), value);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
index 7ae6cb7..8659b68 100644
--- a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
+++ b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
@@ -15,7 +15,8 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_REVEAL_ANIM;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
@@ -27,6 +28,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.util.FloatProperty;
 import android.view.View;
 
 import com.android.launcher3.BaseQuickstepLauncher;
@@ -49,6 +51,8 @@
 
     // Should be used for animations running alongside this WorkspaceRevealAnim.
     public static final int DURATION_MS = 350;
+    private static final FloatProperty<View> REVEAL_SCALE_PROPERTY =
+            SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_REVEAL_ANIM);
 
     private final float mScaleStart;
     private final AnimatorSet mAnimators = new AnimatorSet();
@@ -90,7 +94,7 @@
     }
 
     private void addRevealAnimatorsForView(View v) {
-        ObjectAnimator scale = ObjectAnimator.ofFloat(v, SCALE_PROPERTY, mScaleStart, 1f);
+        ObjectAnimator scale = ObjectAnimator.ofFloat(v, REVEAL_SCALE_PROPERTY, mScaleStart, 1f);
         scale.setDuration(DURATION_MS);
         scale.setInterpolator(Interpolators.DECELERATED_EASE);
         mAnimators.play(scale);
@@ -103,7 +107,7 @@
         mAnimators.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                SCALE_PROPERTY.set(v, 1f);
+                REVEAL_SCALE_PROPERTY.set(v, 1f);
                 v.setAlpha(1f);
             }
         });
diff --git a/quickstep/src/com/android/quickstep/util/WorkspaceUnlockAnim.java b/quickstep/src/com/android/quickstep/util/WorkspaceUnlockAnim.java
index b01447e..aa3f0d7 100644
--- a/quickstep/src/com/android/quickstep/util/WorkspaceUnlockAnim.java
+++ b/quickstep/src/com/android/quickstep/util/WorkspaceUnlockAnim.java
@@ -21,6 +21,7 @@
 /**
  * Animation to animate in a workspace during the unlock transition.
  */
+// TODO(b/219444608): use SCALE_PROPERTY_FACTORY once the scale is reset to 1.0 after unlocking.
 public class WorkspaceUnlockAnim {
     /** Scale for the workspace icons at the beginning of the animation. */
     private static final float START_SCALE = 0.9f;
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index b56c012..4300392 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -27,6 +27,8 @@
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
 
+import com.android.launcher3.util.MultiScalePropertyFactory;
+
 public class LauncherAnimUtils {
     /**
      * Durations for various state animations. These are not defined in resources to allow
@@ -64,6 +66,25 @@
                 }
             };
 
+    /**
+     * Property to set the scale of workspace and hotseat. The value is based on a combination
+     * of all the ones set, to have a smooth experience even in the case of overlapping scaling
+     * animation.
+     */
+    public static final MultiScalePropertyFactory<View> SCALE_PROPERTY_FACTORY =
+            new MultiScalePropertyFactory<View>("scale_property") {
+                @Override
+                protected void apply(View view, float scale) {
+                    view.setScaleX(scale);
+                    view.setScaleY(scale);
+                }
+            };
+
+    public static final int SCALE_INDEX_UNFOLD_ANIMATION = 1;
+    public static final int SCALE_INDEX_UNLOCK_ANIMATION = 2;
+    public static final int SCALE_INDEX_WORKSPACE_STATE = 3;
+    public static final int SCALE_INDEX_REVEAL_ANIM = 4;
+
     /** Increase the duration if we prevented the fling, as we are going against a high velocity. */
     public static int blockedFlingDurationFactor(float velocity) {
         return (int) Utilities.boundToRange(Math.abs(velocity) / 2, 2f, 6f);
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 1b9647a..98e785f 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -18,7 +18,8 @@
 
 import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WORKSPACE_STATE;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
@@ -42,6 +43,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM;
 
 import android.animation.ValueAnimator;
+import android.util.FloatProperty;
 import android.view.View;
 import android.view.animation.Interpolator;
 
@@ -62,6 +64,9 @@
  */
 public class WorkspaceStateTransitionAnimation {
 
+    private static final FloatProperty<View> WORKSPACE_STATE_SCALE_PROPERTY =
+            SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WORKSPACE_STATE);
+
     private final Launcher mLauncher;
     private final Workspace mWorkspace;
 
@@ -117,7 +122,8 @@
             ((PendingAnimation) propertySetter).add(getSpringScaleAnimator(mLauncher,
                     mWorkspace, mNewScale));
         } else {
-            propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
+            propertySetter.setFloat(mWorkspace, WORKSPACE_STATE_SCALE_PROPERTY, mNewScale,
+                    scaleInterpolator);
         }
 
         mWorkspace.setPivotToScaleWithSelf(hotseat);
@@ -128,7 +134,7 @@
         } else {
             Interpolator hotseatScaleInterpolator = config.getInterpolator(ANIM_HOTSEAT_SCALE,
                     scaleInterpolator);
-            propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale,
+            propertySetter.setFloat(hotseat, WORKSPACE_STATE_SCALE_PROPERTY, hotseatScale,
                     hotseatScaleInterpolator);
         }
 
@@ -205,9 +211,9 @@
                 .setDampingRatio(damping)
                 .setMinimumVisibleChange(MIN_VISIBLE_CHANGE_SCALE)
                 .setEndValue(scale)
-                .setStartValue(SCALE_PROPERTY.get(v))
+                .setStartValue(WORKSPACE_STATE_SCALE_PROPERTY.get(v))
                 .setStartVelocity(velocityPxPerS)
-                .build(v, SCALE_PROPERTY);
+                .build(v, WORKSPACE_STATE_SCALE_PROPERTY);
 
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 8ebfd62..2eae99a 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -23,16 +23,23 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Insets;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
 import android.view.WindowInsets;
 
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.ResourceBasedOverride;
 import com.android.launcher3.widget.picker.WidgetsFullSheet;
 
@@ -62,12 +69,18 @@
         mLauncherAppState = LauncherAppState.getInstanceNoCreate();
     }
 
-    public Bundle call(String method) {
-        return call(method, /*arg=*/ null);
-    }
-
-    public Bundle call(String method, String arg) {
+    /**
+     * handle a request and return result Bundle.
+     *
+     * @param method request name.
+     * @param arg    optional single string argument.
+     * @param extra  extra request payload.
+     */
+    public Bundle call(String method, String arg, @Nullable Bundle extra) {
         final Bundle response = new Bundle();
+        if (extra != null && extra.getClassLoader() == null) {
+            extra.setClassLoader(getClass().getClassLoader());
+        }
         switch (method) {
             case TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT: {
                 return getLauncherUIProperty(Bundle::putInt, l -> {
@@ -163,11 +176,48 @@
                                 .forceAllowRotationForTesting(Boolean.parseBoolean(arg)));
                 return null;
 
+            case TestProtocol.REQUEST_WORKSPACE_CELL_LAYOUT_SIZE:
+                return getLauncherUIProperty(Bundle::putIntArray, launcher -> {
+                    final Workspace workspace = launcher.getWorkspace();
+                    final int screenId = workspace.getScreenIdForPageIndex(
+                            workspace.getCurrentPage());
+                    final CellLayout cellLayout = workspace.getScreenWithId(screenId);
+                    return new int[]{cellLayout.getCountX(), cellLayout.getCountY()};
+                });
+
+            case TestProtocol.REQUEST_WORKSPACE_CELL_CENTER:
+                final WorkspaceCellCenterRequest request = extra.getParcelable(
+                        TestProtocol.TEST_INFO_REQUEST_FIELD);
+                return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
+                    final Workspace workspace = launcher.getWorkspace();
+                    // TODO(b/216387249): allow caller selecting different pages.
+                    CellLayout cellLayout = (CellLayout) workspace.getPageAt(
+                            workspace.getCurrentPage());
+                    final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher,
+                            cellLayout, request.cellX, request.cellY, request.spanX, request.spanY);
+                    return new Point(cellRect.centerX(), cellRect.centerY());
+                });
+
             default:
                 return null;
         }
     }
 
+    private static Rect getDescendantRectRelativeToDragLayerForCell(Launcher launcher,
+            CellLayout cellLayout, int cellX, int cellY, int spanX, int spanY) {
+        final DragLayer dragLayer = launcher.getDragLayer();
+        final Rect target = new Rect();
+
+        cellLayout.cellToRect(cellX, cellY, spanX, spanY, target);
+        int[] leftTop = {target.left, target.top};
+        int[] rightBottom = {target.right, target.bottom};
+        dragLayer.getDescendantCoordRelativeToSelf(cellLayout, leftTop);
+        dragLayer.getDescendantCoordRelativeToSelf(cellLayout, rightBottom);
+
+        target.set(leftTop[0], leftTop[1], rightBottom[0], rightBottom[1]);
+        return target;
+    }
+
     protected boolean isLauncherInitialized() {
         return Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null
                 || LauncherAppState.getInstance(mContext).getModel().isModelLoaded();
diff --git a/src/com/android/launcher3/testing/TestInformationProvider.java b/src/com/android/launcher3/testing/TestInformationProvider.java
index 4f2619c..bcc7c2d 100644
--- a/src/com/android/launcher3/testing/TestInformationProvider.java
+++ b/src/com/android/launcher3/testing/TestInformationProvider.java
@@ -60,7 +60,7 @@
         if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
             TestInformationHandler handler = TestInformationHandler.newInstance(getContext());
             handler.init(getContext());
-            return handler.call(method, arg);
+            return handler.call(method, arg, extras);
         }
         return null;
     }
diff --git a/src/com/android/launcher3/testing/TestInformationRequest.java b/src/com/android/launcher3/testing/TestInformationRequest.java
new file mode 100644
index 0000000..272ae56
--- /dev/null
+++ b/src/com/android/launcher3/testing/TestInformationRequest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 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.testing;
+
+import android.os.Parcelable;
+
+/**
+ * A Request sent to TestInformationHandler can implement this interface to carry more information.
+ */
+public interface TestInformationRequest extends Parcelable {
+    /**
+     * The name for handler to dispatch request.
+     */
+    String getRequestName();
+}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 78e9fe8..4f40567 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -68,6 +68,7 @@
         }
     }
 
+    public static final String TEST_INFO_REQUEST_FIELD = "request";
     public static final String TEST_INFO_RESPONSE_FIELD = "response";
 
     public static final String REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT =
@@ -104,6 +105,10 @@
     public static final String REQUEST_GET_ACTIVITIES_CREATED_COUNT =
             "get-activities-created-count";
     public static final String REQUEST_GET_ACTIVITIES = "get-activities";
+
+    public static final String REQUEST_WORKSPACE_CELL_LAYOUT_SIZE = "workspace-cell-layout-size";
+    public static final String REQUEST_WORKSPACE_CELL_CENTER = "workspace-cell-center";
+
     public static final String REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET =
             "get-focused-task-height-for-tablet";
     public static final String REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET =
diff --git a/src/com/android/launcher3/testing/WorkspaceCellCenterRequest.java b/src/com/android/launcher3/testing/WorkspaceCellCenterRequest.java
new file mode 100644
index 0000000..71ab09f
--- /dev/null
+++ b/src/com/android/launcher3/testing/WorkspaceCellCenterRequest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 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.testing;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Request object for querying a workspace cell region in Rect.
+ */
+public class WorkspaceCellCenterRequest implements TestInformationRequest {
+    public final int cellX;
+    public final int cellY;
+    public final int spanX;
+    public final int spanY;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(cellX);
+        dest.writeInt(cellY);
+        dest.writeInt(spanX);
+        dest.writeInt(spanY);
+    }
+
+    public static final Parcelable.Creator<WorkspaceCellCenterRequest> CREATOR =
+            new Parcelable.Creator<WorkspaceCellCenterRequest>() {
+
+                @Override
+                public WorkspaceCellCenterRequest createFromParcel(Parcel source) {
+                    return new WorkspaceCellCenterRequest(source);
+                }
+
+                @Override
+                public WorkspaceCellCenterRequest[] newArray(int size) {
+                    return new WorkspaceCellCenterRequest[size];
+                }
+            };
+
+    private WorkspaceCellCenterRequest(int cellX, int cellY, int spanX, int spanY) {
+        this.cellX = cellX;
+        this.cellY = cellY;
+        this.spanX = spanX;
+        this.spanY = spanY;
+    }
+
+    private WorkspaceCellCenterRequest(Parcel in) {
+        this(in.readInt(), in.readInt(), in.readInt(), in.readInt());
+    }
+
+    /**
+     * Create a builder for WorkspaceCellRectRequest.
+     *
+     * @return WorkspaceCellRectRequest builder.
+     */
+    public static WorkspaceCellCenterRequest.Builder builder() {
+        return new WorkspaceCellCenterRequest.Builder();
+    }
+
+    @Override
+    public String getRequestName() {
+        return TestProtocol.REQUEST_WORKSPACE_CELL_CENTER;
+    }
+
+    /**
+     * WorkspaceCellRectRequest Builder.
+     */
+    public static final class Builder {
+        private int mCellX;
+        private int mCellY;
+        private int mSpanX;
+        private int mSpanY;
+
+        private Builder() {
+            this.mCellX = 0;
+            this.mCellY = 0;
+            this.mSpanX = 1;
+            this.mSpanY = 1;
+        }
+
+        /**
+         * Set X coordinate of upper left corner expressed as a cell position
+         */
+        public WorkspaceCellCenterRequest.Builder setCellX(int x) {
+            this.mCellX = x;
+            return this;
+        }
+
+        /**
+         * Set Y coordinate of upper left corner expressed as a cell position
+         */
+        public WorkspaceCellCenterRequest.Builder setCellY(int y) {
+            this.mCellY = y;
+            return this;
+        }
+
+        /**
+         * Set span Width in cells
+         */
+        public WorkspaceCellCenterRequest.Builder setSpanX(int x) {
+            this.mSpanX = x;
+            return this;
+        }
+
+        /**
+         * Set span Height in cells
+         */
+        public WorkspaceCellCenterRequest.Builder setSpanY(int y) {
+            this.mCellY = y;
+            return this;
+        }
+
+        /**
+         * build the WorkspaceCellRectRequest.
+         */
+        public WorkspaceCellCenterRequest build() {
+            return new WorkspaceCellCenterRequest(mCellX, mCellY, mSpanX, mSpanY);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/MultiScalePropertyFactory.java b/src/com/android/launcher3/util/MultiScalePropertyFactory.java
new file mode 100644
index 0000000..f27d0f0
--- /dev/null
+++ b/src/com/android/launcher3/util/MultiScalePropertyFactory.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import android.util.ArrayMap;
+import android.util.FloatProperty;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Allows to combine multiple values set by several sources.
+ *
+ * The various sources are meant to use [set], providing different `setterIndex` params. When it is
+ * not set, 0 is used. This is meant to cover the case multiple animations are going on at the same
+ * time.
+ *
+ * This class behaves similarly to [MultiValueAlpha], but is meant to be more abstract and reusable.
+ * It sets the multiplication of all values, bounded to the max and the min values.
+ *
+ * @param <T> Type where to apply the property.
+ */
+public abstract class MultiScalePropertyFactory<T> {
+
+    private final String mName;
+    private final ArrayMap<Integer, MultiScaleProperty> mProperties =
+            new ArrayMap<Integer, MultiScaleProperty>();
+
+    // This is an optimization for cases when set is called repeatedly with the same setterIndex.
+    private float mMinOfOthers = 0;
+    private float mMaxOfOthers = 0;
+    private float mMultiplicationOfOthers = 0;
+    private Integer mLastIndexSet = -1;
+    private float mLastAggregatedValue = 1.0f;
+
+    public MultiScalePropertyFactory(String name) {
+        mName = name;
+    }
+
+    /** Returns the [MultiFloatProperty] associated with [inx], creating it if not present. */
+    public MultiScaleProperty get(Integer index) {
+        return mProperties.computeIfAbsent(index,
+                (k) -> new MultiScaleProperty(index, mName + "_" + index));
+    }
+
+
+    /**
+     * Each [setValue] will be aggregated with the other properties values created by the
+     * corresponding factory.
+     */
+    class MultiScaleProperty extends FloatProperty<T> {
+        private final int mInx;
+        private float mValue = 1.0f;
+
+        MultiScaleProperty(int inx, String name) {
+            super(name);
+            mInx = inx;
+        }
+
+        @Override
+        public void setValue(T obj, float newValue) {
+            if (mLastIndexSet != mInx) {
+                mMinOfOthers = Float.MAX_VALUE;
+                mMaxOfOthers = Float.MIN_VALUE;
+                mMultiplicationOfOthers = 1.0f;
+                mProperties.forEach((key, property) -> {
+                    if (key != mInx) {
+                        mMinOfOthers = Math.min(mMinOfOthers, property.mValue);
+                        mMaxOfOthers = Math.max(mMaxOfOthers, property.mValue);
+                        mMultiplicationOfOthers *= property.mValue;
+                    }
+                });
+                mLastIndexSet = mInx;
+            }
+            float minValue = Math.min(mMinOfOthers, newValue);
+            float maxValue = Math.max(mMaxOfOthers, newValue);
+            float multValue = mMultiplicationOfOthers * newValue;
+            mLastAggregatedValue = Utilities.boundToRange(multValue, minValue, maxValue);
+            mValue = newValue;
+            apply(obj, mLastAggregatedValue);
+        }
+
+        @Override
+        public Float get(T t) {
+            return mLastAggregatedValue;
+        }
+
+        @Override
+        public String toString() {
+            return String.valueOf(mValue);
+        }
+    }
+
+    /** Applies value to object after setValue method is called. */
+    protected abstract void apply(T obj, float value);
+}
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 55524dd..106bb92 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -560,6 +560,12 @@
                 launcher, parent);
         view.recycle();
 
+        // Init properties before getting the drawable.
+        view.mIsVerticalBarLayout = launcher.getDeviceProfile().isVerticalBarLayout();
+        view.mIsOpening = isOpening;
+        view.mOriginalIcon = originalView;
+        view.mPositionOut = positionOut;
+
         // Get the drawable on the background thread
         boolean shouldLoadIcon = originalView.getTag() instanceof ItemInfo && hideOriginal;
         if (shouldLoadIcon) {
@@ -573,11 +579,6 @@
         }
         sIconLoadResult = null;
 
-        view.mIsVerticalBarLayout = launcher.getDeviceProfile().isVerticalBarLayout();
-        view.mIsOpening = isOpening;
-        view.mOriginalIcon = originalView;
-        view.mPositionOut = positionOut;
-
         // Match the position of the original view.
         view.matchPositionOf(launcher, originalView, isOpening, positionOut);
 
@@ -635,6 +636,7 @@
         mLoadIconSignal = null;
         mEndRunnable = null;
         mFinalDrawableBounds.setEmpty();
+        mIsOpening = false;
         mPositionOut = null;
         mListenerView.setListener(null);
         mOriginalIcon = null;
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 4b4f1d9..2035da1 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -24,6 +24,8 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.graphics.Point;
+
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -368,8 +370,7 @@
             AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
             allApps.freeze();
             try {
-                appIcon = allApps.getAppIcon(name);
-                appIcon.dragToWorkspace(false, false);
+                allApps.getAppIcon(name).dragToWorkspace(false, false);
             } finally {
                 allApps.unfreeze();
             }
@@ -431,7 +432,7 @@
     @PortraitLandscape
     public void testDeleteFromWorkspace() throws Exception {
         // test delete both built-in apps and user-installed app from workspace
-        for (String appName : new String[] {"Gmail", "Play Store", APP_NAME}) {
+        for (String appName : new String[]{"Gmail", "Play Store", APP_NAME}) {
             final AppIcon appIcon = createShortcutIfNotExist(appName);
             Workspace workspace = mLauncher.getWorkspace().deleteAppIcon(appIcon);
             assertNull(appName + " app was found after being deleted from workspace",
@@ -481,7 +482,39 @@
         }
     }
 
+    @Test
+    @PortraitLandscape
+    public void testDragAppIconToWorkspaceCell() throws Exception {
+        final Point dimensions = mLauncher.getWorkspace().getIconGridDimensions();
+
+        Point[] targets = {
+                new Point(0, 1),
+                new Point(0, dimensions.y - 2),
+                new Point(dimensions.x - 1, 1),
+                new Point(dimensions.x - 1, dimensions.y - 2),
+                new Point(dimensions.x / 2, dimensions.y / 2)
+        };
+
+        for (Point target : targets) {
+            final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+            allApps.freeze();
+            try {
+                allApps.getAppIcon(APP_NAME).dragToWorkspace(target.x, target.y);
+            } finally {
+                allApps.unfreeze();
+            }
+            // Reset the workspace for the next shortcut creation.
+            initialize(this);
+        }
+
+        // test to move a shortcut to other cell.
+        final AppIcon launcherTestAppIcon = createShortcutIfNotExist(APP_NAME);
+        for (Point target : targets) {
+            launcherTestAppIcon.dragToWorkspace(target.x, target.y);
+        }
+    }
+
     public static String getAppPackageName() {
         return getInstrumentation().getContext().getPackageName();
     }
-}
\ No newline at end of file
+}
diff --git a/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt b/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
new file mode 100644
index 0000000..c4a8db6
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
@@ -0,0 +1,92 @@
+package com.android.launcher3.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Unit tests for [MultiScalePropertyFactory] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MultiScalePropertyTest {
+
+    private val received = mutableListOf<Float>()
+
+    private val factory =
+        object : MultiScalePropertyFactory<Int?>("Test") {
+            override fun apply(obj: Int?, value: Float) {
+                received.add(value)
+            }
+        }
+
+    private val p1 = factory.get(1)
+    private val p2 = factory.get(2)
+    private val p3 = factory.get(3)
+
+    @Test
+    fun set_multipleSame_bothAppliedd() {
+        p1.set(null, 0.5f)
+        p1.set(null, 0.5f)
+
+        assertThat(received).containsExactly(0.5f, 0.5f)
+    }
+
+    @Test
+    fun set_differentIndexes_oneValuesNotCounted() {
+        val v1 = 0.5f
+        val v2 = 1.0f
+        p1.set(null, v1)
+        p2.set(null, v2)
+
+        assertThat(received).containsExactly(v1, v1)
+    }
+
+    @Test
+    fun set_onlyOneSetToOne_oneApplied() {
+        p1.set(null, 1.0f)
+
+        assertThat(received).containsExactly(1.0f)
+    }
+
+    @Test
+    fun set_onlyOneLessThanOne_applied() {
+        p1.set(null, 0.5f)
+
+        assertThat(received).containsExactly(0.5f)
+    }
+
+    @Test
+    fun set_differentIndexes_boundToMin() {
+        val v1 = 0.5f
+        val v2 = 0.6f
+        p1.set(null, v1)
+        p2.set(null, v2)
+
+        assertThat(received).containsExactly(v1, v1)
+    }
+
+    @Test
+    fun set_allHigherThanOne_boundToMax() {
+        val v1 = 3.0f
+        val v2 = 2.0f
+        val v3 = 1.0f
+        p1.set(null, v1)
+        p2.set(null, v2)
+        p3.set(null, v3)
+
+        assertThat(received).containsExactly(v1, v1, v1)
+    }
+
+    @Test
+    fun set_differentIndexes_firstModified_aggregationApplied() {
+        val v1 = 0.5f
+        val v2 = 0.6f
+        val v3 = 4f
+        p1.set(null, v1)
+        p2.set(null, v2)
+        p3.set(null, v3)
+
+        assertThat(received).containsExactly(v1, v1, v1 * v2 * v3)
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index c1b0220..800322b 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -51,8 +51,7 @@
         // Wait for the recycler to populate.
         mLauncher.waitForObjectInContainer(appListRecycler, By.clazz(TextView.class));
         verifyNotFrozen("All apps freeze flags upon opening all apps");
-        mIconHeight = mLauncher.getTestInfo(
-                        TestProtocol.REQUEST_ICON_HEIGHT)
+        mIconHeight = mLauncher.getTestInfo(TestProtocol.REQUEST_ICON_HEIGHT)
                 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
@@ -211,7 +210,7 @@
 
     private int getAllAppsScroll() {
         return mLauncher.getTestInfo(
-                        TestProtocol.REQUEST_APPS_LIST_SCROLL_Y)
+                TestProtocol.REQUEST_APPS_LIST_SCROLL_Y)
                 .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 8fa9e12..50611d7 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -27,6 +27,7 @@
 
 import com.android.launcher3.testing.TestProtocol;
 
+import java.util.function.Supplier;
 import java.util.regex.Pattern;
 
 /**
@@ -34,8 +35,11 @@
  */
 public abstract class AppIcon extends Launchable implements FolderDragTarget {
 
+    private final String mAppName;
+
     AppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
         super(launcher, icon);
+        mAppName = icon.getText();
     }
 
     static BySelector getAppIconSelector(String appName, LauncherInstrumentation launcher) {
@@ -138,4 +142,31 @@
             );
         }
     }
+
+    /**
+     * Drag an object to the given cell in workspace. The target cell must be empty.
+     *
+     * @param cellX zero based column number, starting from the left of the screen.
+     * @param cellY zero based row number, starting from the top of the screen.
+     */
+    public AppIcon dragToWorkspace(int cellX, int cellY) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     String.format("want to drag the icon to cell(%d, %d)", cellX, cellY))
+        ) {
+            final Supplier<Point> dest = () -> Workspace.getCellCenter(mLauncher, cellX, cellY);
+            Workspace.dragIconToWorkspace(mLauncher, this, dest, true, getLongPressIndicator(),
+                    () -> addExpectedEventsForLongClick(), null);
+            try (LauncherInstrumentation.Closable ignore = mLauncher.addContextLayer("dragged")) {
+                WorkspaceAppIcon appIcon =
+                        (WorkspaceAppIcon) mLauncher.getWorkspace().getWorkspaceAppIcon(mAppName);
+                mLauncher.assertTrue(
+                        String.format(
+                                "The %s icon should be in the cell (%d, %d).", mAppName, cellX,
+                                cellY),
+                        appIcon.isInCell(cellX, cellY));
+                return appIcon;
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 12be54e..7c377d5 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -66,6 +66,7 @@
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.testing.TestInformationRequest;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.systemui.shared.system.ContextUtils;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -301,9 +302,13 @@
     }
 
     Bundle getTestInfo(String request, String arg) {
+        return getTestInfo(request, arg, null);
+    }
+
+    Bundle getTestInfo(String request, String arg, Bundle extra) {
         try (ContentProviderClient client = getContext().getContentResolver()
                 .acquireContentProviderClient(mTestProviderUri)) {
-            return client.call(request, arg, null);
+            return client.call(request, arg, extra);
         } catch (DeadObjectException e) {
             fail("Launcher crashed");
             return null;
@@ -312,6 +317,12 @@
         }
     }
 
+    Bundle getTestInfo(TestInformationRequest request) {
+        Bundle extra = new Bundle();
+        extra.putParcelable(TestProtocol.TEST_INFO_REQUEST_FIELD, request);
+        return getTestInfo(request.getRequestName(), null, extra);
+    }
+
     Insets getTargetInsets() {
         return getTestInfo(TestProtocol.REQUEST_TARGET_INSETS)
                 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
@@ -1757,4 +1768,4 @@
         return ResourceUtils.getBoolByName(
                 "config_supportsRoundedCornersOnWindows", resources, false);
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 16987e9..1947da3 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -41,6 +41,7 @@
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.WorkspaceCellCenterRequest;
 
 import java.util.List;
 import java.util.function.Supplier;
@@ -88,7 +89,7 @@
             final int windowCornerRadius = (int) Math.ceil(mLauncher.getWindowCornerRadius());
             final int startY = deviceHeight - Math.max(bottomGestureMargin, windowCornerRadius) - 1;
             final int swipeHeight = mLauncher.getTestInfo(
-                            TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT)
+                    TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT)
                     .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
             LauncherInstrumentation.log(
                     "switchToAllApps: deviceHeight = " + deviceHeight + ", startY = " + startY
@@ -180,8 +181,8 @@
      *
      * @param appIcon   - icon to drag.
      * @param pageDelta - how many pages should the icon be dragged from the current page.
-     *                    It can be a negative value. currentPage + pageDelta should be greater
-     *                    than or equal to 0.
+     *                  It can be a negative value. currentPage + pageDelta should be greater
+     *                  than or equal to 0.
      */
     public void dragIcon(AppIcon appIcon, int pageDelta) {
         if (mHotseat.getVisibleBounds().height() > mHotseat.getVisibleBounds().width()) {
@@ -266,8 +267,9 @@
     /**
      * Uninstall the appIcon by dragging it to the 'uninstall' drop point of the drop_target_bar.
      *
-     * @param launcher the root TAPL instrumentation object of {@link LauncherInstrumentation} type.
-     * @param appIcon to be uninstalled.
+     * @param launcher              the root TAPL instrumentation object of {@link
+     *                              LauncherInstrumentation} type.
+     * @param appIcon               to be uninstalled.
      * @param expectLongClickEvents the runnable to be executed to verify expected longclick event.
      * @return validated workspace after the existing appIcon being uninstalled.
      */
@@ -305,6 +307,23 @@
     }
 
     /**
+     * Get cell layout's grids size. The return point's x and y values are the cell counts in X and
+     * Y directions respectively, not the values in pixels.
+     */
+    public Point getIconGridDimensions() {
+        int[] countXY = mLauncher.getTestInfo(
+                TestProtocol.REQUEST_WORKSPACE_CELL_LAYOUT_SIZE).getIntArray(
+                TestProtocol.TEST_INFO_RESPONSE_FIELD);
+        return new Point(countXY[0], countXY[1]);
+    }
+
+    static Point getCellCenter(LauncherInstrumentation launcher, int cellX, int cellY) {
+        return launcher.getTestInfo(WorkspaceCellCenterRequest.builder().setCellX(
+                cellX).setCellY(cellY).build()).getParcelable(
+                TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
+    /**
      * Finds folder icons in the current workspace.
      *
      * @return a list of folder icons.
diff --git a/tests/tapl/com/android/launcher3/tapl/WorkspaceAppIcon.java b/tests/tapl/com/android/launcher3/tapl/WorkspaceAppIcon.java
index 5f4e469..0523a63 100644
--- a/tests/tapl/com/android/launcher3/tapl/WorkspaceAppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/WorkspaceAppIcon.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.tapl;
 
+import android.graphics.Point;
+
 import androidx.test.uiautomator.UiObject2;
 
 import java.util.regex.Pattern;
@@ -33,4 +35,9 @@
     protected Pattern getLongClickEvent() {
         return Workspace.LONG_CLICK_EVENT;
     }
+
+    boolean isInCell(int cellX, int cellY) {
+        final Point center = Workspace.getCellCenter(mLauncher, cellX, cellY);
+        return mObject.getParent().getVisibleBounds().contains(center.x, center.y);
+    }
 }