Merge changes from topic "kingyo-new-buttons-master"

* changes:
  Toggle notification panel when new buttons are clicked in taskbar
  Add new buttons to taskbar
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index c581415..02b83fe 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -42,6 +42,7 @@
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <!-- for rotating surface by arbitrary degree -->
     <uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
     
     <!--
     Permissions required for read/write access to the workspace data. These permission name
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index d1bf152..c559988 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -60,6 +60,7 @@
     PredictedHotseatContainer predicted_hotseat_container = 10;
     TaskSwitcherContainer task_switcher_container = 11;
     TaskBarContainer task_bar_container = 12;
+    WallpapersContainer wallpapers_container = 13;
     ExtendedContainers extended_containers = 20;
   }
 }
@@ -247,6 +248,12 @@
   }
 }
 
+// Represents wallpapers container for quick switching.
+message WallpapersContainer {
+  // Number of wallpapers in the container.
+  optional int32 cardinality = 1;
+}
+
 // Represents state of EditText field before update.
 enum FromState {
   // Default value.
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 25b39ed..352cd3e 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -36,6 +36,7 @@
     <uses-permission android:name="android.permission.READ_FRAME_BUFFER"/>
     <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY"/>
     <uses-permission android:name="android.permission.MONITOR_INPUT"/>
+    <uses-permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES"/>
 
     <uses-permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY" />
 
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index f303f31..01d675f 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -25,13 +25,6 @@
         android:clipToPadding="false"
         android:visibility="invisible" />
 
-    <com.android.quickstep.views.SplitPlaceholderView
-        android:id="@+id/split_placeholder"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/split_placeholder_size"
-        android:background="@android:color/darker_gray"
-        android:visibility="gone" />
-
     <include
         android:id="@+id/overview_actions_view"
         layout="@layout/overview_actions_container" />
diff --git a/quickstep/res/values-sw720dp/dimens.xml b/quickstep/res/values-sw720dp/dimens.xml
new file mode 100644
index 0000000..2831a6f
--- /dev/null
+++ b/quickstep/res/values-sw720dp/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+*/
+-->
+<resources>
+    <dimen name="overview_grid_row_spacing">44dp</dimen>
+    <dimen name="overview_page_spacing_grid_portrait">44dp</dimen>
+    <dimen name="overview_page_spacing_grid_landscape">44dp</dimen>
+</resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index c37b335..c9e33a7 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -36,6 +36,7 @@
     <dimen name="overview_task_margin">16dp</dimen>
     <dimen name="overview_task_margin_focused">12dp</dimen>
     <dimen name="overview_task_margin_grid">4dp</dimen>
+    <item name="overview_max_scale" format="float" type="dimen">0.7</item>
 
     <!-- Overrideable in overlay that provides the Overview Actions. -->
     <dimen name="overview_actions_height">48dp</dimen>
@@ -51,9 +52,10 @@
 
     <dimen name="overview_grid_side_margin_portrait">60dp</dimen>
     <dimen name="overview_grid_side_margin_landscape">52dp</dimen>
-    <dimen name="overview_grid_row_spacing_portrait">17.13dp</dimen>
-    <dimen name="overview_grid_row_spacing_landscape">13.38dp</dimen>
-    <dimen name="overview_grid_focus_vertical_margin">0dp</dimen>
+    <dimen name="overview_grid_row_spacing">36dp</dimen>
+    <dimen name="overview_page_spacing">16dp</dimen>
+    <dimen name="overview_page_spacing_grid_portrait">36dp</dimen>
+    <dimen name="overview_page_spacing_grid_landscape">38dp</dimen>
 
     <!-- These speeds are in dp/s -->
     <dimen name="max_task_dismiss_drag_velocity">2.25dp</dimen>
@@ -61,8 +63,6 @@
     <dimen name="default_task_dismiss_drag_velocity_grid">1dp</dimen>
     <dimen name="default_task_dismiss_drag_velocity_grid_focus_task">5dp</dimen>
 
-    <dimen name="recents_page_spacing">16dp</dimen>
-    <dimen name="recents_page_spacing_grid">36dp</dimen>
     <dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
 
     <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
@@ -216,6 +216,7 @@
 
     <!-- Taskbar -->
     <dimen name="taskbar_size">@*android:dimen/taskbar_frame_height</dimen>
+    <dimen name="taskbar_ime_size">48dp</dimen>
     <dimen name="taskbar_icon_touch_size">48dp</dimen>
     <dimen name="taskbar_icon_drag_icon_size">54dp</dimen>
     <dimen name="taskbar_folder_margin">16dp</dimen>
@@ -227,7 +228,7 @@
     <dimen name="taskbar_contextual_buttons_size">35dp</dimen>
     <dimen name="taskbar_stashed_size">24dp</dimen>
     <dimen name="taskbar_stashed_handle_width">220dp</dimen>
-    <dimen name="taskbar_stashed_handle_height">6dp</dimen>
+    <dimen name="taskbar_stashed_handle_height">4dp</dimen>
     <dimen name="taskbar_edu_wave_anim_trans_y">25dp</dimen>
     <dimen name="taskbar_edu_wave_anim_trans_y_return_overshoot">4dp</dimen>
 </resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index fce49dd..59997e9 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -233,6 +233,16 @@
     <string name="taskbar_edu_close">Close</string>
     <!-- Text on button to finish a tutorial [CHAR_LIMIT=16] -->
     <string name="taskbar_edu_done">Done</string>
+        <!-- Content description for home button [CHAR_LIMIT=16] -->
+    <string name="taskbar_button_home">Home</string>
+    <!-- Content description for accessibility button [CHAR_LIMIT=16] -->
+    <string name="taskbar_button_a11y">Accessibility</string>
+    <!-- Content description for back button [CHAR_LIMIT=16] -->
+    <string name="taskbar_button_back">Back</string>
+    <!-- Content description for ime switcher button [CHAR_LIMIT=16] -->
+    <string name="taskbar_button_ime_switcher">IME switcher</string>
+    <!-- Content description for recents button [CHAR_LIMIT=16] -->
+    <string name="taskbar_button_recents">Recents</string>
 
     <!-- Label for moving drop target to the top or left side of the screen, depending on orientation (from the taskbar only). -->
     <string name="move_drop_target_top_or_left">Move to top&#47;left</string>
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 6ab49f8..da18357 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -64,6 +64,7 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.PersistedItemArray;
+import com.android.quickstep.logging.SettingsChangeLogger;
 import com.android.quickstep.logging.StatsLogCompatManager;
 import com.android.systemui.shared.system.SysUiStatsLog;
 
@@ -97,10 +98,12 @@
     private final InvariantDeviceProfile mIDP;
     private final AppEventProducer mAppEventProducer;
     private final StatsManager mStatsManager;
+    private final Context mContext;
 
     protected boolean mActive = false;
 
     public QuickstepModelDelegate(Context context) {
+        mContext = context;
         mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
 
         mIDP = InvariantDeviceProfile.INSTANCE.get(context);
@@ -210,6 +213,7 @@
                                         "Successfully logged %d workspace items with instanceId=%d",
                                         itemsIdMap.size(), instanceId.getId()));
                         additionalSnapshotEvents(instanceId);
+                        SettingsChangeLogger.INSTANCE.get(mContext).logSnapshot(instanceId);
                         return StatsManager.PULL_SUCCESS;
                     }
             );
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 2622700..5e8db69 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -20,7 +20,6 @@
 
 import android.animation.Animator;
 import android.annotation.ColorInt;
-import android.graphics.Rect;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -114,13 +113,6 @@
         return !mTaskbarLauncherStateController.isAnimatingToLauncher();
     }
 
-    @Override
-    protected void updateContentInsets(Rect outContentInsets) {
-        int contentHeight = mControllers.taskbarStashController.getContentHeight();
-        TaskbarDragLayer dragLayer = mControllers.taskbarActivityContext.getDragLayer();
-        outContentInsets.top = dragLayer.getHeight() - contentHeight;
-    }
-
     /**
      * Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 3beb55f..fe892c3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -110,6 +110,9 @@
 
     private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
             this::updateNavButtonTranslationY);
+    private final AnimatedFloat mTaskbarNavButtonTranslationYForIme = new AnimatedFloat(
+            this::updateNavButtonTranslationY);
+    // Only applies to mTaskbarNavButtonTranslationY
     private final AnimatedFloat mNavButtonTranslationYMultiplier = new AnimatedFloat(
             this::updateNavButtonTranslationY);
     private final AnimatedFloat mTaskbarNavButtonDarkIntensity = new AnimatedFloat(
@@ -166,14 +169,26 @@
                 .getKeyguardBgTaskbar(),
                 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0, AnimatedFloat.VALUE, 1, 0));
 
-        // Make sure to remove nav bar buttons translation when notification shade is expanded.
-        mPropertyHolders.add(new StatePropertyHolder(mNavButtonTranslationYMultiplier,
-                flags -> (flags & FLAG_NOTIFICATION_SHADE_EXPANDED) != 0, AnimatedFloat.VALUE,
-                0, 1));
-
         // Force nav buttons (specifically back button) to be visible during setup wizard.
         boolean isInSetup = !mContext.isUserSetupComplete();
-        if (isThreeButtonNav || isInSetup) {
+        boolean alwaysShowButtons = isThreeButtonNav || isInSetup;
+
+        // Make sure to remove nav bar buttons translation when notification shade is expanded or
+        // IME is showing (add separate translation for IME).
+        int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE;
+        mPropertyHolders.add(new StatePropertyHolder(mNavButtonTranslationYMultiplier,
+                flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE,
+                0, 1));
+        // Center nav buttons in new height for IME.
+        float transForIme = (mContext.getDeviceProfile().taskbarSize
+                - mContext.getTaskbarHeightForIme()) / 2f;
+        // For gesture nav, nav buttons only show for IME anyway so keep them translated down.
+        float defaultButtonTransY = alwaysShowButtons ? 0 : transForIme;
+        mPropertyHolders.add(new StatePropertyHolder(mTaskbarNavButtonTranslationYForIme,
+                flags -> (flags & FLAG_IME_VISIBLE) != 0, AnimatedFloat.VALUE, transForIme,
+                defaultButtonTransY));
+
+        if (alwaysShowButtons) {
             initButtons(mNavButtonContainer, mEndContextualContainer,
                     mControllers.navButtonController);
 
@@ -423,8 +438,10 @@
     }
 
     private void updateNavButtonTranslationY() {
-        mNavButtonsView.setTranslationY(mTaskbarNavButtonTranslationY.value
-                * mNavButtonTranslationYMultiplier.value);
+        float normalTranslationY = mTaskbarNavButtonTranslationY.value
+                * mNavButtonTranslationYMultiplier.value;
+        float otherTranslationY = mTaskbarNavButtonTranslationYForIme.value;
+        mNavButtonsView.setTranslationY(normalTranslationY + otherTranslationY);
     }
 
     private void updateNavButtonDarkIntensity() {
@@ -448,6 +465,8 @@
             @LayoutRes int layoutId) {
         ImageView buttonView = addButton(parent, id, layoutId);
         buttonView.setImageResource(drawableId);
+        buttonView.setContentDescription(parent.getContext().getString(
+                navButtonController.getButtonContentDescription(buttonType)));
         buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType));
         buttonView.setOnLongClickListener(view ->
                 navButtonController.onButtonLongClick(buttonType));
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 883515e..87de596 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -32,6 +32,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo.Config;
 import android.content.pm.LauncherApps;
+import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
@@ -100,6 +101,7 @@
 
     private final WindowManager mWindowManager;
     private final @Nullable RoundedCorner mLeftCorner, mRightCorner;
+    private final int mTaskbarHeightForIme;
     private WindowManager.LayoutParams mWindowLayoutParams;
     private boolean mIsFullscreen;
     // The size we should return to when we call setTaskbarWindowFullscreen(false)
@@ -128,10 +130,13 @@
         mIsUserSetupComplete = SettingsCache.INSTANCE.get(this).getValue(
                 Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
 
-        float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
-        mDeviceProfile.updateIconSize(1, getResources());
+        final Resources resources = getResources();
+        float taskbarIconSize = resources.getDimension(R.dimen.taskbar_icon_size);
+        mDeviceProfile.updateIconSize(1, resources);
         float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
-        mDeviceProfile.updateIconSize(iconScale, getResources());
+        mDeviceProfile.updateIconSize(iconScale, resources);
+
+        mTaskbarHeightForIme = resources.getDimensionPixelSize(R.dimen.taskbar_ime_size);
 
         mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
 
@@ -205,7 +210,9 @@
         // Adjust the frame by the rounded corners (ie. leaving just the bar as the inset) when
         // the IME is showing
         mWindowLayoutParams.providedInternalImeInsets = Insets.of(0,
-                getDefaultTaskbarWindowHeight() - mDeviceProfile.taskbarSize, 0, 0);
+                getDefaultTaskbarWindowHeight() - mTaskbarHeightForIme, 0, 0);
+
+        mWindowLayoutParams.insetsRoundedCornerFrame = true;
 
         // Initialize controllers after all are constructed.
         mControllers.init(sharedState);
@@ -441,13 +448,27 @@
     }
 
     /**
+     * Notify system to inset the rounded corner frame based on the task bar insets.
+     */
+    public void updateInsetRoundedCornerFrame(boolean shouldInsetsRoundedCorner) {
+        if (!mDragLayer.isAttachedToWindow()
+                || mWindowLayoutParams.insetsRoundedCornerFrame == shouldInsetsRoundedCorner) {
+            return;
+        }
+        mWindowLayoutParams.insetsRoundedCornerFrame = shouldInsetsRoundedCorner;
+        mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
+    }
+
+    /**
      * Updates the TaskbarContainer height (pass {@link #getDefaultTaskbarWindowHeight()} to reset).
      */
     public void setTaskbarWindowHeight(int height) {
         if (mWindowLayoutParams.height == height || mIsDestroyed) {
             return;
         }
-        if (height != MATCH_PARENT) {
+        if (height == MATCH_PARENT) {
+            height = mDeviceProfile.heightPx;
+        } else {
             mLastRequestedNonFullscreenHeight = height;
             if (mIsFullscreen) {
                 // We still need to be fullscreen, so defer any change to our height until we call
@@ -458,6 +479,8 @@
             }
         }
         mWindowLayoutParams.height = height;
+        mWindowLayoutParams.providedInternalImeInsets =
+                Insets.of(0, height - mTaskbarHeightForIme, 0, 0);
         mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
     }
 
@@ -468,6 +491,13 @@
         return mDeviceProfile.taskbarSize + Math.max(getLeftCornerRadius(), getRightCornerRadius());
     }
 
+    /**
+     * Returns the bottom insets taskbar provides to the IME when IME is visible.
+     */
+    public int getTaskbarHeightForIme() {
+        return mTaskbarHeightForIme;
+    }
+
     protected void onTaskbarIconClicked(View view) {
         Object tag = view.getTag();
         if (tag instanceof Task) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 806b390..a918016 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -181,10 +181,13 @@
         }
 
         /**
-         * Called to update the {@link InsetsInfo#contentInsets}.
+         * 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) {
-            mControllers.uiController.updateContentInsets(outContentInsets);
+            int contentHeight = mControllers.taskbarStashController
+                    .getContentHeightToReportToApps();
+            outContentInsets.top = mTaskbarDragLayer.getHeight() - contentHeight;
         }
 
         /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index b2c29b2..7a50d0b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -49,13 +49,15 @@
 
     public static final int FLAG_RESUMED = 1 << 0;
     public static final int FLAG_RECENTS_ANIMATION_RUNNING = 1 << 1;
-    public static final int FLAG_TRANSITION_STATE_START_STASHED = 1 << 2;
-    public static final int FLAG_TRANSITION_STATE_COMMITTED_STASHED = 1 << 3;
+    public static final int FLAG_TRANSITION_STATE_RUNNING = 1 << 2;
+
+    /** Equivalent to an int with all 1s for binary operation purposes */
+    private static final int FLAGS_ALL = ~0;
 
     private final AnimatedFloat mIconAlignmentForResumedState =
-            new AnimatedFloat(this::onIconAlignmentRatioChanged);
+            new AnimatedFloat(this::onIconAlignmentRatioChangedForAppAndHomeTransition);
     private final AnimatedFloat mIconAlignmentForGestureState =
-            new AnimatedFloat(this::onIconAlignmentRatioChanged);
+            new AnimatedFloat(this::onIconAlignmentRatioChangedForAppAndHomeTransition);
     private final AnimatedFloat mIconAlignmentForLauncherState =
             new AnimatedFloat(this::onIconAlignmentRatioChangedForStateTransition);
 
@@ -64,8 +66,9 @@
     private MultiValueAlpha.AlphaProperty mIconAlphaForHome;
     private BaseQuickstepLauncher mLauncher;
 
-    private int mPrevState;
+    private Integer mPrevState;
     private int mState;
+    private LauncherState mLauncherState = LauncherState.NORMAL;
 
     private boolean mIsAnimatingToLauncherViaGesture;
     private boolean mIsAnimatingToLauncherViaResume;
@@ -75,15 +78,20 @@
 
                 @Override
                 public void onStateTransitionStart(LauncherState toState) {
-                    updateStateForFlag(FLAG_TRANSITION_STATE_START_STASHED,
-                            toState.isTaskbarStashed());
+                    if (toState != mLauncherState) {
+                        // Treat FLAG_TRANSITION_STATE_RUNNING as a changed flag even if a previous
+                        // state transition was already running, so we update the new target.
+                        mPrevState &= ~FLAG_TRANSITION_STATE_RUNNING;
+                        mLauncherState = toState;
+                    }
+                    updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, true);
                     applyState();
                 }
 
                 @Override
                 public void onStateTransitionComplete(LauncherState finalState) {
-                    updateStateForFlag(FLAG_TRANSITION_STATE_COMMITTED_STASHED,
-                            finalState.isTaskbarStashed());
+                    mLauncherState = finalState;
+                    updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, false);
                     applyState();
                 }
             };
@@ -100,9 +108,14 @@
                 (Consumer<Float>) alpha -> mLauncher.getHotseat().setIconsAlpha(alpha > 0 ? 0 : 1));
 
         mIconAlignmentForResumedState.finishAnimation();
-        onIconAlignmentRatioChanged();
+        onIconAlignmentRatioChangedForAppAndHomeTransition();
 
         mLauncher.getStateManager().addStateListener(mStateListener);
+
+        // Initialize to the current launcher state
+        updateStateForFlag(FLAG_RESUMED, launcher.hasBeenResumed());
+        mLauncherState = launcher.getStateManager().getState();
+        applyState(0);
     }
 
     public void onDestroy() {
@@ -121,9 +134,10 @@
         // If going home, align the icons to hotseat
         AnimatorSet animatorSet = new AnimatorSet();
 
+        // Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly.
         TaskbarStashController stashController = mControllers.taskbarStashController;
         stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
-                toState.isTaskbarStashed());
+                toState.isTaskbarStashed(mLauncher));
         stashController.updateStateForFlag(FLAG_IN_APP, false);
 
         updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, true);
@@ -182,10 +196,11 @@
 
     public Animator applyState(long duration, boolean start) {
         Animator animator = null;
-        if (mPrevState != mState) {
-            int changedFlags = mPrevState ^ mState;
-            animator = onStateChangeApplied(changedFlags, duration, start);
+        if (mPrevState == null || mPrevState != mState) {
+            // If this is our initial state, treat all flags as changed.
+            int changedFlags = mPrevState == null ? FLAGS_ALL : mPrevState ^ mState;
             mPrevState = mState;
+            animator = onStateChangeApplied(changedFlags, duration, start);
         }
         return animator;
     }
@@ -195,7 +210,8 @@
         if (hasAnyFlag(changedFlags, FLAG_RESUMED)) {
             boolean isResumed = isResumed();
             ObjectAnimator anim = mIconAlignmentForResumedState
-                    .animateToValue(isResumed ? 1 : 0)
+                    .animateToValue(isResumed && goingToUnstashedLauncherState()
+                            ? 1 : 0)
                     .setDuration(duration);
 
             anim.addListener(new AnimatorListenerAdapter() {
@@ -219,7 +235,8 @@
         if (hasAnyFlag(changedFlags, FLAG_RECENTS_ANIMATION_RUNNING)) {
             boolean isRecentsAnimationRunning = isRecentsAnimationRunning();
             Animator animator = mIconAlignmentForGestureState
-                    .animateToValue(isRecentsAnimationRunning ? 1 : 0);
+                    .animateToValue(isRecentsAnimationRunning && goingToUnstashedLauncherState()
+                            ? 1 : 0);
             if (isRecentsAnimationRunning) {
                 animator.setDuration(duration);
             }
@@ -237,14 +254,21 @@
             animatorSet.play(animator);
         }
 
-        if (hasAnyFlag(changedFlags, FLAG_TRANSITION_STATE_START_STASHED)) {
-            playStateTransitionAnim(isTransitionStateStartStashed(), animatorSet, duration,
-                    false /* committed */);
+        if (hasAnyFlag(changedFlags, FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING)) {
+            boolean goingToLauncher = hasAnyFlag(FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING);
+            animatorSet.play(mTaskbarBackgroundAlpha.animateToValue(goingToLauncher ? 0 : 1)
+                    .setDuration(duration));
         }
 
-        if (hasAnyFlag(changedFlags, FLAG_TRANSITION_STATE_COMMITTED_STASHED)) {
-            playStateTransitionAnim(isTransitionStateCommittedStashed(), animatorSet, duration,
-                    true /* committed */);
+        if (hasAnyFlag(changedFlags, FLAG_TRANSITION_STATE_RUNNING)) {
+            boolean committed = !hasAnyFlag(FLAG_TRANSITION_STATE_RUNNING);
+            playStateTransitionAnim(animatorSet, duration, committed);
+
+            if (committed && mLauncherState == LauncherState.QUICK_SWITCH) {
+                // We're about to be paused, set immediately to ensure seamless handoff.
+                updateStateForFlag(FLAG_RESUMED, false);
+                applyState(0 /* duration */);
+            }
         }
 
         if (start) {
@@ -253,17 +277,24 @@
         return animatorSet;
     }
 
-    private void playStateTransitionAnim(boolean isTransitionStateStashed,
-            AnimatorSet animatorSet, long duration, boolean committed) {
+    /** Returns whether we're going to a state where taskbar icons should align with launcher. */
+    private boolean goingToUnstashedLauncherState() {
+        return !mControllers.taskbarStashController.isInStashedLauncherState();
+    }
+
+    private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
+            boolean committed) {
+        boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
+        float toAlignment = mLauncherState.isTaskbarAlignedWithHotseat(mLauncher) ? 1 : 0;
+
         TaskbarStashController controller = mControllers.taskbarStashController;
-        controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
-                isTransitionStateStashed);
+        controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState);
         Animator stashAnimator = controller.applyStateWithoutStart(duration);
         if (stashAnimator != null) {
             stashAnimator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    if (isTransitionStateStashed && committed) {
+                    if (isInStashedState && committed) {
                         // Reset hotseat alpha to default
                         mLauncher.getHotseat().setIconsAlpha(1);
                     }
@@ -271,14 +302,16 @@
 
                 @Override
                 public void onAnimationStart(Animator animation) {
-                    mIconAlphaForHome.setValue(mLauncher.getHotseat().getIconsAlpha());
+                    if (mLauncher.getHotseat().getIconsAlpha() > 0) {
+                        mIconAlphaForHome.setValue(mLauncher.getHotseat().getIconsAlpha());
+                    }
                 }
             });
             animatorSet.play(stashAnimator);
-            animatorSet.play(mIconAlignmentForLauncherState.animateToValue(
-                    getCurrentIconAlignmentRatioForLauncherState(),
-                    isTransitionStateStashed ? 0 : 1));
         }
+
+        animatorSet.play(mIconAlignmentForLauncherState.animateToValue(toAlignment)
+                .setDuration(duration));
     }
 
     private boolean isResumed() {
@@ -289,20 +322,15 @@
         return (mState & FLAG_RECENTS_ANIMATION_RUNNING) != 0;
     }
 
-    private boolean isTransitionStateStartStashed() {
-        return (mState & FLAG_TRANSITION_STATE_START_STASHED) != 0;
-    }
-
-    private boolean isTransitionStateCommittedStashed() {
-        return (mState & FLAG_TRANSITION_STATE_COMMITTED_STASHED) != 0;
-    }
-
     private void onIconAlignmentRatioChangedForStateTransition() {
+        if (!isResumed()) {
+            return;
+        }
         onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioForLauncherState);
     }
 
-    private void onIconAlignmentRatioChanged() {
-        onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatio);
+    private void onIconAlignmentRatioChangedForAppAndHomeTransition() {
+        onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioBetweenAppAndHome);
     }
 
     private void onIconAlignmentRatioChanged(Supplier<Float> alignmentSupplier) {
@@ -313,13 +341,11 @@
         mControllers.taskbarViewController.setLauncherIconAlignment(
                 alignment, mLauncher.getDeviceProfile());
 
-        mTaskbarBackgroundAlpha.updateValue(1 - alignment);
-
         // Switch taskbar and hotseat in last frame
         setTaskbarViewVisible(alignment < 1);
     }
 
-    private float getCurrentIconAlignmentRatio() {
+    private float getCurrentIconAlignmentRatioBetweenAppAndHome() {
         return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
     }
 
@@ -354,12 +380,12 @@
 
             // Update the resumed state immediately to ensure a seamless handoff
             boolean launcherResumed = !finishedToApp;
-            mIconAlignmentForResumedState.updateValue(launcherResumed ? 1 : 0);
-
             updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, false);
             updateStateForFlag(FLAG_RESUMED, launcherResumed);
             applyState();
-
+            // Set this last because applyState() might also animate it.
+            mIconAlignmentForResumedState.cancelAnimation();
+            mIconAlignmentForResumedState.updateValue(launcherResumed ? 1 : 0);
 
             TaskbarStashController controller = mControllers.taskbarStashController;
             controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 7b52ef7..a601984 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -24,7 +24,9 @@
 import android.os.Handler;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.StringRes;
 
+import com.android.launcher3.R;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.quickstep.OverviewCommandHelper;
@@ -129,6 +131,23 @@
         }
     }
 
+    public @StringRes int getButtonContentDescription(@TaskbarButton int buttonType) {
+        switch (buttonType) {
+            case BUTTON_HOME:
+                return R.string.taskbar_button_home;
+            case BUTTON_A11Y:
+                return R.string.taskbar_button_a11y;
+            case BUTTON_BACK:
+                return R.string.taskbar_button_back;
+            case BUTTON_IME_SWITCH:
+                return R.string.taskbar_button_ime_switcher;
+            case BUTTON_RECENTS:
+                return R.string.taskbar_button_recents;
+            default:
+                return 0;
+        }
+    }
+
     /**
      * Checks if the user has long pressed back and recents buttons
      * "together" (within {@link #SCREEN_PIN_LONG_PRESS_THRESHOLD})ms
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 8965dc4..d1560fe 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -53,10 +53,17 @@
     public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 6;
 
     // If we're in an app and any of these flags are enabled, taskbar should be stashed.
-    public static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
+    private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
             | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
             | FLAG_STASHED_IN_APP_IME;
 
+    // If any of these flags are enabled, inset apps by our stashed height instead of our unstashed
+    // height. This way the reported insets are consistent even during transitions out of the app.
+    // Currently any flag that causes us to stash in an app is included, except for IME since that
+    // covers the underlying app anyway and thus the app shouldn't change insets.
+    private static final int FLAGS_REPORT_STASHED_INSETS_TO_APP = FLAGS_STASHED_IN_APP
+            & ~FLAG_STASHED_IN_APP_IME;
+
     /**
      * How long to stash/unstash when manually invoked via long press.
      */
@@ -164,9 +171,13 @@
 
         boolean isManuallyStashedInApp = supportsManualStashing()
                 && mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
+        boolean isInSetup = !mActivity.isUserSetupComplete() || sharedState.setupUIVisible;
         updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
-        updateStateForFlag(FLAG_STASHED_IN_APP_SETUP,
-                !mActivity.isUserSetupComplete() || sharedState.setupUIVisible);
+        updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
+        if (isInSetup) {
+            // Update the in-app state to ensure isStashed() reflects right state during SUW
+            updateStateForFlag(FLAG_IN_APP, true);
+        }
         applyState();
 
         notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
@@ -216,6 +227,13 @@
         return hasAnyFlag(FLAGS_STASHED_IN_APP);
     }
 
+    /**
+     * Returns whether the taskbar should be stashed in the current LauncherState.
+     */
+    public boolean isInStashedLauncherState() {
+        return hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing();
+    }
+
     private boolean hasAnyFlag(int flagMask) {
         return hasAnyFlag(mState, flagMask);
     }
@@ -232,8 +250,11 @@
         return !mIsStashed && (mState & FLAG_IN_APP) != 0;
     }
 
-    public int getContentHeight() {
-        if (isStashed()) {
+    /**
+     * Returns the height that taskbar will inset when inside apps.
+     */
+    public int getContentHeightToReportToApps() {
+        if (hasAnyFlag(FLAGS_REPORT_STASHED_INSETS_TO_APP)) {
             boolean isAnimating = mAnimator != null && mAnimator.isStarted();
             return mControllers.stashedHandleViewController.isStashedHandleVisible() || isAnimating
                     ? mStashedHeight : 0;
@@ -518,6 +539,7 @@
 
     private void notifyStashChange(boolean visible, boolean stashed) {
         mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
+        mControllers.taskbarActivityContext.updateInsetRoundedCornerFrame(visible && !stashed);
         mControllers.rotationButtonController.onTaskbarStateChange(visible, stashed);
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index f6bc785..abad906 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.taskbar;
 
-import android.graphics.Rect;
 import android.view.View;
 
 import androidx.annotation.CallSuper;
@@ -49,8 +48,6 @@
         return true;
     }
 
-    protected void updateContentInsets(Rect outContentInsets) { }
-
     protected void onStashedInAppChanged() { }
 
     public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index c554fd0..8f89d30 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -69,7 +69,7 @@
     }
 
     @Override
-    public boolean isTaskbarStashed() {
+    public boolean isTaskbarStashed(Launcher launcher) {
         return true;
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 4984b95..b733007 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -74,8 +74,7 @@
         return super.getVisibleElements(launcher)
                 & ~OVERVIEW_ACTIONS
                 & ~CLEAR_ALL_BUTTON
-                & ~VERTICAL_SWIPE_INDICATOR
-                | TASKBAR;
+                & ~VERTICAL_SWIPE_INDICATOR;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index a4eff87..08d0a80 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -92,7 +92,7 @@
     }
 
     @Override
-    public boolean isTaskbarStashed() {
+    public boolean isTaskbarStashed(Launcher launcher) {
         return true;
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index d36e76b..969abc2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.util.Themes;
@@ -42,6 +43,10 @@
 
     @Override
     public int getWorkspaceScrimColor(Launcher launcher) {
+        DeviceProfile dp = launcher.getDeviceProfile();
+        if (dp.isTaskbarPresentInApps) {
+            return launcher.getColor(R.color.taskbar_background);
+        }
         return Themes.getAttrColor(launcher, R.attr.overviewScrimColor);
     }
 
@@ -53,6 +58,16 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        return TASKBAR;
+        return NONE;
+    }
+
+    @Override
+    public boolean isTaskbarStashed(Launcher launcher) {
+        return !launcher.getDeviceProfile().isTaskbarPresentInApps;
+    }
+
+    @Override
+    public boolean isTaskbarAlignedWithHotseat(Launcher launcher) {
+        return false;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index 4f5f27a..e79d56b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -17,8 +17,6 @@
 package com.android.launcher3.uioverrides.states;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -38,12 +36,6 @@
     @Override
     public float getSplitSelectTranslation(Launcher launcher) {
         RecentsView recentsView = launcher.getOverviewPanel();
-        int splitPosition = recentsView.getSplitPlaceholder().getActiveSplitStagePosition();
-        if (!recentsView.shouldShiftThumbnailsForSplitSelect()) {
-            return 0f;
-        }
-        PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
-        int direction = orientationHandler.getSplitTranslationDirectionFactor(splitPosition);
-        return launcher.getResources().getDimension(R.dimen.split_placeholder_size) * direction;
+        return recentsView.getSplitSelectTranslation();
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index ff3c517..f6148a7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -236,8 +236,10 @@
         //   - RecentsView fade (if it's empty)
         PendingAnimation xAnim = new PendingAnimation((long) (mXRange * 2));
         xAnim.setFloat(mRecentsView, ADJACENT_PAGE_HORIZONTAL_OFFSET, scaleAndOffset[1], LINEAR);
+        // Use QuickSwitchState instead of OverviewState to determine scrim color,
+        // since we need to take potential taskbar into account.
         xAnim.setViewBackgroundColor(mLauncher.getScrimView(),
-                toState.getWorkspaceScrimColor(mLauncher), LINEAR);
+                QUICK_SWITCH.getWorkspaceScrimColor(mLauncher), LINEAR);
         if (mRecentsView.getTaskViewCount() == 0) {
             xAnim.addFloat(mRecentsView, CONTENT_ALPHA, 0f, 1f, LINEAR);
         }
@@ -310,6 +312,11 @@
                 }
             });
             overviewAnim.start();
+
+            // Create an empty state transition so StateListeners get onStateTransitionStart().
+            mLauncher.getStateManager().createAnimationToNewWorkspace(
+                    OVERVIEW, config.duration, StateAnimationConfig.SKIP_ALL_ANIMATIONS)
+                    .dispatchOnStart();
             return;
         }
 
@@ -384,6 +391,7 @@
             config.animFlags = SKIP_ALL_ANIMATIONS;
             updateNonOverviewAnim(targetState, config);
             nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
+            mNonOverviewAnim.dispatchOnStart();
 
             new WorkspaceRevealAnim(mLauncher, false /* animateOverviewScrim */).start();
         } else {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index fe69c9b..1bc789b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -19,6 +19,7 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN;
 
@@ -47,17 +48,6 @@
 
     private static final String TAG = "StatusBarController";
 
-    /**
-     * Window flag: Enable touches to slide out of a window into neighboring
-     * windows in mid-gesture instead of being captured for the duration of
-     * the gesture.
-     *
-     * This flag changes the behavior of touch focus for this window only.
-     * Touches can slide out of the window but they cannot necessarily slide
-     * back in (unless the other window with touch focus permits it).
-     */
-    private static final int FLAG_SLIPPERY = 0x20000000;
-
     private final Launcher mLauncher;
     private final SystemUiProxy mSystemUiProxy;
     private final float mTouchSlop;
@@ -140,6 +130,15 @@
         return true;
     }
 
+    /**
+     * FLAG_SLIPPERY enables touches to slide out of a window into neighboring
+     * windows in mid-gesture instead of being captured for the duration of
+     * the gesture.
+     *
+     * This flag changes the behavior of touch focus for this window only.
+     * Touches can slide out of the window but they cannot necessarily slide
+     * back in (unless the other window with touch focus permits it).
+     */
     private void setWindowSlippery(boolean enable) {
         Window w = mLauncher.getWindow();
         WindowManager.LayoutParams wlp = w.getAttributes();
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 1a901f1..0e5282a 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -74,6 +74,7 @@
 import android.view.WindowInsets;
 import android.view.animation.Interpolator;
 import android.widget.Toast;
+import android.window.PictureInPictureSurfaceTransaction;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
@@ -90,6 +91,7 @@
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.tracing.InputConsumerProto;
 import com.android.launcher3.tracing.SwipeHandlerProto;
+import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.BaseActivityInterface.AnimationFactory;
@@ -97,7 +99,6 @@
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActivityInitListener;
-import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.InputConsumerProxy;
 import com.android.quickstep.util.InputProxyHandlerFactory;
@@ -1261,7 +1262,8 @@
             HomeAnimationFactory homeAnimFactory =
                     createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
                             runningTaskTarget);
-            mIsSwipingPipToHome = homeAnimFactory.supportSwipePipToHome() && appCanEnterPip;
+            mIsSwipingPipToHome = !mIsSwipeForStagedSplit
+                    && homeAnimFactory.supportSwipePipToHome() && appCanEnterPip;
             final RectFSpringAnim[] windowAnim;
             if (mIsSwipingPipToHome) {
                 mSwipePipToHomeAnimator = createWindowAnimationToPip(
@@ -1728,7 +1730,7 @@
             // If there are no targets or the animation not started, then there is nothing to finish
             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
         } else {
-            maybeFinishSwipePipToHome();
+            maybeFinishSwipeToHome();
             finishRecentsControllerToHome(
                     () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
         }
@@ -1737,10 +1739,11 @@
     }
 
     /**
-     * Resets the {@link #mIsSwipingPipToHome} and notifies SysUI that transition is finished
-     * if applicable. This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
+     * Notifies SysUI that transition is finished if applicable and also pass leash transactions
+     * from Launcher to WM.
+     * This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
      */
-    private void maybeFinishSwipePipToHome() {
+    private void maybeFinishSwipeToHome() {
         if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
             SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
                     mSwipePipToHomeAnimator.getComponentName(),
@@ -1751,6 +1754,18 @@
                     mSwipePipToHomeAnimator.getFinishTransaction(),
                     mSwipePipToHomeAnimator.getContentOverlay());
             mIsSwipingPipToHome = false;
+        } else if (mIsSwipeForStagedSplit) {
+            // Transaction to hide the task to avoid flicker for entering PiP from split-screen.
+            PictureInPictureSurfaceTransaction tx =
+                    new PictureInPictureSurfaceTransaction.Builder()
+                            .setAlpha(0f)
+                            .build();
+            int[] taskIds =
+                        LauncherSplitScreenListener.INSTANCE.getNoCreate().getRunningSplitTaskIds();
+            for (int taskId : taskIds) {
+                mRecentsAnimationController.setFinishTaskTransaction(taskId,
+                        tx, null /* overlay */);
+            }
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index a566765..1d4ed4c 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -221,6 +221,7 @@
 
             PointF taskDimension = getTaskDimension(context, dp);
             float scale = gridRect.height() / taskDimension.y;
+            scale = Math.min(scale, res.getFloat(R.dimen.overview_max_scale));
             int outWidth = Math.round(scale * taskDimension.x);
             int outHeight = Math.round(scale * taskDimension.y);
 
diff --git a/quickstep/src/com/android/quickstep/OrientationRectF.java b/quickstep/src/com/android/quickstep/OrientationRectF.java
index 59a202c..aa01b05 100644
--- a/quickstep/src/com/android/quickstep/OrientationRectF.java
+++ b/quickstep/src/com/android/quickstep/OrientationRectF.java
@@ -66,7 +66,7 @@
         return applyTransform(event, deltaRotation(mRotation, currentRotation), forceTransform);
     }
 
-    private boolean applyTransform(MotionEvent event, int deltaRotation, boolean forceTransform) {
+    public boolean applyTransform(MotionEvent event, int deltaRotation, boolean forceTransform) {
         mTmpMatrix.reset();
         postDisplayRotation(deltaRotation, mHeight, mWidth, mTmpMatrix);
         if (forceTransform) {
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index ecff4f1..d2d3ba3 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -22,6 +22,7 @@
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
+import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.RectF;
@@ -358,7 +359,18 @@
                 if (mLastRectTouched == null) {
                     return;
                 }
-                mLastRectTouched.applyTransformFromRotation(event, mCurrentDisplay.rotation, true);
+                if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+                    if (event.getSurfaceRotation() != mActiveTouchRotation) {
+                        // With Shell transitions, we should rotated to the orientation at the start
+                        // of the gesture not the current display rotation which will happen early
+                        mLastRectTouched.applyTransform(event,
+                                deltaRotation(event.getSurfaceRotation(), mActiveTouchRotation),
+                                true);
+                    }
+                } else {
+                    mLastRectTouched.applyTransformFromRotation(event, mCurrentDisplay.rotation,
+                            true);
+                }
                 break;
             }
             case ACTION_CANCEL:
@@ -366,7 +378,18 @@
                 if (mLastRectTouched == null) {
                     return;
                 }
-                mLastRectTouched.applyTransformFromRotation(event, mCurrentDisplay.rotation, true);
+                if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+                    if (event.getSurfaceRotation() != mActiveTouchRotation) {
+                        // With Shell transitions, we should rotated to the orientation at the start
+                        // of the gesture not the current display rotation which will happen early
+                        mLastRectTouched.applyTransform(event,
+                                deltaRotation(event.getSurfaceRotation(), mActiveTouchRotation),
+                                true);
+                    }
+                } else {
+                    mLastRectTouched.applyTransformFromRotation(event, mCurrentDisplay.rotation,
+                            true);
+                }
                 mLastRectTouched = null;
                 break;
             }
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 65847f1..678372c 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -16,16 +16,16 @@
 package com.android.quickstep;
 
 import android.annotation.TargetApi;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Build;
+import android.os.RemoteException;
 import android.os.UserManager;
 import android.util.Log;
 
 import com.android.launcher3.BuildConfig;
 import com.android.launcher3.MainProcessInitializer;
-import com.android.launcher3.util.Executors;
-import com.android.quickstep.logging.SettingsChangeLogger;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.ThreadedRendererCompat;
 
@@ -63,8 +63,11 @@
         ThreadedRendererCompat.setContextPriority(
                 ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
 
-        // Initialize settings logger after a default timeout
-        Executors.MAIN_EXECUTOR.getHandler()
-                .postDelayed(() -> new SettingsChangeLogger(context), SETUP_DELAY_MILLIS);
+        // Enable binder tracing on system server for calls originating from Launcher
+        try {
+            ActivityManager.getService().enableBinderTracing();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to enable binder tracing", e);
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index b031c47..a12a670 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -117,8 +117,8 @@
             secondaryTaskTarget = targets.findTask(splitIds[1]);
 
             mStagedSplitBounds = new StagedSplitBounds(
-                    primaryTaskTarget.screenSpaceBounds,
-                    secondaryTaskTarget.screenSpaceBounds, splitIds[0], splitIds[1]);
+                    primaryTaskTarget.startScreenSpaceBounds,
+                    secondaryTaskTarget.startScreenSpaceBounds, splitIds[0], splitIds[1]);
             mRemoteTargetHandles[0].mTransformParams.setTargetSet(
                     createRemoteAnimationTargetsForTarget(primaryTaskTarget, targets));
             mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget,
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 50b69dc..ff175f1 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -24,14 +24,22 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
+import static com.android.quickstep.fallback.RecentsState.OVERVIEW_SPLIT_SELECT;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
+import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLATION;
+import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
 
+import android.util.FloatProperty;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
@@ -100,5 +108,31 @@
 
         setter.setViewBackgroundColor(mActivity.getScrimView(), state.getScrimColor(mActivity),
                 config.getInterpolator(ANIM_SCRIM_FADE, LINEAR));
+
+        RecentsState currentState = mActivity.getStateManager().getState();
+        if (isSplitSelectionState(state) && !isSplitSelectionState(currentState)) {
+            setter.add(mRecentsView.createSplitSelectInitAnimation().buildAnim());
+        }
+
+        Pair<FloatProperty, FloatProperty> taskViewsFloat =
+                mRecentsView.getPagedOrientationHandler().getSplitSelectTaskOffset(
+                        TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
+                        mActivity.getDeviceProfile());
+        setter.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR);
+        if (isSplitSelectionState(state)) {
+            mRecentsView.applySplitPrimaryScrollOffset();
+            setter.setFloat(mRecentsView, taskViewsFloat.first,
+                    mRecentsView.getSplitSelectTranslation(), LINEAR);
+        } else {
+            mRecentsView.resetSplitPrimaryScrollOffset();
+            setter.setFloat(mRecentsView, taskViewsFloat.first, 0, LINEAR);
+        }
+    }
+
+    /**
+     * @return true if {@param toState} is {@link RecentsState#OVERVIEW_SPLIT_SELECT}
+     */
+    private boolean isSplitSelectionState(@NonNull RecentsState toState) {
+        return toState == OVERVIEW_SPLIT_SELECT;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 5b4eb8b..69da977 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -19,12 +19,12 @@
 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
 import static com.android.quickstep.fallback.RecentsState.HOME;
 import static com.android.quickstep.fallback.RecentsState.MODAL_TASK;
+import static com.android.quickstep.fallback.RecentsState.OVERVIEW_SPLIT_SELECT;
 
 import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
@@ -35,6 +35,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.quickstep.FallbackActivityInterface;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsActivity;
@@ -208,6 +209,13 @@
     }
 
     @Override
+    public void initiateSplitSelect(TaskView taskView,
+            @SplitConfigurationOptions.StagePosition int stagePosition) {
+        super.initiateSplitSelect(taskView, stagePosition);
+        mActivity.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
+    }
+
+    @Override
     public void onStateTransitionStart(RecentsState toState) {
         setOverviewStateEnabled(true);
         setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
@@ -246,12 +254,4 @@
         // Do not let touch escape to siblings below this view.
         return result || mActivity.getStateManager().getState().overviewUi();
     }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-
-        // Reset modal state if full configuration changes
-        setModalStateEnabled(false);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index 917b58a..15feb18 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -51,6 +51,8 @@
             FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN | FLAG_OVERVIEW_UI);
     public static final RecentsState HOME = new RecentsState(3, 0);
     public static final RecentsState BG_LAUNCHER = new LauncherState(4, 0);
+    public static final RecentsState OVERVIEW_SPLIT_SELECT = new RecentsState(5,
+            FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_OVERVIEW_UI);
 
     public final int ordinal;
     private final int mFlags;
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 7ae0fc8..a0cd0d7 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -40,10 +40,11 @@
 import com.android.launcher3.AutoInstallsLayout;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.logging.InstanceIdSequence;
+import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.model.DeviceGridState;
+import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SettingsCache;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
@@ -53,12 +54,19 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.Optional;
 
 /**
  * Utility class to log launcher settings changes
  */
 public class SettingsChangeLogger implements
         NavigationModeChangeListener, OnSharedPreferenceChangeListener {
+  
+    /**
+     * Singleton instance
+     */
+    public static MainThreadInitializedObject<SettingsChangeLogger> INSTANCE =
+            new MainThreadInitializedObject<>(SettingsChangeLogger::new);
 
     private static final String TAG = "SettingsChangeLogger";
     private static final String ROOT_TAG = "androidx.preference.PreferenceScreen";
@@ -66,12 +74,15 @@
 
     private final Context mContext;
     private final ArrayMap<String, LoggablePref> mLoggablePrefs;
+    private final StatsLogManager mStatsLogManager;
 
     private Mode mNavMode;
-    private boolean mNotificationDotsEnabled;
+    private StatsLogManager.LauncherEvent mNotificationDotsEvent;
+    private StatsLogManager.LauncherEvent mHomeScreenSuggestionEvent;
 
-    public SettingsChangeLogger(Context context) {
+    private SettingsChangeLogger(Context context) {
         mContext = context;
+        mStatsLogManager = StatsLogManager.newInstance(mContext);
         mLoggablePrefs = loadPrefKeys(context);
         mNavMode = SysUINavigationMode.INSTANCE.get(context).addModeChangeListener(this);
 
@@ -118,14 +129,21 @@
     }
 
     private void onNotificationDotsChanged(boolean isDotsEnabled) {
-        mNotificationDotsEnabled = isDotsEnabled;
-        dispatchUserEvent();
+        StatsLogManager.LauncherEvent mEvent =
+                isDotsEnabled ? LAUNCHER_NOTIFICATION_DOT_ENABLED
+                        : LAUNCHER_NOTIFICATION_DOT_DISABLED;
+
+        // Log only when the setting is actually changed and not during initialization.
+        if (mNotificationDotsEvent != null && mNotificationDotsEvent != mEvent) {
+            mStatsLogManager.logger().log(mNotificationDotsEvent);
+        }
+        mNotificationDotsEvent = mEvent;
     }
 
     @Override
     public void onNavigationModeChanged(Mode newMode) {
         mNavMode = newMode;
-        dispatchUserEvent();
+        mStatsLogManager.logger().log(newMode.launcherEvent);
     }
 
     @Override
@@ -134,27 +152,27 @@
                 || KEY_WORKSPACE_SIZE.equals(key)
                 || KEY_THEMED_ICONS.equals(key)
                 || mLoggablePrefs.containsKey(key)) {
-            dispatchUserEvent();
+
+            mHomeScreenSuggestionEvent = getDevicePrefs(mContext)
+                    .getBoolean(LAST_PREDICTION_ENABLED_STATE, true)
+                    ? LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED
+                    : LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED;
+
+            mStatsLogManager.logger().log(mHomeScreenSuggestionEvent);
         }
     }
 
-    private void dispatchUserEvent() {
-        StatsLogger logger = StatsLogManager.newInstance(mContext).logger()
-                .withInstanceId(new InstanceIdSequence().newInstanceId());
+    /**
+     * Takes snapshot of all eligible launcher settings and log them with the provided instance ID.
+     */
+    public void logSnapshot(InstanceId snapshotInstanceId) {
+        StatsLogger logger = mStatsLogManager.logger().withInstanceId(snapshotInstanceId);
 
-        logger.log(mNotificationDotsEnabled
-                ? LAUNCHER_NOTIFICATION_DOT_ENABLED
-                : LAUNCHER_NOTIFICATION_DOT_DISABLED);
-        logger.log(mNavMode.launcherEvent);
-        logger.log(getDevicePrefs(mContext).getBoolean(LAST_PREDICTION_ENABLED_STATE, true)
-                ? LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED
-                : LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED);
-
-        StatsLogManager.LauncherEvent gridSizeChangedEvent =
-                new DeviceGridState(mContext).getWorkspaceSizeEvent();
-        if (gridSizeChangedEvent != null) {
-            logger.log(gridSizeChangedEvent);
-        }
+        Optional.ofNullable(mNotificationDotsEvent).ifPresent(logger::log);
+        Optional.ofNullable(mNavMode).map(mode -> mode.launcherEvent).ifPresent(logger::log);
+        Optional.ofNullable(mHomeScreenSuggestionEvent).ifPresent(logger::log);
+        Optional.ofNullable(new DeviceGridState(mContext).getWorkspaceSizeEvent()).ifPresent(
+                logger::log);
 
         SharedPreferences prefs = getPrefs(mContext);
         if (FeatureFlags.ENABLE_THEMED_ICONS.get()) {
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 8ccab71..ae8e45a 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -52,6 +52,7 @@
 import com.android.launcher3.util.SettingsCache;
 import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.views.TaskView;
 
 import java.lang.annotation.Retention;
@@ -340,6 +341,11 @@
 
     @SurfaceRotation
     public int getDisplayRotation() {
+        if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+            // When shell transitions are enabled, both the display and activity rotations should
+            // be the same once the gesture starts
+            return mRecentsActivityRotation;
+        }
         return mDisplayRotation;
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index f676091..f66a6de 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -170,7 +171,7 @@
      * Sets the targets which the simulator will control
      */
     public void setPreview(RemoteAnimationTargetCompat runningTarget) {
-        setPreviewBounds(runningTarget.screenSpaceBounds, runningTarget.contentInsets);
+        setPreviewBounds(runningTarget.startScreenSpaceBounds, runningTarget.contentInsets);
     }
 
     /**
@@ -304,7 +305,13 @@
             mOrientationStateId = mOrientationState.getStateId();
 
             getFullScreenScale();
-            mThumbnailData.rotation = mOrientationState.getDisplayRotation();
+            if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+                // With shell transitions, the display is rotated early so we need to actually use
+                // the rotation when the gesture starts
+                mThumbnailData.rotation = mOrientationState.getTouchRotation();
+            } else {
+                mThumbnailData.rotation = mOrientationState.getDisplayRotation();
+            }
 
             // mIsRecentsRtl is the inverse of TaskView RTL.
             boolean isRtlEnabled = !mIsRecentsRtl;
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index a343e0a..18ab3bb 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -17,8 +17,9 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -43,7 +44,7 @@
 
     private SplitPlaceholderView mSplitPlaceholderView;
     private RectF mStartingPosition;
-    private final Launcher mLauncher;
+    private final BaseDraggingActivity mActivity;
     private final boolean mIsRtl;
     private final Rect mOutline = new Rect();
     private PagedOrientationHandler mOrientationHandler;
@@ -59,7 +60,7 @@
 
     public FloatingTaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mLauncher = Launcher.getLauncher(context);
+        mActivity = BaseActivity.fromContext(context);
         mIsRtl = Utilities.isRtl(getResources());
     }
 
@@ -114,7 +115,7 @@
     public void updateInitialPositionForView(TaskView originalView) {
         View thumbnail = originalView.getThumbnail();
         Rect viewBounds = new Rect(0, 0, thumbnail.getWidth(), thumbnail.getHeight());
-        Utilities.getBoundsForViewInDragLayer(mLauncher.getDragLayer(), thumbnail, viewBounds,
+        Utilities.getBoundsForViewInDragLayer(mActivity.getDragLayer(), thumbnail, viewBounds,
                 true /* ignoreTransform */, null /* recycle */,
                 mStartingPosition);
         mStartingPosition.offset(originalView.getTranslationX(), originalView.getTranslationY());
@@ -129,7 +130,7 @@
     public void update(RectF position, float progress, float windowRadius) {
         MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
 
-        float dX = position.left - lp.getMarginStart();
+        float dX = position.left - mStartingPosition.left;
         float dY = position.top - lp.topMargin;
 
         setTranslationX(dX);
@@ -150,21 +151,31 @@
         mOrientationHandler.setSecondaryScale(mSplitPlaceholderView.getIconView(), childScaleY);
     }
 
+    public void updateOrientationHandler(PagedOrientationHandler orientationHandler) {
+        mOrientationHandler = orientationHandler;
+        mSplitPlaceholderView.getIconView().setRotation(mOrientationHandler.getDegreesRotated());
+    }
+
     protected void initPosition(RectF pos, InsettableFrameLayout.LayoutParams lp) {
         mStartingPosition.set(pos);
         lp.ignoreInsets = true;
         // Position the floating view exactly on top of the original
         lp.topMargin = Math.round(pos.top);
-        lp.setMarginStart(Math.round(pos.left));
+        if (mIsRtl) {
+            lp.setMarginStart(mActivity.getDeviceProfile().widthPx - Math.round(pos.right));
+        } else {
+            lp.setMarginStart(Math.round(pos.left));
+        }
+
         // Set the properties here already to make sure they are available when running the first
         // animation frame.
-        int left = lp.leftMargin;
+        int left = (int) pos.left;
         layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
     }
 
     public void addAnimation(PendingAnimation animation, RectF startingBounds, Rect endBounds,
             View viewToCover, boolean fadeWithThumbnail) {
-        final BaseDragLayer dragLayer = mLauncher.getDragLayer();
+        final BaseDragLayer dragLayer = mActivity.getDragLayer();
         int[] dragLayerBounds = new int[2];
         dragLayer.getLocationOnScreen(dragLayerBounds);
         SplitOverlayProperties prop = new SplitOverlayProperties(endBounds,
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 3e06f55..e0395ea 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -24,7 +24,6 @@
 
 import android.annotation.TargetApi;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
@@ -169,12 +168,4 @@
         super.initiateSplitSelect(taskView, stagePosition);
         mActivity.getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
     }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        // If overview is in modal state when rotate, reset it to overview state without running
-        // animation.
-        setModalStateEnabled(false);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2ad586d..4ec16ad 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -91,6 +91,7 @@
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseBooleanArray;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -629,7 +630,7 @@
      * this doesn't get adjusted to reflect the new child count after the taskView is dismissed/
      * removed from recentsView
      */
-    private int mSplitHiddenTaskViewIndex;
+    private int mSplitHiddenTaskViewIndex = -1;
     @Nullable
     private FloatingTaskView mFirstFloatingTaskView;
     @Nullable
@@ -1263,7 +1264,7 @@
     @Override
     protected void onNotSnappingToPageInFreeScroll() {
         int finalPos = mScroller.getFinalX();
-        if (!showAsGrid() && finalPos > mMinScroll && finalPos < mMaxScroll) {
+        if (finalPos > mMinScroll && finalPos < mMaxScroll) {
             int firstPageScroll = getScrollForPage(!mIsRtl ? 0 : getPageCount() - 1);
             int lastPageScroll = getScrollForPage(!mIsRtl ? getPageCount() - 1 : 0);
 
@@ -1277,6 +1278,19 @@
                             ? mMaxScroll
                             : getScrollForPage(mNextPage);
 
+            if (showAsGrid()) {
+                if (isSplitSelectionActive()) {
+                    return;
+                }
+                TaskView taskView = getTaskViewAt(mNextPage);
+                // Only snap to fully visible focused task.
+                if (taskView == null
+                        || !taskView.isFocusedTask()
+                        || !isTaskViewFullyVisible(taskView)) {
+                    return;
+                }
+            }
+
             mScroller.setFinalX(pageSnapped);
             // Ensure the scroll/snap doesn't happen too fast;
             int extraScrollDuration = OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION
@@ -1382,7 +1396,7 @@
 
         // Removing views sets the currentPage to 0, so we save this and restore it after
         // the new set of views are added
-        int previousPage = mCurrentPage;
+        int previousCurrentPage = mCurrentPage;
         removeAllViews();
 
         // Add views as children based on whether it's grouped or single task
@@ -1406,7 +1420,14 @@
         if (!taskGroups.isEmpty()) {
             addView(mClearAllButton);
         }
-        setCurrentPage(previousPage);
+
+        boolean settlingOnNewTask = mNextPage != INVALID_PAGE;
+        if (settlingOnNewTask) {
+            // Restore mCurrentPage but don't call setCurrentPage() as that clobbers the scroll.
+            mCurrentPage = previousCurrentPage;
+        } else {
+            setCurrentPage(previousCurrentPage);
+        }
 
         // Keep same previous focused task
         TaskView newFocusedTaskView = getTaskViewByTaskId(focusedTaskId);
@@ -1432,7 +1453,7 @@
         }
 
         int targetPage = -1;
-        if (mNextPage == INVALID_PAGE) {
+        if (!settlingOnNewTask) {
             // Set the current page to the running task, but not if settling on new task.
             if (runningTaskId != -1) {
                 targetPage = indexOfChild(newRunningTaskView);
@@ -1620,7 +1641,7 @@
                 || !mOrientationHandler.equals(oldOrientationHandler)) {
             // Changed orientations, update controllers so they intercept accordingly.
             mActivity.getDragLayer().recreateControllers();
-            setModalStateEnabled(false);
+            onOrientationChanged();
         }
 
         boolean isInLandscape = mOrientationState.getTouchRotation() != ROTATION_0
@@ -1639,6 +1660,15 @@
         setCurrentPage(mCurrentPage);
     }
 
+    private void onOrientationChanged() {
+        // If overview is in modal state when rotate, reset it to overview state without running
+        // animation.
+        setModalStateEnabled(false);
+        if (isSplitSelectionActive()) {
+            onRotateInSplitSelectionState();
+        }
+    }
+
     // Update task size and padding that are dependent on DeviceProfile and insets.
     private void updateSizeAndPadding() {
         DeviceProfile dp = mActivity.getDeviceProfile();
@@ -2231,7 +2261,7 @@
         updateChildTaskOrientations();
 
         // Reload the task list
-        mTaskListChangeId = mModel.getTasks(this::applyLoadPlan);
+        reloadIfNeeded();
     }
 
     /**
@@ -3333,7 +3363,7 @@
      * {@link #mFirstFloatingTaskView}.
      */
     public boolean shouldShiftThumbnailsForSplitSelect() {
-        return !mActivity.getDeviceProfile().isTablet;
+        return !mActivity.getDeviceProfile().isTablet || !mActivity.getDeviceProfile().isLandscape;
     }
 
     protected void onDismissAnimationEnds() {
@@ -3519,6 +3549,7 @@
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         updateRecentsRotation();
+        onOrientationChanged();
     }
 
     /**
@@ -3833,13 +3864,18 @@
      * Apply scroll offset to children of RecentsView when entering split select.
      */
     public void applySplitPrimaryScrollOffset() {
+        float taskSplitScrollOffsetPrimary = 0f;
+        float clearAllSplitScrollOffsetPrimar = 0f;
         if (isSplitPlaceholderFirstInGrid()) {
-            for (int i = 0; i < getTaskViewCount(); i++) {
-                requireTaskViewAt(i).setSplitScrollOffsetPrimary(mSplitPlaceholderSize);
-            }
+            taskSplitScrollOffsetPrimary = mSplitPlaceholderSize;
         } else if (isSplitPlaceholderLastInGrid()) {
-            mClearAllButton.setSplitSelectScrollOffsetPrimary(-mSplitPlaceholderSize);
+            clearAllSplitScrollOffsetPrimar = -mSplitPlaceholderSize;
         }
+
+        for (int i = 0; i < getTaskViewCount(); i++) {
+            requireTaskViewAt(i).setSplitScrollOffsetPrimary(taskSplitScrollOffsetPrimary);
+        }
+        mClearAllButton.setSplitSelectScrollOffsetPrimary(clearAllSplitScrollOffsetPrimar);
     }
 
     /**
@@ -3959,6 +3995,9 @@
 
     /** TODO(b/181707736) More gracefully handle exiting split selection state */
     private void resetFromSplitSelectionState() {
+        if (mSplitHiddenTaskViewIndex == -1) {
+            return;
+        }
         if (!mActivity.getDeviceProfile().overviewShowAsGrid) {
             int pageToSnapTo = mCurrentPage;
             if (mSplitHiddenTaskViewIndex <= pageToSnapTo) {
@@ -3987,6 +4026,41 @@
         }
     }
 
+    /**
+     * Returns how much additional translation there should be for each of the child TaskViews.
+     * Note that the translation can be its primary or secondary dimension.
+     */
+    public float getSplitSelectTranslation() {
+        int splitPosition = getSplitPlaceholder().getActiveSplitStagePosition();
+        if (!shouldShiftThumbnailsForSplitSelect()) {
+            return 0f;
+        }
+        PagedOrientationHandler orientationHandler = getPagedOrientationHandler();
+        int direction = orientationHandler.getSplitTranslationDirectionFactor(
+                splitPosition, mActivity.getDeviceProfile());
+        return mActivity.getResources().getDimension(R.dimen.split_placeholder_size) * direction;
+    }
+
+    protected void onRotateInSplitSelectionState() {
+        mOrientationHandler.getInitialSplitPlaceholderBounds(mSplitPlaceholderSize,
+                mActivity.getDeviceProfile(),
+                mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
+        mTempRectF.set(mTempRect);
+        // TODO(194414938) set correct corner radius
+        mFirstFloatingTaskView.updateOrientationHandler(mOrientationHandler);
+        mFirstFloatingTaskView.update(mTempRectF, /*progress=*/1f, /*windowRadius=*/0f);
+
+        PagedOrientationHandler orientationHandler = getPagedOrientationHandler();
+        Pair<FloatProperty, FloatProperty> taskViewsFloat =
+                orientationHandler.getSplitSelectTaskOffset(
+                        TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
+                        mActivity.getDeviceProfile());
+        taskViewsFloat.first.set(this, getSplitSelectTranslation());
+        taskViewsFloat.second.set(this, 0f);
+
+        applySplitPrimaryScrollOffset();
+    }
+
     private void updateDeadZoneRects() {
         // Get the deadzone rect surrounding the clear all button to not dismiss overview to home
         mClearAllButtonDeadZoneRect.setEmpty();
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index d91669a..bf1d6bd 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -297,14 +297,15 @@
             }
         }
 
-        // Draw the background in all cases, except when the thumbnail data is opaque
+        // Always draw the background since the snapshots might be translucent or partially empty
+        // (For example, tasks been reparented out of dismissing split root when drag-to-dismiss
+        // split screen).
+        canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mBackgroundPaint);
+
         final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
                 || mThumbnailData == null;
-        if (drawBackgroundOnly || mThumbnailData.isTranslucent) {
-            canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mBackgroundPaint);
-            if (drawBackgroundOnly) {
-                return;
-            }
+        if (drawBackgroundOnly) {
+            return;
         }
 
         canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 467d225..ab29972 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -677,7 +677,7 @@
      *         second app. {@code false} otherwise
      */
     private boolean confirmSecondSplitSelectApp() {
-        boolean isSelectingSecondSplitApp = mActivity.isInState(OVERVIEW_SPLIT_SELECT);
+        boolean isSelectingSecondSplitApp = getRecentsView().isSplitSelectionActive();
         if (isSelectingSecondSplitApp) {
             getRecentsView().confirmSplitSelect(this);
         }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 93be6fb..237e426 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.tapl.Overview;
 import com.android.launcher3.tapl.OverviewActions;
 import com.android.launcher3.tapl.OverviewTask;
+import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
@@ -321,17 +322,18 @@
     @Test
     @PortraitLandscape
     public void testOverviewForTablet() throws Exception {
-        if (!mLauncher.isTablet()) {
+        // TODO(b/210158657): Re-enable for OOP
+        if (!mLauncher.isTablet() || !TestHelpers.isInLauncherProcess()) {
             return;
         }
-        for (int i = 2; i <= 12; i++) {
+        for (int i = 2; i <= 14; i++) {
             startTestActivity(i);
         }
 
         Overview overview = mLauncher.pressHome().switchToOverview();
         executeOnLauncher(
-                launcher -> assertTrue("Don't have at least 11 tasks",
-                        getTaskCount(launcher) >= 11));
+                launcher -> assertTrue("Don't have at least 13 tasks",
+                        getTaskCount(launcher) >= 13));
 
         // Test scroll the first task off screen
         overview.scrollCurrentTaskOffScreen();
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 08f0089..08570eb 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -154,7 +154,15 @@
         <attr name="demoModeLayoutId" format="reference" />
         <attr name="isScalable" format="boolean" />
         <attr name="devicePaddingId" format="reference" />
-        <attr name="gridEnabled" format="boolean" />
+        <!-- By default all categories are enabled -->
+        <attr name="deviceCategory" format="integer" >
+            <!-- Enable on phone only -->
+            <flag name="phone" value="1" />
+            <!-- Enable on tablets only -->
+            <flag name="tablet" value="2" />
+            <!-- Enable on multi display devices only -->
+            <flag name="multi_display" value="4" />
+        </attr>
 
     </declare-styleable>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 3523ea4..086a254 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -350,10 +350,10 @@
     <dimen name="overview_actions_margin_three_button">0dp</dimen>
     <dimen name="overview_grid_side_margin_portrait">0dp</dimen>
     <dimen name="overview_grid_side_margin_landscape">0dp</dimen>
-    <dimen name="overview_grid_row_spacing_portrait">0dp</dimen>
-    <dimen name="overview_grid_row_spacing_landscape">0dp</dimen>
-    <dimen name="recents_page_spacing">0dp</dimen>
-    <dimen name="recents_page_spacing_grid">0dp</dimen>
+    <dimen name="overview_grid_row_spacing">0dp</dimen>
+    <dimen name="overview_page_spacing">0dp</dimen>
+    <dimen name="overview_page_spacing_grid_portrait">0dp</dimen>
+    <dimen name="overview_page_spacing_grid_landscape">0dp</dimen>
     <dimen name="split_placeholder_size">110dp</dimen>
     <dimen name="task_menu_width_grid">200dp</dimen>
 
diff --git a/res/xml/default_workspace_6x5.xml b/res/xml/default_workspace_6x5.xml
new file mode 100644
index 0000000..b078cfd
--- /dev/null
+++ b/res/xml/default_workspace_6x5.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+
+    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+    <!-- Mail Calendar Gallery Store Internet Camera -->
+    <resolve
+        launcher:container="-101"
+        launcher:screen="0"
+        launcher:x="0"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
+        <favorite launcher:uri="mailto:" />
+    </resolve>
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="1"
+        launcher:x="1"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_CALENDAR;end" />
+    </resolve>
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="2"
+        launcher:x="2"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
+        <favorite launcher:uri="#Intent;type=images/*;end" />
+    </resolve>
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="3"
+        launcher:x="3"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
+        <favorite launcher:uri="market://details?id=com.android.launcher" />
+    </resolve>
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="4"
+        launcher:x="4"
+        launcher:y="0" >
+        <favorite
+            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
+        <favorite launcher:uri="http://www.example.com/" />
+    </resolve>
+
+    <!-- Resolve camera intent if GoogleCamera is not available e.g. on emulator -->
+    <resolve
+        launcher:container="-101"
+        launcher:screen="5"
+        launcher:x="5"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
+        <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
+    </resolve>
+
+</favorites>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index e030f81..08698e7 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -25,7 +25,8 @@
         launcher:numFolderColumns="3"
         launcher:numHotseatIcons="3"
         launcher:dbFile="launcher_3_by_3.db"
-        launcher:defaultLayoutId="@xml/default_workspace_3x3" >
+        launcher:defaultLayoutId="@xml/default_workspace_3x3"
+        launcher:deviceCategory="phone|multi_display" >
 
         <display-option
             launcher:name="Super Short Stubby"
@@ -53,7 +54,8 @@
         launcher:numFolderColumns="4"
         launcher:numHotseatIcons="4"
         launcher:dbFile="launcher_4_by_4.db"
-        launcher:defaultLayoutId="@xml/default_workspace_4x4" >
+        launcher:defaultLayoutId="@xml/default_workspace_4x4"
+        launcher:deviceCategory="phone|multi_display" >
 
         <display-option
             launcher:name="Short Stubby"
@@ -105,7 +107,8 @@
         launcher:numFolderColumns="4"
         launcher:numHotseatIcons="5"
         launcher:dbFile="launcher.db"
-        launcher:defaultLayoutId="@xml/default_workspace_5x5" >
+        launcher:defaultLayoutId="@xml/default_workspace_5x5"
+        launcher:deviceCategory="phone|multi_display" >
 
         <display-option
             launcher:name="Large Phone"
@@ -133,4 +136,32 @@
 
     </grid-option>
 
+    <grid-option
+        launcher:name="6_by_5"
+        launcher:numRows="5"
+        launcher:numColumns="6"
+        launcher:numFolderRows="3"
+        launcher:numFolderColumns="3"
+        launcher:numHotseatIcons="6"
+        launcher:numAllAppsColumns="6"
+        launcher:dbFile="launcher_6_by_5.db"
+        launcher:defaultLayoutId="@xml/default_workspace_6x5"
+        launcher:deviceCategory="tablet" >
+
+        <display-option
+            launcher:name="Tablet"
+            launcher:minWidthDps="900"
+            launcher:minHeightDps="820"
+            launcher:minCellHeightDps="104"
+            launcher:minCellWidthDps="80"
+            launcher:iconImageSize="60"
+            launcher:iconTextSize="14"
+            launcher:borderSpaceDps="16"
+            launcher:allAppsIconSize="60"
+            launcher:allAppsIconTextSize="14"
+            launcher:allAppsCellSpacingDps="16"
+            launcher:canBeDefault="true" />
+
+    </grid-option>
+
 </profiles>
\ No newline at end of file
diff --git a/res/xml/device_profiles_split.xml b/res/xml/device_profiles_split.xml
deleted file mode 100644
index 2fad0c9..0000000
--- a/res/xml/device_profiles_split.xml
+++ /dev/null
@@ -1,137 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     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.
--->
-
-<profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >
-
-    <grid-option
-        launcher:name="3_by_3"
-        launcher:numRows="3"
-        launcher:numColumns="3"
-        launcher:numFolderRows="2"
-        launcher:numFolderColumns="3"
-        launcher:numHotseatIcons="3"
-        launcher:dbFile="launcher_3_by_3.db"
-        launcher:defaultLayoutId="@xml/default_workspace_3x3" >
-
-        <display-option
-            launcher:name="Super Short Stubby"
-            launcher:minWidthDps="255"
-            launcher:minHeightDps="300"
-            launcher:iconImageSize="48"
-            launcher:iconTextSize="13.0"
-            launcher:canBeDefault="true" />
-
-        <display-option
-            launcher:name="Shorter Stubby"
-            launcher:minWidthDps="255"
-            launcher:minHeightDps="400"
-            launcher:iconImageSize="48"
-            launcher:iconTextSize="13.0"
-            launcher:canBeDefault="true" />
-
-    </grid-option>
-
-    <grid-option
-        launcher:name="4_by_4"
-        launcher:numRows="4"
-        launcher:numColumns="4"
-        launcher:numFolderRows="3"
-        launcher:numFolderColumns="4"
-        launcher:numHotseatIcons="4"
-        launcher:dbFile="launcher_4_by_4.db"
-        launcher:defaultLayoutId="@xml/default_workspace_4x4" >
-
-        <display-option
-            launcher:name="Short Stubby"
-            launcher:minWidthDps="275"
-            launcher:minHeightDps="420"
-            launcher:iconImageSize="48"
-            launcher:iconTextSize="13.0"
-            launcher:canBeDefault="true" />
-
-        <display-option
-            launcher:name="Stubby"
-            launcher:minWidthDps="255"
-            launcher:minHeightDps="450"
-            launcher:iconImageSize="48"
-            launcher:iconTextSize="13.0"
-            launcher:canBeDefault="true" />
-
-        <display-option
-            launcher:name="Nexus S"
-            launcher:minWidthDps="296"
-            launcher:minHeightDps="491.33"
-            launcher:iconImageSize="48"
-            launcher:iconTextSize="13.0"
-            launcher:canBeDefault="true" />
-
-        <display-option
-            launcher:name="Nexus 4"
-            launcher:minWidthDps="359"
-            launcher:minHeightDps="567"
-            launcher:iconImageSize="54"
-            launcher:iconTextSize="13.0"
-            launcher:canBeDefault="true" />
-
-        <display-option
-            launcher:name="Nexus 5"
-            launcher:minWidthDps="335"
-            launcher:minHeightDps="567"
-            launcher:iconImageSize="54"
-            launcher:iconTextSize="13.0"
-            launcher:canBeDefault="true" />
-
-    </grid-option>
-
-    <grid-option
-        launcher:name="5_by_5"
-        launcher:numRows="5"
-        launcher:numColumns="5"
-        launcher:numFolderRows="4"
-        launcher:numFolderColumns="4"
-        launcher:numHotseatIcons="5"
-        launcher:numExtendedHotseatIcons="8"
-        launcher:dbFile="launcher.db"
-        launcher:defaultLayoutId="@xml/default_workspace_5x5" >
-
-        <display-option
-            launcher:name="Large Phone"
-            launcher:minWidthDps="406"
-            launcher:minHeightDps="694"
-            launcher:iconImageSize="56"
-            launcher:iconTextSize="14.4"
-            launcher:canBeDefault="true" />
-
-        <display-option
-            launcher:name="Large Phone Split Display"
-            launcher:minWidthDps="406"
-            launcher:minHeightDps="694"
-            launcher:iconImageSize="56"
-            launcher:iconTextSize="14.4"
-            launcher:canBeDefault="true" />
-
-        <display-option
-            launcher:name="Shorter Stubby"
-            launcher:minWidthDps="255"
-            launcher:minHeightDps="400"
-            launcher:iconImageSize="48"
-            launcher:iconTextSize="13.0"
-            launcher:canBeDefault="true" />
-
-    </grid-option>
-
-</profiles>
\ No newline at end of file
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index b90cca0..624afe1 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -373,11 +373,15 @@
                         R.dimen.overview_actions_top_margin_gesture_grid_landscape);
                 overviewActionsBottomMarginGesturePx = res.getDimensionPixelSize(
                         R.dimen.overview_actions_bottom_margin_gesture_grid_landscape);
+                overviewPageSpacing = res.getDimensionPixelSize(
+                        R.dimen.overview_page_spacing_grid_landscape);
             } else {
                 overviewActionsTopMarginGesturePx = res.getDimensionPixelSize(
                         R.dimen.overview_actions_top_margin_gesture_grid_portrait);
                 overviewActionsBottomMarginGesturePx = res.getDimensionPixelSize(
                         R.dimen.overview_actions_bottom_margin_gesture_grid_portrait);
+                overviewPageSpacing = res.getDimensionPixelSize(
+                        R.dimen.overview_page_spacing_grid_portrait);
             }
             overviewActionsButtonSpacing = res.getDimensionPixelSize(
                     R.dimen.overview_actions_button_spacing_grid);
@@ -385,17 +389,19 @@
             overviewActionsTopMarginGesturePx = res.getDimensionPixelSize(
                     R.dimen.overview_actions_margin_gesture);
             overviewActionsBottomMarginGesturePx = overviewActionsTopMarginGesturePx;
+            overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing);
             overviewActionsButtonSpacing = res.getDimensionPixelSize(
                     R.dimen.overview_actions_button_spacing);
         }
         overviewActionsMarginThreeButtonPx = res.getDimensionPixelSize(
                 R.dimen.overview_actions_margin_three_button);
-        overviewPageSpacing = overviewShowAsGrid
-                ? res.getDimensionPixelSize(R.dimen.recents_page_spacing_grid)
-                : res.getDimensionPixelSize(R.dimen.recents_page_spacing);
-        overviewRowSpacing = isLandscape
-                ? res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing_landscape)
-                : res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing_portrait);
+        // 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.
+        int extraTopMargin = overviewTaskThumbnailTopMarginPx - overviewTaskIconSizePx
+                - overviewTaskMarginGridPx;
+        overviewRowSpacing = res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing)
+                - extraTopMargin;
         overviewGridSideMargin = isLandscape
                 ? res.getDimensionPixelSize(R.dimen.overview_grid_side_margin_landscape)
                 : res.getDimensionPixelSize(R.dimen.overview_grid_side_margin_portrait);
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 2ebedf7..ff7a90c 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -42,6 +42,7 @@
 import android.util.Xml;
 import android.view.Display;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
@@ -58,6 +59,8 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -70,6 +73,13 @@
     public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
             new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({TYPE_PHONE, TYPE_MULTI_DISPLAY, TYPE_TABLET})
+    public @interface DeviceType{}
+    public static final int TYPE_PHONE = 0;
+    public static final int TYPE_MULTI_DISPLAY = 1;
+    public static final int TYPE_TABLET = 2;
+
     private static final String KEY_IDP_GRID_NAME = "idp_grid_name";
 
     private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48;
@@ -106,7 +116,7 @@
     public float[] iconTextSize;
     public int iconBitmapSize;
     public int fillResIconDpi;
-    public boolean isSplitDisplay;
+    public @DeviceType int deviceType;
 
     public PointF[] minCellSize;
 
@@ -198,13 +208,21 @@
         String gridName = getCurrentGridName(context);
 
         // Get the display info based on default display and interpolate it to existing display
+        Info defaultInfo = DisplayController.INSTANCE.get(context).getInfo();
+        @DeviceType int defaultDeviceType = getDeviceType(defaultInfo);
         DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
-                DisplayController.INSTANCE.get(context).getInfo(),
-                getPredefinedDeviceProfiles(context, gridName, false, false), false);
+                defaultInfo,
+                getPredefinedDeviceProfiles(context, gridName, defaultDeviceType,
+                        /*allowDisabledGrid=*/false),
+                defaultDeviceType);
 
         Info myInfo = new Info(context, display);
+        @DeviceType int deviceType = getDeviceType(myInfo);
         DisplayOption myDisplayOption = invDistWeightedInterpolate(
-                myInfo, getPredefinedDeviceProfiles(context, gridName, false, false), false);
+                myInfo,
+                getPredefinedDeviceProfiles(context, gridName, deviceType,
+                        /*allowDisabledGrid=*/false),
+                deviceType);
 
         DisplayOption result = new DisplayOption(defaultDisplayOption.grid)
                 .add(myDisplayOption);
@@ -220,7 +238,7 @@
         System.arraycopy(defaultDisplayOption.borderSpaces, 0, result.borderSpaces, 0,
                 COUNT_SIZES);
 
-        initGrid(context, myInfo, result, false);
+        initGrid(context, myInfo, result, deviceType);
     }
 
     /**
@@ -246,6 +264,18 @@
         }
     }
 
+    private static @DeviceType int getDeviceType(Info displayInfo) {
+        // Each screen has two profiles (portrait/landscape), so devices with four or more
+        // supported profiles implies two or more internal displays.
+        if (displayInfo.supportedBounds.size() >= 4 && ENABLE_TWO_PANEL_HOME.get()) {
+            return TYPE_MULTI_DISPLAY;
+        } else if (displayInfo.supportedBounds.stream().allMatch(displayInfo::isTablet)) {
+            return TYPE_TABLET;
+        } else {
+            return TYPE_PHONE;
+        }
+    }
+
     public static String getCurrentGridName(Context context) {
         return Utilities.isGridOptionsEnabled(context)
                 ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) : null;
@@ -253,23 +283,19 @@
 
     private String initGrid(Context context, String gridName) {
         Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
-        // Each screen has two profiles (portrait/landscape), so devices with four or more
-        // supported profiles implies two or more internal displays.
-        boolean isSplitDisplay =
-                displayInfo.supportedBounds.size() >= 4 && ENABLE_TWO_PANEL_HOME.get();
+        @DeviceType int deviceType = getDeviceType(displayInfo);
 
         ArrayList<DisplayOption> allOptions =
-                getPredefinedDeviceProfiles(context, gridName, isSplitDisplay,
+                getPredefinedDeviceProfiles(context, gridName, deviceType,
                         RestoreDbTask.isPending(context));
         DisplayOption displayOption =
-                invDistWeightedInterpolate(displayInfo, allOptions, isSplitDisplay);
-        initGrid(context, displayInfo, displayOption, isSplitDisplay);
+                invDistWeightedInterpolate(displayInfo, allOptions, deviceType);
+        initGrid(context, displayInfo, displayOption, deviceType);
         return displayOption.grid.name;
     }
 
-    private void initGrid(
-            Context context, Info displayInfo, DisplayOption displayOption,
-            boolean isSplitDisplay) {
+    private void initGrid(Context context, Info displayInfo, DisplayOption displayOption,
+            @DeviceType int deviceType) {
         DisplayMetrics metrics = context.getResources().getDisplayMetrics();
         GridOption closestProfile = displayOption.grid;
         numRows = closestProfile.numRows;
@@ -281,7 +307,7 @@
         numFolderColumns = closestProfile.numFolderColumns;
         isScalable = closestProfile.isScalable;
         devicePaddingId = closestProfile.devicePaddingId;
-        this.isSplitDisplay = isSplitDisplay;
+        this.deviceType = deviceType;
 
         mExtraAttrs = closestProfile.extraAttrs;
 
@@ -303,11 +329,11 @@
         horizontalMargin = displayOption.horizontalMargin;
 
         numShownHotseatIcons = closestProfile.numHotseatIcons;
-        numDatabaseHotseatIcons = isSplitDisplay
+        numDatabaseHotseatIcons = deviceType == TYPE_MULTI_DISPLAY
                 ? closestProfile.numDatabaseHotseatIcons : closestProfile.numHotseatIcons;
 
         numAllAppsColumns = closestProfile.numAllAppsColumns;
-        numDatabaseAllAppsColumns = isSplitDisplay
+        numDatabaseAllAppsColumns = deviceType == TYPE_MULTI_DISPLAY
                 ? closestProfile.numDatabaseAllAppsColumns : closestProfile.numAllAppsColumns;
 
         if (!Utilities.isGridOptionsEnabled(context)) {
@@ -327,7 +353,7 @@
         defaultWallpaperSize = new Point(displayInfo.currentSize);
         for (WindowBounds bounds : displayInfo.supportedBounds) {
             localSupportedProfiles.add(new DeviceProfile.Builder(context, this, displayInfo)
-                    .setUseTwoPanels(isSplitDisplay)
+                    .setUseTwoPanels(deviceType == TYPE_MULTI_DISPLAY)
                     .setWindowBounds(bounds).build());
 
             // Wallpaper size should be the maximum of the all possible sizes Launcher expects
@@ -350,11 +376,6 @@
         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
     }
 
-    @Nullable
-    public TypedValue getAttrValue(int attr) {
-        return mExtraAttrs == null ? null : mExtraAttrs.get(attr);
-    }
-
     public void addOnChangeListener(OnIDPChangeListener listener) {
         mChangeListeners.add(listener);
     }
@@ -389,12 +410,11 @@
         }
     }
 
-    private static ArrayList<DisplayOption> getPredefinedDeviceProfiles(
-            Context context, String gridName, boolean isSplitDisplay, boolean allowDisabledGrid) {
+    private static ArrayList<DisplayOption> getPredefinedDeviceProfiles(Context context,
+            String gridName, @DeviceType int deviceType, boolean allowDisabledGrid) {
         ArrayList<DisplayOption> profiles = new ArrayList<>();
-        int xmlResource = isSplitDisplay ? R.xml.device_profiles_split : R.xml.device_profiles;
 
-        try (XmlResourceParser parser = context.getResources().getXml(xmlResource)) {
+        try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
             final int depth = parser.getDepth();
             int type;
             while (((type = parser.next()) != XmlPullParser.END_TAG ||
@@ -402,8 +422,8 @@
                 if ((type == XmlPullParser.START_TAG)
                         && GridOption.TAG_NAME.equals(parser.getName())) {
 
-                    GridOption gridOption =
-                            new GridOption(context, Xml.asAttributeSet(parser), isSplitDisplay);
+                    GridOption gridOption = new GridOption(context, Xml.asAttributeSet(parser),
+                            deviceType);
                     if (gridOption.isEnabled || allowDisabledGrid) {
                         final int displayDepth = parser.getDepth();
                         while (((type = parser.next()) != XmlPullParser.END_TAG
@@ -450,9 +470,8 @@
      */
     public List<GridOption> parseAllGridOptions(Context context) {
         List<GridOption> result = new ArrayList<>();
-        int xmlResource = isSplitDisplay ? R.xml.device_profiles_split : R.xml.device_profiles;
 
-        try (XmlResourceParser parser = context.getResources().getXml(xmlResource)) {
+        try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
             final int depth = parser.getDepth();
             int type;
             while (((type = parser.next()) != XmlPullParser.END_TAG
@@ -460,7 +479,7 @@
                 if ((type == XmlPullParser.START_TAG)
                         && GridOption.TAG_NAME.equals(parser.getName())) {
                     GridOption option =
-                            new GridOption(context, Xml.asAttributeSet(parser), isSplitDisplay);
+                            new GridOption(context, Xml.asAttributeSet(parser), deviceType);
                     if (option.isEnabled) {
                         result.add(option);
                     }
@@ -514,12 +533,12 @@
     }
 
     private static DisplayOption invDistWeightedInterpolate(
-            Info displayInfo, ArrayList<DisplayOption> points, boolean isSplitDisplay) {
+            Info displayInfo, ArrayList<DisplayOption> points, @DeviceType int deviceType) {
         int minWidthPx = Integer.MAX_VALUE;
         int minHeightPx = Integer.MAX_VALUE;
         for (WindowBounds bounds : displayInfo.supportedBounds) {
             boolean isTablet = displayInfo.isTablet(bounds);
-            if (isTablet && isSplitDisplay) {
+            if (isTablet && deviceType == TYPE_MULTI_DISPLAY) {
                 // For split displays, take half width per page
                 minWidthPx = Math.min(minWidthPx, bounds.availableSize.x / 2);
                 minHeightPx = Math.min(minHeightPx, bounds.availableSize.y);
@@ -643,6 +662,12 @@
 
         public static final String TAG_NAME = "grid-option";
 
+        private static final int DEVICE_CATEGORY_PHONE = 1 << 0;
+        private static final int DEVICE_CATEGORY_TABLET = 1 << 1;
+        private static final int DEVICE_CATEGORY_MULTI_DISPLAY = 1 << 2;
+        private static final int DEVICE_CATEGORY_ALL =
+                DEVICE_CATEGORY_PHONE | DEVICE_CATEGORY_TABLET | DEVICE_CATEGORY_MULTI_DISPLAY;
+
         public final String name;
         public final int numRows;
         public final int numColumns;
@@ -666,7 +691,7 @@
 
         private final SparseArray<TypedValue> extraAttrs;
 
-        public GridOption(Context context, AttributeSet attrs, boolean isSplitDisplay) {
+        public GridOption(Context context, AttributeSet attrs, @DeviceType int deviceType) {
             TypedArray a = context.obtainStyledAttributes(
                     attrs, R.styleable.GridDisplayOption);
             name = a.getString(R.styleable.GridDisplayOption_name);
@@ -674,7 +699,7 @@
             numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
 
             dbFile = a.getString(R.styleable.GridDisplayOption_dbFile);
-            defaultLayoutId = a.getResourceId(isSplitDisplay && a.hasValue(
+            defaultLayoutId = a.getResourceId(deviceType == TYPE_MULTI_DISPLAY && a.hasValue(
                     R.styleable.GridDisplayOption_defaultSplitDisplayLayoutId)
                     ? R.styleable.GridDisplayOption_defaultSplitDisplayLayoutId
                     : R.styleable.GridDisplayOption_defaultLayoutId, 0);
@@ -701,7 +726,15 @@
             devicePaddingId = a.getResourceId(
                     R.styleable.GridDisplayOption_devicePaddingId, 0);
 
-            isEnabled = a.getBoolean(R.styleable.GridDisplayOption_gridEnabled, true);
+            int deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory,
+                    DEVICE_CATEGORY_ALL);
+            isEnabled = (deviceType == TYPE_PHONE
+                        && ((deviceCategory & DEVICE_CATEGORY_PHONE) == DEVICE_CATEGORY_PHONE))
+                    || (deviceType == TYPE_TABLET
+                        && ((deviceCategory & DEVICE_CATEGORY_TABLET) == DEVICE_CATEGORY_TABLET))
+                    || (deviceType == TYPE_MULTI_DISPLAY
+                        && ((deviceCategory & DEVICE_CATEGORY_MULTI_DISPLAY)
+                            == DEVICE_CATEGORY_MULTI_DISPLAY));
 
             a.recycle();
             extraAttrs = Themes.createValueMap(context, attrs,
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e9725cb..801a31d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1639,6 +1639,8 @@
         AbstractFloatingView.closeOpenViews(this, false, TYPE_ALL & ~TYPE_REBIND_SAFE);
         finishAutoCancelActionMode();
 
+        DragView.removeAllViews(this);
+
         if (mPendingRequestArgs != null) {
             outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs);
         }
diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java
index 64f1d95..e59eac8 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -16,6 +16,7 @@
     private static final String XML = ".xml";
 
     public static final String LAUNCHER_DB = "launcher.db";
+    public static final String LAUNCHER_6_BY_5_DB = "launcher_6_by_5.db";
     public static final String LAUNCHER_4_BY_5_DB = "launcher_4_by_5.db";
     public static final String LAUNCHER_4_BY_4_DB = "launcher_4_by_4.db";
     public static final String LAUNCHER_3_BY_3_DB = "launcher_3_by_3.db";
@@ -32,6 +33,7 @@
 
     public static final List<String> GRID_DB_FILES = Collections.unmodifiableList(Arrays.asList(
             LAUNCHER_DB,
+            LAUNCHER_6_BY_5_DB,
             LAUNCHER_4_BY_5_DB,
             LAUNCHER_4_BY_4_DB,
             LAUNCHER_3_BY_3_DB,
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 15378e0..be2cd88 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -57,10 +57,9 @@
     public static final int ALL_APPS_CONTENT = 1 << 1;
     public static final int VERTICAL_SWIPE_INDICATOR = 1 << 2;
     public static final int OVERVIEW_ACTIONS = 1 << 3;
-    public static final int TASKBAR = 1 << 4;
-    public static final int CLEAR_ALL_BUTTON = 1 << 5;
-    public static final int WORKSPACE_PAGE_INDICATOR = 1 << 6;
-    public static final int SPLIT_PLACHOLDER_VIEW = 1 << 7;
+    public static final int CLEAR_ALL_BUTTON = 1 << 4;
+    public static final int WORKSPACE_PAGE_INDICATOR = 1 << 5;
+    public static final int SPLIT_PLACHOLDER_VIEW = 1 << 6;
 
     // Flag indicating workspace has multiple pages visible.
     public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);
@@ -186,7 +185,7 @@
     }
 
     public int getVisibleElements(Launcher launcher) {
-        return HOTSEAT_ICONS | WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR | TASKBAR;
+        return HOTSEAT_ICONS | WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR;
     }
 
     /**
@@ -197,10 +196,16 @@
         return (getVisibleElements(launcher) & elements) == elements;
     }
 
-    public boolean isTaskbarStashed() {
+    /** Returns whether taskbar is stashed and thus should replace hotseat with a handle */
+    public boolean isTaskbarStashed(Launcher launcher) {
         return false;
     }
 
+    /** Returns whether taskbar is aligned with the hotseat vs position inside apps */
+    public boolean isTaskbarAlignedWithHotseat(Launcher launcher) {
+        return !isTaskbarStashed(launcher);
+    }
+
     /**
      * Fraction shift in the vertical translation UI and related properties
      *
diff --git a/src/com/android/launcher3/anim/PropertySetter.java b/src/com/android/launcher3/anim/PropertySetter.java
index 729523f..8d77b4b 100644
--- a/src/com/android/launcher3/anim/PropertySetter.java
+++ b/src/com/android/launcher3/anim/PropertySetter.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.anim;
 
+import android.animation.Animator;
 import android.animation.TimeInterpolator;
 import android.util.FloatProperty;
 import android.util.IntProperty;
@@ -64,4 +65,9 @@
             TimeInterpolator interpolator) {
         property.setValue(target, value);
     }
+
+    default void add(Animator animatorSet) {
+        animatorSet.setDuration(0);
+        animatorSet.start();
+    }
 }
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 8313571..4588a04 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -564,4 +564,19 @@
         iv.setImageDrawable(drawable);
         return iv;
     }
+
+    /**
+     * Removes any stray DragView from the DragLayer.
+     */
+    public static void removeAllViews(ActivityContext activity) {
+        BaseDragLayer dragLayer = activity.getDragLayer();
+        // Iterate in reverse order. DragView is added later to the dragLayer,
+        // and will be one of the last views.
+        for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
+            View child = dragLayer.getChildAt(i);
+            if (child instanceof DragView) {
+                dragLayer.removeView(child);
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 0fc4c2d..fa11d4e 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.InvariantDeviceProfile.DeviceType;
+import static com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_2;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_3;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_4;
@@ -25,15 +27,10 @@
 import android.content.SharedPreferences;
 import android.text.TextUtils;
 
-import androidx.annotation.IntDef;
-
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
-import com.android.launcher3.util.IntSet;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.Locale;
 import java.util.Objects;
 
@@ -46,20 +43,6 @@
     public static final String KEY_HOTSEAT_COUNT = "migration_src_hotseat_count";
     public static final String KEY_DEVICE_TYPE = "migration_src_device_type";
 
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({TYPE_PHONE, TYPE_MULTI_DISPLAY, TYPE_TABLET})
-    public @interface DeviceType{}
-    public static final int TYPE_PHONE = 0;
-    public static final int TYPE_MULTI_DISPLAY = 1;
-    public static final int TYPE_TABLET = 2;
-
-    private static final IntSet COMPATIBLE_TYPES = IntSet.wrap(TYPE_PHONE, TYPE_MULTI_DISPLAY);
-
-    public static boolean deviceTypeCompatible(@DeviceType int typeA, @DeviceType int typeB) {
-        return typeA == typeB
-                || (COMPATIBLE_TYPES.contains(typeA) && COMPATIBLE_TYPES.contains(typeB));
-    }
-
     private final String mGridSizeString;
     private final int mNumHotseat;
     private final @DeviceType int mDeviceType;
@@ -67,11 +50,7 @@
     public DeviceGridState(InvariantDeviceProfile idp) {
         mGridSizeString = String.format(Locale.ENGLISH, "%d,%d", idp.numColumns, idp.numRows);
         mNumHotseat = idp.numDatabaseHotseatIcons;
-        mDeviceType = idp.supportedProfiles.size() > 2
-                ? TYPE_MULTI_DISPLAY
-                : idp.supportedProfiles.stream().allMatch(dp -> dp.isTablet)
-                        ? TYPE_TABLET
-                        : TYPE_PHONE;
+        mDeviceType = idp.deviceType;
     }
 
     public DeviceGridState(Context context) {
@@ -135,7 +114,6 @@
         if (this == other) return true;
         if (other == null) return false;
         return mNumHotseat == other.mNumHotseat
-                && deviceTypeCompatible(mDeviceType, other.mDeviceType)
                 && Objects.equals(mGridSizeString, other.mGridSizeString);
     }
 }
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index b72f462..1e8e3ca 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -25,6 +25,7 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SETTINGS;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WALLPAPERS;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
 import static com.android.launcher3.LauncherSettings.Favorites.EXTENDED_CONTAINERS;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
@@ -55,6 +56,7 @@
 import com.android.launcher3.logger.LauncherAtom.Shortcut;
 import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer;
 import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer;
+import com.android.launcher3.logger.LauncherAtom.WallpapersContainer;
 import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.util.ContentWriter;
@@ -374,6 +376,7 @@
     protected LauncherAtom.ItemInfo.Builder getDefaultItemInfoBuilder() {
         LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
         itemBuilder.setIsWork(!Process.myUserHandle().equals(user));
+        itemBuilder.setRank(rank);
         return itemBuilder;
     }
 
@@ -428,6 +431,10 @@
                 return ContainerInfo.newBuilder()
                         .setTaskSwitcherContainer(TaskSwitcherContainer.getDefaultInstance())
                         .build();
+            case CONTAINER_WALLPAPERS:
+                return ContainerInfo.newBuilder()
+                        .setWallpapersContainer(WallpapersContainer.getDefaultInstance())
+                        .build();
             case EXTENDED_CONTAINERS:
                 return ContainerInfo.newBuilder()
                         .setExtendedContainers(getExtendedContainer())
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 8f607a1..d994dbe 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -16,8 +16,8 @@
 
 package com.android.launcher3.provider;
 
-import static com.android.launcher3.model.DeviceGridState.TYPE_MULTI_DISPLAY;
-import static com.android.launcher3.model.DeviceGridState.TYPE_PHONE;
+import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
+import static com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 
 import android.app.backup.BackupManager;
@@ -87,12 +87,6 @@
     }
 
     private static boolean performRestore(Context context, DatabaseHelper helper) {
-        if (!DeviceGridState.deviceTypeCompatible(
-                new DeviceGridState(LauncherAppState.getIDP(context)).getDeviceType(),
-                Utilities.getPrefs(context).getInt(RESTORED_DEVICE_TYPE, TYPE_PHONE))) {
-            // DO NOT restore if the device types are incompatible.
-            return false;
-        }
         SQLiteDatabase db = helper.getWritableDatabase();
         try (SQLiteTransaction t = new SQLiteTransaction(db)) {
             RestoreDbTask task = new RestoreDbTask();
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 5fe5450..d52594e 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -90,9 +90,4 @@
     public float getWorkspaceBackgroundAlpha(Launcher launcher) {
         return 0.2f;
     }
-
-    @Override
-    public int getVisibleElements(Launcher launcher) {
-        return (super.getVisibleElements(launcher) | HOTSEAT_ICONS) & ~TASKBAR;
-    }
 }
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 0c39067..9a2d6d8 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -255,7 +255,7 @@
     }
 
     @Override
-    public int getSplitTranslationDirectionFactor(int stagePosition) {
+    public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
         if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
             return -1;
         } else {
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 54b30fb..95168fb 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -95,7 +95,8 @@
     int getScrollOffsetStart(View view, Rect insets);
     int getScrollOffsetEnd(View view, Rect insets);
     int getSecondaryTranslationDirectionFactor();
-    int getSplitTranslationDirectionFactor(@StagePosition int stagePosition);
+    int getSplitTranslationDirectionFactor(@StagePosition int stagePosition,
+            DeviceProfile deviceProfile);
     ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild);
     void setMaxScroll(AccessibilityEvent event, int maxScroll);
     boolean getRecentsRtlSetting(Resources resources);
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index cb1ba7d..7d70f77 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -261,8 +261,8 @@
     }
 
     @Override
-    public int getSplitTranslationDirectionFactor(int stagePosition) {
-        if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+    public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
+        if (deviceProfile.isLandscape && stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
             return -1;
         } else {
             return 1;
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index ce2e136..a921f94 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -37,7 +37,6 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import com.android.launcher3.views.BaseDragLayer;
@@ -53,7 +52,7 @@
     }
 
     @Override
-    public int getSplitTranslationDirectionFactor(int stagePosition) {
+    public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
         if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
             return -1;
         } else {
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index c0ec9d8..06313e7 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -88,7 +88,6 @@
 
     private IconLoadResult mIconLoadResult;
 
-    // Draw the drawable of the BubbleTextView behind ClipIconView to reveal the built in shadow.
     private View mBtvDrawable;
 
     private ClipIconView mClipIconView;
@@ -347,10 +346,23 @@
             mClipIconView.setLayoutParams(clipViewLp);
         }
 
-        if (!mIsOpening && btvIcon != null) {
+        setOriginalDrawableBackground(btvIcon);
+        invalidate();
+    }
+
+    /**
+     * Draws the drawable of the BubbleTextView behind ClipIconView
+     *
+     * This is used to:
+     * - Have icon displayed while Adaptive Icon is loading
+     * - Displays the built in shadow to ensure a clean handoff
+     *
+     * Allows nullable as this may be cleared when drawing is deferred to ClipIconView.
+     */
+    private void setOriginalDrawableBackground(@Nullable Drawable btvIcon) {
+        if (!mIsOpening) {
             mBtvDrawable.setBackground(btvIcon);
         }
-        invalidate();
     }
 
     /**
@@ -455,7 +467,9 @@
 
     @Override
     public void onAnimationStart(Animator animator) {
-        if (mIconLoadResult != null && mIconLoadResult.isIconLoaded) {
+        if ((mIconLoadResult != null && mIconLoadResult.isIconLoaded)
+                || (!mIsOpening && mBtvDrawable.getBackground() != null)) {
+            // No need to wait for icon load since we can display the BubbleTextView drawable.
             setVisibility(View.VISIBLE);
         }
         if (!mIsOpening && mOriginalIcon != null) {
@@ -518,6 +532,7 @@
 
         IconLoadResult result = new IconLoadResult(info,
                 btvIcon == null ? false : btvIcon.isThemed());
+        result.btvDrawable = btvIcon;
 
         final long fetchIconId = sFetchIconId++;
         MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> {
@@ -556,6 +571,7 @@
                 view.mIconLoadResult = fetchIcon(launcher, originalView,
                         (ItemInfo) originalView.getTag(), isOpening);
             }
+            view.setOriginalDrawableBackground(view.mIconLoadResult.btvDrawable);
         }
         sIconLoadResult = null;
 
diff --git a/src_plugins/com/android/systemui/plugins/OneSearch.java b/src_plugins/com/android/systemui/plugins/OneSearch.java
index 13a956b..534bc87 100644
--- a/src_plugins/com/android/systemui/plugins/OneSearch.java
+++ b/src_plugins/com/android/systemui/plugins/OneSearch.java
@@ -28,7 +28,7 @@
 @ProvidesInterface(action = OneSearch.ACTION, version = OneSearch.VERSION)
 public interface OneSearch extends Plugin {
     String ACTION = "com.android.systemui.action.PLUGIN_ONE_SEARCH";
-    int VERSION = 5;
+    int VERSION = 6;
 
     /**
      * Get the content provider warmed up.
@@ -45,6 +45,8 @@
     /** Get image bitmap with the URL. */
     Parcelable getImageBitmap(String imageUrl);
 
+    void setSuggestOnChrome(boolean enable);
+
     /**
      * Notifies search events to plugin
      *
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 9f2a629..e44c172 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -251,6 +251,22 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity-alias>
+        <activity-alias android:name="Activity13" android:exported="true"
+            android:label="TestActivity13"
+            android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+        <activity-alias android:name="Activity14" android:exported="true"
+            android:label="TestActivity14"
+            android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
 
         <!-- [b/197780098] Disable eager initialization of Jetpack libraries. -->
         <provider
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 19dca45..075505e 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -149,7 +149,7 @@
                 }
                 sDumpWasGenerated = true;
                 Log.d("b/195319692", "sDumpWasGenerated := true", new Exception());
-                result = "memory dump filename: " + fileName;
+                result = "saved memory dump as an artifact";
             } catch (Throwable e) {
                 Log.e(TAG, "dumpHprofData failed", e);
                 result = "failed to save memory dump";
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 41c7c37..5b940a8 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -423,6 +423,18 @@
         waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
     }
 
+    @Test
+    @PortraitLandscape
+    public void testDeleteFromWorkspace() throws Exception {
+        // test delete both built-in apps and user-installed app from workspace
+        for (String appName : new String[] {"Gmail", "Play Store", APP_NAME}) {
+            final AppIcon appIcon = createShortcutIfNotExist(appName);
+            Workspace workspace = mLauncher.getWorkspace().deleteAppIcon(appIcon);
+            assertNull(appName + " app was found after being deleted from workspace",
+                    workspace.tryGetWorkspaceAppIcon(appName));
+        }
+    }
+
     public static String getAppPackageName() {
         return getInstrumentation().getContext().getPackageName();
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 91b1bc7..631e8f1 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1399,14 +1399,15 @@
         final Point start = new Point(startX, startY);
         final Point end = new Point(endX, endY);
         sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
-        final long endTime = movePointer(start, end, steps, downTime, slowDown, gestureScope);
+        final long endTime = movePointer(
+                start, end, steps, false, downTime, slowDown, gestureScope);
         sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end, gestureScope);
     }
 
-    long movePointer(Point start, Point end, int steps, long downTime, boolean slowDown,
-            GestureScope gestureScope) {
-        long endTime = movePointer(
-                downTime, downTime, steps * GESTURE_STEP_MS, start, end, gestureScope);
+    long movePointer(Point start, Point end, int steps, boolean isDecelerating,
+            long downTime, boolean slowDown, GestureScope gestureScope) {
+        long endTime = movePointer(downTime, downTime, steps * GESTURE_STEP_MS,
+                isDecelerating, start, end, gestureScope);
         if (slowDown) {
             endTime = movePointer(downTime, endTime + GESTURE_STEP_MS, 5 * GESTURE_STEP_MS, end,
                     end, gestureScope);
@@ -1485,21 +1486,55 @@
 
     public long movePointer(long downTime, long startTime, long duration, Point from, Point to,
             GestureScope gestureScope) {
+        return movePointer(
+                downTime, startTime, duration, false, from, to, gestureScope);
+    }
+
+    public long movePointer(long downTime, long startTime, long duration, boolean isDecelerating,
+            Point from, Point to, GestureScope gestureScope) {
         log("movePointer: " + from + " to " + to);
         final Point point = new Point();
         long steps = duration / GESTURE_STEP_MS;
+
         long currentTime = startTime;
-        for (long i = 0; i < steps; ++i) {
-            sleep(GESTURE_STEP_MS);
 
-            currentTime += GESTURE_STEP_MS;
-            final float progress = (currentTime - startTime) / (float) duration;
+        if (isDecelerating) {
+            // formula: V = V0 - D*T, assuming V = 0 when T = duration
 
-            point.x = from.x + (int) (progress * (to.x - from.x));
-            point.y = from.y + (int) (progress * (to.y - from.y));
+            // vx0: initial speed at the x-dimension, set as twice the avg speed
+            // dx: the constant deceleration at the x-dimension
+            double vx0 = 2 * (to.x - from.x) / duration;
+            double dx = vx0 / duration;
+            // vy0: initial speed at the y-dimension, set as twice the avg speed
+            // dy: the constant deceleration at the y-dimension
+            double vy0 = 2 * (to.y - from.y) / duration;
+            double dy = vy0 / duration;
 
-            sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point, gestureScope);
+            for (long i = 0; i < steps; ++i) {
+                sleep(GESTURE_STEP_MS);
+                currentTime += GESTURE_STEP_MS;
+
+                // formula: P = P0 + V0*T - (D*T^2/2)
+                final double t = (i + 1) * GESTURE_STEP_MS;
+                point.x = from.x + (int) (vx0 * t - 0.5 * dx * t * t);
+                point.y = from.y + (int) (vy0 * t - 0.5 * dy * t * t);
+
+                sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point, gestureScope);
+            }
+        } else {
+            for (long i = 0; i < steps; ++i) {
+                sleep(GESTURE_STEP_MS);
+                currentTime += GESTURE_STEP_MS;
+
+                final float progress = (currentTime - startTime) / (float) duration;
+                point.x = from.x + (int) (progress * (to.x - from.x));
+                point.y = from.y + (int) (progress * (to.y - from.y));
+
+                sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point, gestureScope);
+
+            }
         }
+
         return currentTime;
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index d9f5cc8..3f0d7fd 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -46,6 +46,9 @@
  */
 public final class Workspace extends Home {
     private static final int FLING_STEPS = 10;
+    private static final int DEFAULT_DRAG_STEPS = 10;
+    private static final String DROP_BAR_RES_ID = "drop_target_bar";
+    private static final String DELETE_TARGET_TEXT_ID = "delete_target_text";
 
     static final Pattern EVENT_CTRL_W_DOWN = Pattern.compile(
             "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_W"
@@ -211,6 +214,40 @@
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
+    /*
+     * Get the center point of the delete icon in the drop target bar.
+     */
+    private Point getDeleteDropPoint() {
+        return mLauncher.waitForObjectInContainer(
+                mLauncher.waitForLauncherObject(DROP_BAR_RES_ID),
+                DELETE_TARGET_TEXT_ID).getVisibleCenter();
+    }
+
+    /**
+     * Delete the appIcon from the workspace.
+     *
+     * @param appIcon to be deleted.
+     * @return validated workspace after the existing appIcon being deleted.
+     */
+    public Workspace deleteAppIcon(AppIcon appIcon) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "removing app icon from workspace")) {
+            dragIconToWorkspace(
+                    mLauncher, appIcon,
+                    () -> getDeleteDropPoint(),
+                    true, /* decelerating */
+                    appIcon.getLongPressIndicator(),
+                    () -> mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT),
+                    null);
+
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "dragged the app to the drop bar")) {
+                return new Workspace(mLauncher);
+            }
+        }
+    }
+
     /**
      * Finds folder icons in the current workspace.
      *
@@ -241,8 +278,8 @@
             expectLongClickEvents.run();
             launcher.waitForLauncherObject(longPressIndicator);
             LauncherInstrumentation.log("dragIconToSpringLoaded: indicator");
-            launcher.movePointer(iconCenter, dragStartCenter, 10, downTime, true,
-                    LauncherInstrumentation.GestureScope.INSIDE);
+            launcher.movePointer(iconCenter, dragStartCenter, DEFAULT_DRAG_STEPS, false,
+                    downTime, true, LauncherInstrumentation.GestureScope.INSIDE);
         }, SPRING_LOADED_STATE_ORDINAL, "long-pressing and triggering drag start");
         return dragStartCenter;
     }
@@ -270,7 +307,7 @@
             expectDropEvents = () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN,
                     LauncherInstrumentation.EVENT_START);
         }
-        dragIconToWorkspace(launcher, launchable, () -> dest, longPressIndicator,
+        dragIconToWorkspace(launcher, launchable, () -> dest, false, longPressIndicator,
                 expectLongClickEvents, expectDropEvents);
     }
 
@@ -280,13 +317,13 @@
      */
     static void dragIconToWorkspace(LauncherInstrumentation launcher, Launchable launchable,
             Supplier<Point> destSupplier, String longPressIndicator) {
-        dragIconToWorkspace(launcher, launchable, destSupplier, longPressIndicator,
+        dragIconToWorkspace(launcher, launchable, destSupplier, false, longPressIndicator,
                 () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT), null);
     }
 
     static void dragIconToWorkspace(
             LauncherInstrumentation launcher, Launchable launchable, Supplier<Point> dest,
-            String longPressIndicator, Runnable expectLongClickEvents,
+            boolean isDecelerating, String longPressIndicator, Runnable expectLongClickEvents,
             @Nullable Runnable expectDropEvents) {
         try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer(
                 "want to drag icon to workspace")) {
@@ -301,8 +338,8 @@
             while (targetDest.x > displayX || targetDest.x < 0) {
                 int edgeX = targetDest.x > 0 ? displayX : 0;
                 Point screenEdge = new Point(edgeX, targetDest.y);
-                launcher.movePointer(dragStart, screenEdge, 10, downTime, true,
-                        LauncherInstrumentation.GestureScope.INSIDE);
+                launcher.movePointer(dragStart, screenEdge, DEFAULT_DRAG_STEPS, isDecelerating,
+                        downTime, true, LauncherInstrumentation.GestureScope.INSIDE);
                 launcher.waitForIdle(); // Wait for the page change to happen
                 targetDest.x += displayX * (targetDest.x > 0 ? -1 : 1);
                 dragStart = screenEdge;
@@ -310,8 +347,8 @@
 
             // targetDest.x is now between 0 and displayX so we found the target page,
             // we just have to put move the icon to the destination and drop it
-            launcher.movePointer(dragStart, targetDest, 10, downTime, true,
-                    LauncherInstrumentation.GestureScope.INSIDE);
+            launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, isDecelerating,
+                    downTime, true, LauncherInstrumentation.GestureScope.INSIDE);
             dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
         }
     }