Animate SplitPlaceholderView when entering split from overview

Bugs tracked in b/181704764

Bug: 181704764
Test: Tested on phone and large screen in multiple orientations

Change-Id: I07509006ae3d1f4425dc5119d0c8ed52b41a3bc2
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index f54070e..5fc969d 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -43,7 +43,6 @@
     <dimen name="overview_grid_side_margin">54dp</dimen>
     <dimen name="overview_grid_row_spacing">42dp</dimen>
     <dimen name="overview_grid_focus_vertical_margin">40dp</dimen>
-    <dimen name="split_placeholder_size">110dp</dimen>
 
     <!-- These speeds are in dp/s -->
     <dimen name="max_task_dismiss_drag_velocity">2.25dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index ecd38b4..5250d18 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -271,12 +271,10 @@
 
         SysUINavigationMode.INSTANCE.get(this).updateMode();
         mActionsView = findViewById(R.id.overview_actions_view);
-        mSplitPlaceholderView = findViewById(R.id.split_placeholder);
         RecentsView overviewPanel = (RecentsView) getOverviewPanel();
-        mSplitPlaceholderView.init(
-                new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this))
-        );
-        overviewPanel.init(mActionsView, mSplitPlaceholderView);
+        SplitSelectStateController controller =
+                new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this));
+        overviewPanel.init(mActionsView, controller);
         mActionsView.setDp(getDeviceProfile());
         mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 6cad3dd..1f744e1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -18,13 +18,11 @@
 import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
 import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
-import static com.android.launcher3.LauncherState.SPLIT_PLACHOLDER_VIEW;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
-import static com.android.quickstep.views.SplitPlaceholderView.ALPHA_FLOAT;
 import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
 
 import android.annotation.TargetApi;
@@ -110,11 +108,6 @@
         propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
                 MultiValueAlpha.VALUE, overviewButtonAlpha, config.getInterpolator(
                         ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
-
-        float splitPlaceholderAlpha = state.areElementsVisible(mLauncher, SPLIT_PLACHOLDER_VIEW) ?
-                0.85f : 0;
-        propertySetter.setFloat(mRecentsView.getSplitPlaceholder(), ALPHA_FLOAT,
-                splitPlaceholderAlpha, LINEAR);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index 6968494..1882a0c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -43,7 +43,7 @@
     @Override
     public float getSplitSelectTranslation(Launcher launcher) {
         RecentsView recentsView = launcher.getOverviewPanel();
-        int splitPosition = recentsView.getSplitPlaceholder().getSplitController()
+        int splitPosition = recentsView.getSplitPlaceholder()
                 .getActiveSplitPositionOption().mStagePosition;
         if (!recentsView.shouldShiftThumbnailsForSplitSelect(splitPosition)) {
             return 0f;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 051485a..0603ba5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -227,7 +227,8 @@
         if (goingUp) {
             currentInterpolator = Interpolators.LINEAR;
             pa = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
-                    true /* animateTaskView */, true /* removeTask */, maxDuration);
+                    true /* animateTaskView */, true /* removeTask */, maxDuration,
+                    false /* dismissingForSplitSelection*/);
 
             mEndDisplacement = -secondaryTaskDimension;
         } else {
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 9dfcd12..95be45a 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -122,13 +122,10 @@
         mActionsView = findViewById(R.id.overview_actions_view);
         SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f);
 
-        SplitPlaceholderView splitPlaceholderView = findViewById(R.id.split_placeholder);
-        splitPlaceholderView.init(
-                new SplitSelectStateController(mUiHandler, SystemUiProxy.INSTANCE.get(this))
-        );
-
+        SplitSelectStateController controller =
+                new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this));
         mDragLayer.recreateControllers();
-        mFallbackRecentsView.init(mActionsView, splitPlaceholderView);
+        mFallbackRecentsView.init(mActionsView, controller);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 3bf79f1..de79372 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -37,6 +37,7 @@
 import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.SplitPlaceholderView;
@@ -62,8 +63,8 @@
     }
 
     @Override
-    public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
-        super.init(actionsView, splitPlaceholderView);
+    public void init(OverviewActionsView actionsView, SplitSelectStateController splitController) {
+        super.init(actionsView, splitController);
         setOverviewStateEnabled(true);
         setOverlayEnabled(true);
     }
@@ -96,7 +97,8 @@
         if (mHomeTaskInfo != null && endTarget == RECENTS && animatorSet != null) {
             TaskView tv = getTaskView(mHomeTaskInfo.taskId);
             if (tv != null) {
-                PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150);
+                PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150,
+                        false /* dismissingForSplitSelection*/);
                 pa.addEndListener(e -> setCurrentTask(-1));
                 AnimatorPlaybackController controller = pa.createPlaybackController();
                 controller.dispatchOnStart();
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 2351a4e..16c925a 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -56,6 +56,7 @@
 
     private final SystemUiProxy mSystemUiProxy;
     private TaskView mInitialTaskView;
+    private TaskView mSecondTaskView;
     private SplitPositionOption mInitialPosition;
     private Rect mInitialBounds;
     private final Handler mHandler;
@@ -79,23 +80,19 @@
      * To be called after second task selected
      */
     public void setSecondTaskId(TaskView taskView) {
-        if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
-            // Assume initial task is for top/left part of screen
-            final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT
-                    ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id}
-                    : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id};
+        mSecondTaskView = taskView;
+        // Assume initial task is for top/left part of screen
 
+        final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT
+                ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id}
+                : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id};
+        if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
             RemoteSplitLaunchTransitionRunner animationRunner =
                     new RemoteSplitLaunchTransitionRunner(mInitialTaskView, taskView);
             mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],
                     null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
                     new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR));
         } else {
-            // Assume initial task is for top/left part of screen
-            final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT
-                    ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id}
-                    : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id};
-
             RemoteSplitLaunchAnimationRunner animationRunner =
                     new RemoteSplitLaunchAnimationRunner(mInitialTaskView, taskView);
             final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
@@ -191,12 +188,17 @@
      */
     public void resetState() {
         mInitialTaskView = null;
+        mSecondTaskView = null;
         mInitialPosition = null;
         mInitialBounds = null;
     }
 
+    /**
+     * @return {@code true} if first task has been selected and waiting for the second task to be
+     *         chosen
+     */
     public boolean isSplitSelectActive() {
-        return mInitialTaskView != null;
+        return mInitialTaskView != null && mSecondTaskView == null;
     }
 
     public Rect getInitialBounds() {
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
new file mode 100644
index 0000000..a1befc5
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -0,0 +1,241 @@
+package com.android.quickstep.views;
+
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.util.MultiValueUpdateListener;
+
+/**
+ * Create an instance via {@link #getFloatingTaskView(StatefulActivity, TaskView, RectF)} to
+ * which will have the thumbnail from the provided existing TaskView overlaying the taskview itself.
+ *
+ * Can then animate the taskview using
+ * {@link #addAnimation(PendingAnimation, RectF, Rect, View, boolean)}
+ * giving a starting and ending bounds. Currently this is set to use the split placeholder view,
+ * but it could be generified.
+ *
+ * TODO: Figure out how to copy thumbnail data from existing TaskView to this view.
+ */
+public class FloatingTaskView extends FrameLayout {
+
+    private SplitPlaceholderView mSplitPlaceholderView;
+    private RectF mStartingPosition;
+    private final Launcher mLauncher;
+    private final boolean mIsRtl;
+    private final Rect mOutline = new Rect();
+    private PagedOrientationHandler mOrientationHandler;
+    private ImageView mImageView;
+
+    public FloatingTaskView(Context context) {
+        this(context, null);
+    }
+
+    public FloatingTaskView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FloatingTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
+        mIsRtl = Utilities.isRtl(getResources());
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mImageView = findViewById(R.id.thumbnail);
+        mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+        mImageView.setLayerType(LAYER_TYPE_HARDWARE, null);
+        mSplitPlaceholderView = findViewById(R.id.split_placeholder);
+        mSplitPlaceholderView.setAlpha(0);
+        mSplitPlaceholderView.setBackgroundColor(getResources().getColor(android.R.color.white));
+    }
+
+    public static FloatingTaskView getFloatingTaskView(StatefulActivity launcher,
+            TaskView originalView, RectF positionOut) {
+        final BaseDragLayer dragLayer = launcher.getDragLayer();
+        ViewGroup parent = (ViewGroup) dragLayer.getParent();
+        final FloatingTaskView floatingView = (FloatingTaskView) launcher.getLayoutInflater()
+                .inflate(R.layout.floating_split_select_view, parent, false);
+
+        floatingView.mStartingPosition = positionOut;
+        floatingView.updateInitialPositionForView(originalView);
+        final InsettableFrameLayout.LayoutParams lp =
+                (InsettableFrameLayout.LayoutParams) floatingView.getLayoutParams();
+
+        floatingView.mSplitPlaceholderView.setLayoutParams(
+                new FrameLayout.LayoutParams(lp.width, lp.height));
+        positionOut.round(floatingView.mOutline);
+        floatingView.setPivotX(0);
+        floatingView.setPivotY(0);
+
+        // Copy bounds of exiting thumbnail into ImageView
+        TaskThumbnailView thumbnail = originalView.getThumbnail();
+        floatingView.mImageView.setImageBitmap(thumbnail.getThumbnail());
+        floatingView.mImageView.setVisibility(VISIBLE);
+
+        floatingView.mOrientationHandler =
+                originalView.getRecentsView().getPagedOrientationHandler();
+        floatingView.mSplitPlaceholderView.setIcon(originalView.getIconView());
+        floatingView.mSplitPlaceholderView.getIcon()
+                .setRotation(floatingView.mOrientationHandler.getDegreesRotated());
+        parent.addView(floatingView);
+        return floatingView;
+    }
+
+    public void updateInitialPositionForView(TaskView originalView) {
+        View thumbnail = originalView.getThumbnail();
+        Rect viewBounds = new Rect(0, 0, thumbnail.getWidth(), thumbnail.getHeight());
+        Utilities.getBoundsForViewInDragLayer(mLauncher.getDragLayer(), thumbnail, viewBounds,
+                true /* ignoreTransform */, null /* recycle */,
+                mStartingPosition);
+        mStartingPosition.offset(originalView.getTranslationX(), originalView.getTranslationY());
+        final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams(
+                Math.round(mStartingPosition.width()),
+                Math.round(mStartingPosition.height()));
+        initPosition(mStartingPosition, lp);
+        setLayoutParams(lp);
+    }
+
+    // TODO(194414938) set correct corner radii
+    public void update(RectF position, float progress, float windowRadius) {
+        MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
+
+        float dX = mIsRtl
+                ? position.left - (lp.getMarginStart() - lp.width)
+                : position.left - lp.getMarginStart();
+        float dY = position.top - lp.topMargin;
+
+        setTranslationX(dX);
+        setTranslationY(dY);
+
+        float scaleX = position.width() / lp.width;
+        float scaleY = position.height() / lp.height;
+        setScaleX(scaleX);
+        setScaleY(scaleY);
+        float childScaleX = 1f / scaleX;
+        float childScaleY = 1f / scaleY;
+
+        invalidate();
+        // TODO(194414938) seems like this scale value could be fine tuned, some stretchiness
+        mImageView.setScaleX(1f / scaleX + scaleX * progress);
+        mImageView.setScaleY(1f / scaleY + scaleY * progress);
+        mOrientationHandler.setPrimaryScale(mSplitPlaceholderView.getIcon(), childScaleX);
+        mOrientationHandler.setSecondaryScale(mSplitPlaceholderView.getIcon(), childScaleY);
+    }
+
+    protected void initPosition(RectF pos, InsettableFrameLayout.LayoutParams lp) {
+        mStartingPosition.set(pos);
+        lp.ignoreInsets = true;
+        // Position the floating view exactly on top of the original
+        lp.topMargin = Math.round(pos.top);
+        if (mIsRtl) {
+            lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - pos.right));
+        } else {
+            lp.setMarginStart(Math.round(pos.left));
+        }
+        // Set the properties here already to make sure they are available when running the first
+        // animation frame.
+        int left = mIsRtl
+                ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
+                : lp.leftMargin;
+        layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
+    }
+
+    public void addAnimation(PendingAnimation animation, RectF startingBounds, Rect endBounds,
+            View viewToCover, boolean fadeWithThumbnail) {
+        final BaseDragLayer dragLayer = mLauncher.getDragLayer();
+        int[] dragLayerBounds = new int[2];
+        dragLayer.getLocationOnScreen(dragLayerBounds);
+        SplitOverlayProperties prop = new SplitOverlayProperties(endBounds,
+                startingBounds, viewToCover, dragLayerBounds[0],
+                dragLayerBounds[1]);
+
+        ValueAnimator transitionAnimator = ValueAnimator.ofFloat(0, 1);
+        animation.add(transitionAnimator);
+        long animDuration = animation.getDuration();
+        Rect crop = new Rect();
+        RectF floatingTaskViewBounds = new RectF();
+        final float initialWindowRadius = supportsRoundedCornersOnWindows(getResources())
+                ? Math.max(crop.width(), crop.height()) / 2f
+                : 0f;
+
+        if (fadeWithThumbnail) {
+            animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ALPHA_FLOAT,
+                    0, 1, ACCEL);
+            animation.addFloat(mImageView, LauncherAnimUtils.VIEW_ALPHA,
+                    1, 0, DEACCEL_3);
+        }
+
+        MultiValueUpdateListener listener = new MultiValueUpdateListener() {
+            final FloatProp mWindowRadius = new FloatProp(initialWindowRadius,
+                    initialWindowRadius, 0, animDuration, LINEAR);
+            final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration, LINEAR);
+            final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration, LINEAR);
+            final FloatProp mTaskViewScaleX = new FloatProp(prop.initialTaskViewScaleX,
+                    prop.finalTaskViewScaleX, 0, animDuration, LINEAR);
+            final FloatProp mTaskViewScaleY = new FloatProp(prop.initialTaskViewScaleY,
+                    prop.finalTaskViewScaleY, 0, animDuration, LINEAR);
+            @Override
+            public void onUpdate(float percent, boolean initOnly) {
+                // Calculate the icon position.
+                floatingTaskViewBounds.set(startingBounds);
+                floatingTaskViewBounds.offset(mDx.value, mDy.value);
+                Utilities.scaleRectFAboutCenter(floatingTaskViewBounds, mTaskViewScaleX.value,
+                        mTaskViewScaleY.value);
+
+                update(floatingTaskViewBounds, percent, mWindowRadius.value * 1);
+            }
+        };
+        transitionAnimator.addUpdateListener(listener);
+    }
+
+    private static class SplitOverlayProperties {
+
+        private final float initialTaskViewScaleX;
+        private final float initialTaskViewScaleY;
+        private final float finalTaskViewScaleX;
+        private final float finalTaskViewScaleY;
+        private final float dX;
+        private final float dY;
+
+        SplitOverlayProperties(Rect endBounds, RectF startTaskViewBounds, View view,
+                int dragLayerLeft, int dragLayerTop) {
+            float maxScaleX = endBounds.width() / startTaskViewBounds.width();
+            float maxScaleY = endBounds.height() / startTaskViewBounds.height();
+
+            initialTaskViewScaleX = view.getScaleX();
+            initialTaskViewScaleY = view.getScaleY();
+            finalTaskViewScaleX = maxScaleX;
+            finalTaskViewScaleY = maxScaleY;
+
+            // Animate the app icon to the center of the window bounds in screen coordinates.
+            float centerX = endBounds.centerX() - dragLayerLeft;
+            float centerY = endBounds.centerY() - dragLayerTop;
+
+            dX = centerX - startTaskViewBounds.centerX();
+            dY = centerY - startTaskViewBounds.centerY();
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 65956d5..152c2bd 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.quickstep.LauncherActivityInterface;
+import com.android.quickstep.util.SplitSelectStateController;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.RecentsExtraCard;
 
@@ -81,7 +82,8 @@
     }
 
     @Override
-    public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
+    public void init(OverviewActionsView actionsView,
+            SplitSelectStateController splitPlaceholderView) {
         super.init(actionsView, splitPlaceholderView);
         setContentAlpha(0);
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index edda439..be6e3ff 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -545,15 +545,18 @@
     /**
      * Placeholder view indicating where the first split screen selected app will be placed
      */
-    private SplitPlaceholderView mSplitPlaceholderView;
+    private SplitSelectStateController mSplitSelectStateController;
     /**
      * The first task that split screen selection was initiated with. When split select state is
      * initialized, we create a
-     * {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long)} for this TaskView but
-     * don't actually remove the task since the user might back out. As such, we also ensure this
-     * View doesn't go back into the {@link #mTaskViewPool}, see {@link #onViewRemoved(View)}
+     * {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long, boolean)} for this
+     * TaskView but don't actually remove the task since the user might back out. As such, we also
+     * ensure this View doesn't go back into the {@link #mTaskViewPool},
+     * see {@link #onViewRemoved(View)}
      */
     private TaskView mSplitHiddenTaskView;
+    private TaskView mSecondSplitHiddenTaskView;
+
     /**
      * Keeps track of the index of the TaskView that split screen was initialized with so we know
      * where to insert it back into list of taskViews in case user backs out of entering split
@@ -563,6 +566,9 @@
      * removed from recentsView
      */
     private int mSplitHiddenTaskViewIndex;
+    private FloatingTaskView mFirstFloatingTaskView;
+    private FloatingTaskView mSecondFloatingTaskView;
+
     /**
      * The task to be removed and immediately re-added. Should not be added to task pool.
      */
@@ -776,18 +782,18 @@
         updateTaskStackListenerState();
     }
 
-    public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
+    public void init(OverviewActionsView actionsView, SplitSelectStateController splitController) {
         mActionsView = actionsView;
         mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
-        mSplitPlaceholderView = splitPlaceholderView;
+        mSplitSelectStateController = splitController;
     }
 
-    public SplitPlaceholderView getSplitPlaceholder() {
-        return mSplitPlaceholderView;
+    public SplitSelectStateController getSplitPlaceholder() {
+        return mSplitSelectStateController;
     }
 
     public boolean isSplitSelectionActive() {
-        return mSplitPlaceholderView.getSplitController().isSplitSelectActive();
+        return mSplitSelectStateController.isSplitSelectActive();
     }
 
     @Override
@@ -984,7 +990,7 @@
             // Reset the running task when leaving overview since it can still have a reference to
             // its thumbnail
             mTmpRunningTask = null;
-            if (mSplitPlaceholderView.getSplitController().isSplitSelectActive()) {
+            if (mSplitSelectStateController.isSplitSelectActive()) {
                 cancelSplitSelect(false);
             }
         }
@@ -2286,50 +2292,23 @@
             PendingAnimation anim) {
         // Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
         // alpha is set to 0 so that it can be recycled in the view pool properly
-        anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f));
         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask()) {
             anim.setFloat(mLiveTileParams, TransformParams.TARGET_ALPHA, 0,
                     clampToProgress(ACCEL, 0, 0.5f));
         }
-        SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
+        anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f));
+        FloatProperty<TaskView> secondaryViewTranslate =
+                taskView.getSecondaryDissmissTranslationProperty();
+        int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
+        int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
 
         ResourceProvider rp = DynamicResource.provider(mActivity);
         SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START)
                 .setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio))
                 .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness));
-        FloatProperty<TaskView> dismissingTaskViewTranslate =
-                taskView.getSecondaryDissmissTranslationProperty();
-        // TODO(b/186800707) translate entire grid size distance
-        int translateDistance = mOrientationHandler.getSecondaryDimension(taskView);
-        int positiveNegativeFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
-        if (splitController.isSplitSelectActive()) {
-            // Have the task translate towards whatever side was just pinned
-            int dir = mOrientationHandler.getSplitTaskViewDismissDirection(splitController
-                    .getActiveSplitPositionOption(), mActivity.getDeviceProfile());
-            switch (dir) {
-                case PagedOrientationHandler.SPLIT_TRANSLATE_SECONDARY_NEGATIVE:
-                    dismissingTaskViewTranslate = taskView
-                            .getSecondaryDissmissTranslationProperty();
-                    positiveNegativeFactor = -1;
-                    break;
 
-                case PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_POSITIVE:
-                    dismissingTaskViewTranslate = taskView.getPrimaryDismissTranslationProperty();
-                    positiveNegativeFactor = 1;
-                    break;
-
-                case PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_NEGATIVE:
-                    dismissingTaskViewTranslate = taskView.getPrimaryDismissTranslationProperty();
-                    positiveNegativeFactor = -1;
-                    break;
-                default:
-                    throw new IllegalStateException("Invalid split task translation: " + dir);
-            }
-        }
-        // Double translation distance so dismissal drag is the full height, as we only animate
-        // the drag for the first half of the progress.
-        anim.add(ObjectAnimator.ofFloat(taskView, dismissingTaskViewTranslate,
-                positiveNegativeFactor * translateDistance * 2).setDuration(duration), LINEAR, sp);
+        anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate,
+                verticalFactor * secondaryTaskDimension * 2).setDuration(duration), LINEAR, sp);
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
                 && taskView.isRunningTask()) {
@@ -2344,15 +2323,37 @@
     }
 
     /**
+     * Places an {@link FloatingTaskView} on top of the thumbnail for {@link #mSplitHiddenTaskView}
+     * and then animates it into the split position that was desired
+     */
+    private void createInitialSplitSelectAnimation(PendingAnimation anim) {
+        float placeholderHeight = getResources().getDimension(R.dimen.split_placeholder_size);
+        mOrientationHandler.getInitialSplitPlaceholderBounds((int) placeholderHeight,
+                        mActivity.getDeviceProfile(),
+                mSplitSelectStateController.getActiveSplitPositionOption(), mTempRect);
+
+        RectF startingTaskRect = new RectF();
+        mSplitHiddenTaskView.setVisibility(INVISIBLE);
+        mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
+                mSplitHiddenTaskView, startingTaskRect);
+        mFirstFloatingTaskView.setAlpha(1);
+        mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
+                mTempRect, mSplitHiddenTaskView, true /*fadeWithThumbnail*/);
+    }
+
+    /**
      * Creates a {@link PendingAnimation} for dismissing the specified {@link TaskView}.
      * @param dismissedTaskView the {@link TaskView} to be dismissed
      * @param animateTaskView whether the {@link TaskView} to be dismissed should be animated
      * @param shouldRemoveTask whether the associated {@link Task} should be removed from
      *                         ActivityManager after dismissal
      * @param duration duration of the animation
+     * @param dismissingForSplitSelection task dismiss animation is used for entering split
+     *                                    selection state from app icon
      */
     public PendingAnimation createTaskDismissAnimation(TaskView dismissedTaskView,
-            boolean animateTaskView, boolean shouldRemoveTask, long duration) {
+            boolean animateTaskView, boolean shouldRemoveTask, long duration,
+            boolean dismissingForSplitSelection) {
         if (mPendingAnimation != null) {
             mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd();
         }
@@ -2419,7 +2420,11 @@
             View child = getChildAt(i);
             if (child == dismissedTaskView) {
                 if (animateTaskView) {
-                    addDismissedTaskAnimations(dismissedTaskView, duration, anim);
+                    if (dismissingForSplitSelection) {
+                        createInitialSplitSelectAnimation(anim);
+                    } else {
+                        addDismissedTaskAnimations(dismissedTaskView, duration, anim);
+                    }
                 }
             } else if (!showAsGrid) {
                 // Compute scroll offsets from task dismissal for animation.
@@ -2602,7 +2607,7 @@
 
     /**
      * @return {@code true} if one of the task thumbnails would intersect/overlap with the
-     *         {@link #mSplitPlaceholderView}
+     *         {@link #mFirstFloatingTaskView}
      */
     public boolean shouldShiftThumbnailsForSplitSelect(@SplitConfigurationOptions.StagePosition
             int stagePosition) {
@@ -2705,7 +2710,7 @@
 
     public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
         runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask,
-                DISMISS_TASK_DURATION));
+                DISMISS_TASK_DURATION, false /* dismissingForSplitSelection*/));
     }
 
     @SuppressWarnings("unused")
@@ -3107,8 +3112,11 @@
     protected void setTaskViewsSecondarySplitTranslation(float translation) {
         mTaskViewsSecondarySplitTranslation = translation;
         for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView task = getTaskViewAt(i);
-            task.getSecondarySplitTranslationProperty().set(task, translation);
+            TaskView taskView = getTaskViewAt(i);
+            if (taskView == mSplitHiddenTaskView) {
+                continue;
+            }
+            taskView.getSecondarySplitTranslationProperty().set(taskView, translation);
         }
     }
 
@@ -3124,15 +3132,11 @@
 
     public void initiateSplitSelect(TaskView taskView, SplitPositionOption splitPositionOption) {
         mSplitHiddenTaskView = taskView;
-        SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
         Rect initialBounds = new Rect(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
                 taskView.getBottom());
-        splitController.setInitialTaskSelect(taskView, splitPositionOption, initialBounds);
+        mSplitSelectStateController.setInitialTaskSelect(taskView,
+                splitPositionOption, initialBounds);
         mSplitHiddenTaskViewIndex = indexOfChild(taskView);
-        mSplitPlaceholderView.setLayoutParams(
-                splitController.getLayoutParamsForActivePosition(getResources(),
-                        mActivity.getDeviceProfile()));
-        mSplitPlaceholderView.setIcon(taskView.getIconView());
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             finishRecentsAnimation(true, null);
         }
@@ -3140,17 +3144,48 @@
 
     public PendingAnimation createSplitSelectInitAnimation() {
         int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
-        return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration);
+        return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration,
+                true /* dismissingForSplitSelection*/);
     }
 
     public void confirmSplitSelect(TaskView taskView) {
-        mSplitPlaceholderView.getSplitController().setSecondTaskId(taskView);
-        resetTaskVisuals();
-        setTranslationY(0);
+        RectF secondTaskStartingBounds = new RectF();
+        Rect secondTaskEndingBounds = new Rect();
+        // TODO(194414938) starting bounds seem slightly off, investigate
+        Rect firstTaskStartingBounds = new Rect();
+        Rect firstTaskEndingBounds = mTempRect;
+        int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
+        PendingAnimation pendingAnimation = new PendingAnimation(duration);
+
+        int halfDividerSize = getResources()
+                .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
+        mOrientationHandler.getFinalSplitPlaceholderBounds(halfDividerSize,
+                mActivity.getDeviceProfile(),
+                mSplitSelectStateController.getActiveSplitPositionOption(), firstTaskEndingBounds,
+                secondTaskEndingBounds);
+
+        mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
+        mFirstFloatingTaskView.addAnimation(pendingAnimation,
+                new RectF(firstTaskStartingBounds), firstTaskEndingBounds, mFirstFloatingTaskView,
+                false /*fadeWithThumbnail*/);
+
+        mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
+                taskView, secondTaskStartingBounds);
+        mSecondFloatingTaskView.setAlpha(1);
+        mSecondFloatingTaskView.addAnimation(pendingAnimation, secondTaskStartingBounds,
+                secondTaskEndingBounds, taskView.getThumbnail(),
+                true /*fadeWithThumbnail*/);
+        pendingAnimation.addEndListener(aBoolean -> {
+            mSplitSelectStateController.setSecondTaskId(taskView);
+            resetFromSplitSelectionState();
+        });
+        mSecondSplitHiddenTaskView = taskView;
+        taskView.setVisibility(INVISIBLE);
+        pendingAnimation.buildAnim().start();
     }
 
     public PendingAnimation cancelSplitSelect(boolean animate) {
-        SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
+        SplitSelectStateController splitController = mSplitSelectStateController;
         SplitPositionOption splitOption = splitController.getActiveSplitPositionOption();
         Rect initialBounds = splitController.getInitialBounds();
         splitController.resetState();
@@ -3263,8 +3298,19 @@
         }
         onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
         resetTaskVisuals();
+        mSplitHiddenTaskView.setVisibility(VISIBLE);
         mSplitHiddenTaskView = null;
+        mSecondSplitHiddenTaskView.setVisibility(VISIBLE);
+        mSecondSplitHiddenTaskView = null;
         mSplitHiddenTaskViewIndex = -1;
+        if (mFirstFloatingTaskView != null) {
+            mActivity.getRootView().removeView(mFirstFloatingTaskView);
+            mFirstFloatingTaskView = null;
+        }
+        if (mSecondFloatingTaskView != null) {
+            mActivity.getRootView().removeView(mSecondFloatingTaskView);
+            mSecondFloatingTaskView = null;
+        }
     }
 
     private void updateDeadZoneRects() {
diff --git a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
index bb8bc11..a712d1a 100644
--- a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
@@ -22,6 +22,8 @@
 import android.view.Gravity;
 import android.widget.FrameLayout;
 
+import androidx.annotation.Nullable;
+
 import com.android.quickstep.util.SplitSelectStateController;
 
 public class SplitPlaceholderView extends FrameLayout {
@@ -55,6 +57,11 @@
         return mSplitController;
     }
 
+    @Nullable
+    public IconView getIcon() {
+        return mIcon;
+    }
+
     public void setIcon(IconView icon) {
         if (mIcon == null) {
             mIcon = new IconView(getContext());
diff --git a/res/layout/floating_split_select_view.xml b/res/layout/floating_split_select_view.xml
new file mode 100644
index 0000000..e184b91
--- /dev/null
+++ b/res/layout/floating_split_select_view.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.quickstep.views.FloatingTaskView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/thumbnail"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone" />
+
+    <com.android.quickstep.views.SplitPlaceholderView
+        android:id="@+id/split_placeholder"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/split_placeholder_size"
+        android:background="@android:color/white"
+        android:visibility="gone" />
+
+</com.android.quickstep.views.FloatingTaskView>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 46570f0..dd27685 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -326,6 +326,7 @@
     <dimen name="overview_task_margin">0dp</dimen>
     <dimen name="overview_actions_bottom_margin_gesture">0dp</dimen>
     <dimen name="overview_actions_bottom_margin_three_button">0dp</dimen>
+    <dimen name="split_placeholder_size">110dp</dimen>
 
 <!-- Workspace grid visualization parameters -->
     <dimen name="grid_visualization_rounding_radius">22dp</dimen>
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 3cabc87..7d818d2 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -381,6 +381,21 @@
     }
 
     /**
+     * Similar to {@link #scaleRectAboutCenter(Rect, float)} except this allows different scales
+     * for X and Y
+     */
+    public static void scaleRectFAboutCenter(RectF r, float scaleX, float scaleY) {
+        float px = r.centerX();
+        float py = r.centerY();
+        r.offset(-px, -py);
+        r.left = r.left * scaleX;
+        r.top = r.top * scaleY;
+        r.right = r.right * scaleX;
+        r.bottom = r.bottom * scaleY;
+        r.offset(px, py);
+    }
+
+    /**
      * Maps t from one range to another range.
      * @param t The value to map.
      * @param fromMin The lower bound of the range that t is being mapped from.
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 01f7de6..3ab893b 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -56,6 +56,10 @@
         mAnim = new AnimatorSet();
     }
 
+    public long getDuration() {
+        return mDuration;
+    }
+
     /**
      * Utility method to sent an interpolator on an animation and add it to the list
      */
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index d047eca..816e5dc 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -205,6 +205,16 @@
     }
 
     @Override
+    public void setPrimaryScale(View view, float scale) {
+        view.setScaleY(scale);
+    }
+
+    @Override
+    public void setSecondaryScale(View view, float scale) {
+        view.setScaleX(scale);
+    }
+
+    @Override
     public int getChildStart(View view) {
         return view.getTop();
     }
@@ -353,6 +363,25 @@
     }
 
     @Override
+    public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp,
+            SplitPositionOption splitPositionOption, Rect out) {
+        // In fake land/seascape, the placeholder always needs to go to the "top" of the device,
+        // which is the same bounds as 0 rotation.
+        int width = dp.widthPx;
+        out.set(0, 0, width, placeholderHeight);
+    }
+
+    @Override
+    public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
+            SplitPositionOption initialSplitOption, Rect out1, Rect out2) {
+        // In fake land/seascape, the window bounds are always top and bottom half
+        int screenHeight = dp.heightPx;
+        int screenWidth = dp.widthPx;
+        out1.set(0, 0, screenWidth, screenHeight / 2  - splitDividerSize);
+        out2.set(0, screenHeight / 2  + splitDividerSize, screenWidth, screenHeight);
+    }
+
+    @Override
     public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
             DeviceProfile deviceProfile) {
         return primary;
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 266e05f..dae2dde 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -99,6 +99,8 @@
     boolean getRecentsRtlSetting(Resources resources);
     float getDegreesRotated();
     int getRotation();
+    void setPrimaryScale(View view, float scale);
+    void setSecondaryScale(View view, float scale);
 
     <T> T getPrimaryValue(T x, T y);
     <T> T getSecondaryValue(T x, T y);
@@ -114,6 +116,22 @@
             DeviceProfile deviceProfile);
     int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
     List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp);
+    /**
+     * @param splitholderSize height of placeholder view in portrait, width in landscape
+     */
+    void getInitialSplitPlaceholderBounds(int splitholderSize, DeviceProfile dp,
+            SplitPositionOption splitPositionOption, Rect out);
+
+    /**
+     * @param splitDividerSize height of split screen drag handle in portrait, width in landscape
+     * @param initialSplitOption the split position option (top/left, bottom/right) of the first
+     *                           task selected for entering split
+     * @param out1 the bounds for where the first selected app will be
+     * @param out2 the bounds for where the second selected app will be, complimentary to
+     *             {@param out1} based on {@param initialSplitOption}
+     */
+    void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
+            SplitPositionOption initialSplitOption, Rect out1, Rect out2);
 
     // Overview TaskMenuView methods
     float getTaskMenuX(float x, View thumbnailView, int overScroll);
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index dd97af5..1253589 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -24,6 +24,7 @@
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
 
 import android.content.res.Resources;
+import android.graphics.Matrix;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -47,6 +48,9 @@
 
 public class PortraitPagedViewHandler implements PagedOrientationHandler {
 
+    private final Matrix mTmpMatrix = new Matrix();
+    private final RectF mTmpRectF = new RectF();
+
     @Override
     public <T> T getPrimaryValue(T x, T y) {
         return x;
@@ -207,6 +211,16 @@
     }
 
     @Override
+    public void setPrimaryScale(View view, float scale) {
+        view.setScaleX(scale);
+    }
+
+    @Override
+    public void setSecondaryScale(View view, float scale) {
+        view.setScaleY(scale);
+    }
+
+    @Override
     public int getChildStart(View view) {
         return view.getLeft();
     }
@@ -398,6 +412,62 @@
     }
 
     @Override
+    public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp,
+            SplitPositionOption splitPositionOption, Rect out) {
+        int width = dp.widthPx;
+        out.set(0, 0, width, placeholderHeight);
+        if (!dp.isLandscape) {
+            // portrait, phone or tablet - spans width of screen, nothing else to do
+            return;
+        }
+
+        // Now we rotate the portrait rect depending on what side we want pinned
+        boolean pinToRight = splitPositionOption.mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
+
+        int screenHeight = dp.heightPx;
+        float postRotateScale = (float) screenHeight / width;
+        mTmpMatrix.reset();
+        mTmpMatrix.postRotate(pinToRight ? 90 : 270);
+        mTmpMatrix.postTranslate(pinToRight ? width : 0, pinToRight ? 0 : width);
+        // 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.roundOut(out);
+    }
+
+    @Override
+    public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
+            SplitPositionOption initialSplitOption, 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.isLandscape) {
+            // 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 = initialSplitOption.mStagePosition == 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 FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
             DeviceProfile dp) {
         if (dp.isLandscape) { // or seascape