Merge "Adding TaplTestsSplitscreen to OOP tests" into udc-qpr-dev
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index 63ea20c..f8b08f8 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -135,7 +135,7 @@
   }
 }
 
-// Next value 51
+// Next value 52
 enum Attribute {
   option allow_alias = true;
 
@@ -187,6 +187,7 @@
   ALL_APPS_SEARCH_RESULT_SYSTEM_POINTER = 42;
   ALL_APPS_SEARCH_RESULT_EDUCARD = 43;
   ALL_APPS_SEARCH_RESULT_LOCATION = 50;
+  ALL_APPS_SEARCH_RESULT_TEXT_HEADER = 51;
 
   // Result sources
   DATA_SOURCE_APPSEARCH_APP_PREVIEW = 45;
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 28bd701..f64b5cf 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -15,23 +15,16 @@
  */
 package com.android.launcher3;
 
-import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
 import android.os.Build;
-import android.os.CancellationSignal;
-import android.view.RemoteAnimationTarget;
 
-import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.RemoteAnimationProvider;
 
 import java.util.function.BiPredicate;
 
 @TargetApi(Build.VERSION_CODES.P)
 public class LauncherInitListener extends ActivityInitListener<Launcher> {
 
-    private RemoteAnimationProvider mRemoteAnimationProvider;
-
     /**
      * @param onInitListener a callback made when the activity is initialized. The callback should
      *                       return true to continue receiving callbacks (ie. for if the activity is
@@ -43,37 +36,7 @@
 
     @Override
     public boolean handleInit(Launcher launcher, boolean alreadyOnHome) {
-        if (mRemoteAnimationProvider != null) {
-            QuickstepTransitionManager appTransitionManager =
-                    ((QuickstepLauncher) launcher).getAppTransitionManager();
-
-            // Set a one-time animation provider. After the first call, this will get cleared.
-            // TODO: Probably also check the intended target id.
-            CancellationSignal cancellationSignal = new CancellationSignal();
-            appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() {
-                @Override
-                public AnimatorSet createWindowAnimation(RemoteAnimationTarget[] appTargets,
-                        RemoteAnimationTarget[] wallpaperTargets) {
-
-                    // On the first call clear the reference.
-                    cancellationSignal.cancel();
-                    RemoteAnimationProvider provider = mRemoteAnimationProvider;
-                    mRemoteAnimationProvider = null;
-
-                    if (provider != null && launcher.getStateManager().getState().overviewUi) {
-                        return provider.createWindowAnimation(appTargets, wallpaperTargets);
-                    }
-                    return null;
-                }
-            }, cancellationSignal);
-        }
         launcher.deferOverlayCallbacksUntilNextResumeOrStop();
         return super.handleInit(launcher, alreadyOnHome);
     }
-
-    @Override
-    public void unregister() {
-        mRemoteAnimationProvider = null;
-        super.unregister();
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 110d275..7091a87 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -82,7 +82,6 @@
 import android.graphics.RectF;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -140,7 +139,6 @@
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.RectFSpringAnim.DefaultSpringConfig;
 import com.android.quickstep.util.RectFSpringAnim.TaskbarHotseatSpringConfig;
-import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.quickstep.util.SurfaceTransaction;
 import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
@@ -229,7 +227,6 @@
 
     private DeviceProfile mDeviceProfile;
 
-    private RemoteAnimationProvider mRemoteAnimationProvider;
     // Strong refs to runners which are cleared when the launcher activity is destroyed
     private RemoteAnimationFactory mWallpaperOpenRunner;
     private RemoteAnimationFactory mAppLaunchRunner;
@@ -468,16 +465,6 @@
         return bounds;
     }
 
-    public void setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider,
-            CancellationSignal cancellationSignal) {
-        mRemoteAnimationProvider = animationProvider;
-        cancellationSignal.setOnCancelListener(() -> {
-            if (animationProvider == mRemoteAnimationProvider) {
-                mRemoteAnimationProvider = null;
-            }
-        });
-    }
-
     /** Dump debug logs to bug report. */
     public void dump(@NonNull String prefix, @NonNull PrintWriter printWriter) {}
 
@@ -1234,7 +1221,7 @@
      * ie. pressing home, swiping up from nav bar.
      */
     RemoteAnimationFactory createWallpaperOpenRunner(boolean fromUnlock) {
-        return new WallpaperOpenLauncherAnimationRunner(mHandler, fromUnlock);
+        return new WallpaperOpenLauncherAnimationRunner(fromUnlock);
     }
 
     /**
@@ -1352,7 +1339,7 @@
     /**
      * Closing animator that animates the window into its final location on the workspace.
      */
-    private RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation,
+    protected RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation,
             RemoteAnimationTarget[] targets, View launcherView, PointF velocityPxPerS,
             RectF closingWindowStartRect, float startWindowCornerRadius) {
         FloatingIconView floatingIconView = null;
@@ -1592,89 +1579,80 @@
             RectF startRect,
             float startWindowCornerRadius,
             boolean fromPredictiveBack) {
-        AnimatorSet anim = null;
+        AnimatorSet anim = new AnimatorSet();
         RectFSpringAnim rectFSpringAnim = null;
 
-        RemoteAnimationProvider provider = mRemoteAnimationProvider;
-        if (provider != null) {
-            anim = provider.createWindowAnimation(appTargets, wallpaperTargets);
+        final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible()
+                || launcherIsATargetWithMode(appTargets, MODE_OPENING);
+
+        View launcherView = findLauncherView(appTargets);
+        boolean playFallBackAnimation = (launcherView == null
+                && launcherIsForceInvisibleOrOpening)
+                || mLauncher.getWorkspace().isOverlayShown()
+                || shouldPlayFallbackClosingAnimation(appTargets);
+
+        boolean playWorkspaceReveal = true;
+        boolean skipAllAppsScale = false;
+        if (fromUnlock) {
+            anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
+        } else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get()
+                && !playFallBackAnimation) {
+            // Use a fixed velocity to start the animation.
+            float velocityPxPerS = DynamicResource.provider(mLauncher)
+                    .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
+            PointF velocity = new PointF(0, -velocityPxPerS);
+            rectFSpringAnim = getClosingWindowAnimators(
+                    anim, appTargets, launcherView, velocity, startRect,
+                    startWindowCornerRadius);
+            if (mLauncher.isInState(LauncherState.ALL_APPS)) {
+                // Skip scaling all apps, otherwise FloatingIconView will get wrong
+                // layout bounds.
+                skipAllAppsScale = true;
+            } else if (!fromPredictiveBack) {
+                anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
+                        true /* animateOverviewScrim */, launcherView).getAnimators());
+
+                if (!areAllTargetsTranslucent(appTargets)) {
+                    anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController().stateDepth,
+                            MULTI_PROPERTY_VALUE,
+                            BACKGROUND_APP.getDepth(mLauncher), NORMAL.getDepth(mLauncher)));
+                }
+
+                // We play StaggeredWorkspaceAnim as a part of the closing window animation.
+                playWorkspaceReveal = false;
+            }
+        } else {
+            anim.play(getFallbackClosingWindowAnimators(appTargets));
         }
 
-        if (anim == null) {
-            anim = new AnimatorSet();
+        // Normally, we run the launcher content animation when we are transitioning
+        // home, but if home is already visible, then we don't want to animate the
+        // contents of launcher unless we know that we are animating home as a result
+        // of the home button press with quickstep, which will result in launcher being
+        // started on touch down, prior to the animation home (and won't be in the
+        // targets list because it is already visible). In that case, we force
+        // invisibility on touch down, and only reset it after the animation to home
+        // is initialized.
+        if (launcherIsForceInvisibleOrOpening) {
+            addCujInstrumentation(
+                    anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
+            // Only register the content animation for cancellation when state changes
+            mLauncher.getStateManager().setCurrentAnimation(anim);
 
-            final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible()
-                    || launcherIsATargetWithMode(appTargets, MODE_OPENING);
-
-            View launcherView = findLauncherView(appTargets);
-            boolean playFallBackAnimation = (launcherView == null
-                    && launcherIsForceInvisibleOrOpening)
-                    || mLauncher.getWorkspace().isOverlayShown()
-                    || shouldPlayFallbackClosingAnimation(appTargets);
-
-            boolean playWorkspaceReveal = true;
-            boolean skipAllAppsScale = false;
-            if (fromUnlock) {
-                anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
-            } else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get()
-                    && !playFallBackAnimation) {
-                // Use a fixed velocity to start the animation.
-                float velocityPxPerS = DynamicResource.provider(mLauncher)
-                        .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
-                PointF velocity = new PointF(0, -velocityPxPerS);
-                rectFSpringAnim = getClosingWindowAnimators(
-                        anim, appTargets, launcherView, velocity, startRect,
-                        startWindowCornerRadius);
-                if (mLauncher.isInState(LauncherState.ALL_APPS)) {
-                    // Skip scaling all apps, otherwise FloatingIconView will get wrong
-                    // layout bounds.
-                    skipAllAppsScale = true;
-                } else if (!fromPredictiveBack) {
-                    anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
-                            true /* animateOverviewScrim */, launcherView).getAnimators());
-
-                    if (!areAllTargetsTranslucent(appTargets)) {
-                        anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController().stateDepth,
-                                MULTI_PROPERTY_VALUE,
-                                BACKGROUND_APP.getDepth(mLauncher), NORMAL.getDepth(mLauncher)));
+            if (mLauncher.isInState(LauncherState.ALL_APPS)) {
+                Pair<AnimatorSet, Runnable> contentAnimator =
+                        getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY,
+                                skipAllAppsScale);
+                anim.play(contentAnimator.first);
+                anim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        contentAnimator.second.run();
                     }
-
-                    // We play StaggeredWorkspaceAnim as a part of the closing window animation.
-                    playWorkspaceReveal = false;
-                }
+                });
             } else {
-                anim.play(getFallbackClosingWindowAnimators(appTargets));
-            }
-
-            // Normally, we run the launcher content animation when we are transitioning
-            // home, but if home is already visible, then we don't want to animate the
-            // contents of launcher unless we know that we are animating home as a result
-            // of the home button press with quickstep, which will result in launcher being
-            // started on touch down, prior to the animation home (and won't be in the
-            // targets list because it is already visible). In that case, we force
-            // invisibility on touch down, and only reset it after the animation to home
-            // is initialized.
-            if (launcherIsForceInvisibleOrOpening) {
-                addCujInstrumentation(
-                        anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
-                // Only register the content animation for cancellation when state changes
-                mLauncher.getStateManager().setCurrentAnimation(anim);
-
-                if (mLauncher.isInState(LauncherState.ALL_APPS)) {
-                    Pair<AnimatorSet, Runnable> contentAnimator =
-                            getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY,
-                                    skipAllAppsScale);
-                    anim.play(contentAnimator.first);
-                    anim.addListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            contentAnimator.second.run();
-                        }
-                    });
-                } else {
-                    if (playWorkspaceReveal) {
-                        anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
-                    }
+                if (playWorkspaceReveal) {
+                    anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
                 }
             }
         }
@@ -1687,11 +1665,9 @@
      */
     protected class WallpaperOpenLauncherAnimationRunner implements RemoteAnimationFactory {
 
-        private final Handler mHandler;
         private final boolean mFromUnlock;
 
-        public WallpaperOpenLauncherAnimationRunner(Handler handler, boolean fromUnlock) {
-            mHandler = handler;
+        public WallpaperOpenLauncherAnimationRunner(boolean fromUnlock) {
             mFromUnlock = fromUnlock;
         }
 
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 5691ecf..db225be 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -158,11 +158,11 @@
     }
 
     private void animateOpen() {
-        if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+        if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
             return;
         }
         mIsOpen = true;
-        setUpDefaultOpenAnimator().start();
+        setUpDefaultOpenAnimation().start();
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 85d0ab5..619bef2 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -114,8 +114,7 @@
         WorkspaceItemInfo dragItem = new WorkspaceItemInfo((WorkspaceItemInfo) v.getTag());
         v.setVisibility(View.INVISIBLE);
         mLauncher.getWorkspace().beginDragShared(
-                v, null, this, dragItem, new DragPreviewProvider(v),
-                mLauncher.getDefaultWorkspaceDragOptions());
+                v, null, this, dragItem, new DragPreviewProvider(v), new DragOptions());
         return true;
     };
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index b5b453b..6cc54ca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -676,7 +676,6 @@
                     hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
             mAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
                 mAnimator = null;
-                mIsStashed = isStashed;
             }));
             return;
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 3fe7359..537d2c6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -17,11 +17,13 @@
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
 
+import android.animation.Animator;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.animation.Interpolator;
 import android.window.OnBackInvokedDispatcher;
 
@@ -29,6 +31,7 @@
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.taskbar.allapps.TaskbarAllAppsViewController.TaskbarAllAppsCallbacks;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
@@ -58,22 +61,50 @@
 
     /** Opens the all apps view. */
     void show(boolean animate) {
-        if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+        if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
             return;
         }
         mIsOpen = true;
         attachToContainer();
-        mAllAppsCallbacks.onAllAppsTransitionStart(true);
 
-        if (animate) {
-            setUpOpenCloseAnimator(TRANSLATION_SHIFT_OPENED, EMPHASIZED);
-            mOpenCloseAnimator.addListener(AnimatorListeners.forEndCallback(
-                    () -> mAllAppsCallbacks.onAllAppsTransitionEnd(true)));
-            mOpenCloseAnimator.setDuration(mAllAppsCallbacks.getOpenDuration()).start();
-        } else {
-            mTranslationShift = TRANSLATION_SHIFT_OPENED;
+        addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+            @Override
+            public void onViewAttachedToWindow(View v) {
+                removeOnAttachStateChangeListener(this);
+                // Wait for view and its descendants to be fully attached before starting open.
+                post(() -> showOnFullyAttachedToWindow(animate));
+            }
+
+            @Override
+            public void onViewDetachedFromWindow(View v) {
+                removeOnAttachStateChangeListener(this);
+            }
+        });
+    }
+
+    private void showOnFullyAttachedToWindow(boolean animate) {
+        mAllAppsCallbacks.onAllAppsTransitionStart(true);
+        if (!animate) {
             mAllAppsCallbacks.onAllAppsTransitionEnd(true);
+            mTranslationShift = TRANSLATION_SHIFT_OPENED;
+            return;
         }
+
+        setUpOpenAnimation(mAllAppsCallbacks.getOpenDuration());
+        Animator animator = mOpenCloseAnimation.getAnimationPlayer();
+        animator.setInterpolator(EMPHASIZED);
+        animator.addListener(AnimatorListeners.forEndCallback(() -> {
+            if (mIsOpen) {
+                mAllAppsCallbacks.onAllAppsTransitionEnd(true);
+            }
+        }));
+        animator.start();
+    }
+
+    @Override
+    protected void onOpenCloseAnimationPending(PendingAnimation animation) {
+        mAllAppsCallbacks.onAllAppsAnimationPending(
+                animation, mToTranslationShift == TRANSLATION_SHIFT_OPENED);
     }
 
     /** The apps container inside this view. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
index f43169b..85633e9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -20,6 +20,7 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.allapps.AllAppsTransitionListener;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.appprediction.AppsDividerView;
 import com.android.launcher3.taskbar.NavbarButtonsViewController;
 import com.android.launcher3.taskbar.TaskbarControllers;
@@ -125,5 +126,9 @@
         boolean handleSearchBackInvoked() {
             return mSearchSessionController.handleBackInvoked();
         }
+
+        void onAllAppsAnimationPending(PendingAnimation animation, boolean toAllApps) {
+            mSearchSessionController.onAllAppsAnimationPending(animation, toAllApps);
+        }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt
index c26977f..8a2041f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt
@@ -20,6 +20,7 @@
 import android.view.View
 import com.android.launcher3.R
 import com.android.launcher3.allapps.AllAppsTransitionListener
+import com.android.launcher3.anim.PendingAnimation
 import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.dragndrop.DragOptions.PreDragCondition
 import com.android.launcher3.model.data.ItemInfo
@@ -50,6 +51,8 @@
 
     open fun handleBackInvoked(): Boolean = false
 
+    open fun onAllAppsAnimationPending(animation: PendingAnimation, toAllApps: Boolean) = Unit
+
     companion object {
         @JvmStatic
         fun newInstance(context: Context): TaskbarSearchSessionController {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index a53dc15..475f465 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -16,13 +16,16 @@
 
 package com.android.launcher3.uioverrides;
 
+import android.app.ActivityOptions;
 import android.app.Person;
 import android.content.Context;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
+import android.window.RemoteTransition;
 
 import com.android.launcher3.Utilities;
+import com.android.quickstep.util.FadeOutRemoteTransition;
 
 import java.util.Map;
 
@@ -41,4 +44,13 @@
     public static Map<String, LauncherActivityInfo> getActivityOverrides(Context context) {
         return context.getSystemService(LauncherApps.class).getActivityOverrides();
     }
+
+    /**
+     * Creates an ActivityOptions to play fade-out animation on closing targets
+     */
+    public static ActivityOptions createFadeOutAnimOptions(Context context) {
+        ActivityOptions options = ActivityOptions.makeBasic();
+        options.setRemoteTransition(new RemoteTransition(new FadeOutRemoteTransition()));
+        return options;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 08ce794..f2d4660 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -33,7 +33,6 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
 import static com.android.launcher3.config.FeatureFlags.RECEIVE_UNFOLD_EVENTS_FROM_SYSUI;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
@@ -59,7 +58,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.content.Context;
@@ -76,13 +74,11 @@
 import android.media.permission.SafeCloseable;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.CancellationSignal;
 import android.os.IBinder;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
-import android.view.RemoteAnimationTarget;
 import android.view.View;
 import android.window.BackEvent;
 import android.window.OnBackAnimationCallback;
@@ -110,7 +106,6 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.appprediction.PredictionRowView;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
@@ -164,8 +159,6 @@
 import com.android.quickstep.util.LauncherUnfoldAnimationController;
 import com.android.quickstep.util.ProxyScreenStatusProvider;
 import com.android.quickstep.util.QuickstepOnboardingPrefs;
-import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.SplitToWorkspaceController;
 import com.android.quickstep.util.SplitWithKeyboardShortcutController;
@@ -203,6 +196,10 @@
 
     public static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
             SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
+    private static final boolean TRACE_LAYOUTS =
+            SystemProperties.getBoolean("persist.debug.trace_layouts", false);
+    private static final String TRACE_RELAYOUT_CLASS =
+            SystemProperties.get("persist.debug.trace_request_layout_class", null);
 
     public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
 
@@ -217,7 +214,6 @@
     private TISBindHelper mTISBindHelper;
     private @Nullable LauncherTaskbarUIController mTaskbarUIController;
     // Will be updated when dragging from taskbar.
-    private @Nullable DragOptions mNextWorkspaceDragOptions = null;
     private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
     private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
 
@@ -615,6 +611,8 @@
             mViewCapture = SettingsAwareViewCapture.getInstance(this).startCapture(getWindow());
         }
         getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
+        View.setTraceLayoutSteps(TRACE_LAYOUTS);
+        View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS);
     }
 
     @Override
@@ -1043,41 +1041,6 @@
     }
 
     @Override
-    public DragOptions getDefaultWorkspaceDragOptions() {
-        if (mNextWorkspaceDragOptions != null) {
-            DragOptions options = mNextWorkspaceDragOptions;
-            mNextWorkspaceDragOptions = null;
-            return options;
-        }
-        return super.getDefaultWorkspaceDragOptions();
-    }
-
-    public void setNextWorkspaceDragOptions(DragOptions dragOptions) {
-        mNextWorkspaceDragOptions = dragOptions;
-    }
-
-    @Override
-    public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
-        QuickstepTransitionManager appTransitionManager = getAppTransitionManager();
-        appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() {
-            @Override
-            public AnimatorSet createWindowAnimation(RemoteAnimationTarget[] appTargets,
-                    RemoteAnimationTarget[] wallpaperTargets) {
-
-                // On the first call clear the reference.
-                signal.cancel();
-
-                ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
-                fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets,
-                        wallpaperTargets));
-                AnimatorSet anim = new AnimatorSet();
-                anim.play(fadeAnimation);
-                return anim;
-            }
-        }, signal);
-    }
-
-    @Override
     public float[] getNormalOverviewScaleAndOffset() {
         return DisplayController.getNavigationMode(this).hasGestures
                 ? new float[] {1, 1} : new float[] {1.1f, NO_OFFSET};
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 2d5b8db..af49774 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -413,20 +413,14 @@
     }
 
     /**
-     * TODO: This doesn't animate at present. Feel free to blow out everyhing in this method
-     * if needed
+     * If {@param launchingTaskView} is not null, then this will play the tasks launch animation
+     * from the position of the GroupedTaskView (when user taps on the TaskView to start it).
+     * Technically this case should be taken care of by
+     * {@link #composeRecentsSplitLaunchAnimatorLegacy} below, but the way we launch tasks whether
+     * it's a single task or multiple tasks results in different entry-points.
      *
-     * We could manually try to animate the just the bounds for the leashes we get back, but we try
-     * to do it through TaskViewSimulator(TVS) since that handles a lot of the recents UI stuff for
-     * us.
-     *
-     * First you have to call TVS#setPreview() to indicate which leash it will operate one
-     * Then operations happen in TVS#apply() on each frame callback.
-     *
-     * TVS uses DeviceProfile to try to figure out things like task height and such based on if the
-     * device is in multiWindowMode or not. It's unclear given the two calls to startTask() when the
-     * device is considered in multiWindowMode and things like insets and stuff change
-     * and calculations have to be adjusted in the animations for that
+     * If it is null, then it will simply fade in the starting apps and fade out launcher (for the
+     * case where launcher handles animating starting split tasks from app icon)
      */
     public static void composeRecentsSplitLaunchAnimator(GroupedTaskView launchingTaskView,
             @NonNull StateManager stateManager, @Nullable DepthController depthController,
@@ -461,9 +455,9 @@
             return;
         }
 
-        // TODO: consider initialTaskPendingIntent
         TransitionInfo.Change splitRoot1 = null;
         TransitionInfo.Change splitRoot2 = null;
+        final ArrayList<SurfaceControl> openingTargets = new ArrayList<>();
         for (int i = 0; i < transitionInfo.getChanges().size(); ++i) {
             final TransitionInfo.Change change = transitionInfo.getChanges().get(i);
             if (change.getTaskInfo() == null) {
@@ -484,32 +478,48 @@
             if (taskId == initialTaskId) {
                 splitRoot1 = change.getParent() == null ? change :
                         transitionInfo.getChange(change.getParent());
+                openingTargets.add(splitRoot1.getLeash());
             }
             if (taskId == secondTaskId) {
                 splitRoot2 = change.getParent() == null ? change :
                         transitionInfo.getChange(change.getParent());
+                openingTargets.add(splitRoot2.getLeash());
             }
         }
 
-        // This is where we should animate the split roots. For now, though, just make them visible.
-        animateSplitRoot(t, splitRoot1);
-        animateSplitRoot(t, splitRoot2);
+        SurfaceControl.Transaction animTransaction = new SurfaceControl.Transaction();
+        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+        animator.setDuration(SPLIT_LAUNCH_DURATION);
+        animator.addUpdateListener(valueAnimator -> {
+            float progress = valueAnimator.getAnimatedFraction();
+            for (SurfaceControl leash: openingTargets) {
+                animTransaction.setAlpha(leash, progress);
+            }
+            animTransaction.apply();
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                for (SurfaceControl leash: openingTargets) {
+                    animTransaction.show(leash)
+                            .setAlpha(leash, 0.0f);
+                }
+                animTransaction.apply();
+            }
 
-        // This contains the initial state (before animation), so apply this at the beginning of
-        // the animation.
-        t.apply();
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finishCallback.run();
+            }
+        });
 
-        // Once there is an animation, this should be called AFTER the animation completes.
-        finishCallback.run();
-    }
-
-    private static void animateSplitRoot(SurfaceControl.Transaction t,
-            TransitionInfo.Change splitRoot) {
-        testLogD(LAUNCH_SPLIT_PAIR, "animateSplitRoot: " + splitRoot);
-        if (splitRoot != null) {
-            t.show(splitRoot.getLeash());
-            t.setAlpha(splitRoot.getLeash(), 1.f);
+        if (splitRoot1 != null && splitRoot1.getParent() != null) {
+            // Set the highest level split root alpha; we could technically use the parent of either
+            // splitRoot1 or splitRoot2
+            t.setAlpha(transitionInfo.getChange(splitRoot1.getParent()).getLeash(), 1f);
         }
+        t.apply();
+        animator.start();
     }
 
     /**
@@ -522,7 +532,9 @@
      * it's a single task or multiple tasks results in different entry-points.
      *
      * If it is null, then it will simply fade in the starting apps and fade out launcher (for the
-     * case where launcher handles animating starting split tasks from app icon) */
+     * case where launcher handles animating starting split tasks from app icon)
+     * @deprecated with shell transitions
+     */
     public static void composeRecentsSplitLaunchAnimatorLegacy(
             @Nullable GroupedTaskView launchingTaskView, int initialTaskId, int secondTaskId,
             @NonNull RemoteAnimationTarget[] appTargets,
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index f11bc81..49814df 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -95,9 +95,10 @@
 
     private static final float ANIMATION_PAUSE_ALPHA_THRESHOLD = 0.1f;
 
+    private final AnimatedFloat mSwipeProgress = new AnimatedFloat(this::onSwipeProgressUpdate);
+
     private TISBindHelper mTISBindHelper;
 
-    private final AnimatedFloat mSwipeProgress = new AnimatedFloat(this::onSwipeProgressUpdate);
     private BgDrawable mBackground;
     private View mRootView;
     private float mSwipeUpShift;
@@ -172,7 +173,7 @@
                         LOTTIE_TERTIARY_COLOR_TOKEN, R.color.all_set_bg_tertiary),
                 getTheme());
 
-        startBackgroundAnimation();
+        startBackgroundAnimation(dp.isTablet);
     }
 
     private void runOnUiHelperThread(Runnable runnable) {
@@ -183,7 +184,7 @@
         Executors.UI_HELPER_EXECUTOR.execute(runnable);
     }
 
-    private void startBackgroundAnimation() {
+    private void startBackgroundAnimation(boolean forTablet) {
         if (!Utilities.ATLEAST_S || mVibrator == null) {
             return;
         }
@@ -199,7 +200,7 @@
                     .addPrimitive(supportsThud
                                     ? VibrationEffect.Composition.PRIMITIVE_THUD
                                     : VibrationEffect.Composition.PRIMITIVE_TICK,
-                            /* scale= */ 1.0f,
+                            /* scale= */ forTablet ? 1.0f : 0.3f,
                             /* delay= */ 50)
                     .compose();
 
diff --git a/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt b/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt
new file mode 100644
index 0000000..59ff81d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/FadeOutRemoteTransition.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 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 android.animation.ValueAnimator
+import android.os.IBinder
+import android.os.RemoteException
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.window.IRemoteTransition
+import android.window.IRemoteTransitionFinishedCallback
+import android.window.TransitionInfo
+import com.android.launcher3.anim.AnimatorListeners.forEndCallback
+import com.android.launcher3.util.Executors
+import com.android.wm.shell.util.TransitionUtil
+
+/** Remote animation which fades out the closing targets */
+class FadeOutRemoteTransition : IRemoteTransition.Stub() {
+
+    override fun mergeAnimation(
+        iBinder: IBinder,
+        transitionInfo: TransitionInfo,
+        transaction: Transaction,
+        mergeTarget: IBinder,
+        finishCB: IRemoteTransitionFinishedCallback
+    ) {
+
+        try {
+            finishCB.onTransitionFinished(null, Transaction())
+        } catch (e: RemoteException) {
+            // Ignore
+        }
+    }
+
+    override fun startAnimation(
+        transition: IBinder,
+        info: TransitionInfo,
+        startT: Transaction,
+        finishCB: IRemoteTransitionFinishedCallback
+    ) {
+        val anim = ValueAnimator.ofFloat(1f, 0f)
+
+        val closingControls: MutableList<SurfaceControl> = mutableListOf()
+        for (chg in info.changes) {
+            startT.show(chg.leash)
+            if (TransitionUtil.isClosingType(chg.mode)) {
+                closingControls.add(chg.leash)
+            }
+        }
+        startT.apply()
+
+        anim.addUpdateListener {
+            val t = Transaction()
+            closingControls.forEach { t.setAlpha(it, anim.animatedValue as Float) }
+            t.apply()
+        }
+        anim.addListener(
+            forEndCallback(
+                Runnable {
+                    val t = Transaction()
+                    closingControls.forEach { t.hide(it) }
+                    try {
+                        finishCB.onTransitionFinished(null, t)
+                    } catch (e: RemoteException) {
+                        // Ignore
+                    }
+                }
+            )
+        )
+
+        Executors.MAIN_EXECUTOR.execute { anim.start() }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
deleted file mode 100644
index 10f2eaa..0000000
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.animation.AnimatorSet;
-import android.view.RemoteAnimationTarget;
-
-public abstract class RemoteAnimationProvider {
-
-    public abstract AnimatorSet createWindowAnimation(RemoteAnimationTarget[] appTargets,
-            RemoteAnimationTarget[] wallpaperTargets);
-
-    /**
-     * @return the target with the lowest opaque layer for a certain app animation, or null.
-     */
-    public static RemoteAnimationTarget findLowestOpaqueLayerTarget(
-            RemoteAnimationTarget[] appTargets, int mode) {
-        int lowestLayer = Integer.MAX_VALUE;
-        int lowestLayerIndex = -1;
-        for (int i = appTargets.length - 1; i >= 0; i--) {
-            RemoteAnimationTarget target = appTargets[i];
-            if (target.mode == mode && !target.isTranslucent) {
-                int layer = target.prefixOrderIndex;
-                if (layer < lowestLayer) {
-                    lowestLayer = layer;
-                    lowestLayerIndex = i;
-                }
-            }
-        }
-        return lowestLayerIndex != -1
-                ? appTargets[lowestLayerIndex]
-                : null;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java b/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
deleted file mode 100644
index 382cf79..0000000
--- a/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2018 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 static android.view.RemoteAnimationTarget.MODE_CLOSING;
-
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl.Transaction;
-
-import com.android.quickstep.RemoteAnimationTargets;
-
-/**
- * Animation listener which fades out the closing targets
- */
-public class RemoteFadeOutAnimationListener implements AnimatorUpdateListener {
-
-    private final RemoteAnimationTargets mTarget;
-    private boolean mFirstFrame = true;
-
-    public RemoteFadeOutAnimationListener(RemoteAnimationTarget[] appTargets,
-            RemoteAnimationTarget[] wallpaperTargets) {
-        mTarget = new RemoteAnimationTargets(appTargets, wallpaperTargets,
-                new RemoteAnimationTarget[0], MODE_CLOSING);
-    }
-
-    @Override
-    public void onAnimationUpdate(ValueAnimator valueAnimator) {
-        Transaction t = new Transaction();
-        if (mFirstFrame) {
-            for (RemoteAnimationTarget target : mTarget.unfilteredApps) {
-                t.show(target.leash);
-            }
-            mFirstFrame = false;
-        }
-
-        float alpha = 1 - valueAnimator.getAnimatedFraction();
-        for (RemoteAnimationTarget app : mTarget.apps) {
-            t.setAlpha(app.leash, alpha);
-        }
-        t.apply();
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 5565139..0c89766 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -592,9 +592,8 @@
                             if (mSuccessCallback != null) {
                                 mSuccessCallback.accept(true);
                             }
+                            resetState();
                         });
-                // After successful launch, call resetState
-                resetState();
             });
         }
 
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 5127190..20aa410 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -16,14 +16,17 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
+
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -44,9 +47,9 @@
         startTestActivity(2);
     }
 
-    @Ignore
     @Test
     @NavigationModeSwitch
+    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/187761685
     public void testStressPressHome() {
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
@@ -57,9 +60,9 @@
         }
     }
 
-    @Ignore
     @Test
     @NavigationModeSwitch
+    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/187761685
     public void testStressSwipeToOverview() {
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 4d75fb8..05eaf88 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -202,6 +202,7 @@
         <attr name="demoModeLayoutId" format="reference" />
         <attr name="isScalable" format="boolean" />
         <attr name="devicePaddingId" format="reference" />
+
         <!-- File that contains the specs for the workspace.
         Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
         <attr name="workspaceSpecsId" format="reference" />
@@ -210,11 +211,14 @@
         Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
         <attr name="allAppsSpecsId" format="reference" />
         <attr name="allAppsSpecsTwoPanelId" format="reference" />
-
         <!-- File that contains the specs for the workspace.
         Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
         <attr name="folderSpecsId" format="reference" />
         <attr name="folderSpecsTwoPanelId" format="reference" />
+        <!-- File that contains the specs for hotseat bar.
+        Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
+        <attr name="hotseatSpecsId" format="reference" />
+        <attr name="hotseatSpecsTwoPanelId" format="reference" />
 
         <!-- By default all categories are enabled -->
         <attr name="deviceCategory" format="integer">
@@ -278,6 +282,11 @@
         <attr name="maxAvailableSize" />
     </declare-styleable>
 
+    <declare-styleable name="HotseatSpec">
+        <attr name="specType" />
+        <attr name="maxAvailableSize" />
+    </declare-styleable>
+
     <declare-styleable name="SizeSpec">
         <attr name="fixedSize" format="dimension" />
         <attr name="ofAvailableSpace" format="float" />
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 9a1ccd0..7131452 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -360,6 +360,37 @@
             lp.y = sTmpRect.top;
         }
 
+        // Handle invalid resize across CellLayouts in the two panel UI.
+        if (mCellLayout.getParent() instanceof Workspace) {
+            Workspace<?> workspace = (Workspace<?>) mCellLayout.getParent();
+            CellLayout pairedCellLayout = workspace.getScreenPair(mCellLayout);
+            if (pairedCellLayout != null) {
+                Rect focusedCellLayoutBound = sTmpRect;
+                mDragLayerRelativeCoordinateHelper.viewToRect(mCellLayout, focusedCellLayoutBound);
+                Rect resizeFrameBound = sTmpRect2;
+                findViewById(R.id.widget_resize_frame).getGlobalVisibleRect(resizeFrameBound);
+                float progress = 1f;
+                if (workspace.indexOfChild(pairedCellLayout) < workspace.indexOfChild(mCellLayout)
+                        && mDeltaX < 0
+                        && resizeFrameBound.left < focusedCellLayoutBound.left) {
+                    // Resize from right to left.
+                    progress = (mDragAcrossTwoPanelOpacityMargin + mDeltaX)
+                            / mDragAcrossTwoPanelOpacityMargin;
+                } else if (workspace.indexOfChild(pairedCellLayout)
+                                > workspace.indexOfChild(mCellLayout)
+                        && mDeltaX > 0
+                        && resizeFrameBound.right > focusedCellLayoutBound.right) {
+                    // Resize from left to right.
+                    progress = (mDragAcrossTwoPanelOpacityMargin - mDeltaX)
+                            / mDragAcrossTwoPanelOpacityMargin;
+                }
+                float alpha = Math.max(MIN_OPACITY_FOR_CELL_LAYOUT_DURING_INVALID_RESIZE, progress);
+                float springLoadedProgress = Math.min(1f, 1f - progress);
+                updateInvalidResizeEffect(mCellLayout, pairedCellLayout, alpha,
+                        springLoadedProgress);
+            }
+        }
+
         requestLayout();
     }
 
@@ -516,6 +547,13 @@
         }
 
         final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+        final CellLayout pairedCellLayout;
+        if (mCellLayout.getParent() instanceof Workspace) {
+            Workspace<?> workspace = (Workspace<?>) mCellLayout.getParent();
+            pairedCellLayout = workspace.getScreenPair(mCellLayout);
+        } else {
+            pairedCellLayout = null;
+        }
         if (!animate) {
             lp.width = newWidth;
             lp.height = newHeight;
@@ -524,6 +562,10 @@
             for (int i = 0; i < HANDLE_COUNT; i++) {
                 mDragHandles[i].setAlpha(1f);
             }
+            if (pairedCellLayout != null) {
+                updateInvalidResizeEffect(mCellLayout, pairedCellLayout, /* alpha= */ 1f,
+                        /* springLoadedProgress= */ 0f);
+            }
             requestLayout();
         } else {
             ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp,
@@ -539,6 +581,10 @@
                 set.play(mFirstFrameAnimatorHelper.addTo(
                         ObjectAnimator.ofFloat(mDragHandles[i], ALPHA, 1f)));
             }
+            if (pairedCellLayout != null) {
+                updateInvalidResizeEffect(mCellLayout, pairedCellLayout, /* alpha= */ 1f,
+                        /* springLoadedProgress= */ 0f, /* animatorSet= */ set);
+            }
             set.setDuration(SNAP_DURATION);
             set.start();
         }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 83de98a..7ece9a4 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -56,8 +56,10 @@
 import com.android.launcher3.responsive.AllAppsSpecs;
 import com.android.launcher3.responsive.CalculatedAllAppsSpec;
 import com.android.launcher3.responsive.CalculatedFolderSpec;
+import com.android.launcher3.responsive.CalculatedHotseatSpec;
 import com.android.launcher3.responsive.CalculatedWorkspaceSpec;
 import com.android.launcher3.responsive.FolderSpecs;
+import com.android.launcher3.responsive.HotseatSpecs;
 import com.android.launcher3.responsive.WorkspaceSpecs;
 import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.DisplayController;
@@ -121,6 +123,7 @@
     private CalculatedAllAppsSpec mAllAppsResponsiveHeightSpec;
     private CalculatedFolderSpec mResponsiveFolderWidthSpec;
     private CalculatedFolderSpec mResponsiveFolderHeightSpec;
+    private CalculatedHotseatSpec mResponsiveHotseatSpec;
 
     /**
      * The maximum amount of left/right workspace padding as a percentage of the screen width.
@@ -316,7 +319,8 @@
         // TODO(b/241386436): shouldn't change any launcher behaviour
         mIsResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE
                 && inv.allAppsSpecsId != INVALID_RESOURCE_HANDLE
-                && inv.folderSpecsId != INVALID_RESOURCE_HANDLE;
+                && inv.folderSpecsId != INVALID_RESOURCE_HANDLE
+                && inv.hotseatSpecsId != INVALID_RESOURCE_HANDLE;
 
         mIsScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
         // Determine device posture.
@@ -495,7 +499,17 @@
 
         int hotseatBarBottomSpace = pxFromDp(inv.hotseatBarBottomSpace[mTypeIndex], mMetrics);
         int minQsbMargin = res.getDimensionPixelSize(R.dimen.min_qsb_margin);
-        hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics);
+
+        if (mIsResponsiveGrid) {
+            HotseatSpecs hotseatSpecs =
+                    HotseatSpecs.create(new ResourceHelper(context,
+                            isTwoPanels ? inv.hotseatSpecsTwoPanelId : inv.hotseatSpecsId));
+            mResponsiveHotseatSpec = hotseatSpecs.getCalculatedHeightSpec(heightPx);
+            hotseatQsbSpace = mResponsiveHotseatSpec.getHotseatQsbSpace();
+        } else {
+            hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics);
+        }
+
         // Have a little space between the inset and the QSB
         if (mInsets.bottom + minQsbMargin > hotseatBarBottomSpace) {
             int availableSpace = hotseatQsbSpace - (mInsets.bottom - hotseatBarBottomSpace);
@@ -618,6 +632,10 @@
         // Hotseat and QSB width depends on updated cellSize and workspace padding
         recalculateHotseatWidthAndBorderSpace();
 
+        if (mIsResponsiveGrid && isVerticalBarLayout()) {
+            hotseatBorderSpace = cellLayoutBorderSpacePx.y;
+        }
+
         // AllApps height calculation depends on updated cellSize
         if (isTablet) {
             int collapseHandleHeight =
@@ -717,7 +735,7 @@
     /** Updates hotseatCellHeightPx and hotseatBarSizePx */
     private void updateHotseatSizes(int hotseatIconSizePx) {
         // Ensure there is enough space for folder icons, which have a slightly larger radius.
-        hotseatCellHeightPx = (int) Math.ceil(hotseatIconSizePx * ICON_OVERLAP_FACTOR);
+        hotseatCellHeightPx = getIconSizeWithOverlap(hotseatIconSizePx);
 
         if (isVerticalBarLayout()) {
             hotseatBarSizePx = hotseatIconSizePx + hotseatBarSidePaddingStartPx
@@ -783,7 +801,6 @@
             hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidthPx,
                     (isQsbInline ? 1 : 0) + /* border between nav buttons and first icon */ 1);
         } while (hotseatBorderSpace < mMinHotseatIconSpacePx && numShownHotseatIcons > 1);
-
     }
 
     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) {
@@ -866,11 +883,24 @@
         float workspaceCellPaddingY = getCellSize().y - iconSizePx - iconDrawablePaddingPx
                 - iconTextHeight;
 
+        if (mIsResponsiveGrid) {
+            // Hide text only if doesn't fit inside the cell for responsive grid
+            if (workspaceCellPaddingY < 0) {
+                iconTextSizePx = 0;
+                iconDrawablePaddingPx = 0;
+                int iconSizeWithOverlap = getIconSizeWithOverlap(iconSizePx);
+                cellYPaddingPx = Math.max(0, getCellSize().y - iconSizeWithOverlap) / 2;
+                autoResizeAllAppsCells();
+            }
+
+            return;
+        }
+
         // We want enough space so that the text is closer to its corresponding icon.
         if (workspaceCellPaddingY < iconTextHeight) {
             iconTextSizePx = 0;
             iconDrawablePaddingPx = 0;
-            cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR);
+            cellHeightPx = getIconSizeWithOverlap(iconSizePx);
             autoResizeAllAppsCells();
         }
     }
@@ -950,6 +980,10 @@
         return Math.max(0, drawablePadding - iconSizeDiff / 2);
     }
 
+    private int getIconSizeWithOverlap(int iconSize) {
+        return (int) Math.ceil(iconSize * ICON_OVERLAP_FACTOR);
+    }
+
     /**
      * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx,
      * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
@@ -1052,7 +1086,7 @@
         } else {
             iconDrawablePaddingPx = (int) (getNormalizedIconDrawablePadding() * iconScale);
             cellWidthPx = iconSizePx + iconDrawablePaddingPx;
-            cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR)
+            cellHeightPx = getIconSizeWithOverlap(iconSizePx)
                     + iconDrawablePaddingPx
                     + Utilities.calculateTextHeight(iconTextSizePx);
             int cellPaddingY = (getCellSize().y - cellHeightPx) / 2;
@@ -1107,7 +1141,6 @@
         return Math.min(hotseatBorderSpacePx, mMaxHotseatIconSpacePx);
     }
 
-
     /**
      * Updates the iconSize for allApps* variants.
      */
@@ -1455,14 +1488,26 @@
     private void updateWorkspacePadding() {
         Rect padding = workspacePadding;
         if (isVerticalBarLayout()) {
-            padding.top = 0;
-            padding.bottom = edgeMarginPx;
-            if (isSeascape()) {
-                padding.left = hotseatBarSizePx;
-                padding.right = hotseatBarSidePaddingStartPx;
+            if (mIsResponsiveGrid) {
+                padding.top = mResponsiveHeightSpec.getStartPaddingPx();
+                padding.bottom = mResponsiveHeightSpec.getEndPaddingPx();
+                if (isSeascape()) {
+                    padding.left = hotseatBarSizePx + mResponsiveWidthSpec.getEndPaddingPx();
+                    padding.right = mResponsiveWidthSpec.getStartPaddingPx();
+                } else {
+                    padding.left = mResponsiveWidthSpec.getStartPaddingPx();
+                    padding.right = hotseatBarSizePx + mResponsiveWidthSpec.getEndPaddingPx();
+                }
             } else {
-                padding.left = hotseatBarSidePaddingStartPx;
-                padding.right = hotseatBarSizePx;
+                padding.top = 0;
+                padding.bottom = edgeMarginPx;
+                if (isSeascape()) {
+                    padding.left = hotseatBarSizePx;
+                    padding.right = hotseatBarSidePaddingStartPx;
+                } else {
+                    padding.left = hotseatBarSidePaddingStartPx;
+                    padding.right = hotseatBarSizePx;
+                }
             }
         } else {
             // Pad the bottom of the workspace with hotseat bar
@@ -1505,7 +1550,9 @@
             // in vertical bar layout.
             // Workspace icons are moved up by a small factor. The variable diffOverlapFactor
             // is set to account for that difference.
-            float diffOverlapFactor = iconSizePx * (ICON_OVERLAP_FACTOR - 1) / 2;
+            float diffOverlapFactor = mIsResponsiveGrid ? 0
+                    : iconSizePx * (ICON_OVERLAP_FACTOR - 1) / 2;
+
             int paddingTop = Math.max((int) (mInsets.top + cellLayoutPaddingPx.top
                     - diffOverlapFactor), 0);
             int paddingBottom = Math.max((int) (mInsets.bottom + cellLayoutPaddingPx.bottom
@@ -1952,6 +1999,7 @@
                     + mAllAppsResponsiveWidthSpec.toString());
             writer.println(prefix + "\tmResponsiveFolderHeightSpec:" + mResponsiveFolderHeightSpec);
             writer.println(prefix + "\tmResponsiveFolderWidthSpec:" + mResponsiveFolderWidthSpec);
+            writer.println(prefix + "\tmResponsiveHotseatSpec:" + mResponsiveHotseatSpec);
         }
     }
 
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index f94a3c5..3c90408 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -21,6 +21,7 @@
 import android.graphics.Rect;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.inputmethod.InputMethodManager;
@@ -37,6 +38,8 @@
  * Note: AppCompatEditText doesn't fully support #displayCompletions and #onCommitCompletion
  */
 public class ExtendedEditText extends EditText {
+    private static final String TAG = "ExtendedEditText";
+
     private final Set<OnFocusChangeListener> mOnFocusChangeListeners = new HashSet<>();
 
     private boolean mForceDisableSuggestions = false;
@@ -89,9 +92,17 @@
         return false;
     }
 
-    public void showKeyboard() {
+    /**
+     * Synchronously shows the soft input method.
+     *
+     * @param shouldFocus whether this EditText should also request focus.
+     * @return true if the keyboard is shown correctly and focus is given to this view (if
+     *     applicable).
+     */
+    public boolean showKeyboard(boolean shouldFocus) {
         onKeyboardShown();
-        showSoftInput();
+        boolean focusResult = !shouldFocus || requestFocus();
+        return focusResult && showSoftInputInternal();
     }
 
     public void hideKeyboard() {
@@ -104,10 +115,15 @@
                 .keyboardStateManager().setKeyboardState(SHOW);
     }
 
-    private boolean showSoftInput() {
-        return requestFocus() &&
-                getContext().getSystemService(InputMethodManager.class)
-                    .showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
+    private boolean showSoftInputInternal() {
+        boolean result = false;
+        InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
+        if (imm != null) {
+            result = imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
+        } else {
+            Log.w(TAG, "Failed to retrieve InputMethodManager from the system.");
+        }
+        return result;
     }
 
     public void dispatchBackKey() {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index dac6120..be14844 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.LauncherPrefs.GRID_NAME;
 import static com.android.launcher3.Utilities.dpiFromPx;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TWO_PANEL_HOME;
 import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
@@ -189,6 +190,8 @@
     public int folderSpecsId = INVALID_RESOURCE_HANDLE;
     @XmlRes
     public int folderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+    public int hotseatSpecsId = INVALID_RESOURCE_HANDLE;
+    public int hotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
 
     public String dbFile;
     public int defaultLayoutId;
@@ -311,7 +314,7 @@
         int type = displayInfo.supportedBounds.stream()
                 .mapToInt(bounds -> displayInfo.isTablet(bounds) ? flagTablet : flagPhone)
                 .reduce(0, (a, b) -> a | b);
-        if ((type == (flagPhone | flagTablet))) {
+        if ((type == (flagPhone | flagTablet)) && ENABLE_TWO_PANEL_HOME.get()) {
             // device has profiles supporting both phone and table modes
             return TYPE_MULTI_DISPLAY;
         } else if (type == flagTablet) {
@@ -368,6 +371,8 @@
         allAppsSpecsTwoPanelId = closestProfile.mAllAppsSpecsTwoPanelId;
         folderSpecsId = closestProfile.mFolderSpecsId;
         folderSpecsTwoPanelId = closestProfile.mFolderSpecsTwoPanelId;
+        hotseatSpecsId = closestProfile.mHotseatSpecsId;
+        hotseatSpecsTwoPanelId = closestProfile.mHotseatSpecsTwoPanelId;
         this.deviceType = deviceType;
 
         inlineNavButtonsEndSpacing = closestProfile.inlineNavButtonsEndSpacing;
@@ -819,6 +824,8 @@
         private final int mAllAppsSpecsTwoPanelId;
         private final int mFolderSpecsId;
         private final int mFolderSpecsTwoPanelId;
+        private final int mHotseatSpecsId;
+        private final int mHotseatSpecsTwoPanelId;
 
         public GridOption(Context context, AttributeSet attrs) {
             TypedArray a = context.obtainStyledAttributes(
@@ -896,6 +903,11 @@
                 mFolderSpecsTwoPanelId = a.getResourceId(
                         R.styleable.GridDisplayOption_folderSpecsTwoPanelId,
                         INVALID_RESOURCE_HANDLE);
+                mHotseatSpecsId = a.getResourceId(
+                        R.styleable.GridDisplayOption_hotseatSpecsId, INVALID_RESOURCE_HANDLE);
+                mHotseatSpecsTwoPanelId = a.getResourceId(
+                        R.styleable.GridDisplayOption_hotseatSpecsTwoPanelId,
+                        INVALID_RESOURCE_HANDLE);
             } else {
                 mWorkspaceSpecsId = INVALID_RESOURCE_HANDLE;
                 mWorkspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
@@ -903,6 +915,8 @@
                 mAllAppsSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
                 mFolderSpecsId = INVALID_RESOURCE_HANDLE;
                 mFolderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+                mHotseatSpecsId = INVALID_RESOURCE_HANDLE;
+                mHotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
             }
 
             int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c737074..f166833 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -44,6 +44,7 @@
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
+import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
 import static com.android.launcher3.config.FeatureFlags.SHOW_DOT_PAGINATION;
 import static com.android.launcher3.logging.StatsLogManager.EventEnum;
@@ -99,7 +100,6 @@
 import android.graphics.RectF;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.CancellationSignal;
 import android.os.Parcelable;
 import android.os.StrictMode;
 import android.os.SystemClock;
@@ -154,7 +154,6 @@
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.dragndrop.LauncherDragController;
 import com.android.launcher3.folder.Folder;
@@ -766,7 +765,7 @@
         }
 
         onDeviceProfileInitiated();
-        if (mDeviceProfile.isTwoPanels) {
+        if (FOLDABLE_SINGLE_PAGE.get() && mDeviceProfile.isTwoPanels) {
             mCellPosMapper = new TwoPanelCellPosMapper(mDeviceProfile.inv.numColumns);
         } else {
             mCellPosMapper = CellPosMapper.DEFAULT;
@@ -3249,8 +3248,6 @@
         return new TouchController[] {getDragController(), new AllAppsSwipeController(this)};
     }
 
-    public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { }
-
     public void onDragLayerHierarchyChanged() {
         updateDisallowBack();
     }
@@ -3316,10 +3313,6 @@
         return false;
     }
 
-    public DragOptions getDefaultWorkspaceDragOptions() {
-        return new DragOptions();
-    }
-
     /**
      * Animates Launcher elements during a transition to the All Apps page.
      *
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index b141e18..adaf20f 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -28,6 +28,7 @@
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
+import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT;
@@ -126,6 +127,7 @@
 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayCallbacks;
 
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -501,14 +503,19 @@
                 .log(LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
     }
 
-    public void deferRemoveExtraEmptyScreen() {
-        mDeferRemoveExtraEmptyScreen = true;
+    private boolean isTwoPanelEnabled() {
+        return !FOLDABLE_SINGLE_PAGE.get() && mLauncher.mDeviceProfile.isTwoPanels;
     }
 
     @Override
     public int getPanelCount() {
-        return super.getPanelCount();
+        return isTwoPanelEnabled() ? 2 : super.getPanelCount();
     }
+
+    public void deferRemoveExtraEmptyScreen() {
+        mDeferRemoveExtraEmptyScreen = true;
+    }
+
     @Override
     public void onDragEnd() {
         if (ENFORCE_DRAG_EVENT_ORDER) {
@@ -661,7 +668,7 @@
         // created CellLayout.
         DeviceProfile dp = mLauncher.getDeviceProfile();
         CellLayout newScreen;
-        if (dp.isTwoPanels) {
+        if (FOLDABLE_SINGLE_PAGE.get() && dp.isTwoPanels) {
             newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
                     R.layout.workspace_screen_foldable, this, false /* attachToRoot */);
         } else {
@@ -686,6 +693,15 @@
 
         if (mDragSourceInternal != null) {
             int dragSourceChildCount = mDragSourceInternal.getChildCount();
+
+            // If the icon was dragged from Hotseat, there is no page pair
+            if (isTwoPanelEnabled() && !(mDragSourceInternal.getParent() instanceof Hotseat)) {
+                int pagePairScreenId = getScreenPair(getCellPosMapper().mapModelToPresenter(
+                        dragObject.dragInfo).screenId);
+                CellLayout pagePair = mWorkspaceScreens.get(pagePairScreenId);
+                dragSourceChildCount += pagePair.getShortcutsAndWidgets().getChildCount();
+            }
+
             // When the drag view content is a LauncherAppWidgetHostView, we should increment the
             // drag source child count by 1 because the widget in drag has been detached from its
             // original parent, ShortcutAndWidgetContainer, and reattached to the DragView.
@@ -696,6 +712,11 @@
             if (dragSourceChildCount == 1) {
                 lastChildOnScreen = true;
             }
+            CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
+            if (!FOLDABLE_SINGLE_PAGE.get() && getLeftmostVisiblePageForIndex(indexOfChild(cl))
+                    == getLeftmostVisiblePageForIndex(getPageCount() - 1)) {
+                childOnFinalScreen = true;
+            }
         }
 
         // If this is the last item on the final screen
@@ -730,6 +751,9 @@
      */
     private void forEachExtraEmptyPageId(Consumer<Integer> callback) {
         callback.accept(EXTRA_EMPTY_SCREEN_ID);
+        if (isTwoPanelEnabled()) {
+            callback.accept(EXTRA_EMPTY_SCREEN_SECOND_ID);
+        }
     }
 
     /**
@@ -843,7 +867,9 @@
 
     public boolean hasExtraEmptyScreens() {
         return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)
-                && getChildCount() > getPanelCount();
+                && getChildCount() > getPanelCount()
+                && (!isTwoPanelEnabled()
+                || mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_SECOND_ID));
     }
 
     /**
@@ -949,7 +975,14 @@
      */
     @Nullable
     public CellLayout getScreenPair(CellLayout cellLayout) {
-        return null;
+        if (!isTwoPanelEnabled()) {
+            return null;
+        }
+        int screenId = getIdForScreen(cellLayout);
+        if (screenId == -1) {
+            return null;
+        }
+        return getScreenWithId(getScreenPair(screenId));
     }
 
     public void stripEmptyScreens() {
@@ -977,6 +1010,22 @@
             }
         }
 
+        // When two panel home is enabled we only remove an empty page if both visible pages are
+        // empty.
+        if (isTwoPanelEnabled()) {
+            // We go through all the pages that were marked as removable and check their page pair
+            Iterator<Integer> removeScreensIterator = removeScreens.iterator();
+            while (removeScreensIterator.hasNext()) {
+                int pageToRemove = removeScreensIterator.next();
+                int pagePair = getScreenPair(pageToRemove);
+                if (!removeScreens.contains(pagePair)) {
+                    // The page pair isn't empty so we want to remove the current page from the
+                    // removable pages' collection
+                    removeScreensIterator.remove();
+                }
+            }
+        }
+
         // We enforce at least one page (two pages on two panel home) to add new items to.
         // In the case that we remove the last such screen(s), we convert the last screen(s)
         // to the empty screen(s)
@@ -997,7 +1046,12 @@
                 removeView(cl);
             } else {
                 // The last page(s) should be converted into extra empty page(s)
-                int extraScreenId = EXTRA_EMPTY_SCREEN_ID;
+                int extraScreenId = isTwoPanelEnabled() && id % 2 == 1
+                        // This is the right panel in a two panel scenario
+                        ? EXTRA_EMPTY_SCREEN_SECOND_ID
+                        // This is either the last screen in a one panel scenario, or the left panel
+                        // in a two panel scenario when there are only two empty pages left
+                        : EXTRA_EMPTY_SCREEN_ID;
                 mWorkspaceScreens.put(extraScreenId, cl);
                 mScreenOrder.add(extraScreenId);
             }
@@ -2518,7 +2572,8 @@
         // Go through the pages and check if the dragged item is inside one of them. This block
         // is responsible for determining whether we need to snap to a different screen.
         int nextPage = getNextPage();
-        IntSet pageIndexesToVerify = IntSet.wrap(nextPage - 1, nextPage + 1);
+        IntSet pageIndexesToVerify = IntSet.wrap(nextPage - 1,
+                nextPage + (isTwoPanelEnabled() ? 2 : 1));
 
         for (int pageIndex : pageIndexesToVerify) {
             // When deciding whether to perform a page switch, we need to consider the most
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 4427a49..ecbc7a9 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -160,7 +160,7 @@
      * Focuses the search field to handle key events.
      */
     public void focusSearchField() {
-        mInput.showKeyboard();
+        mInput.showKeyboard(true /* shouldFocus */);
     }
 
     /**
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 39d5f9a..7ca6c70 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -83,11 +83,11 @@
      */
     // TODO(Block 1): Clean up flags
     public static final BooleanFlag ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES = getReleaseFlag(
-            270394041, "ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES", TEAMFOOD,
+            270394041, "ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES", ENABLED,
             "Enable option to replace decorator-based search result backgrounds with drawables");
 
     public static final BooleanFlag ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION = getReleaseFlag(
-            270394392, "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", TEAMFOOD,
+            270394392, "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", ENABLED,
             "Enable option to launch search results using the new view container transitions");
 
     // TODO(Block 2): Clean up flags
@@ -183,6 +183,17 @@
             "Enables predictive back animation from all apps and widgets to home");
 
     // TODO(Block 11): Clean up flags
+    public static final BooleanFlag ENABLE_TWO_PANEL_HOME = getDebugFlag(270392643,
+            "ENABLE_TWO_PANEL_HOME", ENABLED,
+            "Uses two panel on home screen. Only applicable on large screen devices.");
+
+    public static final BooleanFlag FOLDABLE_WORKSPACE_REORDER = getDebugFlag(270395070,
+            "FOLDABLE_WORKSPACE_REORDER", DISABLED,
+            "In foldables, when reordering the icons and widgets, is now going to use both sides");
+
+    public static final BooleanFlag FOLDABLE_SINGLE_PAGE = getDebugFlag(270395274,
+            "FOLDABLE_SINGLE_PAGE", ENABLED, "Use a single page for the workspace");
+
     public static final BooleanFlag ENABLE_PARAMETRIZE_REORDER = getDebugFlag(289420844,
             "ENABLE_PARAMETRIZE_REORDER", DISABLED,
             "Enables generating the reorder using a set of parameters");
@@ -245,7 +256,7 @@
             "Inject fallback app corpus result when AiAi fails to return it.");
 
     public static final BooleanFlag ENABLE_LONG_PRESS_NAV_HANDLE =
-            getDebugFlag(282993230, "ENABLE_LONG_PRESS_NAV_HANDLE", DISABLED,
+            getReleaseFlag(282993230, "ENABLE_LONG_PRESS_NAV_HANDLE", TEAMFOOD,
                     "Enables long pressing on the bottom bar nav handle to trigger events.");
 
     // TODO(Block 17): Clean up flags
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 00f4285..213c458 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -26,7 +26,6 @@
 import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY;
 
 import android.annotation.TargetApi;
-import android.app.ActivityOptions;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ClipData;
@@ -68,6 +67,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.pm.PinRequestHelper;
+import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.views.AbstractSlideInView;
@@ -259,9 +259,7 @@
                         .setPackage(getPackageName())
                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         Launcher.ACTIVITY_TRACKER.registerCallback(listener);
-        startActivity(homeIntent,
-                ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out)
-                        .toBundle());
+        startActivity(homeIntent, ApiWrapper.createFadeOutAnimOptions(this).toBundle());
         logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_DRAGGED);
         mFinishOnPause = true;
         return false;
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index af43ae8..48b5646 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -31,7 +31,6 @@
 import android.widget.RemoteViews;
 
 import com.android.launcher3.DragSource;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -72,15 +71,6 @@
     }
 
     @Override
-    public boolean init(Launcher launcher, boolean alreadyOnHome) {
-        super.init(launcher, alreadyOnHome);
-        if (!alreadyOnHome) {
-            launcher.useFadeOutAnimationForLauncherStart(mCancelSignal);
-        }
-        return false;
-    }
-
-    @Override
     protected PendingItemDragHelper createDragHelper() {
         final PendingAddItemInfo item;
         if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index f38cce1..55a539a 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -535,7 +535,7 @@
                     mFolderName.selectAll();
                 }
             }
-            mFolderName.showKeyboard();
+            mFolderName.showKeyboard(true /* shouldFocus */);
             mFolderName.displayCompletions(
                     Stream.of(mInfo.suggestedFolderNames.getLabels())
                             .filter(Objects::nonNull)
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 68106c4..ae44f0a 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -45,12 +45,11 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.view.ContextThemeWrapper;
+import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.TextClock;
 
@@ -94,6 +93,7 @@
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
@@ -206,14 +206,7 @@
         } else {
             mDpOrig = mDp;
         }
-
-        WindowInsets currentWindowInsets = context.getSystemService(WindowManager.class)
-                .getCurrentWindowMetrics().getWindowInsets();
-        mInsets = new Rect(
-                currentWindowInsets.getSystemWindowInsetLeft(),
-                currentWindowInsets.getSystemWindowInsetTop(),
-                currentWindowInsets.getSystemWindowInsetRight(),
-                mDp.isTaskbarPresent ? 0 : currentWindowInsets.getSystemWindowInsetBottom());
+        mInsets = getInsets(context);
         mDp.updateInsets(mInsets);
 
         mHomeElementInflater = LayoutInflater.from(
@@ -265,6 +258,26 @@
         mAppWidgetHost = new LauncherPreviewAppWidgetHost(context);
     }
 
+    /**
+     * Returns the insets of the screen closest to the display given by the context
+     */
+    private Rect getInsets(Context context) {
+        DisplayController.Info info = DisplayController.INSTANCE.get(context).getInfo();
+        float maxDiff = Float.MAX_VALUE;
+        Display display = context.getDisplay();
+        Rect insets = new Rect();
+        for (WindowBounds supportedBound : info.supportedBounds) {
+            double diff = Math.pow(display.getWidth() - supportedBound.availableSize.x, 2)
+                    + Math.pow(display.getHeight() - supportedBound.availableSize.y, 2);
+            if (supportedBound.rotationHint == context.getDisplay().getRotation()
+                    && diff < maxDiff) {
+                maxDiff = (float) diff;
+                insets = supportedBound.insets;
+            }
+        }
+        return new Rect(insets);
+    }
+
     /** Populate preview and render it. */
     public View getRenderedView(BgDataModel dataModel,
             Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index e89c0c5..aebcdd4 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.graphics;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -80,11 +82,12 @@
     private static final String KEY_DISPLAY_ID = "display_id";
     private static final String KEY_COLORS = "wallpaper_colors";
 
-    private final Context mContext;
-    private final InvariantDeviceProfile mIdp;
+    private Context mContext;
     private final IBinder mHostToken;
     private final int mWidth;
     private final int mHeight;
+    private String mGridName;
+
     private final Display mDisplay;
     private final WallpaperColors mWallpaperColors;
     private final RunnableList mOnDestroyCallbacks = new RunnableList();
@@ -97,15 +100,13 @@
 
     public PreviewSurfaceRenderer(Context context, Bundle bundle) throws Exception {
         mContext = context;
-
-        String gridName = bundle.getString("name");
+        mGridName = bundle.getString("name");
         bundle.remove("name");
-        if (gridName == null) {
-            gridName = InvariantDeviceProfile.getCurrentGridName(context);
+        if (mGridName == null) {
+            mGridName = InvariantDeviceProfile.getCurrentGridName(context);
         }
         mWallpaperColors = bundle.getParcelable(KEY_COLORS);
         mHideQsb = bundle.getBoolean(GridCustomizationsProvider.KEY_HIDE_BOTTOM_ROW);
-        mIdp = new InvariantDeviceProfile(context, gridName);
 
         mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
         mWidth = bundle.getInt(KEY_VIEW_WIDTH);
@@ -113,9 +114,9 @@
         mDisplay = context.getSystemService(DisplayManager.class)
                 .getDisplay(bundle.getInt(KEY_DISPLAY_ID));
 
-        mSurfaceControlViewHost = MAIN_EXECUTOR
-                .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken))
-                .get(5, TimeUnit.SECONDS);
+        mSurfaceControlViewHost = MAIN_EXECUTOR.submit(() -> new SurfaceControlViewHost(mContext,
+                context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY),
+                mHostToken)).get(5, TimeUnit.SECONDS);
         mOnDestroyCallbacks.add(mSurfaceControlViewHost::release);
     }
 
@@ -195,28 +196,33 @@
         }
     }
 
+    /***
+     * Generates a new context overriding the theme color and the display size without affecting the
+     * main application context
+     */
+    private Context getPreviewContext() {
+        Context context = mContext.createDisplayContext(mDisplay);
+        if (mWallpaperColors == null) {
+            return new ContextThemeWrapper(context,
+                    Themes.getActivityThemeRes(context));
+        }
+        if (Utilities.ATLEAST_R) {
+            context = context.createWindowContext(
+                    LayoutParams.TYPE_APPLICATION_OVERLAY, null);
+        }
+        LocalColorExtractor.newInstance(context)
+                .applyColorsOverride(context, mWallpaperColors);
+        return new ContextThemeWrapper(context,
+                Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
+    }
+
     @WorkerThread
     private void loadModelData() {
-        final Context inflationContext;
-        if (mWallpaperColors != null) {
-            // Create a themed context, without affecting the main application context
-            Context context = mContext.createDisplayContext(mDisplay);
-            if (Utilities.ATLEAST_R) {
-                context = context.createWindowContext(
-                        LayoutParams.TYPE_APPLICATION_OVERLAY, null);
-            }
-            LocalColorExtractor.newInstance(mContext)
-                    .applyColorsOverride(context, mWallpaperColors);
-            inflationContext = new ContextThemeWrapper(context,
-                    Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
-        } else {
-            inflationContext = new ContextThemeWrapper(mContext,
-                    Themes.getActivityThemeRes(mContext));
-        }
-
-        if (GridSizeMigrationUtil.needsToMigrate(inflationContext, mIdp)) {
+        final Context inflationContext = getPreviewContext();
+        final InvariantDeviceProfile idp = new InvariantDeviceProfile(inflationContext, mGridName);
+        if (GridSizeMigrationUtil.needsToMigrate(inflationContext, idp)) {
             // Start the migration
-            PreviewContext previewContext = new PreviewContext(inflationContext, mIdp);
+            PreviewContext previewContext = new PreviewContext(inflationContext, idp);
             // Copy existing data to preview DB
             LauncherDbUtils.copyTable(LauncherAppState.getInstance(mContext)
                     .getModel().getModelDbController().getDb(),
@@ -239,7 +245,7 @@
 
                 @Override
                 public void run() {
-                    DeviceProfile deviceProfile = mIdp.getDeviceProfile(previewContext);
+                    DeviceProfile deviceProfile = idp.getDeviceProfile(previewContext);
                     String query =
                             LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID
                                     + " or " + LauncherSettings.Favorites.CONTAINER + " = "
@@ -254,7 +260,8 @@
                             getLoadedLauncherWidgetInfo(previewContext.getBaseContext());
 
                     MAIN_EXECUTOR.execute(() -> {
-                        renderView(previewContext, mBgDataModel, mWidgetProvidersMap, spanInfo);
+                        renderView(previewContext, mBgDataModel, mWidgetProvidersMap, spanInfo,
+                                idp);
                         mOnDestroyCallbacks.add(previewContext::onDestroy);
                     });
                 }
@@ -263,7 +270,7 @@
             LauncherAppState.getInstance(inflationContext).getModel().loadAsync(dataModel -> {
                 if (dataModel != null) {
                     MAIN_EXECUTOR.execute(() -> renderView(inflationContext, dataModel, null,
-                            null));
+                            null, idp));
                 } else {
                     Log.e(TAG, "Model loading failed");
                 }
@@ -274,11 +281,11 @@
     @UiThread
     private void renderView(Context inflationContext, BgDataModel dataModel,
             Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap,
-            @Nullable final SparseArray<Size> launcherWidgetSpanInfo) {
+            @Nullable final SparseArray<Size> launcherWidgetSpanInfo, InvariantDeviceProfile idp) {
         if (mDestroyed) {
             return;
         }
-        mRenderer = new LauncherPreviewRenderer(inflationContext, mIdp,
+        mRenderer = new LauncherPreviewRenderer(inflationContext, idp,
                 mWallpaperColors, launcherWidgetSpanInfo);
         mRenderer.hideBottomRow(mHideQsb);
         View view = mRenderer.getRenderedView(dataModel, widgetProviderInfoMap);
diff --git a/src/com/android/launcher3/responsive/HotseatSpecs.kt b/src/com/android/launcher3/responsive/HotseatSpecs.kt
new file mode 100644
index 0000000..482508d
--- /dev/null
+++ b/src/com/android/launcher3/responsive/HotseatSpecs.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 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.responsive
+
+import android.content.res.TypedArray
+import android.util.Log
+import com.android.launcher3.R
+import com.android.launcher3.util.ResourceHelper
+
+class HotseatSpecs(val specs: List<HotseatSpec>) {
+
+    fun getCalculatedHeightSpec(availableHeight: Int): CalculatedHotseatSpec {
+        val spec = specs.firstOrNull { availableHeight <= it.maxAvailableSize }
+        check(spec != null) { "No available height spec found within $availableHeight." }
+        return CalculatedHotseatSpec(availableHeight, spec)
+    }
+
+    companion object {
+        private const val XML_HOTSEAT_SPEC = "hotseatSpec"
+
+        @JvmStatic
+        fun create(resourceHelper: ResourceHelper): HotseatSpecs {
+            val parser = ResponsiveSpecsParser(resourceHelper)
+            val specs = parser.parseXML(XML_HOTSEAT_SPEC, ::HotseatSpec)
+            return HotseatSpecs(specs.filter { it.specType == ResponsiveSpec.SpecType.HEIGHT })
+        }
+    }
+}
+
+data class HotseatSpec(
+    val maxAvailableSize: Int,
+    val specType: ResponsiveSpec.SpecType,
+    val hotseatQsbSpace: SizeSpec
+) {
+
+    init {
+        check(isValid()) { "Invalid HotseatSpec found." }
+    }
+
+    constructor(
+        attrs: TypedArray,
+        specs: Map<String, SizeSpec>
+    ) : this(
+        maxAvailableSize =
+            attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
+        specType =
+            ResponsiveSpec.SpecType.values()[
+                    attrs.getInt(
+                        R.styleable.ResponsiveSpec_specType,
+                        ResponsiveSpec.SpecType.HEIGHT.ordinal
+                    )],
+        hotseatQsbSpace = specs.getOrError(SizeSpec.XmlTags.HOTSEAT_QSB_SPACE)
+    )
+
+    fun isValid(): Boolean {
+        if (maxAvailableSize <= 0) {
+            Log.e(LOG_TAG, "${this::class.simpleName}#isValid - maxAvailableSize <= 0")
+            return false
+        }
+
+        // All specs need to be individually valid
+        if (!allSpecsAreValid()) {
+            Log.e(LOG_TAG, "${this::class.simpleName}#isValid - !allSpecsAreValid()")
+            return false
+        }
+
+        return true
+    }
+
+    private fun allSpecsAreValid(): Boolean {
+        return hotseatQsbSpace.isValid() && hotseatQsbSpace.onlyFixedSize()
+    }
+
+    companion object {
+        private const val LOG_TAG = "HotseatSpec"
+    }
+}
+
+class CalculatedHotseatSpec(val availableSpace: Int, val spec: HotseatSpec) {
+
+    var hotseatQsbSpace: Int = 0
+        private set
+
+    init {
+        hotseatQsbSpace = spec.hotseatQsbSpace.getCalculatedValue(availableSpace)
+    }
+
+    override fun hashCode(): Int {
+        var result = availableSpace.hashCode()
+        result = 31 * result + hotseatQsbSpace.hashCode()
+        result = 31 * result + spec.hashCode()
+        return result
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return other is CalculatedHotseatSpec &&
+            availableSpace == other.availableSpace &&
+            hotseatQsbSpace == other.hotseatQsbSpace &&
+            spec == other.spec
+    }
+
+    override fun toString(): String {
+        return "${this::class.simpleName}(" +
+            "availableSpace=$availableSpace, hotseatQsbSpace=$hotseatQsbSpace, " +
+            "${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
+            ")"
+    }
+}
diff --git a/src/com/android/launcher3/responsive/SizeSpec.kt b/src/com/android/launcher3/responsive/SizeSpec.kt
index d3868f0..c868c9f 100644
--- a/src/com/android/launcher3/responsive/SizeSpec.kt
+++ b/src/com/android/launcher3/responsive/SizeSpec.kt
@@ -107,11 +107,20 @@
         return true
     }
 
+    fun onlyFixedSize(): Boolean {
+        if (ofAvailableSpace > 0 || ofRemainderSpace > 0 || matchWorkspace) {
+            Log.e(TAG, "SizeSpec#onlyFixedSize - only fixed size allowed for this tag")
+            return false
+        }
+        return true
+    }
+
     object XmlTags {
         const val START_PADDING = "startPadding"
         const val END_PADDING = "endPadding"
         const val GUTTER = "gutter"
         const val CELL_SIZE = "cellSize"
+        const val HOTSEAT_QSB_SPACE = "hotseatQsbSpace"
     }
 
     companion object {
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 438a4a0..5306932 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TRACKPAD_GESTURE;
+import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.annotation.TargetApi;
@@ -156,7 +157,8 @@
                 return response;
 
             case TestProtocol.REQUEST_IS_TWO_PANELS:
-                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, false);
+                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        FOLDABLE_SINGLE_PAGE.get() ? false : mDeviceProfile.isTwoPanels);
                 return response;
 
             case TestProtocol.REQUEST_GET_HAD_NONTEST_EVENTS:
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 9cba19d..122b1e0 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -67,7 +67,7 @@
         if (!(v.getTag() instanceof ItemInfo)) return false;
 
         launcher.setWaitingForResult(null);
-        beginDrag(v, launcher, (ItemInfo) v.getTag(), launcher.getDefaultWorkspaceDragOptions());
+        beginDrag(v, launcher, (ItemInfo) v.getTag(), new DragOptions());
         return true;
     }
 
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index f69d299..30e0971 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -17,6 +17,7 @@
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
+import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
@@ -24,16 +25,14 @@
 import static com.android.launcher3.allapps.AllAppsTransitionController.REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS;
 import static com.android.launcher3.util.ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE;
 
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Outline;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.util.Property;
+import android.util.FloatProperty;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -51,6 +50,8 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.touch.BaseSwipeDetector;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 
@@ -66,8 +67,8 @@
 public abstract class AbstractSlideInView<T extends Context & ActivityContext>
         extends AbstractFloatingView implements SingleAxisSwipeDetector.Listener {
 
-    protected static final Property<AbstractSlideInView, Float> TRANSLATION_SHIFT =
-            new Property<AbstractSlideInView, Float>(Float.class, "translationShift") {
+    protected static final FloatProperty<AbstractSlideInView<?>> TRANSLATION_SHIFT =
+            new FloatProperty<>("translationShift") {
 
                 @Override
                 public Float get(AbstractSlideInView view) {
@@ -75,31 +76,54 @@
                 }
 
                 @Override
-                public void set(AbstractSlideInView view, Float value) {
+                public void setValue(AbstractSlideInView view, float value) {
                     view.setTranslationShift(value);
                 }
             };
     protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
     protected static final float TRANSLATION_SHIFT_OPENED = 0f;
     private static final float VIEW_NO_SCALE = 1f;
-    private static final int NO_DURATION = -1;
+    private static final int DEFAULT_DURATION = 300;
 
     protected final T mActivityContext;
 
     protected final SingleAxisSwipeDetector mSwipeDetector;
-    protected @NonNull AnimatorSet mOpenCloseAnimator;
-    private final ObjectAnimator mTranslationShiftAnimator;
+    protected @NonNull AnimatorPlaybackController mOpenCloseAnimation;
 
     protected ViewGroup mContent;
     protected final View mColorScrim;
 
+    /**
+     * Interpolator for {@link #mOpenCloseAnimation} when we are closing due to dragging downwards.
+     */
     private Interpolator mScrollInterpolator;
     private long mScrollDuration;
+    /**
+     * End progress for {@link #mOpenCloseAnimation} when we are closing due to dragging downloads.
+     * <p>
+     * There are two cases that determine this value:
+     * <ol>
+     *     <li>
+     *         If the drag interrupts the opening transition (i.e. {@link #mToTranslationShift}
+     *         is {@link #TRANSLATION_SHIFT_OPENED}), we need to animate back to {@code 0} to
+     *         reverse the animation that was paused at {@link #onDragStart(boolean, float)}.
+     *     </li>
+     *     <li>
+     *         If the drag started after the view is fully opened (i.e.
+     *         {@link #mToTranslationShift} is {@link #TRANSLATION_SHIFT_CLOSED}), the animation
+     *         that was set up at {@link #onDragStart(boolean, float)} for closing the view
+     *         should go forward to {@code 1}.
+     *     </li>
+     * </ol>
+     */
+    private float mScrollEndProgress;
 
     // range [0, 1], 0=> completely open, 1=> completely closed
     protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED;
-    /** {@link #mTranslationShift} at the invocation of {@link #onDragStart(boolean, float)}. */
-    protected float mDragStartTranslationShift;
+    protected float mFromTranslationShift;
+    protected float mToTranslationShift;
+    /** {@link #mOpenCloseAnimation} progress at {@link #onDragStart(boolean, float)}. */
+    private float mDragStartProgress;
 
     protected boolean mNoIntercept;
     protected @Nullable OnCloseListener mOnCloseBeginListener;
@@ -128,52 +152,78 @@
         mActivityContext = ActivityContext.lookupContext(context);
 
         mScrollInterpolator = Interpolators.SCROLL_CUBIC;
-        mScrollDuration = NO_DURATION;
+        mScrollDuration = DEFAULT_DURATION;
         mSwipeDetector = new SingleAxisSwipeDetector(context, this,
                 SingleAxisSwipeDetector.VERTICAL);
 
-        mOpenCloseAnimator = new AnimatorSet();
-        mTranslationShiftAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
+        mOpenCloseAnimation = new PendingAnimation(0).createPlaybackController();
 
         int scrimColor = getScrimColor(context);
         mColorScrim = scrimColor != -1 ? createColorScrim(context, scrimColor) : null;
     }
 
     /**
-     * Sets up a {@link #mOpenCloseAnimator} for opening with default parameters.
+     * Sets up a {@link #mOpenCloseAnimation} for opening with default parameters.
      *
-     * @see #setUpOpenCloseAnimator(float, Interpolator)
+     * @see #setUpOpenCloseAnimation(float, float, long)
      */
-    protected final AnimatorSet setUpDefaultOpenAnimator() {
-        return setUpOpenCloseAnimator(TRANSLATION_SHIFT_OPENED, Interpolators.FAST_OUT_SLOW_IN);
+    protected final AnimatorPlaybackController setUpDefaultOpenAnimation() {
+        AnimatorPlaybackController animation = setUpOpenCloseAnimation(
+                TRANSLATION_SHIFT_CLOSED, TRANSLATION_SHIFT_OPENED, DEFAULT_DURATION);
+        animation.getAnimationPlayer().setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        return animation;
     }
 
     /**
-     * Initializes a new {@link #mOpenCloseAnimator}.
-     * <p>
-     * Subclasses should override this method if they want to add more {@code Animator} instances
-     * to the set.
+     * Sets up a {@link #mOpenCloseAnimation} for opening with a given duration.
      *
-     * @param translationShift             translation shift to animate to.
-     * @param translationShiftInterpolator interpolator for {@link #mTranslationShiftAnimator}.
-     * @return {@link #mOpenCloseAnimator}
+     * @see #setUpOpenCloseAnimation(float, float, long)
      */
-    protected AnimatorSet setUpOpenCloseAnimator(
-            float translationShift, Interpolator translationShiftInterpolator) {
-        mOpenCloseAnimator = new AnimatorSet();
-        mOpenCloseAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
+    protected final AnimatorPlaybackController setUpOpenAnimation(long duration) {
+        return setUpOpenCloseAnimation(
+                TRANSLATION_SHIFT_CLOSED, TRANSLATION_SHIFT_OPENED, duration);
+    }
+
+    private AnimatorPlaybackController setUpCloseAnimation(long duration) {
+        return setUpOpenCloseAnimation(
+                TRANSLATION_SHIFT_OPENED, TRANSLATION_SHIFT_CLOSED, duration);
+    }
+
+    /**
+     * Initializes a new {@link #mOpenCloseAnimation}.
+     *
+     * @param fromTranslationShift translation shift to animate from.
+     * @param toTranslationShift   translation shift to animate to.
+     * @param duration             animation duration.
+     * @return {@link #mOpenCloseAnimation}
+     */
+    private AnimatorPlaybackController setUpOpenCloseAnimation(
+            float fromTranslationShift, float toTranslationShift, long duration) {
+        mFromTranslationShift = fromTranslationShift;
+        mToTranslationShift = toTranslationShift;
+
+        PendingAnimation animation = new PendingAnimation(duration);
+        animation.addEndListener(b -> {
             mSwipeDetector.finishedScrolling();
             announceAccessibilityChanges();
-        }));
+        });
 
-        mTranslationShiftAnimator.setValues(PropertyValuesHolder.ofFloat(
-                TRANSLATION_SHIFT, translationShift));
-        mTranslationShiftAnimator.setInterpolator(translationShiftInterpolator);
-        mOpenCloseAnimator.play(mTranslationShiftAnimator);
+        animation.addFloat(
+                this, TRANSLATION_SHIFT, fromTranslationShift, toTranslationShift, LINEAR);
+        onOpenCloseAnimationPending(animation);
 
-        return mOpenCloseAnimator;
+        mOpenCloseAnimation = animation.createPlaybackController();
+        return mOpenCloseAnimation;
     }
 
+    /**
+     * Invoked when a {@link #mOpenCloseAnimation} is being set up.
+     * <p>
+     * Subclasses can override this method to modify the animation before it's used to create a
+     * {@link AnimatorPlaybackController}.
+     */
+    protected void onOpenCloseAnimationPending(PendingAnimation animation) {}
+
     protected void attachToContainer() {
         if (mColorScrim != null) {
             getPopupContainer().addView(mColorScrim);
@@ -316,29 +366,33 @@
     }
 
     private boolean isOpeningAnimationRunning() {
-        return mIsOpen && mOpenCloseAnimator.isRunning();
+        return mIsOpen && mOpenCloseAnimation.getAnimationPlayer().isRunning();
     }
 
     /* SingleAxisSwipeDetector.Listener */
 
     @Override
     public void onDragStart(boolean start, float startDisplacement) {
-        mOpenCloseAnimator.cancel();
-        mDragStartTranslationShift = mTranslationShift;
+        if (mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
+            mOpenCloseAnimation.pause();
+            mDragStartProgress = mOpenCloseAnimation.getProgressFraction();
+        } else {
+            setUpCloseAnimation(DEFAULT_DURATION);
+            mDragStartProgress = 0;
+        }
     }
 
     @Override
     public boolean onDrag(float displacement) {
-        setTranslationShift(Utilities.boundToRange(
-                mDragStartTranslationShift + displacement / getShiftRange(),
-                TRANSLATION_SHIFT_OPENED,
-                TRANSLATION_SHIFT_CLOSED));
+        float progress = mDragStartProgress
+                + Math.signum(mToTranslationShift - mFromTranslationShift)
+                * (displacement / getShiftRange());
+        mOpenCloseAnimation.setPlayFraction(Utilities.boundToRange(progress, 0, 1));
         return true;
     }
 
     @Override
     public void onDragEnd(float velocity) {
-        mDragStartTranslationShift = 0;
         float successfulShiftThreshold = mActivityContext.getDeviceProfile().isTablet
                 ? TABLET_BOTTOM_SHEET_SUCCESS_TRANSITION_PROGRESS : SUCCESS_TRANSITION_PROGRESS;
         if ((mSwipeDetector.isFling(velocity) && velocity > 0)
@@ -346,10 +400,15 @@
             mScrollInterpolator = scrollInterpolatorForVelocity(velocity);
             mScrollDuration = BaseSwipeDetector.calculateDuration(
                     velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift);
+            mScrollEndProgress = mToTranslationShift == TRANSLATION_SHIFT_OPENED ? 0 : 1;
             close(true);
         } else {
-            setUpOpenCloseAnimator(TRANSLATION_SHIFT_OPENED, Interpolators.DECELERATE)
-                    .setDuration(BaseSwipeDetector.calculateDuration(velocity, mTranslationShift))
+            ValueAnimator animator = mOpenCloseAnimation.getAnimationPlayer();
+            animator.setInterpolator(Interpolators.DECELERATE);
+            animator.setFloatValues(
+                    mOpenCloseAnimation.getProgressFraction(),
+                    mToTranslationShift == TRANSLATION_SHIFT_OPENED ? 1 : 0);
+            animator.setDuration(BaseSwipeDetector.calculateDuration(velocity, mTranslationShift))
                     .start();
         }
     }
@@ -371,24 +430,27 @@
         Optional.ofNullable(mOnCloseBeginListener).ifPresent(OnCloseListener::onSlideInViewClosed);
 
         if (!animate) {
-            mOpenCloseAnimator.cancel();
+            mOpenCloseAnimation.pause();
             setTranslationShift(TRANSLATION_SHIFT_CLOSED);
             onCloseComplete();
             return;
         }
 
-        final Interpolator interpolator;
-        final long duration;
+        final ValueAnimator animator;
         if (mSwipeDetector.isIdleState()) {
-            interpolator = getIdleInterpolator();
-            duration = defaultDuration;
+            setUpCloseAnimation(defaultDuration);
+            animator = mOpenCloseAnimation.getAnimationPlayer();
+            animator.setInterpolator(getIdleInterpolator());
         } else {
-            interpolator = mScrollInterpolator;
-            duration = mScrollDuration > NO_DURATION ? mScrollDuration : defaultDuration;
+            animator = mOpenCloseAnimation.getAnimationPlayer();
+            animator.setInterpolator(mScrollInterpolator);
+            animator.setDuration(mScrollDuration);
+            mOpenCloseAnimation.getAnimationPlayer().setFloatValues(
+                    mOpenCloseAnimation.getProgressFraction(), mScrollEndProgress);
         }
-        setUpOpenCloseAnimator(TRANSLATION_SHIFT_CLOSED, interpolator)
-                .addListener(AnimatorListeners.forEndCallback(this::onCloseComplete));
-        mOpenCloseAnimator.setDuration(duration).start();
+
+        animator.addListener(AnimatorListeners.forEndCallback(this::onCloseComplete));
+        animator.start();
     }
 
     protected Interpolator getIdleInterpolator() {
diff --git a/src/com/android/launcher3/views/WidgetsEduView.java b/src/com/android/launcher3/views/WidgetsEduView.java
index 92e048b..e70b1cb 100644
--- a/src/com/android/launcher3/views/WidgetsEduView.java
+++ b/src/com/android/launcher3/views/WidgetsEduView.java
@@ -116,11 +116,11 @@
     }
 
     private void animateOpen() {
-        if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+        if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
             return;
         }
         mIsOpen = true;
-        setUpDefaultOpenAnimator().start();
+        setUpDefaultOpenAnimation().start();
     }
 
     /** Shows widget education dialog. */
diff --git a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
index c6fc5fe..80b1cdd 100644
--- a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
@@ -128,11 +128,11 @@
     }
 
     private void animateOpen() {
-        if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+        if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
             return;
         }
         mIsOpen = true;
-        setUpDefaultOpenAnimator().start();
+        setUpDefaultOpenAnimation().start();
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 82394f1..c347939 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -224,12 +224,12 @@
     }
 
     private void animateOpen() {
-        if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+        if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
             return;
         }
         mIsOpen = true;
         setupNavBarColor();
-        setUpDefaultOpenAnimator().start();
+        setUpDefaultOpenAnimation().start();
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 43f1846..438d4a0 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
+import android.animation.Animator;
 import android.content.Context;
 import android.content.pm.LauncherApps;
 import android.content.res.Configuration;
@@ -624,13 +625,12 @@
                 mContent.setAlpha(0);
                 setTranslationShift(VERTICAL_START_POSITION);
             }
-            setUpOpenCloseAnimator(
-                    TRANSLATION_SHIFT_OPENED,
-                    AnimationUtils.loadInterpolator(
-                            getContext(), android.R.interpolator.linear_out_slow_in));
+            setUpOpenAnimation(mActivityContext.getDeviceProfile().bottomSheetOpenDuration);
+            Animator animator = mOpenCloseAnimation.getAnimationPlayer();
+            animator.setInterpolator(AnimationUtils.loadInterpolator(
+                    getContext(), android.R.interpolator.linear_out_slow_in));
             post(() -> {
-                mOpenCloseAnimator
-                        .setDuration(mActivityContext.getDeviceProfile().bottomSheetOpenDuration)
+                animator.setDuration(mActivityContext.getDeviceProfile().bottomSheetOpenDuration)
                         .start();
                 mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
             });
@@ -888,6 +888,18 @@
         return mContent;
     }
 
+    /** Gets the search bar, which is used for testing */ // b/294050472
+    @VisibleForTesting
+    public View getSearchBar() {
+        return (View) mSearchBar;
+    }
+
+    /** Gets the search bar container, which is used for testing */ // b/294050472
+    @VisibleForTesting
+    public View getSearchBarContainer() {
+        return (View) mSearchBarContainer;
+    }
+
     /** Opens the first header in widget picker and scrolls to the top of the RecyclerView. */
     @VisibleForTesting
     public void openFirstHeader() {
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index d6b41c9..599a591 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.uioverrides;
 
+import android.app.ActivityOptions;
 import android.app.Person;
 import android.content.Context;
 import android.content.pm.LauncherActivityInfo;
@@ -40,4 +41,11 @@
     public static Map<String, LauncherActivityInfo> getActivityOverrides(Context context) {
         return Collections.emptyMap();
     }
+
+    /**
+     * Creates an ActivityOptions to play fade-out animation on closing targets
+     */
+    public static ActivityOptions createFadeOutAnimOptions(Context context) {
+        return ActivityOptions.makeCustomAnimation(context, 0, android.R.anim.fade_out);
+    }
 }
diff --git a/tests/res/xml/invalid_hotseat_file_case_1.xml b/tests/res/xml/invalid_hotseat_file_case_1.xml
new file mode 100644
index 0000000..fcbc5ea
--- /dev/null
+++ b/tests/res/xml/invalid_hotseat_file_case_1.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 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.
+  -->
+<hotseatSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+
+    <hotseatSpec
+        launcher:specType="height"
+        launcher:maxAvailableSize="847dp">
+        <hotseatQsbSpace launcher:ofAvailableSpace="1" />
+    </hotseatSpec>
+
+    <hotseatSpec
+        launcher:specType="height"
+        launcher:maxAvailableSize="9999dp">
+        <hotseatQsbSpace launcher:fixedSize="36dp" />
+    </hotseatSpec>
+
+</hotseatSpecs>
\ No newline at end of file
diff --git a/tests/res/xml/valid_hotseat_file.xml b/tests/res/xml/valid_hotseat_file.xml
new file mode 100644
index 0000000..c7f52e8
--- /dev/null
+++ b/tests/res/xml/valid_hotseat_file.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 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.
+  -->
+<hotseatSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+
+    <hotseatSpec
+        launcher:specType="height"
+        launcher:maxAvailableSize="847dp">
+        <hotseatQsbSpace launcher:fixedSize="24dp" />
+    </hotseatSpec>
+
+    <hotseatSpec
+        launcher:specType="height"
+        launcher:maxAvailableSize="9999dp">
+        <hotseatQsbSpace launcher:fixedSize="36dp" />
+    </hotseatSpec>
+
+</hotseatSpecs>
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index f0cedd3..dd79ca8 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -22,6 +22,7 @@
 import android.util.DisplayMetrics
 import android.view.Surface
 import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.testing.shared.ResourceUtils
 import com.android.launcher3.util.DisplayController
 import com.android.launcher3.util.NavigationMode
 import com.android.launcher3.util.WindowBounds
@@ -158,41 +159,55 @@
     }
 
     protected fun initializeVarsForTwoPanel(
-        deviceTabletSpec: DeviceSpec,
-        deviceSpec: DeviceSpec,
+        deviceSpecUnfolded: DeviceSpec,
+        deviceSpecFolded: DeviceSpec,
         isLandscape: Boolean = false,
-        isGestureMode: Boolean = true
+        isGestureMode: Boolean = true,
+        isFolded: Boolean = false
     ) {
-        val (tabletNaturalX, tabletNaturalY) = deviceTabletSpec.naturalSize
-        val tabletWindowsBounds =
-            tabletWindowsBounds(deviceTabletSpec, tabletNaturalX, tabletNaturalY)
-        val tabletDisplayInfo =
+        val (unfoldedNaturalX, unfoldedNaturalY) = deviceSpecUnfolded.naturalSize
+        val unfoldedWindowsBounds =
+            tabletWindowsBounds(deviceSpecUnfolded, unfoldedNaturalX, unfoldedNaturalY)
+        val unfoldedDisplayInfo =
             CachedDisplayInfo(
-                Point(tabletNaturalX, tabletNaturalY),
+                Point(unfoldedNaturalX, unfoldedNaturalY),
                 Surface.ROTATION_0,
                 Rect(0, 0, 0, 0)
             )
 
-        val (phoneNaturalX, phoneNaturalY) = deviceSpec.naturalSize
-        val phoneWindowsBounds =
-            phoneWindowsBounds(deviceSpec, isGestureMode, phoneNaturalX, phoneNaturalY)
-        val phoneDisplayInfo =
+        val (foldedNaturalX, foldedNaturalY) = deviceSpecFolded.naturalSize
+        val foldedWindowsBounds =
+            phoneWindowsBounds(deviceSpecFolded, isGestureMode, foldedNaturalX, foldedNaturalY)
+        val foldedDisplayInfo =
             CachedDisplayInfo(
-                Point(phoneNaturalX, phoneNaturalY),
+                Point(foldedNaturalX, foldedNaturalY),
                 Surface.ROTATION_0,
                 Rect(0, 0, 0, 0)
             )
 
         val perDisplayBoundsCache =
-            mapOf(tabletDisplayInfo to tabletWindowsBounds, phoneDisplayInfo to phoneWindowsBounds)
+            mapOf(
+                unfoldedDisplayInfo to unfoldedWindowsBounds,
+                foldedDisplayInfo to foldedWindowsBounds
+            )
 
-        initializeCommonVars(
-            perDisplayBoundsCache,
-            tabletDisplayInfo,
-            rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90,
-            isGestureMode,
-            densityDpi = deviceTabletSpec.densityDpi
-        )
+        if (isFolded) {
+            initializeCommonVars(
+                perDisplayBoundsCache = perDisplayBoundsCache,
+                displayInfo = foldedDisplayInfo,
+                rotation = if (isLandscape) Surface.ROTATION_90 else Surface.ROTATION_0,
+                isGestureMode = isGestureMode,
+                densityDpi = deviceSpecFolded.densityDpi
+            )
+        } else {
+            initializeCommonVars(
+                perDisplayBoundsCache = perDisplayBoundsCache,
+                displayInfo = unfoldedDisplayInfo,
+                rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90,
+                isGestureMode = isGestureMode,
+                densityDpi = deviceSpecUnfolded.densityDpi
+            )
+        }
     }
 
     private fun phoneWindowsBounds(
@@ -306,4 +321,12 @@
     private fun writeToDevice(context: Context, fileName: String, content: String) {
         File(context.getDir("dumpTests", Context.MODE_PRIVATE), fileName).writeText(content)
     }
+
+    protected fun Float.dpToPx(): Float {
+        return ResourceUtils.pxFromDp(this, context!!.resources.displayMetrics).toFloat()
+    }
+
+    protected fun Int.dpToPx(): Int {
+        return ResourceUtils.pxFromDp(this.toFloat(), context!!.resources.displayMetrics)
+    }
 }
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt
index 863cf76..f2a269a 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt
+++ b/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt
@@ -21,7 +21,6 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.AbstractDeviceProfileTest
-import com.android.launcher3.testing.shared.ResourceUtils
 import com.android.launcher3.tests.R
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
@@ -118,8 +117,4 @@
             assertThat(cellSizePx).isEqualTo(calculatedWorkspace.cellSizePx)
         }
     }
-
-    private fun Int.dpToPx(): Int {
-        return ResourceUtils.pxFromDp(this.toFloat(), context!!.resources.displayMetrics)
-    }
 }
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
new file mode 100644
index 0000000..0ecf7ba
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 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.responsive
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.tests.R as TestR
+import com.android.launcher3.util.TestResourceHelper
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CalculatedHotseatSpecTest : AbstractDeviceProfileTest() {
+    override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+    /**
+     * This test tests:
+     * - (height spec) gets the correct breakpoint from the XML - skips the first breakpoint
+     */
+    @Test
+    fun normalPhone_returnsSecondBreakpointSpec() {
+        val deviceSpec = deviceSpecs["phone"]!!
+        initializeVarsForPhone(deviceSpec)
+
+        // Hotseat uses the whole device height
+        val availableHeight = deviceSpec.naturalSize.second
+
+        val hotseatSpecs =
+            HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_hotseat_file))
+        val heightSpec = hotseatSpecs.getCalculatedHeightSpec(availableHeight)
+
+        assertThat(heightSpec.availableSpace).isEqualTo(availableHeight)
+        assertThat(heightSpec.hotseatQsbSpace).isEqualTo(95)
+    }
+
+    /**
+     * This test tests:
+     * - (height spec) gets the correct breakpoint from the XML - use the first breakpoint
+     */
+    @Test
+    fun smallPhone_returnsFirstBreakpointSpec() {
+        val deviceSpec = deviceSpecs["phone"]!!
+        deviceSpec.densityDpi = 540 // larger display size
+        initializeVarsForPhone(deviceSpec)
+
+        // Hotseat uses the whole device height
+        val availableHeight = deviceSpec.naturalSize.second
+
+        val hotseatSpecs =
+            HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_hotseat_file))
+        val heightSpec = hotseatSpecs.getCalculatedHeightSpec(availableHeight)
+
+        assertThat(heightSpec.availableSpace).isEqualTo(availableHeight)
+        assertThat(heightSpec.hotseatQsbSpace).isEqualTo(81)
+    }
+}
diff --git a/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt b/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt
index e21af57..4b05949 100644
--- a/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt
+++ b/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt
@@ -22,7 +22,6 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.AbstractDeviceProfileTest
 import com.android.launcher3.responsive.ResponsiveSpec.SpecType
-import com.android.launcher3.testing.shared.ResourceUtils
 import com.android.launcher3.tests.R
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
@@ -249,12 +248,4 @@
         val folderSpecs = FolderSpecs.create(resourceHelper)
         folderSpecs.getCalculatedHeightSpec(cells, availableSpace, calculatedWorkspaceSpec)
     }
-
-    private fun Float.dpToPx(): Float {
-        return ResourceUtils.pxFromDp(this, context!!.resources.displayMetrics).toFloat()
-    }
-
-    private fun Int.dpToPx(): Int {
-        return ResourceUtils.pxFromDp(this.toFloat(), context!!.resources.displayMetrics)
-    }
 }
diff --git a/tests/src/com/android/launcher3/responsive/HotseatSpecsTest.kt b/tests/src/com/android/launcher3/responsive/HotseatSpecsTest.kt
new file mode 100644
index 0000000..c764e47
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/HotseatSpecsTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 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.responsive
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.tests.R as TestR
+import com.android.launcher3.util.TestResourceHelper
+import com.android.systemui.util.dpToPx
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HotseatSpecsTest : AbstractDeviceProfileTest() {
+    override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+    @Before
+    fun setup() {
+        initializeVarsForPhone(deviceSpecs["phone"]!!)
+    }
+
+    @Test
+    fun parseValidFile() {
+        val hotseatSpecs =
+            HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_hotseat_file))
+        assertThat(hotseatSpecs.specs.size).isEqualTo(2)
+
+        val expectedSpecs =
+            listOf(
+                HotseatSpec(
+                    maxAvailableSize = 847.dpToPx(),
+                    specType = ResponsiveSpec.SpecType.HEIGHT,
+                    hotseatQsbSpace = SizeSpec(24f.dpToPx())
+                ),
+                HotseatSpec(
+                    maxAvailableSize = 9999.dpToPx(),
+                    specType = ResponsiveSpec.SpecType.HEIGHT,
+                    hotseatQsbSpace = SizeSpec(36f.dpToPx())
+                ),
+            )
+
+        assertThat(hotseatSpecs.specs.size).isEqualTo(expectedSpecs.size)
+        assertThat(hotseatSpecs.specs[0]).isEqualTo(expectedSpecs[0])
+        assertThat(hotseatSpecs.specs[1]).isEqualTo(expectedSpecs[1])
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun parseInvalidFile_spaceIsNotFixedSize_throwsError() {
+        HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.invalid_hotseat_file_case_1))
+    }
+}
diff --git a/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt b/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt
index 088cae1..8ca07c6 100644
--- a/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt
+++ b/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt
@@ -139,4 +139,20 @@
             assertThat(instance.isValid()).isEqualTo(false)
         }
     }
+
+    @Test
+    fun onlyFixedSize() {
+        assertThat(SizeSpec(fixedSize = 16f).onlyFixedSize()).isEqualTo(true)
+
+        val combinations =
+            listOf(
+                SizeSpec(0f, 1.1f, 0f, false),
+                SizeSpec(0f, 0f, 1.1f, false),
+                SizeSpec(0f, 0f, 0f, true)
+            )
+
+        for (instance in combinations) {
+            assertThat(instance.onlyFixedSize()).isEqualTo(false)
+        }
+    }
 }
diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
index f33a50a..38de071 100644
--- a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
@@ -17,6 +17,8 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.content.pm.PackageManager;
 import android.os.Build;
 import android.util.Log;
@@ -69,12 +71,9 @@
             return new Statement() {
                 @Override
                 public void evaluate() throws Throwable {
-                    if ((stability.flavors() & getRunFlavor()) != 0) {
-                        Log.d(TAG, "Running " + description.getDisplayName());
-                        base.evaluate();
-                    } else {
-                        Log.d(TAG, "Skipping " + description.getDisplayName());
-                    }
+                    assumeTrue("Ignoring the test due to @Stability annotation",
+                            (stability.flavors() & getRunFlavor()) != 0);
+                    base.evaluate();
                 }
             };
         } else {
diff --git a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
index f3f9b89..0f08eef 100644
--- a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
+++ b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
@@ -25,6 +25,7 @@
 import com.android.app.viewcapture.data.ExportedData
 import com.android.launcher3.tapl.TestHelpers
 import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter
+import com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT
 import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer
 import java.io.BufferedOutputStream
 import java.io.FileOutputStream
@@ -116,6 +117,9 @@
         // OOP tests don't produce ViewCapture data
         if (!TestHelpers.isInLauncherProcess()) return
 
+        // Due to flakiness of ViewCapture verifier, don't run the check in presubmit
+        if (TestStabilityRule.getRunFlavor() != PLATFORM_POSTSUBMIT) return
+
         var frameCount = 0
         for (i in 0 until viewCaptureData!!.windowDataCount) {
             frameCount += viewCaptureData!!.getWindowData(i).frameDataCount
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
index 38de2fa..83d9f4a 100644
--- a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
@@ -110,6 +110,7 @@
             DRAG_LAYER + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container",
             CONTENT + "LauncherRootView:id/launcher|FloatingIconView",
             RECENTS_DRAG_LAYER + "ArrowTipView",
+            DRAG_LAYER + "ArrowTipView",
             DRAG_LAYER + "FallbackRecentsView:id/overview_panel",
             RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel",
             DRAG_LAYER
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
index d9517b0..4210d06 100644
--- a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
@@ -41,12 +41,10 @@
             DRAG_LAYER
                     + "SearchContainerView:id/apps_view|AllAppsRecyclerView:id/apps_list_view"
                     + "|BubbleTextView:id/icon",
-            DRAG_LAYER + "LauncherDragView|ImageView",
             DRAG_LAYER + "LauncherRecentsView:id/overview_panel|TaskView|TextView",
             DRAG_LAYER
                     + "LauncherAllAppsContainerView:id/apps_view|AllAppsRecyclerView:id"
                     + "/apps_list_view|BubbleTextView:id/icon",
-            DRAG_LAYER + "LauncherDragView|View",
             CONTENT
                     + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id"
                     + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content"
@@ -61,7 +59,8 @@
             RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView|IconView:id/icon",
             DRAG_LAYER
                     + "SearchContainerView:id/apps_view|UniversalSearchInputView:id"
-                    + "/search_container_all_apps|View:id/ripple"
+                    + "/search_container_all_apps|View:id/ripple",
+            DRAG_LAYER + "LauncherDragView"
     ));
 
     // Per-AnalysisNode data that's specific to this detector.