Merge "Moving the class LayoutParams to a file outside CellLayout" into tm-qpr-dev
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
new file mode 100644
index 0000000..0c8543f
--- /dev/null
+++ b/quickstep/res/layout/task_desktop.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.quickstep.views.DesktopTaskView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:clipChildren="true"
+    android:clipToOutline="true"
+    android:defaultFocusHighlightEnabled="false"
+    android:focusable="true">
+
+    <!--
+         TODO(b249371338): DesktopTaskView extends from TaskView. TaskView expects TaskThumbnailView
+         and IconView with these ids to be present. Need to refactor RecentsView to accept child
+         views that do not inherint from TaskView only or create a generic TaskView that have
+         N number of tasks.
+     -->
+    <com.android.quickstep.views.TaskThumbnailView
+        android:id="@+id/snapshot"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <com.android.quickstep.views.IconView
+        android:id="@+id/icon"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        android:visibility="gone" />
+
+</com.android.quickstep.views.DesktopTaskView>
diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml
index 905fbda..bc5d02a 100644
--- a/quickstep/res/values-land/dimens.xml
+++ b/quickstep/res/values-land/dimens.xml
@@ -80,4 +80,8 @@
     <dimen name="taskbar_button_margin_6_5">219.6dp</dimen>
     <dimen name="taskbar_button_margin_4_5">84dp</dimen>
     <dimen name="taskbar_button_margin_4_4">79dp</dimen>
+    <dimen name="taskbar_contextual_button_margin">48dp</dimen>
+    <dimen name="taskbar_suw_frame">96dp</dimen>
+    <dimen name="taskbar_suw_insets">24dp</dimen>
+
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index c85e71c..3add4dc 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -258,7 +258,10 @@
     <dimen name="taskbar_contextual_button_padding">16dp</dimen>
     <dimen name="taskbar_contextual_padding_top">8dp</dimen>
     <dimen name="taskbar_nav_buttons_size">44dp</dimen>
-    <dimen name="taskbar_contextual_button_margin">47dp</dimen>
+    <dimen name="taskbar_split_instructions_margin">48dp</dimen>
+    <dimen name="taskbar_contextual_button_margin">120dp</dimen>
+    <dimen name="taskbar_suw_insets">48dp</dimen>
+    <dimen name="taskbar_suw_frame">48dp</dimen>
     <dimen name="taskbar_hotseat_nav_spacing">24dp</dimen>
     <dimen name="taskbar_contextual_buttons_size">35dp</dimen>
     <dimen name="taskbar_stashed_size">24dp</dimen>
@@ -268,6 +271,7 @@
     <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>
+    <dimen name="taskbar_edu_horizontal_margin">112dp</dimen>
     <dimen name="taskbar_nav_buttons_width_kids">88dp</dimen>
     <dimen name="taskbar_nav_buttons_height_kids">40dp</dimen>
     <dimen name="taskbar_nav_buttons_corner_radius_kids">40dp</dimen>
@@ -283,4 +287,9 @@
     <dimen name="taskbar_button_margin_4_5">47dp</dimen>
     <dimen name="taskbar_button_margin_4_4">47dp</dimen>
     <dimen name="taskbar_button_margin_default">47dp</dimen>
+
+    <!-- Launcher splash screen -->
+    <!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
+    <!--     starting_surface_exit_animation_window_shift_length -->
+    <dimen name="starting_surface_exit_animation_window_shift_length">20dp</dimen>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index a219ac6..b9b4fc3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfo;
@@ -116,7 +117,8 @@
 
     @Override
     protected boolean isTaskbarTouchable() {
-        return !mTaskbarLauncherStateController.isAnimatingToLauncher();
+        return !(mTaskbarLauncherStateController.isAnimatingToLauncher()
+                && mTaskbarLauncherStateController.goingToAlignedLauncherState());
     }
 
     public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) {
@@ -171,7 +173,7 @@
             }
         }
 
-        if (ENABLE_SHELL_TRANSITIONS
+        if (ENABLE_SHELL_TRANSITIONS && isResumed
                 && !mLauncher.getStateManager().getState().isTaskbarAlignedWithHotseat(mLauncher)) {
             // Launcher is resumed, but in a state where taskbar is still independent, so
             // ignore the state change.
@@ -291,9 +293,14 @@
     @Override
     public void setSystemGestureInProgress(boolean inProgress) {
         super.setSystemGestureInProgress(inProgress);
-        // Launcher's ScrimView will draw the background throughout the gesture. But once the
-        // gesture ends, start drawing taskbar's background again since launcher might stop drawing.
-        forceHideBackground(inProgress);
+        // TODO(b/250645563): Don't show round corners when leaving in-app state, and remove
+        // forceHideBackground call entirely.
+        if (!FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
+            // Launcher's ScrimView will draw the background throughout the gesture. But once the
+            // gesture ends, start drawing taskbar's background again since launcher might stop
+            // drawing.
+            forceHideBackground(inProgress);
+        }
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 1c345a6..339f3a9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -126,7 +126,8 @@
 
     public static final int ALPHA_INDEX_IMMERSIVE_MODE = 0;
     public static final int ALPHA_INDEX_KEYGUARD_OR_DISABLE = 1;
-    private static final int NUM_ALPHA_CHANNELS = 2;
+    public static final int ALPHA_INDEX_SUW = 2;
+    private static final int NUM_ALPHA_CHANNELS = 3;
 
     private final ArrayList<StatePropertyHolder> mPropertyHolders = new ArrayList<>();
     private final ArrayList<ImageView> mAllButtons = new ArrayList<>();
@@ -203,8 +204,10 @@
         DeviceProfile deviceProfile = mContext.getDeviceProfile();
         Resources resources = mContext.getResources();
         mNavButtonsView.getLayoutParams().height = !isPhoneMode(deviceProfile) ?
-                deviceProfile.taskbarSize :
-                resources.getDimensionPixelSize(R.dimen.taskbar_size);
+                mContext.isUserSetupComplete()
+                        ? deviceProfile.taskbarSize
+                        : resources.getDimensionPixelSize(R.dimen.taskbar_suw_frame)
+                : resources.getDimensionPixelSize(R.dimen.taskbar_size);
 
         mIsImeRenderingNavButtons =
                 InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
@@ -266,14 +269,12 @@
             updateButtonLayoutSpacing();
             updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneButtonNavMode(mContext));
             if (isInSetup) {
-                // Since setup wizard only has back button enabled, it looks strange to be
-                // end-aligned, so start-align instead.
-                FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
-                        mNavButtonContainer.getLayoutParams();
-                navButtonsLayoutParams.setMarginStart(navButtonsLayoutParams.getMarginEnd());
-                navButtonsLayoutParams.setMarginEnd(0);
-                navButtonsLayoutParams.gravity = Gravity.START;
-                mNavButtonContainer.requestLayout();
+                handleSetupUi();
+
+                // Hide back button in SUW if keyboard is showing (IME draws its own back).
+                mPropertyHolders.add(new StatePropertyHolder(
+                        mBackButtonAlpha.getProperty(ALPHA_INDEX_SUW),
+                        flags -> (flags & FLAG_IME_VISIBLE) == 0));
 
                 // TODO(b/210906568) Dark intensity is currently not propagated during setup, so set
                 //  it based on dark theme for now.
@@ -738,14 +739,38 @@
         if (mFloatingRotationButton != null) {
             mFloatingRotationButton.onConfigurationChanged(configChanges);
         }
+        if (!mContext.isUserSetupComplete()) {
+            handleSetupUi();
+        }
         updateButtonLayoutSpacing();
     }
 
+    private void handleSetupUi() {
+        // Since setup wizard only has back button enabled, it looks strange to be
+        // end-aligned, so start-align instead.
+        FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
+                mNavButtonContainer.getLayoutParams();
+        Resources resources = mContext.getResources();
+        DeviceProfile deviceProfile = mContext.getDeviceProfile();
+        int setupMargin = resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_margin);
+        navButtonsLayoutParams.setMarginStart(setupMargin);
+        navButtonsLayoutParams.bottomMargin = !deviceProfile.isLandscape
+                ? 0
+                : setupMargin -
+                        (resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2);
+        navButtonsLayoutParams.setMarginEnd(0);
+        navButtonsLayoutParams.gravity = Gravity.START;
+        mNavButtonContainer.setLayoutParams(navButtonsLayoutParams);
+        mNavButtonContainer.requestLayout();
+    }
+
     /**
-     * Adds the correct spacing to 3 button nav container. No-op if using gesture nav or kids mode.
+     * Adds the correct spacing to 3 button nav container. No-op if using gesture nav, setup
+     * is incomplete, or in kids mode.
      */
     private void updateButtonLayoutSpacing() {
-        if (!mContext.isThreeButtonNav() || mContext.isNavBarKidsModeActive()) {
+        if (!mContext.isThreeButtonNav() || mContext.isNavBarKidsModeActive()
+                || !mContext.isUserSetupComplete()) {
             return;
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 9d15ea8..72c163e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -89,6 +89,7 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
 
 import java.io.PrintWriter;
@@ -148,11 +149,12 @@
         mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
         mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
                 () -> getPackageManager().isSafeMode());
-        mIsUserSetupComplete = SettingsCache.INSTANCE.get(this).getValue(
+        SettingsCache settingsCache = SettingsCache.INSTANCE.get(this);
+        mIsUserSetupComplete = settingsCache.getValue(
                 Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
-        mIsNavBarForceVisible = SettingsCache.INSTANCE.get(this).getValue(
+        mIsNavBarForceVisible = settingsCache.getValue(
                 Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
-        mIsNavBarKidsMode = SettingsCache.INSTANCE.get(this).getValue(
+        mIsNavBarKidsMode = settingsCache.getValue(
                 Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
 
         updateIconSize(resources);
@@ -197,7 +199,9 @@
                 new TaskbarViewController(this, taskbarView),
                 new TaskbarScrimViewController(this, taskbarScrimView),
                 new TaskbarUnfoldAnimationController(this, unfoldTransitionProgressProvider,
-                        mWindowManager, WindowManagerGlobal.getWindowManagerService()),
+                    mWindowManager,
+                    new RotationChangeProvider(WindowManagerGlobal.getWindowManagerService(), this,
+                        getMainExecutor())),
                 new TaskbarKeyguardController(this),
                 new StashedHandleViewController(this, stashedHandleView),
                 new TaskbarStashController(this),
@@ -614,6 +618,10 @@
                     resources.getDimensionPixelSize(R.dimen.taskbar_size) :
                     resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
         }
+
+        if (!isUserSetupComplete()) {
+            return getResources().getDimensionPixelSize(R.dimen.taskbar_suw_frame);
+        }
         return mDeviceProfile.taskbarSize + Math.max(getLeftCornerRadius(), getRightCornerRadius());
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 2b80b75..707023b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -165,6 +165,7 @@
      * Cleans up all controllers.
      */
     public void onDestroy() {
+        mAreAllControllersInitialized = false;
         mSharedState = null;
 
         navbarButtonsViewController.onDestroy();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
index 454a2a4..95b93fe 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
@@ -220,5 +220,9 @@
                         v -> mTaskbarEduView.snapToPage(currentPage + 1));
             }
         }
+
+        int getIconLayoutBoundsWidth() {
+            return mControllers.taskbarViewController.getIconLayoutBounds().width();
+        }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
index 89d67be..c0cbbd6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
@@ -39,6 +39,9 @@
 
     private final Rect mInsets = new Rect();
 
+    // Initialized in init.
+    private TaskbarEduController.TaskbarEduCallbacks mTaskbarEduCallbacks;
+
     private Button mStartButton;
     private Button mEndButton;
     private TaskbarEduPagedView mPagedView;
@@ -56,6 +59,7 @@
         if (mPagedView != null) {
             mPagedView.setControllerCallbacks(callbacks);
         }
+        mTaskbarEduCallbacks = callbacks;
     }
 
     @Override
@@ -101,6 +105,22 @@
                 Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0);
     }
 
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        int contentWidth = Math.min(getContentAreaWidth(), getMeasuredWidth());
+        contentWidth = Math.max(contentWidth, mTaskbarEduCallbacks.getIconLayoutBoundsWidth());
+        int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY);
+
+        mContent.measure(contentAreaWidthSpec, MeasureSpec.UNSPECIFIED);
+    }
+
+    private int getContentAreaWidth() {
+        return mTaskbarEduCallbacks.getIconLayoutBoundsWidth()
+                + getResources().getDimensionPixelSize(R.dimen.taskbar_edu_horizontal_margin) * 2;
+    }
+
     /** Show the Education flow. */
     public void show() {
         attachToContainer();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 7b2b7ec..079e8a1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -44,7 +44,7 @@
     /** The bottom insets taskbar provides to the IME when IME is visible. */
     val taskbarHeightForIme: Int = context.resources.getDimensionPixelSize(
         KtR.dimen.taskbar_ime_size)
-    private val contentRegion: Region = Region()
+    private val touchableRegion: Region = Region()
     private val deviceProfileChangeListener = { _: DeviceProfile ->
         onTaskbarWindowHeightOrInsetsChanged()
     }
@@ -77,10 +77,11 @@
     }
 
     fun onTaskbarWindowHeightOrInsetsChanged() {
-        var contentHeight = controllers.taskbarStashController.contentHeightToReportToApps
-        contentRegion.set(0, windowLayoutParams.height - contentHeight,
+        val touchableHeight = controllers.taskbarStashController.touchableHeight
+        touchableRegion.set(0, windowLayoutParams.height - touchableHeight,
             context.deviceProfile.widthPx, windowLayoutParams.height)
-        var tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
+        val contentHeight = controllers.taskbarStashController.contentHeightToReportToApps
+        val tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
         for (provider in windowLayoutParams.providedInsets) {
             if (provider.type == ITYPE_EXTRA_NAVIGATION_BAR) {
                 provider.insetsSize = Insets.of(0, 0, 0, contentHeight)
@@ -154,7 +155,7 @@
                 if (context.isTaskbarWindowFullscreen) {
                     TOUCHABLE_INSETS_FRAME
                 } else {
-                    insetsInfo.touchableRegion.set(contentRegion)
+                    insetsInfo.touchableRegion.set(touchableRegion)
                     TOUCHABLE_INSETS_REGION
                 }
             )
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 58c689b..de37b70 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -50,7 +50,6 @@
 import java.util.HashMap;
 import java.util.StringJoiner;
 import java.util.function.Consumer;
-import java.util.function.Supplier;
 
 /**
  * Track LauncherState, RecentsAnimation, resumed state for task bar in one place here and animate
@@ -65,15 +64,12 @@
     public static final int FLAG_RECENTS_ANIMATION_RUNNING = 1 << 1;
     public static final int FLAG_TRANSITION_STATE_RUNNING = 1 << 2;
 
+    private static final int FLAGS_LAUNCHER = FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING;
     /** 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::onIconAlignmentRatioChangedForAppAndHomeTransition);
-    private final AnimatedFloat mIconAlignmentForGestureState =
-            new AnimatedFloat(this::onIconAlignmentRatioChangedForAppAndHomeTransition);
-    private final AnimatedFloat mIconAlignmentForLauncherState =
-            new AnimatedFloat(this::onIconAlignmentRatioChangedForStateTransition);
+    private final AnimatedFloat mIconAlignment =
+            new AnimatedFloat(this::onIconAlignmentRatioChanged);
 
     private TaskbarControllers mControllers;
     private AnimatedFloat mTaskbarBackgroundAlpha;
@@ -86,8 +82,7 @@
 
     private @Nullable TaskBarRecentsAnimationListener mTaskBarRecentsAnimationListener;
 
-    private boolean mIsAnimatingToLauncherViaGesture;
-    private boolean mIsAnimatingToLauncherViaResume;
+    private boolean mIsAnimatingToLauncher;
 
     private boolean mShouldDelayLauncherStateAnim;
 
@@ -148,8 +143,8 @@
         mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
         mIconAlphaForHome.setConsumer(mIconAlphaForHomeConsumer);
 
-        mIconAlignmentForResumedState.finishAnimation();
-        onIconAlignmentRatioChangedForAppAndHomeTransition();
+        mIconAlignment.finishAnimation();
+        onIconAlignmentRatioChanged();
 
         mLauncher.getStateManager().addStateListener(mStateListener);
 
@@ -165,9 +160,7 @@
     public void onDestroy() {
         mCanSyncViews = false;
 
-        mIconAlignmentForResumedState.finishAnimation();
-        mIconAlignmentForGestureState.finishAnimation();
-        mIconAlignmentForLauncherState.finishAnimation();
+        mIconAlignment.finishAnimation();
 
         mIconAlphaForHome.setConsumer(null);
         mLauncher.getHotseat().setIconsAlpha(1f);
@@ -187,6 +180,9 @@
         TaskbarStashController stashController = mControllers.taskbarStashController;
         stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
                 toState.isTaskbarStashed(mLauncher));
+        if (DEBUG) {
+            Log.d(TAG, "createAnimToLauncher - FLAG_IN_APP: " + false);
+        }
         stashController.updateStateForFlag(FLAG_IN_APP, false);
 
         updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, true);
@@ -201,7 +197,7 @@
     }
 
     public boolean isAnimatingToLauncher() {
-        return mIsAnimatingToLauncherViaResume || mIsAnimatingToLauncherViaGesture;
+        return mIsAnimatingToLauncher;
     }
 
     public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) {
@@ -261,11 +257,29 @@
     }
 
     private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) {
+        boolean goingToLauncher = isInLauncher();
+        final float toAlignment;
+        if (goingToLauncher) {
+            boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
+            boolean willStashVisually = isInStashedState
+                    && mControllers.taskbarStashController.supportsVisualStashing();
+            boolean isTaskbarAlignedWithHotseat =
+                    mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
+            toAlignment = isTaskbarAlignedWithHotseat && !willStashVisually ? 1 : 0;
+        } else {
+            toAlignment = 0;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "onStateChangeApplied - mState: " + getStateString(mState)
+                    + ", changedFlags: " + getStateString(changedFlags)
+                    + ", goingToLauncher: " + goingToLauncher
+                    + ", mLauncherState: " + mLauncherState
+                    + ", toAlignment: " + toAlignment);
+        }
         AnimatorSet animatorSet = new AnimatorSet();
 
         // Add the state animation first to ensure FLAG_IN_STASHED_LAUNCHER_STATE is set and we can
         // determine whether goingToUnstashedLauncherStateChanged.
-        boolean wasGoingToUnstashedLauncherState = goingToUnstashedLauncherState();
         if (hasAnyFlag(changedFlags, FLAG_TRANSITION_STATE_RUNNING)) {
             boolean committed = !hasAnyFlag(FLAG_TRANSITION_STATE_RUNNING);
             playStateTransitionAnim(animatorSet, duration, committed);
@@ -276,95 +290,69 @@
                 applyState(0 /* duration */);
             }
         }
-        boolean goingToUnstashedLauncherStateChanged = wasGoingToUnstashedLauncherState
-                != goingToUnstashedLauncherState();
 
-        boolean launcherStateChangedDuringAnimToResumeAlignment =
-                mIconAlignmentForResumedState.isAnimating() && goingToUnstashedLauncherStateChanged;
-        if (hasAnyFlag(changedFlags, FLAG_RESUMED)
-                || launcherStateChangedDuringAnimToResumeAlignment) {
-            boolean isResumed = isResumed();
-            // If launcher is resumed, we show the icons when going to an unstashed launcher state
-            // or launcher state is not changed (e.g. in overview, launcher is paused and resumed).
-            float toAlignmentForResumedState = isResumed && (goingToUnstashedLauncherState()
-                    || !goingToUnstashedLauncherStateChanged) ? 1 : 0;
-            // If we're already animating to the value, just leave it be instead of restarting it.
-            if (!mIconAlignmentForResumedState.isAnimatingToValue(toAlignmentForResumedState)) {
-                ObjectAnimator resumeAlignAnim = mIconAlignmentForResumedState
-                        .animateToValue(toAlignmentForResumedState)
-                        .setDuration(duration);
-                if (DEBUG) {
-                    Log.d(TAG, "mIconAlignmentForResumedState - "
-                            + mIconAlignmentForResumedState.value
-                            + " -> " + toAlignmentForResumedState + ": " + duration);
+        if (hasAnyFlag(changedFlags, FLAGS_LAUNCHER)) {
+            animatorSet.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mIsAnimatingToLauncher = false;
                 }
 
-                resumeAlignAnim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mIsAnimatingToLauncherViaResume = false;
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mIsAnimatingToLauncher = goingToLauncher;
+
+                    TaskbarStashController stashController =
+                            mControllers.taskbarStashController;
+                    if (DEBUG) {
+                        Log.d(TAG, "onAnimationStart - FLAG_IN_APP: " + !goingToLauncher);
                     }
-
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        mIsAnimatingToLauncherViaResume = isResumed;
-
-                        TaskbarStashController stashController =
-                                mControllers.taskbarStashController;
-                        stashController.updateStateForFlag(FLAG_IN_APP, !isResumed);
-                        stashController.applyState(duration);
-                    }
-                });
-                animatorSet.play(resumeAlignAnim);
-            }
-        }
-
-
-        boolean launcherStateChangedDuringAnimToGestureAlignment =
-                mIconAlignmentForGestureState.isAnimating() && goingToUnstashedLauncherStateChanged;
-        if (hasAnyFlag(changedFlags, FLAG_RECENTS_ANIMATION_RUNNING)
-                || launcherStateChangedDuringAnimToGestureAlignment) {
-            boolean isRecentsAnimationRunning = isRecentsAnimationRunning();
-            float toAlignmentForGestureState = isRecentsAnimationRunning
-                    && goingToUnstashedLauncherState() ? 1 : 0;
-            // If we're already animating to the value, just leave it be instead of restarting it.
-            if (!mIconAlignmentForGestureState.isAnimatingToValue(toAlignmentForGestureState)) {
-                Animator gestureAlignAnim = mIconAlignmentForGestureState
-                        .animateToValue(toAlignmentForGestureState);
-                if (isRecentsAnimationRunning) {
-                    gestureAlignAnim.setDuration(duration);
+                    stashController.updateStateForFlag(FLAG_IN_APP, !goingToLauncher);
+                    stashController.applyState(duration);
                 }
-                if (DEBUG) {
-                    Log.d(TAG, "mIconAlignmentForGestureState - "
-                            + mIconAlignmentForGestureState.value
-                            + " -> " + toAlignmentForGestureState + ": " + duration);
-                }
-                gestureAlignAnim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mIsAnimatingToLauncherViaGesture = false;
-                    }
+            });
 
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        mIsAnimatingToLauncherViaGesture = isRecentsAnimationRunning();
-                    }
-                });
-                animatorSet.play(gestureAlignAnim);
-            }
-        }
-
-        if (hasAnyFlag(changedFlags, FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING)) {
-            boolean goingToLauncher = hasAnyFlag(FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING);
             if (goingToLauncher) {
                 // Handle closing open popups when going home/overview
                 AbstractFloatingView.closeAllOpenViews(mControllers.taskbarActivityContext);
             }
-            animatorSet.play(mTaskbarBackgroundAlpha.animateToValue(goingToLauncher ? 0 : 1)
+        }
+
+        float backgroundAlpha =
+                goingToLauncher && mLauncherState.isTaskbarAlignedWithHotseat(mLauncher)
+                        ? 0 : 1;
+        // Don't animate if background has reached desired value.
+        if (mTaskbarBackgroundAlpha.isAnimating()
+                || mTaskbarBackgroundAlpha.value != backgroundAlpha) {
+            mTaskbarBackgroundAlpha.cancelAnimation();
+            if (DEBUG) {
+                Log.d(TAG, "onStateChangeApplied - taskbarBackgroundAlpha - "
+                        + mTaskbarBackgroundAlpha.value
+                        + " -> " + backgroundAlpha + ": " + duration);
+            }
+            animatorSet.play(mTaskbarBackgroundAlpha.animateToValue(backgroundAlpha)
                     .setDuration(duration));
         }
 
+        if (mIconAlignment.isAnimatingToValue(toAlignment)
+                || mIconAlignment.isSettledOnValue(toAlignment)) {
+            // Already at desired value, but make sure we run the callback at the end.
+            animatorSet.addListener(AnimatorListeners.forEndCallback(
+                    this::onIconAlignmentRatioChanged));
+        } else {
+            mIconAlignment.cancelAnimation();
+            ObjectAnimator iconAlignAnim = mIconAlignment
+                    .animateToValue(toAlignment)
+                    .setDuration(duration);
+            if (DEBUG) {
+                Log.d(TAG, "onStateChangeApplied - iconAlignment - "
+                        + mIconAlignment.value
+                        + " -> " + toAlignment + ": " + duration);
+            }
+            animatorSet.play(iconAlignAnim);
+        }
         animatorSet.setInterpolator(EMPHASIZED);
+
         if (start) {
             animatorSet.start();
         }
@@ -372,18 +360,13 @@
     }
 
     /** Returns whether we're going to a state where taskbar icons should align with launcher. */
-    private boolean goingToUnstashedLauncherState() {
-        return !mControllers.taskbarStashController.isInStashedLauncherState();
+    public boolean goingToAlignedLauncherState() {
+        return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
     }
 
     private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
             boolean committed) {
         boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
-        boolean willStashVisually =
-                isInStashedState && mControllers.taskbarStashController.supportsVisualStashing();
-        float toAlignment =
-                mLauncherState.isTaskbarAlignedWithHotseat(mLauncher) && !willStashVisually ? 1 : 0;
-
         TaskbarStashController stashController = mControllers.taskbarStashController;
         stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState);
         Animator stashAnimator = stashController.applyStateWithoutStart(duration);
@@ -406,55 +389,23 @@
             });
             animatorSet.play(stashAnimator);
         }
-        if (mIconAlignmentForLauncherState.value == toAlignment) {
-            // Already at expected value, but make sure we run the callback at the end.
-            animatorSet.addListener(AnimatorListeners.forEndCallback(
-                    this::onIconAlignmentRatioChangedForStateTransition));
-        }
-        if (!mIconAlignmentForLauncherState.isAnimatingToValue(toAlignment)) {
-            // If we're already animating to the value, just leave it be instead of restarting it.
-            mIconAlignmentForLauncherState.finishAnimation();
-            animatorSet.play(mIconAlignmentForLauncherState.animateToValue(toAlignment)
-                    .setDuration(duration));
-            if (DEBUG) {
-                Log.d(TAG, "mIconAlignmentForLauncherState - "
-                        + mIconAlignmentForLauncherState.value
-                        + " -> " + toAlignment + ": " + duration);
-            }
-            animatorSet.setInterpolator(EMPHASIZED);
-        }
     }
 
-    private boolean isResumed() {
-        return (mState & FLAG_RESUMED) != 0;
+    private boolean isInLauncher() {
+        return (mState & FLAGS_LAUNCHER) != 0;
     }
 
-    private boolean isRecentsAnimationRunning() {
-        return (mState & FLAG_RECENTS_ANIMATION_RUNNING) != 0;
-    }
-
-    private void onIconAlignmentRatioChangedForStateTransition() {
-        if (!isResumed() && mTaskBarRecentsAnimationListener == null) {
-            return;
-        }
-        onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioForLauncherState);
-    }
-
-    private void onIconAlignmentRatioChangedForAppAndHomeTransition() {
-        onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioBetweenAppAndHome);
-    }
-
-    private void onIconAlignmentRatioChanged(Supplier<AnimatedFloat> alignmentSupplier) {
-        if (mControllers == null) {
-            return;
-        }
-        AnimatedFloat animatedFloat = alignmentSupplier.get();
+    private void onIconAlignmentRatioChanged() {
         float currentValue = mIconAlphaForHome.getValue();
-        boolean taskbarWillBeVisible = animatedFloat.value < 1;
+        boolean taskbarWillBeVisible = mIconAlignment.value < 1;
         boolean firstFrameVisChanged = (taskbarWillBeVisible && Float.compare(currentValue, 1) != 0)
                 || (!taskbarWillBeVisible && Float.compare(currentValue, 0) != 0);
 
-        updateIconAlignment(animatedFloat.value, animatedFloat.getEndValue());
+        mControllers.taskbarViewController.setLauncherIconAlignment(
+                mIconAlignment.value, mIconAlignment.getEndValue(), mLauncher.getDeviceProfile());
+        mControllers.navbarButtonsViewController.updateTaskbarAlignment(mIconAlignment.value);
+        // Switch taskbar and hotseat in last frame
+        mIconAlphaForHome.setValue(taskbarWillBeVisible ? 1 : 0);
 
         // Sync the first frame where we swap taskbar and hotseat.
         if (firstFrameVisChanged && mCanSyncViews && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
@@ -464,28 +415,6 @@
         }
     }
 
-    private void updateIconAlignment(float alignment, Float endAlignment) {
-        mControllers.taskbarViewController.setLauncherIconAlignment(
-                alignment, endAlignment, mLauncher.getDeviceProfile());
-
-        // Switch taskbar and hotseat in last frame
-        setTaskbarViewVisible(alignment < 1);
-        mControllers.navbarButtonsViewController.updateTaskbarAlignment(alignment);
-    }
-
-    private AnimatedFloat getCurrentIconAlignmentRatioBetweenAppAndHome() {
-        return mIconAlignmentForResumedState.value > mIconAlignmentForGestureState.value
-                ? mIconAlignmentForResumedState : mIconAlignmentForGestureState;
-    }
-
-    private AnimatedFloat getCurrentIconAlignmentRatioForLauncherState() {
-        return mIconAlignmentForLauncherState;
-    }
-
-    private void setTaskbarViewVisible(boolean isVisible) {
-        mIconAlphaForHome.setValue(isVisible ? 1 : 0);
-    }
-
     private final class TaskBarRecentsAnimationListener implements
             RecentsAnimationCallbacks.RecentsAnimationListener {
         private final RecentsAnimationCallbacks mCallbacks;
@@ -515,11 +444,11 @@
             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;
+            if (DEBUG) {
+                Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp);
+            }
             controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
             controller.applyState();
         }
@@ -527,29 +456,24 @@
 
     private static String getStateString(int flags) {
         StringJoiner str = new StringJoiner("|");
-        str.add((flags & FLAG_RESUMED) != 0 ? "FLAG_RESUMED" : "");
-        str.add((flags & FLAG_RECENTS_ANIMATION_RUNNING) != 0
-                ? "FLAG_RECENTS_ANIMATION_RUNNING" : "");
-        str.add((flags & FLAG_TRANSITION_STATE_RUNNING) != 0
-                ? "FLAG_TRANSITION_STATE_RUNNING" : "");
+        if ((flags & FLAG_RESUMED) != 0) {
+            str.add("FLAG_RESUMED");
+        }
+        if ((flags & FLAG_RECENTS_ANIMATION_RUNNING) != 0) {
+            str.add("FLAG_RECENTS_ANIMATION_RUNNING");
+        }
+        if ((flags & FLAG_TRANSITION_STATE_RUNNING) != 0) {
+            str.add("FLAG_TRANSITION_STATE_RUNNING");
+        }
         return str.toString();
     }
 
     protected void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarLauncherStateController:");
-
         pw.println(String.format(
-                "%s\tmIconAlignmentForResumedState=%.2f",
+                "%s\tmIconAlignment=%.2f",
                 prefix,
-                mIconAlignmentForResumedState.value));
-        pw.println(String.format(
-                "%s\tmIconAlignmentForGestureState=%.2f",
-                prefix,
-                mIconAlignmentForGestureState.value));
-        pw.println(String.format(
-                "%s\tmIconAlignmentForLauncherState=%.2f",
-                prefix,
-                mIconAlignmentForLauncherState.value));
+                mIconAlignment.value));
         pw.println(String.format(
                 "%s\tmTaskbarBackgroundAlpha=%.2f", prefix, mTaskbarBackgroundAlpha.value));
         pw.println(String.format(
@@ -558,13 +482,9 @@
         pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState)));
         pw.println(String.format("%s\tmLauncherState=%s", prefix, mLauncherState));
         pw.println(String.format(
-                "%s\tmIsAnimatingToLauncherViaGesture=%b",
+                "%s\tmIsAnimatingToLauncher=%b",
                 prefix,
-                mIsAnimatingToLauncherViaGesture));
-        pw.println(String.format(
-                "%s\tmIsAnimatingToLauncherViaResume=%b",
-                prefix,
-                mIsAnimatingToLauncherViaResume));
+                mIsAnimatingToLauncher));
         pw.println(String.format(
                 "%s\tmShouldDelayLauncherStateAnim=%b", prefix, mShouldDelayLauncherStateAnim));
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 4b0adb1..9fd2bf9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -67,7 +67,7 @@
     public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 4; // setup wizard and AllSetActivity
     public static final int FLAG_STASHED_IN_APP_IME = 1 << 5; // IME is visible
     public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 6;
-    public static final int FLAG_STASHED_IN_APP_ALL_APPS = 1 << 7; // All apps is visible.
+    public static final int FLAG_STASHED_IN_TASKBAR_ALL_APPS = 1 << 7; // All apps is visible.
     public static final int FLAG_IN_SETUP = 1 << 8; // In the Setup Wizard
     public static final int FLAG_STASHED_SMALL_SCREEN = 1 << 9; // phone screen gesture nav, stashed
 
@@ -77,8 +77,8 @@
     // If we're in an app and any of these flags are enabled, taskbar should be stashed.
     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 | FLAG_STASHED_IN_APP_ALL_APPS |
-            FLAG_STASHED_SMALL_SCREEN;
+            | FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_TASKBAR_ALL_APPS
+            | FLAG_STASHED_SMALL_SCREEN;
 
     private static final int FLAGS_STASHED_IN_APP_IGNORING_IME =
             FLAGS_STASHED_IN_APP & ~FLAG_STASHED_IN_APP_IME;
@@ -88,7 +88,7 @@
     // Currently any flag that causes us to stash in an app is included, except for IME or All Apps
     // since those cover 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 & ~FLAG_STASHED_IN_APP_ALL_APPS;
+            & ~FLAG_STASHED_IN_APP_IME & ~FLAG_STASHED_IN_TASKBAR_ALL_APPS;
 
     /**
      * How long to stash/unstash when manually invoked via long press.
@@ -168,9 +168,11 @@
                 boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
                 boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
                 boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
+                boolean stashedInTaskbarAllApps =
+                        hasAnyFlag(flags, FLAG_STASHED_IN_TASKBAR_ALL_APPS);
                 boolean stashedForSmallScreen = hasAnyFlag(flags, FLAG_STASHED_SMALL_SCREEN);
                 return (inApp && stashedInApp) || (!inApp && stashedLauncherState)
-                        || stashedForSmallScreen;
+                        || stashedInTaskbarAllApps || stashedForSmallScreen;
             });
 
     public TaskbarStashController(TaskbarActivityContext activity) {
@@ -240,6 +242,7 @@
      */
     protected boolean supportsManualStashing() {
         return supportsVisualStashing()
+                && isInApp()
                 && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || mEnableManualStashingDuringTests);
     }
 
@@ -317,6 +320,13 @@
     }
 
     /**
+     * Returns the height that taskbar will be touchable.
+     */
+    public int getTouchableHeight() {
+        return mIsStashed ? mStashedHeight : mUnstashedHeight;
+    }
+
+    /**
      * Returns the height that taskbar will inset when inside apps.
      * @see WindowInsets.Type#navigationBars()
      * @see WindowInsets.Type#systemBars()
@@ -345,6 +355,11 @@
             }
             return mStashedHeight;
         }
+
+        if (!mActivity.isUserSetupComplete()) {
+            // Special insets for SUW.
+            return mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_suw_insets);
+        }
         return mUnstashedHeight;
     }
 
@@ -659,7 +674,7 @@
             return;
         }
 
-        updateStateForFlag(FLAG_STASHED_IN_APP_ALL_APPS, false);
+        updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, false);
         if (applyState) {
             applyState(ALL_APPS.getTransitionDuration(
                     mControllers.taskbarActivityContext, false /* isToState */));
@@ -779,7 +794,7 @@
         appendFlag(str, flags, FLAG_STASHED_IN_APP_SETUP, "FLAG_STASHED_IN_APP_SETUP");
         appendFlag(str, flags, FLAG_STASHED_IN_APP_IME, "FLAG_STASHED_IN_APP_IME");
         appendFlag(str, flags, FLAG_IN_STASHED_LAUNCHER_STATE, "FLAG_IN_STASHED_LAUNCHER_STATE");
-        appendFlag(str, flags, FLAG_STASHED_IN_APP_ALL_APPS, "FLAG_STASHED_IN_APP_ALL_APPS");
+        appendFlag(str, flags, FLAG_STASHED_IN_TASKBAR_ALL_APPS, "FLAG_STASHED_IN_APP_ALL_APPS");
         appendFlag(str, flags, FLAG_IN_SETUP, "FLAG_IN_SETUP");
         return str.toString();
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
index 64a4fa7..4c937a7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
@@ -15,13 +15,13 @@
  */
 package com.android.launcher3.taskbar;
 
-import android.view.IWindowManager;
 import android.view.View;
 import android.view.WindowManager;
 
 import com.android.quickstep.util.LauncherViewsMoveFromCenterTranslationApplier;
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
 
@@ -41,16 +41,20 @@
 
     public TaskbarUnfoldAnimationController(BaseTaskbarContext context,
             ScopedUnfoldTransitionProgressProvider source,
-            WindowManager windowManager, IWindowManager iWindowManager) {
+            WindowManager windowManager,
+            RotationChangeProvider rotationChangeProvider) {
         mScopedUnfoldTransitionProgressProvider = source;
         mNaturalUnfoldTransitionProgressProvider =
-                new NaturalRotationUnfoldProgressProvider(context, iWindowManager, source);
+                new NaturalRotationUnfoldProgressProvider(context,
+                        rotationChangeProvider,
+                        source);
         mMoveFromCenterAnimator = new UnfoldMoveFromCenterAnimator(windowManager,
                 new LauncherViewsMoveFromCenterTranslationApplier());
     }
 
     /**
      * Initializes the controller
+     *
      * @param taskbarControllers references to all other taskbar controllers
      */
     public void init(TaskbarControllers taskbarControllers) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 00d5083..16dd90d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -102,6 +102,9 @@
 
     private int mThemeIconsColor;
 
+    private final DeviceProfile.OnDeviceProfileChangeListener mDeviceProfileChangeListener =
+            dp -> commitRunningAppsToUI();
+
     public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
         mActivity = activity;
         mTaskbarView = taskbarView;
@@ -129,10 +132,13 @@
                 controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY();
         mTaskbarNavButtonTranslationYForInAppDisplay = controllers.navbarButtonsViewController
                 .getTaskbarNavButtonTranslationYForInAppDisplay();
+
+        mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
     }
 
     public void onDestroy() {
         LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
+        mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
         mModelCallbacks.unregisterListeners();
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
index b0d3528..128fa5e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.taskbar.allapps;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_APP_ALL_APPS;
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_TASKBAR_ALL_APPS;
 import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
 
 import com.android.launcher3.AbstractFloatingView;
@@ -84,7 +84,7 @@
     }
 
     private void setUpTaskbarStashing() {
-        mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_APP_ALL_APPS, true);
+        mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, true);
         mTaskbarStashController.applyState(
                 ALL_APPS.getTransitionDuration(mContext, true /* isToState */));
         mNavbarButtonsViewController.setSlideInViewVisible(true);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 192ac62..ee9845b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -29,7 +29,6 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_WIDGET_PICKER_DEPTH;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
@@ -67,6 +66,7 @@
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
+import android.view.WindowManagerGlobal;
 import android.window.SplashScreen;
 
 import androidx.annotation.Nullable;
@@ -140,12 +140,14 @@
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.unfold.UnfoldSharedComponent;
 import com.android.systemui.unfold.UnfoldTransitionFactory;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
 import com.android.systemui.unfold.config.UnfoldTransitionConfig;
 import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider;
 import com.android.systemui.unfold.system.DeviceStateManagerFoldProvider;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -174,6 +176,7 @@
     // Will be updated when dragging from taskbar.
     private @Nullable DragOptions mNextWorkspaceDragOptions = null;
     private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
+    private @Nullable RotationChangeProvider mRotationChangeProvider;
     private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
     /**
      * If Launcher restarted while in the middle of an Overview split select, it needs this data to
@@ -559,10 +562,8 @@
     @Override
     protected void onScreenOff() {
         super.onScreenOff();
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            RecentsView recentsView = getOverviewPanel();
-            recentsView.finishRecentsAnimation(true /* toRecents */, null);
-        }
+        RecentsView recentsView = getOverviewPanel();
+        recentsView.finishRecentsAnimation(true /* toRecents */, null);
     }
 
     /**
@@ -667,8 +668,8 @@
     private void initUnfoldTransitionProgressProvider() {
         final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
         if (config.isEnabled()) {
-            mUnfoldTransitionProgressProvider =
-                    UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
+            UnfoldSharedComponent unfoldComponent =
+                    UnfoldTransitionFactory.createUnfoldSharedComponent(
                             /* context= */ this,
                             config,
                             ProxyScreenStatusProvider.INSTANCE,
@@ -680,13 +681,21 @@
                             getMainThreadHandler(),
                             getMainExecutor(),
                             /* backgroundExecutor= */ THREAD_POOL_EXECUTOR,
-                            /* tracingTagPrefix= */ "launcher"
+                            /* tracingTagPrefix= */ "launcher",
+                            WindowManagerGlobal.getWindowManagerService()
                     );
 
+            mUnfoldTransitionProgressProvider = unfoldComponent.getUnfoldTransitionProvider()
+                    .orElseThrow(() -> new IllegalStateException(
+                            "Trying to create UnfoldTransitionProgressProvider when the "
+                                    + "transition is disabled"));
+
+            mRotationChangeProvider = unfoldComponent.getRotationChangeProvider();
             mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
-                    this,
+                    /* launcher= */ this,
                     getWindowManager(),
-                    mUnfoldTransitionProgressProvider
+                    mUnfoldTransitionProgressProvider,
+                    mRotationChangeProvider
             );
         }
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 910b99b..0e1120b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -31,11 +31,13 @@
 import android.annotation.TargetApi;
 import android.os.Build;
 import android.util.FloatProperty;
+import android.util.Log;
 import android.util.Pair;
 
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
@@ -88,6 +90,13 @@
             // While animating into recents, update the visible task data as needed
             builder.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
             mRecentsView.updateEmptyMessage();
+            // TODO(b/246283207): Remove logging once root cause of flake detected.
+            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                Log.d("b/246283207", "RecentsView#setStateWithAnimationInternal getCurrentPage(): "
+                                + mRecentsView.getCurrentPage()
+                                + ", getScrollForPage(getCurrentPage())): "
+                                + mRecentsView.getScrollForPage(mRecentsView.getCurrentPage()));
+            }
         } else {
             builder.addListener(
                     AnimatorListeners.forSuccessCallback(mRecentsView::resetTaskVisuals));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 6f07568..d075750 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -26,6 +26,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Themes;
 import com.android.quickstep.util.LayoutUtils;
@@ -104,7 +105,12 @@
 
     @Override
     public boolean isTaskbarStashed(Launcher launcher) {
-        return true;
+        return !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get();
+    }
+
+    @Override
+    public boolean isTaskbarAlignedWithHotseat(Launcher launcher) {
+        return !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get();
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index bcd722f..5eeeb36 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -113,19 +113,19 @@
                 config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
                 config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, EMPHASIZED_DECELERATE);
                 config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, FINAL_FRAME);
+
+                // Scroll RecentsView to page 0 as it goes offscreen, if necessary.
+                int numPagesToScroll = overview.getNextPage() - DEFAULT_PAGE;
+                long scrollDuration = Math.min(MAX_PAGE_SCROLL_DURATION,
+                        numPagesToScroll * PER_PAGE_SCROLL_DURATION);
+                config.duration = Math.max(config.duration, scrollDuration);
+                overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
             } else {
                 config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL);
                 config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
                 config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
             }
 
-            // Scroll RecentsView to page 0 as it goes offscreen, if necessary.
-            int numPagesToScroll = overview.getNextPage() - DEFAULT_PAGE;
-            long scrollDuration = Math.min(MAX_PAGE_SCROLL_DURATION,
-                    numPagesToScroll * PER_PAGE_SCROLL_DURATION);
-            config.duration = Math.max(config.duration, scrollDuration);
-            overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
-
             Workspace<?> workspace = mActivity.getWorkspace();
             // Start from a higher workspace scale, but only if we're invisible so we don't jump.
             boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
@@ -197,7 +197,8 @@
             config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR,
                     timings.getActionsFadeStartOffset(),
                     timings.getActionsFadeEndOffset()));
-        } else if (fromState == NORMAL && toState == OVERVIEW_SPLIT_SELECT) {
+        } else if ((fromState == NORMAL || fromState == ALL_APPS)
+                && toState == OVERVIEW_SPLIT_SELECT) {
             // Splitting from Home is currently only available on tablets
             SplitAnimationTimings timings = SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
             config.setInterpolator(ANIM_SCRIM_FADE, clampToProgress(LINEAR,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 30bb892..40dfd82 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -25,7 +25,6 @@
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_TRANSLATION;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
@@ -139,9 +138,7 @@
             AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher,
                     builder);
 
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                builder.addOnFrameCallback(recentsView::redrawLiveTile);
-            }
+            builder.addOnFrameCallback(recentsView::redrawLiveTile);
 
             AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_TASK_MENU);
         } else if (mStartState == ALL_APPS) {
@@ -182,11 +179,9 @@
         boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS
                 || (velocity < 0 && fling);
         if (success) {
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                RecentsView recentsView = mLauncher.getOverviewPanel();
-                recentsView.switchToScreenshot(null,
-                        () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
-            }
+            RecentsView recentsView = mLauncher.getOverviewPanel();
+            recentsView.switchToScreenshot(null,
+                    () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
             if (mStartState.overviewUi) {
                 new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState))
                         .animateWithVelocity(velocity);
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index f3630c1..07ddcc8 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -26,7 +26,6 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
@@ -76,6 +75,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnApplyWindowInsetsListener;
+import android.view.ViewGroup;
 import android.view.ViewTreeObserver.OnDrawListener;
 import android.view.ViewTreeObserver.OnScrollChangedListener;
 import android.view.WindowInsets;
@@ -128,12 +128,17 @@
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.startingsurface.SplashScreenExitAnimationUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Optional;
 import java.util.function.Consumer;
 
 /**
@@ -249,6 +254,13 @@
 
     private static final float MAX_QUICK_SWITCH_RECENTS_SCALE_PROGRESS = 0.07f;
 
+    // Controls task thumbnail splash's reveal animation after landing on a task from quickswitch.
+    // These values match WindowManager/Shell starting_window_app_reveal_* config values.
+    private static final int SPLASH_FADE_OUT_DURATION = 133;
+    private static final int SPLASH_APP_REVEAL_DELAY = 83;
+    private static final int SPLASH_APP_REVEAL_DURATION = 266;
+    private static final int SPLASH_ANIMATION_DURATION = 349;
+
     /**
      * Used as the page index for logging when we return to the last task at the end of the gesture.
      */
@@ -273,7 +285,7 @@
     private AnimatorControllerWithResistance mLauncherTransitionController;
     private boolean mHasEndedLauncherTransition;
 
-    private AnimationFactory mAnimationFactory = (t) -> { };
+    private AnimationFactory mAnimationFactory = (t, s) -> { };
 
     private boolean mWasLauncherAlreadyVisible;
 
@@ -286,6 +298,8 @@
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
 
+    private final int mSplashMainWindowShiftLength;
+
     private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
 
     private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
@@ -321,6 +335,9 @@
         mQuickSwitchScaleScrollThreshold = context.getResources().getDimension(
                 R.dimen.quick_switch_scaling_scroll_threshold);
 
+        mSplashMainWindowShiftLength = -context.getResources().getDimensionPixelSize(
+                R.dimen.starting_surface_exit_animation_window_shift_length);
+
         initAfterSubclassConstructor();
         initStateCallbacks();
     }
@@ -400,12 +417,6 @@
                 this::resetStateForAnimationCancel);
         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_FINISH_WITH_NO_END,
                 this::resetStateForAnimationCancel);
-
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
-                            | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
-                    (b) -> mRecentsView.setRunningTaskHidden(!b));
-        }
     }
 
     protected boolean onActivityInit(Boolean alreadyOnHome) {
@@ -508,7 +519,9 @@
             Runnable initAnimFactory = () -> {
                 mAnimationFactory = mActivityInterface.prepareRecentsUI(mDeviceState,
                         mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
-                maybeUpdateRecentsAttachedState(false /* animate */);
+                maybeUpdateRecentsAttachedState(
+                        false /* animate */,
+                        new ActiveGestureLog.CompoundString("on Launcher start (animate=false)"));
                 if (mGestureState.getEndTarget() != null) {
                     // Update the end target in case the gesture ended before we init.
                     mAnimationFactory.setEndTarget(mGestureState.getEndTarget());
@@ -583,14 +596,10 @@
     }
 
     private void onDeferredActivityLaunch() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mActivityInterface.switchRunningTaskViewToScreenshot(
-                    null, () -> {
-                        mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
-                    });
-        } else {
-            mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
-        }
+        mActivityInterface.switchRunningTaskViewToScreenshot(
+                null, () -> {
+                    mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
+                });
     }
 
     private void setupRecentsViewUi() {
@@ -617,7 +626,8 @@
     }
 
     private void initializeLauncherAnimationController() {
-        buildAnimationController();
+        buildAnimationController(new ActiveGestureLog.CompoundString(
+                "initializing launcher animation controller"));
 
         Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents",
                 TraceHelper.FLAG_IGNORE_BINDERS);
@@ -636,7 +646,11 @@
             @Override
             public void onMotionPauseDetected() {
                 mHasMotionEverBeenPaused = true;
-                maybeUpdateRecentsAttachedState(true/* animate */, true/* moveFocusedTask */);
+                maybeUpdateRecentsAttachedState(
+                        true/* animate */,
+                        true/* moveFocusedTask */,
+                        new ActiveGestureLog.CompoundString(
+                                "motion pause detected (animate=true)"));
                 performHapticFeedback();
             }
 
@@ -647,12 +661,13 @@
         };
     }
 
-    private void maybeUpdateRecentsAttachedState() {
-        maybeUpdateRecentsAttachedState(true /* animate */);
+    private void maybeUpdateRecentsAttachedState(ActiveGestureLog.CompoundString reason) {
+        maybeUpdateRecentsAttachedState(true /* animate */, reason.append(" (animate=true)"));
     }
 
-    private void maybeUpdateRecentsAttachedState(boolean animate) {
-        maybeUpdateRecentsAttachedState(animate, false /* moveFocusedTask */);
+    private void maybeUpdateRecentsAttachedState(
+            boolean animate, ActiveGestureLog.CompoundString reason) {
+        maybeUpdateRecentsAttachedState(animate, false /* moveFocusedTask */, reason);
     }
 
     /**
@@ -664,7 +679,8 @@
      * @param animate whether to animate when attaching RecentsView
      * @param moveFocusedTask whether to move focused task to front when attaching
      */
-    private void maybeUpdateRecentsAttachedState(boolean animate, boolean moveFocusedTask) {
+    private void maybeUpdateRecentsAttachedState(
+            boolean animate, boolean moveFocusedTask, ActiveGestureLog.CompoundString reason) {
         if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
             return;
         }
@@ -674,14 +690,25 @@
         final boolean recentsAttachedToAppWindow;
         if (mGestureState.getEndTarget() != null) {
             recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow;
+            reason.append("; gesture state end target != null (attached=")
+                    .append(Boolean.toString(recentsAttachedToAppWindow))
+                    .append(")");
         } else if (mContinuingLastGesture
                 && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
             recentsAttachedToAppWindow = true;
+            reason.append("; continuing last gesture (attached=true)");
         } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
             // The window is going away so make sure recents is always visible in this case.
             recentsAttachedToAppWindow = true;
+            reason.append("; make sure recents is always visible (attached=true)");
         } else {
             recentsAttachedToAppWindow = mHasMotionEverBeenPaused || mIsLikelyToStartNewTask;
+            reason.append(mHasMotionEverBeenPaused
+                            ? "; motion has been paused"
+                            : "; gesture is likely to start a new task")
+                    .append(" (attached=")
+                    .append(Boolean.toString(recentsAttachedToAppWindow))
+                    .append(")");
         }
         if (moveFocusedTask && !mAnimationFactory.hasRecentsEverAttachedToAppWindow()
                 && recentsAttachedToAppWindow) {
@@ -689,7 +716,8 @@
             // TaskView jumping to new position as we move the tasks.
             mRecentsView.moveFocusedTaskToFront();
         }
-        mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
+        mAnimationFactory.setRecentsAttachedToAppWindow(
+                recentsAttachedToAppWindow, animate, reason);
 
         // Reapply window transform throughout the attach animation, as the animation affects how
         // much the window is bound by overscroll (vs moving freely).
@@ -709,22 +737,29 @@
     }
 
     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
-        setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */);
+        setIsLikelyToStartNewTask(
+                isLikelyToStartNewTask,
+                true /* animate */,
+                new ActiveGestureLog.CompoundString(
+                        "setting gesture likely to start (animate=true)"));
     }
 
-    private void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask, boolean animate) {
+    private void setIsLikelyToStartNewTask(
+            boolean isLikelyToStartNewTask,
+            boolean animate,
+            ActiveGestureLog.CompoundString reason) {
         if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
             mIsLikelyToStartNewTask = isLikelyToStartNewTask;
-            maybeUpdateRecentsAttachedState(animate);
+            maybeUpdateRecentsAttachedState(animate, reason);
         }
     }
 
-    private void buildAnimationController() {
+    private void buildAnimationController(ActiveGestureLog.CompoundString reason) {
         if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
             return;
         }
         initTransitionEndpoints(mActivity.getDeviceProfile());
-        mAnimationFactory.createActivityInterface(mTransitionDragLength);
+        mAnimationFactory.createActivityInterface(mTransitionDragLength, reason);
     }
 
     /**
@@ -739,7 +774,7 @@
     @Override
     public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
         WindowInsets result = view.onApplyWindowInsets(windowInsets);
-        buildAnimationController();
+        buildAnimationController(new ActiveGestureLog.CompoundString("applying window insets"));
         // Reapply the current shift to ensure it takes new insets into account, e.g. when long
         // pressing to stash taskbar without moving the finger.
         updateFinalShift();
@@ -911,7 +946,10 @@
             });
         }
         notifyGestureStartedAsync();
-        setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
+        setIsLikelyToStartNewTask(
+                isLikelyToStartNewTask,
+                false /* animate */,
+                new ActiveGestureLog.CompoundString("on gesture started (animate=false)"));
         mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
         mGestureStarted = true;
         SystemUiProxy.INSTANCE.get(mContext).notifySwipeUpGestureStarted();
@@ -993,7 +1031,8 @@
 
     private void onSettledOnEndTarget() {
         // Fast-finish the attaching animation if it's still running.
-        maybeUpdateRecentsAttachedState(false);
+        maybeUpdateRecentsAttachedState(false, new ActiveGestureLog.CompoundString(
+                "on settled on end target (animate=false)"));
         final GestureEndTarget endTarget = mGestureState.getEndTarget();
         // Wait until the given View (if supplied) draws before resuming the last task.
         View postResumeLastTask = mActivityInterface.onSettledOnEndTarget(endTarget);
@@ -1010,8 +1049,8 @@
         switch (endTarget) {
             case HOME:
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
-                // Notify swipe-to-home (recents animation) is finished
-                SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
+                // Notify the SysUI to use fade-in animation when entering PiP
+                SystemUiProxy.INSTANCE.get(mContext).setPipAnimationTypeToAlpha();
                 break;
             case RECENTS:
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
@@ -1299,7 +1338,8 @@
     @UiThread
     private void animateToProgressInternal(float start, float end, long duration,
             Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
-        maybeUpdateRecentsAttachedState();
+        maybeUpdateRecentsAttachedState(new ActiveGestureLog.CompoundString(
+                "animate to progress internal"));
 
         // If we are transitioning to launcher, then listen for the activity to be restarted while
         // the transition is in progress
@@ -1618,7 +1658,9 @@
                     mRecentsView.post(mRecentsView::resetTaskVisuals);
                 }
                 // Make sure recents is in its final state
-                maybeUpdateRecentsAttachedState(false);
+                maybeUpdateRecentsAttachedState(
+                        false, new ActiveGestureLog.CompoundString(
+                                "setting up window animation (animate=false)"));
                 mActivityInterface.onSwipeUpToHomeComplete(mDeviceState);
             }
         });
@@ -1719,8 +1761,7 @@
     }
 
     private void invalidateHandler() {
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get() || !mActivityInterface.isInLiveTileMode()
-                || mGestureState.getEndTarget() != RECENTS) {
+        if (!mActivityInterface.isInLiveTileMode() || mGestureState.getEndTarget() != RECENTS) {
             mInputConsumerProxy.destroy();
             mTaskAnimationManager.setLiveTileCleanUpHandler(null);
         }
@@ -1765,10 +1806,6 @@
      * continued quick switch gesture, which cancels the previous handler but doesn't invalidate it.
      */
     private void resetLauncherListeners() {
-        // Reset the callback for deferred activity launches
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mActivityInterface.setOnDeferredActivityLaunchCallback(null);
-        }
         mActivity.getRootView().setOnApplyWindowInsetsListener(null);
 
         mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
@@ -1790,7 +1827,6 @@
             mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
         } else {
             final int runningTaskId = mGestureState.getRunningTaskId();
-            final boolean refreshView = !ENABLE_QUICKSTEP_LIVE_TILE.get() /* refreshView */;
             boolean finishTransitionPosted = false;
             if (mRecentsAnimationController != null) {
                 // Update the screenshot of the task
@@ -1801,14 +1837,14 @@
                                 mRecentsAnimationController.screenshotTask(runningTaskId);
                         MAIN_EXECUTOR.execute(() -> {
                             mTaskSnapshot = taskSnapshot;
-                            if (!updateThumbnail(runningTaskId, refreshView)) {
+                            if (!updateThumbnail(runningTaskId, false /* refreshView */)) {
                                 setScreenshotCapturedState();
                             }
                         });
                     });
                     return;
                 }
-                finishTransitionPosted = updateThumbnail(runningTaskId, refreshView);
+                finishTransitionPosted = updateThumbnail(runningTaskId, false /* refreshView */);
             }
             if (!finishTransitionPosted) {
                 setScreenshotCapturedState();
@@ -1846,17 +1882,10 @@
     }
 
     private void finishCurrentTransitionToRecents() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
-            if (mRecentsAnimationController != null) {
-                mRecentsAnimationController.detachNavigationBarFromApp(true);
-            }
-        } else if (!hasTargets() || mRecentsAnimationController == null) {
-            // If there are no targets or the animation not started, then there is nothing to finish
-            mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
-        } else {
-            mRecentsAnimationController.finish(true /* toRecents */,
-                    () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
+        // TODO(b/245569277#comment2): enable once isFreeformActive is implemented
+        mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+        if (mRecentsAnimationController != null) {
+            mRecentsAnimationController.detachNavigationBarFromApp(true);
         }
         ActiveGestureLog.INSTANCE.addLog(
                 /* event= */ "finishRecentsAnimation",
@@ -1920,13 +1949,11 @@
         }
         endLauncherTransitionController();
         mRecentsView.onSwipeUpAnimationSuccess();
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mTaskAnimationManager.setLiveTileCleanUpHandler(() -> {
-                mRecentsView.cleanupRemoteTargets();
-                mInputConsumerProxy.destroy();
-            });
-            mTaskAnimationManager.enableLiveTileRestartListener();
-        }
+        mTaskAnimationManager.setLiveTileCleanUpHandler(() -> {
+            mRecentsView.cleanupRemoteTargets();
+            mInputConsumerProxy.destroy();
+        });
+        mTaskAnimationManager.enableLiveTileRestartListener();
 
         SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
         doLogGesture(RECENTS, mRecentsView.getCurrentPageTaskView());
@@ -2061,16 +2088,59 @@
     public void onTasksAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) {
         if (mRecentsAnimationController != null) {
             if (handleTaskAppeared(appearedTaskTargets)) {
-                mRecentsAnimationController.finish(false /* toRecents */,
-                        null /* onFinishComplete */);
-                ActiveGestureLog.INSTANCE.addLog(
-                        /* event= */ "finishRecentsAnimation",
-                        /* extras= */ false,
-                        /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
+                Optional<RemoteAnimationTargetCompat> taskTargetOptional =
+                        Arrays.stream(appearedTaskTargets)
+                                .filter(targetCompat ->
+                                        targetCompat.taskId == mGestureState.getLastStartedTaskId())
+                                .findFirst();
+                if (!taskTargetOptional.isPresent()) {
+                    finishRecentsAnimationOnTasksAppeared();
+                    return;
+                }
+                RemoteAnimationTargetCompat taskTarget = taskTargetOptional.get();
+                TaskView taskView = mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
+                if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) {
+                    finishRecentsAnimationOnTasksAppeared();
+                    return;
+                }
+
+                ViewGroup splashView = mActivity.getDragLayer();
+
+                // When revealing the app with launcher splash screen, make the app visible
+                // and behind the splash view before the splash is animated away.
+                SyncRtSurfaceTransactionApplierCompat surfaceApplier =
+                        new SyncRtSurfaceTransactionApplierCompat(splashView);
+                ArrayList<SurfaceParams> params = new ArrayList<>();
+                for (RemoteAnimationTargetCompat target : appearedTaskTargets) {
+                    SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash);
+                    builder.withAlpha(1);
+                    builder.withLayer(-1);
+                    params.add(builder.build());
+                }
+                surfaceApplier.scheduleApply(params.toArray(new SurfaceParams[0]));
+
+                SplashScreenExitAnimationUtils.startAnimations(splashView, taskTarget.leash,
+                        mSplashMainWindowShiftLength, new TransactionPool(), new Rect(),
+                        SPLASH_ANIMATION_DURATION, SPLASH_FADE_OUT_DURATION,
+                        /* iconStartAlpha= */ 0, /* brandingStartAlpha= */ 0,
+                        SPLASH_APP_REVEAL_DELAY, SPLASH_APP_REVEAL_DURATION,
+                        new AnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                finishRecentsAnimationOnTasksAppeared();
+                            }
+                        });
             }
         }
     }
 
+    private void finishRecentsAnimationOnTasksAppeared() {
+        if (mRecentsAnimationController != null) {
+            mRecentsAnimationController.finish(false /* toRecents */, null /* onFinishComplete */);
+        }
+        ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+    }
+
     /**
      * @return The index of the TaskView in RecentsView whose taskId matches the task that will
      * resume if we finish the controller.
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
index a166553..b06b894 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -135,6 +135,13 @@
     }
 
     /**
+     * Returns whether we are currently not animating, and the animation's value matches the given.
+     */
+    public boolean isSettledOnValue(float endValue) {
+        return !isAnimating() && value == endValue;
+    }
+
+    /**
      * Returns the value we are animating to, or {@code null} if we are not currently animating.
      */
     public Float getEndValue() {
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 226b173..a343c2d 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -57,6 +57,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.views.ScrimView;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.views.RecentsView;
@@ -393,14 +394,16 @@
 
     public interface AnimationFactory {
 
-        void createActivityInterface(long transitionLength);
+        void createActivityInterface(long transitionLength, ActiveGestureLog.CompoundString reason);
 
         /**
          * @param attached Whether to show RecentsView alongside the app window. If false, recents
          *                 will be hidden by some property we can animate, e.g. alpha.
          * @param animate Whether to animate recents to/from its new attached state.
+         * @param reason Explanation for why this method is being called with the given param values
          */
-        default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
+        default void setRecentsAttachedToAppWindow(
+                boolean attached, boolean animate, ActiveGestureLog.CompoundString reason) { }
 
         default boolean isRecentsAttachedToAppWindow() {
             return false;
@@ -442,7 +445,8 @@
         }
 
         @Override
-        public void createActivityInterface(long transitionLength) {
+        public void createActivityInterface(
+                long transitionLength, ActiveGestureLog.CompoundString reason) {
             PendingAnimation pa = new PendingAnimation(transitionLength * 2);
             createBackgroundToOverviewAnim(mActivity, pa);
             AnimatorPlaybackController controller = pa.createPlaybackController();
@@ -465,13 +469,29 @@
             // (because we set the animation as the current state animation), so we reapply the
             // attached state here as well to ensure recents is shown/hidden appropriately.
             if (DisplayController.getNavigationMode(mActivity) == NavigationMode.NO_BUTTON) {
-                setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
+                setRecentsAttachedToAppWindow(
+                        mIsAttachedToWindow,
+                        false,
+                        reason.append("; reapplying the attached state (attached=")
+                                .append(Boolean.toString(mIsAttachedToWindow))
+                                .append(", animate=false)"));
             }
         }
 
         @Override
-        public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) {
+        public void setRecentsAttachedToAppWindow(
+                boolean attached, boolean animate, ActiveGestureLog.CompoundString reason) {
+            // TODO(b/244593270): remove these logs; too verbose
+            ActiveGestureLog.INSTANCE.addLog(
+                    new ActiveGestureLog.CompoundString("setRecentsAttachedToAppWindow: attached=")
+                            .append(Boolean.toString(attached))
+                            .append(", animate=")
+                            .append(Boolean.toString(animate))
+                            .append(", reason=")
+                            .append(reason));
             if (mIsAttachedToWindow == attached && animate) {
+                ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                        "setRecentsAttachedToAppWindow: exiting early"));
                 return;
             }
             mIsAttachedToWindow = attached;
@@ -488,9 +508,21 @@
                     .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
             if (!recentsView.isShown() && animate) {
                 ADJACENT_PAGE_HORIZONTAL_OFFSET.set(recentsView, fromTranslation);
+                ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                        "setRecentsAttachedToAppWindow: recents view not shown, setting ")
+                        .append("ADJACENT_PAGE_HORIZONTAL_OFFSET to ")
+                        .append(Float.toString(fromTranslation)));
             } else {
                 fromTranslation = ADJACENT_PAGE_HORIZONTAL_OFFSET.get(recentsView);
+                ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                        "setRecentsAttachedToAppWindow: updating fromTranslation to ")
+                        .append(Float.toString(fromTranslation)));
             }
+            ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                    "setRecentsAttachedToAppWindow: fromTranslation=")
+                    .append(Float.toString(fromTranslation))
+                    .append(", toTranslation=")
+                    .append(Float.toString(toTranslation)));
             if (!animate) {
                 ADJACENT_PAGE_HORIZONTAL_OFFSET.set(recentsView, toTranslation);
             } else {
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 466abbe..6e963f3 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
 import static com.android.quickstep.fallback.RecentsState.BACKGROUND_APP;
 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
@@ -120,8 +119,7 @@
     public RecentsView getVisibleRecentsView() {
         RecentsActivity activity = getCreatedActivity();
         if (activity != null) {
-            if (activity.hasBeenResumed()
-                    || (ENABLE_QUICKSTEP_LIVE_TILE.get() && isInLiveTileMode())) {
+            if (activity.hasBeenResumed() || isInLiveTileMode()) {
                 return activity.getOverviewPanel();
             }
         }
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 1127e2c..1cb17cb 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.LauncherState.QUICK_SWITCH;
 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.animation.Animator;
@@ -203,8 +202,7 @@
     private Launcher getVisibleLauncher() {
         Launcher launcher = getCreatedActivity();
         return (launcher != null) && launcher.isStarted()
-                && ((ENABLE_QUICKSTEP_LIVE_TILE.get() && isInLiveTileMode())
-                || launcher.hasBeenResumed()) ? launcher : null;
+                && (isInLiveTileMode() || launcher.hasBeenResumed()) ? launcher : null;
     }
 
     @Override
@@ -213,7 +211,7 @@
         if (launcher == null) {
             return false;
         }
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isInLiveTileMode()) {
+        if (isInLiveTileMode()) {
             RecentsView recentsView = getVisibleRecentsView();
             if (recentsView == null) {
                 return false;
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 36ca993..d1533f0 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -84,7 +84,8 @@
 
         final View workspaceView = findWorkspaceView(launchCookies,
                 mRecentsView.getRunningTaskView());
-        boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow();
+        boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow()
+                && workspaceView.getHeight() > 0;
 
         mActivity.getRootView().setForceHideBackArrow(true);
         if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 6b616b1..7bcc661 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -17,6 +17,8 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
+import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
@@ -30,6 +32,7 @@
 
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.wm.shell.recents.IRecentTasksListener;
@@ -253,8 +256,9 @@
         };
 
         TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
+
         for (GroupedRecentTaskInfo rawTask : rawTasks) {
-            if (rawTask.getType() == GroupedRecentTaskInfo.TYPE_FREEFORM) {
+            if (DESKTOP_MODE_SUPPORTED && rawTask.getType() == TYPE_FREEFORM) {
                 GroupTask desktopTask = createDesktopTask(rawTask);
                 allTasks.add(desktopTask);
                 continue;
@@ -284,14 +288,18 @@
         return allTasks;
     }
 
-    private GroupTask createDesktopTask(GroupedRecentTaskInfo taskInfo) {
-        // TODO(b/244348395): create a subclass of GroupTask for desktop tile
-        // We need a single task information as the primary task. Use the first task
-        Task.TaskKey key = new Task.TaskKey(taskInfo.getTaskInfo1());
-        Task task = new Task(key);
-        task.desktopTile = true;
-        task.topActivity = key.sourceComponent;
-        return new GroupTask(task, null, null);
+    private DesktopTask createDesktopTask(GroupedRecentTaskInfo recentTaskInfo) {
+        ArrayList<Task> tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size());
+        for (ActivityManager.RecentTaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
+            Task.TaskKey key = new Task.TaskKey(taskInfo);
+            Task task = Task.from(key, taskInfo, false);
+            task.setLastSnapshotData(taskInfo);
+            task.positionInParent = taskInfo.positionInParent;
+            task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
+            // TODO(b/244348395): tasks should be sorted from oldest to most recently used
+            tasks.add(task);
+        }
+        return new DesktopTask(tasks);
     }
 
     private SplitConfigurationOptions.SplitBounds convertSplitBounds(
@@ -306,7 +314,7 @@
     private ArrayList<GroupTask> copyOf(ArrayList<GroupTask> tasks) {
         ArrayList<GroupTask> newTasks = new ArrayList<>();
         for (int i = 0; i < tasks.size(); i++) {
-            newTasks.add(new GroupTask(tasks.get(i)));
+            newTasks.add(tasks.get(i).copy());
         }
         return newTasks;
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index c879494..4f5e216 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -18,7 +18,6 @@
 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
 import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
@@ -393,13 +392,9 @@
     }
 
     public void startHome() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            RecentsView recentsView = getOverviewPanel();
-            recentsView.switchToScreenshot(() -> recentsView.finishRecentsAnimation(true,
-                    this::startHomeInternal));
-        } else {
-            startHomeInternal();
-        }
+        RecentsView recentsView = getOverviewPanel();
+        recentsView.switchToScreenshot(() -> recentsView.finishRecentsAnimation(true,
+                this::startHomeInternal));
     }
 
     private void startHomeInternal() {
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 3b2df31..129b88e 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -187,7 +187,7 @@
         linkToDeath();
         // re-attach the listeners once missing due to setProxy has not been initialized yet.
         if (mPipAnimationListener != null && mPip != null) {
-            setPinnedStackAnimationListener(mPipAnimationListener);
+            setPipAnimationListener(mPipAnimationListener);
         }
         if (mSplitScreenListener != null && mSplitScreen != null) {
             registerSplitScreenListener(mSplitScreenListener);
@@ -357,20 +357,6 @@
         }
     }
 
-    /**
-     * Notifies that swipe-to-home action is finished.
-     */
-    @Override
-    public void notifySwipeToHomeFinished() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.notifySwipeToHomeFinished();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call notifySwipeToHomeFinished", e);
-            }
-        }
-    }
-
     @Override
     public void notifyPrioritizedRotation(int rotation) {
         if (mSystemUiProxy != null) {
@@ -475,12 +461,12 @@
     }
 
     /**
-     * Sets listener to get pinned stack animation callbacks.
+     * Sets listener to get pip animation callbacks.
      */
-    public void setPinnedStackAnimationListener(IPipAnimationListener listener) {
+    public void setPipAnimationListener(IPipAnimationListener listener) {
         if (mPip != null) {
             try {
-                mPip.setPinnedStackAnimationListener(listener);
+                mPip.setPipAnimationListener(listener);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
             }
@@ -522,6 +508,19 @@
         }
     }
 
+    /**
+     * Sets the next pip animation type to be the alpha animation.
+     */
+    public void setPipAnimationTypeToAlpha() {
+        if (mPip != null) {
+            try {
+                mPip.setPipAnimationTypeToAlpha();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call setPipAnimationTypeToAlpha", e);
+            }
+        }
+    }
+
     //
     // Splitscreen
     //
@@ -549,15 +548,43 @@
     }
 
     /** Start multiple tasks in split-screen simultaneously. */
-    public void startTasks(int mainTaskId, Bundle mainOptions, int sideTaskId, Bundle sideOptions,
-            @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
+    public void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2,
+            @SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
             RemoteTransitionCompat remoteTransition, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
-                mSplitScreen.startTasks(mainTaskId, mainOptions, sideTaskId, sideOptions,
-                        sidePosition, splitRatio, remoteTransition.getTransition(), instanceId);
+                mSplitScreen.startTasks(taskId1, options1, taskId2, options2, splitPosition,
+                        splitRatio, remoteTransition.getTransition(), instanceId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startTask");
+                Log.w(TAG, "Failed call startTasks");
+            }
+        }
+    }
+
+    public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
+            Bundle options1, int taskId, Bundle options2,
+            @SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
+            RemoteTransitionCompat remoteTransition, InstanceId instanceId) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSplitScreen.startIntentAndTask(pendingIntent, fillInIntent, options1,
+                        taskId, options2, splitPosition, splitRatio,
+                        remoteTransition.getTransition(), instanceId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call startIntentAndTask");
+            }
+        }
+    }
+
+    public void startShortcutAndTask(ShortcutInfo shortcutInfo, Bundle options1, int taskId,
+            Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
+            float splitRatio, RemoteTransitionCompat remoteTransition, InstanceId instanceId) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSplitScreen.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
+                        splitPosition, splitRatio, remoteTransition.getTransition(), instanceId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call startShortcutAndTask");
             }
         }
     }
@@ -565,13 +592,13 @@
     /**
      * Start multiple tasks in split-screen simultaneously.
      */
-    public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId,
-            Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition,
+    public void startTasksWithLegacyTransition(int taskId1, Bundle options1, int taskId2,
+            Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
             float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
-                mSplitScreen.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId,
-                        sideOptions, sidePosition, splitRatio, adapter, instanceId);
+                mSplitScreen.startTasksWithLegacyTransition(taskId1, options1, taskId2, options2,
+                        splitPosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startTasksWithLegacyTransition");
             }
@@ -579,28 +606,26 @@
     }
 
     public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
-            Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
-            @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
+            Intent fillInIntent, Bundle options1, int taskId, Bundle options2,
+            @SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
-                        taskId, mainOptions, sideOptions, sidePosition, splitRatio, adapter,
-                        instanceId);
+                        options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startIntentAndTaskWithLegacyTransition");
             }
         }
     }
 
-    public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, int taskId,
-            Bundle mainOptions, Bundle sideOptions,
-            @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
-            RemoteAnimationAdapter adapter, InstanceId instanceId) {
+    public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, Bundle options1,
+            int taskId, Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
+            float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
-                mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, taskId,
-                        mainOptions, sideOptions, sidePosition, splitRatio, adapter, instanceId);
+                mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, options1,
+                        taskId, options2, splitPosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startShortcutAndTaskWithLegacyTransition");
             }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index b9a1b06..7f16565 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
@@ -72,7 +71,7 @@
                 return;
             }
             BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode()
+            if (activityInterface.isInLiveTileMode()
                     && activityInterface.getCreatedActivity() != null) {
                 RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
                 if (recentsView != null) {
@@ -171,7 +170,7 @@
                                 .map(RemoteAnimationTargetCompat::unwrap)
                                 .toArray(RemoteAnimationTarget[]::new));
 
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode()
+                if (activityInterface.isInLiveTileMode()
                         && activityInterface.getCreatedActivity() != null) {
                     RecentsView recentsView =
                             activityInterface.getCreatedActivity().getOverviewPanel();
@@ -204,7 +203,7 @@
 
             @Override
             public boolean onSwitchToScreenshot(Runnable onFinished) {
-                if (!ENABLE_QUICKSTEP_LIVE_TILE.get() || !activityInterface.isInLiveTileMode()
+                if (!activityInterface.isInLiveTileMode()
                         || activityInterface.getCreatedActivity() == null) {
                     // No need to switch since tile is already a screenshot.
                     onFinished.run();
@@ -266,7 +265,7 @@
             return;
         }
         BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode()
+        if (activityInterface.isInLiveTileMode()
                 && activityInterface.getCreatedActivity() != null) {
             RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
             if (recentsView != null) {
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index e10cab6..d40f2ae 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -18,7 +18,6 @@
 
 import static android.view.Surface.ROTATION_0;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL;
 import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
 
@@ -174,14 +173,10 @@
          * @param callback callback to run, after switching to screenshot
          */
         public void endLiveTileMode(@NonNull Runnable callback) {
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                RecentsView recentsView = mThumbnailView.getTaskView().getRecentsView();
-                recentsView.switchToScreenshot(
-                        () -> recentsView.finishRecentsAnimation(true /* toRecents */,
-                                false /* shouldPip */, callback));
-            } else {
-                callback.run();
-            }
+            RecentsView recentsView = mThumbnailView.getTaskView().getRecentsView();
+            recentsView.switchToScreenshot(
+                    () -> recentsView.finishRecentsAnimation(true /* toRecents */,
+                            false /* shouldPip */, callback));
         }
 
         /**
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index a96524e..df80e2f 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep;
 
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -35,7 +34,6 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.statehandlers.DepthController.STATE_DEPTH;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
@@ -304,9 +302,15 @@
             // to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
             //    Mt K(0)` K(t) Mt`
             TaskThumbnailView[] thumbnails = v.getThumbnails();
-            Matrix[] mt = new Matrix[simulatorCopies.length];
-            Matrix[] mti = new Matrix[simulatorCopies.length];
-            for (int i = 0; i < thumbnails.length; i++) {
+
+            // In case simulator copies and thumbnail size do no match, ensure we get the lesser.
+            // This ensures we do not create arrays with empty elements or attempt to references
+            // indexes out of array bounds.
+            final int matrixSize = Math.min(simulatorCopies.length, thumbnails.length);
+
+            Matrix[] mt = new Matrix[matrixSize];
+            Matrix[] mti = new Matrix[matrixSize];
+            for (int i = 0; i < matrixSize; i++) {
                 TaskThumbnailView ttv = thumbnails[i];
                 RectF localBounds = new RectF(0, 0,  ttv.getWidth(), ttv.getHeight());
                 float[] tvBoundsMapped = new float[]{0, 0,  ttv.getWidth(), ttv.getHeight()};
@@ -323,14 +327,14 @@
                 mti[i] = localMti;
             }
 
-            Matrix[] k0i = new Matrix[simulatorCopies.length];
-            for (int i = 0; i < simulatorCopies.length; i++) {
+            Matrix[] k0i = new Matrix[matrixSize];
+            for (int i = 0; i < matrixSize; i++) {
                 k0i[i] = new Matrix();
                 simulatorCopies[i].getTaskViewSimulator().getCurrentMatrix().invert(k0i[i]);
             }
             Matrix animationMatrix = new Matrix();
             out.addOnFrameCallback(() -> {
-                for (int i = 0; i < simulatorCopies.length; i++) {
+                for (int i = 0; i < matrixSize; i++) {
                     animationMatrix.set(mt[i]);
                     animationMatrix.postConcat(k0i[i]);
                     animationMatrix.postConcat(simulatorCopies[i]
@@ -426,8 +430,10 @@
         TransitionInfo.Change splitRoot2 = null;
         for (int i = 0; i < transitionInfo.getChanges().size(); ++i) {
             final TransitionInfo.Change change = transitionInfo.getChanges().get(i);
-            final int taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1;
+            if (change.getTaskInfo() == null) continue;
+            final int taskId = change.getTaskInfo().taskId;
             final int mode = change.getMode();
+
             // Find the target tasks' root tasks since those are the split stages that need to
             // be animated (the tasks themselves are children and thus inherit animation).
             if (taskId == initialTaskId || taskId == secondTaskId) {
@@ -440,7 +446,7 @@
                             + "root of " + taskId + " is already visible or has broken hierarchy.");
                 }
             }
-            if (taskId == initialTaskId && initialTaskId != INVALID_TASK_ID) {
+            if (taskId == initialTaskId) {
                 splitRoot1 = transitionInfo.getChange(change.getParent());
             }
             if (taskId == secondTaskId) {
@@ -637,7 +643,7 @@
             };
         }
         pa.add(launcherAnim);
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1) {
+        if (recentsView.getRunningTaskIndex() != -1) {
             pa.addOnFrameCallback(recentsView::redrawLiveTile);
         }
         anim.play(pa.buildAnim());
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 1999701..1452c8f 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -20,25 +20,24 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.config.FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_DESKTOP_MODE;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_FLOATING_TASKS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
 
 import android.annotation.TargetApi;
 import android.app.PendingIntent;
@@ -183,7 +182,7 @@
                     ISysuiUnlockAnimationController.Stub.asInterface(
                             bundle.getBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER));
             IRecentTasks recentTasks = IRecentTasks.Stub.asInterface(
-                    bundle.getBinder(KEY_EXTRA_RECENT_TASKS));
+                    bundle.getBinder(KEY_EXTRA_SHELL_RECENT_TASKS));
             IBackAnimation backAnimation = IBackAnimation.Stub.asInterface(
                     bundle.getBinder(KEY_EXTRA_SHELL_BACK_ANIMATION));
             IDesktopMode desktopMode = IDesktopMode.Stub.asInterface(
@@ -955,8 +954,7 @@
         boolean launcherResumedThroughShellTransition =
                 gestureState.getActivityInterface().isResumed()
                         && !previousGestureState.isRecentsAnimationRunning();
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()
-                && gestureState.getActivityInterface().isInLiveTileMode()) {
+        if (gestureState.getActivityInterface().isInLiveTileMode()) {
             return createOverviewInputConsumer(
                     previousGestureState,
                     gestureState,
@@ -1044,8 +1042,7 @@
                 previousGestureState.isRunningAnimationToLauncher();
         boolean forcingOverviewInputConsumer =
                 ASSISTANT_GIVES_LAUNCHER_FOCUS.get() && forceOverviewInputConsumer;
-        boolean isInLiveTileMode = ENABLE_QUICKSTEP_LIVE_TILE.get()
-                && gestureState.getActivityInterface().isInLiveTileMode();
+        boolean isInLiveTileMode = gestureState.getActivityInterface().isInLiveTileMode();
         reasonString.append(SUBSTRING_PREFIX)
                 .append(hasWindowFocus
                         ? "activity has window focus"
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
index eca61bb..db4927a 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
@@ -15,8 +15,6 @@
  */
 package com.android.quickstep.fallback;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-
 import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
 import com.android.quickstep.RecentsActivity;
 
@@ -28,8 +26,7 @@
 
     @Override
     protected boolean isRecentsInteractive() {
-        return mActivity.hasWindowFocus() || (ENABLE_QUICKSTEP_LIVE_TILE.get()
-                && mActivity.getStateManager().getState().hasLiveTile());
+        return mActivity.hasWindowFocus() || mActivity.getStateManager().getState().hasLiveTile();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 6f35928..64165b6 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.inputconsumers;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.media.AudioManager;
@@ -100,27 +99,23 @@
 
     @Override
     public void onHoverEvent(MotionEvent ev) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mActivity.dispatchGenericMotionEvent(ev);
-        }
+        mActivity.dispatchGenericMotionEvent(ev);
     }
 
     @Override
     public void onKeyEvent(KeyEvent ev) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            switch (ev.getKeyCode()) {
-                case KeyEvent.KEYCODE_VOLUME_DOWN:
-                case KeyEvent.KEYCODE_VOLUME_UP:
-                case KeyEvent.KEYCODE_VOLUME_MUTE:
-                    MediaSessionManager mgr = mActivity.getSystemService(MediaSessionManager.class);
-                    mgr.dispatchVolumeKeyEventAsSystemService(ev,
-                            AudioManager.USE_DEFAULT_STREAM_TYPE);
-                    break;
-                default:
-                    break;
-            }
-            mActivity.dispatchKeyEvent(ev);
+        switch (ev.getKeyCode()) {
+            case KeyEvent.KEYCODE_VOLUME_DOWN:
+            case KeyEvent.KEYCODE_VOLUME_UP:
+            case KeyEvent.KEYCODE_VOLUME_MUTE:
+                MediaSessionManager mgr = mActivity.getSystemService(MediaSessionManager.class);
+                mgr.dispatchVolumeKeyEventAsSystemService(ev,
+                        AudioManager.USE_DEFAULT_STREAM_TYPE);
+                break;
+            default:
+                break;
         }
+        mActivity.dispatchKeyEvent(ev);
     }
 }
 
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 37a28e5..a9ff0fb 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -190,7 +190,8 @@
                 getCardinality(info), // cardinality = 16;
                 info.getWidget().getSpanX(), // span_x = 17 [default = 1];
                 info.getWidget().getSpanY(), // span_y = 18 [default = 1];
-                getAttributes(info) /* attributes */
+                getAttributes(info) /* attributes = 19 [(log_mode) = MODE_BYTES] */,
+                false /* is_kids_mode = 20 */
         );
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
index 143042f..2a513ee 100644
--- a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
@@ -16,12 +16,14 @@
 package com.android.quickstep.util;
 
 import android.annotation.CallSuper;
+import android.view.Surface.Rotation;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -32,15 +34,20 @@
 public abstract class BaseUnfoldMoveFromCenterAnimator implements TransitionProgressListener {
 
     private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimation;
+    private final RotationChangeProvider mRotationChangeProvider;
 
     private final Map<ViewGroup, Boolean> mOriginalClipToPadding = new HashMap<>();
     private final Map<ViewGroup, Boolean> mOriginalClipChildren = new HashMap<>();
 
+    private final UnfoldMoveFromCenterRotationListener mRotationListener =
+            new UnfoldMoveFromCenterRotationListener();
     private boolean mAnimationInProgress = false;
 
-    public BaseUnfoldMoveFromCenterAnimator(WindowManager windowManager) {
+    public BaseUnfoldMoveFromCenterAnimator(WindowManager windowManager,
+            RotationChangeProvider rotationChangeProvider) {
         mMoveFromCenterAnimation = new UnfoldMoveFromCenterAnimator(windowManager,
                 new LauncherViewsMoveFromCenterTranslationApplier());
+        mRotationChangeProvider = rotationChangeProvider;
     }
 
     @CallSuper
@@ -50,6 +57,7 @@
         mMoveFromCenterAnimation.updateDisplayProperties();
         onPrepareViewsForAnimation();
         onTransitionProgress(0f);
+        mRotationChangeProvider.addCallback(mRotationListener);
     }
 
     @CallSuper
@@ -62,6 +70,7 @@
     @Override
     public void onTransitionFinished() {
         mAnimationInProgress = false;
+        mRotationChangeProvider.removeCallback(mRotationListener);
         mMoveFromCenterAnimation.onTransitionFinished();
         clearRegisteredViews();
     }
@@ -109,4 +118,14 @@
             view.setClipChildren(originalClipChildren);
         }
     }
+
+    private class UnfoldMoveFromCenterRotationListener implements
+            RotationChangeProvider.RotationListener {
+
+        @Override
+        public void onRotationChanged(@Rotation int newRotation) {
+            mMoveFromCenterAnimation.updateDisplayProperties(newRotation);
+            updateRegisteredViewsIfNeeded();
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java
new file mode 100644
index 0000000..433d23f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
+
+import java.util.ArrayList;
+
+/**
+ * A {@link Task} container that can contain N number of tasks that are part of the desktop in
+ * recent tasks list.
+ */
+public class DesktopTask extends GroupTask {
+
+    public ArrayList<Task> tasks;
+
+    public DesktopTask(ArrayList<Task> tasks) {
+        super(tasks.get(0), null, null, TaskView.Type.DESKTOP);
+        this.tasks = tasks;
+    }
+
+    @Override
+    public boolean containsTask(int taskId) {
+        for (Task task : tasks) {
+            if (task.key.id == taskId) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean hasMultipleTasks() {
+        return true;
+    }
+
+    @Override
+    public DesktopTask copy() {
+        return new DesktopTask(tasks);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java
index f30d00c..2be4f0a 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.java
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.java
@@ -20,6 +20,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
+import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
 
 /**
@@ -27,24 +28,25 @@
  * are represented as an app-pair in the recents task list.
  */
 public class GroupTask {
-    public @NonNull Task task1;
-    public @Nullable Task task2;
-    public @Nullable
-    SplitBounds mSplitBounds;
+    @NonNull
+    public final Task task1;
+    @Nullable
+    public final Task task2;
+    @Nullable
+    public final SplitBounds mSplitBounds;
+    @TaskView.Type
+    public final int taskViewType;
 
-    public GroupTask(@NonNull Task t1, @Nullable Task t2,
-            @Nullable SplitBounds splitBounds) {
+    public GroupTask(@NonNull Task t1, @Nullable Task t2, @Nullable SplitBounds splitBounds) {
+        this(t1, t2, splitBounds, t2 != null ? TaskView.Type.GROUPED : TaskView.Type.SINGLE);
+    }
+
+    protected GroupTask(@NonNull Task t1, @Nullable Task t2, @Nullable SplitBounds splitBounds,
+            @TaskView.Type int taskViewType) {
         task1 = t1;
         task2 = t2;
         mSplitBounds = splitBounds;
-    }
-
-    public GroupTask(@NonNull GroupTask group) {
-        task1 = new Task(group.task1);
-        task2 = group.task2 != null
-                ? new Task(group.task2)
-                : null;
-        mSplitBounds = group.mSplitBounds;
+        this.taskViewType = taskViewType;
     }
 
     public boolean containsTask(int taskId) {
@@ -54,4 +56,14 @@
     public boolean hasMultipleTasks() {
         return task2 != null;
     }
+
+    /**
+     * Create a copy of this instance
+     */
+    public GroupTask copy() {
+        return new GroupTask(
+                new Task(task1),
+                task2 != null ? new Task(task2) : null,
+                mSplitBounds);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
index 97be437..f12074b 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -24,7 +24,6 @@
 import android.util.FloatProperty;
 import android.util.MathUtils;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 
 import androidx.core.view.OneShotPreDrawListener;
 
@@ -34,6 +33,7 @@
 import com.android.launcher3.util.HorizontalInsettableView;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
 
@@ -62,16 +62,17 @@
     public LauncherUnfoldAnimationController(
             Launcher launcher,
             WindowManager windowManager,
-            UnfoldTransitionProgressProvider unfoldTransitionProgressProvider) {
+            UnfoldTransitionProgressProvider unfoldTransitionProgressProvider,
+            RotationChangeProvider rotationChangeProvider) {
         mLauncher = launcher;
         mProgressProvider = new ScopedUnfoldTransitionProgressProvider(
                 unfoldTransitionProgressProvider);
         mUnfoldMoveFromCenterHotseatAnimator = new UnfoldMoveFromCenterHotseatAnimator(launcher,
-                windowManager);
+                windowManager, rotationChangeProvider);
         mUnfoldMoveFromCenterWorkspaceAnimator = new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
-                windowManager);
+                windowManager, rotationChangeProvider);
         mNaturalOrientationProgressProvider = new NaturalRotationUnfoldProgressProvider(launcher,
-                WindowManagerGlobal.getWindowManagerService(), mProgressProvider);
+                rotationChangeProvider, mProgressProvider);
         mNaturalOrientationProgressProvider.init();
 
         // Animated in all orientations
diff --git a/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
index 9e7351a..3d9e09e 100644
--- a/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
+++ b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
@@ -16,10 +16,6 @@
 
 package com.android.quickstep.util;
 
-import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
-
-import android.view.animation.Interpolator;
-
 /**
  * Timings for the OverviewSplitSelect > confirmed animation on phones.
  */
@@ -33,8 +29,4 @@
     public int getStagedRectSlideEnd() { return 333; }
 
     public int getDuration() { return PHONE_CONFIRM_DURATION; }
-    public Interpolator getStagedRectXInterpolator() { return EMPHASIZED; }
-    public Interpolator getStagedRectYInterpolator() { return EMPHASIZED; }
-    public Interpolator getStagedRectScaleXInterpolator() { return EMPHASIZED; }
-    public Interpolator getStagedRectScaleYInterpolator() { return EMPHASIZED; }
 }
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index efbe783..f07f990 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -22,8 +22,6 @@
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 
 import android.annotation.NonNull;
 import android.app.ActivityOptions;
@@ -216,22 +214,36 @@
             @Nullable InstanceId shellInstanceId) {
         TestLogging.recordEvent(
                 TestProtocol.SEQUENCE_MAIN, "launchSplitTasks");
-        // Assume initial task is for top/left part of screen
-        final int[] taskIds = stagePosition == STAGE_POSITION_TOP_OR_LEFT
-                ? new int[]{taskId1, taskId2}
-                : new int[]{taskId2, taskId1};
+        final ActivityOptions options1 = ActivityOptions.makeBasic();
+        if (freezeTaskList) {
+            options1.setFreezeRecentTasksReordering();
+        }
         if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
-            RemoteSplitLaunchTransitionRunner animationRunner =
+            final RemoteSplitLaunchTransitionRunner animationRunner =
                     new RemoteSplitLaunchTransitionRunner(taskId1, taskPendingIntent, taskId2,
                             callback);
-            mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],
-                    null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, splitRatio,
-                    new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR,
-                            ActivityThread.currentActivityThread().getApplicationThread()),
-                    shellInstanceId);
-            // TODO(b/237635859): handle intent/shortcut + task with shell transition
+            final RemoteTransitionCompat remoteTransition = new RemoteTransitionCompat(
+                    animationRunner, MAIN_EXECUTOR,
+                    ActivityThread.currentActivityThread().getApplicationThread());
+            if (taskPendingIntent == null) {
+                mSystemUiProxy.startTasks(taskId1, options1.toBundle(), taskId2,
+                        null /* options2 */, stagePosition, splitRatio, remoteTransition,
+                        shellInstanceId);
+            } else {
+                final ShortcutInfo shortcutInfo = getShortcutInfo(mInitialTaskIntent,
+                        taskPendingIntent.getCreatorUserHandle());
+                if (shortcutInfo != null) {
+                    mSystemUiProxy.startShortcutAndTask(shortcutInfo,
+                            options1.toBundle(), taskId2, null /* options2 */, stagePosition,
+                            splitRatio, remoteTransition, shellInstanceId);
+                } else {
+                    mSystemUiProxy.startIntentAndTask(taskPendingIntent,
+                            fillInIntent, options1.toBundle(), taskId2, null /* options2 */,
+                            stagePosition, splitRatio, remoteTransition, shellInstanceId);
+                }
+            }
         } else {
-            RemoteSplitLaunchAnimationRunner animationRunner =
+            final RemoteSplitLaunchAnimationRunner animationRunner =
                     new RemoteSplitLaunchAnimationRunner(taskId1, taskPendingIntent, taskId2,
                             callback);
             final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
@@ -239,24 +251,20 @@
                     300, 150,
                     ActivityThread.currentActivityThread().getApplicationThread());
 
-            ActivityOptions mainOpts = ActivityOptions.makeBasic();
-            if (freezeTaskList) {
-                mainOpts.setFreezeRecentTasksReordering();
-            }
             if (taskPendingIntent == null) {
-                mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(),
-                        taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
-                        splitRatio, adapter, shellInstanceId);
+                mSystemUiProxy.startTasksWithLegacyTransition(taskId1, options1.toBundle(),
+                        taskId2, null /* options2 */, stagePosition, splitRatio, adapter,
+                        shellInstanceId);
             } else {
                 final ShortcutInfo shortcutInfo = getShortcutInfo(mInitialTaskIntent,
                         taskPendingIntent.getCreatorUserHandle());
                 if (shortcutInfo != null) {
-                    mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(shortcutInfo, taskId2,
-                            mainOpts.toBundle(), null /* sideOptions */, stagePosition, splitRatio,
-                            adapter, shellInstanceId);
+                    mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
+                            options1.toBundle(), taskId2, null /* options2 */, stagePosition,
+                            splitRatio, adapter, shellInstanceId);
                 } else {
                     mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent,
-                            fillInIntent, taskId2, mainOpts.toBundle(), null /* sideOptions */,
+                            fillInIntent, options1.toBundle(), taskId2, null /* options2 */,
                             stagePosition, splitRatio, adapter, shellInstanceId);
                 }
             }
diff --git a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
index 3026e98..f5b00cf 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
@@ -16,6 +16,8 @@
 
 package com.android.quickstep.util;
 
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+
 import android.view.animation.Interpolator;
 
 /**
@@ -33,12 +35,12 @@
     // Common timings
     public int getInstructionsFadeStart() { return 0; }
     public int getInstructionsFadeEnd() { return 67; }
+    public Interpolator getStagedRectXInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectYInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectScaleXInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectScaleYInterpolator() { return EMPHASIZED; }
 
     abstract public int getDuration();
-    abstract public Interpolator getStagedRectXInterpolator();
-    abstract public Interpolator getStagedRectYInterpolator();
-    abstract public Interpolator getStagedRectScaleXInterpolator();
-    abstract public Interpolator getStagedRectScaleYInterpolator();
 
     public float getInstructionsFadeStartOffset() {
         return (float) getInstructionsFadeStart() / getDuration();
diff --git a/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
index 3ea8466..580cc99 100644
--- a/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
+++ b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
@@ -16,10 +16,6 @@
 
 package com.android.quickstep.util;
 
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-
-import android.view.animation.Interpolator;
-
 /**
  * Timings for the OverviewSplitSelect > confirmed animation on tablets.
  */
@@ -33,8 +29,4 @@
     public int getStagedRectSlideEnd() { return 383; }
 
     public int getDuration() { return TABLET_CONFIRM_DURATION; }
-    public Interpolator getStagedRectXInterpolator() { return LINEAR; }
-    public Interpolator getStagedRectYInterpolator() { return LINEAR; }
-    public Interpolator getStagedRectScaleXInterpolator() { return LINEAR; }
-    public Interpolator getStagedRectScaleYInterpolator() { return LINEAR; }
 }
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 1a026fc..c03aa3f 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -17,7 +17,6 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -47,9 +46,9 @@
 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;
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
 
@@ -318,9 +317,9 @@
             // mIsRecentsRtl is the inverse of TaskView RTL.
             boolean isRtlEnabled = !mIsRecentsRtl;
             mPositionHelper.updateThumbnailMatrix(
-                    mThumbnailPosition, mThumbnailData,
-                    mTaskRect.width(), mTaskRect.height(),
-                    mDp, mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
+                    mThumbnailPosition, mThumbnailData, mTaskRect.width(), mTaskRect.height(),
+                    mDp.widthPx, mDp.taskbarSize, mDp.isTablet,
+                    mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
             mPositionHelper.getMatrix().invert(mInversePositionMatrix);
             if (DEBUG) {
                 Log.d(TAG, " taskRect: " + mTaskRect);
@@ -393,7 +392,7 @@
                 .withCornerRadius(getCurrentCornerRadius());
 
         // If mDrawsBelowRecents is unset, no reordering will be enforced.
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mDrawsBelowRecents != null) {
+        if (mDrawsBelowRecents != null) {
             // In legacy transitions, the animation leashes remain in same hierarchy in the
             // TaskDisplayArea, so we don't want to bump the layer too high otherwise it will
             // conflict with layers that WM core positions (ie. the input consumers).  For shell
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
index dc97dd6..01a997a 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
@@ -21,6 +21,7 @@
 
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 
 /**
  * Animation that moves hotseat icons from center to the sides (final position)
@@ -29,8 +30,9 @@
 
     private final Launcher mLauncher;
 
-    public UnfoldMoveFromCenterHotseatAnimator(Launcher launcher, WindowManager windowManager) {
-        super(windowManager);
+    public UnfoldMoveFromCenterHotseatAnimator(Launcher launcher, WindowManager windowManager,
+            RotationChangeProvider rotationChangeProvider) {
+        super(windowManager, rotationChangeProvider);
         mLauncher = launcher;
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
index 354d157..95a4b8f 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
@@ -22,6 +22,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 
 /**
  * Animation that moves launcher icons and widgets from center to the sides (final position)
@@ -30,8 +31,9 @@
 
     private final Launcher mLauncher;
 
-    public UnfoldMoveFromCenterWorkspaceAnimator(Launcher launcher, WindowManager windowManager) {
-        super(windowManager);
+    public UnfoldMoveFromCenterWorkspaceAnimator(Launcher launcher, WindowManager windowManager,
+            RotationChangeProvider rotationChangeProvider) {
+        super(windowManager, rotationChangeProvider);
         mLauncher = launcher;
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
new file mode 100644
index 0000000..9874f96
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views;
+
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RoundRectShape;
+import android.os.SystemProperties;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.RunnableList;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskThumbnailCache;
+import com.android.quickstep.util.CancellableTask;
+import com.android.quickstep.util.RecentsOrientedState;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * TaskView that contains all tasks that are part of the desktop.
+ */
+// TODO(b/249371338): TaskView needs to be refactored to have better support for N tasks.
+public class DesktopTaskView extends TaskView {
+
+    /** Flag to indicate whether desktop mode is available on the device */
+    public static final boolean DESKTOP_MODE_SUPPORTED = SystemProperties.getBoolean(
+            "persist.wm.debug.desktop_mode", false);
+
+    private static final String TAG = DesktopTaskView.class.getSimpleName();
+
+    private static final boolean DEBUG = true;
+
+    private List<Task> mTasks;
+
+    private final ArrayList<TaskThumbnailView> mSnapshotViews = new ArrayList<>();
+
+    /** Maps {@code taskIds} to corresponding {@link TaskThumbnailView}s */
+    private final SparseArray<TaskThumbnailView> mSnapshotViewMap = new SparseArray<>();
+
+    private final ArrayList<CancellableTask<?>> mPendingThumbnailRequests = new ArrayList<>();
+
+    public DesktopTaskView(Context context) {
+        this(context, null);
+    }
+
+    public DesktopTaskView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DesktopTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        float[] outerRadii = new float[8];
+        Arrays.fill(outerRadii, getTaskCornerRadius());
+        RoundRectShape shape = new RoundRectShape(outerRadii, null, null);
+        ShapeDrawable background = new ShapeDrawable(shape);
+        background.setTint(getResources().getColor(android.R.color.system_neutral2_300));
+        // TODO(b/244348395): this should be wallpaper
+        setBackground(background);
+
+        mSnapshotViews.add(mSnapshotView);
+    }
+
+    @Override
+    public void bind(Task task, RecentsOrientedState orientedState) {
+        bind(Collections.singletonList(task), orientedState);
+    }
+
+    /**
+     * Updates this desktop task to the gives task list defined in {@code tasks}
+     */
+    public void bind(List<Task> tasks, RecentsOrientedState orientedState) {
+        if (DEBUG) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("bind tasks=").append(tasks.size()).append("\n");
+            for (Task task : tasks) {
+                sb.append(" key=").append(task.key).append("\n");
+            }
+            Log.d(TAG, sb.toString());
+        }
+        if (tasks.isEmpty()) {
+            return;
+        }
+        cancelPendingLoadTasks();
+
+        mTasks = tasks;
+        mSnapshotViewMap.clear();
+
+        // Ensure there are equal number of snapshot views and tasks.
+        // More tasks than views, add views. More views than tasks, remove views.
+        // TODO(b/251586230): use a ViewPool for creating TaskThumbnailViews
+        if (mSnapshotViews.size() > mTasks.size()) {
+            int diff = mSnapshotViews.size() - mTasks.size();
+            for (int i = 0; i < diff; i++) {
+                TaskThumbnailView snapshotView = mSnapshotViews.remove(0);
+                removeView(snapshotView);
+            }
+        } else if (mSnapshotViews.size() < mTasks.size()) {
+            int diff = mTasks.size() - mSnapshotViews.size();
+            for (int i = 0; i < diff; i++) {
+                TaskThumbnailView snapshotView = new TaskThumbnailView(getContext());
+                mSnapshotViews.add(snapshotView);
+                addView(snapshotView, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+            }
+        }
+
+        for (int i = 0; i < mTasks.size(); i++) {
+            Task task = mTasks.get(i);
+            TaskThumbnailView snapshotView = mSnapshotViews.get(i);
+            snapshotView.bind(task);
+            mSnapshotViewMap.put(task.key.id, snapshotView);
+        }
+
+        updateTaskIdContainer();
+        updateTaskIdAttributeContainer();
+
+        setOrientationState(orientedState);
+    }
+
+    private void updateTaskIdContainer() {
+        // TODO(b/249371338): TaskView expects the array to have at least 2 elements.
+        // At least 2 elements in the array
+        mTaskIdContainer = new int[Math.max(mTasks.size(), 2)];
+        for (int i = 0; i < mTasks.size(); i++) {
+            mTaskIdContainer[i] = mTasks.get(i).key.id;
+        }
+    }
+
+    private void updateTaskIdAttributeContainer() {
+        // TODO(b/249371338): TaskView expects the array to have at least 2 elements.
+        // At least 2 elements in the array
+        mTaskIdAttributeContainer = new TaskIdAttributeContainer[Math.max(mTasks.size(), 2)];
+        for (int i = 0; i < mTasks.size(); i++) {
+            Task task = mTasks.get(i);
+            TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+            mTaskIdAttributeContainer[i] = createAttributeContainer(task, thumbnailView);
+        }
+    }
+
+    private TaskIdAttributeContainer createAttributeContainer(Task task,
+            TaskThumbnailView thumbnailView) {
+        return new TaskIdAttributeContainer(task, thumbnailView, null, STAGE_POSITION_UNDEFINED);
+    }
+
+    @Nullable
+    @Override
+    public Task getTask() {
+        // TODO(b/249371338): returning first task. This won't work well with multiple tasks.
+        return mTasks.size() > 0 ? mTasks.get(0) : null;
+    }
+
+    @Override
+    public TaskThumbnailView getThumbnail() {
+        // TODO(b/249371338): returning single thumbnail. This won't work well with multiple tasks.
+        Task task = getTask();
+        if (task != null) {
+            return mSnapshotViewMap.get(task.key.id);
+        }
+        return null;
+    }
+
+    @Override
+    public boolean containsTaskId(int taskId) {
+        // Thumbnail map contains taskId -> thumbnail map. Use the keys for contains
+        return mSnapshotViewMap.contains(taskId);
+    }
+
+    @Override
+    public void onTaskListVisibilityChanged(boolean visible, int changes) {
+        cancelPendingLoadTasks();
+        if (visible) {
+            RecentsModel model = RecentsModel.INSTANCE.get(getContext());
+            TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
+
+            if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+                for (Task task : mTasks) {
+                    CancellableTask<?> thumbLoadRequest =
+                            thumbnailCache.updateThumbnailInBackground(task, thumbnailData -> {
+                                TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+                                if (thumbnailView != null) {
+                                    thumbnailView.setThumbnail(task, thumbnailData);
+                                }
+                            });
+                    if (thumbLoadRequest != null) {
+                        mPendingThumbnailRequests.add(thumbLoadRequest);
+                    }
+                }
+            }
+        } else {
+            if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+                for (Task task : mTasks) {
+                    TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+                    if (thumbnailView != null) {
+                        thumbnailView.setThumbnail(null, null);
+                    }
+                    // Reset the task thumbnail ref
+                    task.thumbnail = null;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setOrientationState(RecentsOrientedState orientationState) {
+        // TODO(b/249371338): this copies logic from TaskView
+        PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
+        boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+        DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+
+        LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
+
+        int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
+        int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
+        int taskMargin = deviceProfile.overviewTaskMarginPx;
+
+        orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconHeight,
+                thumbnailTopMargin, isRtl);
+
+        LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
+        snapshotParams.topMargin = thumbnailTopMargin;
+
+        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+            TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i);
+            thumbnailView.setLayoutParams(snapshotParams);
+        }
+    }
+
+    @Override
+    protected void cancelPendingLoadTasks() {
+        for (CancellableTask<?> cancellableTask : mPendingThumbnailRequests) {
+            cancellableTask.cancel();
+        }
+        mPendingThumbnailRequests.clear();
+    }
+
+    @Override
+    public boolean offerTouchToChildren(MotionEvent event) {
+        return false;
+    }
+
+    @Override
+    protected boolean showTaskMenuWithContainer(IconView iconView) {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public RunnableList launchTaskAnimated() {
+        RunnableList endCallback = new RunnableList();
+        SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps();
+        RecentsView<?, ?> recentsView = getRecentsView();
+        recentsView.addSideTaskLaunchCallback(endCallback);
+        return endCallback;
+    }
+
+    @Override
+    public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
+        SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps();
+        callback.accept(true);
+    }
+
+    @Override
+    void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) {
+        // Sets new thumbnails based on the incoming data and refreshes the rest.
+        // Create a copy of the thumbnail map, so we can track thumbnails that need refreshing.
+        SparseArray<TaskThumbnailView> thumbnailsToRefresh = mSnapshotViewMap.clone();
+        if (thumbnailDatas != null) {
+            for (Task task : mTasks) {
+                int key = task.key.id;
+                TaskThumbnailView thumbnailView = thumbnailsToRefresh.get(key);
+                ThumbnailData thumbnailData = thumbnailDatas.get(key);
+                if (thumbnailView != null && thumbnailData != null) {
+                    thumbnailView.setThumbnail(task, thumbnailData);
+                    // Remove this thumbnail from the list that should be refreshed.
+                    thumbnailsToRefresh.remove(key);
+                }
+            }
+        }
+
+        // Refresh the rest that were not updated.
+        for (int i = 0; i < thumbnailsToRefresh.size(); i++) {
+            thumbnailsToRefresh.valueAt(i).refresh();
+        }
+    }
+
+    @Override
+    public TaskThumbnailView[] getThumbnails() {
+        TaskThumbnailView[] thumbnails = new TaskThumbnailView[mSnapshotViewMap.size()];
+        for (int i = 0; i < thumbnails.length; i++) {
+            thumbnails[i] = mSnapshotViewMap.valueAt(i);
+        }
+        return thumbnails;
+    }
+
+    @Override
+    public void onRecycle() {
+        resetPersistentViewTransforms();
+        // Clear any references to the thumbnail (it will be re-read either from the cache or the
+        // system on next bind)
+        for (Task task : mTasks) {
+            TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+            if (thumbnailView != null) {
+                thumbnailView.setThumbnail(task, null);
+            }
+        }
+        setOverlayEnabled(false);
+        onTaskListVisibilityChanged(false);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int containerWidth = MeasureSpec.getSize(widthMeasureSpec);
+        int containerHeight = MeasureSpec.getSize(heightMeasureSpec);
+
+        setMeasuredDimension(containerWidth, containerHeight);
+
+        int thumbnails = mSnapshotViewMap.size();
+        if (thumbnails == 0) {
+            return;
+        }
+
+        int windowWidth = mActivity.getDeviceProfile().widthPx;
+        int windowHeight = mActivity.getDeviceProfile().heightPx;
+
+        float scaleWidth = containerWidth / (float) windowWidth;
+        float scaleHeight = containerHeight / (float) windowHeight;
+
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onMeasure: container=[" + containerWidth + "," + containerHeight + "] window=["
+                            + windowWidth + "," + windowHeight + "] scale=[" + scaleWidth + ","
+                            + scaleHeight + "]");
+        }
+
+        // Desktop tile is a shrunk down version of launcher and freeform task thumbnails.
+        for (int i = 0; i < mTasks.size(); i++) {
+            Task task = mTasks.get(i);
+            Rect taskSize = task.appBounds;
+            if (taskSize == null) {
+                // Default to quarter of the desktop if we did not get app bounds.
+                taskSize = new Rect(0, 0, windowWidth / 4, windowHeight / 4);
+            }
+
+            int thumbWidth = (int) (taskSize.width() * scaleWidth);
+            int thumbHeight = (int) (taskSize.height() * scaleHeight);
+
+            TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+            if (thumbnailView != null) {
+                thumbnailView.measure(MeasureSpec.makeMeasureSpec(thumbWidth, MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(thumbHeight, MeasureSpec.EXACTLY));
+
+                // Position the task to the same position as it would be on the desktop
+                Point positionInParent = task.positionInParent;
+                if (positionInParent == null) {
+                    positionInParent = new Point(0, 0);
+                }
+                int taskX = (int) (positionInParent.x * scaleWidth);
+                int taskY = (int) (positionInParent.y * scaleHeight);
+                thumbnailView.setX(taskX);
+                thumbnailView.setY(taskY);
+
+                if (DEBUG) {
+                    Log.d(TAG, "onMeasure: task=" + task.key + " thumb=[" + thumbWidth + ","
+                            + thumbHeight + "]" + " pos=[" + taskX + "," + taskY + "]");
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setOverlayEnabled(boolean overlayEnabled) {
+        // Intentional no-op to prevent setting smart actions overlay on thumbnails
+    }
+
+    @Override
+    public void setFullscreenProgress(float progress) {
+        // TODO(b/249371338): this copies parent implementation and makes it work for N thumbs
+        progress = Utilities.boundToRange(progress, 0, 1);
+        mFullscreenProgress = progress;
+        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+            TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i);
+            thumbnailView.getTaskOverlay().setFullscreenProgress(progress);
+            updateSnapshotRadius();
+        }
+    }
+
+    @Override
+    protected void updateSnapshotRadius() {
+        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+            mSnapshotViewMap.valueAt(i).setFullscreenParams(mCurrentFullscreenParams);
+        }
+    }
+
+    @Override
+    protected void setIconAndDimTransitionProgress(float progress, boolean invert) {
+        // no-op
+    }
+
+    @Override
+    public void setColorTint(float amount, int tintColor) {
+        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+            mSnapshotViewMap.valueAt(i).setDimAlpha(amount);
+        }
+    }
+
+    @Override
+    protected void applyThumbnailSplashAlpha() {
+        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+            mSnapshotViewMap.valueAt(i).setSplashAlpha(mTaskThumbnailSplashAlpha);
+        }
+    }
+
+    @Override
+    void setThumbnailVisibility(int visibility) {
+        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+            mSnapshotViewMap.valueAt(i).setVisibility(visibility);
+        }
+    }
+
+    @Override
+    protected boolean confirmSecondSplitSelectApp() {
+        // Desktop tile can't be in split screen
+        return false;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 3a5f606..71b0c60 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -225,6 +225,12 @@
     }
 
     @Override
+    public boolean containsTaskId(int taskId) {
+        return (mTask != null && mTask.key.id == taskId)
+                || (mSecondaryTask != null && mSecondaryTask.key.id == taskId);
+    }
+
+    @Override
     public TaskThumbnailView[] getThumbnails() {
         return new TaskThumbnailView[]{mSnapshotView, mSnapshotView2};
     }
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 08a17c4..514d5b9 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -31,6 +31,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
@@ -82,10 +83,11 @@
     private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3;
     private static final int INDEX_SHARE_TARGET_ALPHA = 4;
 
-    public @interface SplitButtonDisabledFlags { }
-
+    public @interface SplitButtonHiddenFlags { }
     public static final int FLAG_IS_NOT_TABLET = 1 << 0;
-    public static final int FLAG_SINGLE_TASK = 1 << 1;
+
+    public @interface SplitButtonDisabledFlags { }
+    public static final int FLAG_SINGLE_TASK = 1 << 0;
 
     private MultiValueAlpha mMultiValueAlpha;
     private Button mSplitButton;
@@ -96,6 +98,9 @@
     @ActionsDisabledFlags
     protected int mDisabledFlags;
 
+    @SplitButtonHiddenFlags
+    private int mSplitButtonHiddenFlags;
+
     @SplitButtonDisabledFlags
     private int mSplitButtonDisabledFlags;
 
@@ -191,20 +196,40 @@
         }
         boolean isEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
         LayoutUtils.setViewEnabled(this, isEnabled);
+        updateSplitButtonEnabledState();
     }
 
     /**
-     * Updates the proper flags to indicate whether the "Split screen" button should be enabled.
+     * Updates the proper flags to indicate whether the "Split screen" button should be hidden.
      *
-     * @param flag          The flag to update.
-     * @param enable        Whether to enable the disable flag: True will cause view to be disabled.
+     * @param flag   The flag to update.
+     * @param enable Whether to enable the hidden flag: True will cause view to be hidden.
      */
-    public void updateSplitButtonFlags(@SplitButtonDisabledFlags int flag, boolean enable) {
+    public void updateSplitButtonHiddenFlags(@SplitButtonHiddenFlags int flag, boolean enable) {
+        if (enable) {
+            mSplitButtonHiddenFlags |= flag;
+        } else {
+            mSplitButtonHiddenFlags &= ~flag;
+        }
+        if (mSplitButton == null) return;
+        boolean shouldBeVisible = mSplitButtonHiddenFlags == 0;
+        mSplitButton.setVisibility(shouldBeVisible ? VISIBLE : GONE);
+        findViewById(R.id.action_split_space).setVisibility(shouldBeVisible ? VISIBLE : GONE);
+    }
+
+    /**
+     * Updates the proper flags to indicate whether the "Split screen" button should be disabled.
+     *
+     * @param flag   The flag to update.
+     * @param enable Whether to enable the disable flag: True will cause view to be disabled.
+     */
+    public void updateSplitButtonDisabledFlags(@SplitButtonDisabledFlags int flag, boolean enable) {
         if (enable) {
             mSplitButtonDisabledFlags |= flag;
         } else {
             mSplitButtonDisabledFlags &= ~flag;
         }
+        updateSplitButtonEnabledState();
     }
 
     public AlphaProperty getContentAlpha() {
@@ -234,7 +259,9 @@
         // If in 3-button mode, shift action buttons to accommodate 3-button layout.
         // (Special exception for landscape tablets, where there is enough room and we don't need to
         // shift the action buttons.)
-        if (mDp.areNavButtonsInline && !largeScreenLandscape) {
+        if (mDp.areNavButtonsInline && !largeScreenLandscape
+                // If taskbar is in overview, overview action has dedicated space above nav buttons
+                && !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
             // Add extra horizontal spacing
             int additionalPadding = mDp.hotseatBarEndOffset;
             if (isLayoutRtl()) {
@@ -264,7 +291,8 @@
             return 0;
         }
 
-        if (!mDp.isGestureMode && mDp.isTaskbarPresent) {
+        if (!mDp.isGestureMode && mDp.isTaskbarPresent
+                && !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
             return mDp.getOverviewActionsClaimedSpaceBelow();
         }
 
@@ -289,16 +317,16 @@
     }
 
     /**
-     * Shows/hides the "Split" button based on the status of mHiddenFlags.
+     * Enables/disables the "Split" button based on the status of mSplitButtonDisabledFlags and
+     * mDisabledFlags.
      */
-    public void updateSplitButtonVisibility() {
+    private void updateSplitButtonEnabledState() {
         if (mSplitButton == null) {
             return;
         }
-        boolean shouldBeVisible = mSplitButtonDisabledFlags == 0
-                // and neither of these flags are active
-                && (mHiddenFlags & (HIDDEN_SPLIT_SCREEN | HIDDEN_SPLIT_SELECT_ACTIVE)) == 0;
-        mSplitButton.setVisibility(shouldBeVisible ? VISIBLE : GONE);
-        findViewById(R.id.action_split_space).setVisibility(shouldBeVisible ? VISIBLE : GONE);
+        boolean isParentEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
+        boolean shouldBeEnabled = mSplitButtonDisabledFlags == 0 && isParentEnabled;
+        mSplitButton.setEnabled(shouldBeEnabled);
     }
+
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 7ad0e48..c8ef958 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -41,7 +41,6 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_0_75;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
@@ -55,6 +54,7 @@
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
+import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
 import static com.android.quickstep.views.OverviewActionsView.FLAG_IS_NOT_TABLET;
 import static com.android.quickstep.views.OverviewActionsView.FLAG_SINGLE_TASK;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
@@ -73,6 +73,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
+import android.app.WindowConfiguration;
 import android.content.Context;
 import android.content.LocusId;
 import android.content.res.Configuration;
@@ -172,6 +173,7 @@
 import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.AnimUtils;
+import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsOrientedState;
@@ -196,6 +198,7 @@
 import com.android.wm.shell.pip.IPipAnimationListener;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
@@ -478,10 +481,11 @@
     private final InvariantDeviceProfile mIdp;
 
     /**
-     * Getting views should be done via {@link #getTaskViewFromPool(boolean)}
+     * Getting views should be done via {@link #getTaskViewFromPool(int)}
      */
     private final ViewPool<TaskView> mTaskViewPool;
     private final ViewPool<GroupedTaskView> mGroupedTaskViewPool;
+    private final ViewPool<DesktopTaskView> mDesktopTaskViewPool;
 
     private final TaskOverlayFactory mTaskOverlayFactory;
 
@@ -738,6 +742,8 @@
                 10 /* initial size */);
         mGroupedTaskViewPool = new ViewPool<>(context, this,
                 R.layout.task_grouped, 20 /* max size */, 10 /* initial size */);
+        mDesktopTaskViewPool = new ViewPool<>(context, this, R.layout.task_desktop,
+                5 /* max size */, 1 /* initial size */);
 
         mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
         setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
@@ -817,8 +823,7 @@
             }
             super.dispatchDraw(canvas);
         }
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
-                && mRemoteTargetHandles != null) {
+        if (mEnableDrawingLiveTile && mRemoteTargetHandles != null) {
             redrawLiveTile();
         }
     }
@@ -941,7 +946,7 @@
                 .setSyncTransactionApplier(mSyncTransactionApplier));
         RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
         mIPipAnimationListener.setActivityAndRecentsView(mActivity, this);
-        SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
+        SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener(
                 mIPipAnimationListener);
         mOrientationState.initListeners();
         SplitScreenBounds.INSTANCE.addOnChangeListener(this);
@@ -960,7 +965,7 @@
                 .setSyncTransactionApplier(null));
         executeSideTaskLaunchCallback();
         RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
-        SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
+        SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener(null);
         SplitScreenBounds.INSTANCE.removeOnChangeListener(this);
         mIPipAnimationListener.setActivityAndRecentsView(null, null);
         mOrientationState.destroyListeners();
@@ -983,6 +988,8 @@
             }
             if (child instanceof GroupedTaskView) {
                 mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
+            } else if (child instanceof DesktopTaskView) {
+                mDesktopTaskViewPool.recycle((DesktopTaskView) taskView);
             } else {
                 mTaskViewPool.recycle(taskView);
             }
@@ -1201,8 +1208,7 @@
 
         for (int i = 0; i < getTaskViewCount(); i++) {
             TaskView taskView = requireTaskViewAt(i);
-            int[] taskIds = taskView.getTaskIds();
-            if (taskIds[0] == taskId || taskIds[1] == taskId) {
+            if (taskView.containsTaskId(taskId)) {
                 return taskView;
             }
         }
@@ -1483,33 +1489,28 @@
         // Add views as children based on whether it's grouped or single task
         for (int i = taskGroups.size() - 1; i >= 0; i--) {
             GroupTask groupTask = taskGroups.get(i);
-            boolean hasMultipleTasks = groupTask.hasMultipleTasks();
-            TaskView taskView = getTaskViewFromPool(hasMultipleTasks);
+            TaskView taskView = getTaskViewFromPool(groupTask.taskViewType);
             addView(taskView);
 
-            if (hasMultipleTasks) {
+            if (taskView instanceof GroupedTaskView) {
                 boolean firstTaskIsLeftTopTask =
                         groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
                 Task leftTopTask = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
                 Task rightBottomTask = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
                 ((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
                         groupTask.mSplitBounds);
+            } else if (taskView instanceof DesktopTaskView) {
+                ((DesktopTaskView) taskView).bind(((DesktopTask) groupTask).tasks,
+                        mOrientationState);
             } else {
                 taskView.bind(groupTask.task1, mOrientationState);
             }
         }
+
         if (!taskGroups.isEmpty()) {
             addView(mClearAllButton);
         }
 
-        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);
         // If the list changed, maybe the focused task doesn't exist anymore
@@ -1534,21 +1535,36 @@
         }
 
         int targetPage = -1;
-        if (!settlingOnNewTask) {
+        if (mNextPage != INVALID_PAGE) {
+            // Restore mCurrentPage but don't call setCurrentPage() as that clobbers the scroll.
+            mCurrentPage = previousCurrentPage;
+            if (currentTaskId != -1) {
+                currentTaskView = getTaskViewByTaskId(currentTaskId);
+                if (currentTaskView != null) {
+                    targetPage = indexOfChild(currentTaskView);
+                }
+            }
+        } else {
             // Set the current page to the running task, but not if settling on new task.
             if (runningTaskId != -1) {
                 targetPage = indexOfChild(newRunningTaskView);
             } else if (getTaskViewCount() > 0) {
                 targetPage = indexOfChild(requireTaskViewAt(0));
             }
-        } else if (currentTaskId != -1) {
-            currentTaskView = getTaskViewByTaskId(currentTaskId);
-            if (currentTaskView != null) {
-                targetPage = indexOfChild(currentTaskView);
-            }
         }
         if (targetPage != -1 && mCurrentPage != targetPage) {
-            setCurrentPage(targetPage);
+            int finalTargetPage = targetPage;
+            runOnPageScrollsInitialized(() -> {
+                // TODO(b/246283207): Remove logging once root cause of flake detected.
+                if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                    Log.d("b/246283207", "RecentsView#applyLoadPlan() -> "
+                            + "previousCurrentPage: " + previousCurrentPage
+                            + ", targetPage: " + finalTargetPage
+                            + ", getScrollForPage(targetPage): "
+                            + getScrollForPage(finalTargetPage));
+                }
+                setCurrentPage(finalTargetPage);
+            });
         }
 
         if (mIgnoreResetTaskId != -1 &&
@@ -1629,22 +1645,20 @@
                 taskView.setTaskThumbnailSplashAlpha(mTaskThumbnailSplashAlpha);
             }
         }
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            // resetTaskVisuals is called at the end of dismiss animation which could update
-            // primary and secondary translation of the live tile cut out. We will need to do so
-            // here accordingly.
-            runActionOnRemoteHandles(remoteTargetHandle -> {
-                TaskViewSimulator simulator = remoteTargetHandle.getTaskViewSimulator();
-                simulator.taskPrimaryTranslation.value = 0;
-                simulator.taskSecondaryTranslation.value = 0;
-                simulator.fullScreenProgress.value = 0;
-                simulator.recentsViewScale.value = 1;
-            });
-            // Similar to setRunningTaskHidden below, reapply the state before runningTaskView is
-            // null.
-            if (!mRunningTaskShowScreenshot) {
-                setRunningTaskViewShowScreenshot(mRunningTaskShowScreenshot);
-            }
+        // resetTaskVisuals is called at the end of dismiss animation which could update
+        // primary and secondary translation of the live tile cut out. We will need to do so
+        // here accordingly.
+        runActionOnRemoteHandles(remoteTargetHandle -> {
+            TaskViewSimulator simulator = remoteTargetHandle.getTaskViewSimulator();
+            simulator.taskPrimaryTranslation.value = 0;
+            simulator.taskSecondaryTranslation.value = 0;
+            simulator.fullScreenProgress.value = 0;
+            simulator.recentsViewScale.value = 1;
+        });
+        // Similar to setRunningTaskHidden below, reapply the state before runningTaskView is
+        // null.
+        if (!mRunningTaskShowScreenshot) {
+            setRunningTaskViewShowScreenshot(mRunningTaskShowScreenshot);
         }
         if (mRunningTaskTileHidden) {
             setRunningTaskHidden(mRunningTaskTileHidden);
@@ -1724,6 +1738,7 @@
             // Changed orientations, update controllers so they intercept accordingly.
             mActivity.getDragLayer().recreateControllers();
             onOrientationChanged();
+            resetTaskVisuals();
         }
 
         boolean isInLandscape = mOrientationState.getTouchRotation() != ROTATION_0
@@ -1881,6 +1896,8 @@
                 mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, alphaValue);
         mActionsViewAlphaAnimatorFinalValue = alphaValue;
         mActionsViewAlphaAnimator.setDuration(duration);
+        // Set autocancel to prevent race-conditiony setting of alpha from other animations
+        mActionsViewAlphaAnimator.setAutoCancel(true);
         mActionsViewAlphaAnimator.start();
     }
 
@@ -2036,7 +2053,7 @@
         mFocusedTaskViewId = -1;
 
         if (mRecentsAnimationController != null) {
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) {
+            if (mEnableDrawingLiveTile) {
                 // We are still drawing the live tile, finish it now to clean up.
                 finishRecentsAnimation(true /* toRecents */, null);
             } else {
@@ -2117,10 +2134,19 @@
      * Handle the edge case where Recents could increment task count very high over long
      * period of device usage. Probably will never happen, but meh.
      */
-    private <T extends TaskView> T getTaskViewFromPool(boolean isGrouped) {
-        T taskView = isGrouped ?
-                (T) mGroupedTaskViewPool.getView() :
-                (T) mTaskViewPool.getView();
+    private TaskView getTaskViewFromPool(@TaskView.Type int type) {
+        TaskView taskView;
+        switch (type) {
+            case TaskView.Type.GROUPED:
+                taskView = mGroupedTaskViewPool.getView();
+                break;
+            case TaskView.Type.DESKTOP:
+                taskView = mDesktopTaskViewPool.getView();
+                break;
+            case TaskView.Type.SINGLE:
+            default:
+                taskView = mTaskViewPool.getView();
+        }
         taskView.setTaskViewId(mTaskViewIdCount);
         if (mTaskViewIdCount == Integer.MAX_VALUE) {
             mTaskViewIdCount = 0;
@@ -2275,9 +2301,6 @@
 
         setEnableFreeScroll(true);
         setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS);
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            setRunningTaskViewShowScreenshot(true);
-        }
         setRunningTaskHidden(false);
         animateUpTaskIconScale();
         animateActionsViewIn();
@@ -2315,12 +2338,19 @@
         }
         int runningTaskViewId = -1;
         boolean needGroupTaskView = runningTasks.length > 1;
+        boolean needDesktopTask = hasDesktopTask(runningTasks);
         if (shouldAddStubTaskView(runningTasks)) {
             boolean wasEmpty = getChildCount() == 0;
             // Add an empty view for now until the task plan is loaded and applied
             final TaskView taskView;
-            if (needGroupTaskView) {
-                taskView = getTaskViewFromPool(true);
+            if (needDesktopTask) {
+                taskView = getTaskViewFromPool(TaskView.Type.DESKTOP);
+                mTmpRunningTasks = Arrays.copyOf(runningTasks, runningTasks.length);
+                addView(taskView, 0);
+                ((DesktopTaskView) taskView).bind(Arrays.asList(mTmpRunningTasks),
+                        mOrientationState);
+            } else if (needGroupTaskView) {
+                taskView = getTaskViewFromPool(TaskView.Type.GROUPED);
                 mTmpRunningTasks = new Task[]{runningTasks[0], runningTasks[1]};
                 addView(taskView, 0);
                 // When we create a placeholder task view mSplitBoundsConfig will be null, but with
@@ -2329,7 +2359,7 @@
                 ((GroupedTaskView)taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1],
                         mOrientationState, mSplitBoundsConfig);
             } else {
-                taskView = getTaskViewFromPool(false);
+                taskView = getTaskViewFromPool(TaskView.Type.SINGLE);
                 addView(taskView, 0);
                 // The temporary running task is only used for the duration between the start of the
                 // gesture and the task list is loaded and applied
@@ -2364,6 +2394,18 @@
         reloadIfNeeded();
     }
 
+    private boolean hasDesktopTask(Task[] runningTasks) {
+        if (!DESKTOP_MODE_SUPPORTED) {
+            return false;
+        }
+        for (Task task : runningTasks) {
+            if (task.key.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Sets the running task id, cleaning up the old running task if necessary.
      */
@@ -2402,12 +2444,10 @@
     }
 
     private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mRunningTaskShowScreenshot = showScreenshot;
-            TaskView runningTaskView = getRunningTaskView();
-            if (runningTaskView != null) {
-                runningTaskView.setShowScreenshot(mRunningTaskShowScreenshot);
-            }
+        mRunningTaskShowScreenshot = showScreenshot;
+        TaskView runningTaskView = getRunningTaskView();
+        if (runningTaskView != null) {
+            runningTaskView.setShowScreenshot(mRunningTaskShowScreenshot);
         }
     }
 
@@ -2788,8 +2828,7 @@
         anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate,
                 verticalFactor * secondaryTaskDimension * 2).setDuration(duration), LINEAR, sp);
 
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
-                && taskView.isRunningTask()) {
+        if (mEnableDrawingLiveTile && taskView.isRunningTask()) {
             anim.addOnFrameCallback(() -> {
                 runActionOnRemoteHandles(
                         remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
@@ -3028,8 +3067,7 @@
                             dismissTranslationInterpolationEnd
                                     - halfAdditionalDismissTranslationOffset,
                             END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1);
-                    if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
-                            && taskView.isRunningTask()) {
+                    if (mEnableDrawingLiveTile && taskView.isRunningTask()) {
                         anim.addOnFrameCallback(() -> {
                             runActionOnRemoteHandles(
                                     remoteTargetHandle ->
@@ -3124,8 +3162,7 @@
                             )
                     );
 
-                    if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
-                            && child instanceof TaskView
+                    if (mEnableDrawingLiveTile && child instanceof TaskView
                             && ((TaskView) child).isRunningTask()) {
                         anim.addOnFrameCallback(() -> {
                             runActionOnRemoteHandles(
@@ -3248,8 +3285,7 @@
         mPendingAnimation.addEndListener(new Consumer<Boolean>() {
             @Override
             public void accept(Boolean success) {
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
-                        && dismissedTaskView.isRunningTask() && success) {
+                if (mEnableDrawingLiveTile && dismissedTaskView.isRunningTask() && success) {
                     finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
                             () -> onEnd(success));
                 } else {
@@ -3266,8 +3302,7 @@
                 if (success) {
                     if (shouldRemoveTask) {
                         if (dismissedTaskView.getTask() != null) {
-                            if (ENABLE_QUICKSTEP_LIVE_TILE.get()
-                                    && dismissedTaskView.isRunningTask()) {
+                            if (dismissedTaskView.isRunningTask()) {
                                 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
                                         () -> removeTaskInternal(dismissedTaskViewId));
                             } else {
@@ -3468,10 +3503,9 @@
         boolean isCurrentSplit = getCurrentPageTaskView() instanceof GroupedTaskView;
         mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit);
         mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive());
-        mActionsView.updateSplitButtonFlags(FLAG_IS_NOT_TABLET,
+        mActionsView.updateSplitButtonHiddenFlags(FLAG_IS_NOT_TABLET,
                 !mActivity.getDeviceProfile().isTablet);
-        mActionsView.updateSplitButtonFlags(FLAG_SINGLE_TASK, getTaskViewCount() <= 1);
-        mActionsView.updateSplitButtonVisibility();
+        mActionsView.updateSplitButtonDisabledFlags(FLAG_SINGLE_TASK, getTaskViewCount() <= 1);
     }
 
     /**
@@ -3926,8 +3960,7 @@
                     ? ((TaskView) child).getPrimaryTaskOffsetTranslationProperty()
                     : mOrientationHandler.getPrimaryViewTranslate();
             translationProperty.set(child, totalTranslation);
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
-                    && i == getRunningTaskIndex()) {
+            if (mEnableDrawingLiveTile && i == getRunningTaskIndex()) {
                 runActionOnRemoteHandles(
                         remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
                                 .taskPrimaryTranslation.value = totalTranslation);
@@ -4151,12 +4184,13 @@
         mSplitSelectStateController.setInitialTaskSelect(taskView.getTask().key.id,
                 stagePosition, splitEvent, taskView.getItemInfo());
         mSplitHiddenTaskViewIndex = indexOfChild(taskView);
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
-                    null /* onFinishComplete */);
-        }
+        finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
+                null /* onFinishComplete */);
     }
 
+    /**
+     * Called when staging a split from Home/AllApps, using the icon long-press menu.
+     */
     public void initiateSplitSelect(QuickstepSystemShortcut.SplitSelectSource splitSelectSource) {
         mSplitSelectSource = splitSelectSource;
         mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
@@ -4295,6 +4329,10 @@
 
         resetTaskVisuals();
         mSplitHiddenTaskViewIndex = -1;
+        if (mSplitHiddenTaskView != null) {
+            mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE);
+            mSplitHiddenTaskView = null;
+        }
     }
 
     private void safeRemoveDragLayerView(@Nullable View viewToRemove) {
@@ -4434,9 +4472,7 @@
             anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex),
                     mOrientationHandler.getPrimaryViewTranslate(), primaryTranslation));
             int runningTaskIndex = getRunningTaskIndex();
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()
-                    && runningTaskIndex != -1
-                    && runningTaskIndex != taskIndex
+            if (runningTaskIndex != -1 && runningTaskIndex != taskIndex
                     && getRemoteTargetHandles() != null) {
                 for (RemoteTargetHandle remoteHandle : getRemoteTargetHandles()) {
                     anim.play(ObjectAnimator.ofFloat(
@@ -4525,12 +4561,10 @@
 
         mPendingAnimation = new PendingAnimation(duration);
         mPendingAnimation.add(anim);
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            runActionOnRemoteHandles(
-                    remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
-                            .addOverviewToAppAnim(mPendingAnimation, interpolator));
-            mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
-        }
+        runActionOnRemoteHandles(
+                remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
+                        .addOverviewToAppAnim(mPendingAnimation, interpolator));
+        mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
         mPendingAnimation.addEndListener(isSuccess -> {
             if (isSuccess) {
                 if (tv.getTaskIds()[1] != -1 && mRemoteTargetHandles != null) {
@@ -4542,7 +4576,7 @@
                                 dividerAnimator.end();
                             });
                 }
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && tv.isRunningTask()) {
+                if (tv.isRunningTask()) {
                     finishRecentsAnimation(false /* toRecents */, null);
                     onTaskLaunchAnimationEnd(true /* success */);
                 } else {
@@ -4715,7 +4749,7 @@
         if (sendUserLeaveHint) {
             // Notify the SysUI to use fade-in animation when entering PiP from live tile.
             final SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(getContext());
-            systemUiProxy.notifySwipeToHomeFinished();
+            systemUiProxy.setPipAnimationTypeToAlpha();
             systemUiProxy.setShelfHeight(true, mActivity.getDeviceProfile().hotseatBarSizePx);
             // Transaction to hide the task to avoid flicker for entering PiP from split-screen.
             // See also {@link AbsSwipeUpHandler#maybeFinishSwipeToHome}.
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index 27ec01a..b0b111d 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -29,6 +29,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.util.DisplayController;
 
@@ -114,11 +115,13 @@
     int getThreeButtonNavShift() {
         DeviceProfile dp = mLauncher.getDeviceProfile();
         if ((DisplayController.getNavigationMode(getContext()) == THREE_BUTTONS)
-                && ((dp.isTwoPanels) || (dp.isTablet && !dp.isLandscape))) {
+                && ((dp.isTwoPanels) || (dp.isTablet && !dp.isLandscape))
+                // If taskbar is in overview, overview action has dedicated space above nav buttons
+                && !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
             int navButtonWidth = getResources().getDimensionPixelSize(
                     R.dimen.taskbar_nav_buttons_size);
             int extraMargin = getResources().getDimensionPixelSize(
-                    R.dimen.taskbar_contextual_button_margin);
+                    R.dimen.taskbar_split_instructions_margin);
             // Explanation: The 3-button nav for non-phones sits on one side of the screen, taking
             // up 3 buttons + a side margin worth of space. Our splitInstructionsView starts in the
             // center of the screen and we want to center it in the remaining space, therefore we
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index d7a8599..6792dc5 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -16,11 +16,11 @@
 
 package com.android.quickstep.views;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.systemui.shared.recents.utilities.PreviewPositionHelper.MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT;
+import static com.android.systemui.shared.recents.utilities.Utilities.isRelativePercentDifferenceGreaterThan;
 
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -41,7 +41,6 @@
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Property;
-import android.view.Surface;
 import android.view.View;
 import android.widget.ImageView;
 
@@ -60,6 +59,7 @@
 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
 
 /**
  * A task in the Recents view.
@@ -67,7 +67,6 @@
 public class TaskThumbnailView extends View {
     private static final MainThreadInitializedObject<FullscreenDrawParams> TEMP_PARAMS =
             new MainThreadInitializedObject<>(FullscreenDrawParams::new);
-    private static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f;
 
     public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
             new FloatProperty<TaskThumbnailView>("dimAlpha") {
@@ -320,13 +319,11 @@
 
     public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
             float cornerRadius) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
-                canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
-                canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
-                        mDimmingPaintAfterClearing);
-                return;
-            }
+        if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
+            canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
+            canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
+                    mDimmingPaintAfterClearing);
+            return;
         }
 
         // Always draw the background since the snapshots might be translucent or partially empty
@@ -373,7 +370,7 @@
      * <p>We want to show the splash if the aspect ratio or rotation of the thumbnail would be
      * different from the task.
      */
-    boolean shouldShowSplashView() {
+    public boolean shouldShowSplashView() {
         return isThumbnailAspectRatioDifferentFromThumbnailData()
                 || isThumbnailRotationDifferentFromTask();
     }
@@ -421,7 +418,7 @@
         float thumbnailDataAspect =
                 mThumbnailData.thumbnail.getWidth() / (float) mThumbnailData.thumbnail.getHeight();
 
-        return Utilities.isRelativePercentDifferenceGreaterThan(thumbnailViewAspect,
+        return isRelativePercentDifferenceGreaterThan(thumbnailViewAspect,
                 thumbnailDataAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
     }
 
@@ -445,8 +442,8 @@
      */
     private void refreshOverlay() {
         if (mOverlayEnabled) {
-            getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
-                    mPreviewPositionHelper.mIsOrientationChanged);
+            getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.getMatrix(),
+                    mPreviewPositionHelper.isOrientationChanged());
         } else {
             getTaskOverlay().reset();
         }
@@ -467,18 +464,19 @@
     }
 
     private void updateThumbnailMatrix() {
-        mPreviewPositionHelper.mIsOrientationChanged = false;
+        mPreviewPositionHelper.setOrientationChanged(false);
         if (mBitmapShader != null && mThumbnailData != null) {
             mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
                     mThumbnailData.thumbnail.getHeight());
             int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
                     .getRecentsActivityRotation();
             boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+            DeviceProfile dp = mActivity.getDeviceProfile();
             mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
-                    getMeasuredWidth(), getMeasuredHeight(), mActivity.getDeviceProfile(),
-                    currentRotation, isRtl);
+                    getMeasuredWidth(), getMeasuredHeight(), dp.widthPx, dp.taskbarSize,
+                    dp.isTablet, currentRotation, isRtl);
 
-            mBitmapShader.setLocalMatrix(mPreviewPositionHelper.mMatrix);
+            mBitmapShader.setLocalMatrix(mPreviewPositionHelper.getMatrix());
             mPaint.setShader(mBitmapShader);
         }
         getTaskView().updateCurrentFullscreenParams(mPreviewPositionHelper);
@@ -518,199 +516,4 @@
         }
         return mThumbnailData.isRealSnapshot && !mTask.isLocked;
     }
-
-    /**
-     * Utility class to position the thumbnail in the TaskView
-     */
-    public static class PreviewPositionHelper {
-
-        private static final RectF EMPTY_RECT_F = new RectF();
-
-        // Contains the portion of the thumbnail that is unclipped when fullscreen progress = 1.
-        private final RectF mClippedInsets = new RectF();
-        private final Matrix mMatrix = new Matrix();
-        private boolean mIsOrientationChanged;
-
-        public Matrix getMatrix() {
-            return mMatrix;
-        }
-
-        /**
-         * Updates the matrix based on the provided parameters
-         */
-        public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
-                int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation,
-                boolean isRtl) {
-            boolean isRotated = false;
-            boolean isOrientationDifferent;
-
-            int thumbnailRotation = thumbnailData.rotation;
-            int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
-            RectF thumbnailClipHint = new RectF();
-            float canvasScreenRatio = canvasWidth / (float) dp.widthPx;
-            float scaledTaskbarSize = dp.taskbarSize * canvasScreenRatio;
-            thumbnailClipHint.bottom = dp.isTablet ? scaledTaskbarSize : 0;
-
-            float scale = thumbnailData.scale;
-            final float thumbnailScale;
-
-            // Landscape vs portrait change.
-            // Note: Disable rotation in grid layout.
-            boolean windowingModeSupportsRotation =
-                    thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !dp.isTablet;
-            isOrientationDifferent = isOrientationChange(deltaRotate)
-                    && windowingModeSupportsRotation;
-            if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
-                // If we haven't measured , skip the thumbnail drawing and only draw the background
-                // color
-                thumbnailScale = 0f;
-            } else {
-                // Rotate the screenshot if not in multi-window mode
-                isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
-
-                float surfaceWidth = thumbnailBounds.width() / scale;
-                float surfaceHeight = thumbnailBounds.height() / scale;
-                float availableWidth = surfaceWidth
-                        - (thumbnailClipHint.left + thumbnailClipHint.right);
-                float availableHeight = surfaceHeight
-                        - (thumbnailClipHint.top + thumbnailClipHint.bottom);
-
-                float canvasAspect = canvasWidth / (float) canvasHeight;
-                float availableAspect = isRotated
-                        ? availableHeight / availableWidth
-                        : availableWidth / availableHeight;
-                boolean isAspectLargelyDifferent =
-                        Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect,
-                                availableAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
-                if (isRotated && isAspectLargelyDifferent) {
-                    // Do not rotate thumbnail if it would not improve fit
-                    isRotated = false;
-                    isOrientationDifferent = false;
-                }
-
-                if (isAspectLargelyDifferent) {
-                    // Crop letterbox insets if insets isn't already clipped
-                    thumbnailClipHint.left = thumbnailData.letterboxInsets.left;
-                    thumbnailClipHint.right = thumbnailData.letterboxInsets.right;
-                    thumbnailClipHint.top = thumbnailData.letterboxInsets.top;
-                    thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom;
-                    availableWidth = surfaceWidth
-                            - (thumbnailClipHint.left + thumbnailClipHint.right);
-                    availableHeight = surfaceHeight
-                            - (thumbnailClipHint.top + thumbnailClipHint.bottom);
-                }
-
-                final float targetW, targetH;
-                if (isOrientationDifferent) {
-                    targetW = canvasHeight;
-                    targetH = canvasWidth;
-                } else {
-                    targetW = canvasWidth;
-                    targetH = canvasHeight;
-                }
-                float targetAspect = targetW / targetH;
-
-                // Update the clipHint such that
-                //   > the final clipped position has same aspect ratio as requested by canvas
-                //   > first fit the width and crop the extra height
-                //   > if that will leave empty space, fit the height and crop the width instead
-                float croppedWidth = availableWidth;
-                float croppedHeight = croppedWidth / targetAspect;
-                if (croppedHeight > availableHeight) {
-                    croppedHeight = availableHeight;
-                    if (croppedHeight < targetH) {
-                        croppedHeight = Math.min(targetH, surfaceHeight);
-                    }
-                    croppedWidth = croppedHeight * targetAspect;
-
-                    // One last check in case the task aspect radio messed up something
-                    if (croppedWidth > surfaceWidth) {
-                        croppedWidth = surfaceWidth;
-                        croppedHeight = croppedWidth / targetAspect;
-                    }
-                }
-
-                // Update the clip hints. Align to 0,0, crop the remaining.
-                if (isRtl) {
-                    thumbnailClipHint.left += availableWidth - croppedWidth;
-                    if (thumbnailClipHint.right < 0) {
-                        thumbnailClipHint.left += thumbnailClipHint.right;
-                        thumbnailClipHint.right = 0;
-                    }
-                } else {
-                    thumbnailClipHint.right += availableWidth - croppedWidth;
-                    if (thumbnailClipHint.left < 0) {
-                        thumbnailClipHint.right += thumbnailClipHint.left;
-                        thumbnailClipHint.left = 0;
-                    }
-                }
-                thumbnailClipHint.bottom += availableHeight - croppedHeight;
-                if (thumbnailClipHint.top < 0) {
-                    thumbnailClipHint.bottom += thumbnailClipHint.top;
-                    thumbnailClipHint.top = 0;
-                } else if (thumbnailClipHint.bottom < 0) {
-                    thumbnailClipHint.top += thumbnailClipHint.bottom;
-                    thumbnailClipHint.bottom = 0;
-                }
-
-                thumbnailScale = targetW / (croppedWidth * scale);
-            }
-
-            if (!isRotated) {
-                mMatrix.setTranslate(
-                        -thumbnailClipHint.left * scale,
-                        -thumbnailClipHint.top * scale);
-            } else {
-                setThumbnailRotation(deltaRotate, thumbnailBounds);
-            }
-
-            mClippedInsets.set(0, 0, 0, scaledTaskbarSize);
-
-            mMatrix.postScale(thumbnailScale, thumbnailScale);
-            mIsOrientationChanged = isOrientationDifferent;
-        }
-
-        private int getRotationDelta(int oldRotation, int newRotation) {
-            int delta = newRotation - oldRotation;
-            if (delta < 0) delta += 4;
-            return delta;
-        }
-
-        /**
-         * @param deltaRotation the number of 90 degree turns from the current orientation
-         * @return {@code true} if the change in rotation results in a shift from landscape to
-         * portrait or vice versa, {@code false} otherwise
-         */
-        private boolean isOrientationChange(int deltaRotation) {
-            return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
-        }
-
-        private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) {
-            float translateX = 0;
-            float translateY = 0;
-
-            mMatrix.setRotate(90 * deltaRotate);
-            switch (deltaRotate) { /* Counter-clockwise */
-                case Surface.ROTATION_90:
-                    translateX = thumbnailPosition.height();
-                    break;
-                case Surface.ROTATION_270:
-                    translateY = thumbnailPosition.width();
-                    break;
-                case Surface.ROTATION_180:
-                    translateX = thumbnailPosition.width();
-                    translateY = thumbnailPosition.height();
-                    break;
-            }
-            mMatrix.postTranslate(translateX, translateY);
-        }
-
-        /**
-         * Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
-         */
-        public RectF getInsetsToDrawInFullscreen(DeviceProfile dp) {
-            return dp.isTaskbarPresent && !dp.isTaskbarPresentInApps
-                    ? mClippedInsets : EMPTY_RECT_F;
-        }
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index a0f195c..35f0f5d 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -18,14 +18,12 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.widget.Toast.LENGTH_SHORT;
-import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR;
 
 import static com.android.launcher3.Utilities.comp;
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -88,7 +86,6 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
-import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskIconCache;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
@@ -99,9 +96,9 @@
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.TaskCornerRadius;
 import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -122,6 +119,8 @@
     private static final String TAG = TaskView.class.getSimpleName();
     private static final boolean DEBUG = false;
 
+    private static final RectF EMPTY_RECT_F = new RectF();
+
     public static final int FLAG_UPDATE_ICON = 1;
     public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1;
 
@@ -135,6 +134,17 @@
     @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL})
     public @interface TaskDataChanges {}
 
+    /**
+     * Type of task view
+     */
+    @Retention(SOURCE)
+    @IntDef({Type.SINGLE, Type.GROUPED, Type.DESKTOP})
+    public @interface Type {
+        int SINGLE = 1;
+        int GROUPED = 2;
+        int DESKTOP = 3;
+    }
+
     /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
     public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
 
@@ -331,7 +341,7 @@
     protected TaskThumbnailView mSnapshotView;
     protected IconView mIconView;
     protected final DigitalWellBeingToast mDigitalWellBeingToast;
-    private float mFullscreenProgress;
+    protected float mFullscreenProgress;
     private float mGridProgress;
     protected float mTaskThumbnailSplashAlpha;
     private float mNonGridScale = 1;
@@ -373,8 +383,8 @@
     /**
      * Index 0 will contain taskID of left/top task, index 1 will contain taskId of bottom/right
      */
-    protected final int[] mTaskIdContainer = new int[]{-1, -1};
-    protected final TaskIdAttributeContainer[] mTaskIdAttributeContainer =
+    protected int[] mTaskIdContainer = new int[]{-1, -1};
+    protected TaskIdAttributeContainer[] mTaskIdAttributeContainer =
             new TaskIdAttributeContainer[2];
 
     private boolean mShowScreenshot;
@@ -525,6 +535,13 @@
     }
 
     /**
+     * Check if given {@code taskId} is tracked in this view
+     */
+    public boolean containsTaskId(int taskId) {
+        return mTask != null && mTask.key.id == taskId;
+    }
+
+    /**
      * @return integer array of two elements to be size consistent with max number of tasks possible
      *         index 0 will contain the taskId, index 1 will be -1 indicating a null taskID value
      */
@@ -564,13 +581,13 @@
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         RecentsView recentsView = getRecentsView();
-        if (recentsView == null || mTask == null) {
+        if (recentsView == null || getTask() == null) {
             return false;
         }
         SplitSelectStateController splitSelectStateController =
                 recentsView.getSplitSelectController();
         if (splitSelectStateController.isSplitSelectActive() &&
-                splitSelectStateController.getInitialTaskId() == mTask.key.id) {
+                splitSelectStateController.getInitialTaskId() == getTask().key.id) {
             // Prevent taps on the this taskview if it's being animated into split select state
             return false;
         }
@@ -597,11 +614,14 @@
      * @return {@code true} if user is already in split select mode and this tap was to choose the
      *         second app. {@code false} otherwise
      */
-    private boolean confirmSecondSplitSelectApp() {
+    protected boolean confirmSecondSplitSelectApp() {
         int index = getLastSelectedChildTaskIndex();
         TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
-        return getRecentsView().confirmSplitSelect(this, container.getTask(),
-                container.getIconView(), container.getThumbnailView());
+        if (container != null) {
+            return getRecentsView().confirmSplitSelect(this, container.getTask(),
+                    container.getIconView(), container.getThumbnailView());
+        }
+        return false;
     }
 
     /**
@@ -626,7 +646,7 @@
             if (ActivityManagerWrapper.getInstance()
                     .startActivityFromRecents(mTask.key, opts.options)) {
                 RecentsView recentsView = getRecentsView();
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskViewId() != -1) {
+                if (recentsView.getRunningTaskViewId() != -1) {
                     recentsView.onTaskLaunchedInLiveTileMode();
 
                     // Return a fresh callback in the live tile case, so that it's not accidentally
@@ -668,9 +688,7 @@
             if (freezeTaskList) {
                 opts.setFreezeRecentTasksReordering();
             }
-            // TODO(b/202826469): Replace setSplashScreenStyle with setDisableStartingWindow.
-            opts.setSplashScreenStyle(mSnapshotView.shouldShowSplashView()
-                    ? SPLASH_SCREEN_STYLE_SOLID_COLOR : opts.getSplashScreenStyle());
+            opts.setDisableStartingWindow(mSnapshotView.shouldShowSplashView());
             Task.TaskKey key = mTask.key;
             UI_HELPER_EXECUTOR.execute(() -> {
                 if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(key, opts)) {
@@ -709,12 +727,7 @@
         RecentsView recentsView = getRecentsView();
         RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
         RunnableList runnableList = new RunnableList();
-        if (mTask != null && mTask.desktopTile) {
-            // clicked on desktop
-            SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps();
-            return runnableList;
-        }
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask() && remoteTargetHandles != null) {
+        if (isRunningTask() && remoteTargetHandles != null) {
             if (!mIsClickableAsLiveTile) {
                 return runnableList;
             }
@@ -1573,7 +1586,7 @@
          */
         public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale,
                 int previewWidth, DeviceProfile dp, PreviewPositionHelper pph) {
-            RectF insets = pph.getInsetsToDrawInFullscreen(dp);
+            RectF insets = getInsetsToDrawInFullscreen(pph, dp);
 
             float currentInsetsLeft = insets.left * fullscreenProgress;
             float currentInsetsTop = insets.top * fullscreenProgress;
@@ -1592,6 +1605,14 @@
                 mScale = previewWidth / (previewWidth + currentInsetsLeft + currentInsetsRight);
             }
         }
+
+        /**
+         * Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
+         */
+        private static RectF getInsetsToDrawInFullscreen(PreviewPositionHelper pph, DeviceProfile dp) {
+            return dp.isTaskbarPresent && !dp.isTaskbarPresentInApps
+                    ? pph.getClippedInsets() : EMPTY_RECT_F;
+        }
     }
 
     public class TaskIdAttributeContainer {
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index f190e27..e2774c0 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -16,8 +16,6 @@
 
 package com.android.quickstep;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-
 import static org.junit.Assert.assertTrue;
 
 import android.os.SystemProperties;
@@ -79,8 +77,7 @@
 
     private boolean isInLiveTileMode(Launcher launcher,
             LauncherInstrumentation.ContainerType expectedContainerType) {
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()
-                || expectedContainerType != LauncherInstrumentation.ContainerType.OVERVIEW) {
+        if (expectedContainerType != LauncherInstrumentation.ContainerType.OVERVIEW) {
             return false;
         }
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
similarity index 64%
rename from quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt
rename to quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
index cf3c8c9..4785350 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
@@ -20,26 +20,34 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.launcher3.DeviceProfileBaseTest
-import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper
+import com.android.quickstep.views.TaskView.FullscreenDrawParams
 import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
 import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
 
 /**
- * Test for TaskThumbnailView class.
+ * Test for FullscreenDrawParams class.
  */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class TaskThumbnailViewTest : DeviceProfileBaseTest() {
+class FullscreenDrawParamsTest : DeviceProfileBaseTest() {
 
     private var mThumbnailData: ThumbnailData = mock(ThumbnailData::class.java)
 
     private val mPreviewPositionHelper = PreviewPositionHelper()
+    private lateinit var params: FullscreenDrawParams
+
+    @Before
+    fun setup() {
+        params = FullscreenDrawParams(context)
+    }
 
     @Test
-    fun getInsetsToDrawInFullscreen_clipTaskbarSizeFromBottomForTablets() {
+    fun setFullProgress_currentDrawnInsets_clipTaskbarSizeFromBottomForTablets() {
         initializeVarsForTablet()
         val dp = newDP()
         val previewRect = Rect(0, 0, 100, 100)
@@ -49,15 +57,18 @@
         val isRtl = false
 
         mPreviewPositionHelper.updateThumbnailMatrix(previewRect, mThumbnailData, canvasWidth,
-                canvasHeight, dp, currentRotation, isRtl)
+                canvasHeight, dp.widthPx, dp.taskbarSize, dp.isTablet, currentRotation,
+                isRtl)
+        params.setProgress(/* fullscreenProgress= */ 1.0f, /* parentScale= */ 1.0f,
+                /* taskViewScale= */ 1.0f,  /* previewWidth= */ 0, dp, mPreviewPositionHelper)
 
         val expectedClippedInsets = RectF(0f, 0f, 0f, dp.taskbarSize / 2f)
-        assertThat(mPreviewPositionHelper.getInsetsToDrawInFullscreen(dp))
+        assertThat(params.mCurrentDrawnInsets)
                 .isEqualTo(expectedClippedInsets)
     }
 
     @Test
-    fun getInsetsToDrawInFullscreen_doNotClipTaskbarSizeFromBottomForPhones() {
+    fun setFullProgress_currentDrawnInsets_doNotClipTaskbarSizeFromBottomForPhones() {
         initializeVarsForPhone()
         val dp = newDP()
         val previewRect = Rect(0, 0, 100, 100)
@@ -67,10 +78,13 @@
         val isRtl = false
 
         mPreviewPositionHelper.updateThumbnailMatrix(previewRect, mThumbnailData, canvasWidth,
-                canvasHeight, dp, currentRotation, isRtl)
+                canvasHeight, dp.widthPx, dp.taskbarSize, dp.isTablet, currentRotation,
+                isRtl)
+        params.setProgress(/* fullscreenProgress= */ 1.0f, /* parentScale= */ 1.0f,
+                /* taskViewScale= */ 1.0f,  /* previewWidth= */ 0, dp, mPreviewPositionHelper)
 
         val expectedClippedInsets = RectF(0f, 0f, 0f, 0f)
-        assertThat(mPreviewPositionHelper.getInsetsToDrawInFullscreen(dp))
+        assertThat(params.mCurrentDrawnInsets)
                 .isEqualTo(expectedClippedInsets)
     }
 }
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 42e9be3..cc561c6 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -309,6 +309,28 @@
         launchedAppState.switchToOverview();
     }
 
+    @Test
+    @ScreenRecord // b/242163205
+    public void testQuickSwitchToPreviousAppForTablet() throws Exception {
+        assumeTrue(mLauncher.isTablet());
+        startTestActivity(2);
+        startImeTestActivity();
+
+        // Set ignoreTaskbarVisibility to true to verify the task bar visibility explicitly.
+        mLauncher.setIgnoreTaskbarVisibility(true);
+
+        // Expect task bar invisible when the launched app was the IME activity.
+        LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
+        launchedAppState.assertTaskbarHidden();
+
+        // Quick-switch to the test app with swiping to right.
+        launchedAppState.quickSwitchToPreviousApp();
+
+        // Expect task bar visible when the launched app was the test activity.
+        launchedAppState = getAndAssertLaunchedApp();
+        launchedAppState.assertTaskbarVisible();
+    }
+
     private boolean isTestActivityRunning(int activityNumber) {
         return mDevice.wait(Until.hasObject(By.pkg(getAppPackageName())
                         .text("TestActivity" + activityNumber)),
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 4dee6e7..39f93d9 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -34,8 +34,7 @@
         android:clipChildren="false"
         android:orientation="horizontal"
         android:paddingLeft="12dp"
-        android:paddingRight="12dp"
-        android:alpha="0">
+        android:paddingRight="12dp">
 
         <com.android.launcher3.folder.FolderNameEditText
             android:id="@+id/folder_name"
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index eee5ae2..0a4943e 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -109,7 +109,7 @@
     <string name="notification_dots_title" msgid="9062440428204120317">"জাননী বিন্দু"</string>
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"অন আছে"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"অফ আছে"</string>
-    <string name="title_missing_notification_access" msgid="7503287056163941064">"জাননী চাবলৈ অনুমতিৰ প্ৰয়োজন"</string>
+    <string name="title_missing_notification_access" msgid="7503287056163941064">"জাননীৰ এক্সেছৰ প্ৰয়োজন"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"জাননী সম্পৰ্কীয় বিন্দুবোৰ দেখুৱাবলৈ <xliff:g id="NAME">%1$s</xliff:g>ৰ বাবে এপৰ জাননীসমূহ অন কৰক"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"ছেটিং সলনি কৰক"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"জাননী বিন্দু দেখুৱাওক"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index e70466f..605278f 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -58,7 +58,7 @@
     <string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"విడ్జెట్ సెట్టింగ్‌లను మార్చండి"</string>
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"యాప్‌ల కోసం సెర్చ్ చేయండి"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"అప్లికేషన్‌లను లోడ్ చేస్తోంది…"</string>
-    <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"కి సరిపోలే అప్లికేషన్‌లేవీ కనుగొనబడలేదు"</string>
+    <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"కి మ్యాచ్ అయ్యే అప్లికేషన్‌లేవీ కనుగొనబడలేదు"</string>
     <string name="label_application" msgid="8531721983832654978">"యాప్"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"అన్ని యాప్‌లు"</string>
     <string name="notifications_header" msgid="1404149926117359025">"నోటిఫికేషన్‌లు"</string>
@@ -146,12 +146,12 @@
     <string name="create_folder_with" msgid="4050141361160214248">"ఈ పేరుతో ఫోల్డర్‌ను క్రియేట్ చేయండి: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"ఫోల్డర్ క్రియేట్ చేయబడింది"</string>
     <string name="action_move_to_workspace" msgid="39528912300293768">"మొదటి స్క్రీన్‌కు తరలించండి"</string>
-    <string name="action_resize" msgid="1802976324781771067">"పరిమాణం మార్చు"</string>
+    <string name="action_resize" msgid="1802976324781771067">"సైజ్‌ మార్చు"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"వెడల్పును పెంచు"</string>
     <string name="action_increase_height" msgid="459390020612501122">"ఎత్తును పెంచు"</string>
     <string name="action_decrease_width" msgid="1374549771083094654">"వెడల్పును తగ్గించు"</string>
     <string name="action_decrease_height" msgid="282377193880900022">"ఎత్తును తగ్గించు"</string>
-    <string name="widget_resized" msgid="9130327887929620">"విడ్జెట్ పరిమాణం వెడల్పు <xliff:g id="NUMBER_0">%1$s</xliff:g>కి, ఎత్తు <xliff:g id="NUMBER_1">%2$s</xliff:g>కి మార్చబడింది"</string>
+    <string name="widget_resized" msgid="9130327887929620">"విడ్జెట్ సైజ్‌ వెడల్పు <xliff:g id="NUMBER_0">%1$s</xliff:g>కి, ఎత్తు <xliff:g id="NUMBER_1">%2$s</xliff:g>కి మార్చబడింది"</string>
     <string name="action_deep_shortcut" msgid="2864038805849372848">"షార్ట్‌కట్స్"</string>
     <string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"షార్ట్‌కట్‌లు మరియు నోటిఫికేషన్‌లు"</string>
     <string name="action_dismiss_notification" msgid="5909461085055959187">"తీసివేయండి"</string>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 5fb8925..c0a00c2 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -942,6 +942,11 @@
         return mIconSize;
     }
 
+    public boolean isDisplaySearchResult() {
+        return mDisplay == DISPLAY_SEARCH_RESULT ||
+                mDisplay == DISPLAY_SEARCH_RESULT_SMALL;
+    }
+
     private void updateTranslation() {
         super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
                 + mTranslationForMoveFromCenterAnimation.x
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 4cad919..7881a26 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -43,6 +43,7 @@
 
 import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.DevicePaddings.DevicePadding;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.model.data.ItemInfo;
@@ -1271,12 +1272,17 @@
      * Returns the number of pixels required below OverviewActions excluding insets.
      */
     public int getOverviewActionsClaimedSpaceBelow() {
-        if (isTaskbarPresent && !isGestureMode) {
+        if (isTaskbarPresent && !isGestureMode
+                // If taskbar is in overview, overview action has dedicated space above nav buttons
+                && !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
             // Align vertically to where nav buttons are.
             return ((taskbarSize - overviewActionsHeight) / 2) + getTaskbarOffsetY();
         }
 
-        return isTaskbarPresent ? stashedTaskbarSize : mInsets.bottom;
+        if (isTaskbarPresent) {
+            return FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get() ? taskbarSize : stashedTaskbarSize;
+        }
+        return mInsets.bottom;
     }
 
     /** Gets the space that the overview actions will take, including bottom margin. */
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index a6831aa..447745e 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1579,6 +1579,14 @@
         return mOldConfig.orientation;
     }
 
+    /**
+     * Whether keyboard sync is enabled for transitions between Home and All Apps.
+     * TODO(b/251387263): move this method inside an All Apps specific config class.
+     */
+    public boolean isKeyboardSyncEnabled() {
+        return false;
+    }
+
     @Override
     protected void onNewIntent(Intent intent) {
         if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
@@ -2742,6 +2750,8 @@
         getViewCache().setCacheSize(R.layout.folder_page, 2);
 
         TraceHelper.INSTANCE.endSection(traceToken);
+
+        mWorkspace.removeExtraEmptyScreen(true);
     }
 
     private boolean canAnimatePageChange() {
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index a20ff8c..58e85fe 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -85,6 +85,7 @@
 import java.util.Locale;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 public class LauncherProvider extends ContentProvider {
     private static final String TAG = "LauncherProvider";
@@ -944,16 +945,28 @@
             final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db,
                     Favorites.TABLE_NAME, Favorites.APPWIDGET_ID,
                     "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null));
+            boolean isAnyWidgetRemoved = false;
             for (int widgetId : allWidgets) {
                 if (!validWidgets.contains(widgetId)) {
                     try {
                         FileLog.d(TAG, "Deleting invalid widget " + widgetId);
                         host.deleteAppWidgetId(widgetId);
+                        isAnyWidgetRemoved = true;
                     } catch (RuntimeException e) {
                         // Ignore
                     }
                 }
             }
+            if (isAnyWidgetRemoved) {
+                final String allWidgetsIds = Arrays.stream(allWidgets).mapToObj(String::valueOf)
+                        .collect(Collectors.joining(",", "[", "]"));
+                final String validWidgetsIds = Arrays.stream(
+                        validWidgets.getArray().toArray()).mapToObj(String::valueOf)
+                        .collect(Collectors.joining(",", "[", "]"));
+                FileLog.d(TAG, "One or more widgets was removed. db_path=" + db.getPath()
+                        + " allWidgetsIds=" + allWidgetsIds
+                        + ", validWidgetsIds=" + validWidgetsIds);
+            }
         }
 
         /**
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 68c54c7..eb68adb 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -772,6 +772,13 @@
         }
 
         if (mScroller.isFinished() && pageScrollChanged) {
+            // TODO(b/246283207): Remove logging once root cause of flake detected.
+            if (Utilities.IS_RUNNING_IN_TEST_HARNESS && !(this instanceof Workspace)) {
+                Log.d("b/246283207", this.getClass().getSimpleName() + "#onLayout() -> "
+                        + "if(mScroller.isFinished() && pageScrollChanged) -> getNextPage(): "
+                        + getNextPage() + ", getScrollForPage(getNextPage()): "
+                        + getScrollForPage(getNextPage()));
+            }
             setCurrentPage(getNextPage());
         }
         onPageScrollsInitialized();
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 616b08a..f70511a 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -819,16 +819,6 @@
     }
 
     /**
-     * Compares the ratio of two quantities and returns whether that ratio is greater than the
-     * provided bound. Order of quantities does not matter. Bound should be a decimal representation
-     * of a percentage.
-     */
-    public static boolean isRelativePercentDifferenceGreaterThan(float first, float second,
-            float bound) {
-        return (Math.abs(first - second) / Math.abs((first + second) / 2.0f)) > bound;
-    }
-
-    /**
      * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually CCW. Parent
      * sizes represent the "space" that will rotate carrying inOutBounds along with it to determine
      * the final bounds.
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 13387c6..6aff83e 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -107,6 +107,7 @@
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.WallpaperOffsetInterpolator;
+import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.LauncherAppWidgetHost;
 import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -153,6 +154,8 @@
 
     public static final int DEFAULT_PAGE = 0;
 
+    private final int mAllAppsIconSize;
+
     private LayoutTransition mLayoutTransition;
     @Thunk final WallpaperManager mWallpaperManager;
 
@@ -287,7 +290,7 @@
         mLauncher = Launcher.getLauncher(context);
         mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
         mWallpaperManager = WallpaperManager.getInstance(context);
-
+        mAllAppsIconSize = mLauncher.getDeviceProfile().allAppsIconSizePx;
         mWallpaperOffset = new WallpaperOffsetInterpolator(this);
 
         setHapticFeedbackEnabled(false);
@@ -1672,8 +1675,14 @@
             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
         }
 
-        if (child instanceof BubbleTextView && !dragOptions.isAccessibleDrag) {
-            dragOptions.preDragCondition = ((BubbleTextView) child).startLongPressAction();
+        if (child instanceof BubbleTextView) {
+            BubbleTextView btv = (BubbleTextView) child;
+            if (!dragOptions.isAccessibleDrag) {
+                dragOptions.preDragCondition = btv.startLongPressAction();
+            }
+            if (btv.isDisplaySearchResult()) {
+                dragOptions.preDragEndScale = (float) mAllAppsIconSize / btv.getIconSize();
+            }
         }
 
         final DragView dv;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 872c4fd..001b494 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -39,7 +39,6 @@
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.MultiPropertyFactory;
@@ -229,7 +228,7 @@
             StateAnimationConfig config, PendingAnimation builder) {
         if (mLauncher.isInState(ALL_APPS) && !ALL_APPS.equals(toState)) {
             // For atomic animations, we close the keyboard immediately.
-            if (!config.userControlled && !FeatureFlags.ENABLE_KEYBOARD_TRANSITION_SYNC.get()) {
+            if (!config.userControlled && !mLauncher.isKeyboardSyncEnabled()) {
                 mLauncher.getAppsView().getSearchUiManager().getEditText().hideKeyboard();
             }
 
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index f082542..7f6247e 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -532,9 +532,11 @@
         if (isSearching()) {
             getSearchRecyclerView().setVisibility(VISIBLE);
             getAppsRecyclerViewContainer().setVisibility(GONE);
+            mHeader.setVisibility(GONE);
         } else {
             getSearchRecyclerView().setVisibility(GONE);
             getAppsRecyclerViewContainer().setVisibility(VISIBLE);
+            mHeader.setVisibility(VISIBLE);
         }
         if (mHeader.isSetUp()) {
             mHeader.setActiveRV(getCurrentPage());
@@ -797,6 +799,17 @@
         return mActivityContext.getDeviceProfile().isTablet ? mBottomSheetBackground : this;
     }
 
+    /**
+     * Sets whether the view or its children should react to the window inset.
+     * Used for when exiting all apps -> workspace and determines if window inset
+     * should be applied.. ex) the work mode switch.
+     */
+    public void setApplyWindowInset(boolean shouldApplyWindowInset) {
+        if (mWorkManager.getWorkModeSwitch() != null) {
+            mWorkManager.getWorkModeSwitch().setApplyWindowInset(shouldApplyWindowInset);
+        }
+    }
+
     /** Holds a {@link BaseAllAppsAdapter} and related fields. */
     public class AdapterHolder {
         public static final int MAIN = 0;
diff --git a/src/com/android/launcher3/allapps/SearchTransitionController.java b/src/com/android/launcher3/allapps/SearchTransitionController.java
index a1f5bc6..bde4fa6 100644
--- a/src/com/android/launcher3/allapps/SearchTransitionController.java
+++ b/src/com/android/launcher3/allapps/SearchTransitionController.java
@@ -20,6 +20,7 @@
 
 import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
 
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
@@ -38,6 +39,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.model.data.ItemInfo;
 
 /** Coordinates the transition between Search and A-Z in All Apps. */
 public class SearchTransitionController {
@@ -124,6 +126,7 @@
         mSearchToAzAnimator.addListener(forSuccessCallback(onEndRunnable));
 
         mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(true);
+        mAllAppsContainerView.getFloatingHeaderView().setVisibility(VISIBLE);
         mAllAppsContainerView.getAppsRecyclerViewContainer().setVisibility(VISIBLE);
         getSearchRecyclerView().setVisibility(VISIBLE);
         getSearchRecyclerView().setChildAttachedConsumer(this::onSearchChildAttached);
@@ -186,7 +189,9 @@
                 top = searchResultView.getTop();
             }
 
-            if (searchResultView instanceof BubbleTextView) {
+            if (searchResultView instanceof BubbleTextView
+                    && searchResultView.getTag() instanceof ItemInfo
+                    && ((ItemInfo) searchResultView.getTag()).itemType == ITEM_TYPE_APPLICATION) {
                 // The first app icon will set appRowHeight, which will also contribute to
                 // totalHeight. Additional app icons should remove the appRowHeight to remain in
                 // the same row as the first app.
@@ -196,6 +201,8 @@
                     totalHeight += appRowHeight;
                 }
                 // Don't scale/fade app row.
+                searchResultView.setScaleY(1);
+                searchResultView.setAlpha(1);
                 continue;
             }
 
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index 15fb77c..0a938b2 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -52,6 +52,7 @@
     private int mFlags;
     private boolean mWorkEnabled;
     private boolean mOnWorkTab;
+    private boolean mApplyWindowInset;
 
     public WorkModeSwitch(Context context) {
         this(context, null, 0);
@@ -168,7 +169,7 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        if (!Utilities.ATLEAST_R) {
+        if (!Utilities.ATLEAST_R || !mApplyWindowInset) {
             return insets;
         }
         if (insets.isVisible(WindowInsets.Type.ime())) {
@@ -197,4 +198,8 @@
     private void removeFlag(int flag) {
         mFlags &= ~flag;
     }
+
+    public void setApplyWindowInset(boolean applyWindowInset){
+        mApplyWindowInset = applyWindowInset;
+    }
 }
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 886460e..4c461aa 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -84,7 +84,10 @@
         mTextConversions = extractTextConversions(s);
     }
 
-    private static String[] extractTextConversions(CharSequence text) {
+    /**
+     * Extract text conversions from composing text and send them for search.
+     */
+    public static String[] extractTextConversions(CharSequence text) {
         if (text instanceof SpannableStringBuilder) {
             SpannableStringBuilder spanned = (SpannableStringBuilder) text;
             SuggestionSpan[] suggestionSpans =
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 50cf8d6..030ac7e 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -37,7 +37,8 @@
 
     public static final String FLAGS_PREF_NAME = "featureFlags";
 
-    private FeatureFlags() { }
+    private FeatureFlags() {
+    }
 
     public static boolean showFlagTogglerUi(Context context) {
         return Utilities.IS_DEBUG_DEVICE && Utilities.isDevelopersOptionsEnabled(context);
@@ -61,7 +62,7 @@
      * To add a new flag that can be toggled through the flags UI:
      *
      * Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"),
-     *    and set a default value for the flag. This will be the default value on Debug builds.
+     * and set a default value for the flag. This will be the default value on Debug builds.
      */
     public static final BooleanFlag ENABLE_INPUT_CONSUMER_REASON_LOGGING = getDebugFlag(
             "ENABLE_INPUT_CONSUMER_REASON_LOGGING",
@@ -84,9 +85,6 @@
     public static final BooleanFlag KEYGUARD_ANIMATION = getDebugFlag(
             "KEYGUARD_ANIMATION", false, "Enable animation for keyguard going away on wallpaper");
 
-    public static final BooleanFlag ENABLE_QUICKSTEP_LIVE_TILE = getDebugFlag(
-            "ENABLE_QUICKSTEP_LIVE_TILE", true, "Enable live tile in Quickstep overview");
-
     public static final BooleanFlag ENABLE_DEVICE_SEARCH = new DeviceFlag(
             "ENABLE_DEVICE_SEARCH", true, "Allows on device search in all apps");
 
@@ -94,6 +92,10 @@
             getDebugFlag("ENABLE_FLOATING_SEARCH_BAR", false,
                     "Keep All Apps search bar at the bottom (but above keyboard if open)");
 
+    public static final BooleanFlag ENABLE_QUICK_LAUNCH_V2 = new DeviceFlag(
+            "ENABLE_QUICK_LAUNCH_V2", false, "Use quick launch v2 "
+            + "behavior. Quick search and quick launch v1 would be unavailable if this is enabled");
+
     public static final BooleanFlag ENABLE_HIDE_HEADER = new DeviceFlag("ENABLE_HIDE_HEADER",
             true, "Hide header on keyboard before typing in all apps");
 
@@ -157,14 +159,14 @@
     /**
      * Enables region sampling for text color: Needs system health assessment before turning on
      */
-    public static final BooleanFlag ENABLE_REGION_SAMPLING =  getDebugFlag(
+    public static final BooleanFlag ENABLE_REGION_SAMPLING = getDebugFlag(
             "ENABLE_REGION_SAMPLING", false,
             "Enable region sampling to determine color of text on screen.");
 
     public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS =
             getDebugFlag(
-            "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
-            "Always use hardware optimization for folder animations.");
+                    "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
+                    "Always use hardware optimization for folder animations.");
 
     public static final BooleanFlag SEPARATE_RECENTS_ACTIVITY = getDebugFlag(
             "SEPARATE_RECENTS_ACTIVITY", false,
@@ -238,6 +240,10 @@
             "ENABLE_ALL_APPS_ONE_SEARCH_IN_TASKBAR", false,
             "Enables One Search box in Taskbar All Apps.");
 
+    public static final BooleanFlag ENABLE_TASKBAR_IN_OVERVIEW = getDebugFlag(
+            "ENABLE_TASKBAR_IN_OVERVIEW", true,
+            "Enables accessing the system Taskbar in overview.");
+
     public static final BooleanFlag ENABLE_SPLIT_FROM_WORKSPACE = getDebugFlag(
             "ENABLE_SPLIT_FROM_WORKSPACE", true,
             "Enable initiating split screen from workspace.");
@@ -249,11 +255,6 @@
     public static final BooleanFlag ENABLE_ONE_SEARCH_MOTION = new DeviceFlag(
             "ENABLE_ONE_SEARCH_MOTION", true, "Enables animations in OneSearch.");
 
-    public static final BooleanFlag ENABLE_KEYBOARD_TRANSITION_SYNC = new DeviceFlag(
-            "ENABLE_KEYBOARD_TRANSITION_SYNC", IS_STUDIO_BUILD,
-            "Enable option to synchronize the keyboard open and close animations when transitioning"
-                    + " between home and all apps");
-
     public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = new DeviceFlag(
             "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", true,
             "Enable option to show keyboard when going to all-apps");
@@ -292,6 +293,13 @@
     public static final BooleanFlag POPUP_MATERIAL_U = new DeviceFlag(
             "POPUP_MATERIAL_U", false, "Switch popup UX to use material U");
 
+    public static final BooleanFlag SHOW_HOME_GARDENING = new DeviceFlag(
+            "SHOW_HOME_GARDENING", false,
+            "Show the new home gardening mode");
+
+    public static final BooleanFlag ENABLE_TRANSIENT_TASKBAR = getDebugFlag(
+            "ENABLE_TRANSIENT_TASKBAR", false, "Enables transient taskbar.");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 8616f35..5368397 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -31,6 +31,7 @@
 
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -90,6 +91,8 @@
 
     protected boolean mIsInPreDrag;
 
+    private final int DRAG_VIEW_SCALE_DURATION_MS = 500;
+
     /**
      * Interface to receive notifications when a drag starts or stops
      */
@@ -214,6 +217,15 @@
             mOptions.preDragCondition.onPreDragEnd(mDragObject, true /* dragStarted*/);
         }
         mIsInPreDrag = false;
+        if (mOptions.preDragEndScale != 0) {
+            mDragObject.dragView
+                    .animate()
+                    .scaleX(mOptions.preDragEndScale)
+                    .scaleY(mOptions.preDragEndScale)
+                    .setInterpolator(Interpolators.EMPHASIZED)
+                    .setDuration(DRAG_VIEW_SCALE_DURATION_MS)
+                    .start();
+        }
         mDragObject.dragView.onDragStart();
         for (DragListener listener : new ArrayList<>(mListeners)) {
             listener.onDragStart(mDragObject, mOptions);
@@ -295,9 +307,9 @@
                 } else if (mIsInPreDrag) {
                     animateDragViewToOriginalPosition(null, null, -1);
                 }
+                mDragObject.dragView.clearAnimation();
                 mDragObject.dragView = null;
             }
-
             // Only end the drag if we are not deferred
             if (!isDeferred) {
                 callOnDragEnd();
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index e8ff8da..1ff4335 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -40,6 +40,12 @@
     /** Determines when a pre-drag should transition to a drag. By default, this is immediate. */
     public PreDragCondition preDragCondition = null;
 
+    /**
+     * A drag scale that scales the original drag view size when the preDragCondition is met (or
+     * is ignored if preDragEndScale is 0).
+     */
+    public float preDragEndScale;
+
     /** Scale of the icons over the workspace icon size. */
     public float intrinsicIconScaleFactor = 1f;
 
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
index fb8a1bc..08e50dd 100644
--- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
@@ -20,12 +20,14 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.OnAlarmListener;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 
 public class SpringLoadedDragController implements OnAlarmListener {
     // how long the user must hover over a mini-screen before it unshrinks
-    final long ENTER_SPRING_LOAD_HOVER_TIME = 500;
-    final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950;
+    private static final long ENTER_SPRING_LOAD_HOVER_TIME = 500;
+    private static final long ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST = 1500;
+    private static final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950;
 
     Alarm mAlarm;
 
@@ -39,6 +41,13 @@
         mAlarm.setOnAlarmListener(this);
     }
 
+    private long getEnterSpringLoadHoverTime() {
+        // Some TAPL tests are flaky on Cuttlefish with a low waiting time
+        return Utilities.IS_RUNNING_IN_TEST_HARNESS
+                ? ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST
+                : ENTER_SPRING_LOAD_HOVER_TIME;
+    }
+
     public void cancel() {
         mAlarm.cancelAlarm();
     }
@@ -46,8 +55,8 @@
     // Set a new alarm to expire for the screen that we are hovering over now
     public void setAlarm(CellLayout cl) {
         mAlarm.cancelAlarm();
-        mAlarm.setAlarm((cl == null) ? ENTER_SPRING_LOAD_CANCEL_HOVER_TIME :
-            ENTER_SPRING_LOAD_HOVER_TIME);
+        mAlarm.setAlarm((cl == null) ? ENTER_SPRING_LOAD_CANCEL_HOVER_TIME
+                : getEnterSpringLoadHoverTime());
         mScreen = cl;
     }
 
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 706140a..d5ef9df 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -216,6 +216,7 @@
         final int footerStartDelay;
         if (isLargeFolder()) {
             if (mIsOpening) {
+                mFolder.mFooter.setAlpha(0);
                 footerAlphaDuration = LARGE_FOLDER_FOOTER_DURATION;
                 footerStartDelay = mDuration - footerAlphaDuration;
             } else {
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 466f63f..a8e9eb5 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -350,9 +350,11 @@
                 break;
             case ITEM_TYPE_TASK:
                 itemBuilder
-                        .setTask(LauncherAtom.Task.newBuilder()
-                                .setComponentName(getTargetComponent().flattenToShortString())
-                                .setIndex(screenId));
+                        .setTask(nullableComponent
+                                .map(component -> LauncherAtom.Task.newBuilder()
+                                        .setComponentName(component.flattenToShortString())
+                                        .setIndex(screenId))
+                                .orElse(LauncherAtom.Task.newBuilder()));
                 break;
             default:
                 break;
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 9afca4f..ceebc2e 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -504,9 +504,9 @@
             DeviceProfile dp, boolean isRtl) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
-        int dividerBar = splitBoundsConfig.appsStackedVertically
-                ? (int) (splitBoundsConfig.dividerHeightPercent * parentHeight)
-                : (int) (splitBoundsConfig.dividerWidthPercent * parentWidth);
+        int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
+                ? splitBoundsConfig.dividerHeightPercent
+                : splitBoundsConfig.dividerWidthPercent));
         int primarySnapshotHeight;
         int primarySnapshotWidth;
         int secondarySnapshotHeight;
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index bc1b634..5efebaa 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -50,6 +50,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
@@ -524,7 +525,9 @@
         out.setRotation(getDegreesRotated());
         int distanceToEdge;
         if ((DisplayController.getNavigationMode(out.getContext()) == THREE_BUTTONS)
-                && (dp.isTwoPanels || dp.isTablet)) {
+                && (dp.isTwoPanels || dp.isTablet)
+                // If taskbar is in overview, overview action has dedicated space above nav buttons
+                && !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
             // If 3-button nav is active, align the splitInstructionsView with it.
             distanceToEdge = dp.getTaskbarOffsetY()
                     + ((dp.taskbarSize - splitInstructionsHeight) / 2);
@@ -561,8 +564,12 @@
         int insetCorrectionX = (dp.getInsets().right - dp.getInsets().left) / 2;
         // Adjust for any insets on the bottom edge
         int insetCorrectionY = dp.getInsets().bottom;
+        // Adjust for taskbar in overview
+        int taskbarCorrectionY =
+                dp.isTaskbarPresent && FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()
+                        ? dp.taskbarSize : 0;
         out.setTranslationX(insetCorrectionX + threeButtonNavShift);
-        out.setTranslationY(-distanceToEdge + insetCorrectionY);
+        out.setTranslationY(-distanceToEdge + insetCorrectionY - taskbarCorrectionY);
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
         lp.gravity = CENTER_HORIZONTAL | BOTTOM;
         out.setLayoutParams(lp);
@@ -611,15 +618,17 @@
 
         if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
             if (isLandscape) {
-                outRect.right = outRect.left + (int) (outRect.width() * topLeftTaskPercent);
+                outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent);
             } else {
-                outRect.bottom = outRect.top + (int) (outRect.height() * topLeftTaskPercent);
+                outRect.bottom = outRect.top + Math.round(outRect.height() * topLeftTaskPercent);
             }
         } else {
             if (isLandscape) {
-                outRect.left += (int) (outRect.width() * (topLeftTaskPercent + dividerBarPercent));
+                outRect.left += Math.round(outRect.width()
+                        * (topLeftTaskPercent + dividerBarPercent));
             } else {
-                outRect.top += (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
+                outRect.top += Math.round(outRect.height()
+                        * (topLeftTaskPercent + dividerBarPercent));
             }
         }
     }
@@ -630,9 +639,9 @@
             DeviceProfile dp, boolean isRtl) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
-        int dividerBar = splitBoundsConfig.appsStackedVertically
-                ? (int) (splitBoundsConfig.dividerHeightPercent * parentHeight)
-                : (int) (splitBoundsConfig.dividerWidthPercent * parentWidth);
+        int dividerBar = Math.round(splitBoundsConfig.appsStackedVertically
+                ? splitBoundsConfig.dividerHeightPercent * totalThumbnailHeight
+                : splitBoundsConfig.dividerWidthPercent * parentWidth);
         int primarySnapshotHeight;
         int primarySnapshotWidth;
         int secondarySnapshotHeight;
@@ -641,7 +650,7 @@
                 splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent;
         if (dp.isLandscape) {
             primarySnapshotHeight = totalThumbnailHeight;
-            primarySnapshotWidth = (int) (parentWidth * taskPercent);
+            primarySnapshotWidth = Math.round(parentWidth * taskPercent);
 
             secondarySnapshotHeight = totalThumbnailHeight;
             secondarySnapshotWidth = parentWidth - primarySnapshotWidth - dividerBar;
@@ -656,12 +665,22 @@
             secondarySnapshot.setTranslationY(spaceAboveSnapshot);
         } else {
             primarySnapshotWidth = parentWidth;
-            primarySnapshotHeight = (int) (totalThumbnailHeight * taskPercent);
+            primarySnapshotHeight = Math.round(totalThumbnailHeight * taskPercent);
 
             secondarySnapshotWidth = parentWidth;
             secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
             int translationY = primarySnapshotHeight + spaceAboveSnapshot + dividerBar;
             secondarySnapshot.setTranslationY(translationY);
+
+            FrameLayout.LayoutParams primaryParams =
+                    (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
+            FrameLayout.LayoutParams secondaryParams =
+                    (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
+            secondaryParams.topMargin = 0;
+            primaryParams.topMargin = spaceAboveSnapshot;
+
+            // Reset unused translations
+            primarySnapshot.setTranslationY(0);
             secondarySnapshot.setTranslationX(0);
             primarySnapshot.setTranslationX(0);
         }
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 55bb5e8..a616a8b 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -121,9 +121,9 @@
         // the screen. This is to preserve consistency when the user rotates: From the user's POV,
         // the primary should always be on the left.
         if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
-            outRect.top += (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
+            outRect.top += (int) (outRect.height() * ((1 - topLeftTaskPercent)));
         } else {
-            outRect.bottom = outRect.top + (int) (outRect.height() * topLeftTaskPercent);
+            outRect.bottom -= (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
         }
     }
 
@@ -266,6 +266,49 @@
         secondaryIconView.setLayoutParams(secondaryIconParams);
     }
 
+    @Override
+    public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
+            int parentWidth, int parentHeight, SplitBounds splitBoundsConfig, DeviceProfile dp,
+            boolean isRtl) {
+        FrameLayout.LayoutParams primaryParams =
+                (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
+        FrameLayout.LayoutParams secondaryParams =
+                (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
+
+        // Swap the margins that are set in TaskView#setRecentsOrientedState()
+        secondaryParams.topMargin = dp.overviewTaskThumbnailTopMarginPx;
+        primaryParams.topMargin = 0;
+
+        // Measure and layout the thumbnails bottom up, since the primary is on the visual left
+        // (portrait bottom) and secondary is on the right (portrait top)
+        int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
+        int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
+        int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
+                ? splitBoundsConfig.dividerHeightPercent
+                : splitBoundsConfig.dividerWidthPercent));
+        int primarySnapshotHeight;
+        int primarySnapshotWidth;
+        int secondarySnapshotHeight;
+        int secondarySnapshotWidth;
+
+        float taskPercent = splitBoundsConfig.appsStackedVertically ?
+                splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent;
+        primarySnapshotWidth = parentWidth;
+        primarySnapshotHeight = (int) (totalThumbnailHeight * (taskPercent));
+
+        secondarySnapshotWidth = parentWidth;
+        secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
+        secondarySnapshot.setTranslationY(0);
+        primarySnapshot.setTranslationY(secondarySnapshotHeight + spaceAboveSnapshot + dividerBar);
+        primarySnapshot.measure(
+                View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY));
+        secondarySnapshot.measure(
+                View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
+                        View.MeasureSpec.EXACTLY));
+    }
+
     /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
 
     @Override
diff --git a/src/com/android/launcher3/util/LogConfig.java b/src/com/android/launcher3/util/LogConfig.java
index ee1d1ff..5abf95c 100644
--- a/src/com/android/launcher3/util/LogConfig.java
+++ b/src/com/android/launcher3/util/LogConfig.java
@@ -50,4 +50,9 @@
      * When turned on, we enable web suggest appSearch related logging.
      */
     public static final String WEB_APP_SEARCH_LOGGING = "WebAppSearchLogging";
+
+    /**
+     * When turned on, we enable quick launch v2 related logging.
+     */
+    public static final String QUICK_LAUNCH_V2 = "QuickLaunchV2";
 }
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 800b1f6..1e154a2 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -273,6 +273,12 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (ev.getActionIndex() > 0) {
+            // This means there is multiple touch inputs, ignore it, we could also cancel the
+            // previous touch but the user might cancel the drag by accident.
+            return true;
+        }
+
         switch (ev.getAction()) {
             case ACTION_DOWN: {
                 if ((mTouchDispatchState & TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS) != 0) {
diff --git a/tests/Android.bp b/tests/Android.bp
index 1584308..39bd307 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -60,6 +60,7 @@
       "src/com/android/launcher3/testcomponent/CustomShortcutConfigActivity.java",
       "src/com/android/launcher3/testcomponent/TestCommandReceiver.java",
       "src/com/android/launcher3/testcomponent/TestLauncherActivity.java",
+      "src/com/android/launcher3/testcomponent/ImeTestActivity.java",
     ],
 }
 
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 9cc3aed..ae1060e 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -277,6 +277,16 @@
             </intent-filter>
         </activity-alias>
 
+        <activity android:name="com.android.launcher3.testcomponent.ImeTestActivity"
+            android:label="ImeTestActivity"
+            android:icon="@drawable/test_theme_icon"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
         <!-- [b/197780098] Disable eager initialization of Jetpack libraries. -->
         <provider
             android:name="androidx.startup.InitializationProvider"
diff --git a/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java
index a222d3d..047d342 100644
--- a/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java
+++ b/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java
@@ -63,25 +63,7 @@
             MOVE_TO_6x5,
             END_BOARD_STR_6x5);
 
-    /** 4x4 Test
-     **/
-    private static final String START_BOARD_STR_4x4 = ""
-            + "xxxx\n"
-            + "34-m\n"
-            + "3511\n"
-            + "3211";
-    private static final Point MOVE_TO_4x4 = new Point(1, 2);
-    private static final String END_BOARD_STR_4x4 = ""
-            + "xxxx\n"
-            + "345-\n"
-            + "3m11\n"
-            + "3211";
-    private static final ReorderTestCase TEST_CASE_4x4 = new ReorderTestCase(START_BOARD_STR_4x4,
-            MOVE_TO_4x4,
-            END_BOARD_STR_4x4);
-
     public static final Map<Point, ReorderTestCase> TEST_BY_GRID_SIZE =
             Map.of(new Point(5, 5), TEST_CASE_5x5,
-                    new Point(6, 5), TEST_CASE_6x5,
-                    new Point(4, 4), TEST_CASE_4x4);
+                    new Point(6, 5), TEST_CASE_6x5);
 }
diff --git a/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java
index e16ff42..38c9aee 100644
--- a/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java
+++ b/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java
@@ -64,25 +64,7 @@
             MOVE_TO_6x5,
             END_BOARD_STR_6x5);
 
-    /** 4x4 Test
-     **/
-    private static final String START_BOARD_STR_4x4 = ""
-            + "xxxx\n"
-            + "222m\n"
-            + "-111\n"
-            + "----";
-    private static final Point MOVE_TO_4x4 = new Point(2, 1);
-    private static final String END_BOARD_STR_4x4 = ""
-            + "xxxx\n"
-            + "--m-\n"
-            + "222-\n"
-            + "-111";
-    private static final ReorderTestCase TEST_CASE_4x4 = new ReorderTestCase(START_BOARD_STR_4x4,
-            MOVE_TO_4x4,
-            END_BOARD_STR_4x4);
-
     public static final Map<Point, ReorderTestCase> TEST_BY_GRID_SIZE =
             Map.of(new Point(5, 5), TEST_CASE_5x5,
-                    new Point(6, 5), TEST_CASE_6x5,
-                    new Point(4, 4), TEST_CASE_4x4);
+                    new Point(6, 5), TEST_CASE_6x5);
 }
diff --git a/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
index 9c6d102..d3ce67c 100644
--- a/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
+++ b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
@@ -24,7 +24,9 @@
 import android.os.Bundle;
 import android.util.TypedValue;
 import android.view.View;
+import android.view.WindowInsets;
 import android.widget.Button;
+import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.LinearLayout.LayoutParams;
 
@@ -81,6 +83,20 @@
         mView.addView(button, lp);
     }
 
+    protected void addEditor(String initText, String hint, boolean requestIme) {
+        EditText editText = new EditText(this);
+        editText.setHint(hint);
+        editText.setText(initText);
+
+        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+        lp.bottomMargin = mMargin;
+        mView.addView(editText, lp);
+        if (requestIme) {
+            editText.requestFocus();
+            mView.getWindowInsetsController().show(WindowInsets.Type.ime());
+        }
+    }
+
     @Override
     protected void onResume() {
         super.onResume();
diff --git a/tests/src/com/android/launcher3/testcomponent/ImeTestActivity.java b/tests/src/com/android/launcher3/testcomponent/ImeTestActivity.java
new file mode 100644
index 0000000..43952d5
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/ImeTestActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.testcomponent;
+
+import android.os.Bundle;
+
+public class ImeTestActivity extends OtherBaseTestingActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // Requests to focus an editor and show IME for test.
+        addEditor("Focused editor for test", "Focused editor for test", true);
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 2d519d9..70d122b 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -479,6 +479,16 @@
                 false /* newTask */);
     }
 
+    public static void startImeTestActivity() {
+        final String packageName = getAppPackageName();
+        final Intent intent = getInstrumentation().getContext().getPackageManager().
+                getLaunchIntentForPackage(packageName);
+        intent.setComponent(new ComponentName(packageName,
+                "com.android.launcher3.testcomponent.ImeTestActivity"));
+        startIntent(intent, By.pkg(packageName).text("ImeTestActivity"),
+                false /* newTask */);
+    }
+
     private static void startIntent(Intent intent, BySelector selector, boolean newTask) {
         intent.addCategory(Intent.CATEGORY_LAUNCHER);
         if (newTask) {
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 07bfe4c..cf5f5fc 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -18,8 +18,6 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
-import android.platform.test.annotations.IwTest;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -31,6 +29,7 @@
 
 import android.content.Intent;
 import android.graphics.Point;
+import android.platform.test.annotations.IwTest;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -437,6 +436,23 @@
 
     @Test
     @PortraitLandscape
+    public void testDragAndCancelAppIcon() {
+        final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(GMAIL_APP_NAME);
+        Point positionBeforeDrag =
+                mLauncher.getWorkspace().getWorkspaceIconsPositions().get(GMAIL_APP_NAME);
+        assertNotNull("App not found in Workspace before dragging.", positionBeforeDrag);
+
+        mLauncher.getWorkspace().dragAndCancelAppIcon(homeAppIcon);
+
+        Point positionAfterDrag =
+                mLauncher.getWorkspace().getWorkspaceIconsPositions().get(GMAIL_APP_NAME);
+        assertNotNull("App not found in Workspace after dragging.", positionAfterDrag);
+        assertEquals("App not returned to same position in Workspace after drag & cancel",
+                positionBeforeDrag, positionAfterDrag);
+    }
+
+    @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}) {
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index eb8d055..15705e7 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -206,21 +206,30 @@
                 MotionEvent.ACTION_UP, end, gestureScope);
     }
 
+    /**
+     * Quick switching to the app with swiping to right.
+     */
     @NonNull
     public LaunchedAppState quickSwitchToPreviousApp() {
-        boolean toRight = true;
-        quickSwitch(toRight);
+        quickSwitch(true /* toRight */);
         return new LaunchedAppState(mLauncher);
     }
 
+    /**
+     * Quick switching to the app with swiping to left.
+     */
     @NonNull
     public LaunchedAppState quickSwitchToPreviousAppSwipeLeft() {
-        boolean toRight = false;
-        quickSwitch(toRight);
+        quickSwitch(false /* toRight */);
         return new LaunchedAppState(mLauncher);
     }
 
-    @NonNull
+    /**
+     * Making swipe gesture to quick-switch app tasks.
+     *
+     * @param toRight {@code true} means swiping right, {@code false} means swiping left.
+     * @throws {@link AssertionError} when failing to verify the visible UI in the container.
+     */
     private void quickSwitch(boolean toRight) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index a17651b..4b02ecc 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -72,6 +72,16 @@
     }
 
     /**
+     * Waits for the taskbar to be visible, or fails.
+     */
+    public void assertTaskbarVisible() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "waiting for taskbar to be visible")) {
+            mLauncher.waitForLauncherObject(TASKBAR_RES_ID);
+        }
+    }
+
+    /**
      * Returns the Taskbar in a visible state.
      *
      * The taskbar must already be hidden when calling this method.
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 3986df6..0c6e391 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -138,6 +138,15 @@
         OUTSIDE_WITH_KEYCODE,
     }
 
+    /**
+     * Represents a point in the code at which a callback can run.
+     */
+    public enum CALLBACK_RUN_POINT {
+        CALLBACK_HOLD_BEFORE_DROP
+    }
+
+    private Consumer<CALLBACK_RUN_POINT> mCallbackAtRunPoint = null;
+
     // Base class for launcher containers.
     abstract static class VisibleContainer {
         protected final LauncherInstrumentation mLauncher;
@@ -185,13 +194,14 @@
     private final Deque<String> mDiagnosticContext = new LinkedList<>();
     private Function<Long, String> mSystemHealthSupplier;
 
+    private boolean mIgnoreTaskbarVisibility = false;
+
     private Consumer<ContainerType> mOnSettledStateAction;
 
     private LogEventChecker mEventChecker;
 
     private boolean mCheckEventsForSuccessfulGestures = false;
     private Runnable mOnLauncherCrashed;
-
     private static Pattern getTouchEventPattern(String prefix, String action) {
         // The pattern includes checks that we don't get a multi-touch events or other surprises.
         return Pattern.compile(
@@ -680,6 +690,18 @@
         }
     }
 
+    /**
+     * Whether to ignore verifying the task bar visibility during instrumenting.
+     *
+     * @param ignoreTaskbarVisibility {@code true} will ignore the instrumentation implicitly
+     *                                            verifying the task bar visibility with
+     *                                            {@link VisibleContainer#verifyActiveContainer}.
+     *                                            {@code false} otherwise.
+     */
+    public void setIgnoreTaskbarVisibility(boolean ignoreTaskbarVisibility) {
+        mIgnoreTaskbarVisibility = ignoreTaskbarVisibility;
+    }
+
     public void setExpectedRotation(int expectedRotation) {
         mExpectedRotation = expectedRotation;
     }
@@ -768,7 +790,7 @@
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
-                    waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+                    checkTaskbarVisibility();
                     waitUntilLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
 
                     return waitForLauncherObject(OVERVIEW_RES_ID);
@@ -777,7 +799,7 @@
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
-                    waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+                    checkTaskbarVisibility();
 
                     waitForLauncherObject(SPLIT_PLACEHOLDER_RES_ID);
                     return waitForLauncherObject(OVERVIEW_RES_ID);
@@ -786,7 +808,7 @@
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
-                    waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+                    checkTaskbarVisibility();
                     waitUntilLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
 
                     return waitForFallbackLauncherObject(OVERVIEW_RES_ID);
@@ -798,11 +820,7 @@
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
                     waitUntilLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
 
-                    if (isTablet() && !isFallbackOverview()) {
-                        waitForLauncherObject(TASKBAR_RES_ID);
-                    } else {
-                        waitUntilLauncherObjectGone(TASKBAR_RES_ID);
-                    }
+                    checkTaskbarVisibility();
                     return null;
                 }
                 default:
@@ -812,6 +830,18 @@
         }
     }
 
+    private void checkTaskbarVisibility() {
+        if (mIgnoreTaskbarVisibility) {
+            return;
+        }
+
+        if (isTablet() && !isFallbackOverview()) {
+            waitForLauncherObject(TASKBAR_RES_ID);
+        } else {
+            waitUntilLauncherObjectGone(TASKBAR_RES_ID);
+        }
+    }
+
     public void waitForLauncherInitialized() {
         for (int i = 0; i < 100; ++i) {
             if (getTestInfo(
@@ -1935,4 +1965,20 @@
                     LauncherInstrumentation.GestureScope.INSIDE);
         }
     }
+
+    /**
+     * Sets the consumer to run callbacks at all run-points.
+     */
+    public void setRunPointCallback(Consumer<CALLBACK_RUN_POINT> callback) {
+        mCallbackAtRunPoint = callback;
+    }
+
+    /**
+     * Runs the callback at the specified point if it exists.
+     */
+    void runCallbackIfActive(CALLBACK_RUN_POINT runPoint) {
+        if (mCallbackAtRunPoint != null) {
+            mCallbackAtRunPoint.accept(runPoint);
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 2c9fdb3..425a90a 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -18,6 +18,7 @@
 
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_SCROLLED;
 
+import static com.android.launcher3.tapl.LauncherInstrumentation.CALLBACK_RUN_POINT.CALLBACK_HOLD_BEFORE_DROP;
 import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
@@ -302,6 +303,31 @@
     }
 
     /**
+     * Drag the appIcon from the workspace and cancel by dragging icon to corner of screen where no
+     * drop point exists.
+     *
+     * @param homeAppIcon to be dragged.
+     */
+    @NonNull
+    public Workspace dragAndCancelAppIcon(HomeAppIcon homeAppIcon) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "dragging app icon across workspace")) {
+            dragIconToWorkspace(
+                    mLauncher,
+                    homeAppIcon,
+                    () -> new Point(0, 0),
+                    () -> mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT),
+                    null);
+
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "dragged the app across workspace")) {
+                return new Workspace(mLauncher);
+            }
+        }
+    }
+
+    /**
      * Delete the appIcon from the workspace.
      *
      * @param homeAppIcon to be deleted.
@@ -493,6 +519,7 @@
             launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, isDecelerating,
                     downTime, SystemClock.uptimeMillis(), false,
                     LauncherInstrumentation.GestureScope.INSIDE);
+            launcher.runCallbackIfActive(CALLBACK_HOLD_BEFORE_DROP);
             dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
         }
     }