Merge "Retain full opacity during task dismissal unless in bottom grid row." into sc-v2-dev
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index b4c168c..83ad9f3 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -52,6 +52,7 @@
             android:orientation="horizontal"
             android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
             android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
+            android:layout_marginEnd="@dimen/taskbar_contextual_button_margin"
             android:gravity="center_vertical"
             android:layout_gravity="end"/>
 
diff --git a/quickstep/res/layout/taskbar_contextual_button.xml b/quickstep/res/layout/taskbar_contextual_button.xml
new file mode 100644
index 0000000..cbbbfab
--- /dev/null
+++ b/quickstep/res/layout/taskbar_contextual_button.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/taskbar_contextual_buttons_size"
+    android:layout_height="@dimen/taskbar_contextual_buttons_size"
+    android:layout_marginTop="@dimen/taskbar_contextual_button_margin"
+    android:paddingStart="@dimen/taskbar_nav_buttons_spacing"
+    android:background="@drawable/taskbar_icon_click_feedback_roundrect"
+    android:scaleType="center"/>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_nav_button.xml b/quickstep/res/layout/taskbar_nav_button.xml
index 985f928..4ffb8d8 100644
--- a/quickstep/res/layout/taskbar_nav_button.xml
+++ b/quickstep/res/layout/taskbar_nav_button.xml
@@ -1,4 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
 <ImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="@dimen/taskbar_nav_buttons_size"
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index e7de0ac..c649082 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -163,6 +163,8 @@
     <dimen name="taskbar_folder_margin">16dp</dimen>
     <dimen name="taskbar_nav_buttons_spacing">16dp</dimen>
     <dimen name="taskbar_nav_buttons_size">48dp</dimen>
+    <dimen name="taskbar_contextual_button_margin">16dp</dimen>
+    <dimen name="taskbar_contextual_buttons_size">35dp</dimen>
     <dimen name="taskbar_stashed_size">24dp</dimen>
     <dimen name="taskbar_stashed_handle_width">220dp</dimen>
     <dimen name="taskbar_stashed_handle_height">6dp</dimen>
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
deleted file mode 100644
index fd93d98..0000000
--- a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static org.mockito.Mockito.mock;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.hardware.display.DisplayManager;
-import android.view.Surface;
-import android.view.SurfaceControl;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.shadows.LShadowDisplay;
-import com.android.launcher3.util.DisplayController;
-import com.android.quickstep.LauncherActivityInterface;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
-
-import org.hamcrest.Description;
-import org.hamcrest.TypeSafeMatcher;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowDisplayManager;
-
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
-public class TaskViewSimulatorTest {
-
-    @Test
-    public void taskProperlyScaled_portrait_noRotation_sameInsets1() {
-        new TaskMatrixVerifier()
-                .withLauncherSize(1200, 2450)
-                .withInsets(new Rect(0, 80, 0, 120))
-                .verifyNoTransforms();
-    }
-
-    @Test
-    public void taskProperlyScaled_portrait_noRotation_sameInsets2() {
-        new TaskMatrixVerifier()
-                .withLauncherSize(1200, 2450)
-                .withInsets(new Rect(55, 80, 55, 120))
-                .verifyNoTransforms();
-    }
-
-    @Test
-    public void taskProperlyScaled_landscape_noRotation_sameInsets1() {
-        new TaskMatrixVerifier()
-                .withLauncherSize(2450, 1250)
-                .withInsets(new Rect(0, 80, 0, 40))
-                .verifyNoTransforms();
-    }
-
-    @Test
-    public void taskProperlyScaled_landscape_noRotation_sameInsets2() {
-        new TaskMatrixVerifier()
-                .withLauncherSize(2450, 1250)
-                .withInsets(new Rect(0, 80, 120, 0))
-                .verifyNoTransforms();
-    }
-
-    @Test
-    public void taskProperlyScaled_landscape_noRotation_sameInsets3() {
-        new TaskMatrixVerifier()
-                .withLauncherSize(2450, 1250)
-                .withInsets(new Rect(55, 80, 55, 120))
-                .verifyNoTransforms();
-    }
-
-    @Test
-    public void taskProperlyScaled_landscape_rotated() {
-        new TaskMatrixVerifier()
-                .withLauncherSize(1200, 2450)
-                .withInsets(new Rect(0, 80, 0, 120))
-                .withAppBounds(
-                        new Rect(0, 0, 2450, 1200),
-                        new Rect(0, 80, 0, 120),
-                        Surface.ROTATION_90)
-                .verifyNoTransforms();
-    }
-
-    private static class TaskMatrixVerifier extends TransformParams {
-
-        private final Context mContext = RuntimeEnvironment.application;
-
-        private Rect mAppBounds = new Rect();
-        private Rect mLauncherInsets = new Rect();
-
-        private Rect mAppInsets;
-
-        private int mAppRotation = -1;
-        private DeviceProfile mDeviceProfile;
-
-        TaskMatrixVerifier withLauncherSize(int width, int height) {
-            ShadowDisplayManager.changeDisplay(DEFAULT_DISPLAY,
-                    String.format("w%sdp-h%sdp-mdpi", width, height));
-            if (mAppBounds.isEmpty()) {
-                mAppBounds.set(0, 0, width, height);
-            }
-            return this;
-        }
-
-        TaskMatrixVerifier withInsets(Rect insets) {
-            LShadowDisplay shadowDisplay = Shadow.extract(
-                    mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
-            shadowDisplay.setInsets(insets);
-            mLauncherInsets.set(insets);
-            return this;
-        }
-
-        TaskMatrixVerifier withAppBounds(Rect bounds, Rect insets, int appRotation) {
-            mAppBounds.set(bounds);
-            mAppInsets = insets;
-            mAppRotation = appRotation;
-            return this;
-        }
-
-        void verifyNoTransforms() {
-            mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(mContext)
-                    .getDeviceProfile(mContext);
-            mDeviceProfile.updateInsets(mLauncherInsets);
-
-            TaskViewSimulator tvs = new TaskViewSimulator(mContext,
-                    LauncherActivityInterface.INSTANCE);
-            tvs.setDp(mDeviceProfile);
-
-            int launcherRotation = DisplayController.INSTANCE.get(mContext).getInfo().rotation;
-            if (mAppRotation < 0) {
-                mAppRotation = launcherRotation;
-            }
-            tvs.getOrientationState().update(launcherRotation, mAppRotation);
-            if (mAppInsets == null) {
-                mAppInsets = new Rect(mLauncherInsets);
-            }
-            tvs.setPreviewBounds(mAppBounds, mAppInsets);
-
-            tvs.fullScreenProgress.value = 1;
-            tvs.recentsViewScale.value = tvs.getFullScreenScale();
-            tvs.apply(this);
-        }
-
-        @Override
-        public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
-            SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
-            proxy.onBuildTargetParams(builder, mock(RemoteAnimationTargetCompat.class), this);
-            return new SurfaceParams[] {builder.build()};
-        }
-
-        @Override
-        public void applySurfaceParams(SurfaceParams[] params) {
-            // Verify that the task position remains the same
-            RectF newAppBounds = new RectF(mAppBounds);
-            params[0].matrix.mapRect(newAppBounds);
-            Assert.assertThat(newAppBounds, new AlmostSame(mAppBounds));
-
-            System.err.println("Bounds mapped: " + mAppBounds + " => " + newAppBounds);
-        }
-    }
-
-    private static class AlmostSame extends TypeSafeMatcher<RectF>  {
-
-        // Allow 1px error margin to account for float to int conversions
-        private final float mError = 1f;
-        private final Rect mExpected;
-
-        AlmostSame(Rect expected) {
-            mExpected = expected;
-        }
-
-        @Override
-        protected boolean matchesSafely(RectF item) {
-            return Math.abs(item.left - mExpected.left) < mError
-                    && Math.abs(item.top - mExpected.top) < mError
-                    && Math.abs(item.right - mExpected.right) < mError
-                    && Math.abs(item.bottom - mExpected.bottom) < mError;
-        }
-
-        @Override
-        public void describeTo(Description description) {
-            description.appendValue(mExpected);
-        }
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index b172095..764b0d3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -285,6 +286,7 @@
      */
     public void showEdu() {
         if (!FeatureFlags.ENABLE_TASKBAR_EDU.get()
+                || Utilities.IS_RUNNING_IN_TEST_HARNESS
                 || mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN)) {
             return;
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 046ee6f..79bea03 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -34,6 +34,7 @@
 import android.animation.ObjectAnimator;
 import android.annotation.DrawableRes;
 import android.annotation.IdRes;
+import android.annotation.LayoutRes;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.Region.Op;
@@ -123,21 +124,16 @@
                         .getProperty(ALPHA_INDEX_IME),
                 flags -> (flags & FLAG_IME_VISIBLE) == 0, MultiValueAlpha.VALUE, 1, 0));
 
+        boolean isThreeButtonNav = mContext.isThreeButtonNav();
         // IME switcher
         View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
-                mEndContextualContainer, mControllers.navButtonController, R.id.ime_switcher);
+                isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
+                mControllers.navButtonController, R.id.ime_switcher);
         mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton,
                 flags -> ((flags & MASK_IME_SWITCHER_VISIBLE) == MASK_IME_SWITCHER_VISIBLE)
                         && ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)
                         && ((flags & FLAG_A11Y_VISIBLE) == 0)));
 
-        View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
-                mStartContextualContainer, mControllers.navButtonController, R.id.back);
-        imeDownButton.setRotation(Utilities.isRtl(mContext.getResources()) ? 90 : -90);
-        // Rotate when Ime visible
-        mPropertyHolders.add(new StatePropertyHolder(imeDownButton,
-                flags -> (flags & FLAG_IME_VISIBLE) != 0));
-
         mPropertyHolders.add(new StatePropertyHolder(
                 mControllers.taskbarViewController.getTaskbarIconAlpha()
                         .getProperty(ALPHA_INDEX_KEYGUARD),
@@ -150,7 +146,7 @@
         // Force nav buttons (specifically back button) to be visible during setup wizard.
         boolean areButtonsForcedVisible = !SettingsCache.INSTANCE.get(mContext).getValue(
                 Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
-        if (mContext.isThreeButtonNav() || areButtonsForcedVisible) {
+        if (isThreeButtonNav || areButtonsForcedVisible) {
             initButtons(mNavButtonContainer, mEndContextualContainer,
                     mControllers.navButtonController);
 
@@ -163,11 +159,18 @@
 
             // Rotation button
             RotationButton rotationButton = new RotationButtonImpl(
-                    addButton(mEndContextualContainer, R.id.rotate_suggestion));
+                    addButton(mEndContextualContainer, R.id.rotate_suggestion,
+                            R.layout.taskbar_contextual_button));
             rotationButton.hide();
             mControllers.rotationButtonController.setRotationButton(rotationButton);
         } else {
             mControllers.rotationButtonController.setRotationButton(new RotationButton() {});
+            View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
+                    mStartContextualContainer, mControllers.navButtonController, R.id.back);
+            imeDownButton.setRotation(Utilities.isRtl(mContext.getResources()) ? 90 : -90);
+            // Rotate when Ime visible
+            mPropertyHolders.add(new StatePropertyHolder(imeDownButton,
+                    flags -> (flags & FLAG_IME_VISIBLE) != 0));
         }
 
         applyState();
@@ -180,8 +183,11 @@
         mBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
                 mNavButtonContainer, mControllers.navButtonController, R.id.back);
         mPropertyHolders.add(new StatePropertyHolder(mBackButton,
-                flags -> (flags & FLAG_IME_VISIBLE) == 0 &&
-                        (flags & FLAG_DISABLE_BACK) == 0));
+                flags -> (flags & FLAG_DISABLE_BACK) == 0));
+        boolean isRtl = Utilities.isRtl(mContext.getResources());
+        mPropertyHolders.add(new StatePropertyHolder(
+                mBackButton, flags -> (flags & FLAG_IME_VISIBLE) != 0, View.ROTATION,
+                isRtl ? 90 : -90, 0));
         // Hide when keyguard is showing, show when bouncer is showing
         mPropertyHolders.add(new StatePropertyHolder(mBackButton,
                 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 ||
@@ -191,19 +197,18 @@
         View homeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
                 navButtonController, R.id.home);
         mPropertyHolders.add(new StatePropertyHolder(homeButton,
-                flags -> (flags & FLAG_IME_VISIBLE) == 0 &&
-                        (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
+                flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
                         (flags & FLAG_DISABLE_HOME) == 0));
         View recentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
                 navContainer, navButtonController, R.id.recent_apps);
         mPropertyHolders.add(new StatePropertyHolder(recentsButton,
-                flags -> (flags & FLAG_IME_VISIBLE) == 0 &&
-                        (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
+                flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
                         (flags & FLAG_DISABLE_RECENTS) == 0));
 
         // A11y button
         mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
-                endContainer, navButtonController, R.id.accessibility_button);
+                endContainer, navButtonController, R.id.accessibility_button,
+                R.layout.taskbar_contextual_button);
         mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
                 flags -> (flags & FLAG_A11Y_VISIBLE) != 0
                         && (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
@@ -306,15 +311,22 @@
 
     private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
             ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id) {
-        ImageView buttonView = addButton(parent, id);
+        return addButton(drawableId, buttonType, parent, navButtonController, id,
+                R.layout.taskbar_nav_button);
+    }
+
+    private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
+            ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id,
+            @LayoutRes int layoutId) {
+        ImageView buttonView = addButton(parent, id, layoutId);
         buttonView.setImageResource(drawableId);
         buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType));
         return buttonView;
     }
 
-    private ImageView addButton(ViewGroup parent, int id) {
+    private ImageView addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layoutId) {
         ImageView buttonView = (ImageView) mContext.getLayoutInflater()
-                .inflate(R.layout.taskbar_nav_button, parent, false);
+                .inflate(layoutId, parent, false);
         buttonView.setId(id);
         parent.addView(buttonView);
         mAllButtons.add(buttonView);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index ec98bbf..453bf1c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -22,7 +22,10 @@
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
 
+import android.content.ComponentCallbacks;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
 import android.net.Uri;
 import android.provider.Settings;
@@ -58,6 +61,7 @@
     private final SysUINavigationMode mSysUINavigationMode;
     private final TaskbarNavButtonController mNavButtonController;
     private final SettingsCache.OnChangeListener mUserSetupCompleteListener;
+    private final ComponentCallbacks mComponentCallbacks;
 
     // The source for this provider is set when Launcher is available
     private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider =
@@ -84,11 +88,27 @@
         mContext = service.createWindowContext(display, TYPE_APPLICATION_OVERLAY, null);
         mNavButtonController = new TaskbarNavButtonController(service);
         mUserSetupCompleteListener = isUserSetupComplete -> recreateTaskbar();
+        mComponentCallbacks = new ComponentCallbacks() {
+            private Configuration mOldConfig = mContext.getResources().getConfiguration();
+
+            @Override
+            public void onConfigurationChanged(Configuration newConfig) {
+                if ((mOldConfig.diff(newConfig) & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) {
+                    // Color has changed, recreate taskbar to reload background color & icons.
+                    recreateTaskbar();
+                }
+                mOldConfig = newConfig;
+            }
+
+            @Override
+            public void onLowMemory() { }
+        };
 
         mDisplayController.addChangeListener(this);
         mSysUINavigationMode.addModeChangeListener(this);
         SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI,
                 mUserSetupCompleteListener);
+        mContext.registerComponentCallbacks(mComponentCallbacks);
 
         recreateTaskbar();
     }
@@ -210,6 +230,7 @@
         mSysUINavigationMode.removeModeChangeListener(this);
         SettingsCache.INSTANCE.get(mContext).unregister(USER_SETUP_COMPLETE_URI,
                 mUserSetupCompleteListener);
+        mContext.unregisterComponentCallbacks(mComponentCallbacks);
     }
 
     public @Nullable TaskbarActivityContext getCurrentActivityContext() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 8181a84..c9909cc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -56,8 +56,14 @@
     public static int getHotseatEndOffset(Context context) {
         if (SysUINavigationMode.INSTANCE.get(context).getMode() == Mode.THREE_BUTTONS) {
             Resources res = context.getResources();
+            /*
+            * 2 (left + right) x Padding +
+            * 3 nav buttons +
+            * Little space at the end for contextual buttons
+            */
             return 2 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_spacing)
-                    + 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size);
+                    + 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
+                    + res.getDimensionPixelSize(R.dimen.taskbar_contextual_button_margin);
         } else {
             return 0;
         }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index be0c980..830737d 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -96,6 +96,7 @@
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.BaseActivityInterface.AnimationFactory;
 import com.android.quickstep.GestureState.GestureEndTarget;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
@@ -434,7 +435,7 @@
         // RecentsView never updates the display rotation until swipe-up, force update
         // RecentsOrientedState before passing to TaskViewSimulator.
         mRecentsView.updateRecentsRotation();
-        runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator
+        runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
                 .setOrientationState(mRecentsView.getPagedViewOrientedState()));
 
         // If we've already ended the gesture and are going home, don't prepare recents UI,
@@ -753,6 +754,8 @@
     public void onRecentsAnimationStart(RecentsAnimationController controller,
             RecentsAnimationTargets targets) {
         super.onRecentsAnimationStart(controller, targets);
+        ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
+        mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);
         mRecentsAnimationController = controller;
         mRecentsAnimationTargets = targets;
 
@@ -763,7 +766,7 @@
             // orientation state is independent of which remote target handle we use since both
             // should be pointing to the same one. Just choose index 0 for now since that works for
             // both split and non-split
-            RecentsOrientedState orientationState = mRemoteTargetHandles[0].mTaskViewSimulator
+            RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
                     .getOrientationState();
             DeviceProfile dp = orientationState.getLauncherDeviceProfile();
             if (targets.minimizedHomeBounds != null && primaryTaskTarget != null) {
@@ -1350,7 +1353,7 @@
             RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
         // Directly animate the app to PiP (picture-in-picture) mode
         final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
-        final RecentsOrientedState orientationState = mRemoteTargetHandles[0].mTaskViewSimulator
+        final RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
                 .getOrientationState();
         final int windowRotation = calculateWindowRotation(runningTaskTarget, orientationState);
         final int homeRotation = orientationState.getRecentsActivityRotation();
@@ -1386,7 +1389,7 @@
         // is not ROTATION_0 (which implies the rotation is turned on in launcher settings).
         if (homeRotation == ROTATION_0
                 && (windowRotation == ROTATION_90 || windowRotation == ROTATION_270)) {
-            builder.setFromRotation(mRemoteTargetHandles[0].mTaskViewSimulator, windowRotation,
+            builder.setFromRotation(mRemoteTargetHandles[0].getTaskViewSimulator(), windowRotation,
                     taskInfo.displayCutoutInsets);
         }
         final SwipePipToHomeAnimator swipePipToHomeAnimator = builder.build();
@@ -1756,7 +1759,7 @@
      * depend on proper class initialization.
      */
     protected void initAfterSubclassConstructor() {
-        initTransitionEndpoints(mRemoteTargetHandles[0].mTaskViewSimulator
+        initTransitionEndpoints(mRemoteTargetHandles[0].getTaskViewSimulator()
                         .getOrientationState().getLauncherDeviceProfile());
     }
 
@@ -1774,7 +1777,7 @@
 
     protected void linkRecentsViewScroll() {
         SurfaceTransactionApplier.create(mRecentsView, applier -> {
-            runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.mTransformParams
+            runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
                             .setSyncTransactionApplier(applier));
             runOnRecentsAnimationStart(() ->
                     mRecentsAnimationTargets.addReleaseCheck(applier));
@@ -1910,18 +1913,19 @@
         boolean notSwipingPipToHome = mRecentsAnimationTargets != null && !mIsSwipingPipToHome;
         boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null;
         for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
-            AnimatorControllerWithResistance playbackController = remoteHandle.mPlaybackController;
+            AnimatorControllerWithResistance playbackController =
+                    remoteHandle.getPlaybackController();
             if (playbackController != null) {
                 playbackController.setProgress(Math.max(mCurrentShift.value,
                         getScaleProgressDueToScroll()), mDragLengthFactor);
             }
 
             if (notSwipingPipToHome) {
-                TaskViewSimulator taskViewSimulator = remoteHandle.mTaskViewSimulator;
+                TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
                 if (setRecentsScroll) {
                     taskViewSimulator.setScroll(mRecentsView.getScrollOffset());
                 }
-                taskViewSimulator.apply(remoteHandle.mTransformParams);
+                taskViewSimulator.apply(remoteHandle.getTransformParams());
             }
         }
         ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 773817f..80ae65e 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -105,7 +105,7 @@
         mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
         if (mRunningOverHome) {
             runActionOnRemoteHandles(remoteTargetHandle ->
-                    remoteTargetHandle.mTransformParams.setHomeBuilderProxy(
+                    remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
                     FallbackSwipeHandler.this::updateHomeActivityTransformDuringSwipeUp));
         }
     }
@@ -115,7 +115,8 @@
         super.initTransitionEndpoints(dp);
         if (mRunningOverHome) {
             // Full screen scale should be independent of remote target handle
-            mMaxLauncherScale = 1 / mRemoteTargetHandles[0].mTaskViewSimulator.getFullScreenScale();
+            mMaxLauncherScale = 1 / mRemoteTargetHandles[0].getTaskViewSimulator()
+                    .getFullScreenScale();
         }
     }
 
@@ -214,21 +215,21 @@
                 mHomeAlpha.value = Utilities.boundToRange(1 - mCurrentShift.value, 0, 1);
                 mVerticalShiftForScale.value = mCurrentShift.value;
                 runActionOnRemoteHandles(remoteTargetHandle ->
-                        remoteTargetHandle.mTransformParams.setHomeBuilderProxy(
+                        remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
                                 FallbackHomeAnimationFactory.this
                                         ::updateHomeActivityTransformDuringHomeAnim));
             } else {
                 mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha);
                 mHomeAlpha.value = 0;
                 runActionOnRemoteHandles(remoteTargetHandle ->
-                        remoteTargetHandle.mTransformParams.setHomeBuilderProxy(
+                        remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
                                 FallbackHomeAnimationFactory.this
                                         ::updateHomeActivityTransformDuringHomeAnim));
             }
 
             mRecentsAlpha.value = 1;
             runActionOnRemoteHandles(remoteTargetHandle ->
-                    remoteTargetHandle.mTransformParams.setHomeBuilderProxy(
+                    remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
                             FallbackHomeAnimationFactory.this
                                     ::updateRecentsActivityTransformDuringHomeAnim));
         }
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index ce3406c..dc22a61 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -183,13 +183,13 @@
         Rect crop = new Rect();
         // We can assume there is only one remote target here because staged split never animates
         // into the app icon, only into the homescreen
-        mRemoteTargetHandles[0].mTaskViewSimulator.getCurrentCropRect().roundOut(crop);
+        mRemoteTargetHandles[0].getTaskViewSimulator().getCurrentCropRect().roundOut(crop);
         Size windowSize = new Size(crop.width(), crop.height());
         int fallbackBackgroundColor =
                 FloatingWidgetView.getDefaultBackgroundColor(mContext, runningTaskTarget);
         FloatingWidgetView floatingWidgetView = FloatingWidgetView.getFloatingWidgetView(mActivity,
                 hostView, backgroundLocation, windowSize,
-                mRemoteTargetHandles[0].mTaskViewSimulator.getCurrentCornerRadius(),
+                mRemoteTargetHandles[0].getTaskViewSimulator().getCurrentCornerRadius(),
                 isTargetTranslucent, fallbackBackgroundColor);
 
         return new FloatingViewHomeAnimationFactory(floatingWidgetView) {
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
new file mode 100644
index 0000000..dc04016
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2021 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;
+
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
+import com.android.quickstep.util.LauncherSplitScreenListener;
+import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.TransformParams;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * Glues together the necessary components to animate a remote target using a
+ * {@link TaskViewSimulator}
+ */
+public class RemoteTargetGluer {
+    private final RemoteTargetHandle[] mRemoteTargetHandles;
+    private SplitConfigurationOptions.StagedSplitBounds mStagedSplitBounds;
+
+    /**
+     * Use this constructor if remote targets are split-screen independent
+     */
+    public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy,
+            RemoteAnimationTargets targets) {
+        mRemoteTargetHandles = createHandles(context, sizingStrategy, targets.apps.length);
+    }
+
+    /**
+     * Use this constructor if you want the number of handles created to match the number of active
+     * running tasks
+     */
+    public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy) {
+        int[] splitIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+                .getRunningSplitTaskIds();
+        mRemoteTargetHandles = createHandles(context, sizingStrategy, splitIds.length == 2 ? 2 : 1);
+    }
+
+    private RemoteTargetHandle[] createHandles(Context context,
+            BaseActivityInterface sizingStrategy, int numHandles) {
+        RemoteTargetHandle[] handles = new RemoteTargetHandle[numHandles];
+        for (int i = 0; i < numHandles; i++) {
+            TaskViewSimulator tvs = new TaskViewSimulator(context, sizingStrategy);
+            TransformParams transformParams = new TransformParams();
+            handles[i] = new RemoteTargetHandle(tvs, transformParams);
+        }
+        return handles;
+    }
+
+    /**
+     * Pairs together {@link TaskViewSimulator}s and {@link TransformParams} into a
+     * {@link RemoteTargetHandle}
+     * Assigns only the apps associated with {@param targets} into their own TaskViewSimulators.
+     * Length of targets.apps should match that of {@link #mRemoteTargetHandles}.
+     *
+     * If split screen may be active when this is called, you might want to use
+     * {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)}
+     */
+    public RemoteTargetHandle[] assignTargets(RemoteAnimationTargets targets) {
+        for (int i = 0; i < mRemoteTargetHandles.length; i++) {
+            RemoteAnimationTargetCompat primaryTaskTarget = targets.apps[i];
+            mRemoteTargetHandles[i].mTransformParams.setTargetSet(
+                    createRemoteAnimationTargetsForTarget(primaryTaskTarget, targets));
+            mRemoteTargetHandles[i].mTaskViewSimulator.setPreview(primaryTaskTarget, null);
+        }
+        return mRemoteTargetHandles;
+    }
+
+    /**
+     * Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this matches the
+     * apps in targets.apps to that of the split screened tasks. If split screen is active, then
+     * {@link #mRemoteTargetHandles} index 0 will be the left/top task, index one right/bottom
+     */
+    public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) {
+        int[] splitIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+                .getRunningSplitTaskIds();
+        RemoteAnimationTargetCompat primaryTaskTarget;
+        RemoteAnimationTargetCompat secondaryTaskTarget;
+        if (mRemoteTargetHandles.length == 1) {
+            // If we're not in split screen, the splitIds count doesn't really matter since we
+            // should always hit this case. Right now there's no use case for multiple app targets
+            // without being in split screen
+            primaryTaskTarget = targets.apps[0];
+            mRemoteTargetHandles[0].mTransformParams.setTargetSet(targets);
+            mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget, null);
+        } else {
+            // split screen
+            primaryTaskTarget = targets.findTask(splitIds[0]);
+            secondaryTaskTarget = targets.findTask(splitIds[1]);
+
+            mStagedSplitBounds = new SplitConfigurationOptions.StagedSplitBounds(
+                    primaryTaskTarget.screenSpaceBounds,
+                    secondaryTaskTarget.screenSpaceBounds);
+            mRemoteTargetHandles[0].mTransformParams.setTargetSet(
+                    createRemoteAnimationTargetsForTarget(primaryTaskTarget, targets));
+            mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget,
+                    mStagedSplitBounds);
+
+            mRemoteTargetHandles[1].mTransformParams.setTargetSet(
+                    createRemoteAnimationTargetsForTarget(secondaryTaskTarget, targets));
+            mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(secondaryTaskTarget,
+                    mStagedSplitBounds);
+        }
+        return mRemoteTargetHandles;
+    }
+
+    private RemoteAnimationTargets createRemoteAnimationTargetsForTarget(
+            RemoteAnimationTargetCompat target,
+            RemoteAnimationTargets targets) {
+        return new RemoteAnimationTargets(new RemoteAnimationTargetCompat[]{target},
+                targets.wallpapers, targets.nonApps, targets.targetMode);
+    }
+
+    public RemoteTargetHandle[] getRemoteTargetHandles() {
+        return mRemoteTargetHandles;
+    }
+
+    public SplitConfigurationOptions.StagedSplitBounds getStagedSplitBounds() {
+        return mStagedSplitBounds;
+    }
+
+    /**
+     * Container to keep together all the associated objects whose properties need to be updated to
+     * animate a single remote app target
+     */
+    public static class RemoteTargetHandle {
+        private final TaskViewSimulator mTaskViewSimulator;
+        private final TransformParams mTransformParams;
+        @Nullable
+        private AnimatorControllerWithResistance mPlaybackController;
+
+        public RemoteTargetHandle(TaskViewSimulator taskViewSimulator,
+                TransformParams transformParams) {
+            mTransformParams = transformParams;
+            mTaskViewSimulator = taskViewSimulator;
+        }
+
+        public TaskViewSimulator getTaskViewSimulator() {
+            return mTaskViewSimulator;
+        }
+
+        public TransformParams getTransformParams() {
+            return mTransformParams;
+        }
+
+        @Nullable
+        public AnimatorControllerWithResistance getPlaybackController() {
+            return mPlaybackController;
+        }
+
+        public void setPlaybackController(
+                @Nullable AnimatorControllerWithResistance playbackController) {
+            mPlaybackController = playbackController;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 78de300..51a491e 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -15,13 +15,10 @@
  */
 package com.android.quickstep;
 
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-
 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_SELECT;
 import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 
 import android.animation.Animator;
 import android.content.Context;
@@ -40,8 +37,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.AppCloseConfig;
 import com.android.quickstep.util.LauncherSplitScreenListener;
@@ -60,6 +56,7 @@
         RecentsAnimationCallbacks.RecentsAnimationListener{
 
     protected static final Rect TEMP_RECT = new Rect();
+    protected final RemoteTargetGluer mTargetGluer;
 
     protected DeviceProfile mDp;
 
@@ -67,8 +64,7 @@
     protected final RecentsAnimationDeviceState mDeviceState;
     protected final GestureState mGestureState;
 
-    protected final RemoteTargetHandle[] mRemoteTargetHandles;
-    protected SplitConfigurationOptions.StagedSplitBounds mStagedSplitBounds;
+    protected RemoteTargetHandle[] mRemoteTargetHandles;
 
     // Shift in the range of [0, 1].
     // 0 => preview snapShot is completely visible, and hotseat is completely translated down
@@ -98,39 +94,30 @@
         primaryTVS.getOrientationState().update(
                 mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
                 mDeviceState.getRotationTouchHelper().getDisplayRotation());
-        mRemoteTargetHandles = new RemoteTargetHandle[mIsSwipeForStagedSplit ? 2 : 1];
-        mRemoteTargetHandles[0] = new RemoteTargetHandle(primaryTVS, transformParams);
-
-        if (mIsSwipeForStagedSplit) {
-            TaskViewSimulator secondaryTVS = new TaskViewSimulator(context,
-                    gestureState.getActivityInterface());
-            secondaryTVS.getOrientationState().update(
-                    mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
-                    mDeviceState.getRotationTouchHelper().getDisplayRotation());
-            mRemoteTargetHandles[1] = new RemoteTargetHandle(secondaryTVS, new TransformParams());
-        }
+        mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getActivityInterface());
+        mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles();
     }
 
     protected void initTransitionEndpoints(DeviceProfile dp) {
         mDp = dp;
         mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
-                dp, mContext, TEMP_RECT, mRemoteTargetHandles[0].mTaskViewSimulator
+                dp, mContext, TEMP_RECT, mRemoteTargetHandles[0].getTaskViewSimulator()
                         .getOrientationState().getOrientationHandler());
         mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
 
         for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
             PendingAnimation pendingAnimation = new PendingAnimation(mTransitionDragLength * 2);
-            TaskViewSimulator taskViewSimulator = remoteHandle.mTaskViewSimulator;
+            TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
             taskViewSimulator.setDp(dp);
             taskViewSimulator.addAppToOverviewAnim(pendingAnimation, LINEAR);
             AnimatorPlaybackController playbackController =
                     pendingAnimation.createPlaybackController();
 
-            remoteHandle.mPlaybackController = AnimatorControllerWithResistance.createForRecents(
+            remoteHandle.setPlaybackController(AnimatorControllerWithResistance.createForRecents(
                     playbackController, mContext, taskViewSimulator.getOrientationState(),
                     mDp, taskViewSimulator.recentsViewScale, AnimatedFloat.VALUE,
                     taskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE
-            );
+            ));
         }
     }
 
@@ -157,7 +144,7 @@
 
     protected PagedOrientationHandler getOrientationHandler() {
         // OrientationHandler should be independent of remote target, can directly take one
-        return mRemoteTargetHandles[0].mTaskViewSimulator
+        return mRemoteTargetHandles[0].getTaskViewSimulator()
                 .getOrientationState().getOrientationHandler();
     }
 
@@ -246,8 +233,8 @@
         for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
                 i < mRemoteTargetHandlesLength; i++) {
             RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
-            TaskViewSimulator tvs = remoteHandle.mTaskViewSimulator;
-            tvs.apply(remoteHandle.mTransformParams.setProgress(startProgress));
+            TaskViewSimulator tvs = remoteHandle.getTaskViewSimulator();
+            tvs.apply(remoteHandle.getTransformParams().setProgress(startProgress));
 
             startRects[i] = new RectF(tvs.getCurrentCropRect());
             tvs.applyWindowToHomeRotation(outMatrix);
@@ -266,53 +253,10 @@
     /** @return only the TaskViewSimulators from {@link #mRemoteTargetHandles} */
     protected TaskViewSimulator[] getRemoteTaskViewSimulators() {
         return Arrays.stream(mRemoteTargetHandles)
-                .map(remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator)
+                .map(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator())
                 .toArray(TaskViewSimulator[]::new);
     }
 
-    @Override
-    public void onRecentsAnimationStart(RecentsAnimationController controller,
-            RecentsAnimationTargets targets) {
-        ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
-        RemoteAnimationTargetCompat dividerTarget = targets.getNonAppTargetOfType(
-                TYPE_DOCK_DIVIDER);
-        RemoteAnimationTargetCompat primaryTaskTarget;
-        RemoteAnimationTargetCompat secondaryTaskTarget;
-
-        // TODO(b/197568823) Determine if we need to exclude assistant as one of the targets we
-        //  animate
-        if (!mIsSwipeForStagedSplit) {
-            primaryTaskTarget = targets.findTask(mGestureState.getRunningTaskId());
-            mRemoteTargetHandles[0].mTransformParams.setTargetSet(targets);
-
-            if (primaryTaskTarget != null) {
-                mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget);
-            }
-        } else {
-            int[] taskIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
-                    .getRunningSplitTaskIds();
-            // We're in staged split
-            primaryTaskTarget = targets.findTask(taskIds[0]);
-            secondaryTaskTarget = targets.findTask(taskIds[1]);
-            mStagedSplitBounds = new SplitConfigurationOptions.StagedSplitBounds(
-                    primaryTaskTarget.screenSpaceBounds,
-                    secondaryTaskTarget.screenSpaceBounds, dividerTarget.screenSpaceBounds);
-            mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget,
-                    mStagedSplitBounds);
-            mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(secondaryTaskTarget,
-                    mStagedSplitBounds);
-            mRemoteTargetHandles[0].mTransformParams.setTargetSet(
-                    createRemoteAnimationTargetsForTarget(primaryTaskTarget));
-            mRemoteTargetHandles[1].mTransformParams.setTargetSet(
-                    createRemoteAnimationTargetsForTarget(secondaryTaskTarget));
-        }
-    }
-
-    private RemoteAnimationTargets createRemoteAnimationTargetsForTarget(
-            RemoteAnimationTargetCompat target) {
-        return new RemoteAnimationTargets(new RemoteAnimationTargetCompat[]{target},
-                null, null, MODE_CLOSING);
-    }
     /**
      * Creates an animation that transforms the current app window into the home app.
      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
@@ -329,8 +273,8 @@
                 i < mRemoteTargetHandlesLength; i++) {
             RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
             out[i] = getWindowAnimationToHomeInternal(homeAnimationFactory,
-                    targetRect, remoteHandle.mTransformParams, remoteHandle.mTaskViewSimulator,
-                    startRects[i], homeToWindowPositionMap);
+                    targetRect, remoteHandle.getTransformParams(),
+                    remoteHandle.getTaskViewSimulator(), startRects[i], homeToWindowPositionMap);
         }
         return out;
     }
@@ -445,21 +389,6 @@
         }
     }
 
-    /**
-     * Container to keep together all the associated objects whose properties need to be updated to
-     * animate a single remote app target
-     */
-    public static class RemoteTargetHandle {
-        public TaskViewSimulator mTaskViewSimulator;
-        public TransformParams mTransformParams;
-        public AnimatorControllerWithResistance mPlaybackController;
-        public RemoteTargetHandle(TaskViewSimulator taskViewSimulator,
-                TransformParams transformParams) {
-            mTransformParams = transformParams;
-            mTaskViewSimulator = taskViewSimulator;
-        }
-    }
-
     public interface RunningWindowAnim {
         void end();
 
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index d4c94db..23dc913 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -55,7 +55,6 @@
 import android.window.TransitionInfo;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
@@ -67,6 +66,7 @@
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TaskViewSimulator;
@@ -151,28 +151,7 @@
             RemoteAnimationTargetCompat[] wallpaperTargets,
             RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController,
             PendingAnimation out) {
-        boolean isRunningTask = v.isRunningTask();
-        TransformParams params = null;
-        TaskViewSimulator tsv = null;
-        // TODO(b/195675206) handle two TSVs here
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
-            params = v.getRecentsView().getRemoteTargetHandles()[0].mTransformParams;
-            tsv = v.getRecentsView().getRemoteTargetHandles()[0].mTaskViewSimulator;
-        }
-        createRecentsWindowAnimator(v, skipViewChanges, appTargets, wallpaperTargets, nonAppTargets,
-                depthController, out, params, tsv);
-    }
-
-    /**
-     * Creates an animation that controls the window of the opening targets for the recents launch
-     * animation.
-     */
-    public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
-            RemoteAnimationTargetCompat[] appTargets,
-            RemoteAnimationTargetCompat[] wallpaperTargets,
-            RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController,
-            PendingAnimation out, @Nullable TransformParams params,
-            @Nullable TaskViewSimulator tsv) {
+        RecentsView recentsView = v.getRecentsView();
         boolean isQuickSwitch = v.isEndQuickswitchCuj();
         v.setEndQuickswitchCuj(false);
 
@@ -183,16 +162,27 @@
                         inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
         final RemoteAnimationTargetCompat navBarTarget = targets.getNavBarRemoteAnimationTarget();
 
-        if (params == null) {
-            SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
-            targets.addReleaseCheck(applier);
+        SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
+        targets.addReleaseCheck(applier);
 
-            params = new TransformParams()
-                    .setSyncTransactionApplier(applier)
-                    .setTargetSet(targets);
+        RemoteTargetHandle[] remoteTargetHandles;
+        RemoteTargetHandle[] recentsViewHandles = recentsView.getRemoteTargetHandles();
+        if (v.isRunningTask()) {
+            // Re-use existing handles
+            remoteTargetHandles = recentsViewHandles;
+        } else {
+            RemoteTargetGluer gluer = new RemoteTargetGluer(v.getContext(),
+                    recentsView.getSizeStrategy(), targets);
+            if (recentsViewHandles != null && recentsViewHandles.length > 1) {
+                remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets);
+            } else {
+                remoteTargetHandles = gluer.assignTargets(targets);
+            }
+        }
+        for (RemoteTargetHandle remoteTargetGluer : remoteTargetHandles) {
+            remoteTargetGluer.getTransformParams().setSyncTransactionApplier(applier);
         }
 
-        final RecentsView recentsView = v.getRecentsView();
         int taskIndex = recentsView.indexOfChild(v);
         Context context = v.getContext();
         DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
@@ -202,45 +192,51 @@
         float gridTranslationSecondary = recentsView.getGridTranslationSecondary(taskIndex);
         int startScroll = recentsView.getScrollOffset(taskIndex);
 
-        TaskViewSimulator topMostSimulator = null;
+        RemoteTargetHandle[] topMostSimulators = null;
 
-        if (tsv == null && targets.apps.length > 0) {
-            tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
-            tsv.setDp(dp);
+        if (!v.isRunningTask()) {
+            // TVSs already initialized from the running task, no need to re-init
+            for (RemoteTargetHandle targetHandle : remoteTargetHandles) {
+                TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator();
+                tvsLocal.setDp(dp);
 
-            // RecentsView never updates the display rotation until swipe-up so the value may
-            // be stale. Use the display value instead.
-            int displayRotation = DisplayController.INSTANCE.get(context).getInfo().rotation;
-            tsv.getOrientationState().update(displayRotation, displayRotation);
+                // RecentsView never updates the display rotation until swipe-up so the value may
+                // be stale. Use the display value instead.
+                int displayRotation = DisplayController.INSTANCE.get(context).getInfo().rotation;
+                tvsLocal.getOrientationState().update(displayRotation, displayRotation);
 
-            tsv.setPreview(targets.apps[targets.apps.length - 1]);
-            tsv.fullScreenProgress.value = 0;
-            tsv.recentsViewScale.value = 1;
-            if (showAsGrid) {
-                tsv.taskSecondaryTranslation.value = gridTranslationSecondary;
+                tvsLocal.fullScreenProgress.value = 0;
+                tvsLocal.recentsViewScale.value = 1;
+                if (showAsGrid) {
+                    tvsLocal.taskSecondaryTranslation.value = gridTranslationSecondary;
+                }
+                tvsLocal.setScroll(startScroll);
+
+                // Fade in the task during the initial 20% of the animation
+                out.addFloat(targetHandle.getTransformParams(), TransformParams.TARGET_ALPHA, 0, 1,
+                        clampToProgress(LINEAR, 0, 0.2f));
             }
-            tsv.setScroll(startScroll);
-
-            // Fade in the task during the initial 20% of the animation
-            out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
-                    clampToProgress(LINEAR, 0, 0.2f));
         }
 
-        if (tsv != null) {
-            out.setFloat(tsv.fullScreenProgress,
+        for (RemoteTargetHandle targetHandle : remoteTargetHandles) {
+            TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator();
+            out.setFloat(tvsLocal.fullScreenProgress,
                     AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
-            out.setFloat(tsv.recentsViewScale,
-                    AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
+            out.setFloat(tvsLocal.recentsViewScale,
+                    AnimatedFloat.VALUE, tvsLocal.getFullScreenScale(),
+                    TOUCH_RESPONSE_INTERPOLATOR);
             if (showAsGrid) {
-                out.setFloat(tsv.taskSecondaryTranslation, AnimatedFloat.VALUE, 0,
+                out.setFloat(tvsLocal.taskSecondaryTranslation, AnimatedFloat.VALUE, 0,
                         TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL);
             }
-            out.setFloat(tsv.recentsViewScroll, AnimatedFloat.VALUE, 0,
+            out.setFloat(tvsLocal.recentsViewScroll, AnimatedFloat.VALUE, 0,
                     TOUCH_RESPONSE_INTERPOLATOR);
 
-            TaskViewSimulator finalTsv = tsv;
-            TransformParams finalParams = params;
-            out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
+            out.addOnFrameCallback(() -> {
+                for (RemoteTargetHandle handle : remoteTargetHandles) {
+                    handle.getTaskViewSimulator().apply(handle.getTransformParams());
+                }
+            });
             if (navBarTarget != null) {
                 final Rect cropRect = new Rect();
                 out.addOnFrameListener(new MultiValueUpdateListener() {
@@ -253,15 +249,20 @@
                     public void onUpdate(float percent, boolean initOnly) {
                         final SurfaceParams.Builder navBuilder =
                                 new SurfaceParams.Builder(navBarTarget.leash);
-                        if (mNavFadeIn.value > mNavFadeIn.getStartValue()) {
-                            finalTsv.getCurrentCropRect().round(cropRect);
-                            navBuilder.withMatrix(finalTsv.getCurrentMatrix())
-                                    .withWindowCrop(cropRect)
-                                    .withAlpha(mNavFadeIn.value);
-                        } else {
-                            navBuilder.withAlpha(mNavFadeOut.value);
+
+                        // TODO Do we need to operate over multiple TVSs for the navbar leash?
+                        for (RemoteTargetHandle handle : remoteTargetHandles) {
+                            if (mNavFadeIn.value > mNavFadeIn.getStartValue()) {
+                                TaskViewSimulator taskViewSimulator = handle.getTaskViewSimulator();
+                                taskViewSimulator.getCurrentCropRect().round(cropRect);
+                                navBuilder.withMatrix(taskViewSimulator.getCurrentMatrix())
+                                        .withWindowCrop(cropRect)
+                                        .withAlpha(mNavFadeIn.value);
+                            } else {
+                                navBuilder.withAlpha(mNavFadeOut.value);
+                            }
+                            handle.getTransformParams().applySurfaceParams(navBuilder.build());
                         }
-                        finalParams.applySurfaceParams(navBuilder.build());
                     }
                 });
             } else if (inLiveTileMode) {
@@ -273,14 +274,16 @@
                     controller.animateNavigationBarToApp(RECENTS_LAUNCH_DURATION);
                 }
             }
-            topMostSimulator = tsv;
+            topMostSimulators = remoteTargetHandles;
         }
 
-        if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulator != null) {
+        if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulators.length > 0) {
             out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
 
-            TaskViewSimulator simulatorToCopy = topMostSimulator;
-            simulatorToCopy.apply(params);
+            RemoteTargetHandle[] simulatorCopies = topMostSimulators;
+            for (RemoteTargetHandle handle : simulatorCopies) {
+                handle.getTaskViewSimulator().apply(handle.getTransformParams());
+            }
 
             // Mt represents the overall transformation on the thumbnailView relative to the
             // Launcher's rootView
@@ -294,36 +297,49 @@
             // During animation we apply transformation on the thumbnailView (and not the rootView)
             // to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
             //    Mt K(0)` K(t) Mt`
-            TaskThumbnailView ttv = v.getThumbnail();
-            RectF tvBounds = new RectF(0, 0,  ttv.getWidth(), ttv.getHeight());
-            float[] tvBoundsMapped = new float[]{0, 0,  ttv.getWidth(), ttv.getHeight()};
-            getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false);
-            RectF tvBoundsInRoot = new RectF(
-                    tvBoundsMapped[0], tvBoundsMapped[1],
-                    tvBoundsMapped[2], tvBoundsMapped[3]);
+            TaskThumbnailView[] thumbnails = v.getThumbnails();
+            Matrix[] mt = new Matrix[simulatorCopies.length];
+            Matrix[] mti = new Matrix[simulatorCopies.length];
+            for (int i = 0; i < thumbnails.length; i++) {
+                TaskThumbnailView ttv = thumbnails[i];
+                RectF localBounds = new RectF(0, 0,  ttv.getWidth(), ttv.getHeight());
+                float[] tvBoundsMapped = new float[]{0, 0,  ttv.getWidth(), ttv.getHeight()};
+                getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false);
+                RectF localBoundsInRoot = new RectF(
+                        tvBoundsMapped[0], tvBoundsMapped[1],
+                        tvBoundsMapped[2], tvBoundsMapped[3]);
+                Matrix localMt = new Matrix();
+                localMt.setRectToRect(localBounds, localBoundsInRoot, ScaleToFit.FILL);
+                mt[i] = localMt;
 
-            Matrix mt = new Matrix();
-            mt.setRectToRect(tvBounds, tvBoundsInRoot, ScaleToFit.FILL);
+                Matrix localMti = new Matrix();
+                localMti.invert(localMt);
+                mti[i] = localMti;
+            }
 
-            Matrix mti = new Matrix();
-            mt.invert(mti);
-
-            Matrix k0i = new Matrix();
-            simulatorToCopy.getCurrentMatrix().invert(k0i);
-
+            Matrix[] k0i = new Matrix[simulatorCopies.length];
+            for (int i = 0; i < simulatorCopies.length; i++) {
+                k0i[i] = new Matrix();
+                simulatorCopies[i].getTaskViewSimulator().getCurrentMatrix().invert(k0i[i]);
+            }
             Matrix animationMatrix = new Matrix();
             out.addOnFrameCallback(() -> {
-                animationMatrix.set(mt);
-                animationMatrix.postConcat(k0i);
-                animationMatrix.postConcat(simulatorToCopy.getCurrentMatrix());
-                animationMatrix.postConcat(mti);
-                ttv.setAnimationMatrix(animationMatrix);
+                for (int i = 0; i < simulatorCopies.length; i++) {
+                    animationMatrix.set(mt[i]);
+                    animationMatrix.postConcat(k0i[i]);
+                    animationMatrix.postConcat(simulatorCopies[i]
+                            .getTaskViewSimulator().getCurrentMatrix());
+                    animationMatrix.postConcat(mti[i]);
+                    thumbnails[i].setAnimationMatrix(animationMatrix);
+                }
             });
 
             out.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    ttv.setAnimationMatrix(null);
+                    for (TaskThumbnailView ttv : thumbnails) {
+                        ttv.setAnimationMatrix(null);
+                    }
                 }
             });
         }
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index c2c8f61..1f75936 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -46,7 +46,6 @@
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.OverviewComponentObserver;
@@ -260,16 +259,16 @@
 
         void initDp(DeviceProfile dp) {
             initTransitionEndpoints(dp);
-            mRemoteTargetHandles[0].mTaskViewSimulator.setPreviewBounds(
+            mRemoteTargetHandles[0].getTaskViewSimulator().setPreviewBounds(
                     new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets());
         }
 
         @Override
         public void updateFinalShift() {
-            mRemoteTargetHandles[0].mPlaybackController
+            mRemoteTargetHandles[0].getPlaybackController()
                     .setProgress(mCurrentShift.value, mDragLengthFactor);
-            mRemoteTargetHandles[0].mTaskViewSimulator.apply(
-                    mRemoteTargetHandles[0].mTransformParams);
+            mRemoteTargetHandles[0].getTaskViewSimulator().apply(
+                    mRemoteTargetHandles[0].getTransformParams());
         }
 
         AnimatedFloat getCurrentShift() {
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 841e578..a4db596 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -600,17 +600,7 @@
             width = Math.min(currentSize.x, currentSize.y);
             height = Math.max(currentSize.x, currentSize.y);
         }
-
-        DeviceProfile bestMatch = idp.supportedProfiles.get(0);
-        float minDiff = Float.MAX_VALUE;
-        for (DeviceProfile profile : idp.supportedProfiles) {
-            float diff = Math.abs(profile.widthPx - width) + Math.abs(profile.heightPx - height);
-            if (diff < minDiff) {
-                minDiff = diff;
-                bestMatch = profile;
-            }
-        }
-        return bestMatch;
+        return idp.getBestMatch(width, height);
     }
 
     private static String nameAndAddress(Object obj) {
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index a089e73..849a7bc 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -296,9 +296,8 @@
         }
 
         float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
-        mCurrentFullscreenParams.setProgress(
-                fullScreenProgress, recentsViewScale.value, /*taskViewScale=*/1f, mTaskRect.width(),
-                mDp, mPositionHelper);
+        mCurrentFullscreenParams.setProgress(fullScreenProgress, recentsViewScale.value,
+                /* taskViewScale= */1f, mTaskRect.width(), mDp, mPositionHelper);
 
         // Apply thumbnail matrix
         RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index a1befc5..5a86464 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -97,8 +97,9 @@
 
         floatingView.mOrientationHandler =
                 originalView.getRecentsView().getPagedOrientationHandler();
-        floatingView.mSplitPlaceholderView.setIcon(originalView.getIconView());
-        floatingView.mSplitPlaceholderView.getIcon()
+        floatingView.mSplitPlaceholderView.setIconView(originalView.getIconView(),
+                launcher.getDeviceProfile().overviewTaskIconDrawableSizePx);
+        floatingView.mSplitPlaceholderView.getIconView()
                 .setRotation(floatingView.mOrientationHandler.getDegreesRotated());
         parent.addView(floatingView);
         return floatingView;
@@ -141,8 +142,8 @@
         // 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);
+        mOrientationHandler.setPrimaryScale(mSplitPlaceholderView.getIconView(), childScaleX);
+        mOrientationHandler.setSecondaryScale(mSplitPlaceholderView.getIconView(), childScaleY);
     }
 
     protected void initPosition(RectF pos, InsettableFrameLayout.LayoutParams lp) {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index d3077ad..8562719 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -113,6 +113,11 @@
     }
 
     @Override
+    public TaskThumbnailView[] getThumbnails() {
+        return new TaskThumbnailView[]{mSnapshotView, mSnapshotView2};
+    }
+
+    @Override
     public void onRecycle() {
         super.onRecycle();
         mSnapshotView2.setThumbnail(mSecondaryTask, null);
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 1b28c53..ac779b1 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -53,13 +53,15 @@
     @IntDef(flag = true, value = {
             HIDDEN_NON_ZERO_ROTATION,
             HIDDEN_NO_TASKS,
-            HIDDEN_NO_RECENTS})
+            HIDDEN_NO_RECENTS,
+            HIDDEN_FOCUSED_SCROLL})
     @Retention(RetentionPolicy.SOURCE)
     public @interface ActionsHiddenFlags { }
 
     public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 0;
     public static final int HIDDEN_NO_TASKS = 1 << 1;
     public static final int HIDDEN_NO_RECENTS = 1 << 2;
+    public static final int HIDDEN_FOCUSED_SCROLL = 1 << 3;
 
     @IntDef(flag = true, value = {
             DISABLED_SCROLLING,
@@ -76,7 +78,6 @@
     private static final int INDEX_VISIBILITY_ALPHA = 1;
     private static final int INDEX_FULLSCREEN_ALPHA = 2;
     private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3;
-    private static final int INDEX_SCROLL_ALPHA = 4;
 
     private final MultiValueAlpha mMultiValueAlpha;
     private View mSplitButton;
@@ -196,10 +197,6 @@
         return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA);
     }
 
-    public AlphaProperty getScrollAlpha() {
-        return mMultiValueAlpha.getProperty(INDEX_SCROLL_ALPHA);
-    }
-
     private void updateHorizontalPadding() {
         setPadding(mInsets.left, 0, mInsets.right, 0);
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 5e4cf06..e85adcc 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -19,7 +19,6 @@
 import static android.view.Surface.ROTATION_0;
 import static android.view.View.MeasureSpec.EXACTLY;
 import static android.view.View.MeasureSpec.makeMeasureSpec;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
 import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
@@ -54,7 +53,6 @@
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -147,7 +145,8 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
 import com.android.quickstep.RemoteAnimationTargets;
-import com.android.quickstep.SwipeUpAnimationLogic.RemoteTargetHandle;
+import com.android.quickstep.RemoteTargetGluer;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
@@ -326,7 +325,7 @@
                     view.runActionOnRemoteHandles(new Consumer<RemoteTargetHandle>() {
                         @Override
                         public void accept(RemoteTargetHandle remoteTargetHandle) {
-                            remoteTargetHandle.mTaskViewSimulator.recentsViewScale.value =
+                            remoteTargetHandle.getTaskViewSimulator().recentsViewScale.value =
                                     scale;
                         }
                     });
@@ -829,7 +828,7 @@
         mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
         TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
         mSyncTransactionApplier = new SurfaceTransactionApplier(this);
-        runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.mTransformParams
+        runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
                 .setSyncTransactionApplier(mSyncTransactionApplier));
         RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
         mIPipAnimationListener.setActivityAndRecentsView(mActivity, this);
@@ -848,7 +847,7 @@
         mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
         mSyncTransactionApplier = null;
-        runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.mTransformParams
+        runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
                 .setSyncTransactionApplier(null));
         executeSideTaskLaunchCallback();
         RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
@@ -938,7 +937,7 @@
     public void launchSideTaskInLiveTileModeForRestartedApp(int taskId) {
         int runningTaskViewId = getTaskViewIdFromTaskId(taskId);
         if (mRunningTaskViewId != -1 && mRunningTaskViewId == runningTaskViewId) {
-            TransformParams params = mRemoteTargetHandles[0].mTransformParams;
+            TransformParams params = mRemoteTargetHandles[0].getTransformParams();
             RemoteAnimationTargets targets = params.getTargetSet();
             if (targets != null && targets.findTask(taskId) != null) {
                 launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers,
@@ -1482,10 +1481,11 @@
             // to reset the params after it settles in Overview from swipe up so that we don't
             // render with obsolete param values.
             runActionOnRemoteHandles(remoteTargetHandle -> {
-                remoteTargetHandle.mTaskViewSimulator.taskPrimaryTranslation.value = 0;
-                remoteTargetHandle.mTaskViewSimulator.taskSecondaryTranslation.value = 0;
-                remoteTargetHandle.mTaskViewSimulator.fullScreenProgress.value = 0;
-                remoteTargetHandle.mTaskViewSimulator.recentsViewScale.value = 1;
+                TaskViewSimulator simulator = remoteTargetHandle.getTaskViewSimulator();
+                simulator.taskPrimaryTranslation.value = 0;
+                simulator.taskSecondaryTranslation.value = 0;
+                simulator.fullScreenProgress.value = 0;
+                simulator.recentsViewScale.value = 1;
             });
 
             // Similar to setRunningTaskHidden below, reapply the state before runningTaskView is
@@ -1541,7 +1541,7 @@
 
         // Propagate DeviceProfile change event.
         runActionOnRemoteHandles(
-                remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator.setDp(dp));
+                remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator().setDp(dp));
         mActionsView.setDp(dp);
         mOrientationState.setDeviceProfile(dp);
 
@@ -1691,28 +1691,28 @@
 
             // After scrolling, update the visible task's data
             loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
-
-            // After scrolling, update ActionsView's visibility.
-            updateActionsViewScrollAlpha();
         }
 
+        // Update ActionsView's visibility when scroll changes.
+        updateActionsViewFocusedScroll();
+
         // Update the high res thumbnail loader state
         mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast);
         return scrolling;
     }
 
-    private void updateActionsViewScrollAlpha() {
-        float scrollAlpha = 1f;
+    private void updateActionsViewFocusedScroll() {
+        boolean hiddenFocusedScroll;
         if (showAsGrid()) {
             TaskView focusedTaskView = getFocusedTaskView();
-            if (focusedTaskView != null) {
-                float scrollDiff = Math.abs(getScrollForPage(indexOfChild(focusedTaskView))
-                        - mOrientationHandler.getPrimaryScroll(this));
-                float delta = (mGridSideMargin - scrollDiff) / (float) mGridSideMargin;
-                scrollAlpha = Utilities.boundToRange(delta, 0, 1);
-            }
+            hiddenFocusedScroll = focusedTaskView == null
+                    || getScrollForPage(indexOfChild(focusedTaskView))
+                    != mOrientationHandler.getPrimaryScroll(this);
+        } else {
+            hiddenFocusedScroll = false;
         }
-        mActionsView.getScrollAlpha().setValue(scrollAlpha);
+        mActionsView.updateHiddenFlags(OverviewActionsView.HIDDEN_FOCUSED_SCROLL,
+                hiddenFocusedScroll);
     }
 
     /**
@@ -1867,7 +1867,7 @@
         setCurrentTask(-1);
         mIgnoreResetTaskId = -1;
         mTaskListChangeId = -1;
-        mFocusedTaskViewId = getTaskViewCount() > 0 ? getTaskViewAt(0).getTaskViewId() : -1;
+        mFocusedTaskViewId = -1;
 
         if (mRecentsAnimationController != null) {
             if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) {
@@ -1878,7 +1878,7 @@
             }
         }
         setEnableDrawingLiveTile(false);
-        runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.mTransformParams
+        runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
                 .setTargetSet(null));
 
         // These are relatively expensive and don't need to be done this frame (RecentsView isn't
@@ -2591,7 +2591,7 @@
         // alpha is set to 0 so that it can be recycled in the view pool properly
         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask()) {
             runActionOnRemoteHandles(remoteTargetHandle -> {
-                TransformParams params = remoteTargetHandle.mTransformParams;
+                TransformParams params = remoteTargetHandle.getTransformParams();
                 anim.setFloat(params, TransformParams.TARGET_ALPHA, 0,
                         clampToProgress(FINAL_FRAME, 0, 0.5f));
             });
@@ -2617,7 +2617,7 @@
                 && taskView.isRunningTask()) {
             anim.addOnFrameCallback(() -> {
                 runActionOnRemoteHandles(
-                        remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator
+                        remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
                                 .taskSecondaryTranslation.value = mOrientationHandler
                                 .getSecondaryValue(taskView.getTranslationX(),
                                         taskView.getTranslationY()
@@ -2786,7 +2786,7 @@
                         anim.addOnFrameCallback(() -> {
                             runActionOnRemoteHandles(
                                     remoteTargetHandle ->
-                                            remoteTargetHandle.mTaskViewSimulator
+                                            remoteTargetHandle.getTaskViewSimulator()
                                                     .taskPrimaryTranslation.value =
                                                     TaskView.GRID_END_TRANSLATION_X.get(taskView));
                             redrawLiveTile();
@@ -2859,7 +2859,7 @@
                         anim.addOnFrameCallback(() -> {
                             runActionOnRemoteHandles(
                                     remoteTargetHandle ->
-                                            remoteTargetHandle.mTaskViewSimulator
+                                            remoteTargetHandle.getTaskViewSimulator()
                                                     .taskPrimaryTranslation.value =
                                                     mOrientationHandler.getPrimaryValue(
                                                             child.getTranslationX(),
@@ -3004,6 +3004,10 @@
                                                     : getBottomRowIdArray();
                                     int snappedIndex = taskViewIdArray.indexOf(snappedTaskViewId);
                                     taskViewIdArray.removeValue(dismissedTaskViewId);
+                                    if (finalNextFocusedTaskView != null) {
+                                        taskViewIdArray.removeValue(
+                                                finalNextFocusedTaskView.getTaskViewId());
+                                    }
                                     if (snappedIndex < taskViewIdArray.size()) {
                                         taskViewIdToSnapTo = taskViewIdArray.get(snappedIndex);
                                     } else if (snappedIndex == taskViewIdArray.size()) {
@@ -3092,9 +3096,9 @@
                             }
                         }
                         setCurrentPage(pageToSnapTo);
-                        // Update various scroll depedent UI.
+                        // Update various scroll-dependent UI.
                         dispatchScrollChanged();
-                        updateActionsViewScrollAlpha();
+                        updateActionsViewFocusedScroll();
                         if (isClearAllHidden()) {
                             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING,
                                     false);
@@ -3533,7 +3537,7 @@
         mLastComputedTaskEndPushOutDistance = null;
         updatePageOffsets();
         runActionOnRemoteHandles(
-                remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator
+                remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
                         .setScroll(getScrollOffset()));
         setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO
                 : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
@@ -3599,7 +3603,7 @@
             if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
                     && i == getRunningTaskIndex()) {
                 runActionOnRemoteHandles(
-                        remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator
+                        remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
                                 .taskPrimaryTranslation.value = totalTranslation);
                 redrawLiveTile();
             }
@@ -3707,7 +3711,7 @@
             task.getTaskResistanceTranslationProperty().set(task, translation / getScaleY());
         }
         runActionOnRemoteHandles(
-                remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator
+                remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
                         .recentsViewSecondaryTranslation.value = translation);
     }
 
@@ -4023,7 +4027,7 @@
                     && runningTaskIndex != taskIndex) {
                 for (RemoteTargetHandle remoteHandle : recentsView.getRemoteTargetHandles()) {
                     anim.play(ObjectAnimator.ofFloat(
-                            remoteHandle.mTaskViewSimulator.taskPrimaryTranslation,
+                            remoteHandle.getTaskViewSimulator().taskPrimaryTranslation,
                             AnimatedFloat.VALUE,
                             primaryTranslation));
                 }
@@ -4107,7 +4111,7 @@
         mPendingAnimation.add(anim);
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             runActionOnRemoteHandles(
-                    remoteTargetHandle -> remoteTargetHandle.mTaskViewSimulator
+                    remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
                             .addOverviewToAppAnim(mPendingAnimation, interpolator));
             mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
         }
@@ -4201,9 +4205,9 @@
 
     public void redrawLiveTile() {
         runActionOnRemoteHandles(remoteTargetHandle -> {
-            TransformParams params = remoteTargetHandle.mTransformParams;
+            TransformParams params = remoteTargetHandle.getTransformParams();
             if (params.getTargetSet() != null) {
-                remoteTargetHandle.mTaskViewSimulator.apply(params);
+                remoteTargetHandle.getTaskViewSimulator().apply(params);
             }
         });
     }
@@ -4224,44 +4228,14 @@
             recentsAnimationTargets.addReleaseCheck(mSyncTransactionApplier);
         }
 
-        RemoteAnimationTargetCompat dividerTarget =
-                recentsAnimationTargets.getNonAppTargetOfType(TYPE_DOCK_DIVIDER);
-        // TODO Consolidate this shared code with SwipeUpAnimationLogic (or maybe just reuse
-        //  what that class has and pass it into here)
-        mRemoteTargetHandles = new RemoteTargetHandle[dividerTarget == null ? 1 : 2];
-        TaskViewSimulator primaryTvs = createTaskViewSimulator();
-        mRemoteTargetHandles[0] = new RemoteTargetHandle(primaryTvs, new TransformParams());
-        if (dividerTarget == null) {
-            mRemoteTargetHandles[0].mTaskViewSimulator
-                    .setPreview(recentsAnimationTargets.apps[0], null);
-            mRemoteTargetHandles[0].mTransformParams.setTargetSet(recentsAnimationTargets);
-        } else {
-            TaskViewSimulator secondaryTvs = createTaskViewSimulator();
-            secondaryTvs.setOrientationState(mOrientationState);
-            secondaryTvs.recentsViewScale.value = 1;
-
-            mRemoteTargetHandles[1] = new RemoteTargetHandle(secondaryTvs, new TransformParams());
-            RemoteAnimationTargetCompat primaryTaskTarget = recentsAnimationTargets.apps[0];
-            RemoteAnimationTargetCompat secondaryTaskTarget = recentsAnimationTargets.apps[1];
-            mSplitBoundsConfig = new SplitConfigurationOptions.StagedSplitBounds(
-                    primaryTaskTarget.screenSpaceBounds,
-                    secondaryTaskTarget.screenSpaceBounds, dividerTarget.screenSpaceBounds);
-            mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget,
-                    mSplitBoundsConfig);
-            mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(secondaryTaskTarget,
-                    mSplitBoundsConfig);
-            RemoteAnimationTargets rats = new RemoteAnimationTargets(
-                    new RemoteAnimationTargetCompat[]{primaryTaskTarget},
-                    recentsAnimationTargets.wallpapers, recentsAnimationTargets.nonApps,
-                    MODE_CLOSING
-            );
-            RemoteAnimationTargets splitRats = new RemoteAnimationTargets(
-                    new RemoteAnimationTargetCompat[]{secondaryTaskTarget},
-                    recentsAnimationTargets.wallpapers, recentsAnimationTargets.nonApps,
-                    MODE_CLOSING
-            );
-            mRemoteTargetHandles[0].mTransformParams.setTargetSet(rats);
-            mRemoteTargetHandles[1].mTransformParams.setTargetSet(splitRats);
+        RemoteTargetGluer gluer = new RemoteTargetGluer(getContext(), getSizeStrategy());
+        mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(recentsAnimationTargets);
+        mSplitBoundsConfig = gluer.getStagedSplitBounds();
+        for (RemoteTargetHandle remoteTargetHandle : mRemoteTargetHandles) {
+            TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator();
+            tvs.setOrientationState(mOrientationState);
+            tvs.setDp(mActivity.getDeviceProfile());
+            tvs.recentsViewScale.value = 1;
         }
     }
 
@@ -4276,14 +4250,6 @@
         }
     }
 
-    private TaskViewSimulator createTaskViewSimulator() {
-        TaskViewSimulator tvs = new TaskViewSimulator(getContext(), getSizeStrategy());
-        tvs.setOrientationState(mOrientationState);
-        tvs.setDp(mActivity.getDeviceProfile());
-        tvs.recentsViewScale.value = 1;
-        return tvs;
-    }
-
     public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
         finishRecentsAnimation(toRecents, true /* shouldPip */, onFinishComplete);
     }
@@ -4563,7 +4529,7 @@
     public void setOverviewGridEnabled(boolean overviewGridEnabled) {
         if (mOverviewGridEnabled != overviewGridEnabled) {
             mOverviewGridEnabled = overviewGridEnabled;
-            updateActionsViewScrollAlpha();
+            updateActionsViewFocusedScroll();
             // Request layout to ensure scroll position is recalculated with updated mGridProgress.
             requestLayout();
         }
@@ -4778,7 +4744,7 @@
 
     private void dispatchScrollChanged() {
         runActionOnRemoteHandles(remoteTargetHandle ->
-                remoteTargetHandle.mTaskViewSimulator.setScroll(getScrollOffset()));
+                remoteTargetHandle.getTaskViewSimulator().setScroll(getScrollOffset()));
         for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
             mScrollListeners.get(i).onScrollChanged();
         }
diff --git a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
index a712d1a..845e13e 100644
--- a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
@@ -24,8 +24,6 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.quickstep.util.SplitSelectStateController;
-
 public class SplitPlaceholderView extends FrameLayout {
 
     public static final FloatProperty<SplitPlaceholderView> ALPHA_FLOAT =
@@ -42,34 +40,26 @@
                 }
             };
 
-    private SplitSelectStateController mSplitController;
-    private IconView mIcon;
+    private IconView mIconView;
 
     public SplitPlaceholderView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
-    public void init(SplitSelectStateController controller) {
-        this.mSplitController = controller;
-    }
-
-    public SplitSelectStateController getSplitController() {
-        return mSplitController;
-    }
-
     @Nullable
-    public IconView getIcon() {
-        return mIcon;
+    public IconView getIconView() {
+        return mIconView;
     }
 
-    public void setIcon(IconView icon) {
-        if (mIcon == null) {
-            mIcon = new IconView(getContext());
-            addView(mIcon);
+    public void setIconView(IconView iconView, int iconSize) {
+        if (mIconView == null) {
+            mIconView = new IconView(getContext());
+            addView(mIconView);
         }
-        mIcon.setDrawable(icon.getDrawable());
-        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(icon.getLayoutParams());
+        mIconView.setDrawable(iconView.getDrawable());
+        mIconView.setDrawableSize(iconSize, iconSize);
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(iconView.getLayoutParams());
         params.gravity = Gravity.CENTER;
-        mIcon.setLayoutParams(params);
+        mIconView.setLayoutParams(params);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 0e6c0fd..b650c95 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -91,7 +91,7 @@
 import com.android.launcher3.util.ViewPool.Reusable;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RemoteAnimationTargets;
-import com.android.quickstep.SwipeUpAnimationLogic.RemoteTargetHandle;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskIconCache;
 import com.android.quickstep.TaskOverlayFactory;
@@ -592,6 +592,11 @@
         return mSnapshotView;
     }
 
+    /** TODO(b/197033698) Remove all usages of above method and migrate to this one */
+    public TaskThumbnailView[] getThumbnails() {
+        return new TaskThumbnailView[]{mSnapshotView};
+    }
+
     public IconView getIconView() {
         return mIconView;
     }
@@ -618,13 +623,12 @@
             mIsClickableAsLiveTile = false;
             RecentsView recentsView = getRecentsView();
             RemoteAnimationTargets targets;
-            RemoteTargetHandle[] remoteTargetHandles =
-                    recentsView.mRemoteTargetHandles;
+            RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
             if (remoteTargetHandles.length == 1) {
-                targets = remoteTargetHandles[0].mTransformParams.getTargetSet();
+                targets = remoteTargetHandles[0].getTransformParams().getTargetSet();
             } else {
-                TransformParams topLeftParams = remoteTargetHandles[0].mTransformParams;
-                TransformParams rightBottomParams = remoteTargetHandles[1].mTransformParams;
+                TransformParams topLeftParams = remoteTargetHandles[0].getTransformParams();
+                TransformParams rightBottomParams = remoteTargetHandles[1].getTransformParams();
                 RemoteAnimationTargetCompat[] apps = Stream.concat(
                         Arrays.stream(topLeftParams.getTargetSet().apps),
                         Arrays.stream(rightBottomParams.getTargetSet().apps))
@@ -1067,7 +1071,6 @@
 
     private void setNonGridScale(float nonGridScale) {
         mNonGridScale = nonGridScale;
-        updateCornerRadius();
         applyScale();
     }
 
@@ -1098,6 +1101,7 @@
         scale *= mDismissScale;
         setScaleX(scale);
         setScaleY(scale);
+        updateSnapshotRadius();
     }
 
     /**
@@ -1413,29 +1417,25 @@
         mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
         mSnapshotView.getTaskOverlay().setFullscreenProgress(progress);
 
-        updateCornerRadius();
+        updateSnapshotRadius();
 
-        mSnapshotView.setFullscreenParams(mCurrentFullscreenParams);
         mOutlineProvider.updateParams(
                 mCurrentFullscreenParams,
                 mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx);
         invalidateOutline();
     }
 
-    private void updateCornerRadius() {
+    private void updateSnapshotRadius() {
         updateCurrentFullscreenParams(mSnapshotView.getPreviewPositionHelper());
+        mSnapshotView.setFullscreenParams(mCurrentFullscreenParams);
     }
 
     void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) {
         if (getRecentsView() == null) {
             return;
         }
-        mCurrentFullscreenParams.setProgress(
-                mFullscreenProgress,
-                getRecentsView().getScaleX(),
-                mNonGridScale,
-                getWidth(), mActivity.getDeviceProfile(),
-                previewPositionHelper);
+        mCurrentFullscreenParams.setProgress(mFullscreenProgress, getRecentsView().getScaleX(),
+                getScaleX(), getWidth(), mActivity.getDeviceProfile(), previewPositionHelper);
     }
 
     /**
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java b/quickstep/tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
similarity index 89%
rename from quickstep/robolectric_tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
rename to quickstep/tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
index 656379f..47ef13b 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
+++ b/quickstep/tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
@@ -19,33 +19,34 @@
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_90;
 
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
 import android.content.Context;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
 import com.android.quickstep.FallbackActivityInterface;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.LooperMode.Mode;
 
 /**
  * Tests for {@link RecentsOrientedState}
  */
-@RunWith(RobolectricTestRunner.class)
-@LooperMode(Mode.PAUSED)
+@SmallTest
+@RunWith(AndroidJUnit4.class)
 public class RecentsOrientedStateTest {
 
     private RecentsOrientedState mR1, mR2;
 
     @Before
     public void setup() {
-        Context context = RuntimeEnvironment.application;
+        Context context = getApplicationContext();
         mR1 = new RecentsOrientedState(context, FallbackActivityInterface.INSTANCE, i -> { });
         mR2 = new RecentsOrientedState(context, FallbackActivityInterface.INSTANCE, i -> { });
         assertEquals(mR1.getStateId(), mR2.getStateId());
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
new file mode 100644
index 0000000..9c5cfcd
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.ReflectionHelpers;
+import com.android.quickstep.FallbackActivityInterface;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TaskViewSimulatorTest {
+
+    @Test
+    public void taskProperlyScaled_portrait_noRotation_sameInsets1() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(1200, 2450)
+                .withInsets(new Rect(0, 80, 0, 120))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_portrait_noRotation_sameInsets2() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(1200, 2450)
+                .withInsets(new Rect(55, 80, 55, 120))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_landscape_noRotation_sameInsets1() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(2450, 1250)
+                .withInsets(new Rect(0, 80, 0, 40))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_landscape_noRotation_sameInsets2() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(2450, 1250)
+                .withInsets(new Rect(0, 80, 120, 0))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_landscape_noRotation_sameInsets3() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(2450, 1250)
+                .withInsets(new Rect(55, 80, 55, 120))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_landscape_rotated() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(1200, 2450)
+                .withInsets(new Rect(0, 80, 0, 120))
+                .withAppBounds(
+                        new Rect(0, 0, 2450, 1200),
+                        new Rect(0, 80, 0, 120),
+                        Surface.ROTATION_90)
+                .verifyNoTransforms();
+    }
+
+    private static class TaskMatrixVerifier extends TransformParams {
+
+        private Point mDisplaySize = new Point();
+        private Rect mDisplayInsets = new Rect();
+        private Rect mAppBounds = new Rect();
+        private Rect mLauncherInsets = new Rect();
+
+        private Rect mAppInsets;
+
+        private int mAppRotation = -1;
+        private DeviceProfile mDeviceProfile;
+
+        TaskMatrixVerifier withLauncherSize(int width, int height) {
+            mDisplaySize.set(width, height);
+            if (mAppBounds.isEmpty()) {
+                mAppBounds.set(0, 0, width, height);
+            }
+            return this;
+        }
+
+        TaskMatrixVerifier withInsets(Rect insets) {
+            mDisplayInsets.set(insets);
+            mLauncherInsets.set(insets);
+            return this;
+        }
+
+        TaskMatrixVerifier withAppBounds(Rect bounds, Rect insets, int appRotation) {
+            mAppBounds.set(bounds);
+            mAppInsets = insets;
+            mAppRotation = appRotation;
+            return this;
+        }
+
+        void verifyNoTransforms() {
+            LauncherModelHelper helper = new LauncherModelHelper();
+            try {
+                helper.sandboxContext.allow(SystemUiProxy.INSTANCE);
+                helper.sandboxContext.allow(SysUINavigationMode.INSTANCE);
+
+                Display display = mock(Display.class);
+                doReturn(DEFAULT_DISPLAY).when(display).getDisplayId();
+                doReturn(mDisplaySize.x > mDisplaySize.y ? Surface.ROTATION_90 : Surface.ROTATION_0)
+                        .when(display).getRotation();
+                doAnswer(i -> {
+                    ((Point) i.getArgument(0)).set(mDisplaySize.x, mDisplaySize.y);
+                    return null;
+                }).when(display).getRealSize(any());
+                doAnswer(i -> {
+                    Point smallestSize = i.getArgument(0);
+                    Point largestSize = i.getArgument(1);
+                    smallestSize.x = smallestSize.y = Math.min(mDisplaySize.x, mDisplaySize.y);
+                    largestSize.x = largestSize.y = Math.max(mDisplaySize.x, mDisplaySize.y);
+
+                    smallestSize.x -= mDisplayInsets.left + mDisplayInsets.right;
+                    largestSize.x -= mDisplayInsets.left + mDisplayInsets.right;
+
+                    smallestSize.y -= mDisplayInsets.top + mDisplayInsets.bottom;
+                    largestSize.y -= mDisplayInsets.top + mDisplayInsets.bottom;
+                    return null;
+                }).when(display).getCurrentSizeRange(any(), any());
+                DisplayController.Info mockInfo = new Info(helper.sandboxContext, display);
+
+                DisplayController controller =
+                        DisplayController.INSTANCE.get(helper.sandboxContext);
+                controller.close();
+                ReflectionHelpers.setField(controller, "mInfo", mockInfo);
+
+                mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(helper.sandboxContext)
+                        .getBestMatch(mAppBounds.width(), mAppBounds.height());
+                mDeviceProfile.updateInsets(mLauncherInsets);
+
+                TaskViewSimulator tvs = new TaskViewSimulator(helper.sandboxContext,
+                        FallbackActivityInterface.INSTANCE);
+                tvs.setDp(mDeviceProfile);
+
+                int launcherRotation = mockInfo.rotation;
+                if (mAppRotation < 0) {
+                    mAppRotation = launcherRotation;
+                }
+
+                tvs.getOrientationState().update(launcherRotation, mAppRotation);
+                if (mAppInsets == null) {
+                    mAppInsets = new Rect(mLauncherInsets);
+                }
+                tvs.setPreviewBounds(mAppBounds, mAppInsets);
+
+                tvs.fullScreenProgress.value = 1;
+                tvs.recentsViewScale.value = tvs.getFullScreenScale();
+                tvs.apply(this);
+            } finally {
+                helper.destroy();
+            }
+        }
+
+        @Override
+        public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
+            SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
+            proxy.onBuildTargetParams(builder, mock(RemoteAnimationTargetCompat.class), this);
+            return new SurfaceParams[] {builder.build()};
+        }
+
+        @Override
+        public void applySurfaceParams(SurfaceParams[] params) {
+            // Verify that the task position remains the same
+            RectF newAppBounds = new RectF(mAppBounds);
+            params[0].matrix.mapRect(newAppBounds);
+            Assert.assertThat(newAppBounds, new AlmostSame(mAppBounds));
+
+            System.err.println("Bounds mapped: " + mAppBounds + " => " + newAppBounds);
+        }
+    }
+
+    private static class AlmostSame extends TypeSafeMatcher<RectF>  {
+
+        // Allow .1% error margin to account for float to int conversions
+        private final float mErrorFactor = .001f;
+        private final Rect mExpected;
+
+        AlmostSame(Rect expected) {
+            mExpected = expected;
+        }
+
+        @Override
+        protected boolean matchesSafely(RectF item) {
+            float errorWidth = mErrorFactor * mExpected.width();
+            float errorHeight = mErrorFactor * mExpected.height();
+            return Math.abs(item.left - mExpected.left) < errorWidth
+                    && Math.abs(item.top - mExpected.top) < errorHeight
+                    && Math.abs(item.right - mExpected.right) < errorWidth
+                    && Math.abs(item.bottom - mExpected.bottom) < errorHeight;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendValue(mExpected);
+        }
+    }
+}
diff --git a/res/layout/qsb_preview.xml b/res/layout/qsb_preview.xml
new file mode 100644
index 0000000..801fb04
--- /dev/null
+++ b/res/layout/qsb_preview.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.qsb.QsbContainerView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="0dp"
+    android:id="@id/search_container_workspace"
+    android:padding="0dp" >
+
+    <fragment
+        android:name="com.android.launcher3.qsb.QsbContainerView$QsbFragment"
+        android:layout_width="match_parent"
+        android:tag="qsb_view"
+        android:layout_height="match_parent"/>
+</com.android.launcher3.qsb.QsbContainerView>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d1774e5..a6105eb 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -153,6 +153,7 @@
 
     <!-- All applications label -->
     <string name="all_apps_button_label">Apps list</string>
+    <string name="all_apps_search_results">Search results</string>
     <string name="all_apps_button_personal_label">Personal apps list</string>
     <string name="all_apps_button_work_label">Work apps list</string>
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index a2189c9..244cb59 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -548,15 +548,18 @@
         Resources res = context.getResources();
         Configuration config = context.getResources().getConfiguration();
 
-        float availableWidth = config.screenWidthDp * res.getDisplayMetrics().density;
-        float availableHeight = config.screenHeightDp * res.getDisplayMetrics().density;
+        float screenWidth = config.screenWidthDp * res.getDisplayMetrics().density;
+        float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
+        return getBestMatch(screenWidth, screenHeight);
+    }
 
+    public DeviceProfile getBestMatch(float screenWidth, float screenHeight) {
         DeviceProfile bestMatch = supportedProfiles.get(0);
         float minDiff = Float.MAX_VALUE;
 
         for (DeviceProfile profile : supportedProfiles) {
-            float diff = Math.abs(profile.availableWidthPx - availableWidth)
-                    + Math.abs(profile.availableHeightPx - availableHeight);
+            float diff = Math.abs(profile.widthPx - screenWidth)
+                    + Math.abs(profile.heightPx - screenHeight);
             if (diff < minDiff) {
                 minDiff = diff;
                 bestMatch = profile;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1354a7a..ed9f044 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -600,9 +600,11 @@
 
     private void initDeviceProfile(InvariantDeviceProfile idp) {
         // Load configuration-specific DeviceProfile
-        mDeviceProfile = isInMultiWindowMode()
-                ? mDeviceProfile.getMultiWindowProfile(this, getMultiWindowDisplaySize())
-                : idp.getDeviceProfile(this);
+        mDeviceProfile = idp.getDeviceProfile(this);
+        if (isInMultiWindowMode()) {
+            mDeviceProfile = mDeviceProfile.getMultiWindowProfile(
+                    this, getMultiWindowDisplaySize());
+        }
 
         onDeviceProfileInitiated();
         mModelWriter = mModel.getWriter(getDeviceProfile().isVerticalBarLayout(), true, this);
@@ -2227,6 +2229,9 @@
             ArrayList<ItemInfo> addAnimated) {
         // Add the new screens
         if (newScreens != null) {
+            // newScreens can contain an empty right panel that is already bound, but not known
+            // by BgDataModel.
+            newScreens.removeAllValues(mWorkspace.mScreenOrder);
             bindAddScreens(newScreens);
         }
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 6ec48e5..a21c8e3 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -647,7 +647,8 @@
         if (mDragSourceInternal != null) {
             int dragSourceChildCount = mDragSourceInternal.getChildCount();
 
-            if (isTwoPanelEnabled()) {
+            // If the icon was dragged from Hotseat, there is no page pair
+            if (isTwoPanelEnabled() && !(mDragSourceInternal.getParent() instanceof Hotseat)) {
                 int pagePairScreenId = getPagePair(dragObject.dragInfo.screenId);
                 CellLayout pagePair = mWorkspaceScreens.get(pagePairScreenId);
                 if (pagePair == null) {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 57a3e1c..e779ee8 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -120,6 +120,7 @@
 
     protected boolean mUsingTabs;
     private boolean mIsSearching;
+    private boolean mHasWorkApps;
 
     protected RecyclerViewFastScroller mTouchHandler;
     protected final Point mFastScrollerOffset = new Point();
@@ -191,7 +192,7 @@
             int currentPage = state.getInt(BUNDLE_KEY_CURRENT_PAGE, 0);
             if (currentPage != 0 && mViewPager != null) {
                 mViewPager.setCurrentPage(currentPage);
-                rebindAdapters(true);
+                rebindAdapters();
             } else {
                 reset(true);
             }
@@ -245,9 +246,10 @@
                 break;
             }
         }
+        mHasWorkApps = hasWorkApps;
         if (!mAH[AdapterHolder.MAIN].appsList.hasFilter()) {
-            rebindAdapters(hasWorkApps);
-            if (hasWorkApps) {
+            rebindAdapters();
+            if (mHasWorkApps) {
                 resetWorkProfile();
             }
         }
@@ -324,6 +326,8 @@
                     mViewPager.getNextPage() == 0
                             ? R.string.all_apps_button_personal_label
                             : R.string.all_apps_button_work_label;
+        } else if (mIsSearching) {
+            descriptionRes = R.string.all_apps_search_results;
         } else {
             descriptionRes = R.string.all_apps_button_label;
         }
@@ -372,7 +376,7 @@
         });
 
         mHeader = findViewById(R.id.all_apps_header);
-        rebindAdapters(mUsingTabs, true /* force */);
+        rebindAdapters(true /* force */);
 
         mSearchContainer = findViewById(R.id.search_container_all_apps);
         mSearchUiManager = (SearchUiManager) mSearchContainer;
@@ -441,11 +445,12 @@
         }
     }
 
-    private void rebindAdapters(boolean showTabs) {
-        rebindAdapters(showTabs, false /* force */);
+    private void rebindAdapters() {
+        rebindAdapters(false /* force */);
     }
 
-    protected void rebindAdapters(boolean showTabs, boolean force) {
+    protected void rebindAdapters(boolean force) {
+        boolean showTabs = mHasWorkApps && !mIsSearching;
         if (showTabs == mUsingTabs && !force) {
             return;
         }
@@ -628,17 +633,13 @@
             mAH[i].adapter.setLastSearchQuery(query);
         }
         mIsSearching = true;
-        if (mUsingTabs) {
-            rebindAdapters(false); // hide tabs
-        }
+        rebindAdapters();
         mHeader.setCollapsed(true);
     }
 
     public void onClearSearchResult() {
-        if (mUsingTabs) {
-            rebindAdapters(true); // show tabs
-        }
         mIsSearching = false;
+        rebindAdapters();
         getActiveRecyclerView().scrollToTop();
     }
 
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index f64b7cb..b6dcec6 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -22,7 +22,6 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.statemanager.StateManager.StateListener;
 
 /**
  * AllAppsContainerView with launcher specific callbacks
@@ -31,8 +30,6 @@
 
     private final Launcher mLauncher;
 
-    private StateListener<LauncherState> mWorkTabListener;
-
     public LauncherAllAppsContainerView(Context context) {
         this(context, null);
     }
@@ -75,14 +72,6 @@
     }
 
     @Override
-    public void setupHeader() {
-        super.setupHeader();
-        if (mWorkTabListener != null && !mUsingTabs) {
-            mLauncher.getStateManager().removeStateListener(mWorkTabListener);
-        }
-    }
-
-    @Override
     public void onActivePageChanged(int currentActivePage) {
         super.onActivePageChanged(currentActivePage);
     }
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 43ac8f9..94fc708 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -324,7 +324,9 @@
     }
 
     private void inflateAndAddFolder(FolderInfo info) {
-        CellLayout screen = mWorkspaceScreens.get(info.screenId);
+        CellLayout screen = info.container == Favorites.CONTAINER_DESKTOP
+                ? mWorkspaceScreens.get(info.screenId)
+                : mHotseat;
         FolderIcon folderIcon = FolderIcon.inflateIcon(R.layout.folder_icon, this, screen,
                 info);
         addInScreenFromBind(folderIcon, info);
@@ -471,8 +473,8 @@
         // Add first page QSB
         if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
             CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID);
-            View qsb = mHomeElementInflater.inflate(
-                    R.layout.search_container_workspace, firstScreen, false);
+            View qsb = mHomeElementInflater.inflate(R.layout.qsb_preview, firstScreen,
+                    false);
             CellLayout.LayoutParams lp =
                     new CellLayout.LayoutParams(0, 0, firstScreen.getCountX(), 1);
             lp.canReorder = false;
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index ab35bd6..4b86f65 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.pm;
 
 import static com.android.launcher3.Utilities.getPrefs;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -25,7 +24,6 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageManager;
-import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -238,24 +236,12 @@
     }
 
     public InstallSessionTracker registerInstallTracker(InstallSessionTracker.Callback callback) {
-        InstallSessionTracker tracker = new InstallSessionTracker(this, callback);
-
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
-            mInstaller.registerSessionCallback(tracker, MODEL_EXECUTOR.getHandler());
-        } else {
-            mLauncherApps.registerPackageInstallerSessionCallback(MODEL_EXECUTOR, tracker);
-        }
+        InstallSessionTracker tracker = new InstallSessionTracker(
+                this, callback, mInstaller, mLauncherApps);
+        tracker.register();
         return tracker;
     }
 
-    void unregister(InstallSessionTracker tracker) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
-            mInstaller.unregisterSessionCallback(tracker);
-        } else {
-            mLauncherApps.unregisterPackageInstallerSessionCallback(tracker);
-        }
-    }
-
     public static UserHandle getUserHandle(SessionInfo info) {
         return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle();
     }
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index b0b907a..e1b3c1a 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -18,9 +18,12 @@
 import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
 import static com.android.launcher3.pm.PackageInstallInfo.STATUS_FAILED;
 import static com.android.launcher3.pm.PackageInstallInfo.STATUS_INSTALLED;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
+import android.os.Build;
 import android.os.UserHandle;
 import android.util.SparseArray;
 
@@ -28,35 +31,53 @@
 
 import com.android.launcher3.util.PackageUserKey;
 
+import java.lang.ref.WeakReference;
+
 @WorkerThread
 public class InstallSessionTracker extends PackageInstaller.SessionCallback {
 
     // Lazily initialized
     private SparseArray<PackageUserKey> mActiveSessions = null;
 
-    private final InstallSessionHelper mInstallerCompat;
-    private final Callback mCallback;
+    private final WeakReference<InstallSessionHelper> mWeakHelper;
+    private final WeakReference<Callback> mWeakCallback;
+    private final PackageInstaller mInstaller;
+    private final LauncherApps mLauncherApps;
 
-    InstallSessionTracker(InstallSessionHelper installerCompat, Callback callback) {
-        mInstallerCompat = installerCompat;
-        mCallback = callback;
+
+    InstallSessionTracker(InstallSessionHelper installerCompat, Callback callback,
+            PackageInstaller installer, LauncherApps launcherApps) {
+        mWeakHelper = new WeakReference<>(installerCompat);
+        mWeakCallback = new WeakReference<>(callback);
+        mInstaller = installer;
+        mLauncherApps = launcherApps;
     }
 
     @Override
     public void onCreated(int sessionId) {
-        SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId);
+        InstallSessionHelper helper = mWeakHelper.get();
+        Callback callback = mWeakCallback.get();
+        if (callback == null || helper == null) {
+            return;
+        }
+        SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId, helper, callback);
         if (sessionInfo != null) {
-            mCallback.onInstallSessionCreated(PackageInstallInfo.fromInstallingState(sessionInfo));
+            callback.onInstallSessionCreated(PackageInstallInfo.fromInstallingState(sessionInfo));
         }
 
-        mInstallerCompat.tryQueuePromiseAppIcon(sessionInfo);
+        helper.tryQueuePromiseAppIcon(sessionInfo);
     }
 
     @Override
     public void onFinished(int sessionId, boolean success) {
+        InstallSessionHelper helper = mWeakHelper.get();
+        Callback callback = mWeakCallback.get();
+        if (callback == null || helper == null) {
+            return;
+        }
         // For a finished session, we can't get the session info. So use the
         // packageName from our local cache.
-        SparseArray<PackageUserKey> activeSessions = getActiveSessionMap();
+        SparseArray<PackageUserKey> activeSessions = getActiveSessionMap(helper);
         PackageUserKey key = activeSessions.get(sessionId);
         activeSessions.remove(sessionId);
 
@@ -65,21 +86,26 @@
             PackageInstallInfo info = PackageInstallInfo.fromState(
                     success ? STATUS_INSTALLED : STATUS_FAILED,
                     packageName, key.mUser);
-            mCallback.onPackageStateChanged(info);
+            callback.onPackageStateChanged(info);
 
-            if (!success && mInstallerCompat.promiseIconAddedForId(sessionId)) {
-                mCallback.onSessionFailure(packageName, key.mUser);
+            if (!success && helper.promiseIconAddedForId(sessionId)) {
+                callback.onSessionFailure(packageName, key.mUser);
                 // If it is successful, the id is removed in the the package added flow.
-                mInstallerCompat.removePromiseIconId(sessionId);
+                helper.removePromiseIconId(sessionId);
             }
         }
     }
 
     @Override
     public void onProgressChanged(int sessionId, float progress) {
-        SessionInfo session = mInstallerCompat.getVerifiedSessionInfo(sessionId);
+        InstallSessionHelper helper = mWeakHelper.get();
+        Callback callback = mWeakCallback.get();
+        if (callback == null || helper == null) {
+            return;
+        }
+        SessionInfo session = helper.getVerifiedSessionInfo(sessionId);
         if (session != null && session.getAppPackageName() != null) {
-            mCallback.onPackageStateChanged(PackageInstallInfo.fromInstallingState(session));
+            callback.onPackageStateChanged(PackageInstallInfo.fromInstallingState(session));
         }
     }
 
@@ -88,35 +114,53 @@
 
     @Override
     public void onBadgingChanged(int sessionId) {
-        SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId);
+        InstallSessionHelper helper = mWeakHelper.get();
+        Callback callback = mWeakCallback.get();
+        if (callback == null || helper == null) {
+            return;
+        }
+        SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId, helper, callback);
         if (sessionInfo != null) {
-            mInstallerCompat.tryQueuePromiseAppIcon(sessionInfo);
+            helper.tryQueuePromiseAppIcon(sessionInfo);
         }
     }
 
-    private SessionInfo pushSessionDisplayToLauncher(int sessionId) {
-        SessionInfo session = mInstallerCompat.getVerifiedSessionInfo(sessionId);
+    private SessionInfo pushSessionDisplayToLauncher(
+            int sessionId, InstallSessionHelper helper, Callback callback) {
+        SessionInfo session = helper.getVerifiedSessionInfo(sessionId);
         if (session != null && session.getAppPackageName() != null) {
             PackageUserKey key =
                     new PackageUserKey(session.getAppPackageName(), getUserHandle(session));
-            getActiveSessionMap().put(session.getSessionId(), key);
-            mCallback.onUpdateSessionDisplay(key, session);
+            getActiveSessionMap(helper).put(session.getSessionId(), key);
+            callback.onUpdateSessionDisplay(key, session);
             return session;
         }
         return null;
     }
 
-    private SparseArray<PackageUserKey> getActiveSessionMap() {
+    private SparseArray<PackageUserKey> getActiveSessionMap(InstallSessionHelper helper) {
         if (mActiveSessions == null) {
             mActiveSessions = new SparseArray<>();
-            mInstallerCompat.getActiveSessions().forEach(
+            helper.getActiveSessions().forEach(
                     (key, si) -> mActiveSessions.put(si.getSessionId(), key));
         }
         return mActiveSessions;
     }
 
+    void register() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+            mInstaller.registerSessionCallback(this, MODEL_EXECUTOR.getHandler());
+        } else {
+            mLauncherApps.registerPackageInstallerSessionCallback(MODEL_EXECUTOR, this);
+        }
+    }
+
     public void unregister() {
-        mInstallerCompat.unregister(this);
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+            mInstaller.unregisterSessionCallback(this);
+        } else {
+            mLauncherApps.unregisterPackageInstallerSessionCallback(this);
+        }
     }
 
     public interface Callback {
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 915e140..f348a33 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -62,8 +62,9 @@
         SharedPreferences.OnSharedPreferenceChangeListener{
 
     /** List of fragments that can be hosted by this activity. */
-    private static final List<String> VALID_PREFERENCE_FRAGMENTS = Collections.singletonList(
-            DeveloperOptionsFragment.class.getName());
+    private static final List<String> VALID_PREFERENCE_FRAGMENTS =
+            !Utilities.IS_DEBUG_DEVICE ? Collections.emptyList()
+                    : Collections.singletonList(DeveloperOptionsFragment.class.getName());
 
     private static final String DEVELOPER_OPTIONS_KEY = "pref_developer_options";
     private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 8d330d4..895ca08 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -388,11 +388,12 @@
     public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
             SplitConfigurationOptions.StagedSplitBounds splitInfo, int desiredStagePosition) {
         float diff;
+        float horizontalDividerDiff = splitInfo.visualDividerBounds.width() / 2f;
         if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
-            diff = outRect.height() * (1f - splitInfo.leftTaskPercent);
+            diff = outRect.height() * (1f - splitInfo.leftTaskPercent) + horizontalDividerDiff;
             outRect.bottom -= diff;
         } else {
-            diff = outRect.height() * splitInfo.leftTaskPercent;
+            diff = outRect.height() * splitInfo.leftTaskPercent + horizontalDividerDiff;
             outRect.top += diff;
         }
     }
@@ -402,7 +403,7 @@
             SplitConfigurationOptions.StagedSplitBounds splitInfo, int desiredStagePosition) {
         if (desiredStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
             // The preview set is for the bottom/right, inset by top/left task
-            splitOffset.x = splitInfo.leftTopBounds.width() + splitInfo.dividerBounds.width() / 2;
+            splitOffset.x = splitInfo.leftTopBounds.width() + splitInfo.visualDividerBounds.width();
         }
     }
 
@@ -413,7 +414,7 @@
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = taskParent.getHeight() - spaceAboveSnapshot;
         int totalThumbnailWidth = taskParent.getWidth();
-        int dividerBar = splitBoundsConfig.dividerBounds.width() / 2;
+        int dividerBar = splitBoundsConfig.visualDividerBounds.width();
         ViewGroup.LayoutParams primaryLp = mSnapshotView.getLayoutParams();
         ViewGroup.LayoutParams secondaryLp = mSnapshotView2.getLayoutParams();
 
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 8284659..064d808 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -474,21 +474,23 @@
     public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
             SplitConfigurationOptions.StagedSplitBounds splitInfo, int desiredStagePosition) {
         boolean isLandscape = dp.isLandscape;
+        float verticalDividerDiff = splitInfo.visualDividerBounds.height() / 2f;
+        float horizontalDividerDiff = splitInfo.visualDividerBounds.width() / 2f;
         float diff;
         if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
             if (isLandscape) {
-                diff = outRect.width() * (1f - splitInfo.leftTaskPercent);
+                diff = outRect.width() * (1f - splitInfo.leftTaskPercent) + horizontalDividerDiff;
                 outRect.right -= diff;
             } else {
-                diff = outRect.height() * (1f - splitInfo.topTaskPercent);
+                diff = outRect.height() * (1f - splitInfo.topTaskPercent) + verticalDividerDiff;
                 outRect.bottom -= diff;
             }
         } else {
             if (isLandscape) {
-                diff = outRect.width() * splitInfo.leftTaskPercent;
+                diff = outRect.width() * splitInfo.leftTaskPercent + horizontalDividerDiff;
                 outRect.left += diff;
             } else {
-                diff = outRect.height() * splitInfo.topTaskPercent;
+                diff = outRect.height() * splitInfo.topTaskPercent + verticalDividerDiff;
                 outRect.top += diff;
             }
         }
@@ -500,10 +502,10 @@
         if (desiredStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
             if (dp.isLandscape) {
                 splitOffset.x = splitInfo.leftTopBounds.width() +
-                        splitInfo.dividerBounds.width() / 2;
+                        splitInfo.visualDividerBounds.width();
             } else {
                 splitOffset.y = splitInfo.leftTopBounds.height() +
-                        splitInfo.dividerBounds.height() / 2;
+                        splitInfo.visualDividerBounds.height();
             }
         }
     }
@@ -516,9 +518,8 @@
         int totalThumbnailHeight = taskParent.getHeight() - spaceAboveSnapshot;
         int totalThumbnailWidth = taskParent.getWidth();
         int dividerBar = (dp.isLandscape ?
-                splitBoundsConfig.dividerBounds.width() :
-                splitBoundsConfig.dividerBounds.height())
-                / 2;
+                splitBoundsConfig.visualDividerBounds.width() :
+                splitBoundsConfig.visualDividerBounds.height());
         ViewGroup.LayoutParams primaryLp = mSnapshotView.getLayoutParams();
         ViewGroup.LayoutParams secondaryLp = mSnapshotView2.getLayoutParams();
 
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index e8ba28f..ab3083d 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -32,6 +32,11 @@
  */
 public interface ItemInfoMatcher {
 
+    /**
+     * Empty component used for match testing
+     */
+    ComponentName EMPTY_COMPONENT = new ComponentName("", "");
+
     boolean matches(ItemInfo info, ComponentName cn);
 
     /**
@@ -40,7 +45,7 @@
     default boolean matchesInfo(ItemInfo info) {
         if (info != null) {
             ComponentName cn = info.getTargetComponent();
-            return cn != null && matches(info, cn);
+            return matches(info, cn != null ? cn : EMPTY_COMPONENT);
         } else {
             return false;
         }
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 41693de..5093d85 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -88,25 +88,29 @@
     public static class StagedSplitBounds {
         public final Rect leftTopBounds;
         public final Rect rightBottomBounds;
-        public final Rect dividerBounds;
+        /** This rect represents the actual gap between the two apps */
+        public final Rect visualDividerBounds;
         // This class is orientation-agnostic, so we compute both for later use
         public final float topTaskPercent;
         public final float leftTaskPercent;
 
 
-        public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds, Rect dividerBounds) {
+        public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds) {
             this.leftTopBounds = leftTopBounds;
             this.rightBottomBounds = rightBottomBounds;
-            this.dividerBounds = dividerBounds;
-            float totalHeight = this.leftTopBounds.height()
-                    + this.rightBottomBounds.height()
-                    + this.dividerBounds.height();
-            float totalWidth = this.leftTopBounds.width()
-                    + this.rightBottomBounds.width()
-                    + this.dividerBounds.width();
 
-            leftTaskPercent = this.leftTopBounds.width() / totalWidth;
-            topTaskPercent = this.leftTopBounds.height() / totalHeight;
+            if (rightBottomBounds.top > leftTopBounds.top) {
+                // vertical apps, horizontal divider
+                this.visualDividerBounds = new Rect(leftTopBounds.left, leftTopBounds.bottom,
+                        leftTopBounds.right, rightBottomBounds.top);
+            } else {
+                // horizontal apps, vertical divider
+                this.visualDividerBounds = new Rect(leftTopBounds.right, leftTopBounds.top,
+                        rightBottomBounds.left, leftTopBounds.bottom);
+            }
+
+            leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right;
+            topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
         }
     }
 
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index a5694fc..ccbb662 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -42,7 +42,6 @@
 import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.Wait.Condition;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
 import org.junit.Before;
@@ -78,7 +77,6 @@
     public void testEmpty() throws Throwable { /* needed while the broken tests are being fixed */ }
 
     @Test
-    @ScreenRecord  //b/192010616
     public void testPinWidgetNoConfig() throws Throwable {
         runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo &&
                 ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
index c9b63ae..59966ee 100644
--- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -66,7 +66,6 @@
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.testing.TestInformationProvider;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectProvider;
 import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 
@@ -533,12 +532,6 @@
             super.onDestroy();
         }
 
-
-        @Override
-        protected <T> T createObject(ObjectProvider<T> provider) {
-            return spy(provider.get(this));
-        }
-
         @Override
         public PackageManager getPackageManager() {
             return mPm;