Merge "Adding support for easily extending Launcher DragAndDrop support" into tm-dev
diff --git a/quickstep/res/values-sw600dp-land/dimens.xml b/quickstep/res/values-sw600dp-land/dimens.xml
index 2cd48d5..0cd9b2b 100644
--- a/quickstep/res/values-sw600dp-land/dimens.xml
+++ b/quickstep/res/values-sw600dp-land/dimens.xml
@@ -15,7 +15,6 @@
 */
 -->
 <resources>
+    <!--  Overview actions  -->
     <dimen name="overview_actions_top_margin">12dp</dimen>
-    <dimen name="overview_grid_side_margin">52dp</dimen>
-    <dimen name="overview_page_spacing">38dp</dimen>
 </resources>
diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml
index 5153afa..7494683 100644
--- a/quickstep/res/values-sw600dp/dimens.xml
+++ b/quickstep/res/values-sw600dp/dimens.xml
@@ -17,11 +17,13 @@
 <resources>
     <dimen name="navigation_key_padding">25dp</dimen>
 
+    <!--  Task View  -->
+    <dimen name="task_thumbnail_icon_size">48dp</dimen>
+    <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
     <dimen name="overview_task_margin">12dp</dimen>
-    <dimen name="overview_task_margin_grid">4dp</dimen>
-    <dimen name="overview_actions_button_spacing">36dp</dimen>
-    <dimen name="overview_grid_side_margin">60dp</dimen>
-    <dimen name="overview_grid_row_spacing">36dp</dimen>
+    <dimen name="task_thumbnail_icon_drawable_size_grid">44dp</dimen>
+    <dimen name="overview_task_margin_grid">12dp</dimen>
+    <dimen name="overview_grid_row_spacing">28dp</dimen>
     <dimen name="overview_page_spacing">36dp</dimen>
-    <dimen name="task_thumbnail_icon_drawable_size_grid">32dp</dimen>
+    <dimen name="overview_grid_side_margin">64dp</dimen>
 </resources>
diff --git a/quickstep/res/values-sw720dp-land/dimens.xml b/quickstep/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 0000000..02d1189
--- /dev/null
+++ b/quickstep/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+*/
+-->
+<resources>
+    <!--  Overview actions  -->
+    <dimen name="overview_actions_top_margin">20dp</dimen>
+</resources>
diff --git a/quickstep/res/values-sw720dp/dimens.xml b/quickstep/res/values-sw720dp/dimens.xml
index e381cb0..ceaa8f8 100644
--- a/quickstep/res/values-sw720dp/dimens.xml
+++ b/quickstep/res/values-sw720dp/dimens.xml
@@ -15,11 +15,13 @@
 */
 -->
 <resources>
+    <!--  Task View  -->
+    <dimen name="task_thumbnail_icon_size">48dp</dimen>
+    <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
     <dimen name="overview_task_margin">16dp</dimen>
+    <dimen name="task_thumbnail_icon_drawable_size_grid">44dp</dimen>
     <dimen name="overview_task_margin_grid">16dp</dimen>
-    <dimen name="overview_grid_side_margin">64dp</dimen>
     <dimen name="overview_grid_row_spacing">36dp</dimen>
     <dimen name="overview_page_spacing">44dp</dimen>
-    <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
-    <dimen name="task_thumbnail_icon_drawable_size_grid">44dp</dimen>
+    <dimen name="overview_grid_side_margin">64dp</dimen>
 </resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 4210052..3072a3e 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -15,8 +15,6 @@
 -->
 
 <resources>
-    <dimen name="task_thumbnail_icon_size">48dp</dimen>
-    <dimen name="task_thumbnail_icon_drawable_size">48dp</dimen>
     <!-- For screens without rounded corners -->
     <dimen name="task_corner_radius_small">2dp</dimen>
     <!-- For Launchers that want to override the default dialog corner radius -->
@@ -32,18 +30,20 @@
     <dimen name="overview_proactive_row_bottom_margin">16dp</dimen>
 
     <dimen name="overview_minimum_next_prev_size">50dp</dimen>
+
+    <!--  Task View  -->
+    <dimen name="task_thumbnail_icon_size">48dp</dimen>
+    <dimen name="task_thumbnail_icon_drawable_size">44dp</dimen>
     <dimen name="overview_task_margin">16dp</dimen>
-    <dimen name="overview_task_margin_grid">0dp</dimen>
+    <dimen name="overview_page_spacing">16dp</dimen>
+
     <item name="overview_max_scale" format="float" type="dimen">0.7</item>
     <item name="overview_modal_max_scale" format="float" type="dimen">1.1</item>
 
     <!-- Overrideable in overlay that provides the Overview Actions. -->
-    <dimen name="overview_actions_height">48dp</dimen>
-    <dimen name="overview_actions_button_spacing">32dp</dimen>
     <dimen name="overview_actions_top_margin">24dp</dimen>
-    <dimen name="overview_actions_horizontal_margin">16dp</dimen>
-
-    <dimen name="overview_page_spacing">16dp</dimen>
+    <dimen name="overview_actions_height">48dp</dimen>
+    <dimen name="overview_actions_button_spacing">36dp</dimen>
 
     <!-- These speeds are in dp/s -->
     <dimen name="max_task_dismiss_drag_velocity">2.25dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 351ec4a..5f3a990 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -180,7 +180,7 @@
                 : null;
         super.applyFromWorkspaceItem(info, animate, staggerIndex);
         int oldPlateColor = mPlateColor;
-        int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
+        int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.appColor, 200);
         if (!animate) {
             mPlateColor = newPlateColor;
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 699ce97..5f62749 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -66,9 +66,9 @@
     @Override
     public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
         RecentsView recentsView = launcher.getOverviewPanel();
-        float workspacePageWidth = launcher.getDeviceProfile().getWorkspaceWidth();
+        float workspacePageHeight = launcher.getDeviceProfile().getCellLayoutHeight();
         recentsView.getTaskSize(sTempRect);
-        float scale = (float) sTempRect.width() / workspacePageWidth;
+        float scale = (float) sTempRect.height() / workspacePageHeight;
         float parallaxFactor = 0.5f;
         return new ScaleAndTranslation(scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor);
     }
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 6745246..2fcd286 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -222,32 +222,27 @@
      */
     public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect) {
         Resources res = context.getResources();
+        float maxScale = res.getFloat(R.dimen.overview_max_scale);
         if (dp.isTablet) {
             Rect gridRect = new Rect();
             calculateGridSize(dp, gridRect);
 
-            PointF taskDimension = getTaskDimension(context, dp);
-            float scale = gridRect.height() / taskDimension.y;
-            scale = Math.min(scale, res.getFloat(R.dimen.overview_max_scale));
-            int outWidth = Math.round(scale * taskDimension.x);
-            int outHeight = Math.round(scale * taskDimension.y);
-
-            int gravity = Gravity.CENTER;
-            Gravity.apply(gravity, outWidth, outHeight, gridRect, outRect);
+            calculateTaskSizeInternal(context, dp, gridRect, maxScale, Gravity.CENTER, outRect);
         } else {
             int taskMargin = dp.overviewTaskMarginPx;
             calculateTaskSizeInternal(context, dp,
                     dp.overviewTaskThumbnailTopMarginPx,
                     dp.getOverviewActionsClaimedSpace(),
                     res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
+                    maxScale,
                     Gravity.CENTER,
                     outRect);
         }
     }
 
     private void calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove,
-            int claimedSpaceBelow, int minimumHorizontalPadding, int gravity, Rect outRect) {
-        PointF taskDimension = getTaskDimension(context, dp);
+            int claimedSpaceBelow, int minimumHorizontalPadding, float maxScale, int gravity,
+            Rect outRect) {
         Rect insets = dp.getInsets();
 
         Rect potentialTaskRect = new Rect(0, 0, dp.widthPx, dp.heightPx);
@@ -258,9 +253,17 @@
                 minimumHorizontalPadding,
                 claimedSpaceBelow);
 
+        calculateTaskSizeInternal(context, dp, potentialTaskRect, maxScale, gravity, outRect);
+    }
+
+    private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
+            Rect potentialTaskRect, float maxScale, int gravity, Rect outRect) {
+        PointF taskDimension = getTaskDimension(context, dp);
+
         float scale = Math.min(
                 potentialTaskRect.width() / taskDimension.x,
                 potentialTaskRect.height() / taskDimension.y);
+        scale = Math.min(scale, maxScale);
         int outWidth = Math.round(scale * taskDimension.x);
         int outHeight = Math.round(scale * taskDimension.y);
 
@@ -359,6 +362,7 @@
                 dp.overviewTaskMarginPx,
                 dp.heightPx - outRect.bottom - dp.getInsets().bottom,
                 Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2),
+                1f /*maxScale*/,
                 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM,
                 outRect);
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 920ed71..4fb7e6b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -419,6 +419,13 @@
     }
 
     /**
+     * @return whether notification panel is expanded
+     */
+    public boolean isNotificationPanelExpanded() {
+        return (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) != 0;
+    }
+
+    /**
      * @return whether the global actions dialog is showing
      */
     public boolean isSystemUiDialogShowing() {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 0078d55..f2583fb 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -700,7 +700,10 @@
 
             // If Bubbles is expanded, use the overlay input consumer, which will close Bubbles
             // instead of going all the way home when a swipe up is detected.
-            if (mDeviceState.isBubblesExpanded() || mDeviceState.isSystemUiDialogShowing()) {
+            // Notification panel can be expanded on top of expanded bubbles. Bubbles remain
+            // expanded in the back. Make sure swipe up is not passed to bubbles in this case.
+            if ((mDeviceState.isBubblesExpanded() && !mDeviceState.isNotificationPanelExpanded())
+                    || mDeviceState.isSystemUiDialogShowing()) {
                 base = new SysUiOverlayInputConsumer(
                         getBaseContext(), mDeviceState, mInputMonitorCompat);
             }
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ee5e024..829a21d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -277,6 +277,9 @@
     <!-- Summary for Notification dots setting. Tapping this will link enable/disable notification dots feature on the home screen. [CHAR LIMIT=50] -->
     <string name="notification_dots_service_title">Show notification dots</string>
 
+    <!-- Title for Developer Options setting. [CHAR LIMIT=50] -->
+    <string name="developer_options_title">Developer Options</string>
+
     <!-- Label for the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=60] -->
     <string name="auto_add_shortcuts_label">Add app icons to home screen</string>
     <!-- Text description of the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=NONE] -->
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 90de498..8a0c909 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -53,7 +53,7 @@
     <androidx.preference.PreferenceScreen
         android:key="pref_developer_options"
         android:persistent="false"
-        android:title="Developer Options"
+        android:title="@string/developer_options_title"
         android:fragment="com.android.launcher3.settings.DeveloperOptionsFragment"/>
 
 </androidx.preference.PreferenceScreen>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 490a134..878ac3b 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -252,7 +252,8 @@
      */
     public void reset() {
         mDotInfo = null;
-        mDotParams.color = Color.TRANSPARENT;
+        mDotParams.dotColor = Color.TRANSPARENT;
+        mDotParams.appColor = Color.TRANSPARENT;
         cancelDotScaleAnim();
         mDotParams.scale = 0f;
         mForceHideDot = false;
@@ -373,7 +374,9 @@
             flags |= FLAG_NO_BADGE;
         }
         FastBitmapDrawable iconDrawable = info.newIcon(getContext(), flags);
-        mDotParams.color = iconDrawable.getIconColor();
+        mDotParams.appColor = iconDrawable.getIconColor();
+        mDotParams.dotColor = getContext().getResources()
+                .getColor(android.R.color.system_accent3_100, getContext().getTheme());
         setIcon(iconDrawable);
         applyLabel(info);
     }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 88030ae..be180a6 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -665,11 +665,10 @@
         updateIconSize(1f, res);
 
         updateWorkspacePadding();
-        Point workspacePadding = getTotalWorkspacePadding();
 
         // Check to see if the icons fit within the available height.
         float usedHeight = getCellLayoutHeightSpecification();
-        final int maxHeight = getWorkspaceHeight(workspacePadding);
+        final int maxHeight = getCellLayoutHeight();
         float extraHeight = Math.max(0, maxHeight - usedHeight);
         float scaleY = maxHeight / usedHeight;
         boolean shouldScale = scaleY < 1f;
@@ -702,7 +701,7 @@
     }
 
     private int getCellLayoutWidthSpecification() {
-        int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
+        int numColumns = getPanelCount() * inv.numColumns;
         return (cellWidthPx * numColumns) + (cellLayoutBorderSpacePx.x * (numColumns - 1))
                 + cellLayoutPaddingPx.left + cellLayoutPaddingPx.right;
     }
@@ -902,19 +901,25 @@
             result = new Point();
         }
 
-        // Since we are only concerned with the overall padding, layout direction does
-        // not matter.
-        Point padding = getTotalWorkspacePadding();
-
-        int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
-        int screenWidthPx = getWorkspaceWidth(padding);
-        result.x = calculateCellWidth(screenWidthPx, cellLayoutBorderSpacePx.x, numColumns);
-        int screenHeightPx = getWorkspaceHeight(padding);
-        result.y = calculateCellHeight(screenHeightPx, cellLayoutBorderSpacePx.y, inv.numRows);
+        int shortcutAndWidgetContainerWidth =
+                getCellLayoutWidth() - (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right);
+        result.x = calculateCellWidth(shortcutAndWidgetContainerWidth, cellLayoutBorderSpacePx.x,
+                inv.numColumns);
+        int shortcutAndWidgetContainerHeight =
+                getCellLayoutHeight() - (cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom);
+        result.y = calculateCellHeight(shortcutAndWidgetContainerHeight, cellLayoutBorderSpacePx.y,
+                inv.numRows);
         return result;
     }
 
     /**
+     * Gets the number of panels within the workspace.
+     */
+    public int getPanelCount() {
+        return isTwoPanels ? 2 : 1;
+    }
+
+    /**
      * Gets the space in px from the bottom of last item in the vertical-bar hotseat to the
      * bottom of the screen.
      */
@@ -932,7 +937,7 @@
     /**
      * Gets the scaled top of the workspace in px for the spring-loaded edit state.
      */
-    public float getWorkspaceSpringLoadShrunkTop() {
+    public float getCellLayoutSpringLoadShrunkTop() {
         workspaceSpringLoadShrunkTop = mInsets.top + dropTargetBarTopMarginPx + dropTargetBarSizePx
                 + dropTargetBarBottomMarginPx;
         return workspaceSpringLoadShrunkTop;
@@ -941,7 +946,7 @@
     /**
      * Gets the scaled bottom of the workspace in px for the spring-loaded edit state.
      */
-    private float getWorkspaceSpringLoadShrunkBottom() {
+    private float getCellLayoutSpringLoadShrunkBottom() {
         int topOfHotseat = hotseatBarSizePx + springLoadedHotseatBarTopMarginPx;
         workspaceSpringLoadShrunkBottom =
                 heightPx - (isVerticalBarLayout() ? getVerticalHotseatLastItemBottomOffset()
@@ -960,13 +965,12 @@
      * Gets the scale of the workspace for the spring-loaded edit state.
      */
     public float getWorkspaceSpringLoadScale() {
-        float cellLayoutHeight = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
-        float scale = (getWorkspaceSpringLoadShrunkBottom() - getWorkspaceSpringLoadShrunkTop())
-                / cellLayoutHeight;
+        float scale = (getCellLayoutSpringLoadShrunkBottom() - getCellLayoutSpringLoadShrunkTop())
+                / getCellLayoutHeight();
         scale = Math.min(scale, 1f);
 
         // Reduce scale if next pages would not be visible after scaling the workspace
-        int workspaceWidth = getWorkspaceWidth();
+        int workspaceWidth = availableWidthPx;
         float scaledWorkspaceWidth = workspaceWidth * scale;
         float maxAvailableWidth =
                 workspaceWidth - (2 * getWorkspaceSpringLoadedMinimumNextPageVisible());
@@ -976,19 +980,23 @@
         return scale;
     }
 
-    public int getWorkspaceWidth() {
-        return getWorkspaceWidth(getTotalWorkspacePadding());
+    /**
+     * Gets the width of a single Cell Layout, aka a single panel within a Workspace.
+     *
+     * <p>This is the width of a Workspace, less its horizontal padding. Note that two-panel
+     * layouts have two Cell Layouts per workspace.
+     */
+    public int getCellLayoutWidth() {
+        return (availableWidthPx - getTotalWorkspacePadding().x) / getPanelCount();
     }
 
-    public int getWorkspaceWidth(Point workspacePadding) {
-        int cellLayoutTotalPadding =
-                (isTwoPanels ? 2 : 1) * (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right);
-        return availableWidthPx - workspacePadding.x - cellLayoutTotalPadding;
-    }
-
-    private int getWorkspaceHeight(Point workspacePadding) {
-        return availableHeightPx - workspacePadding.y - (cellLayoutPaddingPx.top
-                + cellLayoutPaddingPx.bottom);
+    /**
+     * Gets the height of a single Cell Layout, aka a single panel within a Workspace.
+     *
+     * <p>This is the height of a Workspace, less its vertical padding.
+     */
+    public int getCellLayoutHeight() {
+        return availableHeightPx - getTotalWorkspacePadding().y;
     }
 
     public Point getTotalWorkspacePadding() {
@@ -1160,7 +1168,7 @@
             return  ((taskbarSize - overviewActionsHeight) / 2) + getTaskbarOffsetY();
         }
 
-        return 0;
+        return isTaskbarPresent ? stashedTaskbarSize : mInsets.bottom;
     }
 
     /** Gets the space that the overview actions will take, including bottom margin. */
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ad87451..135b88d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1169,6 +1169,7 @@
         }
 
         AbstractFloatingView.closeAllOpenViewsExcept(this, false, TYPE_REBIND_SAFE);
+        DragView.removeAllViews(this);
         TraceHelper.INSTANCE.endSection(traceToken);
     }
 
diff --git a/src/com/android/launcher3/anim/AnimationSuccessListener.java b/src/com/android/launcher3/anim/AnimationSuccessListener.java
index a312070..6196df2 100644
--- a/src/com/android/launcher3/anim/AnimationSuccessListener.java
+++ b/src/com/android/launcher3/anim/AnimationSuccessListener.java
@@ -19,6 +19,8 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 
+import androidx.annotation.CallSuper;
+
 /**
  * Extension of {@link AnimatorListenerAdapter} for listening for non-cancelled animations
  */
@@ -27,6 +29,7 @@
     protected boolean mCancelled = false;
 
     @Override
+    @CallSuper
     public void onAnimationCancel(Animator animation) {
         mCancelled = true;
     }
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index a3945fd..0264ae2 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -578,4 +578,19 @@
         iv.setImageDrawable(drawable);
         return iv;
     }
+
+    /**
+     * Removes any stray DragView from the DragLayer.
+     */
+    public static void removeAllViews(ActivityContext activity) {
+        BaseDragLayer dragLayer = activity.getDragLayer();
+        // Iterate in reverse order. DragView is added later to the dragLayer,
+        // and will be one of the last views.
+        for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
+            View child = dragLayer.getChildAt(i);
+            if (child instanceof DragView) {
+                dragLayer.removeView(child);
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 4b436d3..b1e2701 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -637,7 +637,7 @@
 
             // If we are animating to the accepting state, animate the dot out.
             mDotParams.scale = Math.max(0, mDotScale - mBackground.getScaleProgress());
-            mDotParams.color = mBackground.getDotColor();
+            mDotParams.dotColor = mBackground.getDotColor();
             mDotRenderer.draw(canvas, mDotParams);
         }
     }
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 2aa9dde..9f50ff9 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -335,7 +335,13 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 // Change the internal state only when the transition actually starts
-                onStateTransitionStart(state);
+                onStateTransitionStart(mCancelled ? mCurrentStableState : state);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                super.onAnimationCancel(animation);
+                mState = mCurrentStableState;
             }
 
             @Override
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 15cdc20..a205ab5 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -51,7 +51,7 @@
             return super.getWorkspaceScaleAndTranslation(launcher);
         }
 
-        float shrunkTop = grid.getWorkspaceSpringLoadShrunkTop();
+        float shrunkTop = grid.getCellLayoutSpringLoadShrunkTop();
         float scale = grid.getWorkspaceSpringLoadScale();
 
         float halfHeight = ws.getHeight() / 2;
diff --git a/tests/src/com/android/launcher3/DeviceProfileGridDimensionsTest.kt b/tests/src/com/android/launcher3/DeviceProfileGridDimensionsTest.kt
new file mode 100644
index 0000000..80259a5
--- /dev/null
+++ b/tests/src/com/android/launcher3/DeviceProfileGridDimensionsTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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
+
+import android.graphics.PointF
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.util.WindowBounds
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * Test for [DeviceProfile] grid dimensions.
+ *
+ * This includes workspace, cell layout, shortcut and widget container, cell sizes, etc.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceProfileGridDimensionsTest : DeviceProfileBaseTest() {
+
+    @Test
+    fun getCellLayoutWidth_twoPanelLandscapeScalable4By4GridTablet_equalsSinglePanelWidth() {
+        val tabletWidth = 2560
+        val tabletHeight = 1600
+        val availableWidth = 2560
+        val availableHeight = 1500
+        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+        useTwoPanels = true
+        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+        whenever(info.densityDpi).thenReturn(320)
+        inv = newScalableInvariantDeviceProfile()
+
+        val dp = newDP()
+
+        val expectedWorkspaceWidth = availableWidth
+        val expectedCellLayoutWidth =
+                (expectedWorkspaceWidth - (dp.workspacePadding.right + dp.workspacePadding.left)) /
+                        dp.panelCount
+        assertThat(dp.cellLayoutWidth).isEqualTo(expectedCellLayoutWidth)
+    }
+
+    @Test
+    fun getCellLayoutHeight_twoPanelLandscapeScalable4By4GridTablet_equalsSinglePanelHeight() {
+        val tabletWidth = 2560
+        val tabletHeight = 1600
+        val availableWidth = 2560
+        val availableHeight = 1500
+        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+        useTwoPanels = true
+        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+        whenever(info.densityDpi).thenReturn(320)
+        inv = newScalableInvariantDeviceProfile()
+
+        val dp = newDP()
+
+        val expectedWorkspaceHeight = availableHeight
+        val expectedCellLayoutHeight =
+                expectedWorkspaceHeight - (dp.workspacePadding.top + dp.workspacePadding.bottom)
+        assertThat(dp.cellLayoutHeight).isEqualTo(expectedCellLayoutHeight)
+    }
+
+    @Test
+    fun getCellSize_twoPanelLandscapeScalable4By4GridTablet_equalsSinglePanelWidth() {
+        val tabletWidth = 2560
+        val tabletHeight = 1600
+        val availableWidth = 2560
+        val availableHeight = 1500
+        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+        useTwoPanels = true
+        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+        whenever(info.densityDpi).thenReturn(320)
+        inv = newScalableInvariantDeviceProfile()
+
+        val dp = newDP()
+
+        val expectedWorkspaceWidth = availableWidth
+        val expectedCellLayoutWidth =
+                (expectedWorkspaceWidth - (dp.workspacePadding.right + dp.workspacePadding.left)) /
+                        dp.panelCount
+        val expectedShortcutAndWidgetContainerWidth =
+                expectedCellLayoutWidth -
+                        (dp.cellLayoutPaddingPx.left + dp.cellLayoutPaddingPx.right)
+        assertThat(dp.getCellSize().x).isEqualTo(
+                (expectedShortcutAndWidgetContainerWidth -
+                        ((inv!!.numColumns - 1) * dp.cellLayoutBorderSpacePx.x)) / inv!!.numColumns)
+        val expectedWorkspaceHeight = availableHeight
+        val expectedCellLayoutHeight =
+                expectedWorkspaceHeight - (dp.workspacePadding.top + dp.workspacePadding.bottom)
+        val expectedShortcutAndWidgetContainerHeight = expectedCellLayoutHeight -
+                (dp.cellLayoutPaddingPx.top + dp.cellLayoutPaddingPx.bottom)
+        assertThat(dp.getCellSize().y).isEqualTo(
+                (expectedShortcutAndWidgetContainerHeight -
+                        ((inv!!.numRows - 1) * dp.cellLayoutBorderSpacePx.y)) / inv!!.numRows)
+    }
+
+    @Test
+    fun getPanelCount_twoPanelLandscapeScalable4By4GridTablet_equalsTwoPanels() {
+        val tabletWidth = 2560
+        val tabletHeight = 1600
+        val availableWidth = 2560
+        val availableHeight = 1500
+        windowBounds = WindowBounds(tabletWidth, tabletHeight, availableWidth, availableHeight, 0)
+        useTwoPanels = true
+        whenever(info.isTablet(ArgumentMatchers.any())).thenReturn(true)
+        whenever(info.densityDpi).thenReturn(320)
+        inv = newScalableInvariantDeviceProfile()
+
+        val dp = newDP()
+
+        assertThat(dp.panelCount).isEqualTo(2)
+    }
+}
\ No newline at end of file