Merge "Add user_type field and log it in Launcher" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 5689c8a..760d8ac 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -69,3 +69,10 @@
     description: "Enables initiating split from a fullscreen app using keyboard shortcuts"
     bug: "270394122"
 }
+
+flag {
+    name: "enable_launcher_br_metrics"
+    namespace: "launcher"
+    description: "Enables logging of Launcher restore metrics to the Backup & Restore team"
+    bug: "307527314"
+}
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index c4375d3..29b24b7 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.testing;
 
-import static com.android.launcher3.testing.shared.TestProtocol.WORKSPACE_LONG_PRESS;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
@@ -137,12 +135,10 @@
             }
 
             case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
-                testLogD(WORKSPACE_LONG_PRESS, "enablingDebugTracing");
                 TestProtocol.sDebugTracing = true;
                 return response;
 
             case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
-                testLogD(WORKSPACE_LONG_PRESS, "disablingDebugTracing");
                 TestProtocol.sDebugTracing = false;
                 return response;
 
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index a840eb4..5a9e147 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -138,7 +138,7 @@
   }
 }
 
-// Next value 53
+// Next value 54
 enum Attribute {
   option allow_alias = true;
 
@@ -183,7 +183,7 @@
   ALL_APPS_SEARCH_RESULT_CHROMETAB = 24;
   ALL_APPS_SEARCH_RESULT_NAVVYSITE = 25 [deprecated = true];
   ALL_APPS_SEARCH_RESULT_TIPS = 26;
-  ALL_APPS_SEARCH_RESULT_PEOPLE_TILE = 27;
+  ALL_APPS_SEARCH_RESULT_PEOPLE_TILE = 27 [deprecated = true];
   ALL_APPS_SEARCH_RESULT_LEGACY_SHORTCUT = 30;
   ALL_APPS_SEARCH_RESULT_ASSISTANT_MEMORY = 31;
   ALL_APPS_SEARCH_RESULT_VIDEO = 41;
@@ -192,6 +192,7 @@
   ALL_APPS_SEARCH_RESULT_LOCATION = 50;
   ALL_APPS_SEARCH_RESULT_TEXT_HEADER = 51;
   ALL_APPS_SEARCH_RESULT_NO_FULFILLMENT = 52;
+  ALL_APPS_SEARCH_RESULT_QS_TILE = 53;
 
   // Result sources
   DATA_SOURCE_APPSEARCH_APP_PREVIEW = 45;
diff --git a/quickstep/res/layout/split_instructions_view.xml b/quickstep/res/layout/split_instructions_view.xml
index c663bf4..0bbbfd5 100644
--- a/quickstep/res/layout/split_instructions_view.xml
+++ b/quickstep/res/layout/split_instructions_view.xml
@@ -30,9 +30,15 @@
         android:id="@+id/split_instructions_text"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
-        android:gravity="center"
         android:textColor="?androidprv:attr/textColorOnAccent"
-        android:drawableEnd="@drawable/ic_split_exit"
-        android:drawablePadding="@dimen/split_instructions_drawable_padding"
         android:text="@string/toast_split_select_app" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/split_instructions_text_cancel"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:textColor="?androidprv:attr/textColorOnAccent"
+        android:layout_marginStart="@dimen/split_instructions_start_margin_cancel"
+        android:text="@string/toast_split_select_app_cancel"
+        android:visibility="gone"/>
 </com.android.quickstep.views.SplitInstructionsView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 079f01b..4867eba 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -73,9 +73,9 @@
     <!--  The margin at the start of the task icon menu  -->
     <dimen name="task_thumbnail_icon_menu_start_margin">12dp</dimen>
     <!--  The margin at the top of the task icon menu  -->
-    <dimen name="task_thumbnail_icon_menu_top_margin">6dp</dimen>
-    <!--  The margin at the top of the task icon menu when expanded  -->
-    <dimen name="task_thumbnail_icon_menu_top_margin_expanded">4dp</dimen>
+    <dimen name="task_thumbnail_icon_menu_top_margin">12dp</dimen>
+    <!--  The gap at the top of the task icon menu when expanded  -->
+    <dimen name="task_thumbnail_icon_menu_expanded_gap">6dp</dimen>
     <!--  The margin at the start of the task icon view in the icon menu  -->
     <dimen name="task_thumbnail_icon_view_start_margin">6dp</dimen>
     <!--  The space around the task icon arrow within the icon menu  -->
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 3dade66..077b233 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -230,6 +230,7 @@
     <string name="action_split">Split</string>
     <!-- Label for toast with instructions for split screen selection mode. [CHAR_LIMIT=50] -->
     <string name="toast_split_select_app">Tap another app to use split screen</string>
+    <string name="toast_split_select_app_cancel"><b>Cancel</b></string>
     <string name="toast_split_select_cont_desc">Exit split screen selection</string>
     <!-- Label for toast when app selected for split isn't supported. [CHAR_LIMIT=50] -->
     <string name="toast_split_app_unsupported">Choose another app to use split screen</string>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index e77d2c6..d6ab54e 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -239,7 +239,7 @@
                     || Global.getFloat(mLauncher.getContentResolver(),
                     Global.TRANSITION_ANIMATION_SCALE, 1f) > 0;
         }
-    };;
+    };
 
     private DeviceProfile mDeviceProfile;
 
@@ -329,7 +329,7 @@
         ItemInfo tag = (ItemInfo) v.getTag();
         if (tag != null && tag.shouldUseBackgroundAnimation()) {
             ContainerAnimationRunner containerAnimationRunner = ContainerAnimationRunner.from(
-                            v, mLauncher, mStartingWindowListener, onEndCallback);
+                    v, mLauncher, mStartingWindowListener, onEndCallback);
             if (containerAnimationRunner != null) {
                 mAppLaunchRunner = containerAnimationRunner;
             }
@@ -491,9 +491,9 @@
     /**
      * Content is everything on screen except the background and the floating view (if any).
      *
-     * @param isAppOpening True when this is called when an app is opening.
-     *                     False when this is called when an app is closing.
-     * @param startDelay   Start delay duration.
+     * @param isAppOpening     True when this is called when an app is opening.
+     *                         False when this is called when an app is closing.
+     * @param startDelay       Start delay duration.
      * @param skipAllAppsScale True if we want to avoid scaling All Apps
      */
     private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening,
@@ -1060,7 +1060,7 @@
         LaunchDepthController depthController = new LaunchDepthController(mLauncher);
         ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController.stateDepth,
                         MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mLauncher))
-                .setDuration(APP_LAUNCH_DURATION);
+                        .setDuration(APP_LAUNCH_DURATION);
 
         if (allowBlurringLauncher) {
             // Create a temporary effect layer, that lives on top of launcher, so we can apply
@@ -1309,7 +1309,7 @@
             return null;
         }
 
-        final ComponentName[] taskInfoActivities = new ComponentName[] {
+        final ComponentName[] taskInfoActivities = new ComponentName[]{
                 runningTaskTarget.taskInfo.baseActivity,
                 runningTaskTarget.taskInfo.origActivity,
                 runningTaskTarget.taskInfo.realActivity,
@@ -1355,7 +1355,7 @@
                 .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx);
         float secondaryDimension = orientationHandler
                 .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx);
-        final float targetX =  primaryDimension / 2f;
+        final float targetX = primaryDimension / 2f;
         final float targetY = secondaryDimension - dp.hotseatBarSizePx;
         return new RectF(targetX - halfIconSize, targetY - halfIconSize,
                 targetX + halfIconSize, targetY + halfIconSize);
@@ -1366,7 +1366,7 @@
      */
     protected RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation,
             RemoteAnimationTarget[] targets, View launcherView, PointF velocityPxPerS,
-            RectF closingWindowStartRect, float startWindowCornerRadius) {
+            RectF closingWindowStartRectF, float startWindowCornerRadius) {
         FloatingIconView floatingIconView = null;
         FloatingWidgetView floatingWidget = null;
         RectF targetRect = new RectF();
@@ -1406,14 +1406,16 @@
 
         boolean useTaskbarHotseatParams = mDeviceProfile.isTaskbarPresent && isInHotseat;
         RectFSpringAnim anim = new RectFSpringAnim(useTaskbarHotseatParams
-                ? new TaskbarHotseatSpringConfig(mLauncher, closingWindowStartRect, targetRect)
-                : new DefaultSpringConfig(mLauncher, mDeviceProfile, closingWindowStartRect,
+                ? new TaskbarHotseatSpringConfig(mLauncher, closingWindowStartRectF, targetRect)
+                : new DefaultSpringConfig(mLauncher, mDeviceProfile, closingWindowStartRectF,
                         targetRect));
 
         // Hook up floating views to the closing window animators.
         // note the coordinate of closingWindowStartRect is based on launcher
-        Rect windowTargetBounds = new Rect();
-        closingWindowStartRect.round(windowTargetBounds);
+        Rect closingWindowStartRect = new Rect();
+        closingWindowStartRectF.round(closingWindowStartRect);
+        Rect closingWindowOriginalRect =
+                new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
         if (floatingIconView != null) {
             anim.addAnimatorListener(floatingIconView);
             floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
@@ -1425,7 +1427,7 @@
             final float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
 
             RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
-                    windowTargetBounds, startWindowCornerRadius) {
+                    closingWindowStartRect, closingWindowOriginalRect, startWindowCornerRadius) {
                 @Override
                 public void onUpdate(RectF currentRectF, float progress) {
                     finalFloatingIconView.update(1f, currentRectF, progress, windowAlphaThreshold,
@@ -1443,7 +1445,7 @@
             final float floatingWidgetAlpha = isTransluscent ? 0 : 1;
             FloatingWidgetView finalFloatingWidget = floatingWidget;
             RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
-                    windowTargetBounds, startWindowCornerRadius) {
+                    closingWindowStartRect, closingWindowOriginalRect, startWindowCornerRadius) {
                 @Override
                 public void onUpdate(RectF currentRectF, float progress) {
                     final float fallbackBackgroundAlpha =
@@ -1461,7 +1463,8 @@
             // If no floating icon or widget is present, animate the to the default window
             // target rect.
             anim.addOnUpdateListener(new SpringAnimRunner(
-                    targets, targetRect, windowTargetBounds, startWindowCornerRadius));
+                    targets, targetRect, closingWindowStartRect, closingWindowOriginalRect,
+                    startWindowCornerRadius));
         }
 
         // Use a fixed velocity to start the animation.
@@ -1647,7 +1650,7 @@
         // is initialized.
         if (launcherIsForceInvisibleOrOpening) {
             addCujInstrumentation(anim, playFallBackAnimation
-                    ? CUJ_APP_CLOSE_TO_HOME_FALLBACK :  CUJ_APP_CLOSE_TO_HOME);
+                    ? CUJ_APP_CLOSE_TO_HOME_FALLBACK : CUJ_APP_CLOSE_TO_HOME);
             // Only register the content animation for cancellation when state changes
             mLauncher.getStateManager().setCurrentAnimation(anim);
 
@@ -1961,6 +1964,7 @@
 
     /**
      * Transfer the rectangle to another coordinate if needed.
+     *
      * @param toLauncher which one is the anchor of this transfer, if true then transfer from
      *                   animation target to launcher, false transfer from launcher to animation
      *                   target.
@@ -2016,27 +2020,45 @@
         private final float mStartRadius;
         private final float mEndRadius;
         private final SurfaceTransactionApplier mSurfaceApplier;
-        private final Rect mWindowTargetBounds = new Rect();
+        private final Rect mWindowStartBounds = new Rect();
+        private final Rect mWindowOriginalBounds = new Rect();
 
         private final Rect mTmpRect = new Rect();
 
+        /**
+         * Constructor for SpringAnimRunner
+         *
+         * @param appTargets                the list of opening/closing apps
+         * @param targetRect                target rectangle
+         * @param closingWindowStartRect    start position of the window when the spring animation
+         *                                  is started. In the predictive back to home case this
+         *                                  will be smaller than closingWindowOriginalRect because
+         *                                  the window is already scaled by the user gesture
+         * @param closingWindowOriginalRect Original unscaled window rect
+         * @param startWindowCornerRadius   corner radius of window at the start position
+         */
         SpringAnimRunner(RemoteAnimationTarget[] appTargets, RectF targetRect,
-                Rect windowTargetBounds, float startWindowCornerRadius) {
+                Rect closingWindowStartRect, Rect closingWindowOriginalRect,
+                float startWindowCornerRadius) {
             mAppTargets = appTargets;
             mStartRadius = startWindowCornerRadius;
             mEndRadius = Math.max(1, targetRect.width()) / 2f;
             mSurfaceApplier = new SurfaceTransactionApplier(mDragLayer);
-            mWindowTargetBounds.set(windowTargetBounds);
+            mWindowStartBounds.set(closingWindowStartRect);
+            mWindowOriginalBounds.set(closingWindowOriginalRect);
 
             // transfer the coordinate based on animation target.
             if (mAppTargets != null) {
                 for (RemoteAnimationTarget t : mAppTargets) {
                     if (t.mode == MODE_CLOSING) {
-                        final RectF targetBounds = new RectF(mWindowTargetBounds);
+                        final RectF transferRect = new RectF(mWindowStartBounds);
                         final RectF result = new RectF();
-                        transferRectToTargetCoordinate(
-                                t, targetBounds, false, result);
-                        result.round(mWindowTargetBounds);
+                        transferRectToTargetCoordinate(t, transferRect, false, result);
+                        result.round(mWindowStartBounds);
+
+                        transferRect.set(closingWindowOriginalRect);
+                        transferRectToTargetCoordinate(t, transferRect, false, result);
+                        result.round(mWindowOriginalBounds);
                         break;
                     }
                 }
@@ -2061,7 +2083,6 @@
                 }
 
                 if (target.mode == MODE_CLOSING) {
-                    final RectF before = new RectF(currentRectF);
                     transferRectToTargetCoordinate(target, currentRectF, false, currentRectF);
                     currentRectF.round(mCurrentRect);
 
@@ -2069,20 +2090,21 @@
                     final float scale;
 
                     // We need to infer the crop (we crop the window to match the currentRectF).
-                    if (mWindowTargetBounds.height() > mWindowTargetBounds.width()) {
-                        scale = Math.min(1f, currentRectF.width() / mWindowTargetBounds.width());
+                    if (mWindowStartBounds.height() > mWindowStartBounds.width()) {
+                        scale = Math.min(1f, currentRectF.width() / mWindowOriginalBounds.width());
 
                         int unscaledHeight = (int) (mCurrentRect.height() * (1f / scale));
-                        int croppedHeight = mWindowTargetBounds.height() - unscaledHeight;
-                        mTmpRect.set(0, 0, mWindowTargetBounds.width(),
-                                mWindowTargetBounds.height() - croppedHeight);
+                        int croppedHeight = mWindowStartBounds.height() - unscaledHeight;
+                        mTmpRect.set(0, 0, mWindowOriginalBounds.width(),
+                                mWindowStartBounds.height() - croppedHeight);
                     } else {
-                        scale = Math.min(1f, currentRectF.height() / mWindowTargetBounds.height());
+                        scale = Math.min(1f, currentRectF.height()
+                                / mWindowOriginalBounds.height());
 
                         int unscaledWidth = (int) (mCurrentRect.width() * (1f / scale));
-                        int croppedWidth = mWindowTargetBounds.width() - unscaledWidth;
-                        mTmpRect.set(0, 0, mWindowTargetBounds.width() - croppedWidth,
-                                mWindowTargetBounds.height());
+                        int croppedWidth = mWindowStartBounds.width() - unscaledWidth;
+                        mTmpRect.set(0, 0, mWindowStartBounds.width() - croppedWidth,
+                                mWindowOriginalBounds.height());
                     }
 
                     // Match size and position of currentRect.
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 09376d7..bbe73ff 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfo;
@@ -101,9 +100,7 @@
 
         mLauncher.setTaskbarUIController(this);
 
-        if (!FeatureFlags.enableHomeTransitionListener()) {
-            onLauncherVisibilityChanged(mLauncher.hasBeenResumed(), true /* fromInit */);
-        }
+        onLauncherVisibilityChanged(mLauncher.hasBeenResumed(), true /* fromInit */);
 
         onStashedInAppChanged(mLauncher.getDeviceProfile());
         mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 0ef4541..3514447 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
 import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
+import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
@@ -380,10 +381,12 @@
         int navButtonSize = mContext.getResources().getDimensionPixelSize(
                 R.dimen.taskbar_nav_buttons_size);
         boolean isRtl = Utilities.isRtl(mContext.getResources());
-        mPropertyHolders.add(new StatePropertyHolder(
-                mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
-                        || (flags & FLAG_KEYGUARD_VISIBLE) != 0,
-                VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
+        if (!isPhoneMode(mContext.getDeviceProfile())) {
+            mPropertyHolders.add(new StatePropertyHolder(
+                    mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
+                            || (flags & FLAG_KEYGUARD_VISIBLE) != 0,
+                    VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
+        }
 
         // home button
         mHomeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index d29f8ea..ea3411b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -251,5 +251,5 @@
         return
     }
 
-    LottieAnimationColorUtils.updateColors(this, DARK_TO_LIGHT_COLORS, context.theme)
+    LottieAnimationColorUtils.updateToColorResources(this, DARK_TO_LIGHT_COLORS, context.theme)
 }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 8313e09..d7ff59e 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -855,7 +855,9 @@
         if (windowInsets.isVisible(WindowInsets.Type.ime())) {
             return result;
         }
-        buildAnimationController();
+        if (mGestureState.getEndTarget() == null) {
+            buildAnimationController();
+        }
         // Reapply the current shift to ensure it takes new insets into account, e.g. when long
         // pressing to stash taskbar without moving the finger.
         onCurrentShiftUpdated();
@@ -1223,12 +1225,12 @@
                         : null;
         ActiveGestureLog.INSTANCE.addLog(
                 new ActiveGestureLog.CompoundString("calculateEndTarget: velocities=(x=")
-                        .append(Float.toString(dpiFromPx(velocityPxPerMs.x)))
+                        .append(dpiFromPx(velocityPxPerMs.x))
                         .append("dp/ms, y=")
-                        .append(Float.toString(dpiFromPx(velocityPxPerMs.y)))
+                        .append(dpiFromPx(velocityPxPerMs.y))
                         .append("dp/ms), angle=")
-                        .append(Double.toString(Math.toDegrees(Math.atan2(
-                                -velocityPxPerMs.y, velocityPxPerMs.x)))), gestureEvent);
+                        .append(Math.toDegrees(Math.atan2(
+                                -velocityPxPerMs.y, velocityPxPerMs.x))), gestureEvent);
 
         if (mGestureState.isHandlingAtomicEvent()) {
             // Button mode, this is only used to go to recents.
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 406e9f4..5568459 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -43,6 +43,7 @@
 import android.view.View;
 import android.view.ViewRootImpl;
 import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.window.BackEvent;
 import android.window.BackMotionEvent;
@@ -99,6 +100,7 @@
     private final float mWindowScaleEndCornerRadius;
     private final float mWindowScaleStartCornerRadius;
     private final Interpolator mCancelInterpolator;
+    private final Interpolator mProgressInterpolator = new DecelerateInterpolator();
     private final PointF mInitialTouchPos = new PointF();
 
     private RemoteAnimationTarget mBackTarget;
@@ -139,7 +141,8 @@
      * @param handler Handler to the thread to run the animations on.
      */
     public void registerBackCallbacks(Handler handler) {
-        mBackCallback = new OnBackInvokedCallbackStub(handler, mProgressAnimator, this);
+        mBackCallback = new OnBackInvokedCallbackStub(handler, mProgressAnimator,
+                mProgressInterpolator, this);
         SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback,
                 new RemoteAnimationRunnerStub(this));
     }
@@ -147,6 +150,7 @@
     private static class OnBackInvokedCallbackStub extends IOnBackInvokedCallback.Stub {
         private Handler mHandler;
         private BackProgressAnimator mProgressAnimator;
+        private final Interpolator mProgressInterpolator;
         // LauncherBackAnimationController has strong reference to Launcher activity, the binder
         // callback should not hold strong reference to it to avoid memory leak.
         private WeakReference<LauncherBackAnimationController> mControllerRef;
@@ -154,9 +158,11 @@
         private OnBackInvokedCallbackStub(
                 Handler handler,
                 BackProgressAnimator progressAnimator,
+                Interpolator progressInterpolator,
                 LauncherBackAnimationController controller) {
             mHandler = handler;
             mProgressAnimator = progressAnimator;
+            mProgressInterpolator = progressInterpolator;
             mControllerRef = new WeakReference<>(controller);
         }
 
@@ -204,10 +210,8 @@
                     controller.startBack(backEvent);
                     mProgressAnimator.onBackStarted(backEvent, event -> {
                         float backProgress = event.getProgress();
-                        // TODO: Update once the interpolation curve spec is finalized.
                         controller.mBackProgress =
-                                1 - (1 - backProgress) * (1 - backProgress) * (1
-                                        - backProgress);
+                                mProgressInterpolator.getInterpolation(backProgress);
                         controller.updateBackProgress(controller.mBackProgress, event);
                     });
                 }
@@ -345,9 +349,16 @@
         float screenHeight = mStartRect.height();
         float width = Utilities.mapRange(progress, 1, MIN_WINDOW_SCALE) * screenWidth;
         float height = screenHeight / screenWidth * width;
-        float deltaYRatio = (event.getTouchY() - mInitialTouchPos.y) / screenHeight;
+
         // Base the window movement in the Y axis on the touch movement in the Y axis.
-        float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * mWindowMaxDeltaY * progress;
+        float rawYDelta = event.getTouchY() - mInitialTouchPos.y;
+        float yDirection = rawYDelta < 0 ? -1 : 1;
+        // limit yDelta interpretation to 1/2 of screen height in either direction
+        float deltaYRatio = Math.min(screenHeight / 2f, Math.abs(rawYDelta)) / (screenHeight / 2f);
+        float interpolatedYRatio = mProgressInterpolator.getInterpolation(deltaYRatio);
+        // limit y-shift so surface never passes 8dp screen margin
+        float deltaY = yDirection * interpolatedYRatio * Math.max(0f, (screenHeight - height)
+                / 2f - mWindowScaleMarginX);
         // Move the window along the Y axis.
         float top = (screenHeight - height) * 0.5f + deltaY;
         // Move the window along the X axis.
@@ -487,10 +498,14 @@
                 tryFinishBackAnimation();
             }
         });
+        if (mScrimLayer == null) {
+            // Scrim hasn't been attached yet. Let's attach it.
+            addScrimLayer();
+        }
         mScrimAlphaAnimator = new ValueAnimator().ofFloat(1, 0);
         mScrimAlphaAnimator.addUpdateListener(animation -> {
             float value = (Float) animation.getAnimatedValue();
-            if (mScrimLayer.isValid()) {
+            if (mScrimLayer != null && mScrimLayer.isValid()) {
                 mTransaction.setAlpha(mScrimLayer, value * mScrimAlpha);
                 mTransaction.apply();
             }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 0bc44e1..8e03a91 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -34,6 +34,7 @@
 import android.util.Log;
 import android.view.RemoteAnimationTarget;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
@@ -102,7 +103,7 @@
      * Starts a new recents animation for the activity with the given {@param intent}.
      */
     @UiThread
-    public RecentsAnimationCallbacks startRecentsAnimation(GestureState gestureState,
+    public RecentsAnimationCallbacks startRecentsAnimation(@NonNull GestureState gestureState,
             Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) {
         ActiveGestureLog.INSTANCE.addLog(
                 /* event= */ "startRecentsAnimation",
@@ -163,6 +164,7 @@
                     // other apps need to keep visible so finish the animating state after the
                     // enter animation of overview is done. Then 3p launcher can be stopped.
                     mLastGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED, () -> {
+                        if (mLastGestureState != gestureState) return;
                         // Only finish if the end target is RECENTS. Otherwise, if the target is
                         // NEW_TASK, startActivityFromRecents will be skipped.
                         if (mLastGestureState.getEndTarget() == RECENTS) {
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 20a751b..1b3f598 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -31,7 +31,6 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.SparseArray;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.WorkerThread;
 
@@ -45,6 +44,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.Preconditions;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.TaskKeyLruCache;
@@ -62,7 +62,6 @@
 public class TaskIconCache implements DisplayInfoChangeListener {
 
     private final Executor mBgExecutor;
-    private final AccessibilityManager mAccessibilityManager;
 
     private final Context mContext;
     private final TaskKeyLruCache<TaskCacheEntry> mIconCache;
@@ -79,7 +78,6 @@
     public TaskIconCache(Context context, Executor bgExecutor, IconProvider iconProvider) {
         mContext = context;
         mBgExecutor = bgExecutor;
-        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mIconProvider = iconProvider;
 
         Resources res = context.getResources();
@@ -238,14 +236,11 @@
             if ((index = mDefaultIcons.indexOfKey(userId)) >= 0) {
                 return mDefaultIcons.valueAt(index).newIcon(mContext);
             } else {
-                try (BaseIconFactory li = getIconFactory()) {
-                    BitmapInfo info = mDefaultIconBase.withFlags(
-                            li.getBitmapFlagOp(new IconOptions()
-                                    .setUser(UserCache.INSTANCE.get(mContext)
-                                            .getUserInfo(UserHandle.of(userId)))));
-                    mDefaultIcons.put(userId, info);
-                    return info.newIcon(mContext);
-                }
+                BitmapInfo info = mDefaultIconBase.withFlags(
+                        UserCache.INSTANCE.get(mContext).getUserInfo(UserHandle.of(userId))
+                                .applyBitmapInfoFlags(FlagOp.NO_OP));
+                mDefaultIcons.put(userId, info);
+                return info.newIcon(mContext);
             }
         }
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index fce188f..1bec970 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -184,7 +184,7 @@
         mAnimatedBackground.setAnimation(resources.openRawResource(R.raw.all_set_page_bg),
                 null);
 
-        LottieAnimationColorUtils.updateColors(
+        LottieAnimationColorUtils.updateToColorResources(
                 mAnimatedBackground,
                 Map.of(LOTTIE_PRIMARY_COLOR_TOKEN, R.color.all_set_bg_primary,
                         LOTTIE_TERTIARY_COLOR_TOKEN, R.color.all_set_bg_tertiary),
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 631cff7..404bca9 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -41,13 +41,13 @@
         super(fragment, tutorialType);
         // Set the Lottie animation colors specifically for the Back gesture
         if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
-            LottieAnimationColorUtils.updateColors(
+            LottieAnimationColorUtils.updateToArgbColors(
                     mAnimatedGestureDemonstration,
                     Map.of(".onSurfaceBack", fragment.mRootView.mColorOnSurfaceBack,
                             ".surfaceBack", fragment.mRootView.mColorSurfaceBack,
                             ".secondaryBack", fragment.mRootView.mColorSecondaryBack));
 
-            LottieAnimationColorUtils.updateColors(
+            LottieAnimationColorUtils.updateToArgbColors(
                     mCheckmarkAnimation,
                     Map.of(".checkmark",
                             Utilities.isDarkTheme(mContext)
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index daac99b..df552cf 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -38,13 +38,13 @@
 
         // Set the Lottie animation colors specifically for the Home gesture
         if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
-            LottieAnimationColorUtils.updateColors(
+            LottieAnimationColorUtils.updateToArgbColors(
                     mAnimatedGestureDemonstration,
                     Map.of(".onSurfaceHome", fragment.mRootView.mColorOnSurfaceHome,
                             ".surfaceHome", fragment.mRootView.mColorSurfaceHome,
                             ".secondaryHome", fragment.mRootView.mColorSecondaryHome));
 
-            LottieAnimationColorUtils.updateColors(
+            LottieAnimationColorUtils.updateToArgbColors(
                     mCheckmarkAnimation,
                     Map.of(".checkmark",
                             Utilities.isDarkTheme(mContext)
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index afdc1e5..65f3a01 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -53,13 +53,13 @@
 
         // Set the Lottie animation colors specifically for the Overview gesture
         if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
-            LottieAnimationColorUtils.updateColors(
+            LottieAnimationColorUtils.updateToArgbColors(
                     mAnimatedGestureDemonstration,
                     Map.of(".onSurfaceOverview", fragment.mRootView.mColorOnSurfaceOverview,
                             ".surfaceOverview", fragment.mRootView.mColorSurfaceOverview,
                             ".secondaryOverview", fragment.mRootView.mColorSecondaryOverview));
 
-            LottieAnimationColorUtils.updateColors(
+            LottieAnimationColorUtils.updateToArgbColors(
                     mCheckmarkAnimation,
                     Map.of(".checkmark",
                             Utilities.isDarkTheme(mContext)
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index fa4a248..bce2e82 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -32,6 +32,7 @@
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW;
 
 import android.content.Context;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.StatsEvent;
 import android.view.View;
@@ -227,6 +228,7 @@
         private Optional<Integer> mCardinality = Optional.empty();
         private int mInputType = SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__UNKNOWN;
         private Optional<Integer> mFeatures = Optional.empty();
+        private Optional<String> mPackageName = Optional.empty();
 
         StatsCompatLogger(Context context, ActivityContext activityContext) {
             mContext = context;
@@ -332,6 +334,12 @@
         }
 
         @Override
+        public StatsLogger withPackageName(@Nullable String packageName) {
+            mPackageName = Optional.ofNullable(packageName);
+            return this;
+        }
+
+        @Override
         public void log(EventEnum event) {
             if (!Utilities.ATLEAST_R) {
                 return;
@@ -431,6 +439,7 @@
             int srcState = mSrcState;
             int dstState = mDstState;
             int inputType = mInputType;
+            String packageName = mPackageName.orElseGet(() -> getPackageName(atomInfo));
             if (IS_VERBOSE) {
                 String name = (event instanceof Enum) ? ((Enum) event).name() :
                         event.getId() + "";
@@ -448,6 +457,9 @@
                 if (atomInfo.hasContainerInfo()) {
                     logStringBuilder.append("\n").append(atomInfo);
                 }
+                if (!TextUtils.isEmpty(packageName)) {
+                    logStringBuilder.append(String.format("\nPackage name: %s", packageName));
+                }
                 Log.d(TAG, logStringBuilder.toString());
             }
 
@@ -472,7 +484,7 @@
                     atomInfo.getItemCase().getNumber() /* target_id */,
                     instanceId.getId() /* instance_id TODO */,
                     0 /* uid TODO */,
-                    getPackageName(atomInfo) /* package_name */,
+                    packageName /* package_name */,
                     getComponentName(atomInfo) /* component_name */,
                     getGridX(atomInfo, false) /* grid_x */,
                     getGridY(atomInfo, false) /* grid_y */,
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index 6f4b6cb..278ca56 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -18,6 +18,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.util.Preconditions;
+
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -43,14 +45,6 @@
      */
     public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID";
 
-    private static final int TYPE_ONE_OFF = 0;
-    private static final int TYPE_FLOAT = 1;
-    private static final int TYPE_INTEGER = 2;
-    private static final int TYPE_BOOL_TRUE = 3;
-    private static final int TYPE_BOOL_FALSE = 4;
-    private static final int TYPE_COMPOUND_STRING = 5;
-    private static final int TYPE_GESTURE_EVENT = 6;
-
     private final EventLog[] logs;
     private int nextIndex;
     private int mCurrentLogId = 100;
@@ -67,9 +61,12 @@
      *                   execution.
      */
     public void trackEvent(@Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
-        addLog(TYPE_GESTURE_EVENT, "", 0, CompoundString.NO_OP, gestureEvent);
+        addLog(CompoundString.NO_OP, gestureEvent);
     }
 
+    /**
+     * Adds a log to be printed at log-dump-time.
+     */
     public void addLog(String event) {
         addLog(event, null);
     }
@@ -82,54 +79,35 @@
         addLog(event, extras, null);
     }
 
-    public void addLog(CompoundString compoundString) {
-        if (compoundString == CompoundString.NO_OP) return;
-        addLog(TYPE_COMPOUND_STRING, "", 0, compoundString, null);
-    }
-
-    public void addLog(
-            CompoundString compoundString,
-            @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
-        if (compoundString == CompoundString.NO_OP) {
-            trackEvent(gestureEvent);
-            return;
-        }
-        addLog(TYPE_COMPOUND_STRING, "", 0, compoundString, gestureEvent);
-    }
-
     /**
-     * Adds a log and track the associated event for error detection.
+     * Adds a log to be printed at log-dump-time and track the associated event for error detection.
      *
      * @param gestureEvent GestureEvent representing the event being logged.
      */
     public void addLog(
             String event, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
-        addLog(TYPE_ONE_OFF, event, 0, CompoundString.NO_OP, gestureEvent);
+        addLog(new CompoundString(event), gestureEvent);
     }
 
     public void addLog(
             String event,
             int extras,
             @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
-        addLog(TYPE_INTEGER, event, extras, CompoundString.NO_OP, gestureEvent);
+        addLog(new CompoundString(event).append(": ").append(extras), gestureEvent);
     }
 
     public void addLog(
             String event,
             boolean extras,
             @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
-        addLog(
-                extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE,
-                event,
-                0,
-                CompoundString.NO_OP,
-                gestureEvent);
+        addLog(new CompoundString(event).append(": ").append(extras), gestureEvent);
     }
 
-    private void addLog(
-            int type,
-            String event,
-            float extras,
+    public void addLog(CompoundString compoundString) {
+        addLog(compoundString, null);
+    }
+
+    public void addLog(
             CompoundString compoundString,
             @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
         EventLog lastEventLog = logs[(nextIndex + logs.length - 1) % logs.length];
@@ -137,7 +115,7 @@
             EventLog eventLog = new EventLog(mCurrentLogId, mIsFullyGesturalNavMode);
             EventEntry eventEntry = new EventEntry();
 
-            eventEntry.update(type, event, extras, compoundString, gestureEvent);
+            eventEntry.update(compoundString, gestureEvent);
             eventLog.eventEntries.add(eventEntry);
             logs[nextIndex] = eventLog;
             nextIndex = (nextIndex + 1) % logs.length;
@@ -146,17 +124,17 @@
 
         // Update the last EventLog
         List<EventEntry> lastEventEntries = lastEventLog.eventEntries;
-        EventEntry lastEntry = lastEventEntries.size() > 0
+        EventEntry lastEntry = !lastEventEntries.isEmpty()
                 ? lastEventEntries.get(lastEventEntries.size() - 1) : null;
 
         // Update the last EventEntry if it's a duplicate
-        if (isEntrySame(lastEntry, type, event, extras, compoundString, gestureEvent)) {
+        if (isEntrySame(lastEntry, compoundString, gestureEvent)) {
             lastEntry.duplicateCount++;
             return;
         }
         EventEntry eventEntry = new EventEntry();
 
-        eventEntry.update(type, event, extras, compoundString, gestureEvent);
+        eventEntry.update(compoundString, gestureEvent);
         lastEventEntries.add(eventEntry);
     }
 
@@ -181,30 +159,14 @@
 
             writer.println(prefix + "\tLogs for logId: " + eventLog.logId);
             for (EventEntry eventEntry : eventLog.eventEntries) {
+                if (eventEntry.mCompoundString.mIsNoOp) {
+                    continue;
+                }
                 date.setTime(eventEntry.time);
 
-                StringBuilder msg = new StringBuilder(prefix + "\t\t").append(sdf.format(date))
-                        .append(eventEntry.event);
-                switch (eventEntry.type) {
-                    case TYPE_BOOL_FALSE:
-                        msg.append(": false");
-                        break;
-                    case TYPE_BOOL_TRUE:
-                        msg.append(": true");
-                        break;
-                    case TYPE_FLOAT:
-                        msg.append(": ").append(eventEntry.extras);
-                        break;
-                    case TYPE_INTEGER:
-                        msg.append(": ").append((int) eventEntry.extras);
-                        break;
-                    case TYPE_COMPOUND_STRING:
-                        msg.append(eventEntry.mCompoundString);
-                        break;
-                    case TYPE_GESTURE_EVENT:
-                        continue;
-                    default: // fall out
-                }
+                StringBuilder msg = new StringBuilder(prefix + "\t\t")
+                        .append(sdf.format(date))
+                        .append(eventEntry.mCompoundString);
                 if (eventEntry.duplicateCount > 0) {
                     msg.append(" & ").append(eventEntry.duplicateCount).append(" similar events");
                 }
@@ -232,15 +194,9 @@
 
     private boolean isEntrySame(
             EventEntry entry,
-            int type,
-            String event,
-            float extras,
             CompoundString compoundString,
             ActiveGestureErrorDetector.GestureEvent gestureEvent) {
         return entry != null
-                && entry.type == type
-                && entry.event.equals(event)
-                && Float.compare(entry.extras, extras) == 0
                 && entry.mCompoundString.equals(compoundString)
                 && entry.gestureEvent == gestureEvent;
     }
@@ -248,9 +204,6 @@
     /** A single event entry. */
     protected static class EventEntry {
 
-        private int type;
-        private String event;
-        private float extras;
         @NonNull private CompoundString mCompoundString;
         private ActiveGestureErrorDetector.GestureEvent gestureEvent;
         private long time;
@@ -264,14 +217,8 @@
         }
 
         private void update(
-                int type,
-                String event,
-                float extras,
                 @NonNull CompoundString compoundString,
                 ActiveGestureErrorDetector.GestureEvent gestureEvent) {
-            this.type = type;
-            this.event = event;
-            this.extras = extras;
             this.mCompoundString = compoundString;
             this.gestureEvent = gestureEvent;
             time = System.currentTimeMillis();
@@ -302,6 +249,7 @@
         public static final CompoundString NO_OP = new CompoundString();
 
         private final List<String> mSubstrings;
+        private final List<Object> mArgs;
 
         private final boolean mIsNoOp;
 
@@ -313,10 +261,12 @@
             mIsNoOp = substring == null;
             if (mIsNoOp) {
                 mSubstrings = null;
+                mArgs = null;
                 return;
             }
             mSubstrings = new ArrayList<>();
             mSubstrings.add(substring);
+            mArgs = new ArrayList<>();
         }
 
         public CompoundString append(CompoundString substring) {
@@ -338,19 +288,41 @@
         }
 
         public CompoundString append(int num) {
-            if (mIsNoOp) {
-                return this;
-            }
-            mSubstrings.add(Integer.toString(num));
+            mArgs.add(num);
 
-            return this;
+            return append("%d");
+        }
+
+        public CompoundString append(float num) {
+            mArgs.add(num);
+
+            return append("%.2f");
+        }
+
+        public CompoundString append(double num) {
+            mArgs.add(num);
+
+            return append("%.2f");
+        }
+
+        public CompoundString append(boolean bool) {
+            mArgs.add(bool);
+
+            return append("%b");
+        }
+
+        public Object[] getArgs() {
+            return mArgs.toArray();
         }
 
         @Override
         public String toString() {
-            if (mIsNoOp) {
-                return "ERROR: cannot use No-Op compound string";
-            }
+            return String.format(toUnformattedString(), getArgs());
+        }
+
+        public String toUnformattedString() {
+            Preconditions.assertTrue(!mIsNoOp);
+
             StringBuilder sb = new StringBuilder();
             for (String substring : mSubstrings) {
                 sb.append(substring);
diff --git a/quickstep/src/com/android/quickstep/util/LottieAnimationColorUtils.java b/quickstep/src/com/android/quickstep/util/LottieAnimationColorUtils.java
index f98b04b..8833b46 100644
--- a/quickstep/src/com/android/quickstep/util/LottieAnimationColorUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LottieAnimationColorUtils.java
@@ -17,13 +17,11 @@
 
 import static com.airbnb.lottie.LottieProperty.COLOR_FILTER;
 
-import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.airbnb.lottie.LottieAnimationView;
 import com.airbnb.lottie.model.KeyPath;
@@ -40,51 +38,39 @@
     /**
      * Updates the given Lottie animation's tokenized colors according to the given mapping.
      * <p>
-     * Use this method signature only when {@code tokenToColorCodeMap} maps to packed ARBG color
-     * integers.
-     * <p>
      * @param animationView {@link LottieAnimationView} whose animation's colors need to be updated
-     * @param tokenToColorCodeMap A mapping from the color tokens used in the Lottie file used in
+     * @param tokenToArgbColorMap A mapping from the color tokens used in the Lottie file used in
      *                            {@code animationView} to packed ARBG color integers.
      */
-    public static void updateColors(
+    public static void updateToArgbColors(
             @NonNull LottieAnimationView animationView,
-            @NonNull Map<String, Integer> tokenToColorCodeMap) {
-        updateColors(animationView, tokenToColorCodeMap, null);
-    }
-
-    /**
-     * Updates the given Lottie animation's tokenized colors according to the given mapping.
-     * <p>
-     * Use this method signature with a non-null theme only when {@code tokenToColorCodeMap} maps
-     * to color resource references.
-     * <p>
-     * @param animationView {@link LottieAnimationView} whose animation's colors need to be updated
-     * @param tokenToColorCodeMap A mapping from the color tokens used in the Lottie file used in
-     *                            {@code animationView} to packed ARBG color integers or color
-     *                            resource references.
-     * @param theme {@link Theme} to be used when resolving color resource references. {@code null}
-     *              iff {@code tokenToColorCodeMap} maps to packed ARBG color integers.
-     */
-    public static void updateColors(
-            @NonNull LottieAnimationView animationView,
-            @NonNull Map<String, Integer> tokenToColorCodeMap,
-            @Nullable Theme theme) {
-        Resources resources = animationView.getResources();
-        final Map<String, Integer> tokenToColorMap = theme == null
-                // tokenToColorCodeMap maps directly to ARBG values
-                ? tokenToColorCodeMap
-                // tokenToColorCodeMap maps to color references, build a mapping to resolved colors
-                : tokenToColorCodeMap.keySet().stream().collect(Collectors.toMap(
-                        Function.identity(),
-                        token -> resources.getColor(tokenToColorCodeMap.get(token), theme)));
-
+            @NonNull Map<String, Integer> tokenToArgbColorMap) {
         animationView.addLottieOnCompositionLoadedListener(
-                composition -> tokenToColorMap.forEach(
+                composition -> tokenToArgbColorMap.forEach(
                         (token, color) -> animationView.addValueCallback(
                                 new KeyPath("**", token, "**"),
                                 COLOR_FILTER,
                                 frameInfo -> new PorterDuffColorFilter(
                                         color, PorterDuff.Mode.SRC_ATOP))));
     }
+
+    /**
+     * Updates the given Lottie animation's tokenized colors according to the given mapping.
+     * <p>
+     * @param animationView {@link LottieAnimationView} whose animation's colors need to be updated
+     * @param tokenToColorResourceMap A mapping from the color tokens used in the Lottie file used
+     *                                in {@code animationView} to color resource references.
+     * @param theme {@link Theme} to be used when resolving color resource references.
+     */
+    public static void updateToColorResources(
+            @NonNull LottieAnimationView animationView,
+            @NonNull Map<String, Integer> tokenToColorResourceMap,
+            @NonNull Theme theme) {
+        updateToArgbColors(
+                animationView,
+                tokenToColorResourceMap.keySet().stream().collect(Collectors.toMap(
+                        Function.identity(),
+                        token -> animationView.getResources().getColor(
+                                tokenToColorResourceMap.get(token), theme))));
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 6ee65d4..1effaff 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -134,7 +134,7 @@
         val iconView: View = taskIdAttributeContainer.iconView.asView()
         builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLASH_ALPHA, 1f))
         thumbnail.setShowSplashForSplitSelection(true)
-        if (deviceProfile.isLandscape) {
+        if (deviceProfile.isLeftRightSplit) {
             // Center view first so scaling happens uniformly, alternatively we can move pivotX to 0
             val centerThumbnailTranslationX: Float = (taskViewWidth - thumbnail.width) / 2f
             val centerIconTranslationX: Float = (taskViewWidth - iconView.width) / 2f
@@ -252,6 +252,7 @@
         splitSelectStateController.splitInstructionsView = splitInstructionsView
         val timings = AnimUtils.getDeviceOverviewToSplitTimings(launcher.deviceProfile.isTablet)
         val anim = PendingAnimation(100 /*duration */)
+        splitInstructionsView.alpha = 0f
         anim.setViewAlpha(splitInstructionsView, 1f,
                 Interpolators.clampToProgress(Interpolators.LINEAR,
                         timings.instructionsContainerFadeInStartOffset,
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index bdbdfd8..ebdc3a8 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -53,7 +53,6 @@
 public class SplitToWorkspaceController {
 
     private final Launcher mLauncher;
-    private final DeviceProfile mDP;
     private final SplitSelectStateController mController;
 
     private final int mHalfDividerSize;
@@ -61,7 +60,6 @@
 
     public SplitToWorkspaceController(Launcher launcher, SplitSelectStateController controller) {
         mLauncher = launcher;
-        mDP = mLauncher.getDeviceProfile();
         mController = controller;
         mIconCache = LauncherAppState.getInstanceNoCreate().getIconCache();
         mHalfDividerSize = mLauncher.getResources().getDimensionPixelSize(
@@ -136,7 +134,8 @@
 
     private void startWorkspaceAnimation(@NonNull View view, @Nullable Bitmap bitmap,
             @Nullable Drawable icon) {
-        boolean isTablet = mLauncher.getDeviceProfile().isTablet;
+        DeviceProfile dp = mLauncher.getDeviceProfile();
+        boolean isTablet = dp.isTablet;
         SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet);
         PendingAnimation pendingAnimation = new PendingAnimation(timings.getDuration());
 
@@ -147,7 +146,7 @@
 
         RecentsView recentsView = mLauncher.getOverviewPanel();
         recentsView.getPagedOrientationHandler().getFinalSplitPlaceholderBounds(mHalfDividerSize,
-                mDP, mController.getActiveSplitStagePosition(), firstTaskEndingBounds,
+                dp, mController.getActiveSplitStagePosition(), firstTaskEndingBounds,
                 secondTaskEndingBounds);
 
         FloatingTaskView firstFloatingTaskView = mController.getFirstFloatingTaskView();
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index e5a0e10..57b265b 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -180,7 +180,7 @@
         }
 
         // For portrait grid only height of task changes, not width. So we keep the text the same
-        if (!mActivity.getDeviceProfile().isLandscape) {
+        if (!mActivity.getDeviceProfile().isLeftRightSplit) {
             mSplitBannerConfig = SPLIT_GRID_BANNER_LARGE;
             return;
         }
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 7f1d619..b549058 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -308,9 +308,10 @@
 
         requestLayout();
 
-        mSplitButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
-                (dp.isLandscape ? R.drawable.ic_split_horizontal : R.drawable.ic_split_vertical),
-                0, 0, 0);
+        int splitIconRes = dp.isLeftRightSplit
+                ? R.drawable.ic_split_horizontal
+                : R.drawable.ic_split_vertical;
+        mSplitButton.setCompoundDrawablesRelativeWithIntrinsicBounds(splitIconRes, 0, 0, 0);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 22a5064..9769d6b 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -3417,8 +3417,8 @@
         boolean closeGapBetweenClearAll = false;
         boolean isClearAllHidden = isClearAllHidden();
         boolean snapToLastTask = false;
-        boolean isLandscapeSplit =
-                mActivity.getDeviceProfile().isLandscape && isSplitSelectionActive();
+        boolean isLeftRightSplit =
+                mActivity.getDeviceProfile().isLeftRightSplit && isSplitSelectionActive();
         TaskView lastGridTaskView = showAsGrid ? getLastGridTaskView() : null;
         int currentPageScroll = getScrollForPage(mCurrentPage);
         int lastGridTaskScroll = getScrollForPage(indexOfChild(lastGridTaskView));
@@ -3478,7 +3478,7 @@
                     snapToLastTask = true;
                 }
             }
-            if (isLandscapeSplit && !isStagingFocusedTask) {
+            if (isLeftRightSplit && !isStagingFocusedTask) {
                 // LastTask's scroll is the minimum scroll in split select, if current scroll is
                 // beyond that, we'll need to snap to last task instead.
                 TaskView lastTask = getLastGridTaskView();
@@ -3493,7 +3493,7 @@
             }
             if (snapToLastTask) {
                 longGridRowWidthDiff += getSnapToLastTaskScrollDiff();
-            } else if (isLandscapeSplit && currentPageSnapsToEndOfGrid) {
+            } else if (isLeftRightSplit && currentPageSnapsToEndOfGrid) {
                 // Use last task as reference point for scroll diff and snapping calculation as it's
                 // the only invariant point in landscape split screen.
                 snapToLastTask = true;
@@ -4878,7 +4878,7 @@
         int direction = orientationHandler.getSplitTranslationDirectionFactor(
                 splitPosition, deviceProfile);
 
-        if (deviceProfile.isTablet && deviceProfile.isLandscape) {
+        if (deviceProfile.isTablet && deviceProfile.isLeftRightSplit) {
             // Only shift TaskViews if there is not enough space on the side of
             // mLastComputedTaskSize to minimize motion.
             int sideSpace = mIsRtl
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index e8f06ee..8bc85cf 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -17,14 +17,12 @@
 package com.android.quickstep.views;
 
 import android.content.Context;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
-import android.view.MotionEvent;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.FrameLayout;
+import android.widget.LinearLayout;
 
 import androidx.annotation.Nullable;
 import androidx.appcompat.widget.AppCompatTextView;
@@ -41,9 +39,11 @@
  *
  * Appears and disappears concurrently with a FloatingTaskView.
  */
-public class SplitInstructionsView extends FrameLayout {
+public class SplitInstructionsView extends LinearLayout {
     private final StatefulActivity mLauncher;
-    private AppCompatTextView mTextView;
+    private AppCompatTextView mInstructionTextView;
+    /** Only used if {@link com.android.wm.shell.FeatureFlags#enableSplitContextual()} is true. */
+    private AppCompatTextView mCancelTextView;
 
     public static final FloatProperty<SplitInstructionsView> UNFOLD =
             new FloatProperty<SplitInstructionsView>("SplitInstructionsUnfold") {
@@ -97,22 +97,13 @@
     }
 
     private void init() {
-        mTextView = findViewById(R.id.split_instructions_text);
+        mInstructionTextView = findViewById(R.id.split_instructions_text);
+        mCancelTextView = findViewById(R.id.split_instructions_text_cancel);
 
-        if (!FeatureFlags.enableSplitContextually()) {
-            mTextView.setCompoundDrawables(null, null, null, null);
-            return;
+        if (FeatureFlags.enableSplitContextually()) {
+            mCancelTextView.setVisibility(VISIBLE);
+            mCancelTextView.setOnClickListener((v) -> exitSplitSelection());
         }
-
-        mTextView.setOnTouchListener((v, event) -> {
-            if (isTouchInsideRightCompoundDrawable(event)) {
-                if (event.getAction() == MotionEvent.ACTION_UP) {
-                    exitSplitSelection();
-                }
-                return true;
-            }
-            return false;
-        });
     }
 
     private void exitSplitSelection() {
@@ -121,20 +112,6 @@
         mLauncher.getStateManager().goToState(LauncherState.NORMAL);
     }
 
-    private boolean isTouchInsideRightCompoundDrawable(MotionEvent event) {
-        // Get the right compound drawable of the TextView.
-        Drawable rightDrawable = mTextView.getCompoundDrawablesRelative()[2];
-
-        // Check if the touch event intersects with the drawable's bounds.
-        if (rightDrawable != null) {
-            // We can get away w/o caring about the Y bounds since it's such a small view, if it's
-            // above/below the drawable just assume they meant to touch it. ¯\_(ツ)_/¯
-            return  event.getX() >= (mTextView.getWidth() - rightDrawable.getBounds().width());
-        } else {
-            return false;
-        }
-    }
-
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
@@ -172,6 +149,6 @@
     }
 
     public AppCompatTextView getTextView() {
-        return mTextView;
+        return mInstructionTextView;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 0c816b8..cf89d2e 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -311,14 +311,14 @@
                         - mActivity.getDeviceProfile().getOverviewActionsClaimedSpaceBelow();
                 float midpoint = (taskBottom + taskbarTop) / 2f;
                 additionalTranslationY = -Math.max(menuBottom - midpoint, 0);
-            } else {
-                // Animate the menu to leave a small margin at the top of the task.
-                additionalTranslationY = getResources().getDimensionPixelSize(
-                        R.dimen.task_thumbnail_icon_menu_top_margin_expanded);
             }
+            // Translate the menu to account for the expansion of the app chip menu as well.
+            float expandOffsetTranslationY = getResources().getDimensionPixelSize(
+                    R.dimen.task_thumbnail_icon_menu_expanded_gap);
             ObjectAnimator translationYAnim = ObjectAnimator.ofFloat(this, TRANSLATION_Y,
                     closing ? mMenuTranslationYBeforeOpen
-                            : mMenuTranslationYBeforeOpen + additionalTranslationY);
+                            : mMenuTranslationYBeforeOpen + additionalTranslationY
+                                    + expandOffsetTranslationY);
             translationYAnim.setInterpolator(EMPHASIZED);
 
             ObjectAnimator menuTranslationYAnim = ObjectAnimator.ofFloat(
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
index ba9ae67..fc757b4 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
@@ -25,7 +25,6 @@
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.tapl.Taskbar;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.TestUtil;
@@ -55,7 +54,7 @@
                 "com.google.android.apps.nexuslauncher.tests",
                 "com.android.launcher3.testcomponent.BaseTestingActivity");
         mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, layoutBuilder);
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
         startAppFast(CALCULATOR_APP_PACKAGE);
         mLauncher.enableBlockTimeout(true);
         mLauncher.showTaskbarIfHidden();
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 3a89278..85440e9 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -22,7 +22,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 
@@ -40,7 +40,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
         // b/143488140
         mLauncher.goHome();
         // Start an activity where the gestures start.
diff --git a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
index c4c95bc..3f806d1 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
index 74f37a4..829e54b 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
@@ -22,7 +22,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.tapl.KeyboardQuickSwitch;
-import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 
 import org.junit.Assume;
 import org.junit.Test;
@@ -56,7 +56,7 @@
     public void setUp() throws Exception {
         Assume.assumeTrue(mLauncher.isTablet());
         super.setUp();
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
         startAppFast(CALCULATOR_APP_PACKAGE);
         startTestActivity(2);
     }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 8e142c3..b3cc215 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -45,8 +45,8 @@
 import com.android.launcher3.tapl.Overview;
 import com.android.launcher3.tapl.OverviewActions;
 import com.android.launcher3.tapl.OverviewTask;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.TestStabilityRule;
@@ -72,7 +72,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
         executeOnLauncher(launcher -> {
             RecentsView recentsView = launcher.getOverviewPanel();
             recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index 234fe63..1e33635 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -32,15 +32,14 @@
 import com.android.launcher3.tapl.Overview;
 import com.android.launcher3.tapl.Taskbar;
 import com.android.launcher3.tapl.TaskbarAppIcon;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
 import com.android.wm.shell.Flags;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -58,7 +57,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
 
         if (mLauncher.isTablet()) {
             mLauncher.enableBlockTimeout(true);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
index 907dbcc..0eec8b7 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
@@ -19,7 +19,6 @@
 import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON;
 
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
@@ -32,8 +31,8 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.tapl.LauncherInstrumentation.TrackpadGestureType;
 import com.android.launcher3.tapl.Workspace;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 
 import org.junit.After;
@@ -51,7 +50,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
     }
 
     @After
diff --git a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
index 8cc8487..2318f54 100644
--- a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
+++ b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
@@ -56,7 +56,7 @@
 import com.android.launcher3.testcomponent.ListViewService;
 import com.android.launcher3.testcomponent.ListViewService.SimpleViewsFactory;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
-import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -102,7 +102,7 @@
         // is started only after starting another app.
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
 
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
 
         mModel = LauncherAppState.getInstance(mTargetContext).getModel();
         Executors.MODEL_EXECUTOR.submit(mModel.getModelDbController()::createEmptyDB).get();
diff --git a/res/drawable/ic_drag_handle.xml b/res/drawable/ic_drag_handle.xml
deleted file mode 100644
index 9db75f4..0000000
--- a/res/drawable/ic_drag_handle.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="@dimen/deep_shortcut_drag_handle_size"
-        android:height="@dimen/deep_shortcut_drag_handle_size"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="?android:attr/textColorPrimary" >
-
-    <path
-        android:pathData="M20,9H4v2h16V9z M4,15h16v-2H4V15z"
-        android:fillColor="@android:color/white" />
-</vector>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 6c9a238..137ffe8 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -290,7 +290,6 @@
     <dimen name="popup_single_item_radius">100dp</dimen>
     <dimen name="popup_smaller_radius">4dp</dimen>
     <dimen name="deep_shortcut_drawable_padding">16dp</dimen>
-    <dimen name="deep_shortcut_drag_handle_size">16dp</dimen>
     <dimen name="popup_padding_start">10dp</dimen>
     <dimen name="popup_padding_end">14dp</dimen>
     <dimen name="popup_vertical_padding">4dp</dimen>
@@ -430,6 +429,7 @@
     <dimen name="split_instructions_drawable_padding">10dp</dimen>
     <dimen name="split_instructions_bottom_margin_phone_landscape">24dp</dimen>
     <dimen name="split_instructions_bottom_margin_phone_portrait">60dp</dimen>
+    <dimen name="split_instructions_start_margin_cancel">8dp</dimen>
 
     <!-- Workspace grid visualization parameters -->
     <dimen name="grid_visualization_rounding_radius">28dp</dimen>
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 7257d86..17e4a5b 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -89,8 +89,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
 import java.util.Stack;
 
 public class CellLayout extends ViewGroup {
@@ -1811,7 +1809,7 @@
         return bestXY;
     }
 
-    private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
+    public boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
             int[] direction, ItemConfiguration currentState) {
         CellAndSpan c = currentState.map.get(v);
         boolean success = false;
@@ -1830,7 +1828,7 @@
         return success;
     }
 
-    private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
+    public boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
             int[] direction, View dragView, ItemConfiguration currentState) {
 
         ViewCluster cluster = new ViewCluster(this, views, currentState);
@@ -1928,7 +1926,7 @@
     // This method tries to find a reordering solution which satisfies the push mechanic by trying
     // to push items in each of the cardinal directions, in an order based on the direction vector
     // passed.
-    private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
+    public boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
             int[] direction, View ignoreView, ItemConfiguration solution) {
         if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
             // If the direction vector has two non-zero components, we try pushing
@@ -2089,7 +2087,7 @@
         }
     }
 
-    private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
+    public boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
             int[] direction, View dragView, ItemConfiguration currentState) {
         if (views.size() == 0) return true;
 
@@ -2140,71 +2138,6 @@
         return success;
     }
 
-    public boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
-            View ignoreView, ItemConfiguration solution) {
-        // Return early if get invalid cell positions
-        if (cellX < 0 || cellY < 0) return false;
-
-        mIntersectingViews.clear();
-        mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
-
-        // Mark the desired location of the view currently being dragged.
-        if (ignoreView != null) {
-            CellAndSpan c = solution.map.get(ignoreView);
-            if (c != null) {
-                c.cellX = cellX;
-                c.cellY = cellY;
-            }
-        }
-        Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
-        Rect r1 = new Rect();
-        // The views need to be sorted so that the results are deterministic on the views positions
-        // and not by the views hash which is "random".
-        // The views are sorted twice, once for the X position and a second time for the Y position
-        // to ensure same order everytime.
-        Comparator comparator = Comparator.comparing(view ->
-                        ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellX())
-                .thenComparing(view ->
-                        ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellY());
-        List<View> views = solution.map.keySet().stream().sorted(comparator).toList();
-        for (View child : views) {
-            if (child == ignoreView) continue;
-            CellAndSpan c = solution.map.get(child);
-            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
-            r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
-            if (Rect.intersects(r0, r1)) {
-                if (!lp.canReorder) {
-                    return false;
-                }
-                mIntersectingViews.add(child);
-            }
-        }
-
-        solution.intersectingViews = new ArrayList<>(mIntersectingViews);
-
-        // First we try to find a solution which respects the push mechanic. That is,
-        // we try to find a solution such that no displaced item travels through another item
-        // without also displacing that item.
-        if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
-                solution)) {
-            return true;
-        }
-
-        // Next we try moving the views as a block, but without requiring the push mechanic.
-        if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
-                solution)) {
-            return true;
-        }
-
-        // Ok, they couldn't move as a block, let's move them individually
-        for (View v : mIntersectingViews) {
-            if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     public ReorderAlgorithm createReorderAlgorithm() {
         return new ReorderAlgorithm(this);
     }
@@ -2216,18 +2149,13 @@
                 spanX, spanY, direction, dragView, decX, solution);
     }
 
-    public void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
+    public void copyCurrentStateToSolution(ItemConfiguration solution) {
         int childCount = mShortcutsAndWidgets.getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
             CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
-            CellAndSpan c;
-            if (temp) {
-                c = new CellAndSpan(lp.getTmpCellX(), lp.getTmpCellY(), lp.cellHSpan, lp.cellVSpan);
-            } else {
-                c = new CellAndSpan(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan);
-            }
-            solution.add(child, c);
+            solution.add(child,
+                    new CellAndSpan(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan));
         }
     }
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index badd1c4..bb94d4a 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -105,6 +105,8 @@
     public final boolean isMultiWindowMode;
     public final boolean isGestureMode;
 
+    public final boolean isLeftRightSplit;
+
     public final int windowX;
     public final int windowY;
     public final int widthPx;
@@ -691,6 +693,20 @@
         overviewGridSideMargin = res.getDimensionPixelSize(R.dimen.overview_grid_side_margin);
 
         splitPlaceholderInset = res.getDimensionPixelSize(R.dimen.split_placeholder_inset);
+        // We need to use the full window bounds for split determination because on near-square
+        // devices, the available bounds (bounds minus insets) may actually be in landscape while
+        // actually portrait
+        int leftRightSplitPortraitResId = Resources.getSystem().getIdentifier(
+                "config_leftRightSplitInPortrait", "bool", "android");
+        boolean allowLeftRightSplitInPortrait =
+                com.android.wm.shell.Flags.enableLeftRightSplitInPortrait()
+                    && leftRightSplitPortraitResId > 0
+                    && res.getBoolean(leftRightSplitPortraitResId);
+        if (allowLeftRightSplitInPortrait && isTablet) {
+            isLeftRightSplit = !isLandscape;
+        } else {
+            isLeftRightSplit = isLandscape;
+        }
 
         // Calculate all of the remaining variables.
         extraSpace = updateAvailableDimensions(res);
@@ -1002,6 +1018,9 @@
         iconCenterVertically = mIsScalableGrid || mIsResponsiveGrid;
 
         if (mIsResponsiveGrid) {
+            iconSizePx = mResponsiveWorkspaceCellSpec.getIconSize();
+            iconTextSizePx = mResponsiveWorkspaceCellSpec.getIconTextSize();
+            mIconDrawablePaddingOriginalPx = mResponsiveWorkspaceCellSpec.getIconDrawablePadding();
             updateIconSize(1f, res);
             updateWorkspacePadding();
             return 0;
@@ -1096,10 +1115,6 @@
         cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv, scale);
 
         if (mIsResponsiveGrid) {
-            iconSizePx = mResponsiveWorkspaceCellSpec.getIconSize();
-            iconTextSizePx = mResponsiveWorkspaceCellSpec.getIconTextSize();
-            mIconDrawablePaddingOriginalPx = mResponsiveWorkspaceCellSpec.getIconDrawablePadding();
-
             cellWidthPx = mResponsiveWorkspaceWidthSpec.getCellSizePx();
             cellHeightPx = mResponsiveWorkspaceHeightSpec.getCellSizePx();
 
@@ -1986,6 +2001,7 @@
         writer.println(prefix + "\tisLandscape:" + isLandscape);
         writer.println(prefix + "\tisMultiWindowMode:" + isMultiWindowMode);
         writer.println(prefix + "\tisTwoPanels:" + isTwoPanels);
+        writer.println(prefix + "\tisLeftRightSplit:" + isLeftRightSplit);
 
         writer.println(prefix + pxToDpStr("windowX", windowX));
         writer.println(prefix + pxToDpStr("windowY", windowY));
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index 01e65ae..a13dcc1 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -27,6 +27,7 @@
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.app.animation.Interpolators;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
@@ -194,12 +195,13 @@
     }
 
     /**
-     * Scrolls this recycler view to the bottom.
+     * Scrolls this recycler view to the bottom with easing and duration.
      */
-    public void scrollToBottom() {
+    public void scrollToBottomWithMotion() {
         if (mScrollbar != null) {
             mScrollbar.reattachThumbToScroll();
         }
-        smoothScrollToPosition(getAdapter().getItemCount() - 1);
+        // Emphasized interpolators with 500ms duration
+        smoothScrollBy(0, getAvailableScrollHeight(), Interpolators.EMPHASIZED, 500);
     }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 9f7575d..fe33965 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -96,6 +96,7 @@
 import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
 import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
 import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
+import static com.android.launcher3.testing.shared.TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch;
 import static com.android.launcher3.util.SettingsCache.TOUCHPAD_NATURAL_SCROLLING;
@@ -1045,6 +1046,8 @@
         mAppWidgetHolder.setActivityStarted(false);
         NotificationListener.removeNotificationsChangedListener(getPopupDataProvider());
         FloatingIconView.resetIconLoadResult();
+        AccessibilityManagerCompat.sendTestProtocolEventToTest(
+                this, LAUNCHER_ACTIVITY_STOPPED_MESSAGE);
     }
 
     @Override
diff --git a/src/com/android/launcher3/MultipageCellLayout.java b/src/com/android/launcher3/MultipageCellLayout.java
index 123e8ca..5c5d71c 100644
--- a/src/com/android/launcher3/MultipageCellLayout.java
+++ b/src/com/android/launcher3/MultipageCellLayout.java
@@ -120,7 +120,7 @@
     }
 
     @Override
-    public void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
+    public void copyCurrentStateToSolution(ItemConfiguration solution) {
         int childCount = mShortcutsAndWidgets.getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8ba6d2c..e0f6101 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -79,7 +79,6 @@
 
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.graphics.TintedDrawableSpan;
-import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.icons.ShortcutCachingLogic;
@@ -91,6 +90,7 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.testing.shared.ResourceUtils;
+import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.Themes;
@@ -676,12 +676,11 @@
         }
 
         if (badge == null) {
-            try (LauncherIcons li = LauncherIcons.obtain(context)) {
-                badge = BitmapInfo.LOW_RES_INFO.withFlags(
-                                li.getBitmapFlagOp(new BaseIconFactory.IconOptions().setUser(
-                                        UserCache.INSTANCE.get(context).getUserInfo(info.user))))
-                        .getBadgeDrawable(context, useTheme);
-            }
+            badge = BitmapInfo.LOW_RES_INFO.withFlags(
+                            UserCache.INSTANCE.get(context)
+                                    .getUserInfo(info.user)
+                                    .applyBitmapInfoFlags(FlagOp.NO_OP))
+                    .getBadgeDrawable(context, useTheme);
             if (badge == null) {
                 badge = new ColorDrawable(Color.TRANSPARENT);
             }
@@ -758,10 +757,16 @@
      */
     public static List<SplitPositionOption> getSplitPositionOptions(
             DeviceProfile dp) {
+        int splitIconRes = dp.isLeftRightSplit
+                ? R.drawable.ic_split_horizontal
+                : R.drawable.ic_split_vertical;
+        int stagePosition = dp.isLeftRightSplit
+                ? STAGE_POSITION_BOTTOM_OR_RIGHT
+                : STAGE_POSITION_TOP_OR_LEFT;
         return Collections.singletonList(new SplitPositionOption(
-                dp.isLandscape ? R.drawable.ic_split_horizontal : R.drawable.ic_split_vertical,
+                splitIconRes,
                 R.string.recent_task_option_split_screen,
-                dp.isLandscape ? STAGE_POSITION_BOTTOM_OR_RIGHT : STAGE_POSITION_TOP_OR_LEFT,
+                stagePosition,
                 STAGE_TYPE_MAIN
         ));
     }
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index c9e5b1e..e5a223a 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -515,7 +515,7 @@
             // Switch to the main tab
             switchToTab(ActivityAllAppsContainerView.AdapterHolder.MAIN);
             // Scroll to bottom
-            getActiveRecyclerView().scrollToBottom();
+            getActiveRecyclerView().scrollToBottomWithMotion();
         });
     }
 
diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java
index 8121245..4cf6471 100644
--- a/src/com/android/launcher3/apppairs/AppPairIcon.java
+++ b/src/com/android/launcher3/apppairs/AppPairIcon.java
@@ -134,7 +134,7 @@
         mCenterChannelSize = CENTER_CHANNEL_SCALE * defaultIconSize;
         mBigRadius = BIG_RADIUS_SCALE * defaultIconSize;
         mSmallRadius = SMALL_RADIUS_SCALE * defaultIconSize;
-        mIsLandscape = grid.isLandscape;
+        mIsLandscape = grid.isLeftRightSplit;
 
         // Calculate drawable area position
         float leftBound = (canvas.getWidth() / 2f) - (defaultIconSize / 2f);
diff --git a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
index 0f6464b..d098eba 100644
--- a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
+++ b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
@@ -19,7 +19,11 @@
 import android.view.View;
 
 import com.android.launcher3.CellLayout;
+import com.android.launcher3.util.CellAndSpan;
 
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
 import java.util.Map.Entry;
 
 /**
@@ -71,7 +75,7 @@
             int minSpanX, int minSpanY, int spanX, int spanY, int[] direction, View dragView,
             boolean decX, ItemConfiguration solution) {
         // Copy the current state into the solution. This solution will be manipulated as necessary.
-        mCellLayout.copyCurrentStateToSolution(solution, false);
+        mCellLayout.copyCurrentStateToSolution(solution);
         // Copy the current occupied array into the temporary occupied array. This array will be
         // manipulated as necessary to find a solution.
         mCellLayout.getOccupied().copyTo(mCellLayout.mTmpOccupied);
@@ -84,7 +88,7 @@
         boolean success;
         // First we try the exact nearest position of the item being dragged,
         // we will then want to try to move this around to other neighbouring positions
-        success = mCellLayout.rearrangementExists(result[0], result[1], spanX, spanY, direction,
+        success = rearrangementExists(result[0], result[1], spanX, spanY, direction,
                 dragView, solution);
 
         if (!success) {
@@ -108,6 +112,73 @@
         return solution;
     }
 
+    private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
+            View ignoreView, ItemConfiguration solution) {
+        // Return early if get invalid cell positions
+        if (cellX < 0 || cellY < 0) return false;
+
+        ArrayList<View> intersectingViews = new ArrayList<>();
+        Rect occupiedRect = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
+
+        // Mark the desired location of the view currently being dragged.
+        if (ignoreView != null) {
+            CellAndSpan c = solution.map.get(ignoreView);
+            if (c != null) {
+                c.cellX = cellX;
+                c.cellY = cellY;
+            }
+        }
+        Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
+        Rect r1 = new Rect();
+        // The views need to be sorted so that the results are deterministic on the views positions
+        // and not by the views hash which is "random".
+        // The views are sorted twice, once for the X position and a second time for the Y position
+        // to ensure same order everytime.
+        Comparator comparator = Comparator.comparing(view ->
+                        ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellX())
+                .thenComparing(view ->
+                        ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellY());
+        List<View> views = solution.map.keySet().stream().sorted(comparator).toList();
+        for (View child : views) {
+            if (child == ignoreView) continue;
+            CellAndSpan c = solution.map.get(child);
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
+            r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
+            if (Rect.intersects(r0, r1)) {
+                if (!lp.canReorder) {
+                    return false;
+                }
+                intersectingViews.add(child);
+            }
+        }
+
+        solution.intersectingViews = intersectingViews;
+
+        // First we try to find a solution which respects the push mechanic. That is,
+        // we try to find a solution such that no displaced item travels through another item
+        // without also displacing that item.
+        if (mCellLayout.attemptPushInDirection(intersectingViews, occupiedRect, direction,
+                ignoreView,
+                solution)) {
+            return true;
+        }
+
+        // Next we try moving the views as a block, but without requiring the push mechanic.
+        if (mCellLayout.addViewsToTempLocation(intersectingViews, occupiedRect, direction,
+                ignoreView,
+                solution)) {
+            return true;
+        }
+
+        // Ok, they couldn't move as a block, let's move them individually
+        for (View v : intersectingViews) {
+            if (!mCellLayout.addViewToTempLocation(v, occupiedRect, direction, solution)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     /**
      * Returns a "reorder" if there is empty space without rearranging anything.
      *
@@ -123,7 +194,7 @@
         int[] result = mCellLayout.findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY,
                 new int[2]);
         ItemConfiguration solution = new ItemConfiguration();
-        mCellLayout.copyCurrentStateToSolution(solution, false);
+        mCellLayout.copyCurrentStateToSolution(solution);
 
         solution.isSolution = !isConfigurationRegionOccupied(
                 new Rect(result[0], result[1], result[0] + spanX, result[1] + spanY),
@@ -169,7 +240,7 @@
         mCellLayout.findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
                 resultSpan);
         if (result[0] >= 0 && result[1] >= 0) {
-            mCellLayout.copyCurrentStateToSolution(solution, false);
+            mCellLayout.copyCurrentStateToSolution(solution);
             solution.cellX = result[0];
             solution.cellY = result[1];
             solution.spanX = resultSpan[0];
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 14d1683..2a64eea 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -198,7 +198,7 @@
             "Enables generating the reorder using a set of parameters");
 
     public static final BooleanFlag ENABLE_NO_LONG_PRESS_DRAG = getDebugFlag(299748096,
-            "ENABLE_NO_LONG_PRESS_DRAG", DISABLED,
+            "ENABLE_NO_LONG_PRESS_DRAG", ENABLED,
             "Don't trigger the drag if we are still under long press");
 
     // TODO(Block 12): Clean up flags
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index 66001d8..260d490 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -71,7 +71,7 @@
     private static final int ALPHA_MASK_BITMAP_WIDTH_DP = 2;
 
     private static final int BOTTOM_MASK_HEIGHT_DP = 200;
-    private static final int TOP_MASK_HEIGHT_DP = 100;
+    private static final int TOP_MASK_HEIGHT_DP = 70;
 
     private boolean mDrawTopScrim, mDrawBottomScrim;
 
@@ -104,7 +104,7 @@
         mHideSysUiScrim = Themes.getAttrBoolean(view.getContext(), R.attr.isWorkspaceDarkText);
 
         mTopMaskBitmap = mHideSysUiScrim ? null : createDitheredAlphaMask(mTopMaskHeight,
-                new int[]{0x50FFFFFF, 0x0AFFFFFF, 0x00FFFFFF},
+                new int[]{0x3DFFFFFF, 0x0AFFFFFF, 0x00FFFFFF},
                 new float[]{0f, 0.7f, 1f});
         mTopMaskPaint.setColor(0xFF222222);
         mBottomMaskBitmap = mHideSysUiScrim ? null : createDitheredAlphaMask(mBottomMaskHeight,
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 5f73ced..9980218 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -860,6 +860,13 @@
         }
 
         /**
+         * Set the package name of the log message.
+         */
+        default StatsLogger withPackageName(@Nullable String packageName) {
+            return this;
+        }
+
+        /**
          * Builds the final message and logs it as {@link EventEnum}.
          */
         default void log(EventEnum event) {
diff --git a/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt b/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
index 8e3a5b4..ebbff51 100644
--- a/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
+++ b/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
@@ -26,6 +26,7 @@
 class HotseatSpecsProvider(groupOfSpecs: List<ResponsiveSpecGroup<HotseatSpec>>) {
 
     private val groupOfSpecs: List<ResponsiveSpecGroup<HotseatSpec>>
+
     init {
         this.groupOfSpecs = groupOfSpecs.sortedBy { it.aspectRatio }
     }
@@ -90,13 +91,18 @@
 
     fun isValid(): Boolean {
         if (maxAvailableSize <= 0) {
-            Log.e(LOG_TAG, "${this::class.simpleName}#isValid - maxAvailableSize <= 0")
+            logError("The property maxAvailableSize must be higher than 0.")
             return false
         }
 
         // All specs need to be individually valid
         if (!allSpecsAreValid()) {
-            Log.e(LOG_TAG, "${this::class.simpleName}#isValid - !allSpecsAreValid()")
+            logError("One or more specs are invalid!")
+            return false
+        }
+
+        if (!isValidFixedSize()) {
+            logError("The total Fixed Size used must be lower or equal to $maxAvailableSize.")
             return false
         }
 
@@ -110,6 +116,13 @@
             edgePadding.onlyFixedSize()
     }
 
+    private fun isValidFixedSize() =
+        hotseatQsbSpace.fixedSize + edgePadding.fixedSize <= maxAvailableSize
+
+    private fun logError(message: String) {
+        Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
+    }
+
     companion object {
         private const val LOG_TAG = "HotseatSpec"
     }
diff --git a/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt b/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt
index affca60..a4b25e5 100644
--- a/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt
+++ b/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt
@@ -107,25 +107,39 @@
 
     fun isValid(): Boolean {
         if (maxAvailableSize <= 0) {
-            Log.e(LOG_TAG, "${this::class.simpleName}#isValid - maxAvailableSize <= 0")
+            logError("The property maxAvailableSize must be higher than 0.")
             return false
         }
 
         // All specs need to be individually valid
         if (!allSpecsAreValid()) {
-            Log.e(LOG_TAG, "${this::class.simpleName}#isValid - !allSpecsAreValid()")
+            logError("Specs must be either Fixed Size or Match Workspace!")
+            return false
+        }
+
+        if (!isValidFixedSize()) {
+            logError("The total Fixed Size used must be lower or equal to $maxAvailableSize.")
             return false
         }
 
         return true
     }
 
+    private fun isValidFixedSize(): Boolean {
+        val totalSize = iconSize.fixedSize + iconTextSize.fixedSize + iconDrawablePadding.fixedSize
+        return totalSize <= maxAvailableSize
+    }
+
     private fun allSpecsAreValid(): Boolean {
         return (iconSize.fixedSize > 0f || iconSize.matchWorkspace) &&
             (iconTextSize.fixedSize >= 0f || iconTextSize.matchWorkspace) &&
             (iconDrawablePadding.fixedSize >= 0f || iconDrawablePadding.matchWorkspace)
     }
 
+    private fun logError(message: String) {
+        Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
+    }
+
     companion object {
         private const val LOG_TAG = "CellSpec"
     }
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpec.kt b/src/com/android/launcher3/responsive/ResponsiveSpec.kt
index 32dedfb..b0e1b27 100644
--- a/src/com/android/launcher3/responsive/ResponsiveSpec.kt
+++ b/src/com/android/launcher3/responsive/ResponsiveSpec.kt
@@ -90,18 +90,33 @@
                     gutter.matchWorkspace ||
                     cellSize.matchWorkspace)
         ) {
-            Log.e(LOG_TAG, "WorkspaceSpec#isValid - workspace shouldn't contain matchWorkspace!")
+            logError("Workspace spec provided must not have any match workspace value.")
             return false
         }
 
         if (maxAvailableSize <= 0) {
-            Log.e(LOG_TAG, "${this::class.simpleName}#isValid - maxAvailableSize <= 0")
+            logError("The property maxAvailableSize must be higher than 0.")
             return false
         }
 
         // All specs need to be individually valid
         if (!allSpecsAreValid()) {
-            Log.e(LOG_TAG, "${this::class.simpleName}#isValid - !allSpecsAreValid()")
+            logError("One or more specs are invalid!")
+            return false
+        }
+
+        if (!isValidRemainderSpace()) {
+            logError("The total Remainder Space used must be lower or equal to 100%.")
+            return false
+        }
+
+        if (!isValidAvailableSpace()) {
+            logError("The total Available Space used must be lower or equal to 100%.")
+            return false
+        }
+
+        if (!isValidFixedSize()) {
+            logError("The total Fixed Size used must be lower or equal to $maxAvailableSize.")
             return false
         }
 
@@ -115,6 +130,32 @@
             cellSize.isValid()
     }
 
+    private fun isValidRemainderSpace(): Boolean {
+        // TODO(b/313621277): This validation must be update do accept only 0 or 1 instead of <= 1f.
+        return startPadding.ofRemainderSpace +
+            endPadding.ofRemainderSpace +
+            gutter.ofRemainderSpace +
+            cellSize.ofRemainderSpace <= 1f
+    }
+
+    private fun isValidAvailableSpace(): Boolean {
+        return startPadding.ofAvailableSpace +
+            endPadding.ofAvailableSpace +
+            gutter.ofAvailableSpace +
+            cellSize.ofAvailableSpace < 1f
+    }
+
+    private fun isValidFixedSize(): Boolean {
+        return startPadding.fixedSize +
+            endPadding.fixedSize +
+            gutter.fixedSize +
+            cellSize.fixedSize <= maxAvailableSize
+    }
+
+    private fun logError(message: String) {
+        Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
+    }
+
     enum class DimensionType {
         HEIGHT,
         WIDTH
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
index ad3f8df..ded2cee 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
@@ -17,13 +17,11 @@
 package com.android.launcher3.shortcuts;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.widget.Toast;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
@@ -33,11 +31,6 @@
  * A {@link BubbleTextView} that has the shortcut icon on the left and drag handle on the right.
  */
 public class DeepShortcutTextView extends BubbleTextView {
-    private final Rect mDragHandleBounds = new Rect();
-    private final int mDragHandleWidth;
-    private boolean mShowInstructionToast = false;
-
-    private Toast mInstructionToast;
 
     private boolean mShowLoadingState;
     private Drawable mLoadingStatePlaceholder;
@@ -53,23 +46,12 @@
 
     public DeepShortcutTextView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-
-        Resources resources = getResources();
-        mDragHandleWidth = resources.getDimensionPixelSize(R.dimen.popup_padding_end)
-                + resources.getDimensionPixelSize(R.dimen.deep_shortcut_drag_handle_size)
-                + resources.getDimensionPixelSize(R.dimen.deep_shortcut_drawable_padding) / 2;
         showLoadingState(true);
     }
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-        mDragHandleBounds.set(0, 0, mDragHandleWidth, getMeasuredHeight());
-        if (!Utilities.isRtl(getResources())) {
-            mDragHandleBounds.offset(getMeasuredWidth() - mDragHandleBounds.width(), 0);
-        }
-
         setLoadingBounds();
     }
 
@@ -80,10 +62,10 @@
         mLoadingStateBounds.set(
                 0,
                 0,
-                getMeasuredWidth() - mDragHandleWidth - getPaddingStart(),
+                getMeasuredWidth() - getPaddingEnd() - getPaddingStart(),
                 mLoadingStatePlaceholder.getIntrinsicHeight());
         mLoadingStateBounds.offset(
-                Utilities.isRtl(getResources()) ? mDragHandleWidth : getPaddingStart(),
+                Utilities.isRtl(getResources()) ? getPaddingEnd() : getPaddingStart(),
                 (int) ((getMeasuredHeight() - mLoadingStatePlaceholder.getIntrinsicHeight())
                         / 2.0f)
         );
@@ -106,23 +88,11 @@
 
     @Override
     protected boolean shouldIgnoreTouchDown(float x, float y) {
-        // Show toast if user touches the drag handle (long clicks still start the drag).
-        mShowInstructionToast = mDragHandleBounds.contains((int) x, (int) y);
-
         // assume the whole view as clickable
         return false;
     }
 
     @Override
-    public boolean performClick() {
-        if (mShowInstructionToast) {
-            showToast();
-            return true;
-        }
-        return super.performClick();
-    }
-
-    @Override
     public void onDraw(Canvas canvas) {
         if (!mShowLoadingState) {
             super.onDraw(canvas);
@@ -155,15 +125,4 @@
 
         invalidate();
     }
-
-    private void showToast() {
-        if (mInstructionToast != null) {
-            mInstructionToast.cancel();
-        }
-        CharSequence msg = Utilities.wrapForTts(
-                getContext().getText(R.string.long_press_shortcut_to_add),
-                getContext().getString(R.string.long_accessible_way_to_add_shortcut));
-        mInstructionToast = Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT);
-        mInstructionToast.show();
-    }
 }
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index db32829..3bce377 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -61,7 +61,10 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.views.FloatingIconView;
+import com.android.launcher3.views.Snackbar;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.PendingAddShortcutInfo;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.PendingAppWidgetHostView;
 import com.android.launcher3.widget.WidgetAddFlowHandler;
 import com.android.launcher3.widget.WidgetManagerHelper;
@@ -107,6 +110,16 @@
             }
         } else if (tag instanceof ItemClickProxy) {
             ((ItemClickProxy) tag).onItemClicked(v);
+        } else if (tag instanceof PendingAddShortcutInfo) {
+            CharSequence msg = Utilities.wrapForTts(
+                    launcher.getText(R.string.long_press_shortcut_to_add),
+                    launcher.getString(R.string.long_accessible_way_to_add_shortcut));
+            Snackbar.show(launcher, msg, null);
+        } else if (tag instanceof PendingAddWidgetInfo) {
+            CharSequence msg = Utilities.wrapForTts(
+                    launcher.getText(R.string.long_press_widget_to_add),
+                    launcher.getString(R.string.long_accessible_way_to_add));
+            Snackbar.show(launcher, msg, null);
         }
     }
 
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 158747c..04b6710 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -258,7 +258,7 @@
 
     @Override
     public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
-        if (deviceProfile.isLandscape && stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
+        if (deviceProfile.isLeftRightSplit && stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
             return -1;
         } else {
             return 1;
@@ -348,7 +348,7 @@
         }
 
         // Set translations
-        if (deviceProfile.isLandscape) {
+        if (deviceProfile.isLeftRightSplit) {
             if (desiredTaskId == splitBounds.rightBottomTaskId) {
                 float leftTopTaskPercent = splitBounds.appsStackedVertically
                         ? splitBounds.topTaskPercent
@@ -430,7 +430,7 @@
             options.add(new SplitPositionOption(
                     R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
                     STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
-        } else if (dp.isLandscape) {
+        } else if (dp.isLeftRightSplit) {
             options.add(new SplitPositionOption(
                     R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
                     STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
@@ -452,7 +452,7 @@
         int insetSizeAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight);
 
         out.set(0, 0, screenWidth, placeholderHeight + insetSizeAdjustment);
-        if (!dp.isLandscape) {
+        if (!dp.isLeftRightSplit) {
             // portrait, phone or tablet - spans width of screen, nothing else to do
             out.inset(placeholderInset, 0);
 
@@ -497,7 +497,7 @@
             @StagePosition int stagePosition) {
         boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
         float insetAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight) / 2f;
-        if (!dp.isLandscape) {
+        if (!dp.isLeftRightSplit) {
             out.setX(onScreenRectCenterX / fullscreenScaleX
                     - 1.0f * drawableWidth / 2);
             out.setY((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY
@@ -566,7 +566,7 @@
         int screenWidth = dp.widthPx;
         out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize);
         out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight);
-        if (!dp.isLandscape) {
+        if (!dp.isLeftRightSplit) {
             // Portrait - the window bounds are always top and bottom half
             return;
         }
@@ -607,13 +607,13 @@
         float scaledDividerHeight = dividerHeight * scale;
 
         if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
-            if (dp.isLandscape) {
+            if (dp.isLeftRightSplit) {
                 outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent);
             } else {
                 outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight);
             }
         } else {
-            if (dp.isLandscape) {
+            if (dp.isLeftRightSplit) {
                 outRect.left += Math.round(outRect.width()
                         * (topLeftTaskPercent + dividerBarPercent));
             } else {
@@ -637,7 +637,7 @@
         int secondarySnapshotWidth;
         float taskPercent = splitBoundsConfig.appsStackedVertically ?
                 splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent;
-        if (dp.isLandscape) {
+        if (dp.isLeftRightSplit) {
             int scaledDividerBar = Math.round(parentWidth * dividerScale);
             primarySnapshotHeight = totalThumbnailHeight;
             primarySnapshotWidth = Math.round(parentWidth * taskPercent);
@@ -742,7 +742,7 @@
             secondaryIconParams.gravity = TOP | START;
             secondaryIconParams.topMargin = primaryIconParams.topMargin;
             secondaryIconParams.setMarginStart(primaryIconParams.getMarginStart());
-            if (deviceProfile.isLandscape) {
+            if (deviceProfile.isLeftRightSplit) {
                 if (isRtl) {
                     primaryIconView.setTranslationX(-primarySnapshotWidth);
                 } else {
@@ -756,7 +756,7 @@
                 secondaryIconView.setTranslationY(
                         primarySnapshotHeight + (deviceProfile.isTablet ? 0 : dividerThickness));
             }
-        } else if (deviceProfile.isLandscape) {
+        } else if (deviceProfile.isLeftRightSplit) {
             // We calculate the "midpoint" of the thumbnail area, and place the icons there.
             // This is the place where the thumbnail area splits by default, in a near-50/50 split.
             // It is usually not exactly 50/50, due to insets/screen cutouts.
@@ -826,7 +826,7 @@
         if (!deviceProfile.isTablet) {
             throw new IllegalStateException("Default position available only for large screens");
         }
-        if (deviceProfile.isLandscape) {
+        if (deviceProfile.isLeftRightSplit) {
             return STAGE_POSITION_BOTTOM_OR_RIGHT;
         } else {
             return STAGE_POSITION_TOP_OR_LEFT;
@@ -836,7 +836,7 @@
     @Override
     public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
             FloatProperty secondary, DeviceProfile deviceProfile) {
-        if (deviceProfile.isLandscape) { // or seascape
+        if (deviceProfile.isLeftRightSplit) { // or seascape
             return new Pair<>(primary, secondary);
         } else {
             return new Pair<>(secondary, primary);
@@ -846,7 +846,7 @@
     @Override
     public float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
             @StagePosition int stagePosition, DeviceProfile dp) {
-        if (dp.isLandscape) {
+        if (dp.isLeftRightSplit) {
             float currentTranslationX = floatingTask.getTranslationX();
             return stagePosition == STAGE_POSITION_TOP_OR_LEFT
                     ? currentTranslationX - onScreenRect.width()
@@ -860,7 +860,7 @@
     @Override
     public void setFloatingTaskPrimaryTranslation(View floatingTask, float translation,
             DeviceProfile dp) {
-        if (dp.isLandscape) {
+        if (dp.isLeftRightSplit) {
             floatingTask.setTranslationX(translation);
         } else {
             floatingTask.setTranslationY(translation);
@@ -870,7 +870,7 @@
 
     @Override
     public Float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
-        return dp.isLandscape
+        return dp.isLeftRightSplit
                 ? floatingTask.getTranslationX()
                 : floatingTask.getTranslationY();
     }
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index a005c60..5b6c9e0 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -25,11 +25,9 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_CLOSE_TAP_OUTSIDE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_LONGPRESS;
-import static com.android.launcher3.testing.shared.TestProtocol.WORKSPACE_LONG_PRESS;
 
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.util.Log;
 import android.view.GestureDetector;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
@@ -41,7 +39,6 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -112,10 +109,6 @@
                 mTouchDownPoint.set(ev.getX(), ev.getY());
                 // Mouse right button's ACTION_DOWN should immediately show menu
                 if (TouchUtil.isMouseRightClickDownOrMove(ev)) {
-                    if (Utilities.isRunningInTestHarness()) {
-                        Log.d(WORKSPACE_LONG_PRESS, "longPress from mouseHandling timeout: +"
-                                + ViewConfiguration.getLongPressTimeout());
-                    }
                     maybeShowMenu();
                     return true;
                 }
@@ -199,10 +192,6 @@
 
     @Override
     public void onLongPress(MotionEvent event) {
-        if (Utilities.isRunningInTestHarness()) {
-            Log.d(WORKSPACE_LONG_PRESS, "longPress from gestureHandler timeout: " +
-                    ViewConfiguration.getLongPressTimeout());
-        }
         maybeShowMenu();
     }
 
diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java
index 63d56e4..0e5ab9a 100644
--- a/src/com/android/launcher3/util/Preconditions.java
+++ b/src/com/android/launcher3/util/Preconditions.java
@@ -51,6 +51,12 @@
         }
     }
 
+    public static void assertTrue(boolean condition) {
+        if (FeatureFlags.IS_STUDIO_BUILD && !condition) {
+            throw new IllegalStateException();
+        }
+    }
+
     private static boolean isSameLooper(Looper looper) {
         return Looper.myLooper() == looper;
     }
diff --git a/src/com/android/launcher3/views/Snackbar.java b/src/com/android/launcher3/views/Snackbar.java
index 99040ff..54aa6e4 100644
--- a/src/com/android/launcher3/views/Snackbar.java
+++ b/src/com/android/launcher3/views/Snackbar.java
@@ -66,8 +66,8 @@
     }
 
     /** Show a snackbar with just a label. */
-    public static <T extends Context & ActivityContext> void show(T activity, String labelString,
-            Runnable onDismissed) {
+    public static void show(
+            ActivityContext activity, CharSequence labelString, Runnable onDismissed) {
         show(activity, labelString, NO_ID, onDismissed, null);
     }
 
@@ -76,21 +76,21 @@
             int actionStringResId, Runnable onDismissed, @Nullable Runnable onActionClicked) {
         show(
                 activity,
-                activity.getResources().getString(labelStringResId),
+                activity.getResources().getText(labelStringResId),
                 actionStringResId,
                 onDismissed,
                 onActionClicked);
     }
 
     /** Show a snackbar with a label and action. */
-    public static <T extends Context & ActivityContext> void show(T activity, String labelString,
+    public static void show(ActivityContext activity, CharSequence labelString,
             int actionStringResId, Runnable onDismissed, @Nullable Runnable onActionClicked) {
         closeOpenViews(activity, true, TYPE_SNACKBAR);
-        Snackbar snackbar = new Snackbar(activity, null);
+        Snackbar snackbar = new Snackbar((Context) activity, null);
         // Set some properties here since inflated xml only contains the children.
         snackbar.setOrientation(HORIZONTAL);
         snackbar.setGravity(Gravity.CENTER_VERTICAL);
-        Resources res = activity.getResources();
+        Resources res = snackbar.getResources();
         snackbar.setElevation(res.getDimension(R.dimen.snackbar_elevation));
         int padding = res.getDimensionPixelSize(R.dimen.snackbar_padding);
         snackbar.setPadding(padding, padding, padding, padding);
@@ -143,7 +143,8 @@
             actionView.setVisibility(GONE);
         }
 
-        int totalContentWidth = (int) (labelView.getPaint().measureText(labelString) + actionWidth)
+        int totalContentWidth = (int) (labelView.getPaint().measureText(labelString.toString())
+                    + actionWidth)
                 + labelView.getPaddingRight() + labelView.getPaddingLeft()
                 + padding * 2;
         if (totalContentWidth > params.width) {
@@ -177,7 +178,7 @@
                 .setDuration(SHOW_DURATION_MS)
                 .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
                 .start();
-        int timeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis(activity,
+        int timeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis(snackbar.getContext(),
                 TIMEOUT_DURATION_MS, FLAG_CONTENT_TEXT | FLAG_CONTENT_CONTROLS);
         snackbar.postDelayed(() -> snackbar.close(true), timeout);
     }
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 26e191d..5ec1022 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -28,7 +28,6 @@
 import android.view.View.OnLongClickListener;
 import android.view.WindowInsets;
 import android.view.animation.Interpolator;
-import android.widget.Toast;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.Px;
@@ -62,9 +61,6 @@
 
     protected final Rect mInsets = new Rect();
 
-    /* Touch handling related member variables. */
-    private Toast mWidgetInstructionToast;
-
     @Px protected int mContentHorizontalMargin;
     @Px protected int mWidgetCellHorizontalPadding;
 
@@ -113,16 +109,10 @@
 
     @Override
     public final void onClick(View v) {
-        Object tag = null;
         if (v instanceof WidgetCell) {
-            tag = v.getTag();
-        } else if (v.getParent() instanceof WidgetCell) {
-            tag = ((WidgetCell) v.getParent()).getTag();
-        }
-        if (tag instanceof PendingAddShortcutInfo) {
-            mWidgetInstructionToast = showShortcutToast(getContext(), mWidgetInstructionToast);
-        } else {
-            mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast);
+            mActivityContext.getItemOnClickListener().onClick(v);
+        } else if (v.getParent() instanceof WidgetCell wc) {
+            mActivityContext.getItemOnClickListener().onClick(wc);
         }
     }
 
@@ -131,7 +121,7 @@
         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick");
         v.cancelLongPress();
 
-        boolean result = false;
+        boolean result;
         if (v instanceof WidgetCell) {
             result = mActivityContext.getAllAppsItemLongClickListener().onLongClick(v);
         } else if (v.getParent() instanceof WidgetCell wc) {
@@ -238,40 +228,6 @@
         return mActivityContext.getSystemUiController();
     }
 
-    /**
-     * Show Widget tap toast prompting user to drag instead
-     */
-    public static Toast showWidgetToast(Context context, Toast toast) {
-        // Let the user know that they have to long press to add a widget
-        if (toast != null) {
-            toast.cancel();
-        }
-
-        CharSequence msg = Utilities.wrapForTts(
-                context.getText(R.string.long_press_widget_to_add),
-                context.getString(R.string.long_accessible_way_to_add));
-        toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
-        toast.show();
-        return toast;
-    }
-
-    /**
-     * Show shortcut tap toast prompting user to drag instead.
-     */
-    private static Toast showShortcutToast(Context context, Toast toast) {
-        // Let the user know that they have to long press to add a widget
-        if (toast != null) {
-            toast.cancel();
-        }
-
-        CharSequence msg = Utilities.wrapForTts(
-                context.getText(R.string.long_press_shortcut_to_add),
-                context.getString(R.string.long_accessible_way_to_add_shortcut));
-        toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
-        toast.show();
-        return toast;
-    }
-
     /** Shows education tip on top center of {@code view} if view is laid out. */
     @Nullable
     protected ArrowTipView showEducationTipOnViewIfPossible(@Nullable View view) {
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
index 8473b68..18752e9 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
@@ -7,6 +7,7 @@
 	isLandscape:false
 	isMultiWindowMode:false
 	isTwoPanels:false
+	isLeftRightSplit:false
 	windowX: 0.0px (0.0dp)
 	windowY: 0.0px (0.0dp)
 	widthPx: 1080.0px (411.42856dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
index c23fcc0..c0de8d8 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
@@ -7,6 +7,7 @@
 	isLandscape:false
 	isMultiWindowMode:false
 	isTwoPanels:false
+	isLeftRightSplit:false
 	windowX: 0.0px (0.0dp)
 	windowY: 0.0px (0.0dp)
 	widthPx: 1080.0px (411.42856dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
index 5cd7dc8..920ba6f 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
@@ -7,6 +7,7 @@
 	isLandscape:true
 	isMultiWindowMode:false
 	isTwoPanels:false
+	isLeftRightSplit:true
 	windowX: 0.0px (0.0dp)
 	windowY: 0.0px (0.0dp)
 	widthPx: 2400.0px (914.2857dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
index f8afa37..65460ec 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
@@ -7,6 +7,7 @@
 	isLandscape:true
 	isMultiWindowMode:false
 	isTwoPanels:false
+	isLeftRightSplit:true
 	windowX: 0.0px (0.0dp)
 	windowY: 0.0px (0.0dp)
 	widthPx: 2400.0px (914.2857dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
index 32e1f1b..1781673 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
@@ -7,6 +7,7 @@
 	isLandscape:true
 	isMultiWindowMode:false
 	isTwoPanels:false
+	isLeftRightSplit:true
 	windowX: 0.0px (0.0dp)
 	windowY: 0.0px (0.0dp)
 	widthPx: 2560.0px (1280.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
index 7c52c90..bd9e267 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
@@ -7,6 +7,7 @@
 	isLandscape:true
 	isMultiWindowMode:false
 	isTwoPanels:false
+	isLeftRightSplit:true
 	windowX: 0.0px (0.0dp)
 	windowY: 0.0px (0.0dp)
 	widthPx: 2560.0px (1280.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
index b284ce0..e983ef7 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
@@ -7,6 +7,7 @@
 	isLandscape:false
 	isMultiWindowMode:false
 	isTwoPanels:false
+	isLeftRightSplit:false
 	windowX: 0.0px (0.0dp)
 	windowY: 0.0px (0.0dp)
 	widthPx: 1600.0px (800.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
index 7001967..aa92838 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
@@ -7,6 +7,7 @@
 	isLandscape:false
 	isMultiWindowMode:false
 	isTwoPanels:false
+	isLeftRightSplit:false
 	windowX: 0.0px (0.0dp)
 	windowY: 0.0px (0.0dp)
 	widthPx: 1600.0px (800.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
index ba83533..43e4a60 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
@@ -7,6 +7,7 @@
 	isLandscape:true
 	isMultiWindowMode:false
 	isTwoPanels:true
+	isLeftRightSplit:true
 	windowX: 0.0px (0.0dp)
 	windowY: 0.0px (0.0dp)
 	widthPx: 2208.0px (841.1429dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
index 1b1240b..e7ea839 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
@@ -7,6 +7,7 @@
 	isLandscape:true
 	isMultiWindowMode:false
 	isTwoPanels:true
+	isLeftRightSplit:true
 	windowX: 0.0px (0.0dp)
 	windowY: 0.0px (0.0dp)
 	widthPx: 2208.0px (841.1429dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
index 11e7077..043380c 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
@@ -7,6 +7,7 @@
 	isLandscape:false
 	isMultiWindowMode:false
 	isTwoPanels:true
+	isLeftRightSplit:false
 	windowX: 0.0px (0.0dp)
 	windowY: 0.0px (0.0dp)
 	widthPx: 1840.0px (700.9524dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
index 1a8b3ac..a1b3e95 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
@@ -7,6 +7,7 @@
 	isLandscape:false
 	isMultiWindowMode:false
 	isTwoPanels:true
+	isLeftRightSplit:false
 	windowX: 0.0px (0.0dp)
 	windowY: 0.0px (0.0dp)
 	widthPx: 1840.0px (700.9524dp)
diff --git a/tests/res/xml/invalid_cell_specs_3.xml b/tests/res/xml/invalid_cell_specs_3.xml
new file mode 100644
index 0000000..8f8e42b
--- /dev/null
+++ b/tests/res/xml/invalid_cell_specs_3.xml
@@ -0,0 +1,38 @@
+<?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.
+  -->
+<!-- invalid: total fixedSize > maxAvailableSize -->
+<cellSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <!-- portrait -->
+    <specs launcher:maxAspectRatio="1.0">
+        <cellSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <iconDrawablePadding launcher:ofAvailableSpace="0.0125" />
+            <iconSize launcher:fixedSize="48dp" />
+            <iconTextSize launcher:fixedSize="12sp" />
+        </cellSpec>
+    </specs>
+    <!-- landscape -->
+    <specs launcher:maxAspectRatio="10">
+        <cellSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <iconDrawablePadding launcher:fixedSize="9999dp" />
+            <iconSize launcher:ofAvailableSpace="0.0125" />
+            <iconTextSize launcher:fixedSize="1dp" />
+        </cellSpec>
+    </specs>
+</cellSpecs>
\ No newline at end of file
diff --git a/tests/res/xml/invalid_hotseat_file_case_2.xml b/tests/res/xml/invalid_hotseat_file_case_2.xml
new file mode 100644
index 0000000..fbf40ef
--- /dev/null
+++ b/tests/res/xml/invalid_hotseat_file_case_2.xml
@@ -0,0 +1,33 @@
+<?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.
+  -->
+<!-- invalid: total fixed size > maxAvailableSize -->
+<hotseatSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <specs launcher:maxAspectRatio="10">
+        <hotseatSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="847dp">
+            <hotseatQsbSpace launcher:fixedSize="845dp" />
+            <edgePadding launcher:fixedSize="36dp" />
+        </hotseatSpec>
+
+        <hotseatSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <hotseatQsbSpace launcher:fixedSize="36dp" />
+            <edgePadding launcher:fixedSize="36dp" />
+        </hotseatSpec>
+    </specs>
+</hotseatSpecs>
\ No newline at end of file
diff --git a/tests/res/xml/invalid_responsive_spec_4.xml b/tests/res/xml/invalid_responsive_spec_4.xml
new file mode 100644
index 0000000..e9d510e
--- /dev/null
+++ b/tests/res/xml/invalid_responsive_spec_4.xml
@@ -0,0 +1,128 @@
+<?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.
+  -->
+<!-- invalid file - remainder space > 1 (it must be lower or equal 1) -->
+<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <specs launcher:maxAspectRatio="10">
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="34dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+
+        <!-- Height spec -->
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="371dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="24dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+
+        <!-- Width spec -->
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="716dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="64dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="36dp" />
+            <endPadding launcher:fixedSize="80dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="602dp">
+            <startPadding launcher:ofRemainderSpace=".5" />
+            <endPadding launcher:ofRemainderSpace=".5" />
+            <gutter launcher:ofRemainderSpace="0.01" />
+            <cellSize launcher:fixedSize="80dp" />
+        </workspaceSpec>
+    </specs>
+
+    <!-- specs from land/handheld_workspace_spec_4x4.xml -->
+    <specs launcher:maxAspectRatio="12">
+        <!-- Height spec -->
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="2dp" />
+            <endPadding launcher:fixedSize="2dp" />
+            <gutter launcher:fixedSize="8dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+
+        <!-- Width spec -->
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="1dp" />
+            <endPadding launcher:fixedSize="1dp" />
+            <gutter launcher:fixedSize="8dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+    </specs>
+
+    <specs launcher:maxAspectRatio="1.05">
+        <!-- 584 grid height + 28 remainder space -->
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="612dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </workspaceSpec>
+
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="8dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </workspaceSpec>
+
+        <!-- 584 grid height -->
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="584dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="32dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:ofAvailableSpace="0.15808" />
+        </workspaceSpec>
+
+        <!-- Width spec is always the same -->
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="22dp" />
+            <endPadding launcher:fixedSize="22dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+    </specs>
+</workspaceSpecs>
diff --git a/tests/res/xml/invalid_responsive_spec_5.xml b/tests/res/xml/invalid_responsive_spec_5.xml
new file mode 100644
index 0000000..584df35
--- /dev/null
+++ b/tests/res/xml/invalid_responsive_spec_5.xml
@@ -0,0 +1,128 @@
+<?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.
+  -->
+<!-- invalid file - available space > 1 (it must be lower or equal 1) -->
+<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <specs launcher:maxAspectRatio="10">
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="34dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+
+        <!-- Height spec -->
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="371dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="24dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+
+        <!-- Width spec -->
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="716dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="64dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="36dp" />
+            <endPadding launcher:fixedSize="80dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="602dp">
+            <startPadding launcher:ofAvailableSpace=".5" />
+            <endPadding launcher:ofAvailableSpace=".5" />
+            <gutter launcher:ofAvailableSpace="0.01" />
+            <cellSize launcher:fixedSize="80dp" />
+        </workspaceSpec>
+    </specs>
+
+    <!-- specs from land/handheld_workspace_spec_4x4.xml -->
+    <specs launcher:maxAspectRatio="12">
+        <!-- Height spec -->
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="2dp" />
+            <endPadding launcher:fixedSize="2dp" />
+            <gutter launcher:fixedSize="8dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+
+        <!-- Width spec -->
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="1dp" />
+            <endPadding launcher:fixedSize="1dp" />
+            <gutter launcher:fixedSize="8dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+    </specs>
+
+    <specs launcher:maxAspectRatio="1.05">
+        <!-- 584 grid height + 28 remainder space -->
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="612dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </workspaceSpec>
+
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="8dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </workspaceSpec>
+
+        <!-- 584 grid height -->
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="584dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="32dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:ofAvailableSpace="0.15808" />
+        </workspaceSpec>
+
+        <!-- Width spec is always the same -->
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="22dp" />
+            <endPadding launcher:fixedSize="22dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+    </specs>
+</workspaceSpecs>
diff --git a/tests/res/xml/invalid_responsive_spec_6.xml b/tests/res/xml/invalid_responsive_spec_6.xml
new file mode 100644
index 0000000..9711efb
--- /dev/null
+++ b/tests/res/xml/invalid_responsive_spec_6.xml
@@ -0,0 +1,128 @@
+<?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.
+  -->
+<!-- invalid file - fixed size < maxAvailableSize (it must be lower or equal 1) -->
+<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <specs launcher:maxAspectRatio="10">
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="34dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+
+        <!-- Height spec -->
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="371dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="24dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+
+        <!-- Width spec -->
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="716dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="64dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="36dp" />
+            <endPadding launcher:fixedSize="80dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="602dp">
+            <startPadding launcher:fixedSize="200dp" />
+            <endPadding launcher:fixedSize="200dp" />
+            <gutter launcher:fixedSize="150dp" />
+            <cellSize launcher:fixedSize="80dp" />
+        </workspaceSpec>
+    </specs>
+
+    <!-- specs from land/handheld_workspace_spec_4x4.xml -->
+    <specs launcher:maxAspectRatio="12">
+        <!-- Height spec -->
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="2dp" />
+            <endPadding launcher:fixedSize="2dp" />
+            <gutter launcher:fixedSize="8dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+
+        <!-- Width spec -->
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="1dp" />
+            <endPadding launcher:fixedSize="1dp" />
+            <gutter launcher:fixedSize="8dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+    </specs>
+
+    <specs launcher:maxAspectRatio="1.05">
+        <!-- 584 grid height + 28 remainder space -->
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="612dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </workspaceSpec>
+
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="8dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </workspaceSpec>
+
+        <!-- 584 grid height -->
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="584dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="32dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:ofAvailableSpace="0.15808" />
+        </workspaceSpec>
+
+        <!-- Width spec is always the same -->
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="22dp" />
+            <endPadding launcher:fixedSize="22dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+    </specs>
+</workspaceSpecs>
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 4f79adc..ffe8ce1 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -29,6 +29,7 @@
     public static final String DISMISS_ANIMATION_ENDS_MESSAGE = "TAPL_DISMISS_ANIMATION_ENDS";
     public static final String FOLDER_OPENED_MESSAGE = "TAPL_FOLDER_OPENED";
     public static final String SEARCH_RESULT_COMPLETE = "SEARCH_RESULT_COMPLETE";
+    public static final String LAUNCHER_ACTIVITY_STOPPED_MESSAGE = "TAPL_LAUNCHER_ACTIVITY_STOPPED";
     public static final int NORMAL_STATE_ORDINAL = 0;
     public static final int SPRING_LOADED_STATE_ORDINAL = 1;
     public static final int OVERVIEW_STATE_ORDINAL = 2;
diff --git a/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java b/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
index 41e3ea1..ee32e97 100644
--- a/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
+++ b/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
diff --git a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
index 2771483..c7942c7 100644
--- a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
+++ b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.allapps;
 
 import static com.android.launcher3.ui.TaplTestsLauncher3.expectFail;
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
diff --git a/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java b/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java
index fd4619e..9f6bbdf 100644
--- a/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java
+++ b/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
diff --git a/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java b/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
index 0f5d85b..a1f2cef 100644
--- a/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
+++ b/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.appiconmenu;
 
 import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
index bd52bda..28471f6 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
@@ -199,7 +199,7 @@
             solution.isSolution = false;
         }
         if (!solution.isSolution) {
-            cl.copyCurrentStateToSolution(solution, false);
+            cl.copyCurrentStateToSolution(solution);
             if (cl instanceof MultipageCellLayout) {
                 solution =
                         ((MultipageCellLayout) cl).createReorderAlgorithm().removeSeamFromSolution(
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
index 72de885..30bde0a 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.tapl.Widget;
 import com.android.launcher3.tapl.WidgetResizeFrame;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.ModelTestExtensions;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
@@ -73,7 +72,7 @@
     @Before
     public void setup() throws Throwable {
         mWorkspaceBuilder = new TestWorkspaceBuilder(mTargetContext);
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
     }
 
     @After
diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
index 7ec7826..e040367 100644
--- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.util.TestConstants.AppNames.GMAIL_APP_NAME;
 import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
 import static com.android.launcher3.util.TestConstants.AppNames.STORE_APP_NAME;
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
index d69287b..ed34307 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.dragging;
 
 import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 import static com.android.launcher3.util.TestConstants.AppNames.DUMMY_APP_NAME;
 import static com.android.launcher3.util.TestConstants.AppNames.GMAIL_APP_NAME;
 import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
diff --git a/tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt b/tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
index 32249e0..78cb1ac 100644
--- a/tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
+++ b/tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
@@ -110,4 +110,11 @@
             TestResourceHelper(context, TestR.xml.invalid_hotseat_file_case_1)
         )
     }
+
+    @Test(expected = IllegalStateException::class)
+    fun parseInvalidFile_invalidFixedSize_throwsError() {
+        HotseatSpecsProvider.create(
+            TestResourceHelper(context, TestR.xml.invalid_hotseat_file_case_2)
+        )
+    }
 }
diff --git a/tests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt b/tests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
index 295f2e4..50cd358 100644
--- a/tests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
+++ b/tests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
@@ -108,4 +108,11 @@
             TestResourceHelper(context, TestR.xml.invalid_cell_specs_2)
         )
     }
+
+    @Test(expected = IllegalStateException::class)
+    fun parseInvalidFile_invalidFixedSize_throwsError() {
+        ResponsiveCellSpecsProvider.create(
+            TestResourceHelper(context, TestR.xml.invalid_cell_specs_3)
+        )
+    }
 }
diff --git a/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt b/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
index 7af0823..9681ca8 100644
--- a/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
+++ b/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
@@ -134,6 +134,24 @@
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
     }
 
+    @Test(expected = IllegalStateException::class)
+    fun parseInvalidFile_invalidRemainderSpace_throwsError() {
+        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_4)
+        ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun parseInvalidFile_invalidAvailableSpace_throwsError() {
+        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_5)
+        ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun parseInvalidFile_invalidFixedSize_throwsError() {
+        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_6)
+        ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
+    }
+
     private fun validateSpecs(
         specs: ResponsiveSpecGroup<ResponsiveSpec>,
         expectedAspectRatio: Float,
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index cb1102e..5f536c7 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -220,6 +220,23 @@
     @Rule
     public SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
+    public static void initialize(AbstractLauncherUiTest test) throws Exception {
+        initialize(test, false);
+    }
+
+    public static void initialize(
+            AbstractLauncherUiTest test, boolean clearWorkspace) throws Exception {
+        test.reinitializeLauncherData(clearWorkspace);
+        test.mDevice.pressHome();
+        test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
+        test.waitForState("Launcher internal state didn't switch to Home",
+                () -> LauncherState.NORMAL);
+        test.waitForResumed("Launcher internal state is still Background");
+        // Check that we switched to home.
+        test.mLauncher.getWorkspace();
+        AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher, true);
+    }
+
     protected void clearPackageData(String pkg) throws IOException, InterruptedException {
         final CountDownLatch count = new CountDownLatch(2);
         final SimpleBroadcastReceiver broadcastReceiver =
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index f2cbd92..65d1f46 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -22,8 +22,6 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.launcher3.LauncherState;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -38,23 +36,6 @@
         initialize(this);
     }
 
-    public static void initialize(AbstractLauncherUiTest test) throws Exception {
-        initialize(test, false);
-    }
-
-    public static void initialize(
-            AbstractLauncherUiTest test, boolean clearWorkspace) throws Exception {
-        test.reinitializeLauncherData(clearWorkspace);
-        test.mDevice.pressHome();
-        test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
-        test.waitForState("Launcher internal state didn't switch to Home",
-                () -> LauncherState.NORMAL);
-        test.waitForResumed("Launcher internal state is still Background");
-        // Check that we switched to home.
-        test.mLauncher.getWorkspace();
-        AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher, true);
-    }
-
     // Please don't add negative test cases for methods that fail only after a long wait.
     public static void expectFail(String message, Runnable action) {
         boolean failed = false;
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 5c753f9..3c88f1d 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -18,8 +18,6 @@
 import static android.app.PendingIntent.FLAG_MUTABLE;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
-
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 
@@ -44,7 +42,6 @@
 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
 import com.android.launcher3.testcomponent.RequestPinItemActivity;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.Wait.Condition;
@@ -77,7 +74,7 @@
         super.setUp();
         mCallbackAction = UUID.randomUUID().toString();
         mShortcutId = UUID.randomUUID().toString();
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
     }
 
     @Test
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java b/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java
index a5e9868..465e9b4 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.ui.widget;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
@@ -23,6 +23,9 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
 import com.android.launcher3.Launcher;
 import com.android.launcher3.tapl.Widgets;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
@@ -34,11 +37,14 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * This test run in both Out of process (Oop) and in-process (Ipc).
  * Make sure the basic interactions with the WidgetPicker works.
  */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
 public class TaplWidgetPickerTest extends AbstractLauncherUiTest {
 
     @Before
@@ -59,10 +65,12 @@
      * Open Widget picker, make sure the widget picker can scroll and then go to home screen.
      */
     @Test
-    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/293191790
+    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/303263644
     @ScreenRecord
     @PortraitLandscape
     public void testWidgets() {
+        // Testing if this will fix b/303263644
+        mLauncher.goHome();
         // Test opening widgets.
         executeOnLauncher(launcher ->
                 assertTrue("Widgets is initially opened", getWidgetsView(launcher) == null));
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
index d776f21..59c82a7 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.ui.workspace;
 
-import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
 import static com.android.launcher3.util.TestConstants.AppNames.CHROME_APP_NAME;
 
 import static org.junit.Assert.assertEquals;
diff --git a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
index 34c7707..e21918f 100644
--- a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.tapl.HomeAppIcon;
 import com.android.launcher3.tapl.HomeAppIconMenuItem;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.Executors;
 
 import org.junit.Test;
@@ -58,7 +57,7 @@
     @Test
     public void testIconWithoutTheme() throws Exception {
         setThemeEnabled(false);
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
 
         HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
@@ -76,7 +75,7 @@
     @Test
     public void testShortcutIconWithoutTheme() throws Exception {
         setThemeEnabled(false);
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
 
         HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
@@ -95,7 +94,7 @@
     @Test
     public void testIconWithTheme() throws Exception {
         setThemeEnabled(true);
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
 
         HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
@@ -113,7 +112,7 @@
     @Test
     public void testShortcutIconWithTheme() throws Exception {
         setThemeEnabled(true);
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
 
         HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
diff --git a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
index 35b4883..e7112d1 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
@@ -35,7 +35,6 @@
 import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
-import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.TestUtil;
 
@@ -74,7 +73,7 @@
                 .atWorkspace(3, -1, 0).putApp(
                         "com.android.vending", "com.android.vending.AssetBrowserActivity");
         mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder);
-        TaplTestsLauncher3.initialize(this);
+        AbstractLauncherUiTest.initialize(this);
         assumeTrue(mLauncher.isTwoPanels());
 
         // Pre verifying the screens
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index f6fcfa64..2512175 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -47,8 +47,14 @@
         return mObject;
     }
 
+    protected boolean launcherStopsAfterLaunch() {
+        return true;
+    }
+
     /**
      * Clicks the object to launch its app.
+     * We are assuming non-translucent app launches because only such launches generate
+     * LAUNCHER_ACTIVITY_STOPPED_MESSAGE.
      */
     public LaunchedAppState launch(String expectedPackageName) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
@@ -58,7 +64,15 @@
                         + mObject.getVisibleCenter() + " in "
                         + mLauncher.getVisibleBounds(mObject));
 
-                mLauncher.clickLauncherObject(mObject);
+                if (launcherStopsAfterLaunch()) {
+                    mLauncher.executeAndWaitForLauncherEvent(
+                            () -> mLauncher.clickLauncherObject(mObject),
+                            event -> TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE
+                                    .equals(event.getClassName().toString()),
+                            () -> "Launcher activity didn't stop", "clicking the launchable");
+                } else {
+                    mLauncher.clickLauncherObject(mObject);
+                }
 
                 try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
                     expectActivityStartEvents();
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
index 099acd4..d747150 100644
--- a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
@@ -49,4 +49,9 @@
     public Launchable getLaunchable() {
         return this;
     }
+
+    @Override
+    protected boolean launcherStopsAfterLaunch() {
+        return false;
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java
index 424c58e..e6fdda0 100644
--- a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java
@@ -54,4 +54,9 @@
     public Launchable getLaunchable() {
         return this;
     }
+
+    @Override
+    protected boolean launcherStopsAfterLaunch() {
+        return false;
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarSearchWebSuggestion.java b/tests/tapl/com/android/launcher3/tapl/TaskbarSearchWebSuggestion.java
index cd8ce42..f25af09 100644
--- a/tests/tapl/com/android/launcher3/tapl/TaskbarSearchWebSuggestion.java
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarSearchWebSuggestion.java
@@ -42,4 +42,9 @@
     public Launchable getLaunchable() {
         return this;
     }
+
+    @Override
+    protected boolean launcherStopsAfterLaunch() {
+        return false;
+    }
 }