Merge "Finish recents animation on activity destroyed." into tm-dev
diff --git a/go/quickstep/src/com/android/launcher3/AppSharing.java b/go/quickstep/src/com/android/launcher3/AppSharing.java
index c252fba..c287446 100644
--- a/go/quickstep/src/com/android/launcher3/AppSharing.java
+++ b/go/quickstep/src/com/android/launcher3/AppSharing.java
@@ -77,11 +77,12 @@
         return FileProvider.getUriForFile(context, authority, pathFile, displayName);
     }
 
-    private SystemShortcut<Launcher> getShortcut(Launcher launcher, ItemInfo info) {
+    private SystemShortcut<Launcher> getShortcut(Launcher launcher, ItemInfo info,
+            View originalView) {
         if (TextUtils.isEmpty(mSharingComponent)) {
             return null;
         }
-        return new Share(launcher, info);
+        return new Share(launcher, info, originalView);
     }
 
     /**
@@ -104,8 +105,9 @@
         private final PopupDataProvider mPopupDataProvider;
         private final boolean mSharingEnabledForUser;
 
-        public Share(Launcher target, ItemInfo itemInfo) {
-            super(R.drawable.ic_share, R.string.app_share_drop_target_label, target, itemInfo);
+        public Share(Launcher target, ItemInfo itemInfo, View originalView) {
+            super(R.drawable.ic_share, R.string.app_share_drop_target_label, target, itemInfo,
+                    originalView);
             mPopupDataProvider = target.getPopupDataProvider();
 
             mSharingEnabledForUser = bluetoothSharingEnabled(target);
@@ -200,6 +202,7 @@
     /**
      * Shortcut factory for generating the Share App button
      */
-    public static final SystemShortcut.Factory<Launcher> SHORTCUT_FACTORY = (launcher, itemInfo) ->
-            (new AppSharing(launcher)).getShortcut(launcher, itemInfo);
+    public static final SystemShortcut.Factory<Launcher> SHORTCUT_FACTORY =
+            (launcher, itemInfo, originalView) ->
+                    (new AppSharing(launcher)).getShortcut(launcher, itemInfo, originalView);
 }
diff --git a/quickstep/res/values-sw600dp-land/dimens.xml b/quickstep/res/values-sw600dp-land/dimens.xml
index 4e3c02c..2cd48d5 100644
--- a/quickstep/res/values-sw600dp-land/dimens.xml
+++ b/quickstep/res/values-sw600dp-land/dimens.xml
@@ -15,8 +15,7 @@
 */
 -->
 <resources>
-    <dimen name="overview_actions_top_margin_gesture">19.1dp</dimen>
-    <dimen name="overview_actions_bottom_margin_gesture">10dp</dimen>
+    <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 223a5e9..5153afa 100644
--- a/quickstep/res/values-sw600dp/dimens.xml
+++ b/quickstep/res/values-sw600dp/dimens.xml
@@ -20,8 +20,6 @@
     <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_actions_top_margin_gesture">19.37dp</dimen>
-    <dimen name="overview_actions_bottom_margin_gesture">22dp</dimen>
     <dimen name="overview_grid_side_margin">60dp</dimen>
     <dimen name="overview_grid_row_spacing">36dp</dimen>
     <dimen name="overview_page_spacing">36dp</dimen>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 3f08cf3..4210052 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -35,13 +35,12 @@
     <dimen name="overview_task_margin">16dp</dimen>
     <dimen name="overview_task_margin_grid">0dp</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_gesture">28dp</dimen>
-    <dimen name="overview_actions_bottom_margin_gesture">28dp</dimen>
-    <dimen name="overview_actions_margin_three_button">8dp</dimen>
+    <dimen name="overview_actions_top_margin">24dp</dimen>
     <dimen name="overview_actions_horizontal_margin">16dp</dimen>
 
     <dimen name="overview_page_spacing">16dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index fe24c4b..6abcbd5 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -296,7 +296,7 @@
                 new SplitSelectStateController(this, mHandler, getStateManager(),
                         getDepthController());
         overviewPanel.init(mActionsView, controller);
-        mActionsView.setDp(getDeviceProfile());
+        mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
         mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this));
 
         mAppTransitionManager = new QuickstepTransitionManager(this);
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 097609f..9f35401 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -42,7 +42,6 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SCRIM_FOR_APP_LAUNCH;
 import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
-import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
@@ -105,7 +104,6 @@
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.DynamicResource;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.ObjectWrapper;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Themes;
@@ -195,9 +193,7 @@
     private static final int WIDGET_CROSSFADE_DURATION_MILLIS = 125;
 
     protected final BaseQuickstepLauncher mLauncher;
-
     private final DragLayer mDragLayer;
-    private final AlphaProperty mDragLayerAlpha;
 
     final Handler mHandler;
 
@@ -241,7 +237,6 @@
     public QuickstepTransitionManager(Context context) {
         mLauncher = Launcher.cast(Launcher.getLauncher(context));
         mDragLayer = mLauncher.getDragLayer();
-        mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
         mHandler = new Handler(Looper.getMainLooper());
         mDeviceProfile = mLauncher.getDeviceProfile();
         mBackAnimationController = new LauncherBackAnimationController(mLauncher, this);
@@ -1359,7 +1354,7 @@
      */
     private RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation,
             RemoteAnimationTargetCompat[] targets, View launcherView, PointF velocityPxPerS,
-            RectF closingWindowStartRect) {
+            RectF closingWindowStartRect, float startWindowCornerRadius) {
         FloatingIconView floatingIconView = null;
         FloatingWidgetView floatingWidget = null;
         RectF targetRect = new RectF();
@@ -1408,7 +1403,7 @@
             final float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
 
             RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
-                    windowTargetBounds) {
+                    windowTargetBounds, startWindowCornerRadius) {
                 @Override
                 public void onUpdate(RectF currentRectF, float progress) {
                     finalFloatingIconView.update(1f, 255 /* fgAlpha */, currentRectF, progress,
@@ -1426,7 +1421,7 @@
             final float floatingWidgetAlpha = isTransluscent ? 0 : 1;
             FloatingWidgetView finalFloatingWidget = floatingWidget;
             RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
-                    windowTargetBounds) {
+                    windowTargetBounds, startWindowCornerRadius) {
                 @Override
                 public void onUpdate(RectF currentRectF, float progress) {
                     final float fallbackBackgroundAlpha =
@@ -1443,7 +1438,8 @@
         } else {
             // If no floating icon or widget is present, animate the to the default window
             // target rect.
-            anim.addOnUpdateListener(new SpringAnimRunner(targets, targetRect, windowTargetBounds));
+            anim.addOnUpdateListener(new SpringAnimRunner(
+                    targets, targetRect, windowTargetBounds, startWindowCornerRadius));
         }
 
         // Use a fixed velocity to start the animation.
@@ -1585,7 +1581,8 @@
             RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets,
             boolean fromUnlock,
-            RectF startRect) {
+            RectF startRect,
+            float startWindowCornerRadius) {
         AnimatorSet anim = null;
         RectFSpringAnim rectFSpringAnim = null;
 
@@ -1617,7 +1614,8 @@
                         .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
                 PointF velocity = new PointF(0, -velocityPxPerS);
                 rectFSpringAnim = getClosingWindowAnimators(
-                        anim, appTargets, launcherView, velocity, startRect);
+                        anim, appTargets, launcherView, velocity, startRect,
+                        startWindowCornerRadius);
                 if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
                     anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
                             true /* animateOverviewScrim */, launcherView).getAnimators());
@@ -1716,7 +1714,8 @@
 
             Pair<RectFSpringAnim, AnimatorSet> pair = createWallpaperOpenAnimations(
                     appTargets, wallpaperTargets, mFromUnlock,
-                    new RectF(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx));
+                    new RectF(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx),
+                    QuickStepContract.getWindowCornerRadius(mLauncher));
 
             mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
             result.setAnimation(pair.second, mLauncher);
@@ -1879,9 +1878,9 @@
         private final Rect mTmpRect = new Rect();
 
         SpringAnimRunner(RemoteAnimationTargetCompat[] appTargets, RectF targetRect,
-                Rect windowTargetBounds) {
+                Rect windowTargetBounds, float startWindowCornerRadius) {
             mAppTargets = appTargets;
-            mStartRadius = QuickStepContract.getWindowCornerRadius(mLauncher);
+            mStartRadius = startWindowCornerRadius;
             mEndRadius = Math.max(1, targetRect.width()) / 2f;
             mSurfaceApplier = new SurfaceTransactionApplier(mDragLayer);
             mWindowTargetBounds.set(windowTargetBounds);
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 85d9f01..62a8da7 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -409,11 +409,11 @@
     @Nullable
     @Override
     public SystemShortcut<QuickstepLauncher> getShortcut(QuickstepLauncher activity,
-            ItemInfo itemInfo) {
+            ItemInfo itemInfo, View originalView) {
         if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
             return null;
         }
-        return new PinPrediction(activity, itemInfo);
+        return new PinPrediction(activity, itemInfo, originalView);
     }
 
     private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) {
@@ -498,9 +498,9 @@
 
     private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
 
-        private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
+        private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo, View originalView) {
             super(R.drawable.ic_pin, R.string.pin_prediction, target,
-                    itemInfo);
+                    itemInfo, originalView);
         }
 
         @Override
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index e489cb3..68ed682 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -40,6 +40,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.view.View;
 
 import androidx.annotation.MainThread;
 import androidx.annotation.Nullable;
@@ -193,7 +194,7 @@
 
     @MainThread
     private SystemShortcut getShortcutForApp(String packageName, int userId,
-            BaseDraggingActivity activity, ItemInfo info) {
+            BaseDraggingActivity activity, ItemInfo info, View originalView) {
         Preconditions.assertUIThread();
         // Work profile apps are not recognized by digital wellbeing.
         if (userId != UserHandle.myUserId()) {
@@ -217,7 +218,7 @@
                         "getShortcutForApp [" + packageName + "]: action: '" + action.getTitle()
                                 + "'");
             }
-            return new RemoteActionShortcut(action, activity, info);
+            return new RemoteActionShortcut(action, activity, info, originalView);
         }
     }
 
@@ -378,8 +379,8 @@
      * Shortcut factory for generating wellbeing action
      */
     public static final SystemShortcut.Factory<BaseDraggingActivity> SHORTCUT_FACTORY =
-            (activity, info) -> (info.getTargetComponent() == null) ? null : INSTANCE.get(activity)
-                    .getShortcutForApp(
+            (activity, info, originalView) -> (info.getTargetComponent() == null) ? null
+                    : INSTANCE.get(activity).getShortcutForApp(
                             info.getTargetComponent().getPackageName(), info.user.getIdentifier(),
-                            activity, info);
+                            activity, info, originalView);
 }
diff --git a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
index cc0072e..86310fa 100644
--- a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
+++ b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
@@ -34,8 +34,9 @@
 
     static SystemShortcut.Factory<BaseQuickstepLauncher> getSplitSelectShortcutByPosition(
             SplitPositionOption position) {
-        return (activity, itemInfo) -> new QuickstepSystemShortcut.SplitSelectSystemShortcut(
-                activity, itemInfo, position);
+        return (activity, itemInfo, originalView) ->
+                new QuickstepSystemShortcut.SplitSelectSystemShortcut(activity, itemInfo,
+                        originalView, position);
     }
 
     class SplitSelectSystemShortcut extends SystemShortcut<BaseQuickstepLauncher> {
@@ -43,8 +44,8 @@
         private final SplitPositionOption mPosition;
 
         public SplitSelectSystemShortcut(BaseQuickstepLauncher launcher, ItemInfo itemInfo,
-                SplitPositionOption position) {
-            super(position.iconResId, position.textResId, launcher, itemInfo);
+                View originalView, SplitPositionOption position) {
+            super(position.iconResId, position.textResId, launcher, itemInfo, originalView);
 
             mPosition = position;
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index a9ae7bd..b797807 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -176,7 +176,8 @@
         return revealAnim;
     }
 
-    public void onIsStashed(boolean isStashed) {
+    /** Called when taskbar is stashed or unstashed. */
+    public void onIsStashedChanged(boolean isStashed) {
         mRegionSamplingHelper.setWindowVisible(isStashed);
         if (isStashed) {
             mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 9561b74..b349637 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -559,7 +559,7 @@
             }
         }
         mWindowLayoutParams.height = height;
-        mControllers.taskbarInsetsController.onTaskbarWindowHeightChanged();
+        mControllers.taskbarInsetsController.onTaskbarWindowHeightOrInsetsChanged();
         mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 5d3a152..ff08e3d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -160,6 +160,7 @@
         taskbarForceVisibleImmersiveController.onDestroy();
         taskbarAllAppsController.onDestroy();
         navButtonController.onDestroy();
+        taskbarInsetsController.onDestroy();
 
         mControllersToLog = null;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 089c26d..c1a6185 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -83,7 +83,6 @@
     private void onComputeTaskbarInsets(InsetsInfo insetsInfo) {
         if (mControllerCallbacks != null) {
             mControllerCallbacks.updateInsetsTouchability(insetsInfo);
-            mControllerCallbacks.updateContentInsets(insetsInfo.contentInsets);
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 307f674..3e2695c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -164,16 +164,6 @@
         }
 
         /**
-         * Called to update the {@link InsetsInfo#contentInsets}. This is reported to apps but our
-         * internal launcher will ignore these insets.
-         */
-        public void updateContentInsets(Rect outContentInsets) {
-            int contentHeight = mControllers.taskbarStashController
-                    .getContentHeightToReportToApps();
-            outContentInsets.top = mTaskbarDragLayer.getHeight() - contentHeight;
-        }
-
-        /**
          * Called when a child is removed from TaskbarDragLayer.
          */
         public void onDragLayerViewRemoved() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index a2ff780..9870a2e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -18,6 +18,7 @@
 import android.graphics.Insets
 import android.view.WindowManager
 import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS
 import com.android.launcher3.R
 import com.android.launcher3.anim.AlphaUpdateListener
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
@@ -53,20 +54,40 @@
             )
         )
 
+        windowLayoutParams.providedInternalInsets = arrayOfNulls<Insets>(ITYPE_SIZE)
         windowLayoutParams.providedInternalImeInsets = arrayOfNulls<Insets>(ITYPE_SIZE)
 
-        onTaskbarWindowHeightChanged()
+        onTaskbarWindowHeightOrInsetsChanged()
 
         windowLayoutParams.insetsRoundedCornerFrame = true
     }
 
-    fun onTaskbarWindowHeightChanged() {
-        val reducingSize = Insets.of(0, windowLayoutParams.height - taskbarHeightForIme, 0, 0)
+    fun onDestroy() {}
+
+    fun onTaskbarWindowHeightOrInsetsChanged() {
+        var reducingSize = getReducingInsetsForTaskbarInsetsHeight(
+            controllers.taskbarStashController.contentHeightToReportToApps)
+        windowLayoutParams.providedInternalInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize
+        reducingSize = getReducingInsetsForTaskbarInsetsHeight(
+            controllers.taskbarStashController.tappableHeightToReportToApps)
+        windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] = reducingSize
+
+        reducingSize = getReducingInsetsForTaskbarInsetsHeight(taskbarHeightForIme)
         windowLayoutParams.providedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR] = reducingSize
         windowLayoutParams.providedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT] = reducingSize
     }
 
     /**
+     * WindowLayoutParams.providedInternal*Insets expects Insets that subtract from the window frame
+     * height (i.e. WindowLayoutParams#height). So for Taskbar to report bottom insets to apps, it
+     * actually provides insets from the top of its window frame.
+     * @param height The number of pixels from the bottom of the screen that Taskbar insets.
+     */
+    private fun getReducingInsetsForTaskbarInsetsHeight(height: Int): Insets {
+        return Insets.of(0, windowLayoutParams.height - height, 0, 0)
+    }
+
+    /**
      * Called to update the touchable insets.
      * @see InsetsInfo.setTouchableInsets
      */
@@ -88,18 +109,11 @@
         } else if (controllers.taskbarDragController.isSystemDragInProgress) {
             // Let touches pass through us.
             insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
-        } else if (AbstractFloatingView.getOpenView<AbstractFloatingView?>(
-                context,
-                AbstractFloatingView.TYPE_TASKBAR_ALL_APPS
-            ) != null
-        ) {
+        } else if (AbstractFloatingView.hasOpenView(context, TYPE_TASKBAR_ALL_APPS)) {
             // Let touches pass through us.
             insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
         } else if (controllers.taskbarViewController.areIconsVisible()
-            || AbstractFloatingView.getOpenView<AbstractFloatingView?>(
-                context,
-                AbstractFloatingView.TYPE_ALL
-            ) != null
+            || AbstractFloatingView.hasOpenView(context, AbstractFloatingView.TYPE_ALL)
             || context.isNavBarKidsModeActive
         ) {
             // Taskbar has some touchable elements, take over the full taskbar area
@@ -120,6 +134,10 @@
     override fun dumpLogs(prefix: String, pw: PrintWriter) {
         pw.println(prefix + "TaskbarInsetsController:")
         pw.println("$prefix\twindowHeight=${windowLayoutParams.height}")
+        pw.println("$prefix\tprovidedInternalInsets[ITYPE_EXTRA_NAVIGATION_BAR]=" +
+                "${windowLayoutParams.providedInternalInsets[ITYPE_EXTRA_NAVIGATION_BAR]}")
+        pw.println("$prefix\tprovidedInternalInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]=" +
+                "${windowLayoutParams.providedInternalInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]}")
         pw.println("$prefix\tprovidedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR]=" +
                 "${windowLayoutParams.providedInternalImeInsets[ITYPE_EXTRA_NAVIGATION_BAR]}")
         pw.println("$prefix\tprovidedInternalImeInsets[ITYPE_BOTTOM_TAPPABLE_ELEMENT]=" +
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 1ccad78..c6dbc87 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -159,7 +159,7 @@
                 mPopupDataProvider.getNotificationKeysForItem(item),
                 // TODO (b/198438631): add support for INSTALL shortcut factory
                 getSystemShortcuts()
-                        .map(s -> s.getShortcut(context, item))
+                        .map(s -> s.getShortcut(context, item, icon))
                         .filter(Objects::nonNull)
                         .collect(Collectors.toList()));
         container.requestFocus();
@@ -242,7 +242,8 @@
      */
     private SystemShortcut.Factory<BaseTaskbarContext> createSplitShortcutFactory(
             SplitPositionOption position) {
-        return (context, itemInfo) -> new TaskbarSplitShortcut(context, itemInfo, position);
+        return (context, itemInfo, originalView) -> new TaskbarSplitShortcut(context, itemInfo,
+                originalView, position);
     }
 
      /**
@@ -253,9 +254,9 @@
     private static class TaskbarSplitShortcut extends SystemShortcut<BaseTaskbarContext> {
         private final SplitPositionOption mPosition;
 
-        TaskbarSplitShortcut(BaseTaskbarContext context, ItemInfo itemInfo,
+        TaskbarSplitShortcut(BaseTaskbarContext context, ItemInfo itemInfo, View originalView,
                 SplitPositionOption position) {
-            super(position.iconResId, position.textResId, context, itemInfo);
+            super(position.iconResId, position.textResId, context, itemInfo, originalView);
             mPosition = position;
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index be67136..f34759d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -30,6 +30,7 @@
 import android.content.SharedPreferences;
 import android.util.Log;
 import android.view.ViewConfiguration;
+import android.view.WindowInsets;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
@@ -280,6 +281,8 @@
 
     /**
      * Returns the height that taskbar will inset when inside apps.
+     * @see WindowInsets.Type#navigationBars()
+     * @see WindowInsets.Type#systemBars()
      */
     public int getContentHeightToReportToApps() {
         if (supportsVisualStashing() && hasAnyFlag(FLAGS_REPORT_STASHED_INSETS_TO_APP)) {
@@ -304,6 +307,15 @@
         return mUnstashedHeight;
     }
 
+    /**
+     * Returns the height that taskbar will inset when inside apps.
+     * @see WindowInsets.Type#tappableElement()
+     */
+    public int getTappableHeightToReportToApps() {
+        int contentHeight = getContentHeightToReportToApps();
+        return contentHeight <= mStashedHeight ? 0 : contentHeight;
+    }
+
     public int getStashedHeight() {
         return mStashedHeight;
     }
@@ -442,7 +454,7 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 mIsStashed = isStashed;
-                onIsStashed(mIsStashed);
+                onIsStashedChanged(mIsStashed);
             }
 
             @Override
@@ -489,8 +501,11 @@
                 .setDuration(TASKBAR_HINT_STASH_DURATION).start();
     }
 
-    private void onIsStashed(boolean isStashed) {
-        mControllers.stashedHandleViewController.onIsStashed(isStashed);
+    private void onIsStashedChanged(boolean isStashed) {
+        mControllers.runAfterInit(() -> {
+            mControllers.stashedHandleViewController.onIsStashedChanged(isStashed);
+            mControllers.taskbarInsetsController.onTaskbarWindowHeightOrInsetsChanged();
+        });
     }
 
     public void applyState() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index f32b315..7c52e80 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -220,8 +220,7 @@
                     (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
             boolean visible = (state == NORMAL || state == OVERVIEW)
                     && (willUserBeActive || isUserActive())
-                    && !profile.isVerticalBarLayout()
-                    && profile.isPhone && !profile.isLandscape;
+                    && !profile.isVerticalBarLayout();
             UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0,
                     profile.hotseatBarSizePx);
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 947d3e2..86f26c3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -107,7 +107,6 @@
      */
     private void handleSplitSelectionState(@NonNull LauncherState toState,
             @Nullable PendingAnimation builder) {
-        LauncherState currentState = mLauncher.getStateManager().getState();
         boolean animate = builder != null;
         PagedOrientationHandler orientationHandler =
                 ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
@@ -116,7 +115,7 @@
                         TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
                         mLauncher.getDeviceProfile());
 
-        if (isSplitSelectionState(currentState, toState)) {
+        if (toState == OVERVIEW_SPLIT_SELECT) {
             // Animation to "dismiss" selected taskView
             PendingAnimation splitSelectInitAnimation = mRecentsView.createSplitSelectInitAnimation(
                     toState.getTransitionDuration(mLauncher));
@@ -125,30 +124,18 @@
                     toState.getSplitSelectTranslation(mLauncher), LINEAR);
             splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR);
 
-            if (!animate && isSplitSelectionState(currentState, toState)) {
+            if (!animate) {
                 splitSelectInitAnimation.buildAnim().start();
-            } else if (animate &&
-                    isSplitSelectionState(currentState, toState)) {
+            } else {
                 builder.add(splitSelectInitAnimation.buildAnim());
             }
-        }
 
-        if (isSplitSelectionState(currentState, toState)) {
             mRecentsView.applySplitPrimaryScrollOffset();
         } else {
             mRecentsView.resetSplitPrimaryScrollOffset();
         }
     }
 
-    /**
-     * @return true if {@param toState} is {@link LauncherState#OVERVIEW_SPLIT_SELECT}
-     *          and {@param fromState} is not {@link LauncherState#OVERVIEW_SPLIT_SELECT}
-     */
-    private boolean isSplitSelectionState(@NonNull LauncherState fromState,
-            @NonNull LauncherState toState) {
-        return fromState != OVERVIEW_SPLIT_SELECT && toState == OVERVIEW_SPLIT_SELECT;
-    }
-
     private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
             LauncherState state) {
         float clearAllButtonAlpha = state.areElementsVisible(mLauncher, CLEAR_ALL_BUTTON) ? 1 : 0;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 7bfb76a..34a6821 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -21,7 +21,8 @@
 import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_PROGRESS;
+import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_ALPHA;
+import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_TRANSLATION;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU;
@@ -40,11 +41,9 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.TaskUtils;
@@ -148,16 +147,10 @@
             AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_TASK_MENU);
         } else if (mStartState == ALL_APPS) {
             AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
-            builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_PROGRESS,
-                    -mPullbackDistance / allAppsController.getShiftRange(), PULLBACK_INTERPOLATOR);
-
-            // Slightly fade out all apps content to further distinguish from scrolling.
-            StateAnimationConfig config = new StateAnimationConfig();
-            config.duration = accuracy;
-            config.setInterpolator(StateAnimationConfig.ANIM_ALL_APPS_FADE, Interpolators
-                    .mapToProgress(PULLBACK_INTERPOLATOR, 0, 0.5f));
-
-            allAppsController.setAlphas(mEndState, config, builder);
+            builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_TRANSLATION,
+                    -mPullbackDistance, PULLBACK_INTERPOLATOR);
+            builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_ALPHA,
+                    0.5f, PULLBACK_INTERPOLATOR);
         }
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
         if (topView != null) {
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 9686510..48127c0 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -19,7 +19,6 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.util.DisplayController.getNavigationMode;
 import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
@@ -62,7 +61,6 @@
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.SplitScreenBounds;
-import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -224,7 +222,7 @@
         Resources res = context.getResources();
         if (dp.isTablet) {
             Rect gridRect = new Rect();
-            calculateGridSize(context, dp, gridRect);
+            calculateGridSize(dp, gridRect);
 
             PointF taskDimension = getTaskDimension(context, dp);
             float scale = gridRect.height() / taskDimension.y;
@@ -238,15 +236,15 @@
             int taskMargin = dp.overviewTaskMarginPx;
             calculateTaskSizeInternal(context, dp,
                     dp.overviewTaskThumbnailTopMarginPx,
-                    getOverviewActionsHeight(context, dp),
+                    dp.getOverviewActionsClaimedSpace(),
                     res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
+                    Gravity.CENTER,
                     outRect);
         }
     }
 
-    private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
-            int claimedSpaceAbove, int claimedSpaceBelow, int minimumHorizontalPadding,
-            Rect outRect) {
+    private void calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove,
+            int claimedSpaceBelow, int minimumHorizontalPadding, int gravity, Rect outRect) {
         PointF taskDimension = getTaskDimension(context, dp);
         Rect insets = dp.getInsets();
 
@@ -264,7 +262,7 @@
         int outWidth = Math.round(scale * taskDimension.x);
         int outHeight = Math.round(scale * taskDimension.y);
 
-        Gravity.apply(Gravity.CENTER, outWidth, outHeight, potentialTaskRect, outRect);
+        Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect);
     }
 
     private static PointF getTaskDimension(Context context, DeviceProfile dp) {
@@ -314,10 +312,10 @@
     /**
      * Calculates the overview grid size for the provided device configuration.
      */
-    public final void calculateGridSize(Context context, DeviceProfile dp, Rect outRect) {
+    public final void calculateGridSize(DeviceProfile dp, Rect outRect) {
         Rect insets = dp.getInsets();
         int topMargin = dp.overviewTaskThumbnailTopMarginPx;
-        int bottomMargin = getOverviewActionsHeight(context, dp);
+        int bottomMargin = dp.getOverviewActionsClaimedSpace();
         int sideMargin = dp.overviewGridSideMargin;
 
         outRect.set(0, 0, dp.widthPx, dp.heightPx);
@@ -352,21 +350,17 @@
      * Calculates the modal taskView size for the provided device configuration
      */
     public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) {
+        calculateTaskSize(context, dp, outRect);
+        float maxScale = context.getResources().getFloat(R.dimen.overview_modal_max_scale);
         calculateTaskSizeInternal(
                 context, dp,
                 dp.overviewTaskMarginPx,
-                getOverviewActionsHeight(context, dp),
-                dp.overviewTaskMarginPx,
+                dp.heightPx - outRect.bottom - dp.getInsets().bottom,
+                Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2),
+                Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM,
                 outRect);
     }
 
-    /** Gets the space that the overview actions will take, including bottom margin. */
-    private int getOverviewActionsHeight(Context context, DeviceProfile dp) {
-        return OverviewActionsView.getOverviewActionsBottomMarginPx(getNavigationMode(context), dp)
-                + OverviewActionsView.getOverviewActionsTopMarginPx(getNavigationMode(context), dp)
-                + dp.overviewActionsHeight;
-    }
-
     /**
      * Called when the gesture ends and the animation starts towards the given target. Used to add
      * an optional additional animation with the same duration.
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 921674a..6e81b6f 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -29,7 +29,6 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Handler;
-import android.util.MathUtils;
 import android.util.Pair;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
@@ -195,10 +194,10 @@
         float followWidth = screenWidth - dX;
         // The 'progress width' is the width of the window if it strictly linearly interpolates
         // to minimum scale base on progress.
-        float progressWidth = MathUtils.lerp(1, MIN_WINDOW_SCALE, progress) * screenWidth;
+        float progressWidth = Utilities.mapRange(progress, 1, MIN_WINDOW_SCALE) * screenWidth;
         // The final width is derived from interpolating between the follow with and progress width
         // using gesture progress.
-        float width = MathUtils.lerp(followWidth, progressWidth, progress);
+        float width = Utilities.mapRange(progress, followWidth, progressWidth);
         float height = screenHeight / screenWidth * width;
         float deltaYRatio = (event.getTouchY() - mInitialTouchPos.y) / screenHeight;
         // Base the window movement in the Y axis on the touch movement in the Y axis.
@@ -221,13 +220,15 @@
             return;
         }
         mCurrentRect.set(
-                MathUtils.lerp(mCancelRect.left, mStartRect.left, progress),
-                MathUtils.lerp(mCancelRect.top, mStartRect.top, progress),
-                MathUtils.lerp(mCancelRect.right, mStartRect.right, progress),
-                MathUtils.lerp(mCancelRect.bottom, mStartRect.bottom, progress));
+                Utilities.mapRange(progress, mCancelRect.left, mStartRect.left),
+                Utilities.mapRange(progress, mCancelRect.top, mStartRect.top),
+                Utilities.mapRange(progress, mCancelRect.right, mStartRect.right),
+                Utilities.mapRange(progress, mCancelRect.bottom, mStartRect.bottom));
 
+        float endCornerRadius = Utilities.mapRange(
+                mBackProgress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
         float cornerRadius = Utilities.mapRange(
-                progress, mWindowScaleEndCornerRadius, mWindowScaleStartCornerRadius);
+                progress, endCornerRadius, mWindowScaleStartCornerRadius);
         applyTransform(mCurrentRect, cornerRadius);
     }
 
@@ -267,12 +268,15 @@
             mLauncher.getStateManager().moveToRestState();
         }
 
+        float cornerRadius = Utilities.mapRange(
+                mBackProgress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
         Pair<RectFSpringAnim, AnimatorSet> pair =
                 mQuickstepTransitionManager.createWallpaperOpenAnimations(
                     new RemoteAnimationTargetCompat[]{mBackTarget},
                     new RemoteAnimationTargetCompat[]{},
                     false /* fromUnlock */,
-                    mCurrentRect);
+                    mCurrentRect,
+                    cornerRadius);
         startTransitionAnimations(pair.first, pair.second);
         mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
     }
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 300f085..3803f03 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -256,7 +256,7 @@
     private BaseIconFactory getIconFactory() {
         if (mIconFactory == null) {
             mIconFactory = new BaseIconFactory(mContext,
-                    DisplayController.INSTANCE.get(mContext).getInfo().densityDpi,
+                    DisplayController.INSTANCE.get(mContext).getInfo().getDensityDpi(),
                     mContext.getResources().getDimensionPixelSize(R.dimen.taskbar_icon_size));
         }
         return mIconFactory;
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 2d1f17c..d94e5f1 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -118,8 +118,8 @@
      * * There aren't at least 2 tasks in overview to show split options for
      * * Device is in "Lock task mode"
      * * The taskView to show split options for is the focused task AND we haven't started
-     *   scrolling in overview (if we haven't scrolled, there's a split overview action button so
-     *   we don't need this menu option)
+     * scrolling in overview (if we haven't scrolled, there's a split overview action button so
+     * we don't need this menu option)
      */
     private static void addSplitOptions(List<SystemShortcut> outShortcuts,
             BaseDraggingActivity activity, TaskView taskView, DeviceProfile deviceProfile) {
@@ -156,13 +156,15 @@
      * Subclasses can attach any system listeners in this method, must be paired with
      * {@link #removeListeners()}
      */
-    public void initListeners() { }
+    public void initListeners() {
+    }
 
     /**
      * Subclasses should remove any system listeners in this method, must be paired with
      * {@link #initListeners()}
      */
-    public void removeListeners() { }
+    public void removeListeners() {
+    }
 
     /** Note that these will be shown in order from top to bottom, if available for the task. */
     private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
@@ -189,7 +191,7 @@
             mApplicationContext = taskThumbnailView.getContext().getApplicationContext();
             mThumbnailView = taskThumbnailView;
             mImageApi = new ImageActionsApi(
-                mApplicationContext, mThumbnailView::getThumbnail);
+                    mApplicationContext, mThumbnailView::getThumbnail);
         }
 
         protected T getActionsView() {
@@ -263,7 +265,8 @@
         /**
          * Gets the modal state system shortcut.
          */
-        public SystemShortcut getModalStateSystemShortcut(WorkspaceItemInfo itemInfo) {
+        public SystemShortcut getModalStateSystemShortcut(WorkspaceItemInfo itemInfo,
+                View original) {
             return null;
         }
 
@@ -277,9 +280,10 @@
          * Gets the system shortcut for the screenshot that will be added to the task menu.
          */
         public SystemShortcut getScreenshotShortcut(BaseDraggingActivity activity,
-                ItemInfo iteminfo) {
-            return new ScreenshotSystemShortcut(activity, iteminfo);
+                ItemInfo iteminfo, View originalView) {
+            return new ScreenshotSystemShortcut(activity, iteminfo, originalView);
         }
+
         /**
          * Gets the task snapshot as it is displayed on the screen.
          *
@@ -320,8 +324,10 @@
 
             private final BaseDraggingActivity mActivity;
 
-            ScreenshotSystemShortcut(BaseDraggingActivity activity, ItemInfo itemInfo) {
-                super(R.drawable.ic_screenshot, R.string.action_screenshot, activity, itemInfo);
+            ScreenshotSystemShortcut(BaseDraggingActivity activity, ItemInfo itemInfo,
+                    View originalView) {
+                super(R.drawable.ic_screenshot, R.string.action_screenshot, activity, itemInfo,
+                        originalView);
                 mActivity = activity;
             }
 
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index e731b79..e807e26 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -49,7 +49,6 @@
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.shared.recents.view.RecentsTransition;
-import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -78,7 +77,7 @@
                             TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()),
                             taskContainer.getA11yNodeId()
                     );
-            return new AppInfo(activity, taskContainer.getItemInfo(), accessibilityInfo);
+            return new AppInfo(activity, taskContainer.getItemInfo(), taskView, accessibilityInfo);
         }
 
         @Override
@@ -123,7 +122,7 @@
         private final SplitPositionOption mSplitPositionOption;
         public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView,
                 SplitPositionOption option) {
-            super(option.iconResId, option.textResId, target, taskView.getItemInfo());
+            super(option.iconResId, option.textResId, target, taskView.getItemInfo(), taskView);
             mTaskView = taskView;
             mSplitPositionOption = option;
         }
@@ -147,7 +146,8 @@
         public MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity,
                 TaskIdAttributeContainer taskContainer, MultiWindowFactory factory,
                 LauncherEvent launcherEvent) {
-            super(iconRes, textRes, activity, taskContainer.getItemInfo());
+            super(iconRes, textRes, activity, taskContainer.getItemInfo(),
+                    taskContainer.getTaskView());
             mLauncherEvent = launcherEvent;
             mHandler = new Handler(Looper.getMainLooper());
             mTaskView = taskContainer.getTaskView();
@@ -320,7 +320,7 @@
         public PinSystemShortcut(BaseDraggingActivity target,
                 TaskIdAttributeContainer taskContainer) {
             super(R.drawable.ic_pin, R.string.recent_task_option_pin, target,
-                    taskContainer.getItemInfo());
+                    taskContainer.getItemInfo(), taskContainer.getTaskView());
             mTaskView = taskContainer.getTaskView();
         }
 
@@ -337,20 +337,23 @@
 
     TaskShortcutFactory INSTALL = (activity, taskContainer) ->
             InstantAppResolver.newInstance(activity).isInstantApp(activity,
-                 taskContainer.getTask().getTopComponent().getPackageName())
-                    ? new SystemShortcut.Install(activity, taskContainer.getItemInfo()) : null;
+                    taskContainer.getTask().getTopComponent().getPackageName())
+                    ? new SystemShortcut.Install(activity, taskContainer.getItemInfo(),
+                    taskContainer.getTaskView()) : null;
 
     TaskShortcutFactory WELLBEING = (activity, taskContainer) ->
-            WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, taskContainer.getItemInfo());
+            WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, taskContainer.getItemInfo(),
+                    taskContainer.getTaskView());
 
     TaskShortcutFactory SCREENSHOT = (activity, taskContainer) ->
             taskContainer.getThumbnailView().getTaskOverlay()
-                    .getScreenshotShortcut(activity, taskContainer.getItemInfo());
+                    .getScreenshotShortcut(activity, taskContainer.getItemInfo(),
+                            taskContainer.getTaskView());
 
     TaskShortcutFactory MODAL = (activity, taskContainer) -> {
         if (ENABLE_OVERVIEW_SELECTIONS.get()) {
-            return taskContainer.getThumbnailView()
-                    .getTaskOverlay().getModalStateSystemShortcut(taskContainer.getItemInfo());
+            return taskContainer.getThumbnailView().getTaskOverlay().getModalStateSystemShortcut(
+                    taskContainer.getItemInfo(), taskContainer.getTaskView());
         }
         return null;
     };
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 29f2123..6179b81 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -38,7 +38,6 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.launcher3.testing.TestProtocol.BAD_STATE;
-import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
@@ -501,7 +500,7 @@
         }
 
         for (int i = 0; i < nonAppTargets.length; ++i) {
-            final SurfaceControl leash = appTargets[i].leash;
+            final SurfaceControl leash = nonAppTargets[i].leash;
             if (nonAppTargets[i].windowType == TYPE_DOCK_DIVIDER && leash != null) {
                 openingTargets.add(leash);
             }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index c9ee2db..3e68c7f 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -221,6 +221,9 @@
         setOverviewStateEnabled(true);
         setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
         setOverviewFullscreenEnabled(toState.isFullScreen());
+        if (toState == MODAL_TASK) {
+            setOverviewSelectEnabled(true);
+        }
         Log.d(BAD_STATE, "FRV onStateTransitionStart setFreezeVisibility=true, toState=" + toState);
         setFreezeViewVisibility(true);
     }
@@ -236,6 +239,9 @@
         Log.d(BAD_STATE, "FRV onStateTransitionComplete setFreezeVisibility=false, finalState="
                 + finalState);
         setFreezeViewVisibility(false);
+        if (finalState != MODAL_TASK) {
+            setOverviewSelectEnabled(false);
+        }
 
         if (isOverlayEnabled) {
             runActionOnRemoteHandles(remoteTargetHandle ->
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index b686505..9ba5577 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -117,7 +117,22 @@
                 mTutorialFragment.closeTutorial();
             }
         } else if (mTutorialType == BACK_NAVIGATION) {
-            showFeedback(R.string.back_gesture_feedback_swipe_in_nav_bar);
+            switch (result) {
+                case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
+                case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE:
+                case HOME_OR_OVERVIEW_CANCELLED:
+                    showFeedback(R.string.back_gesture_feedback_swipe_too_far_from_edge);
+                    break;
+                case HOME_GESTURE_COMPLETED:
+                case OVERVIEW_GESTURE_COMPLETED:
+                case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
+                case ASSISTANT_COMPLETED:
+                case ASSISTANT_NOT_STARTED_BAD_ANGLE:
+                case ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT:
+                default:
+                    showFeedback(R.string.back_gesture_feedback_swipe_in_nav_bar);
+
+            }
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 45aaf35..306ebd7 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -100,6 +100,9 @@
         setOverviewStateEnabled(toState.overviewUi);
         setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
         setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
+        if (toState == OVERVIEW_MODAL_TASK) {
+            setOverviewSelectEnabled(true);
+        }
         Log.d(BAD_STATE, "LRV onStateTransitionStart setFreezeVisibility=true, toState=" + toState);
         setFreezeViewVisibility(true);
     }
@@ -115,6 +118,9 @@
         Log.d(BAD_STATE, "LRV onStateTransitionComplete setFreezeVisibility=false, finalState="
                 + finalState);
         setFreezeViewVisibility(false);
+        if (finalState != OVERVIEW_MODAL_TASK) {
+            setOverviewSelectEnabled(false);
+        }
 
         if (isOverlayEnabled) {
             runActionOnRemoteHandles(remoteTargetHandle ->
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 99a2d6f..49a540f 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -16,8 +16,6 @@
 
 package com.android.quickstep.views;
 
-import static com.android.launcher3.util.DisplayController.NavigationMode.THREE_BUTTONS;
-
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -99,6 +97,7 @@
 
     @Nullable
     protected DeviceProfile mDp;
+    private final Rect mTaskSize = new Rect();
 
     public OverviewActionsView(Context context) {
         this(context, null);
@@ -202,8 +201,10 @@
      * Offsets OverviewActionsView horizontal position based on 3 button nav container in taskbar.
      */
     private void updatePadding() {
-        boolean alignFor3ButtonTaskbar = mDp.isTaskbarPresent &&
-                DisplayController.getNavigationMode(getContext()) == THREE_BUTTONS;
+        if (mDp == null) {
+            return;
+        }
+        boolean alignFor3ButtonTaskbar = mDp.isTaskbarPresent && !mDp.isGestureMode;
         if (alignFor3ButtonTaskbar) {
             // Add extra horizontal spacing
             int additionalPadding = ApiWrapper.getHotseatEndOffset(getContext());
@@ -225,15 +226,34 @@
         LayoutParams actionParams = (LayoutParams) findViewById(
                 R.id.action_buttons).getLayoutParams();
         actionParams.setMargins(
-                actionParams.leftMargin, getOverviewActionsTopMarginPx(mode, mDp),
-                actionParams.rightMargin, getOverviewActionsBottomMarginPx(mode, mDp));
+                actionParams.leftMargin, mDp.overviewActionsTopMarginPx,
+                actionParams.rightMargin, getBottomMargin());
+    }
+
+    private int getBottomMargin() {
+        if (mDp == null) {
+            return 0;
+        }
+
+        if (mDp.isVerticalBarLayout()) {
+            return mDp.getInsets().bottom;
+        }
+
+        if (!mDp.isGestureMode && mDp.isTaskbarPresent) {
+            return mDp.getOverviewActionsClaimedSpaceBelow();
+        }
+
+        // Align to bottom of task Rect.
+        return mDp.heightPx - mTaskSize.bottom - mDp.overviewActionsTopMarginPx
+                - mDp.overviewActionsHeight;
     }
 
     /**
-     * Set the device profile for this view to draw with.
+     * Updates device profile and task size for this view to draw with.
      */
-    public void setDp(DeviceProfile dp) {
+    public void updateDimension(DeviceProfile dp, Rect taskSize) {
         mDp = dp;
+        mTaskSize.set(taskSize);
         updateVerticalMargin(DisplayController.getNavigationMode(getContext()));
 
         LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
@@ -257,45 +277,4 @@
         mSplitButton.setVisibility(visible ? VISIBLE : GONE);
         findViewById(R.id.action_split_space).setVisibility(visible ? VISIBLE : GONE);
     }
-
-    /** Get the top margin associated with the action buttons in Overview. */
-    public static int getOverviewActionsTopMarginPx(NavigationMode mode, DeviceProfile dp) {
-        // In vertical bar, use the smaller task margin for the top regardless of mode
-        if (dp.isVerticalBarLayout()) {
-            return dp.overviewTaskMarginPx;
-        }
-
-        if (mode == NavigationMode.THREE_BUTTONS) {
-            return dp.overviewActionsMarginThreeButtonPx;
-        }
-
-        return dp.overviewActionsTopMarginGesturePx;
-    }
-
-    /** Get the bottom margin associated with the action buttons in Overview. */
-    public static int getOverviewActionsBottomMarginPx(NavigationMode mode, DeviceProfile dp) {
-        int bottomInset = dp.getInsets().bottom;
-
-        if (dp.isVerticalBarLayout()) {
-            return bottomInset;
-        }
-
-        if (mode == NavigationMode.THREE_BUTTONS) {
-            int bottomMargin = dp.overviewActionsMarginThreeButtonPx + bottomInset;
-            if (dp.isTaskbarPresent) {
-                // Align vertically, using taskbar height + mDp.taskbarOffsetY() to estimate where
-                // the button nav top is.
-                int actionsTop = (dp.heightPx - bottomMargin - bottomInset)
-                        - dp.overviewActionsHeight;
-                int navTop = dp.heightPx - (dp.taskbarSize + dp.getTaskbarOffsetY());
-                bottomMargin -=
-                        navTop - actionsTop + ((dp.taskbarSize - dp.overviewActionsHeight) / 2);
-            }
-            return bottomMargin;
-        }
-
-        // There is no bottom inset when taskbar is present, use stashed taskbar as padding instead.
-        return dp.overviewActionsBottomMarginGesturePx
-                + (dp.isTaskbarPresent ? dp.stashedTaskbarSize : bottomInset);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 5f715f8..5d71ebd 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -455,6 +455,7 @@
     protected boolean mFreezeViewVisibility;
     private boolean mOverviewGridEnabled;
     private boolean mOverviewFullscreenEnabled;
+    private boolean mOverviewSelectEnabled;
 
     private float mAdjacentPageHorizontalOffset = 0;
     protected float mTaskViewsSecondaryTranslation = 0;
@@ -1396,6 +1397,9 @@
         if (taskGroups == null || taskGroups.isEmpty()) {
             removeTasksViewsAndClearAllButton();
             onTaskStackUpdated();
+            // With all tasks removed, touch handling in PagedView is disabled and we need to reset
+            // touch state or otherwise values will be obsolete.
+            resetTouchState();
             return;
         }
 
@@ -1634,11 +1638,11 @@
         // Propagate DeviceProfile change event.
         runActionOnRemoteHandles(
                 remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator().setDp(dp));
-        mActionsView.setDp(dp);
         mOrientationState.setDeviceProfile(dp);
 
         // Update RecentsView and TaskView's DeviceProfile dependent layout.
         updateOrientationHandler();
+        mActionsView.updateDimension(dp, mLastComputedTaskSize);
     }
 
     private void updateOrientationHandler() {
@@ -1703,7 +1707,7 @@
                 dp.widthPx - mInsets.right - mTempRect.right,
                 dp.heightPx - mInsets.bottom - mTempRect.bottom);
 
-        mSizeStrategy.calculateGridSize(mActivity, mActivity.getDeviceProfile(),
+        mSizeStrategy.calculateGridSize(mActivity.getDeviceProfile(),
                 mLastComputedGridSize);
         mSizeStrategy.calculateGridTaskSize(mActivity, mActivity.getDeviceProfile(),
                 mLastComputedGridTaskSize, mOrientationHandler);
@@ -1761,7 +1765,8 @@
      * Returns the size of task selected to enter modal state.
      */
     public Point getSelectedTaskSize() {
-        mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), mTempRect);
+        mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(),
+                mTempRect);
         return new Point(mTempRect.width(), mTempRect.height());
     }
 
@@ -3686,10 +3691,7 @@
 
         // Update the pivots such that when the task is scaled, it fills the full page
         getTaskSize(mTempRect);
-        getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
-                mActivity.getDeviceProfile(), mTempPointF);
-        setPivotX(mTempPointF.x);
-        setPivotY(mTempPointF.y);
+        updatePivots();
         setTaskModalness(mTaskModalness);
         mLastComputedTaskStartPushOutDistance = null;
         mLastComputedTaskEndPushOutDistance = null;
@@ -3701,6 +3703,18 @@
                 : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
     }
 
+    private void updatePivots() {
+        if (mOverviewSelectEnabled) {
+            setPivotX(mLastComputedTaskSize.centerX());
+            setPivotY(mLastComputedTaskSize.bottom);
+        } else {
+            getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
+                    mActivity.getDeviceProfile(), mTempPointF);
+            setPivotX(mTempPointF.x);
+            setPivotY(mTempPointF.y);
+        }
+    }
+
     private void updatePageOffsets() {
         float offset = mAdjacentPageHorizontalOffset;
         float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness);
@@ -4863,6 +4877,17 @@
     }
 
     /**
+     * Update whether RecentsView is in select mode. Should be enabled before transitioning to
+     * select mode, and only disabled after transitioning from select mode.
+     */
+    public void setOverviewSelectEnabled(boolean overviewSelectEnabled) {
+        if (mOverviewSelectEnabled != overviewSelectEnabled) {
+            mOverviewSelectEnabled = overviewSelectEnabled;
+            updatePivots();
+        }
+    }
+
+    /**
      * Switch the current running task view to static snapshot mode,
      * capturing the snapshot at the same time.
      */
diff --git a/res/anim-v33/shared_x_axis_activity_close_enter.xml b/res/anim-v33/shared_x_axis_activity_close_enter.xml
new file mode 100644
index 0000000..94ef06c
--- /dev/null
+++ b/res/anim-v33/shared_x_axis_activity_close_enter.xml
@@ -0,0 +1,42 @@
+<?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.
+  -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false"
+    android:showBackdrop="true">
+
+    <alpha
+        android:fromAlpha="0.0"
+        android:toAlpha="1.0"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/standard_decelerate"
+        android:startOffset="100"
+        android:duration="350" />
+
+    <translate
+        android:fromXDelta="-25%"
+        android:toXDelta="0"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:startOffset="0"
+        android:duration="450" />
+
+</set>
\ No newline at end of file
diff --git a/res/anim-v33/shared_x_axis_activity_close_exit.xml b/res/anim-v33/shared_x_axis_activity_close_exit.xml
new file mode 100644
index 0000000..19eb09e
--- /dev/null
+++ b/res/anim-v33/shared_x_axis_activity_close_exit.xml
@@ -0,0 +1,41 @@
+<?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.
+  -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+
+    <alpha
+        android:fromAlpha="1.0"
+        android:toAlpha="0.0"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/standard_accelerate"
+        android:startOffset="0"
+        android:duration="100" />
+
+    <translate
+        android:fromXDelta="0"
+        android:toXDelta="25%"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:startOffset="0"
+        android:duration="450" />
+
+</set>
\ No newline at end of file
diff --git a/res/anim-v33/shared_x_axis_activity_open_enter.xml b/res/anim-v33/shared_x_axis_activity_open_enter.xml
new file mode 100644
index 0000000..f699cec
--- /dev/null
+++ b/res/anim-v33/shared_x_axis_activity_open_enter.xml
@@ -0,0 +1,42 @@
+<?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.
+  -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false"
+    android:showBackdrop="true">
+
+    <alpha
+        android:fromAlpha="0.0"
+        android:toAlpha="1.0"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/standard_decelerate"
+        android:startOffset="100"
+        android:duration="350" />
+
+    <translate
+        android:fromXDelta="25%"
+        android:toXDelta="0"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:startOffset="0"
+        android:duration="450" />
+
+</set>
\ No newline at end of file
diff --git a/res/anim-v33/shared_x_axis_activity_open_exit.xml b/res/anim-v33/shared_x_axis_activity_open_exit.xml
new file mode 100644
index 0000000..85988ec
--- /dev/null
+++ b/res/anim-v33/shared_x_axis_activity_open_exit.xml
@@ -0,0 +1,41 @@
+<?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.
+  -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false">
+
+    <alpha
+        android:fromAlpha="1.0"
+        android:toAlpha="0.0"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/standard_accelerate"
+        android:startOffset="0"
+        android:duration="100" />
+
+    <translate
+        android:fromXDelta="0"
+        android:toXDelta="-25%"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@interpolator/fast_out_extra_slow_in"
+        android:startOffset="0"
+        android:duration="450" />
+
+</set>
\ No newline at end of file
diff --git a/res/interpolator/fast_out_extra_slow_in.xml b/res/interpolator/fast_out_extra_slow_in.xml
new file mode 100644
index 0000000..f296a82
--- /dev/null
+++ b/res/interpolator/fast_out_extra_slow_in.xml
@@ -0,0 +1,19 @@
+<?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
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1"/>
\ No newline at end of file
diff --git a/res/interpolator/standard_accelerate.xml b/res/interpolator/standard_accelerate.xml
new file mode 100644
index 0000000..394393d
--- /dev/null
+++ b/res/interpolator/standard_accelerate.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.3"
+    android:controlY1="0"
+    android:controlX2="1"
+    android:controlY2="1"/>
\ No newline at end of file
diff --git a/res/interpolator/standard_decelerate.xml b/res/interpolator/standard_decelerate.xml
new file mode 100644
index 0000000..579f4f5
--- /dev/null
+++ b/res/interpolator/standard_decelerate.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0"
+    android:controlY1="0"
+    android:controlX2="0"
+    android:controlY2="1"/>
\ No newline at end of file
diff --git a/res/values-v33/style.xml b/res/values-v33/style.xml
new file mode 100644
index 0000000..bd48468
--- /dev/null
+++ b/res/values-v33/style.xml
@@ -0,0 +1,40 @@
+<?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>
+    <style name="HomeSettings.Theme" parent="@android:style/Theme.DeviceDefault.Settings">
+        <item name="android:listPreferredItemPaddingEnd">16dp</item>
+        <item name="android:listPreferredItemPaddingStart">24dp</item>
+        <item name="android:navigationBarColor">@android:color/transparent</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+        <item name="android:switchStyle">@style/HomeSettings.SwitchStyle</item>
+        <item name="android:textAppearanceListItem">@style/HomeSettings.PreferenceTitle</item>
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="preferenceTheme">@style/HomeSettings.PreferenceTheme</item>
+        <item name="android:windowAnimationStyle">@style/Animation.SharedBackground</item>
+    </style>
+
+    <style name="Animation.SharedBackground" parent="@android:style/Animation.Activity">
+        <item name="android:activityOpenEnterAnimation">@anim/shared_x_axis_activity_open_enter</item>
+        <item name="android:activityOpenExitAnimation">@anim/shared_x_axis_activity_open_exit</item>
+        <item name="android:activityCloseEnterAnimation">@anim/shared_x_axis_activity_close_enter</item>
+        <item name="android:activityCloseExitAnimation">@anim/shared_x_axis_activity_close_exit</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index dd3e08b..e85969b 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -306,6 +306,17 @@
         if not specified -->
         <attr name="allAppsBorderSpaceTwoPanelLandscapeVertical" format="float" />
 
+        <!-- alignment of hotseat to the grid.
+        Not applicable for 3 button mode when taskbar is enabled -->
+        <!-- defaults to numColumns, if not specified -->
+        <attr name="hotseatColumnSpan" format="integer" />
+        <!-- defaults to numColumns, if not specified -->
+        <attr name="hotseatColumnSpanLandscape" format="integer" />
+        <!-- defaults to numColumns, if not specified -->
+        <attr name="hotseatColumnSpanTwoPanelLandscape" format="integer" />
+        <!-- defaults to numColumns, if not specified -->
+        <attr name="hotseatColumnSpanTwoPanelPortrait" format="integer" />
+
         <!-- defaults to borderSpaceDps, if not specified -->
         <attr name="hotseatBorderSpace" format="float" />
         <!-- defaults to hotseatBorderSpace, if not specified -->
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 52ff3f0..2c3f5ed 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -368,9 +368,7 @@
     <dimen name="overview_actions_height">0dp</dimen>
     <dimen name="overview_actions_button_spacing">0dp</dimen>
     <dimen name="overview_actions_margin_gesture">0dp</dimen>
-    <dimen name="overview_actions_top_margin_gesture">0dp</dimen>
-    <dimen name="overview_actions_bottom_margin_gesture">0dp</dimen>
-    <dimen name="overview_actions_margin_three_button">0dp</dimen>
+    <dimen name="overview_actions_top_margin">0dp</dimen>
     <dimen name="overview_grid_side_margin">0dp</dimen>
     <dimen name="overview_grid_row_spacing">0dp</dimen>
     <dimen name="overview_page_spacing">0dp</dimen>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index 07ce598..258da80 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -176,6 +176,7 @@
             launcher:allAppsBorderSpaceHorizontal="8"
             launcher:allAppsBorderSpaceVertical="16"
             launcher:allAppsBorderSpaceLandscape="16"
+            launcher:hotseatColumnSpanLandscape="4"
             launcher:hotseatBorderSpace="58"
             launcher:hotseatBorderSpaceLandscape="50.4"
             launcher:canBeDefault="true" />
diff --git a/res/xml/paddings_6x5.xml b/res/xml/paddings_6x5.xml
index a958ec7..a72f554 100644
--- a/res/xml/paddings_6x5.xml
+++ b/res/xml/paddings_6x5.xml
@@ -32,7 +32,7 @@
     </device-padding>
 
     <device-padding
-        launcher:maxEmptySpace="80dp">
+        launcher:maxEmptySpace="170dp">
         <workspaceTopPadding
             launcher:a="0"
             launcher:b="20dp"/>
@@ -47,7 +47,7 @@
     </device-padding>
 
     <device-padding
-        launcher:maxEmptySpace="280dp">
+        launcher:maxEmptySpace="410dp">
         <workspaceTopPadding
             launcher:a="0"
             launcher:b="112dp"/>
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 4386f41..90869c2 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -208,6 +208,13 @@
     }
 
     /**
+     * Returns whether there is at least one view of the given type where {@link #isOpen()} == true.
+     */
+    public static boolean hasOpenView(ActivityContext activity, @FloatingViewType int type) {
+        return getOpenView(activity, type) != null;
+    }
+
+    /**
      * Returns a view matching FloatingViewType, and {@link #isOpen()} may be false (if animating
      * closed).
      */
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 74ec7ee..6302739 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -96,7 +96,6 @@
     private static final int MAX_SEARCH_LOOP_COUNT = 20;
 
     private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
-    private static final float HIGHLIGHT_SCALE = 1.16f;
 
     private final PointF mTranslationForReorderBounce = new PointF(0, 0);
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
@@ -259,6 +258,12 @@
         mDotParams.scale = 0f;
         mForceHideDot = false;
         setBackground(null);
+
+        setTag(null);
+        if (mIconLoadRequest != null) {
+            mIconLoadRequest.cancel();
+            mIconLoadRequest = null;
+        }
     }
 
     private void cancelDotScaleAnim() {
@@ -363,8 +368,7 @@
         }
     }
 
-    public void setBubbleTextHolder(
-            BubbleTextHolder bubbleTextHolder) {
+    public void setBubbleTextHolder(BubbleTextHolder bubbleTextHolder) {
         mBubbleTextHolder = bubbleTextHolder;
     }
 
@@ -1020,19 +1024,6 @@
         getIconBounds(mIconSize, bounds);
     }
 
-    private int getIconSizeForDisplay(int display) {
-        DeviceProfile grid = mActivity.getDeviceProfile();
-        switch (display) {
-            case DISPLAY_ALL_APPS:
-                return grid.allAppsIconSizePx;
-            case DISPLAY_FOLDER:
-                return grid.folderChildIconSizePx;
-            case DISPLAY_WORKSPACE:
-            default:
-                return grid.iconSizePx;
-        }
-    }
-
     public void getSourceVisualDragBounds(Rect bounds) {
         getIconBounds(mIconSize, bounds);
     }
@@ -1045,8 +1036,8 @@
     }
 
     private void resetIconScale() {
-        if (mIcon instanceof FastBitmapDrawable) {
-            ((FastBitmapDrawable) mIcon).resetScale();
+        if (mIcon != null) {
+            mIcon.resetScale();
         }
     }
 
diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java
index af85594..4daca8b 100644
--- a/src/com/android/launcher3/DefaultLayoutParser.java
+++ b/src/com/android/launcher3/DefaultLayoutParser.java
@@ -7,15 +7,19 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.Bundle;
+import android.os.Process;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.Thunk;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -23,6 +27,7 @@
 
 import java.io.IOException;
 import java.net.URISyntaxException;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -43,6 +48,8 @@
     private static final String ATTR_CONTAINER = "container";
     private static final String ATTR_SCREEN = "screen";
     private static final String ATTR_FOLDER_ITEMS = "folderItems";
+    private static final String ATTR_SHORTCUT_ID = "shortcutId";
+    private static final String ATTR_PACKAGE_NAME = "packageName";
 
     // TODO: Remove support for this broadcast, instead use widget options to send bind time options
     private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
@@ -178,7 +185,6 @@
         }
     }
 
-
     /**
      * Shortcut parser which allows any uri and not just web urls.
      */
@@ -189,6 +195,35 @@
         }
 
         @Override
+        public int parseAndAdd(XmlPullParser parser) {
+            final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
+            final String shortcutId = getAttributeValue(parser, ATTR_SHORTCUT_ID);
+            if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(shortcutId)) {
+                return parseAndAddDeepShortcut(shortcutId, packageName);
+            }
+            return super.parseAndAdd(parser);
+        }
+
+        /**
+         * This method parses and adds a deep shortcut.
+         * @return item id if the shortcut is successfully added else -1
+         */
+        private int parseAndAddDeepShortcut(String shortcutId, String packageName) {
+            try {
+                LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+                launcherApps.pinShortcuts(packageName, Collections.singletonList(shortcutId),
+                        Process.myUserHandle());
+                Intent intent = ShortcutKey.makeIntent(shortcutId, packageName);
+                mValues.put(Favorites.RESTORED, WorkspaceItemInfo.FLAG_RESTORED_ICON);
+                return addShortcut(null, intent, Favorites.ITEM_TYPE_DEEP_SHORTCUT);
+            } catch (Exception e) {
+                Log.e(TAG, "Unable to pin the shortcut for shortcut id = " + shortcutId
+                        + " and package name = " + packageName);
+            }
+            return -1;
+        }
+
+        @Override
         protected Intent parseIntent(XmlPullParser parser) {
             String uri = null;
             try {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 33bb0a5..88030ae 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -158,6 +158,7 @@
     public final int numShownHotseatIcons;
     public int hotseatCellHeightPx;
     private final int hotseatExtraVerticalSize;
+    private final boolean areNavButtonsInline;
     // In portrait: size = height, in landscape: size = width
     public int hotseatBarSizePx;
     public int hotseatBarTopPaddingPx;
@@ -195,9 +196,7 @@
     public int overviewTaskIconDrawableSizeGridPx;
     public int overviewTaskThumbnailTopMarginPx;
     public final int overviewActionsHeight;
-    public final int overviewActionsMarginThreeButtonPx;
-    public final int overviewActionsTopMarginGesturePx;
-    public final int overviewActionsBottomMarginGesturePx;
+    public final int overviewActionsTopMarginPx;
     public final int overviewActionsButtonSpacing;
     public int overviewPageSpacing;
     public int overviewRowSpacing;
@@ -360,7 +359,7 @@
 
         // We shrink hotseat sizes regardless of orientation, if nav buttons are inline and QSB
         // might be inline in either orientations, to keep hotseat size consistent across rotation.
-        boolean areNavButtonsInline = isTaskbarPresent && !isGestureMode;
+        areNavButtonsInline = isTaskbarPresent && !isGestureMode;
         if (areNavButtonsInline && canQsbInline) {
             numShownHotseatIcons = inv.numShrunkenHotseatIcons;
         } else {
@@ -375,15 +374,14 @@
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
         if (isQsbInline) {
             hotseatBarBottomPaddingPx = res.getDimensionPixelSize(R.dimen.inline_qsb_bottom_margin);
-            qsbWidth = calculateQsbWidth();
         } else {
             hotseatBarBottomPaddingPx = (isTallDevice ? res.getDimensionPixelSize(
                     R.dimen.dynamic_grid_hotseat_bottom_tall_padding)
                     : res.getDimensionPixelSize(
                             R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding))
                     + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
-            qsbWidth = 0;
         }
+
         springLoadedHotseatBarTopMarginPx = res.getDimensionPixelSize(
                 R.dimen.spring_loaded_hotseat_top_margin);
         hotseatBarSidePaddingEndPx =
@@ -392,9 +390,7 @@
         hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
         hotseatExtraVerticalSize =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size);
-        hotseatBorderSpace = pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics);
-        updateHotseatIconSize(
-                pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
+        updateHotseatIconSize(pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
 
         qsbBottomMarginOriginalPx = isScalableGrid
                 ? res.getDimensionPixelSize(R.dimen.scalable_grid_qsb_bottom_margin)
@@ -408,16 +404,14 @@
         overviewTaskIconDrawableSizeGridPx =
                 res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid);
         overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx * 2;
-        overviewActionsTopMarginGesturePx = res.getDimensionPixelSize(
-                R.dimen.overview_actions_top_margin_gesture);
-        overviewActionsBottomMarginGesturePx = res.getDimensionPixelSize(
-                R.dimen.overview_actions_bottom_margin_gesture);
+        // In vertical bar, use the smaller task margin for the top regardless of mode.
+        overviewActionsTopMarginPx = isVerticalBarLayout()
+                ? overviewTaskMarginPx
+                : res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
         overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing);
         overviewActionsButtonSpacing = res.getDimensionPixelSize(
                 R.dimen.overview_actions_button_spacing);
         overviewActionsHeight = res.getDimensionPixelSize(R.dimen.overview_actions_height);
-        overviewActionsMarginThreeButtonPx = res.getDimensionPixelSize(
-                R.dimen.overview_actions_margin_three_button);
         // Grid task's top margin is only overviewTaskIconSizePx + overviewTaskMarginGridPx, but
         // overviewTaskThumbnailTopMarginPx is applied to all TaskThumbnailView, so exclude the
         // extra  margin when calculating row spacing.
@@ -487,6 +481,10 @@
                 cellLayoutPadding);
         updateWorkspacePadding();
 
+        // Hotseat and QSB width depends on updated cellSize and workspace padding
+        hotseatBorderSpace = calculateHotseatBorderSpace();
+        qsbWidth = calculateQsbWidth();
+
         flingToDeleteThresholdVelocity = res.getDimensionPixelSize(
                 R.dimen.drag_flingToDeleteMinVelocity);
 
@@ -497,14 +495,26 @@
                 new DotRenderer(allAppsIconSizePx, dotPath, DEFAULT_DOT_SIZE);
     }
 
+    /**
+     * QSB width is always calculated because when in 3 button nav the width doesn't follow the
+     * width of the hotseat.
+     */
     private int calculateQsbWidth() {
-        int columns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
+        if (isQsbInline) {
+            int columns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
+            return getIconToIconWidthForColumns(columns)
+                    - iconSizePx * numShownHotseatIcons
+                    - hotseatBorderSpace * numShownHotseatIcons;
+        } else {
+            int columns = inv.hotseatColumnSpan[mTypeIndex];
+            return getIconToIconWidthForColumns(columns);
+        }
+    }
 
-        return cellWidthPx * columns
-                + cellLayoutBorderSpacePx.x * (columns - 1)
-                - (cellWidthPx - iconSizePx) // left and right cell space
-                - iconSizePx * numShownHotseatIcons
-                - hotseatBorderSpace * numShownHotseatIcons;
+    private int getIconToIconWidthForColumns(int columns) {
+        return columns * getCellSize().x
+                + (columns - 1) * cellLayoutBorderSpacePx.x
+                - (getCellSize().x - iconSizePx);  // left and right cell space
     }
 
     private int getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res) {
@@ -658,7 +668,7 @@
         Point workspacePadding = getTotalWorkspacePadding();
 
         // Check to see if the icons fit within the available height.
-        float usedHeight = getCellLayoutHeight();
+        float usedHeight = getCellLayoutHeightSpecification();
         final int maxHeight = getWorkspaceHeight(workspacePadding);
         float extraHeight = Math.max(0, maxHeight - usedHeight);
         float scaleY = maxHeight / usedHeight;
@@ -669,7 +679,8 @@
             // We scale to fit the cellWidth and cellHeight in the available space.
             // The benefit of scalable grids is that we can get consistent aspect ratios between
             // devices.
-            float usedWidth = getCellLayoutWidth() + (desiredWorkspaceHorizontalMarginPx * 2);
+            float usedWidth =
+                    getCellLayoutWidthSpecification() + (desiredWorkspaceHorizontalMarginPx * 2);
             // We do not subtract padding here, as we also scale the workspace padding if needed.
             scaleX = availableWidthPx / usedWidth;
             shouldScale = true;
@@ -678,19 +689,19 @@
         if (shouldScale) {
             float scale = Math.min(scaleX, scaleY);
             updateIconSize(scale, res);
-            extraHeight = Math.max(0, maxHeight - getCellLayoutHeight());
+            extraHeight = Math.max(0, maxHeight - getCellLayoutHeightSpecification());
         }
 
         updateAvailableFolderCellDimensions(res);
         return Math.round(extraHeight);
     }
 
-    private int getCellLayoutHeight() {
+    private int getCellLayoutHeightSpecification() {
         return (cellHeightPx * inv.numRows) + (cellLayoutBorderSpacePx.y * (inv.numRows - 1))
                 + cellLayoutPaddingPx.top + cellLayoutPaddingPx.bottom;
     }
 
-    private int getCellLayoutWidth() {
+    private int getCellLayoutWidthSpecification() {
         int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
         return (cellWidthPx * numColumns) + (cellLayoutBorderSpacePx.x * (numColumns - 1))
                 + cellLayoutPaddingPx.left + cellLayoutPaddingPx.right;
@@ -744,13 +755,6 @@
         // All apps
         updateAllAppsIconSize(scale, res);
 
-        // Hotseat
-        hotseatBorderSpace = pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics, scale);
-        if (isQsbInline) {
-            qsbWidth = calculateQsbWidth();
-        } else {
-            qsbWidth = 0;
-        }
         updateHotseatIconSize(iconSizePx);
 
         // Folder icon
@@ -758,6 +762,23 @@
         folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
     }
 
+    /**
+     * Hotseat width spans a certain number of columns on scalable grids.
+     * This method calculates the space between the icons to achieve that width.
+     */
+    private int calculateHotseatBorderSpace() {
+        if (!isScalableGrid) return 0;
+        //TODO(http://b/228998082) remove this when 3 button spaces are fixed
+        if (areNavButtonsInline) {
+            return pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics);
+        } else {
+            int columns = inv.hotseatColumnSpan[mTypeIndex];
+            float hotseatWidthPx = getIconToIconWidthForColumns(columns);
+            float hotseatIconsTotalPx = iconSizePx * numShownHotseatIcons;
+            return (int) (hotseatWidthPx - hotseatIconsTotalPx) / (numShownHotseatIcons - 1);
+        }
+    }
+
 
     /**
      * Updates the iconSize for allApps* variants.
@@ -920,7 +941,7 @@
     /**
      * Gets the scaled bottom of the workspace in px for the spring-loaded edit state.
      */
-    public float getWorkspaceSpringLoadShrunkBottom() {
+    private float getWorkspaceSpringLoadShrunkBottom() {
         int topOfHotseat = hotseatBarSizePx + springLoadedHotseatBarTopMarginPx;
         workspaceSpringLoadShrunkBottom =
                 heightPx - (isVerticalBarLayout() ? getVerticalHotseatLastItemBottomOffset()
@@ -931,10 +952,30 @@
     /**
      * Gets the minimum visible amount of the next workspace page when in the spring-loaded state.
      */
-    public float getWorkspaceSpringLoadedMinimumNextPageVisible() {
+    private float getWorkspaceSpringLoadedMinimumNextPageVisible() {
         return getCellSize().x / 2f;
     }
 
+    /**
+     * 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;
+        scale = Math.min(scale, 1f);
+
+        // Reduce scale if next pages would not be visible after scaling the workspace
+        int workspaceWidth = getWorkspaceWidth();
+        float scaledWorkspaceWidth = workspaceWidth * scale;
+        float maxAvailableWidth =
+                workspaceWidth - (2 * getWorkspaceSpringLoadedMinimumNextPageVisible());
+        if (scaledWorkspaceWidth > maxAvailableWidth) {
+            scale *= maxAvailableWidth / scaledWorkspaceWidth;
+        }
+        return scale;
+    }
+
     public int getWorkspaceWidth() {
         return getWorkspaceWidth(getTotalWorkspacePadding());
     }
@@ -1053,6 +1094,13 @@
                 mHotseatPadding.left -= diff;
                 mHotseatPadding.right += diff;
             }
+        } else if (isScalableGrid) {
+            int sideSpacing = (availableWidthPx - qsbWidth) / 2;
+            mHotseatPadding.set(sideSpacing,
+                    hotseatBarTopPaddingPx,
+                    sideSpacing,
+                    hotseatBarSizePx - hotseatCellHeightPx - hotseatBarTopPaddingPx
+                            + mInsets.bottom);
         } else {
             // We want the edges of the hotseat to line up with the edges of the workspace, but the
             // icons in the hotseat are a different size, and so don't line up perfectly. To account
@@ -1104,6 +1152,24 @@
     }
 
     /**
+     * Returns the number of pixels required below OverviewActions excluding insets.
+     */
+    public int getOverviewActionsClaimedSpaceBelow() {
+        if (isTaskbarPresent && !isGestureMode) {
+            // Align vertically to where nav buttons are.
+            return  ((taskbarSize - overviewActionsHeight) / 2) + getTaskbarOffsetY();
+        }
+
+        return 0;
+    }
+
+    /** Gets the space that the overview actions will take, including bottom margin. */
+    public int getOverviewActionsClaimedSpace() {
+        return overviewActionsTopMarginPx + overviewActionsHeight
+                + getOverviewActionsClaimedSpaceBelow();
+    }
+
+    /**
      * @return the bounds for which the open folders should be contained within
      */
     public Rect getAbsoluteOpenFolderBounds() {
@@ -1271,6 +1337,7 @@
         writer.println(prefix + pxToDpStr("allAppsLeftRightMargin", allAppsLeftRightMargin));
 
         writer.println(prefix + pxToDpStr("hotseatBarSizePx", hotseatBarSizePx));
+        writer.println(prefix + "\tinv.hotseatColumnSpan: " + inv.hotseatColumnSpan[mTypeIndex]);
         writer.println(prefix + pxToDpStr("hotseatCellHeightPx", hotseatCellHeightPx));
         writer.println(prefix + pxToDpStr("hotseatBarTopPaddingPx", hotseatBarTopPaddingPx));
         writer.println(prefix + pxToDpStr("hotseatBarBottomPaddingPx", hotseatBarBottomPaddingPx));
@@ -1323,12 +1390,10 @@
                 overviewTaskIconDrawableSizeGridPx));
         writer.println(prefix + pxToDpStr("overviewTaskThumbnailTopMarginPx",
                 overviewTaskThumbnailTopMarginPx));
-        writer.println(prefix + pxToDpStr("overviewActionsMarginThreeButtonPx",
-                overviewActionsMarginThreeButtonPx));
-        writer.println(prefix + pxToDpStr("overviewActionsTopMarginGesturePx",
-                overviewActionsTopMarginGesturePx));
-        writer.println(prefix + pxToDpStr("overviewActionsBottomMarginGesturePx",
-                overviewActionsBottomMarginGesturePx));
+        writer.println(prefix + pxToDpStr("overviewActionsTopMarginPx",
+                overviewActionsTopMarginPx));
+        writer.println(prefix + pxToDpStr("overviewActionsHeight",
+                overviewActionsHeight));
         writer.println(prefix + pxToDpStr("overviewActionsButtonSpacing",
                 overviewActionsButtonSpacing));
         writer.println(prefix + pxToDpStr("overviewPageSpacing", overviewPageSpacing));
@@ -1344,12 +1409,14 @@
                 prefix + pxToDpStr("workspaceSpringLoadShrunkTop", workspaceSpringLoadShrunkTop));
         writer.println(prefix + pxToDpStr("workspaceSpringLoadShrunkBottom",
                 workspaceSpringLoadShrunkBottom));
+        writer.println(
+                prefix + pxToDpStr("getWorkspaceSpringLoadScale()", getWorkspaceSpringLoadScale()));
     }
 
     private static Context getContext(Context c, Info info, int orientation, WindowBounds bounds) {
         Configuration config = new Configuration(c.getResources().getConfiguration());
         config.orientation = orientation;
-        config.densityDpi = info.densityDpi;
+        config.densityDpi = info.getDensityDpi();
         config.smallestScreenWidthDp = (int) info.smallestSizeDp(bounds);
         return c.createConfigurationContext(config);
     }
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index ec3629d..2e3f26c 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -237,10 +237,8 @@
                         rightButton.getMeasuredHeight());
             } else if (dp.isPhone) {
                 // Buttons aligned to outer edges of scaled workspace.
-                float shrunkTop = dp.getWorkspaceSpringLoadShrunkTop();
-                float shrunkBottom = dp.getWorkspaceSpringLoadShrunkBottom();
-                float scale =
-                        (shrunkBottom - shrunkTop) / launcher.getWorkspace().getNormalChildHeight();
+                float scale = dp.getWorkspaceSpringLoadScale();
+
                 int workspaceWidth = (int) (launcher.getWorkspace().getNormalChildWidth() * scale);
                 int start = barCenter - (workspaceWidth / 2);
                 int end = barCenter + (workspaceWidth / 2);
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index a9db5ce..76106fc 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -173,17 +173,9 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
-        int width;
-        if (mActivity.getDeviceProfile().isQsbInline) {
-            width = mActivity.getDeviceProfile().qsbWidth;
-        } else {
-            MarginLayoutParams qsbParams = (MarginLayoutParams) mQsb.getLayoutParams();
-            width = getShortcutsAndWidgets().getMeasuredWidth()
-                    - qsbParams.getMarginStart()
-                    - qsbParams.getMarginEnd();
-        }
+        int qsbWidth = mActivity.getDeviceProfile().qsbWidth;
 
-        mQsb.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+        mQsb.measure(MeasureSpec.makeMeasureSpec(qsbWidth, MeasureSpec.EXACTLY),
                 MeasureSpec.makeMeasureSpec(mQsbHeight, MeasureSpec.EXACTLY));
     }
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 36c1797..89b1771 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -125,6 +125,7 @@
     public PointF[] borderSpaces;
     public float folderBorderSpace;
     public float[] hotseatBorderSpaces;
+    public int[] hotseatColumnSpan;
 
     public float[] horizontalMargin;
 
@@ -356,6 +357,7 @@
         numDatabaseHotseatIcons = deviceType == TYPE_MULTI_DISPLAY
                 ? closestProfile.numDatabaseHotseatIcons : closestProfile.numHotseatIcons;
         hotseatBorderSpaces = displayOption.hotseatBorderSpaces;
+        hotseatColumnSpan = displayOption.hotseatColumnSpan;
 
         numAllAppsColumns = closestProfile.numAllAppsColumns;
         numDatabaseAllAppsColumns = deviceType == TYPE_MULTI_DISPLAY
@@ -396,7 +398,8 @@
             // We need to ensure that there is enough extra space in the wallpaper
             // for the intended parallax effects
             float parallaxFactor =
-                    dpiFromPx(Math.min(displayWidth, displayHeight), displayInfo.densityDpi) < 720
+                    dpiFromPx(Math.min(displayWidth, displayHeight), displayInfo.getDensityDpi())
+                            < 720
                             ? 2
                             : wallpaperTravelToScreenWidthRatio(displayWidth, displayHeight);
             defaultWallpaperSize.x =
@@ -585,8 +588,8 @@
             }
         }
 
-        float width = dpiFromPx(minWidthPx, displayInfo.densityDpi);
-        float height = dpiFromPx(minHeightPx, displayInfo.densityDpi);
+        float width = dpiFromPx(minWidthPx, displayInfo.getDensityDpi());
+        float height = dpiFromPx(minHeightPx, displayInfo.getDensityDpi());
 
         // Sort the profiles based on the closeness to the device size
         Collections.sort(points, (a, b) ->
@@ -806,7 +809,9 @@
         private float folderBorderSpace;
         private final PointF[] borderSpaces = new PointF[COUNT_SIZES];
         private final float[] horizontalMargin = new float[COUNT_SIZES];
+        //TODO(http://b/228998082) remove this when 3 button spaces are fixed
         private final float[] hotseatBorderSpaces = new float[COUNT_SIZES];
+        private final int[] hotseatColumnSpan = new int[COUNT_SIZES];
 
         private final float[] iconSizes = new float[COUNT_SIZES];
         private final float[] textSizes = new float[COUNT_SIZES];
@@ -1032,6 +1037,17 @@
                     R.styleable.ProfileDisplayOption_hotseatBorderSpaceTwoPanelPortrait,
                     hotseatBorderSpaces[INDEX_DEFAULT]);
 
+            hotseatColumnSpan[INDEX_DEFAULT] = a.getInt(
+                    R.styleable.ProfileDisplayOption_hotseatColumnSpan, grid.numColumns);
+            hotseatColumnSpan[INDEX_LANDSCAPE] = a.getInt(
+                    R.styleable.ProfileDisplayOption_hotseatColumnSpanLandscape, grid.numColumns);
+            hotseatColumnSpan[INDEX_TWO_PANEL_LANDSCAPE] = a.getInt(
+                    R.styleable.ProfileDisplayOption_hotseatColumnSpanTwoPanelLandscape,
+                    grid.numColumns);
+            hotseatColumnSpan[INDEX_TWO_PANEL_PORTRAIT] = a.getInt(
+                    R.styleable.ProfileDisplayOption_hotseatColumnSpanTwoPanelPortrait,
+                    grid.numColumns);
+
             a.recycle();
         }
 
@@ -1090,6 +1106,7 @@
                 minCellSize[i].y += p.minCellSize[i].y;
                 horizontalMargin[i] += p.horizontalMargin[i];
                 hotseatBorderSpaces[i] += p.hotseatBorderSpaces[i];
+                hotseatColumnSpan[i] = p.hotseatColumnSpan[i];
                 allAppsCellSize[i].x += p.allAppsCellSize[i].x;
                 allAppsCellSize[i].y += p.allAppsCellSize[i].y;
                 allAppsIconSizes[i] += p.allAppsIconSizes[i];
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b6a05b0..4b42ecb 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -42,7 +42,6 @@
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
-import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
 import static com.android.launcher3.logging.StatsLogManager.EventEnum;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
@@ -65,7 +64,6 @@
 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.app.Notification;
@@ -109,7 +107,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnPreDrawListener;
 import android.view.WindowManager.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
@@ -130,7 +128,6 @@
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.BaseAllAppsContainerView;
 import com.android.launcher3.allapps.DiscoveryBounce;
-import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -186,8 +183,6 @@
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.MultiValueAlpha;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
@@ -341,6 +336,7 @@
     private Runnable mOnDeferredActivityLaunchCallback;
 
     private ViewOnDrawExecutor mPendingExecutor;
+    private OnPreDrawListener mOnInitialBindListener;
 
     private LauncherModel mModel;
     private ModelWriter mModelWriter;
@@ -503,11 +499,10 @@
 
         if (!mModel.addCallbacksAndLoad(this)) {
             if (!internalStateHandled) {
-                Log.d(BAD_STATE, "Launcher onCreate not binding sync, setting DragLayer alpha "
-                        + "ALPHA_INDEX_LAUNCHER_LOAD to 0");
-                // If we are not binding synchronously, show a fade in animation when
-                // the first page bind completes.
-                mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).setValue(0);
+                Log.d(BAD_STATE, "Launcher onCreate not binding sync, prevent drawing");
+                // If we are not binding synchronously, pause drawing until initial bind complete,
+                // so that the system could continue to show the device loading prompt
+                mOnInitialBindListener = Boolean.FALSE::booleanValue;
             }
         }
 
@@ -515,25 +510,9 @@
         setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
 
         setContentView(getRootView());
-        getRootView().getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        // Checks the status of fade in animation.
-                        final AlphaProperty property =
-                                mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD);
-                        if (property.getValue() == 0) {
-                            Log.d(BAD_STATE, "Launcher onPreDraw ALPHA_INDEX_LAUNCHER_LOAD not"
-                                    + " started yet, cancelling draw.");
-                            // Animation haven't started yet; suspend.
-                            return false;
-                        } else {
-                            // The animation is started; start drawing.
-                            getRootView().getViewTreeObserver().removeOnPreDrawListener(this);
-                            return true;
-                        }
-                    }
-                });
+        if (mOnInitialBindListener != null) {
+            getRootView().getViewTreeObserver().addOnPreDrawListener(mOnInitialBindListener);
+        }
         getRootView().dispatchInsets();
 
         // Listen for broadcasts
@@ -2692,36 +2671,12 @@
                     AllAppsStore.DEFER_UPDATES_NEXT_DRAW));
         }
 
-        AlphaProperty property = mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD);
-        if (property.getValue() < 1) {
-            ObjectAnimator anim = ObjectAnimator.ofFloat(property, MultiValueAlpha.VALUE, 1);
-
-            Log.d(BAD_STATE, "Launcher onInitialBindComplete toAlpha=" + 1);
-            anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    Log.d(BAD_STATE, "Launcher onInitialBindComplete onStart");
-                }
-
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    float alpha = mDragLayer == null
-                            ? -1
-                            : mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).getValue();
-                    Log.d(BAD_STATE, "Launcher onInitialBindComplete onCancel, alpha=" + alpha);
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    Log.d(BAD_STATE, "Launcher onInitialBindComplete onEnd");
-                }
-            });
-
-            anim.addListener(AnimatorListeners.forEndCallback(executor::onLoadAnimationCompleted));
-            anim.start();
-        } else {
-            executor.onLoadAnimationCompleted();
+        if (mOnInitialBindListener != null) {
+            getRootView().getViewTreeObserver().removeOnPreDrawListener(mOnInitialBindListener);
+            mOnInitialBindListener = null;
         }
+
+        executor.onLoadAnimationCompleted();
         executor.attachTo(this);
         if (Utilities.ATLEAST_S) {
             Trace.endAsyncSection(DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index f5ac359..0a1d25c 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1440,7 +1440,7 @@
         return Math.abs(velocity) > threshold;
     }
 
-    private void resetTouchState() {
+    protected void resetTouchState() {
         releaseVelocityTracker();
         mIsBeingDragged = false;
         mActivePointerId = INVALID_POINTER;
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8358f2a..7b96838 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -273,6 +273,16 @@
     }
 
     /**
+     * Similar to {@link #mapCoordInSelfToDescendant(View descendant, View root, float[] coord)}
+     * but accepts a Rect instead of float[].
+     */
+    public static void mapRectInSelfToDescendant(View descendant, View root, Rect rect) {
+        float[] coords = new float[]{rect.left, rect.top, rect.right, rect.bottom};
+        mapCoordInSelfToDescendant(descendant, root, coords);
+        rect.set((int) coords[0], (int) coords[1], (int) coords[2], (int) coords[3]);
+    }
+
+    /**
      * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}.
      */
     public static void mapCoordInSelfToDescendant(View descendant, View root, float[] coord) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index ed01660..78771ce 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -200,6 +200,7 @@
 
     private final int[] mTempXY = new int[2];
     private final float[] mTempFXY = new float[2];
+    private final Rect mTempRect = new Rect();
     @Thunk float[] mDragViewVisualCenter = new float[2];
 
     private SpringLoadedDragController mSpringLoadedDragController;
@@ -906,7 +907,11 @@
      * two panel UI is enabled.
      */
     public int getScreenPair(int screenId) {
-        if (screenId % 2 == 0) {
+        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
+            return EXTRA_EMPTY_SCREEN_SECOND_ID;
+        } else if (screenId == EXTRA_EMPTY_SCREEN_SECOND_ID) {
+            return EXTRA_EMPTY_SCREEN_ID;
+        } else if (screenId % 2 == 0) {
             return screenId + 1;
         } else {
             return screenId - 1;
@@ -1730,7 +1735,7 @@
         // If it's an external drop (e.g. from All Apps), check if it should be accepted
         CellLayout dropTargetLayout = mDropToLayout;
         if (d.dragSource != this) {
-            // Don't accept the drop if we're not over a screen at time of drop
+            // Don't accept the drop if we're not over a valid drop target at time of drop
             if (dropTargetLayout == null) {
                 return false;
             }
@@ -2331,17 +2336,6 @@
         xy[1] = xy[1] - v.getTop();
     }
 
-    boolean isPointInSelfOverHotseat(int x, int y) {
-        mTempFXY[0] = x;
-        mTempFXY[1] = y;
-        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempFXY, true);
-        View hotseat = mLauncher.getHotseat();
-        return mTempFXY[0] >= hotseat.getLeft()
-                && mTempFXY[0] <= hotseat.getRight()
-                && mTempFXY[1] >= hotseat.getTop()
-                && mTempFXY[1] <= hotseat.getBottom();
-    }
-
     /**
      * Updates the point in {@param xy} to point to the co-ordinate space of {@param layout}
      * @param layout either hotseat of a page in workspace
@@ -2379,7 +2373,7 @@
 
         final View child = (mDragInfo == null) ? null : mDragInfo.cell;
         if (setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1])) {
-            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
+            if (mDragTargetLayout == null || mLauncher.isHotseatLayout(mDragTargetLayout)) {
                 mSpringLoadedDragController.cancel();
             } else {
                 mSpringLoadedDragController.setAlarm(mDragTargetLayout);
@@ -2458,52 +2452,25 @@
      */
     private boolean setDropLayoutForDragObject(DragObject d, float centerX, float centerY) {
         CellLayout layout = null;
-        // Test to see if we are over the hotseat first
-        if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
-            if (isPointInSelfOverHotseat(d.x, d.y)) {
-                layout = mLauncher.getHotseat();
+        if (shouldUseHotseatAsDropLayout(d)) {
+            layout = mLauncher.getHotseat();
+        } else if (!isDragObjectOverSmartSpace(d)) {
+            // If the object is over qsb/smartspace, we don't want to highlight anything.
+
+            // Check neighbour pages
+            layout = checkDragObjectIsOverNeighbourPages(d, centerX);
+
+            if (layout == null) {
+                // Check visible pages
+                IntSet visiblePageIndices = getVisiblePageIndices();
+                for (int visiblePageIndex : visiblePageIndices) {
+                    layout = verifyInsidePage(visiblePageIndex, d.x, d.y);
+                    if (layout != null) break;
+                }
             }
         }
 
-        // Note, centerX represents the center of the object that is being dragged, visually. d.x
-        // represents the location of the finger within the dragged item.
-        float touchX;
-        float touchY = d.y;
-
-        // Go through the pages and check if the dragged item is inside one of them. This block
-        // is responsible for  determining whether we need to snap to a different screen.
-        int nextPage = getNextPage();
-        IntSet pageIndexesToVerify = IntSet.wrap(nextPage - 1, nextPage
-                + (isTwoPanelEnabled() ? 2 : 1));
-        for (int pageIndex : pageIndexesToVerify) {
-            if (layout != null || isPageInTransition()) {
-                break;
-            }
-
-            // When deciding whether to perform a page switch, we need to consider the most extreme
-            // X coordinate between the finger location and the center of the object being dragged.
-            // This is either the max or the min of the two depending on whether dragging to the
-            // left / right, respectively.
-            touchX = ((((pageIndex < nextPage) && !mIsRtl) || pageIndex > nextPage && mIsRtl)
-                    ? Math.min(d.x, centerX) : Math.max(d.x, centerX));
-            layout = verifyInsidePage(pageIndex, touchX, touchY);
-        }
-
-        // If the dragged item isn't located in one of the pages above, the icon will stay on the
-        // current screen. For two panel pick the closest panel on the current screen,
-        // on one panel just choose the current page.
-        if (layout == null && nextPage >= 0 && nextPage < getPageCount()) {
-            if (isTwoPanelEnabled()) {
-                // When determining which panel to use within a single screen, we always use
-                // the centroid of the object rather than the finger.
-                touchX = centerX;
-                nextPage = getScreenCenter(getScrollX()) > touchX
-                        ? (mIsRtl ? nextPage + 1 : nextPage) // left side
-                        : (mIsRtl ? nextPage : nextPage + 1); // right side
-            }
-            layout = (CellLayout) getChildAt(nextPage);
-        }
-
+        // Update the current drop layout if the target changed
         if (layout != mDragTargetLayout) {
             setCurrentDropLayout(layout);
             setCurrentDragOverlappingLayout(layout);
@@ -2512,6 +2479,69 @@
         return false;
     }
 
+    private boolean shouldUseHotseatAsDropLayout(DragObject dragObject) {
+        if (mLauncher.getHotseat() == null
+                || mLauncher.getHotseat().getShortcutsAndWidgets() == null
+                || isDragWidget(dragObject)) {
+            return false;
+        }
+        View hotseatShortcuts = mLauncher.getHotseat().getShortcutsAndWidgets();
+        getViewBoundsRelativeToWorkspace(hotseatShortcuts, mTempRect);
+        return mTempRect.contains(dragObject.x, dragObject.y);
+    }
+
+    private boolean isDragObjectOverSmartSpace(DragObject dragObject) {
+        if (mQsb == null) {
+            return false;
+        }
+        getViewBoundsRelativeToWorkspace(mQsb, mTempRect);
+        return mTempRect.contains(dragObject.x, dragObject.y);
+    }
+
+    private CellLayout checkDragObjectIsOverNeighbourPages(DragObject d, float centerX) {
+        if (isPageInTransition()) {
+            return null;
+        }
+
+        // Check the workspace pages whether the object is over any of them
+
+        // Note, centerX represents the center of the object that is being dragged, visually.
+        // d.x represents the location of the finger within the dragged item.
+        float touchX;
+        float touchY = d.y;
+
+        // Go through the pages and check if the dragged item is inside one of them. This block
+        // is responsible for determining whether we need to snap to a different screen.
+        int nextPage = getNextPage();
+        IntSet pageIndexesToVerify = IntSet.wrap(nextPage - 1,
+                nextPage + (isTwoPanelEnabled() ? 2 : 1));
+
+        for (int pageIndex : pageIndexesToVerify) {
+            // When deciding whether to perform a page switch, we need to consider the most
+            // extreme X coordinate between the finger location and the center of the object
+            // being dragged. This is either the max or the min of the two depending on whether
+            // dragging to the left / right, respectively.
+            touchX = (((pageIndex < nextPage) && !mIsRtl) || (pageIndex > nextPage && mIsRtl))
+                    ? Math.min(d.x, centerX) : Math.max(d.x, centerX);
+            CellLayout layout = verifyInsidePage(pageIndex, touchX, touchY);
+            if (layout != null) {
+                return layout;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets the given view's bounds relative to Workspace
+     */
+    private void getViewBoundsRelativeToWorkspace(View view, Rect outRect) {
+        mLauncher.getDragLayer()
+                .getDescendantRectRelativeToSelf(view, mTempRect);
+        // map draglayer relative bounds to workspace
+        mLauncher.getDragLayer().mapRectInSelfToDescendant(this, mTempRect);
+        outRect.set(mTempRect);
+    }
+
     /**
      * Returns the child CellLayout if the point is inside the page coordinates, null otherwise.
      */
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 8662d00..637a418 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -44,6 +44,8 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.MultiAdditivePropertyFactory;
+import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.views.ScrimView;
 
@@ -76,20 +78,56 @@
                 }
             };
 
-    public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PULL_BACK_PROGRESS =
-            new FloatProperty<AllAppsTransitionController>("allAppsPullBackProgress") {
+    public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PULL_BACK_TRANSLATION =
+            new FloatProperty<AllAppsTransitionController>("allAppsPullBackTranslation") {
 
                 @Override
                 public Float get(AllAppsTransitionController controller) {
-                    return controller.mPullBackProgress;
+                    if (controller.mIsTablet) {
+                        return controller.mAppsView.getRecyclerViewContainer().getTranslationY();
+                    } else {
+                        return controller.getAppsViewPullbackTranslationY().get(
+                                controller.mAppsView);
+                    }
                 }
 
                 @Override
-                public void setValue(AllAppsTransitionController controller, float progress) {
-                    controller.setPullBackProgress(progress);
+                public void setValue(AllAppsTransitionController controller, float translation) {
+                    if (controller.mIsTablet) {
+                        controller.mAppsView.getRecyclerViewContainer().setTranslationY(
+                                translation);
+                    } else {
+                        controller.getAppsViewPullbackTranslationY().set(controller.mAppsView,
+                                translation);
+                    }
                 }
             };
 
+    public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PULL_BACK_ALPHA =
+            new FloatProperty<AllAppsTransitionController>("allAppsPullBackAlpha") {
+
+                @Override
+                public Float get(AllAppsTransitionController controller) {
+                    if (controller.mIsTablet) {
+                        return controller.mAppsView.getRecyclerViewContainer().getAlpha();
+                    } else {
+                        return controller.getAppsViewPullbackAlpha().getValue();
+                    }
+                }
+
+                @Override
+                public void setValue(AllAppsTransitionController controller, float alpha) {
+                    if (controller.mIsTablet) {
+                        controller.mAppsView.getRecyclerViewContainer().setAlpha(alpha);
+                    } else {
+                        controller.getAppsViewPullbackAlpha().setValue(alpha);
+                    }
+                }
+            };
+
+    private static final int INDEX_APPS_VIEW_PROGRESS = 0;
+    private static final int INDEX_APPS_VIEW_PULLBACK = 1;
+    private static final int APPS_VIEW_INDEX_COUNT = 2;
 
     private ActivityAllAppsContainerView<Launcher> mAppsView;
 
@@ -104,18 +142,23 @@
     // When {@link mProgress} is 1, all apps container is pulled down.
     private float mShiftRange;      // changes depending on the orientation
     private float mProgress;        // [0, 1], mShiftRange * mProgress = shiftCurrent
-    private float mPullBackProgress;  // [0, 1], mShiftRange * mPullBackProgress = shiftCurrent
 
     private ScrimView mScrimView;
-    private View mPullBackView;
+
+    private final MultiAdditivePropertyFactory<View>
+            mAppsViewTranslationYPropertyFactory = new MultiAdditivePropertyFactory<>(
+            "appsViewTranslationY", View.TRANSLATION_Y);
+    private MultiValueAlpha mAppsViewAlpha;
+
+    private boolean mIsTablet;
 
     public AllAppsTransitionController(Launcher l) {
         mLauncher = l;
         DeviceProfile dp = mLauncher.getDeviceProfile();
         setShiftRange(dp.allAppsShiftRange);
         mProgress = 1f;
-        mPullBackProgress = 1f;
         mIsVerticalLayout = dp.isVerticalBarLayout();
+        mIsTablet = dp.isTablet;
         mLauncher.addOnDeviceProfileChangeListener(this);
     }
 
@@ -133,7 +176,7 @@
             mLauncher.getWorkspace().getPageIndicator().setTranslationY(0);
         }
 
-        mPullBackView = dp.isTablet ? mAppsView.getRecyclerViewContainer() : mAppsView;
+        mIsTablet = dp.isTablet;
     }
 
     /**
@@ -146,16 +189,27 @@
      */
     public void setProgress(float progress) {
         mProgress = progress;
-        mAppsView.setTranslationY(mProgress * mShiftRange);
+        getAppsViewProgressTranslationY().set(mAppsView, mProgress * mShiftRange);
     }
 
     public float getProgress() {
         return mProgress;
     }
 
-    private void setPullBackProgress(float progress) {
-        mPullBackProgress = progress;
-        mPullBackView.setTranslationY(mPullBackProgress * mShiftRange);
+    private FloatProperty<View> getAppsViewProgressTranslationY() {
+        return mAppsViewTranslationYPropertyFactory.get(INDEX_APPS_VIEW_PROGRESS);
+    }
+
+    private FloatProperty<View> getAppsViewPullbackTranslationY() {
+        return mAppsViewTranslationYPropertyFactory.get(INDEX_APPS_VIEW_PULLBACK);
+    }
+
+    private MultiValueAlpha.AlphaProperty getAppsViewProgressAlpha() {
+        return mAppsViewAlpha.getProperty(INDEX_APPS_VIEW_PROGRESS);
+    }
+
+    private MultiValueAlpha.AlphaProperty getAppsViewPullbackAlpha() {
+        return mAppsViewAlpha.getProperty(INDEX_APPS_VIEW_PULLBACK);
     }
 
     /**
@@ -164,8 +218,6 @@
      */
     @Override
     public void setState(LauncherState state) {
-        // Always reset pull back progress when switching states.
-        setPullBackProgress(0f);
         setProgress(state.getVerticalProgress(mLauncher));
         setAlphas(state, new StateAnimationConfig(), NO_ANIM_PROPERTY_SETTER);
         onProgressAnimationEnd();
@@ -180,10 +232,13 @@
             StateAnimationConfig config, PendingAnimation builder) {
         if (NORMAL.equals(toState) && mLauncher.isInState(ALL_APPS)) {
             UiThreadHelper.hideKeyboardAsync(mLauncher, mLauncher.getAppsView().getWindowToken());
+            builder.addEndListener(success -> {
+                // Reset pull back progress and alpha after switching states.
+                ALL_APPS_PULL_BACK_TRANSLATION.set(this, 0f);
+                ALL_APPS_PULL_BACK_ALPHA.set(this, 1f);
+            });
         }
 
-        // Always reset pull back progress when switching states.
-        setPullBackProgress(0f);
         float targetProgress = toState.getVerticalProgress(mLauncher);
         if (Float.compare(mProgress, targetProgress) == 0) {
             setAlphas(toState, config, builder);
@@ -222,7 +277,8 @@
         boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
 
         Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
-        setter.setViewAlpha(mAppsView, hasAllAppsContent ? 1 : 0, allAppsFade);
+        setter.setFloat(getAppsViewProgressAlpha(), MultiValueAlpha.VALUE,
+                hasAllAppsContent ? 1 : 0, allAppsFade);
 
         boolean shouldProtectHeader =
                 ALL_APPS == state || mLauncher.getStateManager().getState() == ALL_APPS;
@@ -245,8 +301,8 @@
                             | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
         }
         mAppsView.setScrimView(scrimView);
-        mPullBackView = mLauncher.getDeviceProfile().isTablet
-                ? mAppsView.getRecyclerViewContainer() : mAppsView;
+        mAppsViewAlpha = new MultiValueAlpha(mAppsView, APPS_VIEW_INDEX_COUNT);
+        mAppsViewAlpha.setUpdateVisibility(true);
     }
 
     /**
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 7687fea..9f0c1cc 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -43,10 +43,6 @@
 
     public static final String TAG = "AlphabeticalAppsList";
 
-    private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION = 0;
-    private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS = 1;
-
-    private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
     private final WorkAdapterProvider mWorkAdapterProvider;
 
     /**
@@ -315,8 +311,7 @@
                 }
 
                 // Create an app item
-                AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info,
-                        appIndex++);
+                AdapterItem appItem = AdapterItem.asApp(position++, info);
                 if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
                     lastFastScrollerSectionInfo.fastScrollToItem = appItem;
                 }
@@ -360,34 +355,16 @@
             mNumAppRowsInAdapter = rowIndex + 1;
 
             // Pre-calculate all the fast scroller fractions
-            switch (mFastScrollDistributionMode) {
-                case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION:
-                    float rowFraction = 1f / mNumAppRowsInAdapter;
-                    for (FastScrollSectionInfo info : mFastScrollerSections) {
-                        AdapterItem item = info.fastScrollToItem;
-                        if (!BaseAllAppsAdapter.isIconViewType(item.viewType)) {
-                            info.touchFraction = 0f;
-                            continue;
-                        }
-
-                        float subRowFraction =
-                                item.rowAppIndex * (rowFraction / mNumAppsPerRowAllApps);
-                        info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
-                    }
-                    break;
-                case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS:
-                    float perSectionTouchFraction = 1f / mFastScrollerSections.size();
-                    float cumulativeTouchFraction = 0f;
-                    for (FastScrollSectionInfo info : mFastScrollerSections) {
-                        AdapterItem item = info.fastScrollToItem;
-                        if (!BaseAllAppsAdapter.isIconViewType(item.viewType)) {
-                            info.touchFraction = 0f;
-                            continue;
-                        }
-                        info.touchFraction = cumulativeTouchFraction;
-                        cumulativeTouchFraction += perSectionTouchFraction;
-                    }
-                    break;
+            float perSectionTouchFraction = 1f / mFastScrollerSections.size();
+            float cumulativeTouchFraction = 0f;
+            for (FastScrollSectionInfo info : mFastScrollerSections) {
+                AdapterItem item = info.fastScrollToItem;
+                if (!BaseAllAppsAdapter.isIconViewType(item.viewType)) {
+                    info.touchFraction = 0f;
+                    continue;
+                }
+                info.touchFraction = cumulativeTouchFraction;
+                cumulativeTouchFraction += perSectionTouchFraction;
             }
         }
     }
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 976284d..8ac2536 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.views.ActivityContext;
 
 import java.util.Arrays;
@@ -94,31 +93,21 @@
         // The type of this item
         public int viewType;
 
-        // The section name of this item.  Note that there can be multiple items with different
-        // sectionNames in the same section
-        public String sectionName = null;
         // The row that this item shows up on
         public int rowIndex;
         // The index of this app in the row
         public int rowAppIndex;
         // The associated ItemInfoWithIcon for the item
-        public ItemInfoWithIcon itemInfo = null;
-        // The index of this app not including sections
-        public int appIndex = -1;
-        // Search section associated to result
-        public DecorationInfo decorationInfo = null;
+        public AppInfo itemInfo = null;
 
         /**
          * Factory method for AppIcon AdapterItem
          */
-        public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
-                int appIndex) {
+        public static AdapterItem asApp(int pos, AppInfo appInfo) {
             AdapterItem item = new AdapterItem();
             item.viewType = VIEW_TYPE_ICON;
             item.position = pos;
-            item.sectionName = sectionName;
             item.itemInfo = appInfo;
-            item.appIndex = appIndex;
             return item;
         }
 
@@ -267,11 +256,7 @@
                 AdapterItem adapterItem = mApps.getAdapterItems().get(position);
                 BubbleTextView icon = (BubbleTextView) holder.itemView;
                 icon.reset();
-                if (adapterItem.itemInfo instanceof AppInfo) {
-                    icon.applyFromApplicationInfo((AppInfo) adapterItem.itemInfo);
-                } else {
-                    icon.applyFromItemInfoWithIcon(adapterItem.itemInfo);
-                }
+                icon.applyFromApplicationInfo(adapterItem.itemInfo);
                 break;
             case VIEW_TYPE_EMPTY_SEARCH:
                 TextView emptyViewText = (TextView) holder.itemView;
diff --git a/src/com/android/launcher3/allapps/DecorationInfo.java b/src/com/android/launcher3/allapps/DecorationInfo.java
deleted file mode 100644
index 50b250c..0000000
--- a/src/com/android/launcher3/allapps/DecorationInfo.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * 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.allapps;
-
-public class DecorationInfo {
-}
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index 222c8fe..33d0082 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -85,7 +85,7 @@
         for (int i = 0; i < total && resultCount < MAX_RESULTS_COUNT; i++) {
             AppInfo info = apps.get(i);
             if (StringMatcherUtility.matches(queryTextLower, info.title.toString(), matcher)) {
-                AdapterItem appItem = AdapterItem.asApp(resultCount, "", info, resultCount);
+                AdapterItem appItem = AdapterItem.asApp(resultCount, info);
                 result.add(appItem);
                 resultCount++;
             }
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 14e10918..8eeca7d 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -65,9 +65,7 @@
 public class DragLayer extends BaseDragLayer<Launcher> {
 
     public static final int ALPHA_INDEX_OVERLAY = 0;
-    public static final int ALPHA_INDEX_LAUNCHER_LOAD = 1;
-    public static final int ALPHA_INDEX_TRANSITIONS = 2;
-    private static final int ALPHA_CHANNEL_COUNT = 3;
+    private static final int ALPHA_CHANNEL_COUNT = 1;
 
     public static final int ANIMATION_END_DISAPPEAR = 0;
     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
diff --git a/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java b/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java
index 72956b0..c0a04b1 100644
--- a/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java
+++ b/src/com/android/launcher3/popup/LauncherPopupLiveUpdateHandler.java
@@ -45,8 +45,9 @@
 
     @Override
     public void onWidgetsBound() {
-        ItemInfo itemInfo = (ItemInfo) mPopupContainerWithArrow.getOriginalIcon().getTag();
-        SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mContext, itemInfo);
+        BubbleTextView originalIcon = mPopupContainerWithArrow.getOriginalIcon();
+        SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mContext,
+                (ItemInfo) originalIcon.getTag(), originalIcon);
         View widgetsView = getWidgetsView(mPopupContainerWithArrow);
         if (widgetsView == null && mPopupContainerWithArrow.getWidgetContainer() != null) {
             widgetsView = getWidgetsView(mPopupContainerWithArrow.getWidgetContainer());
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index bd3778a..484b879 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -218,7 +218,7 @@
                 popupDataProvider.getShortcutCountForItem(item),
                 popupDataProvider.getNotificationKeysForItem(item),
                 launcher.getSupportedShortcuts()
-                        .map(s -> s.getShortcut(launcher, item))
+                        .map(s -> s.getShortcut(launcher, item, icon))
                         .filter(Objects::nonNull)
                         .collect(Collectors.toList()));
         launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java
index 7c393ad..e5e2c35 100644
--- a/src/com/android/launcher3/popup/RemoteActionShortcut.java
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -46,8 +46,8 @@
     private final RemoteAction mAction;
 
     public RemoteActionShortcut(RemoteAction action,
-            BaseDraggingActivity activity, ItemInfo itemInfo) {
-        super(0, R.id.action_remote_action_shortcut, activity, itemInfo);
+            BaseDraggingActivity activity, ItemInfo itemInfo, View originalView) {
+        super(0, R.id.action_remote_action_shortcut, activity, itemInfo, originalView);
         mAction = action;
     }
 
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 08d3779..0e25984c 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -46,18 +46,21 @@
 
     protected final T mTarget;
     protected final ItemInfo mItemInfo;
+    protected final View mOriginalView;
 
     /**
      * Indicates if it's invokable or not through some disabled UI
      */
     private boolean isEnabled = true;
 
-    public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo) {
+    public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo,
+            View originalView) {
         mIconResId = iconResId;
         mLabelResId = labelResId;
         mAccessibilityActionId = labelResId;
         mTarget = target;
         mItemInfo = itemInfo;
+        mOriginalView = originalView;
     }
 
     public SystemShortcut(SystemShortcut<T> other) {
@@ -66,6 +69,7 @@
         mAccessibilityActionId = other.mAccessibilityActionId;
         mTarget = other.mTarget;
         mItemInfo = other.mItemInfo;
+        mOriginalView = other.mOriginalView;
     }
 
     /**
@@ -107,10 +111,10 @@
 
     public interface Factory<T extends Context & ActivityContext> {
 
-        @Nullable SystemShortcut<T> getShortcut(T activity, ItemInfo itemInfo);
+        @Nullable SystemShortcut<T> getShortcut(T activity, ItemInfo itemInfo, View originalView);
     }
 
-    public static final Factory<Launcher> WIDGETS = (launcher, itemInfo) -> {
+    public static final Factory<Launcher> WIDGETS = (launcher, itemInfo, originalView) -> {
         if (itemInfo.getTargetComponent() == null) return null;
         final List<WidgetItem> widgets =
                 launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
@@ -118,12 +122,13 @@
         if (widgets.isEmpty()) {
             return null;
         }
-        return new Widgets(launcher, itemInfo);
+        return new Widgets(launcher, itemInfo, originalView);
     };
 
     public static class Widgets extends SystemShortcut<Launcher> {
-        public Widgets(Launcher target, ItemInfo itemInfo) {
-            super(R.drawable.ic_widget, R.string.widget_button_text, target, itemInfo);
+        public Widgets(Launcher target, ItemInfo itemInfo, View originalView) {
+            super(R.drawable.ic_widget, R.string.widget_button_text, target, itemInfo,
+                    originalView);
         }
 
         @Override
@@ -145,9 +150,9 @@
         @Nullable
         private SplitAccessibilityInfo mSplitA11yInfo;
 
-        public AppInfo(T target, ItemInfo itemInfo) {
+        public AppInfo(T target, ItemInfo itemInfo, View originalView) {
             super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label, target,
-                    itemInfo);
+                    itemInfo, originalView);
         }
 
         /**
@@ -160,8 +165,9 @@
          * That way it could directly create the correct node info for any shortcut that supports
          * split, but then we'll need custom resIDs for each pair of shortcuts.
          */
-        public AppInfo(T target, ItemInfo itemInfo, SplitAccessibilityInfo accessibilityInfo) {
-            this(target, itemInfo);
+        public AppInfo(T target, ItemInfo itemInfo, View originalView,
+                SplitAccessibilityInfo accessibilityInfo) {
+            this(target, itemInfo, originalView);
             mSplitA11yInfo = accessibilityInfo;
             mAccessibilityActionId = accessibilityInfo.nodeId;
         }
@@ -203,28 +209,29 @@
         }
     }
 
-    public static final Factory<BaseDraggingActivity> INSTALL = (activity, itemInfo) -> {
-        boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo)
-                && ((WorkspaceItemInfo) itemInfo).hasStatusFlag(
+    public static final Factory<BaseDraggingActivity> INSTALL =
+            (activity, itemInfo, originalView) -> {
+                boolean supportsWebUI = (itemInfo instanceof WorkspaceItemInfo)
+                        && ((WorkspaceItemInfo) itemInfo).hasStatusFlag(
                         WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI);
-        boolean isInstantApp = false;
-        if (itemInfo instanceof com.android.launcher3.model.data.AppInfo) {
-            com.android.launcher3.model.data.AppInfo
-                    appInfo = (com.android.launcher3.model.data.AppInfo) itemInfo;
-            isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo);
-        }
-        boolean enabled = supportsWebUI || isInstantApp;
-        if (!enabled) {
-            return null;
-        }
-        return new Install(activity, itemInfo);
+                boolean isInstantApp = false;
+                if (itemInfo instanceof com.android.launcher3.model.data.AppInfo) {
+                    com.android.launcher3.model.data.AppInfo
+                            appInfo = (com.android.launcher3.model.data.AppInfo) itemInfo;
+                    isInstantApp = InstantAppResolver.newInstance(activity).isInstantApp(appInfo);
+                }
+                boolean enabled = supportsWebUI || isInstantApp;
+                if (!enabled) {
+                    return null;
+                }
+                return new Install(activity, itemInfo, originalView);
     };
 
     public static class Install extends SystemShortcut<BaseDraggingActivity> {
 
-        public Install(BaseDraggingActivity target, ItemInfo itemInfo) {
+        public Install(BaseDraggingActivity target, ItemInfo itemInfo, View originalView) {
             super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label,
-                    target, itemInfo);
+                    target, itemInfo, originalView);
         }
 
         @Override
diff --git a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
index e9058c3..a0ed77e 100644
--- a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
+++ b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
@@ -205,8 +205,8 @@
     /**
      * Returns a system shortcut to pin/unpin a shortcut
      */
-    public SystemShortcut getSystemShortcut(ItemInfo info) {
-        return new PinUnPinShortcut(mLauncher, info,
+    public SystemShortcut getSystemShortcut(ItemInfo info, View originalView) {
+        return new PinUnPinShortcut(mLauncher, info, originalView,
                 mPinnedApps.contains(new ComponentKey(info.getTargetComponent(), info.user)));
     }
 
@@ -214,10 +214,11 @@
 
         private final boolean mIsPinned;
 
-        PinUnPinShortcut(SecondaryDisplayLauncher target, ItemInfo info, boolean isPinned) {
+        PinUnPinShortcut(SecondaryDisplayLauncher target, ItemInfo info, View originalView,
+                boolean isPinned) {
             super(isPinned ? R.drawable.ic_remove_no_shadow : R.drawable.ic_pin,
                     isPinned ? R.string.remove_drop_target_label : R.string.action_add_to_workspace,
-                    target, info);
+                    target, info, originalView);
             mIsPinned = isPinned;
         }
 
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
index 9201006..e906c95 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -193,8 +193,8 @@
         container.populateAndShow((BubbleTextView) v,
                 popupDataProvider.getShortcutCountForItem(item),
                 Collections.emptyList(),
-                Arrays.asList(mPinnedAppsAdapter.getSystemShortcut(item),
-                        APP_INFO.getShortcut(mActivity, item)));
+                Arrays.asList(mPinnedAppsAdapter.getSystemShortcut(item, v),
+                        APP_INFO.getShortcut(mActivity, item, v)));
         v.getParent().requestDisallowInterceptTouchEvent(true);
         return true;
     }
diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java
index 0c6d675..9af68c0 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutKey.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java
@@ -57,11 +57,17 @@
     }
 
     public static Intent makeIntent(ShortcutInfo si) {
+        return makeIntent(si.getId(), si.getPackage()).setComponent(si.getActivity());
+    }
+
+    /**
+     * Creates an intent for shortcut id and package name.
+     */
+    public static Intent makeIntent(String shortcutId, String packageName) {
         return new Intent(Intent.ACTION_MAIN)
                 .addCategory(INTENT_CATEGORY)
-                .setComponent(si.getActivity())
-                .setPackage(si.getPackage())
+                .setPackage(packageName)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
-                .putExtra(EXTRA_SHORTCUT_ID, si.getId());
+                .putExtra(EXTRA_SHORTCUT_ID, shortcutId);
     }
 }
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 7e9d56d..e311bc8 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -52,16 +52,7 @@
         }
 
         float shrunkTop = grid.getWorkspaceSpringLoadShrunkTop();
-        float shrunkBottom = grid.getWorkspaceSpringLoadShrunkBottom();
-        float scale = Math.min((shrunkBottom - shrunkTop) / ws.getNormalChildHeight(), 1f);
-
-        // Reduce scale if next pages would not be visible after scaling the workspace
-        float scaledWorkspaceWidth = ws.getWidth() * scale;
-        float maxAvailableWidth =
-                ws.getWidth() - (2 * grid.getWorkspaceSpringLoadedMinimumNextPageVisible());
-        if (scaledWorkspaceWidth > maxAvailableWidth) {
-            scale *= maxAvailableWidth / scaledWorkspaceWidth;
-        }
+        float scale = grid.getWorkspaceSpringLoadScale();
 
         float halfHeight = ws.getHeight() / 2;
         float myCenter = ws.getTop() + halfHeight;
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 8005181..7c73be5 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -242,7 +242,9 @@
             change |= CHANGE_SUPPORTED_BOUNDS;
 
             Point currentS = newInfo.currentSize;
-            Point expectedS = oldInfo.mPerDisplayBounds.get(newInfo.displayId).first.size;
+            Pair<CachedDisplayInfo, WindowBounds[]> cachedBounds =
+                    oldInfo.mPerDisplayBounds.get(newInfo.displayId);
+            Point expectedS = cachedBounds == null ? null : cachedBounds.first.size;
             if (newInfo.supportedBounds.size() != oldInfo.supportedBounds.size()) {
                 Log.e("b/198965093",
                         "Inconsistent number of displays"
@@ -250,10 +252,12 @@
                                 + "\noldInfo.supportedBounds: " + oldInfo.supportedBounds
                                 + "\nnewInfo.supportedBounds: " + newInfo.supportedBounds);
             }
-            if ((Math.min(currentS.x, currentS.y) != Math.min(expectedS.x, expectedS.y)
+            if (expectedS != null
+                    && (Math.min(currentS.x, currentS.y) != Math.min(expectedS.x, expectedS.y)
                     || Math.max(currentS.x, currentS.y) != Math.max(expectedS.x, expectedS.y))
                     && display.getState() == Display.STATE_OFF) {
-                Log.e("b/198965093", "Display size changed while display is off, ignoring change");
+                Log.e("b/198965093",
+                        "Display size changed while display is off, ignoring change");
                 return;
             }
         }
@@ -286,7 +290,7 @@
 
         // Configuration property
         public final float fontScale;
-        public final int densityDpi;
+        private final int densityDpi;
         public final NavigationMode navigationMode;
 
         private final PortraitSize mScreenSizeDp;
@@ -353,6 +357,10 @@
         public float smallestSizeDp(WindowBounds bounds) {
             return dpiFromPx(Math.min(bounds.bounds.width(), bounds.bounds.height()), densityDpi);
         }
+
+        public int getDensityDpi() {
+            return densityDpi;
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java
index 8485371..6978e0c 100644
--- a/src/com/android/launcher3/util/Executors.java
+++ b/src/com/android/launcher3/util/Executors.java
@@ -15,17 +15,12 @@
  */
 package com.android.launcher3.util;
 
-import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
-
-import static java.util.concurrent.Executors.newSingleThreadExecutor;
-
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Process;
 
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
@@ -42,7 +37,7 @@
     private static final int KEEP_ALIVE = 1;
 
     /** Dedicated executor instances for work depending on other packages. */
-    private static final Map<String, ExecutorService> PACKAGE_EXECUTORS = new ConcurrentHashMap<>();
+    private static final Map<String, LooperExecutor> PACKAGE_EXECUTORS = new ConcurrentHashMap<>();
 
     /**
      * An {@link ThreadPoolExecutor} to be used with async task with no limit on the queue size.
@@ -90,11 +85,10 @@
      *
      * @param packageName Package associated with the executor.
      */
-    public static ExecutorService getPackageExecutor(String packageName) {
+    public static LooperExecutor getPackageExecutor(String packageName) {
         return PACKAGE_EXECUTORS.computeIfAbsent(
-                packageName,
-                p -> newSingleThreadExecutor(
-                        new SimpleThreadFactory(p, THREAD_PRIORITY_BACKGROUND)));
+                packageName, p -> new LooperExecutor(
+                        createAndStartNewLooper(p, Process.THREAD_PRIORITY_DEFAULT)));
     }
 
     /**
diff --git a/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java b/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java
new file mode 100644
index 0000000..50f7027
--- /dev/null
+++ b/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java
@@ -0,0 +1,115 @@
+/*
+ * 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 android.util.Log;
+import android.util.Property;
+import android.view.View;
+
+/**
+ * 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 addition of all values.
+ *
+ * @param <T> Type where to apply the property.
+ */
+public class MultiAdditivePropertyFactory<T extends View> {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "MultiAdditivePropertyFactory";
+    private final String mName;
+    private final ArrayMap<Integer, MultiAdditiveProperty> mProperties =
+            new ArrayMap<>();
+
+    // This is an optimization for cases when set is called repeatedly with the same setterIndex.
+    private float mAggregationOfOthers = 0f;
+    private Integer mLastIndexSet = -1;
+    private final Property<View, Float> mProperty;
+
+    public MultiAdditivePropertyFactory(String name, Property<View, Float> property) {
+        mName = name;
+        mProperty = property;
+    }
+
+    /** Returns the [MultiFloatProperty] associated with [inx], creating it if not present. */
+    public MultiAdditiveProperty get(Integer index) {
+        return mProperties.computeIfAbsent(index,
+                (k) -> new MultiAdditiveProperty(index, mName + "_" + index));
+    }
+
+    /**
+     * Each [setValue] will be aggregated with the other properties values created by the
+     * corresponding factory.
+     */
+    class MultiAdditiveProperty extends FloatProperty<T> {
+        private final int mInx;
+        private float mValue = 0f;
+
+        MultiAdditiveProperty(int inx, String name) {
+            super(name);
+            mInx = inx;
+        }
+
+        @Override
+        public void setValue(T obj, float newValue) {
+            if (mLastIndexSet != mInx) {
+                mAggregationOfOthers = 0f;
+                mProperties.forEach((key, property) -> {
+                    if (key != mInx) {
+                        mAggregationOfOthers += property.mValue;
+                    }
+                });
+                mLastIndexSet = mInx;
+            }
+            float lastAggregatedValue = mAggregationOfOthers + newValue;
+            mValue = newValue;
+            apply(obj, lastAggregatedValue);
+
+            if (DEBUG) {
+                Log.d(TAG, "name=" + mName
+                        + " newValue=" + newValue + " mInx=" + mInx
+                        + " aggregated=" + lastAggregatedValue + " others= " + mProperties);
+            }
+        }
+
+        @Override
+        public Float get(T view) {
+            // The scale of the view should match mLastAggregatedValue. Still, if it has been
+            // changed without using this property, it can differ. As this get method is usually
+            // used to set the starting point on an animation, this would result in some jumps
+            // when the view scale is different than the last aggregated value. To stay on the
+            // safe side, let's return the real view scale.
+            return mProperty.get(view);
+        }
+
+        @Override
+        public String toString() {
+            return String.valueOf(mValue);
+        }
+    }
+
+    protected void apply(View view, float value) {
+        mProperty.set(view, value);
+    }
+}
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index 39d7cfe..c1e4fa8 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -38,8 +38,9 @@
     public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
     public static final String HOTSEAT_DISCOVERY_TIP_COUNT = "launcher.hotseat_discovery_tip_count";
     public static final String HOTSEAT_LONGPRESS_TIP_SEEN = "launcher.hotseat_longpress_tip_seen";
-    public static final String SEARCH_EDU_SEEN = "launcher.search_edu_seen";
+    public static final String SEARCH_KEYBOARD_EDU_SEEN = "launcher.search_edu_seen";
     public static final String SEARCH_SNACKBAR_COUNT = "launcher.keyboard_snackbar_count";
+    public static final String SEARCH_ONBOARDING_COUNT = "launcher.search_onboarding_count";
     public static final String TASKBAR_EDU_SEEN = "launcher.taskbar_edu_seen";
     public static final String ALL_APPS_VISITED_COUNT = "launcher.all_apps_visited_count";
     // When adding a new key, add it here as well, to be able to reset it from Developer Options.
@@ -47,7 +48,8 @@
             "All Apps Bounce", new String[] { HOME_BOUNCE_SEEN, HOME_BOUNCE_COUNT },
             "Hybrid Hotseat Education", new String[] { HOTSEAT_DISCOVERY_TIP_COUNT,
                     HOTSEAT_LONGPRESS_TIP_SEEN },
-            "Search Education", new String[] { SEARCH_EDU_SEEN, SEARCH_SNACKBAR_COUNT },
+            "Search Education", new String[] { SEARCH_KEYBOARD_EDU_SEEN, SEARCH_SNACKBAR_COUNT,
+                    SEARCH_ONBOARDING_COUNT},
             "Taskbar Education", new String[] { TASKBAR_EDU_SEEN },
             "All Apps Visited Count", new String[] {ALL_APPS_VISITED_COUNT}
     );
@@ -58,12 +60,11 @@
     @StringDef(value = {
             HOME_BOUNCE_SEEN,
             HOTSEAT_LONGPRESS_TIP_SEEN,
-            SEARCH_EDU_SEEN,
+            SEARCH_KEYBOARD_EDU_SEEN,
             TASKBAR_EDU_SEEN
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface EventBoolKey {
-    }
+    public @interface EventBoolKey {}
 
     /**
      * Events that occur multiple times, which we count up to a max defined in {@link #MAX_COUNTS}.
@@ -72,19 +73,21 @@
             HOME_BOUNCE_COUNT,
             HOTSEAT_DISCOVERY_TIP_COUNT,
             SEARCH_SNACKBAR_COUNT,
+            SEARCH_ONBOARDING_COUNT,
             ALL_APPS_VISITED_COUNT
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface EventCountKey {
-    }
+    public @interface EventCountKey {}
 
     private static final Map<String, Integer> MAX_COUNTS;
 
     static {
-        Map<String, Integer> maxCounts = new ArrayMap<>(4);
+        Map<String, Integer> maxCounts = new ArrayMap<>(5);
         maxCounts.put(HOME_BOUNCE_COUNT, 3);
         maxCounts.put(HOTSEAT_DISCOVERY_TIP_COUNT, 5);
         maxCounts.put(SEARCH_SNACKBAR_COUNT, 3);
+        // This is the sum of all onboarding cards. Currently there is only 1 card shown 3 times.
+        maxCounts.put(SEARCH_ONBOARDING_COUNT, 3);
         maxCounts.put(ALL_APPS_VISITED_COUNT, 20);
         MAX_COUNTS = Collections.unmodifiableMap(maxCounts);
     }
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 5aaa275..61b7fa1 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -22,7 +22,6 @@
 import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT;
 import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
 import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
-import static com.android.launcher3.ResourceUtils.getDimenByName;
 import static com.android.launcher3.Utilities.dpiFromPx;
 import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 import static com.android.launcher3.util.RotationUtils.deltaRotation;
@@ -157,16 +156,16 @@
         int bottomNav = isTablet
                 ? 0
                 : (config.screenHeightDp > config.screenWidthDp
-                        ? getDimenByName(NAVBAR_HEIGHT, systemRes, 0)
+                        ? getDimenByName(NAVBAR_HEIGHT, systemRes)
                         : (isGesture
-                                ? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes, 0)
+                                ? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes)
                                 : 0));
         Insets newNavInsets = Insets.of(navInsets.left, navInsets.top, navInsets.right, bottomNav);
         insetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets);
         insetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(), newNavInsets);
 
         Insets statusBarInsets = oldInsets.getInsets(WindowInsets.Type.statusBars());
-        int statusBarHeight = getDimenByName("status_bar_height", systemRes, 0);
+        int statusBarHeight = getDimenByName("status_bar_height", systemRes);
         Insets newStatusBarInsets = Insets.of(
                 statusBarInsets.left,
                 Math.max(statusBarInsets.top, statusBarHeight),
@@ -222,23 +221,23 @@
         boolean isTabletOrGesture = isTablet
                 || (Utilities.ATLEAST_R && isGestureNav(context));
 
-        int statusBarHeight = getDimenByName("status_bar_height", systemRes, 0);
+        int statusBarHeight = getDimenByName("status_bar_height", systemRes);
 
         int navBarHeightPortrait, navBarHeightLandscape, navbarWidthLandscape;
 
         navBarHeightPortrait = isTablet
                 ? (mTaskbarDrawnInProcess
                         ? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
-                : getDimenByName(NAVBAR_HEIGHT, systemRes, 0);
+                : getDimenByName(NAVBAR_HEIGHT, systemRes);
 
         navBarHeightLandscape = isTablet
                 ? (mTaskbarDrawnInProcess
                         ? 0 : systemRes.getDimensionPixelSize(R.dimen.taskbar_size))
                 : (isTabletOrGesture
-                        ? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes, 0) : 0);
+                        ? getDimenByName(NAVBAR_HEIGHT_LANDSCAPE, systemRes) : 0);
         navbarWidthLandscape = isTabletOrGesture
                 ? 0
-                : getDimenByName(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, systemRes, 0);
+                : getDimenByName(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, systemRes);
 
         WindowBounds[] result = new WindowBounds[4];
         Point tempSize = new Point();
@@ -274,6 +273,13 @@
         return result;
     }
 
+    /**
+     * Wrapper around the utility method for easier emulation
+     */
+    protected int getDimenByName(String resName, Resources res) {
+        return ResourceUtils.getDimenByName(resName, res, 0);
+    }
+
     protected boolean isGestureNav(Context context) {
         return ResourceUtils.getIntegerByName("config_navBarInteractionMode",
                 context.getResources(), INVALID_RESOURCE_HANDLE) == 2;
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index f71aa13..4c001fd 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -413,6 +413,14 @@
     }
 
     /**
+     * Similar to {@link #mapCoordInSelfToDescendant(View descendant, float[] coord)}
+     * but accepts a Rect instead of float[].
+     */
+    public void mapRectInSelfToDescendant(View descendant, Rect rect) {
+        Utilities.mapRectInSelfToDescendant(descendant, this, rect);
+    }
+
+    /**
      * Inverse of {@link #getDescendantCoordRelativeToSelf(View, float[])}.
      */
     public void mapCoordInSelfToDescendant(View descendant, float[] coord) {
diff --git a/tests/res/raw/devices.json b/tests/res/raw/devices.json
new file mode 100644
index 0000000..a78dd86
--- /dev/null
+++ b/tests/res/raw/devices.json
@@ -0,0 +1,45 @@
+{
+  "pixel6pro": {
+    "width": 1440,
+    "height": 3120,
+    "density": 560,
+    "name": "pixel6pro",
+    "cutout": "0, 130, 0, 0",
+    "grids": [
+      "normal",
+      "reasonable",
+      "practical",
+      "big",
+      "crazy_big"
+    ],
+    "resourceOverrides": {
+      "status_bar_height": 98,
+      "navigation_bar_height_landscape": 56,
+      "navigation_bar_height": 56,
+      "navigation_bar_width": 56
+    }
+  },
+  "test": {
+    "data needs updating": 0
+  },
+  "pixel5": {
+    "width": 1080,
+    "height": 2340,
+    "density": 440,
+    "name": "pixel5",
+    "cutout": "0, 136, 0, 0",
+    "grids": [
+      "normal",
+      "reasonable",
+      "practical",
+      "big",
+      "crazy_big"
+    ],
+    "resourceOverrides": {
+      "status_bar_height": 66,
+      "navigation_bar_height_landscape": 44,
+      "navigation_bar_height": 44,
+      "navigation_bar_width": 44
+    }
+  }
+}
diff --git a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
index f91f1c4..6d0fcb6 100644
--- a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
+++ b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
@@ -64,6 +64,7 @@
         windowBounds = WindowBounds(x, y, x, y - 100, 0)
 
         whenever(info.isTablet(any())).thenReturn(false)
+        whenever(info.getDensityDpi()).thenReturn(560)
 
         inv = newScalableInvariantDeviceProfile()
     }
@@ -77,6 +78,7 @@
         windowBounds = WindowBounds(x, y, x, y - 100, 0)
 
         whenever(info.isTablet(any())).thenReturn(true)
+        whenever(info.getDensityDpi()).thenReturn(320)
 
         inv = newScalableInvariantDeviceProfile()
     }
@@ -107,6 +109,7 @@
                 PointF(16f, 16f)
             ).toTypedArray()
             hotseatBorderSpaces = FloatArray(4) { 16f }
+            hotseatColumnSpan = IntArray(4) { 4 }
             iconSize = FloatArray(4) { 56f }
             allAppsIconSize = FloatArray(4) { 56f }
             iconTextSize = FloatArray(4) { 14f }
diff --git a/tests/src/com/android/launcher3/HotseatSizeTest.kt b/tests/src/com/android/launcher3/HotseatShownIconsTest.kt
similarity index 97%
rename from tests/src/com/android/launcher3/HotseatSizeTest.kt
rename to tests/src/com/android/launcher3/HotseatShownIconsTest.kt
index a44939f..593239d 100644
--- a/tests/src/com/android/launcher3/HotseatSizeTest.kt
+++ b/tests/src/com/android/launcher3/HotseatShownIconsTest.kt
@@ -23,15 +23,13 @@
 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]
  */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class HotseatSizeTest : DeviceProfileBaseTest() {
+class HotseatShownIconsTest : DeviceProfileBaseTest() {
 
     @Test
     fun hotseat_size_is_normal_for_handhelds() {
diff --git a/tests/src/com/android/launcher3/InlineQsbTest.kt b/tests/src/com/android/launcher3/InlineQsbTest.kt
index e00dca8..905c1e1 100644
--- a/tests/src/com/android/launcher3/InlineQsbTest.kt
+++ b/tests/src/com/android/launcher3/InlineQsbTest.kt
@@ -29,17 +29,16 @@
 class InlineQsbTest : DeviceProfileBaseTest() {
 
     @Test
-    fun qsbWidth_is_match_parent_for_phones() {
+    fun qsb_is_not_inline_for_phones() {
         initializeVarsForPhone()
 
         val dp = newDP()
 
         assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.qsbWidth).isEqualTo(0)
     }
 
     @Test
-    fun qsbWidth_is_match_parent_for_tablet_portrait() {
+    fun qsb_is_inline_for_tablet_portrait() {
         initializeVarsForTablet()
         inv = newScalableInvariantDeviceProfile().apply {
             inlineQsb = booleanArrayOf(
@@ -62,11 +61,10 @@
         )
 
         assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.qsbWidth).isEqualTo(0)
     }
 
     @Test
-    fun qsbWidth_has_size_for_tablet_landscape() {
+    fun qsb_is_inline_for_tablet_landscape() {
         initializeVarsForTablet(isLandscape = true)
         inv = newScalableInvariantDeviceProfile().apply {
             inlineQsb = booleanArrayOf(
@@ -75,16 +73,17 @@
                 false,
                 false
             )
+            numColumns = 6
+            numRows = 5
+            numShownHotseatIcons = 6
         }
 
         val dp = newDP()
 
         if (dp.hotseatQsbHeight > 0) {
             assertThat(dp.isQsbInline).isTrue()
-            assertThat(dp.qsbWidth).isGreaterThan(0)
         } else { // Launcher3 doesn't have QSB height
             assertThat(dp.isQsbInline).isFalse()
-            assertThat(dp.qsbWidth).isEqualTo(0)
         }
     }
 
@@ -92,14 +91,13 @@
      * This test is to make sure that a tablet doesn't inline the QSB if the layout doesn't support
      */
     @Test
-    fun qsbWidth_is_match_parent_for_tablet_landscape_without_inline() {
+    fun qsb_is_not_inline_for_tablet_landscape_without_inline() {
         initializeVarsForTablet(isLandscape = true)
         useTwoPanels = true
 
         val dp = newDP()
 
         assertThat(dp.isQsbInline).isFalse()
-        assertThat(dp.qsbWidth).isEqualTo(0)
     }
 
 }
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java b/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java
new file mode 100644
index 0000000..31468c5
--- /dev/null
+++ b/tests/src/com/android/launcher3/deviceemulator/DisplayEmulator.java
@@ -0,0 +1,95 @@
+/*
+ * 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.deviceemulator;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.view.Display;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.uiautomator.UiDevice;
+
+import com.android.launcher3.deviceemulator.models.DeviceEmulationData;
+import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.util.window.WindowManagerProxy;
+
+import java.util.concurrent.Callable;
+
+
+public class DisplayEmulator {
+    Context mContext;
+    LauncherInstrumentation mLauncher;
+    DisplayEmulator(Context context, LauncherInstrumentation launcher) {
+        mContext = context;
+        mLauncher = launcher;
+    }
+
+    /**
+     * By changing the WindowManagerProxy we can override the window insets information
+     **/
+    private IWindowManager changeWindowManagerInstance(DeviceEmulationData deviceData) {
+        WindowManagerProxy.INSTANCE.initializeForTesting(
+                new TestWindowManagerProxy(mContext, deviceData));
+        return WindowManagerGlobal.getWindowManagerService();
+    }
+
+    public <T> T emulate(DeviceEmulationData device, String grid, Callable<T> runInEmulation)
+            throws Exception {
+        WindowManagerProxy original = WindowManagerProxy.INSTANCE.get(mContext);
+        // Set up emulation
+        final int userId = UserHandle.myUserId();
+        WindowManagerProxy.INSTANCE.initializeForTesting(
+                new TestWindowManagerProxy(mContext, device));
+        IWindowManager wm = changeWindowManagerInstance(device);
+        // Change density twice to force display controller to reset its state
+        wm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, device.density / 2, userId);
+        wm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, device.density, userId);
+        wm.setForcedDisplaySize(Display.DEFAULT_DISPLAY, device.width, device.height);
+        wm.setForcedDisplayScalingMode(Display.DEFAULT_DISPLAY, 1);
+
+        // Set up grid
+        setGrid(grid);
+        try {
+            return runInEmulation.call();
+        } finally {
+            // Clear emulation
+            WindowManagerProxy.INSTANCE.initializeForTesting(original);
+            UiDevice.getInstance(getInstrumentation()).executeShellCommand("cmd window reset");
+        }
+    }
+
+    private void setGrid(String gridType) {
+        // When the grid changes, the desktop arrangement get stored in SQL and we need to wait to
+        // make sure there is no SQL operations running and get SQL_BUSY error, that's why we need
+        // to call mLauncher.waitForLauncherInitialized();
+        mLauncher.waitForLauncherInitialized();
+        String testProviderAuthority = mContext.getPackageName() + ".grid_control";
+        Uri gridUri = new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(testProviderAuthority)
+                .appendPath("default_grid")
+                .build();
+        ContentValues values = new ContentValues();
+        values.put("name", gridType);
+        mContext.getContentResolver().update(gridUri, values, null, null);
+    }
+}
diff --git a/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java b/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java
new file mode 100644
index 0000000..ca2f81e
--- /dev/null
+++ b/tests/src/com/android/launcher3/deviceemulator/TestWindowManagerProxy.java
@@ -0,0 +1,75 @@
+/*
+ * 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.deviceemulator;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Display;
+import android.view.WindowInsets;
+
+import com.android.launcher3.deviceemulator.models.DeviceEmulationData;
+import com.android.launcher3.util.RotationUtils;
+import com.android.launcher3.util.WindowBounds;
+import com.android.launcher3.util.window.CachedDisplayInfo;
+import com.android.launcher3.util.window.WindowManagerProxy;
+
+public class TestWindowManagerProxy extends WindowManagerProxy {
+
+    private final DeviceEmulationData mDevice;
+
+    public TestWindowManagerProxy(Context context, DeviceEmulationData device) {
+        super(true);
+        mDevice = device;
+    }
+
+    @Override
+    public boolean isInternalDisplay(Display display) {
+        return display.getDisplayId() == Display.DEFAULT_DISPLAY;
+    }
+
+    @Override
+    protected int getDimenByName(String resName, Resources res) {
+        Integer mock = mDevice.resourceOverrides.get(resName);
+        return mock != null ? mock : super.getDimenByName(resName, res);
+    }
+
+    @Override
+    public CachedDisplayInfo getDisplayInfo(Context context, Display display) {
+        int rotation = display.getRotation();
+        Point size = new Point(mDevice.width, mDevice.height);
+        RotationUtils.rotateSize(size, rotation);
+        Rect cutout = new Rect(mDevice.cutout);
+        RotationUtils.rotateRect(cutout, rotation);
+        return new CachedDisplayInfo(getDisplayId(display), size, rotation, cutout);
+    }
+
+    @Override
+    public WindowBounds getRealBounds(Context windowContext, Display display,
+            CachedDisplayInfo info) {
+        return estimateInternalDisplayBounds(windowContext)
+                .get(getDisplayId(display)).second[display.getRotation()];
+    }
+
+    @Override
+    public WindowInsets normalizeWindowInsets(Context context, WindowInsets oldInsets,
+            Rect outInsets) {
+        outInsets.set(getRealBounds(context, context.getDisplay(),
+                getDisplayInfo(context, context.getDisplay())).insets);
+        return oldInsets;
+    }
+}
diff --git a/tests/src/com/android/launcher3/deviceemulator/models/DeviceEmulationData.java b/tests/src/com/android/launcher3/deviceemulator/models/DeviceEmulationData.java
new file mode 100644
index 0000000..8d275cc
--- /dev/null
+++ b/tests/src/com/android/launcher3/deviceemulator/models/DeviceEmulationData.java
@@ -0,0 +1,154 @@
+/*
+ * 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.deviceemulator.models;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT;
+import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
+import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
+import static com.android.launcher3.ResourceUtils.getDimenByName;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.ArrayMap;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.IOUtils;
+import com.android.launcher3.util.IntArray;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Map;
+
+public class DeviceEmulationData {
+
+    public final int width;
+    public final int height;
+    public final int density;
+    public final String name;
+    public final String[] grids;
+    public final Rect cutout;
+    public final Map<String, Integer> resourceOverrides;
+
+    private static final String[] EMULATED_SYSTEM_RESOURCES = new String[]{
+            NAVBAR_HEIGHT,
+            NAVBAR_HEIGHT_LANDSCAPE,
+            NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE,
+            "status_bar_height",
+    };
+
+    public DeviceEmulationData(int width, int height, int density, Rect cutout, String name,
+            String[] grid,
+            Map<String, Integer> resourceOverrides) {
+        this.width = width;
+        this.height = height;
+        this.density = density;
+        this.name = name;
+        this.grids = grid;
+        this.cutout = cutout;
+        this.resourceOverrides = resourceOverrides;
+    }
+
+    public static DeviceEmulationData deviceFromJSON(JSONObject json) throws JSONException {
+        int width = json.getInt("width");
+        int height = json.getInt("height");
+        int density = json.getInt("density");
+        String name = json.getString("name");
+
+        JSONArray gridArray = json.getJSONArray("grids");
+        String[] grids = new String[gridArray.length()];
+        for (int i = 0, count = grids.length; i < count; i++) {
+            grids[i] = gridArray.getString(i);
+        }
+
+        IntArray deviceCutout = IntArray.fromConcatString(json.getString("cutout"));
+        Rect cutout = new Rect(deviceCutout.get(0), deviceCutout.get(1), deviceCutout.get(2),
+                deviceCutout.get(3));
+
+
+        JSONObject resourceOverridesJson = json.getJSONObject("resourceOverrides");
+        Map<String, Integer> resourceOverrides = new ArrayMap<>();
+        for (String key : resourceOverridesJson.keySet()) {
+            resourceOverrides.put(key, resourceOverridesJson.getInt(key));
+        }
+        return new DeviceEmulationData(width, height, density, cutout, name, grids,
+                resourceOverrides);
+    }
+
+    @Override
+    public String toString() {
+        JSONObject json = new JSONObject();
+        try {
+            json.put("width", width);
+            json.put("height", height);
+            json.put("density", density);
+            json.put("name", name);
+            json.put("cutout", IntArray.wrap(
+                    cutout.left, cutout.top, cutout.right, cutout.bottom).toConcatString());
+
+            JSONArray gridArray = new JSONArray();
+            Arrays.stream(grids).forEach(gridArray::put);
+            json.put("grids", gridArray);
+
+
+            JSONObject resourceOverrides = new JSONObject();
+            for (Map.Entry<String, Integer> e : this.resourceOverrides.entrySet()) {
+                resourceOverrides.put(e.getKey(), e.getValue());
+            }
+            json.put("resourceOverrides", resourceOverrides);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return json.toString();
+    }
+
+    public static DeviceEmulationData getCurrentDeviceData(Context context) {
+        DisplayController.Info info = DisplayController.INSTANCE.get(context).getInfo();
+        String[] grids = InvariantDeviceProfile.INSTANCE.get(context)
+                .parseAllGridOptions(context).stream()
+                .map(go -> go.name).toArray(String[]::new);
+        String code = Build.MODEL.replaceAll("\\s", "").toLowerCase();
+
+        Map<String, Integer> resourceOverrides = new ArrayMap<>();
+        for (String s : EMULATED_SYSTEM_RESOURCES) {
+            resourceOverrides.put(s, getDimenByName(s, context.getResources(), 0));
+        }
+        return new DeviceEmulationData(info.currentSize.x, info.currentSize.y,
+                info.getDensityDpi(), info.cutout, code, grids, resourceOverrides);
+    }
+
+    public static DeviceEmulationData getDevice(String deviceCode) throws Exception {
+        return DeviceEmulationData.deviceFromJSON(readJSON().getJSONObject(deviceCode));
+    }
+
+    private static JSONObject readJSON() throws Exception {
+        Context context = getInstrumentation().getContext();
+        Resources myRes = context.getResources();
+        int resId = myRes.getIdentifier("devices", "raw", context.getPackageName());
+        try (InputStream is = myRes.openRawResource(resId)) {
+            return new JSONObject(new String(IOUtils.toByteArray(is)));
+        }
+    }
+
+}
diff --git a/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt b/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt
new file mode 100644
index 0000000..309d055
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.view.View
+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 [MultiAdditivePropertyFactory] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MultiAdditivePropertyTest {
+
+    private val received = mutableListOf<Float>()
+
+    private val factory =
+        object : MultiAdditivePropertyFactory<View?>("Test", View.TRANSLATION_X) {
+            override fun apply(obj: View?, 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_sameIndexes_allApplied() {
+        val v1 = 50f
+        val v2 = 100f
+        p1.set(null, v1)
+        p1.set(null, v1)
+        p1.set(null, v2)
+
+        assertThat(received).containsExactly(v1, v1, v2)
+    }
+
+    @Test
+    fun set_differentIndexes_aggregationApplied() {
+        val v1 = 50f
+        val v2 = 100f
+        val v3 = 150f
+        p1.set(null, v1)
+        p2.set(null, v2)
+        p3.set(null, v3)
+
+        assertThat(received).containsExactly(v1, v1 + v2, v1 + v2 + v3)
+    }
+}
diff --git a/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt b/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
index 6099987..7d92214 100644
--- a/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
+++ b/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.view.View
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 657f213..4c41d7e 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -132,7 +132,9 @@
         dumpCommand("logcat -d -s TestRunner", diagFile(description, "FilteredLogcat", "txt"));
 
         // Dump bugreport
-        if (launcher.getSystemAnomalyMessage(false, false) != null) {
+        final String systemAnomalyMessage = launcher.getSystemAnomalyMessage(false, false);
+        if (systemAnomalyMessage != null) {
+            Log.d(TAG, "Saving bugreport, system anomaly message: " + systemAnomalyMessage, e);
             dumpCommand("bugreportz -s", diagFile(description, "Bugreport", "zip"));
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index fee4490..ae0f6a6 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -389,8 +389,7 @@
             // Since the destination can be on another page, we need to drag to the edge first
             // until we reach the target page
             while (targetDest.x > displayX || targetDest.x < 0) {
-                // TODO: b/219919285
-                int edgeX = targetDest.x > 0 ? displayX - 1 : 1;
+                int edgeX = targetDest.x > 0 ? displayX : 0;
                 Point screenEdge = new Point(edgeX, targetDest.y);
                 Point finalDragStart = dragStart;
                 executeAndWaitForPageScroll(launcher,