Merge "Add aconfig flag for gesture nav handling on connected displays." into main
diff --git a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
index 7530c28..8ca59c4 100644
--- a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
@@ -137,6 +137,7 @@
         android:layout_above="@id/gesture_tutorial_fragment_action_button"
         android:layout_centerHorizontal="true"
         android:background="@android:color/transparent"
+        android:screenReaderFocusable="true"
         android:paddingTop="24dp"
         android:paddingHorizontal="24dp"
         android:layout_marginBottom="16dp">
@@ -146,8 +147,6 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginTop="104dp"
-            android:accessibilityHeading="true"
-            android:accessibilityTraversalBefore="@id/gesture_tutorial_fragment_feedback_subtitle"
             android:gravity="top"
             android:lineSpacingExtra="-1sp"
             android:textAppearance="@style/TextAppearance.GestureTutorial.MainTitle"
@@ -162,8 +161,6 @@
             android:layout_marginTop="24dp"
             android:lineSpacingExtra="4sp"
             android:textAppearance="@style/TextAppearance.GestureTutorial.MainSubtitle"
-            android:accessibilityTraversalAfter="@id/gesture_tutorial_fragment_feedback_title"
-            android:accessibilityTraversalBefore="@id/gesture_tutorial_fragment_action_button"
 
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_fragment_feedback_title" />
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
index 04e1905..b230fa6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
@@ -38,7 +38,6 @@
 import com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW
 import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
 import com.android.quickstep.util.AnimUtils
-import com.android.quickstep.views.ClearAllButton
 import com.android.quickstep.views.RecentsView
 import com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET
 import com.android.quickstep.views.RecentsView.CONTENT_ALPHA
@@ -288,8 +287,8 @@
         val clearAllButtonAlpha =
             if (state.areElementsVisible(launcher, LauncherState.CLEAR_ALL_BUTTON)) 1f else 0f
         propertySetter.setFloat(
-            recentsView.clearAllButton,
-            ClearAllButton.VISIBILITY_ALPHA,
+            recentsView.clearAllButton.visibilityAlphaProperty,
+            MULTI_PROPERTY_VALUE,
             clearAllButtonAlpha,
             LINEAR,
         )
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 537092f..51f2a33 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -52,7 +52,6 @@
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
-import com.android.quickstep.views.ClearAllButton;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.RecentsViewContainer;
 
@@ -98,8 +97,8 @@
     private void setProperties(RecentsState state, StateAnimationConfig config,
             PropertySetter setter) {
         float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
-        setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
-                clearAllButtonAlpha, LINEAR);
+        setter.setFloat(mRecentsView.getClearAllButton().visibilityAlphaProperty,
+                MULTI_PROPERTY_VALUE, clearAllButtonAlpha, LINEAR);
         if (mRecentsView.getAddDeskButton() != null) {
             float addDeskButtonAlpha = state.hasAddDeskButton() ? 1 : 0;
             setter.setFloat(mRecentsView.getAddDeskButton().getVisibilityAlphaProperty(),
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index e73fb3b..22227c9 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -36,7 +36,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Button;
 import android.widget.FrameLayout;
@@ -124,13 +123,10 @@
 
     // These runnables  should be used when posting callbacks to their views and cleared from their
     // views before posting new callbacks.
-    private final Runnable mTitleViewCallback;
-    private final Runnable mSubtitleViewCallback;
     @Nullable private Runnable mFeedbackViewCallback;
     @Nullable private Runnable mFakeTaskViewCallback;
     @Nullable private Runnable mFakeTaskbarViewCallback;
     private final Runnable mShowFeedbackRunnable;
-    private final AccessibilityManager mAccessibilityManager;
 
     TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
         mTutorialFragment = tutorialFragment;
@@ -187,17 +183,6 @@
                 outline.setRoundRect(mExitingAppRect, mExitingAppRadius);
             }
         });
-
-        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
-        mTitleViewCallback = () -> {
-            mFeedbackTitleView.requestFocus();
-            mFeedbackTitleView.sendAccessibilityEvent(
-                    AccessibilityEvent.TYPE_VIEW_FOCUSED);
-        };
-        mSubtitleViewCallback = () -> {
-            mFeedbackSubtitleView.requestFocus();
-            mFeedbackSubtitleView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
-        };
         mShowFeedbackRunnable = () -> {
             mFeedbackView.setAlpha(0f);
             mFeedbackView.setScaleX(0.95f);
@@ -216,14 +201,14 @@
                             mFeedbackViewCallback = mTutorialFragment::continueTutorial;
                             mFeedbackView.postDelayed(
                                     mFeedbackViewCallback,
-                                    mAccessibilityManager.getRecommendedTimeoutMillis(
-                                            ADVANCE_TUTORIAL_TIMEOUT_MS,
-                                            AccessibilityManager.FLAG_CONTENT_TEXT
+                                    AccessibilityManager.getInstance(mContext)
+                                            .getRecommendedTimeoutMillis(
+                                                    ADVANCE_TUTORIAL_TIMEOUT_MS,
+                                                    AccessibilityManager.FLAG_CONTENT_TEXT
                                                     | AccessibilityManager.FLAG_CONTENT_CONTROLS));
                         }
                     })
                     .start();
-            mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS);
         };
     }
 
@@ -416,8 +401,6 @@
             int titleResId,
             int subtitleResId,
             boolean isGestureSuccessful) {
-        mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
-        mFeedbackSubtitleView.removeCallbacks(mSubtitleViewCallback);
         if (mFeedbackViewCallback != null) {
             mFeedbackView.removeCallbacks(mFeedbackViewCallback);
             mFeedbackViewCallback = null;
@@ -425,15 +408,6 @@
 
         mFeedbackTitleView.setText(titleResId);
         mFeedbackSubtitleView.setText(subtitleResId);
-        mFeedbackTitleView.postDelayed(mTitleViewCallback, mAccessibilityManager
-                .getRecommendedTimeoutMillis(
-                        FEEDBACK_ANIMATION_MS,
-                        AccessibilityManager.FLAG_CONTENT_TEXT));
-        mFeedbackSubtitleView.postDelayed(mSubtitleViewCallback, mAccessibilityManager
-                .getRecommendedTimeoutMillis(
-                        SUBTITLE_ANNOUNCE_DELAY_MS,
-                        AccessibilityManager.FLAG_CONTENT_TEXT));
-
         if (isGestureSuccessful) {
             if (mTutorialFragment.isAtFinalStep()) {
                 TypefaceUtils.setTypeface(
@@ -494,8 +468,6 @@
             mFakeTaskbarView.removeCallbacks(mFakeTaskbarViewCallback);
             mFakeTaskbarViewCallback = null;
         }
-        mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
-        mFeedbackSubtitleView.removeCallbacks(mSubtitleViewCallback);
     }
 
     private void playFeedbackAnimation() {
@@ -588,13 +560,6 @@
         mSkipButton.setVisibility(GONE);
         mDoneButton.setVisibility(View.VISIBLE);
         mDoneButton.setOnClickListener(this::onActionButtonClicked);
-        mDoneButton.postDelayed(() -> {
-            mDoneButton.requestFocus();
-            mDoneButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
-        }, mAccessibilityManager
-                .getRecommendedTimeoutMillis(
-                        DONE_BUTTON_ANNOUNCE_DELAY_MS,
-                        AccessibilityManager.FLAG_CONTENT_CONTROLS));
     }
 
     void hideFakeTaskbar(boolean animateToHotseat) {
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index 17f861d..ee9b3e8 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -103,7 +103,7 @@
         target: T,
         action: Int2DAction<T>,
         primaryParam: Int,
-        secondaryParam: Int
+        secondaryParam: Int,
     ) = action.call(target, secondaryParam, primaryParam)
 
     override fun getPrimaryDirection(event: MotionEvent, pointerIndex: Int): Float =
@@ -171,7 +171,7 @@
 
     override fun getSplitTranslationDirectionFactor(
         stagePosition: Int,
-        deviceProfile: DeviceProfile
+        deviceProfile: DeviceProfile,
     ): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1
 
     override fun getTaskMenuX(
@@ -179,7 +179,7 @@
         thumbnailView: View,
         deviceProfile: DeviceProfile,
         taskInsetMargin: Float,
-        taskViewIcon: View
+        taskViewIcon: View,
     ): Float = thumbnailView.measuredWidth + x - taskInsetMargin
 
     override fun getTaskMenuY(
@@ -188,7 +188,7 @@
         stagePosition: Int,
         taskMenuView: View,
         taskInsetMargin: Float,
-        taskViewIcon: View
+        taskViewIcon: View,
     ): Float {
         val layoutParams = taskMenuView.layoutParams as BaseDragLayer.LayoutParams
         var taskMenuY = y + taskInsetMargin
@@ -203,7 +203,7 @@
     override fun getTaskMenuWidth(
         thumbnailView: View,
         deviceProfile: DeviceProfile,
-        @StagePosition stagePosition: Int
+        @StagePosition stagePosition: Int,
     ): Int =
         when {
             Flags.enableOverviewIconMenu() ->
@@ -218,14 +218,14 @@
         taskInsetMargin: Float,
         deviceProfile: DeviceProfile,
         taskMenuX: Float,
-        taskMenuY: Float
+        taskMenuY: Float,
     ): Int = (taskMenuX - taskInsetMargin).toInt()
 
     override fun setTaskOptionsMenuLayoutOrientation(
         deviceProfile: DeviceProfile,
         taskMenuLayout: LinearLayout,
         dividerSpacing: Int,
-        dividerDrawable: ShapeDrawable
+        dividerDrawable: ShapeDrawable,
     ) {
         taskMenuLayout.orientation = LinearLayout.VERTICAL
         dividerDrawable.intrinsicHeight = dividerSpacing
@@ -235,7 +235,7 @@
     override fun setLayoutParamsForTaskMenuOptionItem(
         lp: LinearLayout.LayoutParams,
         viewGroup: LinearLayout,
-        deviceProfile: DeviceProfile
+        deviceProfile: DeviceProfile,
     ) {
         // Phone fake landscape
         viewGroup.orientation = LinearLayout.HORIZONTAL
@@ -250,7 +250,7 @@
         deviceProfile: DeviceProfile,
         snapshotViewWidth: Int,
         snapshotViewHeight: Int,
-        banner: View
+        banner: View,
     ) {
         banner.pivotX = 0f
         banner.pivotY = 0f
@@ -273,7 +273,7 @@
         deviceProfile: DeviceProfile,
         thumbnailViews: Array<View>,
         desiredTaskId: Int,
-        banner: View
+        banner: View,
     ): Pair<Float, Float> {
         val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
         val translationX = banner.height.toFloat()
@@ -317,7 +317,7 @@
         child: View,
         childStart: Int,
         pageCenter: Int,
-        layoutChild: Boolean
+        layoutChild: Boolean,
     ): ChildBounds {
         val childHeight = child.measuredHeight
         val childWidth = child.measuredWidth
@@ -338,7 +338,7 @@
                 R.drawable.ic_split_horizontal,
                 R.string.recent_task_option_split_screen,
                 STAGE_POSITION_TOP_OR_LEFT,
-                STAGE_TYPE_MAIN
+                STAGE_TYPE_MAIN,
             )
         )
 
@@ -347,7 +347,7 @@
         placeholderInset: Int,
         dp: DeviceProfile,
         @StagePosition stagePosition: Int,
-        out: Rect
+        out: Rect,
     ) {
         // In fake land/seascape, the placeholder always needs to go to the "top" of the device,
         // which is the same bounds as 0 rotation.
@@ -374,7 +374,7 @@
         drawableWidth: Int,
         drawableHeight: Int,
         dp: DeviceProfile,
-        @StagePosition stagePosition: Int
+        @StagePosition stagePosition: Int,
     ) {
         val insetAdjustment = getPlaceholderSizeAdjustment(dp) / 2f
         out.x = (onScreenRectCenterX / fullscreenScaleX - 1.0f * drawableWidth / 2)
@@ -393,7 +393,7 @@
         out: View,
         dp: DeviceProfile,
         splitInstructionsHeight: Int,
-        splitInstructionsWidth: Int
+        splitInstructionsWidth: Int,
     ) {
         out.pivotX = 0f
         out.pivotY = splitInstructionsHeight.toFloat()
@@ -421,7 +421,7 @@
         dp: DeviceProfile,
         @StagePosition stagePosition: Int,
         out1: Rect,
-        out2: Rect
+        out2: Rect,
     ) {
         // In fake land/seascape, the window bounds are always top and bottom half
         val screenHeight = dp.heightPx
@@ -434,7 +434,7 @@
         dp: DeviceProfile,
         outRect: Rect,
         splitInfo: SplitBounds,
-        desiredStagePosition: Int
+        desiredStagePosition: Int,
     ) {
         val topLeftTaskPercent = splitInfo.leftTopTaskPercent
         val dividerBarPercent = splitInfo.dividerPercent
@@ -448,7 +448,7 @@
 
     /**
      * @param inSplitSelection Whether user currently has a task from this task group staged for
-     * split screen. Currently this state is not reachable in fake landscape.
+     *   split screen. Currently this state is not reachable in fake landscape.
      */
     override fun measureGroupedTaskViewThumbnailBounds(
         primarySnapshot: View,
@@ -458,7 +458,7 @@
         splitBoundsConfig: SplitBounds,
         dp: DeviceProfile,
         isRtl: Boolean,
-        inSplitSelection: Boolean
+        inSplitSelection: Boolean,
     ) {
         val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
         val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
@@ -479,13 +479,13 @@
         primarySnapshot.translationY = spaceAboveSnapshot.toFloat()
         primarySnapshot.measure(
             MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
-            MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY)
+            MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY),
         )
         val translationY = taskViewFirst.y + spaceAboveSnapshot + dividerBar
         secondarySnapshot.translationY = (translationY - spaceAboveSnapshot).toFloat()
         secondarySnapshot.measure(
             MeasureSpec.makeMeasureSpec(taskViewSecond.x, MeasureSpec.EXACTLY),
-            MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY)
+            MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY),
         )
     }
 
@@ -493,7 +493,7 @@
         dp: DeviceProfile,
         splitBoundsConfig: SplitBounds,
         parentWidth: Int,
-        parentHeight: Int
+        parentHeight: Int,
     ): Pair<Point, Point> {
         val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
         val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
@@ -511,7 +511,7 @@
         taskIconMargin: Int,
         taskIconHeight: Int,
         thumbnailTopMargin: Int,
-        isRtl: Boolean
+        isRtl: Boolean,
     ) {
         iconParams.gravity =
             if (isRtl) {
@@ -527,7 +527,7 @@
 
     override fun setIconAppChipChildrenParams(
         iconParams: FrameLayout.LayoutParams,
-        chipChildMarginStart: Int
+        chipChildMarginStart: Int,
     ) {
         iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
         iconParams.marginStart = chipChildMarginStart
@@ -538,7 +538,7 @@
         iconAppChipView: IconAppChipView,
         iconMenuParams: FrameLayout.LayoutParams,
         iconMenuMargin: Int,
-        thumbnailTopMargin: Int
+        thumbnailTopMargin: Int,
     ) {
         val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL
 
@@ -564,7 +564,7 @@
 
     /**
      * @param inSplitSelection Whether user currently has a task from this task group staged for
-     * split screen. Currently this state is not reachable in fake landscape.
+     *   split screen. Currently this state is not reachable in fake landscape.
      */
     override fun setSplitIconParams(
         primaryIconView: View,
@@ -606,20 +606,20 @@
     override fun <T> getSplitSelectTaskOffset(
         primary: FloatProperty<T>,
         secondary: FloatProperty<T>,
-        deviceProfile: DeviceProfile
+        deviceProfile: DeviceProfile,
     ): Pair<FloatProperty<T>, FloatProperty<T>> = Pair(primary, secondary)
 
     override fun getFloatingTaskOffscreenTranslationTarget(
         floatingTask: View,
         onScreenRect: RectF,
         @StagePosition stagePosition: Int,
-        dp: DeviceProfile
+        dp: DeviceProfile,
     ): Float = floatingTask.translationY - onScreenRect.height()
 
     override fun setFloatingTaskPrimaryTranslation(
         floatingTask: View,
         translation: Float,
-        dp: DeviceProfile
+        dp: DeviceProfile,
     ) {
         floatingTask.translationY = translation
     }
@@ -660,12 +660,11 @@
         } else {
             if (oneIconHiddenDueToSmallWidth) {
                 // Center both icons
-                val centerY = primarySnapshotHeight + overviewTaskMarginPx +
+                val centerY =
+                    primarySnapshotHeight +
+                        overviewTaskMarginPx +
                         ((taskIconHeight + dividerSize) / 2)
-                SplitIconPositions(
-                    topLeftY = centerY,
-                    bottomRightY = centerY,
-                )
+                SplitIconPositions(topLeftY = centerY, bottomRightY = centerY)
             } else {
                 val topLeftY = primarySnapshotHeight + overviewTaskMarginPx
                 SplitIconPositions(
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
deleted file mode 100644
index c4e82d6..0000000
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ /dev/null
@@ -1,872 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.orientation;
-
-import static android.view.Gravity.BOTTOM;
-import static android.view.Gravity.CENTER_HORIZONTAL;
-import static android.view.Gravity.END;
-import static android.view.Gravity.START;
-import static android.view.Gravity.TOP;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
-
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ShapeDrawable;
-import android.util.FloatProperty;
-import android.util.Pair;
-import android.view.Gravity;
-import android.view.Surface;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.touch.DefaultPagedViewHandler;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.quickstep.views.IconAppChipView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class PortraitPagedViewHandler extends DefaultPagedViewHandler implements
-        RecentsPagedOrientationHandler {
-
-    private final Matrix mTmpMatrix = new Matrix();
-    private final RectF mTmpRectF = new RectF();
-
-    @Override
-    public <T> T getPrimaryValue(T x, T y) {
-        return x;
-    }
-
-    @Override
-    public <T> T getSecondaryValue(T x, T y) {
-        return y;
-    }
-
-    @Override
-    public boolean isLayoutNaturalToLauncher() {
-        return true;
-    }
-
-    @Override
-    public void adjustFloatingIconStartVelocity(PointF velocity) {
-        //no-op
-    }
-
-    @Override
-    public void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile) {
-        if (outStartRect.left > deviceProfile.widthPx) {
-            outStartRect.offsetTo(0, outStartRect.top);
-        } else if (outStartRect.left < -deviceProfile.widthPx) {
-            outStartRect.offsetTo(0, outStartRect.top);
-        }
-    }
-
-    @Override
-    public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
-        action.call(target, 0, param);
-    }
-
-    @Override
-    public <T> void set(T target, Int2DAction<T> action, int primaryParam,
-            int secondaryParam) {
-        action.call(target, primaryParam, secondaryParam);
-    }
-
-    @Override
-    public int getPrimarySize(View view) {
-        return view.getWidth();
-    }
-
-    @Override
-    public float getPrimarySize(RectF rect) {
-        return rect.width();
-    }
-
-    @Override
-    public float getStart(RectF rect) {
-        return rect.left;
-    }
-
-    @Override
-    public float getEnd(RectF rect) {
-        return rect.right;
-    }
-
-    @Override
-    public void rotateInsets(@NonNull Rect insets, @NonNull Rect outInsets) {
-        outInsets.set(insets);
-    }
-
-    @Override
-    public int getClearAllSidePadding(View view, boolean isRtl) {
-        return (isRtl ? view.getPaddingRight() : - view.getPaddingLeft()) / 2;
-    }
-
-    @Override
-    public int getSecondaryDimension(View view) {
-        return view.getHeight();
-    }
-
-    @Override
-    public FloatProperty<View> getPrimaryViewTranslate() {
-        return VIEW_TRANSLATE_X;
-    }
-
-    @Override
-    public FloatProperty<View> getSecondaryViewTranslate() {
-        return VIEW_TRANSLATE_Y;
-    }
-
-    @Override
-    public float getDegreesRotated() {
-        return 0;
-    }
-
-    @Override
-    public int getRotation() {
-        return Surface.ROTATION_0;
-    }
-
-    @Override
-    public void setPrimaryScale(View view, float scale) {
-        view.setScaleX(scale);
-    }
-
-    @Override
-    public void setSecondaryScale(View view, float scale) {
-        view.setScaleY(scale);
-    }
-
-    public int getSecondaryTranslationDirectionFactor() {
-        return -1;
-    }
-
-    @Override
-    public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
-        if (deviceProfile.isLeftRightSplit && stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
-            return -1;
-        } else {
-            return 1;
-        }
-    }
-
-    @Override
-    public float getTaskMenuX(float x, View thumbnailView,
-            DeviceProfile deviceProfile, float taskInsetMargin, View taskViewIcon) {
-        if (deviceProfile.isLandscape) {
-            return x + taskInsetMargin
-                    + (thumbnailView.getMeasuredWidth() - thumbnailView.getMeasuredHeight()) / 2f;
-        } else {
-            return x + taskInsetMargin;
-        }
-    }
-
-    @Override
-    public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
-            View taskMenuView, float taskInsetMargin, View taskViewIcon) {
-        return y + taskInsetMargin;
-    }
-
-    @Override
-    public int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile,
-            @StagePosition int stagePosition) {
-        if (enableOverviewIconMenu()) {
-            return thumbnailView.getResources().getDimensionPixelSize(
-                    R.dimen.task_thumbnail_icon_menu_expanded_width);
-        }
-        int padding = thumbnailView.getResources()
-                .getDimensionPixelSize(R.dimen.task_menu_edge_padding);
-        return (deviceProfile.isLandscape && !deviceProfile.isTablet
-                ? thumbnailView.getMeasuredHeight()
-                : thumbnailView.getMeasuredWidth()) - (2 * padding);
-    }
-
-    @Override
-    public int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile,
-            float taskMenuX, float taskMenuY) {
-        return (int) (deviceProfile.heightPx - deviceProfile.getInsets().top - taskMenuY
-                    - deviceProfile.getOverviewActionsClaimedSpaceBelow());
-    }
-
-    @Override
-    public void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile,
-            LinearLayout taskMenuLayout, int dividerSpacing,
-            ShapeDrawable dividerDrawable) {
-        taskMenuLayout.setOrientation(LinearLayout.VERTICAL);
-        dividerDrawable.setIntrinsicHeight(dividerSpacing);
-        taskMenuLayout.setDividerDrawable(dividerDrawable);
-    }
-
-    @Override
-    public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp,
-            LinearLayout viewGroup, DeviceProfile deviceProfile) {
-        viewGroup.setOrientation(LinearLayout.HORIZONTAL);
-        lp.width = LinearLayout.LayoutParams.MATCH_PARENT;
-        lp.height = WRAP_CONTENT;
-    }
-
-    @Override
-    public void updateDwbBannerLayout(int taskViewWidth, int taskViewHeight,
-            boolean isGroupedTaskView, @NonNull DeviceProfile deviceProfile,
-            int snapshotViewWidth, int snapshotViewHeight, @NonNull View banner) {
-        FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
-        banner.setPivotX(0);
-        banner.setPivotY(0);
-        banner.setRotation(getDegreesRotated());
-        if (isGroupedTaskView) {
-            bannerParams.gravity =
-                    BOTTOM | (deviceProfile.isLeftRightSplit ? START : CENTER_HORIZONTAL);
-            bannerParams.width = snapshotViewWidth;
-        } else {
-            bannerParams.width = MATCH_PARENT;
-            bannerParams.gravity = BOTTOM | CENTER_HORIZONTAL;
-        }
-        banner.setLayoutParams(bannerParams);
-    }
-
-    @NonNull
-    @Override
-    public Pair<Float, Float> getDwbBannerTranslations(int taskViewWidth,
-            int taskViewHeight, SplitBounds splitBounds, @NonNull DeviceProfile deviceProfile,
-            @NonNull View[] thumbnailViews, int desiredTaskId, @NonNull View banner) {
-        float translationX = 0;
-        float translationY = 0;
-        if (splitBounds != null) {
-            if (deviceProfile.isLeftRightSplit) {
-                if (desiredTaskId == splitBounds.rightBottomTaskId) {
-                    float leftTopTaskPercent = splitBounds.getLeftTopTaskPercent();
-                    float dividerThicknessPercent = splitBounds.getDividerPercent();
-                    translationX = ((taskViewWidth * leftTopTaskPercent)
-                            + (taskViewWidth * dividerThicknessPercent));
-                }
-            } else {
-                if (desiredTaskId == splitBounds.leftTopTaskId) {
-                    FrameLayout.LayoutParams snapshotParams =
-                            (FrameLayout.LayoutParams) thumbnailViews[0]
-                                    .getLayoutParams();
-                    float bottomRightTaskPlusDividerPercent =
-                            splitBounds.getRightBottomTaskPercent()
-                                    + splitBounds.getDividerPercent();
-                    translationY = -((taskViewHeight - snapshotParams.topMargin)
-                            * bottomRightTaskPlusDividerPercent);
-                }
-            }
-        }
-        return new Pair<>(translationX, translationY);
-    }
-
-    /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
-
-    @Override
-    public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
-        return VERTICAL;
-    }
-
-    @Override
-    public int getUpDirection(boolean isRtl) {
-        // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
-        return SingleAxisSwipeDetector.DIRECTION_POSITIVE;
-    }
-
-    @Override
-    public int getDownDirection(boolean isRtl) {
-        // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
-        return SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
-    }
-
-    @Override
-    public boolean isGoingUp(float displacement, boolean isRtl) {
-        // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
-        return displacement < 0;
-    }
-
-    @Override
-    public int getTaskDragDisplacementFactor(boolean isRtl) {
-        // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
-        return 1;
-    }
-
-    /* -------------------- */
-    @Override
-    public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
-        return dp.heightPx - rect.bottom;
-    }
-
-    @Override
-    public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
-        if (dp.isTablet) {
-            return Utilities.getSplitPositionOptions(dp);
-        }
-
-        List<SplitPositionOption> options = new ArrayList<>();
-        if (dp.isSeascape()) {
-            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.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));
-        } else {
-            // Only add top option
-            options.add(new SplitPositionOption(
-                    R.drawable.ic_split_vertical, R.string.recent_task_option_split_screen,
-                    STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
-        }
-        return options;
-    }
-
-    @Override
-    public void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset,
-            DeviceProfile dp, @StagePosition int stagePosition, Rect out) {
-        int screenWidth = dp.widthPx;
-        int screenHeight = dp.heightPx;
-        boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
-        int insetSizeAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight);
-
-        out.set(0, 0, screenWidth, placeholderHeight + insetSizeAdjustment);
-        if (!dp.isLeftRightSplit) {
-            // portrait, phone or tablet - spans width of screen, nothing else to do
-            out.inset(placeholderInset, 0);
-
-            // Adjust the top to account for content off screen. This will help to animate the view
-            // in with rounded corners.
-            int totalHeight = (int) (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset)
-                    / screenWidth);
-            out.top -= (totalHeight - placeholderHeight);
-            return;
-        }
-
-        // Now we rotate the portrait rect depending on what side we want pinned
-
-        float postRotateScale = (float) screenHeight / screenWidth;
-        mTmpMatrix.reset();
-        mTmpMatrix.postRotate(pinToRight ? 90 : 270);
-        mTmpMatrix.postTranslate(pinToRight ? screenWidth : 0, pinToRight ? 0 : screenWidth);
-        // The placeholder height stays constant after rotation, so we don't change width scale
-        mTmpMatrix.postScale(1, postRotateScale);
-
-        mTmpRectF.set(out);
-        mTmpMatrix.mapRect(mTmpRectF);
-        mTmpRectF.inset(0, placeholderInset);
-        mTmpRectF.roundOut(out);
-
-        // Adjust the top to account for content off screen. This will help to animate the view in
-        // with rounded corners.
-        int totalWidth = (int) (1.0f * screenWidth / 2 * (screenHeight - 2 * placeholderInset)
-                / screenHeight);
-        int width = out.width();
-        if (pinToRight) {
-            out.right += totalWidth - width;
-        } else {
-            out.left -= totalWidth - width;
-        }
-    }
-
-    @Override
-    public void updateSplitIconParams(View out, float onScreenRectCenterX,
-            float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
-            int drawableWidth, int drawableHeight, DeviceProfile dp,
-            @StagePosition int stagePosition) {
-        boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
-        float insetAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight) / 2f;
-        if (!dp.isLeftRightSplit) {
-            out.setX(onScreenRectCenterX / fullscreenScaleX
-                    - 1.0f * drawableWidth / 2);
-            out.setY((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY
-                    - 1.0f * drawableHeight / 2);
-        } else {
-            if (pinToRight) {
-                out.setX((onScreenRectCenterX - insetAdjustment) / fullscreenScaleX
-                        - 1.0f * drawableWidth / 2);
-            } else {
-                out.setX((onScreenRectCenterX + insetAdjustment) / fullscreenScaleX
-                        - 1.0f * drawableWidth / 2);
-            }
-            out.setY(onScreenRectCenterY / fullscreenScaleY
-                    - 1.0f * drawableHeight / 2);
-        }
-    }
-
-    /**
-     * The split placeholder comes with a default inset to buffer the icon from the top of the
-     * screen. But if the device already has a large inset (from cutouts etc), use that instead.
-     */
-    private int getPlaceholderSizeAdjustment(DeviceProfile dp, boolean pinToRight) {
-        int insetThickness;
-        if (!dp.isLandscape) {
-            insetThickness = dp.getInsets().top;
-        } else {
-            insetThickness = pinToRight ? dp.getInsets().right : dp.getInsets().left;
-        }
-        return Math.max(insetThickness - dp.splitPlaceholderInset, 0);
-    }
-
-    @Override
-    public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
-            int splitInstructionsWidth) {
-        out.setPivotX(0);
-        out.setPivotY(splitInstructionsHeight);
-        out.setRotation(getDegreesRotated());
-        int distanceToEdge;
-        if (dp.isPhone) {
-            if (dp.isLandscape) {
-                distanceToEdge = out.getResources().getDimensionPixelSize(
-                        R.dimen.split_instructions_bottom_margin_phone_landscape);
-            } else {
-                distanceToEdge = out.getResources().getDimensionPixelSize(
-                        R.dimen.split_instructions_bottom_margin_phone_portrait);
-            }
-        } else {
-            distanceToEdge = dp.getOverviewActionsClaimedSpaceBelow();
-        }
-
-        // Center the view in case of unbalanced insets on left or right of screen
-        int insetCorrectionX = (dp.getInsets().right - dp.getInsets().left) / 2;
-        // Adjust for any insets on the bottom edge
-        int insetCorrectionY = dp.getInsets().bottom;
-        out.setTranslationX(insetCorrectionX);
-        out.setTranslationY(-distanceToEdge + insetCorrectionY);
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
-        lp.gravity = CENTER_HORIZONTAL | BOTTOM;
-        out.setLayoutParams(lp);
-    }
-
-    @Override
-    public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
-            @StagePosition int stagePosition, Rect out1, Rect out2) {
-        int screenHeight = dp.heightPx;
-        int screenWidth = dp.widthPx;
-        out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize);
-        out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight);
-        if (!dp.isLeftRightSplit) {
-            // Portrait - the window bounds are always top and bottom half
-            return;
-        }
-
-        // Now we rotate the portrait rect depending on what side we want pinned
-        boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
-        float postRotateScale = (float) screenHeight / screenWidth;
-
-        mTmpMatrix.reset();
-        mTmpMatrix.postRotate(pinToRight ? 90 : 270);
-        mTmpMatrix.postTranslate(pinToRight ? screenHeight : 0, pinToRight ? 0 : screenWidth);
-        mTmpMatrix.postScale(1 / postRotateScale, postRotateScale);
-
-        mTmpRectF.set(out1);
-        mTmpMatrix.mapRect(mTmpRectF);
-        mTmpRectF.roundOut(out1);
-
-        mTmpRectF.set(out2);
-        mTmpMatrix.mapRect(mTmpRectF);
-        mTmpRectF.roundOut(out2);
-    }
-
-    @Override
-    public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
-            SplitBounds splitInfo, int desiredStagePosition) {
-        float topLeftTaskPercent = splitInfo.getLeftTopTaskPercent();
-        float dividerBarPercent = splitInfo.getDividerPercent();
-
-        int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight;
-        float scale = (float) outRect.height() / (dp.availableHeightPx - taskbarHeight);
-        float topTaskHeight = dp.availableHeightPx * topLeftTaskPercent;
-        float scaledTopTaskHeight = topTaskHeight * scale;
-        float dividerHeight = dp.availableHeightPx * dividerBarPercent;
-        float scaledDividerHeight = dividerHeight * scale;
-
-        if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
-            if (dp.isLeftRightSplit) {
-                outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent);
-            } else {
-                outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight);
-            }
-        } else {
-            if (dp.isLeftRightSplit) {
-                outRect.left += Math.round(outRect.width()
-                        * (topLeftTaskPercent + dividerBarPercent));
-            } else {
-                outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight);
-            }
-        }
-    }
-
-    /**
-     * @param inSplitSelection Whether user currently has a task from this task group staged for
-     *                         split screen. If true, we have custom translations/scaling in place
-     *                         for the remaining snapshot, so we'll skip setting translation/scale
-     *                         here.
-     */
-    @Override
-    public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
-            int parentWidth, int parentHeight, SplitBounds splitBoundsConfig,
-            DeviceProfile dp, boolean isRtl, boolean inSplitSelection) {
-        int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
-
-        FrameLayout.LayoutParams primaryParams =
-                (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
-        FrameLayout.LayoutParams secondaryParams =
-                (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
-
-        // Reset margins that aren't used in this method, but are used in other
-        // `RecentsPagedOrientationHandler` variants.
-        secondaryParams.topMargin = 0;
-        primaryParams.topMargin = spaceAboveSnapshot;
-
-        int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
-        float dividerScale = splitBoundsConfig.getDividerPercent();
-        Pair<Point, Point> taskViewSizes =
-                getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight);
-        if (!inSplitSelection) {
-            // Reset translations that aren't used in this method, but are used in other
-            // `RecentsPagedOrientationHandler` variants.
-            primarySnapshot.setTranslationY(0);
-
-            if (dp.isLeftRightSplit) {
-                int scaledDividerBar = Math.round(parentWidth * dividerScale);
-                if (isRtl) {
-                    int translationX = taskViewSizes.second.x + scaledDividerBar;
-                    primarySnapshot.setTranslationX(-translationX);
-                    secondarySnapshot.setTranslationX(0);
-                } else {
-                    int translationX = taskViewSizes.first.x + scaledDividerBar;
-                    secondarySnapshot.setTranslationX(translationX);
-                    primarySnapshot.setTranslationX(0);
-                }
-                secondarySnapshot.setTranslationY(spaceAboveSnapshot);
-            } else {
-                float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale);
-                float translationY =
-                        taskViewSizes.first.y + spaceAboveSnapshot + finalDividerHeight;
-                secondarySnapshot.setTranslationY(translationY);
-
-                // Reset unused translations.
-                secondarySnapshot.setTranslationX(0);
-                primarySnapshot.setTranslationX(0);
-            }
-        }
-
-        primarySnapshot.measure(
-                View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.x, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.y, View.MeasureSpec.EXACTLY));
-        secondarySnapshot.measure(
-                View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.x, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.y,
-                        View.MeasureSpec.EXACTLY));
-    }
-
-    @Override
-    public Pair<Point, Point> getGroupedTaskViewSizes(
-            DeviceProfile dp,
-            SplitBounds splitBoundsConfig,
-            int parentWidth,
-            int parentHeight) {
-        int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
-        int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
-        float dividerScale = splitBoundsConfig.getDividerPercent();
-        float taskPercent = splitBoundsConfig.getLeftTopTaskPercent();
-
-        Point firstTaskViewSize = new Point();
-        Point secondTaskViewSize = new Point();
-
-        if (dp.isLeftRightSplit) {
-            int scaledDividerBar = Math.round(parentWidth * dividerScale);
-            firstTaskViewSize.x = Math.round(parentWidth * taskPercent);
-            firstTaskViewSize.y = totalThumbnailHeight;
-
-            secondTaskViewSize.x = parentWidth - firstTaskViewSize.x - scaledDividerBar;
-            secondTaskViewSize.y = totalThumbnailHeight;
-        } else {
-            int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight;
-            float scale = (float) totalThumbnailHeight / (dp.availableHeightPx - taskbarHeight);
-            float topTaskHeight = dp.availableHeightPx * taskPercent;
-            float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale);
-            float scaledTopTaskHeight = topTaskHeight * scale;
-            firstTaskViewSize.x = parentWidth;
-            firstTaskViewSize.y = Math.round(scaledTopTaskHeight);
-
-            secondTaskViewSize.x = parentWidth;
-            secondTaskViewSize.y = Math.round(totalThumbnailHeight - firstTaskViewSize.y
-                    - finalDividerHeight);
-        }
-
-        return new Pair<>(firstTaskViewSize, secondTaskViewSize);
-    }
-
-    @Override
-    public void setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin,
-            int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
-        iconParams.gravity = TOP | CENTER_HORIZONTAL;
-        // Reset margins, since they may have been set on rotation
-        iconParams.leftMargin = iconParams.rightMargin = 0;
-        iconParams.topMargin = iconParams.bottomMargin = 0;
-    }
-
-    @Override
-    public void setIconAppChipChildrenParams(FrameLayout.LayoutParams iconParams,
-            int chipChildMarginStart) {
-        iconParams.setMarginStart(chipChildMarginStart);
-        iconParams.gravity = Gravity.START | Gravity.CENTER_VERTICAL;
-        iconParams.topMargin = 0;
-    }
-
-    @Override
-    public void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
-            FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin, int thumbnailTopMargin) {
-        iconMenuParams.gravity = TOP | START;
-        iconMenuParams.setMarginStart(iconMenuMargin);
-        iconMenuParams.topMargin = thumbnailTopMargin;
-        iconMenuParams.bottomMargin = 0;
-        iconMenuParams.setMarginEnd(0);
-
-        iconAppChipView.setPivotX(0);
-        iconAppChipView.setPivotY(0);
-        iconAppChipView.setSplitTranslationY(0);
-        iconAppChipView.setRotation(getDegreesRotated());
-    }
-
-    /**
-     * @param inSplitSelection Whether user currently has a task from this task group staged for
-     *                         split screen. If true, we have custom translations in place for the
-     *                         remaining icon, so we'll skip setting translations here.
-     */
-    @Override
-    public void setSplitIconParams(View primaryIconView, View secondaryIconView,
-            int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
-            int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
-            DeviceProfile deviceProfile, SplitBounds splitConfig, boolean inSplitSelection,
-            boolean oneIconHiddenDueToSmallWidth) {
-        FrameLayout.LayoutParams primaryIconParams =
-                (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
-        FrameLayout.LayoutParams secondaryIconParams = enableOverviewIconMenu()
-                ? (FrameLayout.LayoutParams) secondaryIconView.getLayoutParams()
-                : new FrameLayout.LayoutParams(primaryIconParams);
-
-        if (enableOverviewIconMenu()) {
-            IconAppChipView primaryAppChipView = (IconAppChipView) primaryIconView;
-            IconAppChipView secondaryAppChipView = (IconAppChipView) secondaryIconView;
-            primaryIconParams.gravity = TOP | START;
-            secondaryIconParams.gravity = TOP | START;
-            secondaryIconParams.topMargin = primaryIconParams.topMargin;
-            secondaryIconParams.setMarginStart(primaryIconParams.getMarginStart());
-            if (!inSplitSelection) {
-                if (deviceProfile.isLeftRightSplit) {
-                    if (isRtl) {
-                        int secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth;
-                        primaryAppChipView.setSplitTranslationX(-secondarySnapshotWidth);
-                    } else {
-                        secondaryAppChipView.setSplitTranslationX(primarySnapshotWidth);
-                    }
-                } else {
-                    primaryAppChipView.setSplitTranslationX(0);
-                    secondaryAppChipView.setSplitTranslationX(0);
-                    int dividerThickness = Math.min(splitConfig.visualDividerBounds.width(),
-                            splitConfig.visualDividerBounds.height());
-                    secondaryAppChipView.setSplitTranslationY(
-                            primarySnapshotHeight + (deviceProfile.isTablet ? 0
-                                    : dividerThickness));
-                }
-            }
-        } 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.
-            int fullscreenInsetThickness = deviceProfile.isSeascape()
-                    ? deviceProfile.getInsets().right
-                    : deviceProfile.getInsets().left;
-            int fullscreenMidpointFromBottom = ((deviceProfile.widthPx
-                    - fullscreenInsetThickness) / 2);
-            float midpointFromEndPct = (float) fullscreenMidpointFromBottom
-                    / deviceProfile.widthPx;
-            float insetPct = (float) fullscreenInsetThickness / deviceProfile.widthPx;
-            int spaceAboveSnapshots = 0;
-            int overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots;
-            int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness
-                    * midpointFromEndPct);
-            int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
-
-            if (deviceProfile.isSeascape()) {
-                primaryIconParams.gravity = TOP | (isRtl ? END : START);
-                secondaryIconParams.gravity = TOP | (isRtl ? END : START);
-                if (!inSplitSelection) {
-                    if (splitConfig.initiatedFromSeascape) {
-                        if (oneIconHiddenDueToSmallWidth) {
-                            // Center both icons
-                            float centerX = bottomToMidpointOffset - (taskIconHeight / 2f);
-                            primaryIconView.setTranslationX(centerX);
-                            secondaryIconView.setTranslationX(centerX);
-                        } else {
-                            // the task on the right (secondary) is slightly larger
-                            primaryIconView.setTranslationX(
-                                    bottomToMidpointOffset - taskIconHeight);
-                            secondaryIconView.setTranslationX(bottomToMidpointOffset);
-                        }
-                    } else {
-                        if (oneIconHiddenDueToSmallWidth) {
-                            // Center both icons
-                            float centerX =
-                                    bottomToMidpointOffset + insetOffset - (taskIconHeight / 2f);
-                            primaryIconView.setTranslationX(centerX);
-                            secondaryIconView.setTranslationX(centerX);
-                        } else {
-                            // the task on the left (primary) is slightly larger
-                            primaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset
-                                    - taskIconHeight);
-                            secondaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset);
-                        }
-                    }
-                }
-            } else {
-                primaryIconParams.gravity = TOP | (isRtl ? START : END);
-                secondaryIconParams.gravity = TOP | (isRtl ? START : END);
-                if (!inSplitSelection) {
-                    if (!splitConfig.initiatedFromSeascape) {
-                        if (oneIconHiddenDueToSmallWidth) {
-                            // Center both icons
-                            float centerX = -bottomToMidpointOffset + (taskIconHeight / 2f);
-                            primaryIconView.setTranslationX(centerX);
-                            secondaryIconView.setTranslationX(centerX);
-                        } else {
-                            // the task on the left (primary) is slightly larger
-                            primaryIconView.setTranslationX(-bottomToMidpointOffset);
-                            secondaryIconView.setTranslationX(
-                                    -bottomToMidpointOffset + taskIconHeight);
-                        }
-                    } else {
-                        if (oneIconHiddenDueToSmallWidth) {
-                            // Center both icons
-                            float centerX =
-                                    -bottomToMidpointOffset - insetOffset + (taskIconHeight / 2f);
-                            primaryIconView.setTranslationX(centerX);
-                            secondaryIconView.setTranslationX(centerX);
-                        } else {
-                            // the task on the right (secondary) is slightly larger
-                            primaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset);
-                            secondaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset
-                                    + taskIconHeight);
-                        }
-                    }
-                }
-            }
-        } else {
-            primaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
-            secondaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
-            if (!inSplitSelection) {
-                if (oneIconHiddenDueToSmallWidth) {
-                    // Center both icons
-                    primaryIconView.setTranslationX(0);
-                    secondaryIconView.setTranslationX(0);
-                } else {
-                    // shifts icon half a width left (height is used here since icons are square)
-                    primaryIconView.setTranslationX(-(taskIconHeight / 2f));
-                    secondaryIconView.setTranslationX(taskIconHeight / 2f);
-                }
-            }
-        }
-        if (!enableOverviewIconMenu() && !inSplitSelection) {
-            primaryIconView.setTranslationY(0);
-            secondaryIconView.setTranslationY(0);
-        }
-
-
-        primaryIconView.setLayoutParams(primaryIconParams);
-        secondaryIconView.setLayoutParams(secondaryIconParams);
-    }
-
-    @Override
-    public int getDefaultSplitPosition(DeviceProfile deviceProfile) {
-        if (!deviceProfile.isTablet) {
-            throw new IllegalStateException("Default position available only for large screens");
-        }
-        if (deviceProfile.isLeftRightSplit) {
-            return STAGE_POSITION_BOTTOM_OR_RIGHT;
-        } else {
-            return STAGE_POSITION_TOP_OR_LEFT;
-        }
-    }
-
-    @Override
-    public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
-            FloatProperty secondary, DeviceProfile deviceProfile) {
-        if (deviceProfile.isLeftRightSplit) { // or seascape
-            return new Pair<>(primary, secondary);
-        } else {
-            return new Pair<>(secondary, primary);
-        }
-    }
-
-    @Override
-    public float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
-            @StagePosition int stagePosition, DeviceProfile dp) {
-        if (dp.isLeftRightSplit) {
-            float currentTranslationX = floatingTask.getTranslationX();
-            return stagePosition == STAGE_POSITION_TOP_OR_LEFT
-                    ? currentTranslationX - onScreenRect.width()
-                    : currentTranslationX + onScreenRect.width();
-        } else {
-            float currentTranslationY = floatingTask.getTranslationY();
-            return currentTranslationY - onScreenRect.height();
-        }
-    }
-
-    @Override
-    public void setFloatingTaskPrimaryTranslation(View floatingTask, float translation,
-            DeviceProfile dp) {
-        if (dp.isLeftRightSplit) {
-            floatingTask.setTranslationX(translation);
-        } else {
-            floatingTask.setTranslationY(translation);
-        }
-
-    }
-
-    @Override
-    public float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
-        return dp.isLeftRightSplit
-                ? floatingTask.getTranslationX()
-                : floatingTask.getTranslationY();
-    }
-
-    @NonNull
-    @Override
-    public LauncherAtom.TaskSwitcherContainer.OrientationHandler getHandlerTypeForLogging() {
-        return LauncherAtom.TaskSwitcherContainer.OrientationHandler.PORTRAIT;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
new file mode 100644
index 0000000..f2e53fc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
@@ -0,0 +1,903 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.orientation
+
+import android.graphics.Matrix
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.ShapeDrawable
+import android.util.FloatProperty
+import android.util.Pair
+import android.view.Gravity
+import android.view.Surface
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.core.view.updateLayoutParams
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.LauncherAnimUtils
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.logger.LauncherAtom
+import com.android.launcher3.touch.DefaultPagedViewHandler
+import com.android.launcher3.touch.PagedOrientationHandler.Float2DAction
+import com.android.launcher3.touch.PagedOrientationHandler.Int2DAction
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
+import com.android.quickstep.views.IconAppChipView
+import kotlin.math.max
+import kotlin.math.min
+
+class PortraitPagedViewHandler : DefaultPagedViewHandler(), RecentsPagedOrientationHandler {
+    private val tmpMatrix = Matrix()
+    private val tmpRectF = RectF()
+
+    override fun <T> getPrimaryValue(x: T, y: T): T = x
+
+    override fun <T> getSecondaryValue(x: T, y: T): T = y
+
+    override val isLayoutNaturalToLauncher: Boolean = true
+
+    override fun adjustFloatingIconStartVelocity(velocity: PointF) {
+        // no-op
+    }
+
+    override fun fixBoundsForHomeAnimStartRect(outStartRect: RectF, deviceProfile: DeviceProfile) {
+        if (outStartRect.left > deviceProfile.widthPx) {
+            outStartRect.offsetTo(0f, outStartRect.top)
+        } else if (outStartRect.left < -deviceProfile.widthPx) {
+            outStartRect.offsetTo(0f, outStartRect.top)
+        }
+    }
+
+    override fun <T> setSecondary(target: T, action: Float2DAction<T>, param: Float) =
+        action.call(target, 0f, param)
+
+    override fun <T> set(
+        target: T,
+        action: Int2DAction<T>,
+        primaryParam: Int,
+        secondaryParam: Int,
+    ) = action.call(target, primaryParam, secondaryParam)
+
+    override fun getPrimarySize(view: View): Int = view.width
+
+    override fun getPrimarySize(rect: RectF): Float = rect.width()
+
+    override fun getStart(rect: RectF): Float = rect.left
+
+    override fun getEnd(rect: RectF): Float = rect.right
+
+    override fun rotateInsets(insets: Rect, outInsets: Rect) = outInsets.set(insets)
+
+    override fun getClearAllSidePadding(view: View, isRtl: Boolean): Int =
+        (if (isRtl) view.paddingRight else -view.paddingLeft) / 2
+
+    override fun getSecondaryDimension(view: View): Int = view.height
+
+    override val primaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_X
+
+    override val secondaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_Y
+
+    override val degreesRotated: Float = 0f
+
+    override val rotation: Int = Surface.ROTATION_0
+
+    override fun setPrimaryScale(view: View, scale: Float) {
+        view.scaleX = scale
+    }
+
+    override fun setSecondaryScale(view: View, scale: Float) {
+        view.scaleY = scale
+    }
+
+    override val secondaryTranslationDirectionFactor: Int
+        get() = -1
+
+    override fun getSplitTranslationDirectionFactor(
+        stagePosition: Int,
+        deviceProfile: DeviceProfile,
+    ): Int =
+        if (
+            deviceProfile.isLeftRightSplit &&
+                stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+        ) {
+            -1
+        } else {
+            1
+        }
+
+    override fun getTaskMenuX(
+        x: Float,
+        thumbnailView: View,
+        deviceProfile: DeviceProfile,
+        taskInsetMargin: Float,
+        taskViewIcon: View,
+    ): Float =
+        if (deviceProfile.isLandscape) {
+            (x +
+                taskInsetMargin +
+                (thumbnailView.measuredWidth - thumbnailView.measuredHeight) / 2f)
+        } else {
+            x + taskInsetMargin
+        }
+
+    override fun getTaskMenuY(
+        y: Float,
+        thumbnailView: View,
+        stagePosition: Int,
+        taskMenuView: View,
+        taskInsetMargin: Float,
+        taskViewIcon: View,
+    ): Float = y + taskInsetMargin
+
+    override fun getTaskMenuWidth(
+        thumbnailView: View,
+        deviceProfile: DeviceProfile,
+        @StagePosition stagePosition: Int,
+    ): Int =
+        when {
+            enableOverviewIconMenu() -> {
+                thumbnailView.resources.getDimensionPixelSize(
+                    R.dimen.task_thumbnail_icon_menu_expanded_width
+                )
+            }
+
+            (deviceProfile.isLandscape && !deviceProfile.isTablet) -> {
+                val padding =
+                    thumbnailView.resources.getDimensionPixelSize(R.dimen.task_menu_edge_padding)
+                thumbnailView.measuredHeight - (2 * padding)
+            }
+
+            else -> {
+                val padding =
+                    thumbnailView.resources.getDimensionPixelSize(R.dimen.task_menu_edge_padding)
+                thumbnailView.measuredWidth - (2 * padding)
+            }
+        }
+
+    override fun getTaskMenuHeight(
+        taskInsetMargin: Float,
+        deviceProfile: DeviceProfile,
+        taskMenuX: Float,
+        taskMenuY: Float,
+    ): Int =
+        deviceProfile.heightPx -
+            deviceProfile.insets.top -
+            taskMenuY.toInt() -
+            deviceProfile.overviewActionsClaimedSpaceBelow
+
+    override fun setTaskOptionsMenuLayoutOrientation(
+        deviceProfile: DeviceProfile,
+        taskMenuLayout: LinearLayout,
+        dividerSpacing: Int,
+        dividerDrawable: ShapeDrawable,
+    ) {
+        taskMenuLayout.orientation = LinearLayout.VERTICAL
+        dividerDrawable.intrinsicHeight = dividerSpacing
+        taskMenuLayout.dividerDrawable = dividerDrawable
+    }
+
+    override fun setLayoutParamsForTaskMenuOptionItem(
+        lp: LinearLayout.LayoutParams,
+        viewGroup: LinearLayout,
+        deviceProfile: DeviceProfile,
+    ) {
+        viewGroup.orientation = LinearLayout.HORIZONTAL
+        lp.width = LinearLayout.LayoutParams.MATCH_PARENT
+        lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
+    }
+
+    override fun updateDwbBannerLayout(
+        taskViewWidth: Int,
+        taskViewHeight: Int,
+        isGroupedTaskView: Boolean,
+        deviceProfile: DeviceProfile,
+        snapshotViewWidth: Int,
+        snapshotViewHeight: Int,
+        banner: View,
+    ) {
+        banner.pivotX = 0f
+        banner.pivotY = 0f
+        banner.rotation = degreesRotated
+        banner.updateLayoutParams<FrameLayout.LayoutParams> {
+            if (isGroupedTaskView) {
+                gravity =
+                    Gravity.BOTTOM or
+                        (if (deviceProfile.isLeftRightSplit) Gravity.START
+                        else Gravity.CENTER_HORIZONTAL)
+                width = snapshotViewWidth
+            } else {
+                width = ViewGroup.LayoutParams.MATCH_PARENT
+                gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+            }
+        }
+    }
+
+    override fun getDwbBannerTranslations(
+        taskViewWidth: Int,
+        taskViewHeight: Int,
+        splitBounds: SplitConfigurationOptions.SplitBounds?,
+        deviceProfile: DeviceProfile,
+        thumbnailViews: Array<View>,
+        desiredTaskId: Int,
+        banner: View,
+    ): Pair<Float, Float> {
+        var translationX = 0f
+        var translationY = 0f
+        if (splitBounds != null) {
+            if (deviceProfile.isLeftRightSplit) {
+                if (desiredTaskId == splitBounds.rightBottomTaskId) {
+                    val leftTopTaskPercent = splitBounds.leftTopTaskPercent
+                    val dividerThicknessPercent = splitBounds.dividerPercent
+                    translationX =
+                        ((taskViewWidth * leftTopTaskPercent) +
+                            (taskViewWidth * dividerThicknessPercent))
+                }
+            } else {
+                if (desiredTaskId == splitBounds.leftTopTaskId) {
+                    val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
+                    val bottomRightTaskPlusDividerPercent =
+                        (splitBounds.rightBottomTaskPercent + splitBounds.dividerPercent)
+                    translationY =
+                        -((taskViewHeight - snapshotParams.topMargin) *
+                            bottomRightTaskPlusDividerPercent)
+                }
+            }
+        }
+        return Pair(translationX, translationY)
+    }
+
+    /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
+
+    override val upDownSwipeDirection: SingleAxisSwipeDetector.Direction =
+        SingleAxisSwipeDetector.VERTICAL
+
+    // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+    override fun getUpDirection(isRtl: Boolean): Int = SingleAxisSwipeDetector.DIRECTION_POSITIVE
+
+    // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+    override fun getDownDirection(isRtl: Boolean): Int = SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+
+    // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+    override fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean = displacement < 0
+
+    // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+    override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = 1
+
+    /* -------------------- */
+
+    override fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int =
+        dp.heightPx - rect.bottom
+
+    override fun getSplitPositionOptions(dp: DeviceProfile): List<SplitPositionOption> =
+        when {
+            dp.isTablet -> {
+                Utilities.getSplitPositionOptions(dp)
+            }
+
+            dp.isSeascape -> {
+                listOf(
+                    SplitPositionOption(
+                        R.drawable.ic_split_horizontal,
+                        R.string.recent_task_option_split_screen,
+                        SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT,
+                        SplitConfigurationOptions.STAGE_TYPE_MAIN,
+                    )
+                )
+            }
+
+            dp.isLeftRightSplit -> {
+                listOf(
+                    SplitPositionOption(
+                        R.drawable.ic_split_horizontal,
+                        R.string.recent_task_option_split_screen,
+                        SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+                        SplitConfigurationOptions.STAGE_TYPE_MAIN,
+                    )
+                )
+            }
+
+            else -> {
+                // Only add top option
+                listOf(
+                    SplitPositionOption(
+                        R.drawable.ic_split_vertical,
+                        R.string.recent_task_option_split_screen,
+                        SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+                        SplitConfigurationOptions.STAGE_TYPE_MAIN,
+                    )
+                )
+            }
+        }
+
+    override fun getInitialSplitPlaceholderBounds(
+        placeholderHeight: Int,
+        placeholderInset: Int,
+        dp: DeviceProfile,
+        @StagePosition stagePosition: Int,
+        out: Rect,
+    ) {
+        val screenWidth = dp.widthPx
+        val screenHeight = dp.heightPx
+        val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+        val insetSizeAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight)
+
+        out.set(0, 0, screenWidth, placeholderHeight + insetSizeAdjustment)
+        if (!dp.isLeftRightSplit) {
+            // portrait, phone or tablet - spans width of screen, nothing else to do
+            out.inset(placeholderInset, 0)
+
+            // Adjust the top to account for content off screen. This will help to animate the view
+            // in with rounded corners.
+            val totalHeight =
+                (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset) / screenWidth)
+                    .toInt()
+            out.top -= (totalHeight - placeholderHeight)
+            return
+        }
+
+        // Now we rotate the portrait rect depending on what side we want pinned
+        val postRotateScale = screenHeight.toFloat() / screenWidth
+        tmpMatrix.reset()
+        tmpMatrix.postRotate(if (pinToRight) 90f else 270f)
+        tmpMatrix.postTranslate(
+            (if (pinToRight) screenWidth else 0).toFloat(),
+            (if (pinToRight) 0 else screenWidth).toFloat(),
+        )
+        // The placeholder height stays constant after rotation, so we don't change width scale
+        tmpMatrix.postScale(1f, postRotateScale)
+
+        tmpRectF.set(out)
+        tmpMatrix.mapRect(tmpRectF)
+        tmpRectF.inset(0f, placeholderInset.toFloat())
+        tmpRectF.roundOut(out)
+
+        // Adjust the top to account for content off screen. This will help to animate the view in
+        // with rounded corners.
+        val totalWidth =
+            (1.0f * screenWidth / 2 * (screenHeight - 2 * placeholderInset) / screenHeight).toInt()
+        val width = out.width()
+        if (pinToRight) {
+            out.right += totalWidth - width
+        } else {
+            out.left -= totalWidth - width
+        }
+    }
+
+    override fun updateSplitIconParams(
+        out: View,
+        onScreenRectCenterX: Float,
+        onScreenRectCenterY: Float,
+        fullscreenScaleX: Float,
+        fullscreenScaleY: Float,
+        drawableWidth: Int,
+        drawableHeight: Int,
+        dp: DeviceProfile,
+        @StagePosition stagePosition: Int,
+    ) {
+        val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+        val insetAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight) / 2f
+        if (!dp.isLeftRightSplit) {
+            out.x = (onScreenRectCenterX / fullscreenScaleX - 1.0f * drawableWidth / 2)
+            out.y =
+                ((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY -
+                    1.0f * drawableHeight / 2)
+        } else {
+            if (pinToRight) {
+                out.x =
+                    ((onScreenRectCenterX - insetAdjustment) / fullscreenScaleX -
+                        1.0f * drawableWidth / 2)
+            } else {
+                out.x =
+                    ((onScreenRectCenterX + insetAdjustment) / fullscreenScaleX -
+                        1.0f * drawableWidth / 2)
+            }
+            out.y = (onScreenRectCenterY / fullscreenScaleY - 1.0f * drawableHeight / 2)
+        }
+    }
+
+    /**
+     * The split placeholder comes with a default inset to buffer the icon from the top of the
+     * screen. But if the device already has a large inset (from cutouts etc), use that instead.
+     */
+    private fun getPlaceholderSizeAdjustment(dp: DeviceProfile, pinToRight: Boolean): Int {
+        val insetThickness =
+            if (!dp.isLandscape) {
+                dp.insets.top
+            } else {
+                if (pinToRight) dp.insets.right else dp.insets.left
+            }
+        return max((insetThickness - dp.splitPlaceholderInset).toDouble(), 0.0).toInt()
+    }
+
+    override fun setSplitInstructionsParams(
+        out: View,
+        dp: DeviceProfile,
+        splitInstructionsHeight: Int,
+        splitInstructionsWidth: Int,
+    ) {
+        out.pivotX = 0f
+        out.pivotY = splitInstructionsHeight.toFloat()
+        out.rotation = degreesRotated
+        val distanceToEdge =
+            if (dp.isPhone) {
+                if (dp.isLandscape) {
+                    out.resources.getDimensionPixelSize(
+                        R.dimen.split_instructions_bottom_margin_phone_landscape
+                    )
+                } else {
+                    out.resources.getDimensionPixelSize(
+                        R.dimen.split_instructions_bottom_margin_phone_portrait
+                    )
+                }
+            } else {
+                dp.overviewActionsClaimedSpaceBelow
+            }
+
+        // Center the view in case of unbalanced insets on left or right of screen
+        val insetCorrectionX = (dp.insets.right - dp.insets.left) / 2
+        // Adjust for any insets on the bottom edge
+        val insetCorrectionY = dp.insets.bottom
+        out.translationX = insetCorrectionX.toFloat()
+        out.translationY = (-distanceToEdge + insetCorrectionY).toFloat()
+        val lp = out.layoutParams as FrameLayout.LayoutParams
+        lp.gravity = Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+        out.layoutParams = lp
+    }
+
+    override fun getFinalSplitPlaceholderBounds(
+        splitDividerSize: Int,
+        dp: DeviceProfile,
+        @StagePosition stagePosition: Int,
+        out1: Rect,
+        out2: Rect,
+    ) {
+        val screenHeight = dp.heightPx
+        val screenWidth = dp.widthPx
+        out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize)
+        out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight)
+        if (!dp.isLeftRightSplit) {
+            // Portrait - the window bounds are always top and bottom half
+            return
+        }
+
+        // Now we rotate the portrait rect depending on what side we want pinned
+        val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+        val postRotateScale = screenHeight.toFloat() / screenWidth
+
+        tmpMatrix.reset()
+        tmpMatrix.postRotate(if (pinToRight) 90f else 270f)
+        tmpMatrix.postTranslate(
+            (if (pinToRight) screenHeight else 0).toFloat(),
+            (if (pinToRight) 0 else screenWidth).toFloat(),
+        )
+        tmpMatrix.postScale(1 / postRotateScale, postRotateScale)
+
+        tmpRectF.set(out1)
+        tmpMatrix.mapRect(tmpRectF)
+        tmpRectF.roundOut(out1)
+
+        tmpRectF.set(out2)
+        tmpMatrix.mapRect(tmpRectF)
+        tmpRectF.roundOut(out2)
+    }
+
+    override fun setSplitTaskSwipeRect(
+        dp: DeviceProfile,
+        outRect: Rect,
+        splitInfo: SplitConfigurationOptions.SplitBounds,
+        desiredStagePosition: Int,
+    ) {
+        val topLeftTaskPercent = splitInfo.leftTopTaskPercent
+        val dividerBarPercent = splitInfo.dividerPercent
+
+        val taskbarHeight = if (dp.isTransientTaskbar) 0 else dp.taskbarHeight
+        val scale = outRect.height().toFloat() / (dp.availableHeightPx - taskbarHeight)
+        val topTaskHeight = dp.availableHeightPx * topLeftTaskPercent
+        val scaledTopTaskHeight = topTaskHeight * scale
+        val dividerHeight = dp.availableHeightPx * dividerBarPercent
+        val scaledDividerHeight = dividerHeight * scale
+
+        if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
+            if (dp.isLeftRightSplit) {
+                outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent)
+            } else {
+                outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight)
+            }
+        } else {
+            if (dp.isLeftRightSplit) {
+                outRect.left +=
+                    Math.round(outRect.width() * (topLeftTaskPercent + dividerBarPercent))
+            } else {
+                outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight)
+            }
+        }
+    }
+
+    /**
+     * @param inSplitSelection Whether user currently has a task from this task group staged for
+     *   split screen. If true, we have custom translations/scaling in place for the remaining
+     *   snapshot, so we'll skip setting translation/scale here.
+     */
+    override fun measureGroupedTaskViewThumbnailBounds(
+        primarySnapshot: View,
+        secondarySnapshot: View,
+        parentWidth: Int,
+        parentHeight: Int,
+        splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
+        dp: DeviceProfile,
+        isRtl: Boolean,
+        inSplitSelection: Boolean,
+    ) {
+        val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+
+        val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
+        val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
+
+        // Reset margins that aren't used in this method, but are used in other
+        // `RecentsPagedOrientationHandler` variants.
+        secondaryParams.topMargin = 0
+        primaryParams.topMargin = spaceAboveSnapshot
+
+        val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+        val dividerScale = splitBoundsConfig.dividerPercent
+        val taskViewSizes =
+            getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight)
+        if (!inSplitSelection) {
+            // Reset translations that aren't used in this method, but are used in other
+            // `RecentsPagedOrientationHandler` variants.
+            primarySnapshot.translationY = 0f
+
+            if (dp.isLeftRightSplit) {
+                val scaledDividerBar = Math.round(parentWidth * dividerScale)
+                if (isRtl) {
+                    val translationX = taskViewSizes.second.x + scaledDividerBar
+                    primarySnapshot.translationX = -translationX.toFloat()
+                    secondarySnapshot.translationX = 0f
+                } else {
+                    val translationX = taskViewSizes.first.x + scaledDividerBar
+                    secondarySnapshot.translationX = translationX.toFloat()
+                    primarySnapshot.translationX = 0f
+                }
+                secondarySnapshot.translationY = spaceAboveSnapshot.toFloat()
+            } else {
+                val finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale).toFloat()
+                val translationY = taskViewSizes.first.y + spaceAboveSnapshot + finalDividerHeight
+                secondarySnapshot.translationY = translationY
+
+                // Reset unused translations.
+                secondarySnapshot.translationX = 0f
+                primarySnapshot.translationX = 0f
+            }
+        }
+
+        primarySnapshot.measure(
+            View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.x, View.MeasureSpec.EXACTLY),
+            View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.y, View.MeasureSpec.EXACTLY),
+        )
+        secondarySnapshot.measure(
+            View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.x, View.MeasureSpec.EXACTLY),
+            View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.y, View.MeasureSpec.EXACTLY),
+        )
+    }
+
+    override fun getGroupedTaskViewSizes(
+        dp: DeviceProfile,
+        splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
+        parentWidth: Int,
+        parentHeight: Int,
+    ): Pair<Point, Point> {
+        val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+        val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+        val dividerScale = splitBoundsConfig.dividerPercent
+        val taskPercent = splitBoundsConfig.leftTopTaskPercent
+
+        val firstTaskViewSize = Point()
+        val secondTaskViewSize = Point()
+
+        if (dp.isLeftRightSplit) {
+            val scaledDividerBar = Math.round(parentWidth * dividerScale)
+            firstTaskViewSize.x = Math.round(parentWidth * taskPercent)
+            firstTaskViewSize.y = totalThumbnailHeight
+
+            secondTaskViewSize.x = parentWidth - firstTaskViewSize.x - scaledDividerBar
+            secondTaskViewSize.y = totalThumbnailHeight
+        } else {
+            val taskbarHeight = if (dp.isTransientTaskbar) 0 else dp.taskbarHeight
+            val scale = totalThumbnailHeight.toFloat() / (dp.availableHeightPx - taskbarHeight)
+            val topTaskHeight = dp.availableHeightPx * taskPercent
+            val finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale).toFloat()
+            val scaledTopTaskHeight = topTaskHeight * scale
+            firstTaskViewSize.x = parentWidth
+            firstTaskViewSize.y = Math.round(scaledTopTaskHeight)
+
+            secondTaskViewSize.x = parentWidth
+            secondTaskViewSize.y =
+                Math.round((totalThumbnailHeight - firstTaskViewSize.y - finalDividerHeight))
+        }
+
+        return Pair(firstTaskViewSize, secondTaskViewSize)
+    }
+
+    override fun setTaskIconParams(
+        iconParams: FrameLayout.LayoutParams,
+        taskIconMargin: Int,
+        taskIconHeight: Int,
+        thumbnailTopMargin: Int,
+        isRtl: Boolean,
+    ) {
+        iconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+        // Reset margins, since they may have been set on rotation
+        iconParams.rightMargin = 0
+        iconParams.leftMargin = iconParams.rightMargin
+        iconParams.bottomMargin = 0
+        iconParams.topMargin = iconParams.bottomMargin
+    }
+
+    override fun setIconAppChipChildrenParams(
+        iconParams: FrameLayout.LayoutParams,
+        chipChildMarginStart: Int,
+    ) {
+        iconParams.marginStart = chipChildMarginStart
+        iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
+        iconParams.topMargin = 0
+    }
+
+    override fun setIconAppChipMenuParams(
+        iconAppChipView: IconAppChipView,
+        iconMenuParams: FrameLayout.LayoutParams,
+        iconMenuMargin: Int,
+        thumbnailTopMargin: Int,
+    ) {
+        iconMenuParams.gravity = Gravity.TOP or Gravity.START
+        iconMenuParams.marginStart = iconMenuMargin
+        iconMenuParams.topMargin = thumbnailTopMargin
+        iconMenuParams.bottomMargin = 0
+        iconMenuParams.marginEnd = 0
+
+        iconAppChipView.pivotX = 0f
+        iconAppChipView.pivotY = 0f
+        iconAppChipView.setSplitTranslationY(0f)
+        iconAppChipView.rotation = degreesRotated
+    }
+
+    /**
+     * @param inSplitSelection Whether user currently has a task from this task group staged for
+     *   split screen. If true, we have custom translations in place for the remaining icon, so
+     *   we'll skip setting translations here.
+     */
+    override fun setSplitIconParams(
+        primaryIconView: View,
+        secondaryIconView: View,
+        taskIconHeight: Int,
+        primarySnapshotWidth: Int,
+        primarySnapshotHeight: Int,
+        groupedTaskViewHeight: Int,
+        groupedTaskViewWidth: Int,
+        isRtl: Boolean,
+        deviceProfile: DeviceProfile,
+        splitConfig: SplitConfigurationOptions.SplitBounds,
+        inSplitSelection: Boolean,
+        oneIconHiddenDueToSmallWidth: Boolean,
+    ) {
+        val primaryIconParams = primaryIconView.layoutParams as FrameLayout.LayoutParams
+        val secondaryIconParams =
+            if (enableOverviewIconMenu()) secondaryIconView.layoutParams as FrameLayout.LayoutParams
+            else FrameLayout.LayoutParams(primaryIconParams)
+
+        if (enableOverviewIconMenu()) {
+            val primaryAppChipView = primaryIconView as IconAppChipView
+            val secondaryAppChipView = secondaryIconView as IconAppChipView
+            primaryIconParams.gravity = Gravity.TOP or Gravity.START
+            secondaryIconParams.gravity = Gravity.TOP or Gravity.START
+            secondaryIconParams.topMargin = primaryIconParams.topMargin
+            secondaryIconParams.marginStart = primaryIconParams.marginStart
+            if (!inSplitSelection) {
+                if (deviceProfile.isLeftRightSplit) {
+                    if (isRtl) {
+                        val secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth
+                        primaryAppChipView.setSplitTranslationX(-secondarySnapshotWidth.toFloat())
+                    } else {
+                        secondaryAppChipView.setSplitTranslationX(primarySnapshotWidth.toFloat())
+                    }
+                } else {
+                    primaryAppChipView.setSplitTranslationX(0f)
+                    secondaryAppChipView.setSplitTranslationX(0f)
+                    val dividerThickness =
+                        min(
+                                splitConfig.visualDividerBounds.width().toDouble(),
+                                splitConfig.visualDividerBounds.height().toDouble(),
+                            )
+                            .toInt()
+                    secondaryAppChipView.setSplitTranslationY(
+                        (primarySnapshotHeight +
+                                (if (deviceProfile.isTablet) 0 else dividerThickness))
+                            .toFloat()
+                    )
+                }
+            }
+        } 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.
+            val fullscreenInsetThickness =
+                if (deviceProfile.isSeascape) deviceProfile.insets.right
+                else deviceProfile.insets.left
+            val fullscreenMidpointFromBottom =
+                ((deviceProfile.widthPx - fullscreenInsetThickness) / 2)
+            val midpointFromEndPct = fullscreenMidpointFromBottom.toFloat() / deviceProfile.widthPx
+            val insetPct = fullscreenInsetThickness.toFloat() / deviceProfile.widthPx
+            val spaceAboveSnapshots = 0
+            val overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots
+            val bottomToMidpointOffset =
+                (overviewThumbnailAreaThickness * midpointFromEndPct).toInt()
+            val insetOffset = (overviewThumbnailAreaThickness * insetPct).toInt()
+
+            if (deviceProfile.isSeascape) {
+                primaryIconParams.gravity =
+                    Gravity.TOP or (if (isRtl) Gravity.END else Gravity.START)
+                secondaryIconParams.gravity =
+                    Gravity.TOP or (if (isRtl) Gravity.END else Gravity.START)
+                if (!inSplitSelection) {
+                    if (splitConfig.initiatedFromSeascape) {
+                        if (oneIconHiddenDueToSmallWidth) {
+                            // Center both icons
+                            val centerX = bottomToMidpointOffset - (taskIconHeight / 2f)
+                            primaryIconView.translationX = centerX
+                            secondaryIconView.translationX = centerX
+                        } else {
+                            // the task on the right (secondary) is slightly larger
+                            primaryIconView.translationX =
+                                (bottomToMidpointOffset - taskIconHeight).toFloat()
+                            secondaryIconView.translationX = bottomToMidpointOffset.toFloat()
+                        }
+                    } else {
+                        if (oneIconHiddenDueToSmallWidth) {
+                            // Center both icons
+                            val centerX =
+                                bottomToMidpointOffset + insetOffset - (taskIconHeight / 2f)
+                            primaryIconView.translationX = centerX
+                            secondaryIconView.translationX = centerX
+                        } else {
+                            // the task on the left (primary) is slightly larger
+                            primaryIconView.translationX =
+                                (bottomToMidpointOffset + insetOffset - taskIconHeight).toFloat()
+                            secondaryIconView.translationX =
+                                (bottomToMidpointOffset + insetOffset).toFloat()
+                        }
+                    }
+                }
+            } else {
+                primaryIconParams.gravity =
+                    Gravity.TOP or (if (isRtl) Gravity.START else Gravity.END)
+                secondaryIconParams.gravity =
+                    Gravity.TOP or (if (isRtl) Gravity.START else Gravity.END)
+                if (!inSplitSelection) {
+                    if (!splitConfig.initiatedFromSeascape) {
+                        if (oneIconHiddenDueToSmallWidth) {
+                            // Center both icons
+                            val centerX = -bottomToMidpointOffset + (taskIconHeight / 2f)
+                            primaryIconView.translationX = centerX
+                            secondaryIconView.translationX = centerX
+                        } else {
+                            // the task on the left (primary) is slightly larger
+                            primaryIconView.translationX = -bottomToMidpointOffset.toFloat()
+                            secondaryIconView.translationX =
+                                (-bottomToMidpointOffset + taskIconHeight).toFloat()
+                        }
+                    } else {
+                        if (oneIconHiddenDueToSmallWidth) {
+                            // Center both icons
+                            val centerX =
+                                -bottomToMidpointOffset - insetOffset + (taskIconHeight / 2f)
+                            primaryIconView.translationX = centerX
+                            secondaryIconView.translationX = centerX
+                        } else {
+                            // the task on the right (secondary) is slightly larger
+                            primaryIconView.translationX =
+                                (-bottomToMidpointOffset - insetOffset).toFloat()
+                            secondaryIconView.translationX =
+                                (-bottomToMidpointOffset - insetOffset + taskIconHeight).toFloat()
+                        }
+                    }
+                }
+            }
+        } else {
+            primaryIconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+            secondaryIconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+            if (!inSplitSelection) {
+                if (oneIconHiddenDueToSmallWidth) {
+                    // Center both icons
+                    primaryIconView.translationX = 0f
+                    secondaryIconView.translationX = 0f
+                } else {
+                    // shifts icon half a width left (height is used here since icons are square)
+                    primaryIconView.translationX = -(taskIconHeight / 2f)
+                    secondaryIconView.translationX = taskIconHeight / 2f
+                }
+            }
+        }
+        if (!enableOverviewIconMenu() && !inSplitSelection) {
+            primaryIconView.translationY = 0f
+            secondaryIconView.translationY = 0f
+        }
+
+        primaryIconView.layoutParams = primaryIconParams
+        secondaryIconView.layoutParams = secondaryIconParams
+    }
+
+    override fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int {
+        check(deviceProfile.isTablet) { "Default position available only for large screens" }
+        return if (deviceProfile.isLeftRightSplit) {
+            SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+        } else {
+            SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+        }
+    }
+
+    override fun <T> getSplitSelectTaskOffset(
+        primary: FloatProperty<T>,
+        secondary: FloatProperty<T>,
+        deviceProfile: DeviceProfile,
+    ): Pair<FloatProperty<T>, FloatProperty<T>> =
+        if (deviceProfile.isLeftRightSplit) { // or seascape
+            Pair(primary, secondary)
+        } else {
+            Pair(secondary, primary)
+        }
+
+    override fun getFloatingTaskOffscreenTranslationTarget(
+        floatingTask: View,
+        onScreenRect: RectF,
+        @StagePosition stagePosition: Int,
+        dp: DeviceProfile,
+    ): Float {
+        if (dp.isLeftRightSplit) {
+            val currentTranslationX = floatingTask.translationX
+            return if (stagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT)
+                currentTranslationX - onScreenRect.width()
+            else currentTranslationX + onScreenRect.width()
+        } else {
+            val currentTranslationY = floatingTask.translationY
+            return currentTranslationY - onScreenRect.height()
+        }
+    }
+
+    override fun setFloatingTaskPrimaryTranslation(
+        floatingTask: View,
+        translation: Float,
+        dp: DeviceProfile,
+    ) {
+        if (dp.isLeftRightSplit) {
+            floatingTask.translationX = translation
+        } else {
+            floatingTask.translationY = translation
+        }
+    }
+
+    override fun getFloatingTaskPrimaryTranslation(floatingTask: View, dp: DeviceProfile): Float =
+        if (dp.isLeftRightSplit) floatingTask.translationX else floatingTask.translationY
+
+    override fun getHandlerTypeForLogging(): LauncherAtom.TaskSwitcherContainer.OrientationHandler =
+        LauncherAtom.TaskSwitcherContainer.OrientationHandler.PORTRAIT
+}
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
index 0cb983d..7fea55d 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -53,7 +53,7 @@
 
     override fun getSplitTranslationDirectionFactor(
         stagePosition: Int,
-        deviceProfile: DeviceProfile
+        deviceProfile: DeviceProfile,
     ): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1
 
     override fun getRecentsRtlSetting(resources: Resources): Boolean = Utilities.isRtl(resources)
@@ -70,7 +70,7 @@
         thumbnailView: View,
         deviceProfile: DeviceProfile,
         taskInsetMargin: Float,
-        taskViewIcon: View
+        taskViewIcon: View,
     ): Float = x + taskInsetMargin
 
     override fun getTaskMenuY(
@@ -79,7 +79,7 @@
         stagePosition: Int,
         taskMenuView: View,
         taskInsetMargin: Float,
-        taskViewIcon: View
+        taskViewIcon: View,
     ): Float {
         if (Flags.enableOverviewIconMenu()) {
             return y
@@ -97,14 +97,14 @@
         taskInsetMargin: Float,
         deviceProfile: DeviceProfile,
         taskMenuX: Float,
-        taskMenuY: Float
+        taskMenuY: Float,
     ): Int = (deviceProfile.availableWidthPx - taskInsetMargin - taskMenuX).toInt()
 
     override fun setSplitTaskSwipeRect(
         dp: DeviceProfile,
         outRect: Rect,
         splitInfo: SplitBounds,
-        desiredStagePosition: Int
+        desiredStagePosition: Int,
     ) {
         val topLeftTaskPercent = splitInfo.leftTopTaskPercent
         val dividerBarPercent = splitInfo.dividerPercent
@@ -126,7 +126,7 @@
         deviceProfile: DeviceProfile,
         snapshotViewWidth: Int,
         snapshotViewHeight: Int,
-        banner: View
+        banner: View,
     ) {
         banner.pivotX = 0f
         banner.pivotY = 0f
@@ -149,7 +149,7 @@
         deviceProfile: DeviceProfile,
         thumbnailViews: Array<View>,
         desiredTaskId: Int,
-        banner: View
+        banner: View,
     ): Pair<Float, Float> {
         val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
         val translationX: Float = (taskViewWidth - banner.height).toFloat()
@@ -181,7 +181,7 @@
                 R.drawable.ic_split_horizontal,
                 R.string.recent_task_option_split_screen,
                 STAGE_POSITION_BOTTOM_OR_RIGHT,
-                STAGE_TYPE_MAIN
+                STAGE_TYPE_MAIN,
             )
         )
 
@@ -189,7 +189,7 @@
         out: View,
         dp: DeviceProfile,
         splitInstructionsHeight: Int,
-        splitInstructionsWidth: Int
+        splitInstructionsWidth: Int,
     ) {
         out.pivotX = 0f
         out.pivotY = splitInstructionsHeight.toFloat()
@@ -217,7 +217,7 @@
         taskIconMargin: Int,
         taskIconHeight: Int,
         thumbnailTopMargin: Int,
-        isRtl: Boolean
+        isRtl: Boolean,
     ) {
         iconParams.gravity =
             if (isRtl) {
@@ -230,7 +230,7 @@
 
     override fun setIconAppChipChildrenParams(
         iconParams: FrameLayout.LayoutParams,
-        chipChildMarginStart: Int
+        chipChildMarginStart: Int,
     ) {
         iconParams.setMargins(0, 0, 0, 0)
         iconParams.marginStart = chipChildMarginStart
@@ -241,7 +241,7 @@
         iconAppChipView: IconAppChipView,
         iconMenuParams: FrameLayout.LayoutParams,
         iconMenuMargin: Int,
-        thumbnailTopMargin: Int
+        thumbnailTopMargin: Int,
     ) {
         val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL
         val iconCenter = iconAppChipView.getHeight() / 2f
@@ -268,7 +268,7 @@
 
     /**
      * @param inSplitSelection Whether user currently has a task from this task group staged for
-     * split screen. Currently this state is not reachable in fake seascape.
+     *   split screen. Currently this state is not reachable in fake seascape.
      */
     override fun measureGroupedTaskViewThumbnailBounds(
         primarySnapshot: View,
@@ -278,7 +278,7 @@
         splitBoundsConfig: SplitBounds,
         dp: DeviceProfile,
         isRtl: Boolean,
-        inSplitSelection: Boolean
+        inSplitSelection: Boolean,
     ) {
         val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
         val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
@@ -300,11 +300,11 @@
             (taskViewSecond.y + spaceAboveSnapshot + dividerBar).toFloat()
         primarySnapshot.measure(
             MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
-            MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY)
+            MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY),
         )
         secondarySnapshot.measure(
             MeasureSpec.makeMeasureSpec(taskViewSecond.x, MeasureSpec.EXACTLY),
-            MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY)
+            MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY),
         )
     }
 
@@ -312,7 +312,7 @@
         dp: DeviceProfile,
         splitBoundsConfig: SplitBounds,
         parentWidth: Int,
-        parentHeight: Int
+        parentHeight: Int,
     ): Pair<Point, Point> {
         // Measure and layout the thumbnails bottom up, since the primary is on the visual left
         // (portrait bottom) and secondary is on the right (portrait top)
@@ -376,10 +376,7 @@
             if (oneIconHiddenDueToSmallWidth) {
                 // Center both icons
                 val centerY = primarySnapshotHeight + ((dividerSize - taskIconHeight) / 2)
-                SplitIconPositions(
-                    topLeftY = centerY,
-                    bottomRightY = centerY,
-                )
+                SplitIconPositions(topLeftY = centerY, bottomRightY = centerY)
             } else {
                 SplitIconPositions(
                     topLeftY = primarySnapshotHeight - taskIconHeight,
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index a9dbbf2..96a5733 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -143,7 +143,7 @@
         } else {
             // Initiating split from overview on fullscreen task TaskView
             val taskView = taskViewSupplier.get()
-            taskView.taskContainers.first().let {
+            taskView.firstTaskContainer!!.let {
                 val drawable = getDrawable(it.iconView, splitSelectSource)
                 return SplitAnimInitProps(
                     it.snapshotView,
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
deleted file mode 100644
index 2426697..0000000
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.views;
-
-import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.FloatProperty;
-import android.widget.Button;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.Flags;
-import com.android.launcher3.R;
-import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.util.BorderAnimator;
-
-import kotlin.Unit;
-
-public class ClearAllButton extends Button {
-
-    public static final FloatProperty<ClearAllButton> VISIBILITY_ALPHA =
-            new FloatProperty<ClearAllButton>("visibilityAlpha") {
-                @Override
-                public Float get(ClearAllButton view) {
-                    return view.mVisibilityAlpha;
-                }
-
-                @Override
-                public void setValue(ClearAllButton view, float v) {
-                    view.setVisibilityAlpha(v);
-                }
-            };
-
-    public static final FloatProperty<ClearAllButton> DISMISS_ALPHA =
-            new FloatProperty<ClearAllButton>("dismissAlpha") {
-                @Override
-                public Float get(ClearAllButton view) {
-                    return view.mDismissAlpha;
-                }
-
-                @Override
-                public void setValue(ClearAllButton view, float v) {
-                    view.setDismissAlpha(v);
-                }
-            };
-
-    private float mScrollAlpha = 1;
-    private float mContentAlpha = 1;
-    private float mVisibilityAlpha = 1;
-    private float mDismissAlpha = 1;
-    private float mFullscreenProgress = 1;
-    private float mGridProgress = 1;
-
-    private boolean mIsRtl;
-    private float mNormalTranslationPrimary;
-    private float mFullscreenTranslationPrimary;
-    private float mGridTranslationPrimary;
-    private float mTaskAlignmentTranslationY;
-    private float mGridScrollOffset;
-    private float mScrollOffsetPrimary;
-
-    private int mSidePadding;
-    private int mOutlinePadding;
-    private boolean mBorderEnabled;
-    @Nullable
-    private final BorderAnimator mFocusBorderAnimator;
-
-    public ClearAllButton(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
-
-        if (Flags.enableFocusOutline()) {
-            TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
-                    R.styleable.ClearAllButton);
-            Resources resources = getResources();
-            mOutlinePadding = resources.getDimensionPixelSize(
-                    R.dimen.recents_clear_all_outline_padding);
-            mFocusBorderAnimator =
-                    BorderAnimator.createSimpleBorderAnimator(
-                            /* borderRadiusPx= */ resources.getDimensionPixelSize(
-                                    R.dimen.recents_clear_all_outline_radius),
-                            /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
-                                    R.dimen.keyboard_quick_switch_border_width),
-                            /* boundsBuilder= */ this::updateBorderBounds,
-                            /* targetView= */ this,
-                            /* borderColor= */ styledAttrs.getColor(
-                                    R.styleable.ClearAllButton_focusBorderColor,
-                                    DEFAULT_BORDER_COLOR));
-            styledAttrs.recycle();
-        } else {
-            mFocusBorderAnimator = null;
-        }
-    }
-
-    private Unit updateBorderBounds(@NonNull Rect bounds) {
-        bounds.set(0, 0, getWidth(), getHeight());
-        // Make the value negative to form a padding between button and outline
-        bounds.inset(-mOutlinePadding, -mOutlinePadding);
-        return Unit.INSTANCE;
-    }
-
-    @Override
-    public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
-        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-        if (mFocusBorderAnimator != null && mBorderEnabled) {
-            mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
-        }
-    }
-
-    /**
-     * Enable or disable showing border on focus change
-     */
-    public void setBorderEnabled(boolean enabled) {
-        if (mBorderEnabled == enabled) {
-            return;
-        }
-
-        mBorderEnabled = enabled;
-        if (mFocusBorderAnimator != null) {
-            mFocusBorderAnimator.setBorderVisibility(/* visible= */
-                    enabled && isFocused(), /* animated= */true);
-        }
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        if (mFocusBorderAnimator != null) {
-            mFocusBorderAnimator.drawBorder(canvas);
-        }
-        super.draw(canvas);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        RecentsPagedOrientationHandler orientationHandler =
-                getRecentsView().getPagedOrientationHandler();
-        mSidePadding = orientationHandler.getClearAllSidePadding(getRecentsView(), mIsRtl);
-    }
-
-    private RecentsView getRecentsView() {
-        return (RecentsView) getParent();
-    }
-
-    @Override
-    public void onRtlPropertiesChanged(int layoutDirection) {
-        super.onRtlPropertiesChanged(layoutDirection);
-        mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return false;
-    }
-
-    public float getScrollAlpha() {
-        return mScrollAlpha;
-    }
-
-    public void setContentAlpha(float alpha) {
-        if (mContentAlpha != alpha) {
-            mContentAlpha = alpha;
-            updateAlpha();
-        }
-    }
-
-    public void setVisibilityAlpha(float alpha) {
-        if (mVisibilityAlpha != alpha) {
-            mVisibilityAlpha = alpha;
-            updateAlpha();
-        }
-    }
-
-    public void setDismissAlpha(float alpha) {
-        if (mDismissAlpha != alpha) {
-            mDismissAlpha = alpha;
-            updateAlpha();
-        }
-    }
-
-    public void onRecentsViewScroll(int scroll, boolean gridEnabled) {
-        RecentsView recentsView = getRecentsView();
-        if (recentsView == null) {
-            return;
-        }
-
-        RecentsPagedOrientationHandler orientationHandler =
-                recentsView.getPagedOrientationHandler();
-        float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
-        if (orientationSize == 0) {
-            return;
-        }
-
-        int clearAllScroll = recentsView.getClearAllScroll();
-        int adjustedScrollFromEdge = Math.abs(scroll - clearAllScroll);
-        float shift = Math.min(adjustedScrollFromEdge, orientationSize);
-        mNormalTranslationPrimary = mIsRtl ? -shift : shift;
-        if (!gridEnabled) {
-            mNormalTranslationPrimary += mSidePadding;
-        }
-        applyPrimaryTranslation();
-        applySecondaryTranslation();
-        float clearAllSpacing =
-                recentsView.getPageSpacing() + recentsView.getClearAllExtraPageSpacing();
-        clearAllSpacing = mIsRtl ? -clearAllSpacing : clearAllSpacing;
-        mScrollAlpha = Math.max((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing, 0);
-        updateAlpha();
-    }
-
-    private void updateAlpha() {
-        final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha * mDismissAlpha;
-        setAlpha(alpha);
-        setClickable(Math.min(alpha, 1) == 1);
-    }
-
-    public void setFullscreenTranslationPrimary(float fullscreenTranslationPrimary) {
-        mFullscreenTranslationPrimary = fullscreenTranslationPrimary;
-        applyPrimaryTranslation();
-    }
-
-    /**
-     * Sets `mTaskAlignmentTranslationY` to the given `value`. In order to put the button at the
-     * middle in the secondary coordinate.
-     */
-    public void setTaskAlignmentTranslationY(float value) {
-        mTaskAlignmentTranslationY = value;
-        applySecondaryTranslation();
-    }
-
-    public void setGridTranslationPrimary(float gridTranslationPrimary) {
-        mGridTranslationPrimary = gridTranslationPrimary;
-        applyPrimaryTranslation();
-    }
-
-    public void setGridScrollOffset(float gridScrollOffset) {
-        mGridScrollOffset = gridScrollOffset;
-    }
-
-    public void setScrollOffsetPrimary(float scrollOffsetPrimary) {
-        mScrollOffsetPrimary = scrollOffsetPrimary;
-    }
-
-    public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
-        float scrollAdjustment = 0;
-        if (fullscreenEnabled) {
-            scrollAdjustment += mFullscreenTranslationPrimary;
-        }
-        if (gridEnabled) {
-            scrollAdjustment += mGridTranslationPrimary + mGridScrollOffset;
-        }
-        scrollAdjustment += mScrollOffsetPrimary;
-        return scrollAdjustment;
-    }
-
-    public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
-        return getScrollAdjustment(fullscreenEnabled, gridEnabled);
-    }
-
-    /**
-     * Adjust translation when this TaskView is about to be shown fullscreen.
-     *
-     * @param progress: 0 = no translation; 1 = translate according to TaskVIew translations.
-     */
-    public void setFullscreenProgress(float progress) {
-        mFullscreenProgress = progress;
-        applyPrimaryTranslation();
-    }
-
-    /**
-     * Moves ClearAllButton between carousel and 2 row grid.
-     *
-     * @param gridProgress 0 = carousel; 1 = 2 row grid.
-     */
-    public void setGridProgress(float gridProgress) {
-        mGridProgress = gridProgress;
-        applyPrimaryTranslation();
-    }
-
-    private void applyPrimaryTranslation() {
-        RecentsView recentsView = getRecentsView();
-        if (recentsView == null) {
-            return;
-        }
-
-        RecentsPagedOrientationHandler orientationHandler =
-                recentsView.getPagedOrientationHandler();
-        orientationHandler.getPrimaryViewTranslate().set(this,
-                orientationHandler.getPrimaryValue(0f, mTaskAlignmentTranslationY)
-                        + mNormalTranslationPrimary + getFullscreenTrans(
-                        mFullscreenTranslationPrimary) + getGridTrans(mGridTranslationPrimary));
-    }
-
-    private void applySecondaryTranslation() {
-        RecentsView recentsView = getRecentsView();
-        if (recentsView == null) {
-            return;
-        }
-
-        RecentsPagedOrientationHandler orientationHandler =
-                recentsView.getPagedOrientationHandler();
-        orientationHandler.getSecondaryViewTranslate().set(this,
-                orientationHandler.getSecondaryValue(0f, mTaskAlignmentTranslationY));
-    }
-
-    private float getFullscreenTrans(float endTranslation) {
-        return mFullscreenProgress > 0 ? endTranslation : 0;
-    }
-
-    private float getGridTrans(float endTranslation) {
-        return mGridProgress > 0 ? endTranslation : 0;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.kt b/quickstep/src/com/android/quickstep/views/ClearAllButton.kt
new file mode 100644
index 0000000..6420ece
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.View
+import android.widget.Button
+import com.android.launcher3.Flags.enableFocusOutline
+import com.android.launcher3.R
+import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.MultiValueAlpha
+import com.android.quickstep.util.BorderAnimator
+import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
+import kotlin.math.abs
+import kotlin.math.min
+
+class ClearAllButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+    Button(context, attrs) {
+
+    private enum class Alpha {
+        SCROLL,
+        CONTENT,
+        VISIBILITY,
+        DISMISS,
+    }
+
+    private val clearAllButtonAlpha =
+        object : MultiValueAlpha(this, Alpha.entries.size) {
+            override fun apply(value: Float) {
+                super.apply(value)
+                isClickable = value >= 1f
+            }
+        }
+
+    var scrollAlpha
+        set(value) {
+            clearAllButtonAlpha.get(Alpha.SCROLL.ordinal).value = value
+        }
+        get() = clearAllButtonAlpha.get(Alpha.SCROLL.ordinal).value
+
+    var contentAlpha
+        set(value) {
+            clearAllButtonAlpha.get(Alpha.CONTENT.ordinal).value = value
+        }
+        get() = clearAllButtonAlpha.get(Alpha.CONTENT.ordinal).value
+
+    @JvmField
+    val visibilityAlphaProperty: MultiPropertyFactory<View>.MultiProperty =
+        clearAllButtonAlpha.get(Alpha.VISIBILITY.ordinal)
+
+    var dismissAlpha
+        set(value) {
+            dismissAlphaProperty.value = value
+        }
+        get() = dismissAlphaProperty.value
+
+    @JvmField
+    val dismissAlphaProperty: MultiPropertyFactory<View>.MultiProperty =
+        clearAllButtonAlpha.get(Alpha.DISMISS.ordinal)
+
+    var fullscreenProgress = 1f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            applyPrimaryTranslation()
+        }
+
+    /**
+     * Moves ClearAllButton between carousel and 2 row grid.
+     *
+     * 0 = carousel; 1 = 2 row grid.
+     */
+    var gridProgress = 1f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            applyPrimaryTranslation()
+        }
+
+    private var normalTranslationPrimary = 0f
+    var fullscreenTranslationPrimary = 0f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            applyPrimaryTranslation()
+        }
+
+    var gridTranslationPrimary = 0f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            applyPrimaryTranslation()
+        }
+
+    /** Used to put the button at the middle in the secondary coordinate. */
+    var taskAlignmentTranslationY = 0f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            applySecondaryTranslation()
+        }
+
+    var gridScrollOffset = 0f
+    var scrollOffsetPrimary = 0f
+
+    private var sidePadding = 0
+    var borderEnabled = false
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true)
+        }
+
+    private val focusBorderAnimator: BorderAnimator? =
+        if (enableFocusOutline())
+            createSimpleBorderAnimator(
+                context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_radius),
+                context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width),
+                this::getBorderBounds,
+                this,
+                context
+                    .obtainStyledAttributes(attrs, R.styleable.ClearAllButton)
+                    .getColor(
+                        R.styleable.ClearAllButton_focusBorderColor,
+                        BorderAnimator.DEFAULT_BORDER_COLOR,
+                    ),
+            )
+        else null
+
+    private fun getBorderBounds(bounds: Rect) {
+        bounds.set(0, 0, width, height)
+        val outlinePadding =
+            context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_padding)
+        // Make the value negative to form a padding between button and outline
+        bounds.inset(-outlinePadding, -outlinePadding)
+    }
+
+    public override fun onFocusChanged(
+        gainFocus: Boolean,
+        direction: Int,
+        previouslyFocusedRect: Rect?,
+    ) {
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
+        if (borderEnabled) {
+            focusBorderAnimator?.setBorderVisibility(gainFocus, /* animated= */ true)
+        }
+    }
+
+    override fun draw(canvas: Canvas) {
+        focusBorderAnimator?.drawBorder(canvas)
+        super.draw(canvas)
+    }
+
+    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+        super.onLayout(changed, left, top, right, bottom)
+        sidePadding =
+            recentsView?.let { it.pagedOrientationHandler?.getClearAllSidePadding(it, isLayoutRtl) }
+                ?: 0
+    }
+
+    private val recentsView: RecentsView<*, *>?
+        get() = parent as? RecentsView<*, *>?
+
+    override fun hasOverlappingRendering() = false
+
+    fun onRecentsViewScroll(scroll: Int, gridEnabled: Boolean) {
+        val recentsView = recentsView ?: return
+
+        val orientationSize =
+            recentsView.pagedOrientationHandler.getPrimaryValue(width, height).toFloat()
+        if (orientationSize == 0f) {
+            return
+        }
+
+        val clearAllScroll = recentsView.clearAllScroll
+        val adjustedScrollFromEdge = abs((scroll - clearAllScroll)).toFloat()
+        val shift = min(adjustedScrollFromEdge, orientationSize)
+        normalTranslationPrimary = if (isLayoutRtl) -shift else shift
+        if (!gridEnabled) {
+            normalTranslationPrimary += sidePadding.toFloat()
+        }
+        applyPrimaryTranslation()
+        applySecondaryTranslation()
+        var clearAllSpacing = recentsView.pageSpacing + recentsView.clearAllExtraPageSpacing
+        clearAllSpacing = if (isLayoutRtl) -clearAllSpacing else clearAllSpacing
+        scrollAlpha =
+            ((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing.toFloat()).coerceAtLeast(
+                0f
+            )
+    }
+
+    fun getScrollAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean): Float {
+        var scrollAdjustment = 0f
+        if (fullscreenEnabled) {
+            scrollAdjustment += fullscreenTranslationPrimary
+        }
+        if (gridEnabled) {
+            scrollAdjustment += gridTranslationPrimary + gridScrollOffset
+        }
+        scrollAdjustment += scrollOffsetPrimary
+        return scrollAdjustment
+    }
+
+    fun getOffsetAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean) =
+        getScrollAdjustment(fullscreenEnabled, gridEnabled)
+
+    private fun applyPrimaryTranslation() {
+        val recentsView = recentsView ?: return
+        val orientationHandler = recentsView.pagedOrientationHandler
+        orientationHandler.primaryViewTranslate.set(
+            this,
+            (orientationHandler.getPrimaryValue(0f, taskAlignmentTranslationY) +
+                normalTranslationPrimary +
+                getFullscreenTrans(fullscreenTranslationPrimary) +
+                getGridTrans(gridTranslationPrimary)),
+        )
+    }
+
+    private fun applySecondaryTranslation() {
+        val recentsView = recentsView ?: return
+        val orientationHandler = recentsView.pagedOrientationHandler
+        orientationHandler.secondaryViewTranslate.set(
+            this,
+            orientationHandler.getSecondaryValue(0f, taskAlignmentTranslationY),
+        )
+    }
+
+    private fun getFullscreenTrans(endTranslation: Float) =
+        if (fullscreenProgress > 0) endTranslation else 0f
+
+    private fun getGridTrans(endTranslation: Float) = if (gridProgress > 0) endTranslation else 0f
+}
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
index 85d14cc..8d53552 100644
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
@@ -296,7 +296,7 @@
     fun getMenuTranslationY(): MultiPropertyFactory<View>.MultiProperty =
         viewTranslationY[INDEX_MENU_TRANSLATION]
 
-    internal fun revealAnim(isRevealing: Boolean) {
+    internal fun revealAnim(isRevealing: Boolean, animated: Boolean = true) {
         cancelInProgressAnimations()
         val collapsedBackgroundBounds = getCollapsedBackgroundLtrBounds()
         val expandedBackgroundBounds = getExpandedBackgroundLtrBounds()
@@ -392,6 +392,7 @@
             animator!!.setDuration(MENU_BACKGROUND_HIDE_DURATION.toLong())
         }
 
+        if (!animated) animator!!.duration = 0
         animator!!.interpolator = Interpolators.EMPHASIZED
         animator!!.start()
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 5909bf9..e186838 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -66,7 +66,6 @@
 import static com.android.quickstep.BaseContainerInterface.getTaskDimension;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 import static com.android.quickstep.util.LogUtils.splitFailureMessage;
-import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_ACTIONS_IN_MENU;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_DESKTOP;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
@@ -3975,7 +3974,8 @@
 
                 // Change alpha of clear all if translating grid to hide it
                 if (isClearAllHidden) {
-                    anim.setFloat(mClearAllButton, DISMISS_ALPHA, 0, LINEAR);
+                    anim.setFloat(mClearAllButton.dismissAlphaProperty, MULTI_PROPERTY_VALUE, 0,
+                            LINEAR);
                     anim.addListener(new AnimatorListenerAdapter() {
                         @Override
                         public void onAnimationEnd(Animator animation) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.kt b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
index 95336cf..4777f4f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
@@ -75,7 +75,7 @@
         if (ev.action == MotionEvent.ACTION_DOWN) {
             if (!recentsViewContainer.dragLayer.isEventOverView(this, ev)) {
                 // TODO: log this once we have a new container type for it?
-                close(true)
+                animateOpenOrClosed(true)
                 return true
             }
         }
@@ -83,7 +83,7 @@
     }
 
     override fun handleClose(animate: Boolean) {
-        animateClose()
+        animateOpenOrClosed(true, animated = false)
     }
 
     override fun isOfType(type: Int): Boolean = (type and TYPE_TASK_MENU) != 0
@@ -260,11 +260,7 @@
     private val iconView: View
         get() = taskContainer.iconView.asView()
 
-    private fun animateClose() {
-        animateOpenOrClosed(true)
-    }
-
-    private fun animateOpenOrClosed(closing: Boolean) {
+    private fun animateOpenOrClosed(closing: Boolean, animated: Boolean = true) {
         openCloseAnimator?.let { if (it.isRunning) it.cancel() }
         openCloseAnimator = AnimatorSet()
         // If we're opening, we just start from the beginning as a new `TaskMenuView` is created
@@ -312,7 +308,12 @@
                 }
             }
         )
-        val animationDuration = if (closing) REVEAL_CLOSE_DURATION else REVEAL_OPEN_DURATION
+        val animationDuration =
+            when {
+                animated && closing -> REVEAL_CLOSE_DURATION
+                animated && !closing -> REVEAL_OPEN_DURATION
+                else -> 0L
+            }
         openCloseAnimator!!.setDuration(animationDuration)
         openCloseAnimator!!.start()
     }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 27db6d6..ad60329 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -301,7 +301,7 @@
     var sysUiStatusNavFlags: Int = 0
         get() =
             if (enableRefactorTaskThumbnail()) field
-            else taskContainers.first().thumbnailViewDeprecated.sysUiStatusNavFlags
+            else firstTaskContainer?.thumbnailViewDeprecated?.sysUiStatusNavFlags ?: 0
         private set
 
     // Various animation progress variables.
@@ -1476,7 +1476,8 @@
         return if (enableOverviewIconMenu() && menuContainer.iconView is IconAppChipView) {
             menuContainer.iconView.revealAnim(/* isRevealing= */ true)
             TaskMenuView.showForTask(menuContainer) {
-                menuContainer.iconView.revealAnim(/* isRevealing= */ false)
+                val isAnimated = !recentsView.isSplitSelectionActive
+                menuContainer.iconView.revealAnim(/* isRevealing= */ false, isAnimated)
                 if (enableHoverOfChildElementsInTaskview()) {
                     recentsView.setTaskBorderEnabled(true)
                 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 9d000a4..c9d7e1d 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -94,6 +94,7 @@
         whenever(mockTaskContainer.task).thenReturn(mockTask)
         whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable)
         whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
+        whenever(mockTaskView.firstTaskContainer).thenReturn(mockTaskContainer)
 
         whenever(splitSelectSource.drawable).thenReturn(mockSplitSourceDrawable)
         whenever(splitSelectSource.view).thenReturn(mockSplitSourceView)
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 6277e41..84c8040 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -481,10 +481,8 @@
     public static <T extends BaseActivity> T fromContext(Context context) {
         if (context instanceof BaseActivity) {
             return (T) context;
-        } else if (context instanceof ActivityContextDelegate) {
-            return (T) ((ActivityContextDelegate) context).mDelegate;
-        } else if (context instanceof ContextWrapper) {
-            return fromContext(((ContextWrapper) context).getBaseContext());
+        } else if (context instanceof ContextWrapper cw) {
+            return fromContext(cw.getBaseContext());
         } else {
             throw new IllegalArgumentException("Cannot find BaseActivity in parent tree");
         }
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index e4c50f0..2d1a5f5 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.util.Log
+import android.view.ContextThemeWrapper
 import android.view.InflateException
 import androidx.annotation.VisibleForTesting
 import androidx.annotation.VisibleForTesting.Companion.PROTECTED
@@ -33,8 +34,6 @@
 import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
 import com.android.launcher3.util.Themes
 import com.android.launcher3.views.ActivityContext
-import com.android.launcher3.views.ActivityContext.ActivityContextDelegate
-import java.lang.IllegalStateException
 
 const val PREINFLATE_ICONS_ROW_COUNT = 4
 const val EXTRA_ICONS_COUNT = 2
@@ -80,11 +79,9 @@
         // create a separate AssetManager obj internally to avoid lock contention with
         // AssetManager obj that is associated with the launcher context on the main thread.
         val allAppsPreInflationContext =
-            ActivityContextDelegate(
-                context.createConfigurationContext(context.resources.configuration),
-                Themes.getActivityThemeRes(context),
-                context,
-            )
+            ContextThemeWrapper(context, Themes.getActivityThemeRes(context)).apply {
+                applyOverrideConfiguration(context.resources.configuration)
+            }
 
         // Because we perform onCreateViewHolder() on worker thread, we need a separate
         // adapter/inflator object as they are not thread-safe. Note that the adapter
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 81968fc..bbea043 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -43,7 +43,6 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.util.Log;
-import android.view.ContextThemeWrapper;
 import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -552,21 +551,10 @@
     static <T extends Context & ActivityContext> T lookupContextNoThrow(Context context) {
         if (context instanceof ActivityContext) {
             return (T) context;
-        } else if (context instanceof ActivityContextDelegate acd) {
-            return (T) acd.mDelegate;
-        } else if (context instanceof ContextWrapper) {
-            return lookupContextNoThrow(((ContextWrapper) context).getBaseContext());
+        } else if (context instanceof ContextWrapper cw) {
+            return lookupContextNoThrow(cw.getBaseContext());
         } else {
             return null;
         }
     }
-
-    class ActivityContextDelegate extends ContextThemeWrapper {
-        public final ActivityContext mDelegate;
-
-        public ActivityContextDelegate(Context base, int themeResId, ActivityContext delegate) {
-            super(base, themeResId);
-            mDelegate = delegate;
-        }
-    }
 }
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS b/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS
new file mode 100644
index 0000000..02e8ebc
--- /dev/null
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS
@@ -0,0 +1,4 @@
+vadimt@google.com
+sunnygoyal@google.com
+winsonc@google.com
+hyunyoungs@google.com