Merge "Add shortcut drag/drop support to the taskbar."
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 0f92274..25b39ed 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -28,6 +28,7 @@
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
     <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS"/>
     <uses-permission android:name="android.permission.REMOVE_TASKS"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
     <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
     <uses-permission android:name="android.permission.STATUS_BAR"/>
     <uses-permission android:name="android.permission.STOP_APP_SWITCHES"/>
diff --git a/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
index 96559cb..962fd91 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
@@ -44,7 +44,7 @@
 
     @Override
     protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
-        QuickstepLauncher launcher = (QuickstepLauncher) mLauncher;
+        QuickstepLauncher launcher = (QuickstepLauncher) mContext;
         if (action == PIN_PREDICTION) {
             if (launcher.getHotseatPredictionController() == null) {
                 return false;
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index e82c900..6ab49f8 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -161,8 +161,7 @@
             }
             InstanceId instanceId = new InstanceIdSequence().newInstanceId();
             for (ItemInfo info : itemsIdMap) {
-                FolderInfo parent = info.container > 0
-                        ? (FolderInfo) itemsIdMap.get(info.container) : null;
+                FolderInfo parent = getContainer(info, itemsIdMap);
                 StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
             }
             additionalSnapshotEvents(instanceId);
@@ -199,8 +198,7 @@
                         }
 
                         for (ItemInfo info : itemsIdMap) {
-                            FolderInfo parent = info.container > 0
-                                    ? (FolderInfo) itemsIdMap.get(info.container) : null;
+                            FolderInfo parent = getContainer(info, itemsIdMap);
                             LauncherAtom.ItemInfo itemInfo = info.buildProto(parent);
                             Log.d(TAG, itemInfo.toString());
                             StatsEvent statsEvent = StatsLogCompatManager.buildStatsEvent(itemInfo,
@@ -222,6 +220,22 @@
         }
     }
 
+    private static FolderInfo getContainer(ItemInfo info, IntSparseArrayMap<ItemInfo> itemsIdMap) {
+        if (info.container > 0) {
+            ItemInfo containerInfo = itemsIdMap.get(info.container);
+
+            if (!(containerInfo instanceof FolderInfo)) {
+                Log.e(TAG, String.format(
+                        "Item info: %s found with invalid container: %s",
+                        info,
+                        containerInfo));
+            }
+            // Allow crash to help debug b/173838775
+            return (FolderInfo) containerInfo;
+        }
+        return null;
+    }
+
     @Override
     public void validateData() {
         super.validateData();
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 9d70cfa..e1d89a1 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -44,7 +44,6 @@
 import com.android.systemui.shared.system.BlurUtils;
 import com.android.systemui.shared.system.WallpaperManagerCompat;
 
-import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.function.Consumer;
 
@@ -156,6 +155,10 @@
     // Workaround for animating the depth when multiwindow mode changes.
     private boolean mIgnoreStateChangesDuringMultiWindowAnimation = false;
 
+    // Hints that there is potentially content behind Launcher and that we shouldn't optimize by
+    // marking the launcher surface as opaque.  Only used in certain Launcher states.
+    private boolean mHasContentBehindLauncher;
+
     private View.OnAttachStateChangeListener mOnAttachListener;
 
     public DepthController(Launcher l) {
@@ -199,6 +202,10 @@
         mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
     }
 
+    public void setHasContentBehindLauncher(boolean hasContentBehindLauncher) {
+        mHasContentBehindLauncher = hasContentBehindLauncher;
+    }
+
     /**
      * Sets if the underlying activity is started or not
      */
@@ -311,13 +318,14 @@
         }
 
         if (supportsBlur) {
-            boolean opaque = mLauncher.getScrimView().isFullyOpaque();
+            boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
+            boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg;
 
-            mCurrentBlur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch
+            mCurrentBlur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch || hasOpaqueBg
                     ? 0 : (int) (depth * mMaxBlurRadius);
             SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
                     .setBackgroundBlurRadius(mSurface, mCurrentBlur)
-                    .setOpaque(mSurface, opaque);
+                    .setOpaque(mSurface, isSurfaceOpaque);
 
             // Set early wake-up flags when we know we're executing an expensive operation, this way
             // SurfaceFlinger will adjust its internal offsets to avoid jank.
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index 24a88a4..90c035f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -46,16 +46,13 @@
                 }
             };
 
-    // Initialized in init.
-    TaskbarControllers mControllers;
-
     public FallbackTaskbarUIController(RecentsActivity recentsActivity) {
         mRecentsActivity = recentsActivity;
     }
 
     @Override
     protected void init(TaskbarControllers taskbarControllers) {
-        mControllers = taskbarControllers;
+        super.init(taskbarControllers);
 
         mRecentsActivity.setTaskbarUIController(this);
         mRecentsActivity.getStateManager().addStateListener(mStateListener);
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 1c0c773..7d23439 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -25,7 +25,6 @@
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.TaskTransitionSpec;
-import android.view.View;
 import android.view.WindowManagerGlobal;
 
 import androidx.annotation.NonNull;
@@ -62,7 +61,6 @@
             this::onStashedInAppChanged;
 
     // Initialized in init.
-    private TaskbarControllers mControllers;
     private AnimatedFloat mTaskbarOverrideBackgroundAlpha;
     private TaskbarKeyguardController mKeyguardController;
     private final TaskbarLauncherStateController
@@ -83,7 +81,7 @@
 
     @Override
     protected void init(TaskbarControllers taskbarControllers) {
-        mControllers = taskbarControllers;
+        super.init(taskbarControllers);
 
         mTaskbarLauncherStateController.init(mControllers, mLauncher);
         mTaskbarOverrideBackgroundAlpha = mControllers.taskbarDragLayerController
@@ -168,10 +166,6 @@
         return mControllers.taskbarDragController.isDragging();
     }
 
-    public View getRootView() {
-        return mControllers.taskbarActivityContext.getDragLayer();
-    }
-
     @Override
     protected void onStashedInAppChanged() {
         onStashedInAppChanged(mLauncher.getDeviceProfile());
@@ -251,4 +245,12 @@
         mLauncher.logAppLaunch(mControllers.taskbarActivityContext.getStatsLogManager(), item,
                 instanceId);
     }
+
+    @Override
+    public void setSystemGestureInProgress(boolean inProgress) {
+        super.setSystemGestureInProgress(inProgress);
+        // Launcher's ScrimView will draw the background throughout the gesture. But once the
+        // gesture ends, start drawing taskbar's background again since launcher might stop drawing.
+        forceHideBackground(inProgress);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 787aa19..0ab7756 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -17,12 +17,10 @@
 
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y_LONG_CLICK;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
-import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_IME;
 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
@@ -39,6 +37,7 @@
 import android.annotation.DrawableRes;
 import android.annotation.IdRes;
 import android.annotation.LayoutRes;
+import android.content.pm.ActivityInfo.Config;
 import android.content.res.ColorStateList;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -90,7 +89,6 @@
 
     private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE;
 
-    private View.OnLongClickListener mA11yLongClickListener;
     private final ArrayList<StatePropertyHolder> mPropertyHolders = new ArrayList<>();
     private final ArrayList<ImageView> mAllButtons = new ArrayList<>();
     private int mState;
@@ -142,16 +140,6 @@
         mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize;
         mNavButtonTranslationYMultiplier.value = 1;
 
-        mA11yLongClickListener = view -> {
-            mControllers.navButtonController.onButtonClick(BUTTON_A11Y_LONG_CLICK);
-            return true;
-        };
-
-        mPropertyHolders.add(new StatePropertyHolder(
-                mControllers.taskbarViewController.getTaskbarIconAlpha()
-                        .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,
@@ -203,7 +191,7 @@
             }
 
             // Animate taskbar background when any of these flags are enabled
-            int flagsToShowBg = FLAG_IME_VISIBLE | FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE
+            int flagsToShowBg = FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE
                     | FLAG_NOTIFICATION_SHADE_EXPANDED;
             mPropertyHolders.add(new StatePropertyHolder(
                     mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
@@ -285,7 +273,6 @@
         mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
                 flags -> (flags & FLAG_A11Y_VISIBLE) != 0
                         && (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
-        mA11yButton.setOnLongClickListener(mA11yLongClickListener);
     }
 
     private void parseSystemUiFlags(int sysUiStateFlags) {
@@ -441,6 +428,8 @@
         ImageView buttonView = addButton(parent, id, layoutId);
         buttonView.setImageResource(drawableId);
         buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType));
+        buttonView.setOnLongClickListener(view ->
+                navButtonController.onButtonLongClick(buttonType));
         return buttonView;
     }
 
@@ -457,6 +446,12 @@
         return mFloatingRotationButtonBounds.contains((int) ev.getX(), (int) ev.getY());
     }
 
+    public void onConfigurationChanged(@Config int configChanges) {
+        if (mFloatingRotationButton != null) {
+            mFloatingRotationButton.onConfigurationChanged(configChanges);
+        }
+    }
+
     public void onDestroy() {
         mPropertyHolders.clear();
         mControllers.rotationButtonController.unregisterListeners();
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index 2c80f06..22ca63f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -16,6 +16,8 @@
 package com.android.launcher3.taskbar;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.graphics.Outline;
@@ -23,8 +25,6 @@
 import android.view.View;
 import android.view.ViewOutlineProvider;
 
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.RevealOutlineAnimation;
@@ -66,7 +66,10 @@
     private final Rect mStashedHandleBounds = new Rect();
     private float mStashedHandleRadius;
 
-    private boolean mIsAtStashedRevealBounds = true;
+    // When the reveal animation is cancelled, we can assume it's about to create a new animation,
+    // which should start off at the same point the cancelled one left off.
+    private float mStartProgressForNextRevealAnim;
+    private boolean mWasLastRevealAnimReversed;
 
     public StashedHandleViewController(TaskbarActivityContext activity,
             StashedHandleView stashedHandleView) {
@@ -148,15 +151,27 @@
      * shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape
      * morphs into the size of where the taskbar icons will be.
      */
-    public @Nullable Animator createRevealAnimToIsStashed(boolean isStashed) {
-        if (mIsAtStashedRevealBounds == isStashed) {
-            return null;
-        }
-        mIsAtStashedRevealBounds = isStashed;
+    public Animator createRevealAnimToIsStashed(boolean isStashed) {
         final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
                 mStashedHandleRadius, mStashedHandleRadius,
                 mControllers.taskbarViewController.getIconLayoutBounds(), mStashedHandleBounds);
-        return handleRevealProvider.createRevealAnimator(mStashedHandleView, !isStashed);
+
+        boolean isReversed = !isStashed;
+        boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
+        mWasLastRevealAnimReversed = isReversed;
+        if (changingDirection) {
+            mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
+        }
+
+        ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView,
+                isReversed, mStartProgressForNextRevealAnim);
+        revealAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction();
+            }
+        });
+        return revealAnim;
     }
 
     public void onIsStashed(boolean isStashed) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 508ca4e..8fd6d35 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -20,7 +20,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
-import static com.android.launcher3.testing.TestProtocol.TASKBAR_WINDOW_CRASH;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
@@ -31,6 +30,7 @@
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ActivityInfo.Config;
 import android.content.pm.LauncherApps;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
@@ -207,7 +207,10 @@
         updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);
 
         mWindowManager.addView(mDragLayer, mWindowLayoutParams);
-        Log.d(TASKBAR_WINDOW_CRASH, "Adding taskbar window");
+    }
+
+    public void onConfigurationChanged(@Config int configChanges) {
+        mControllers.onConfigurationChanged(configChanges);
     }
 
     public boolean isThreeButtonNav() {
@@ -354,7 +357,6 @@
         setUIController(TaskbarUIController.DEFAULT);
         mControllers.onDestroy();
         mWindowManager.removeViewImmediate(mDragLayer);
-        Log.d(TASKBAR_WINDOW_CRASH, "Removing taskbar window");
     }
 
     public void updateSysuiStateFlags(int systemUiStateFlags, boolean fromInit) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index a7c5d4d..488c822 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -15,10 +15,15 @@
  */
 package com.android.launcher3.taskbar;
 
+import android.content.pm.ActivityInfo.Config;
+
 import androidx.annotation.NonNull;
 
 import com.android.systemui.shared.rotation.RotationButtonController;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Hosts various taskbar controllers to facilitate passing between one another.
  */
@@ -43,6 +48,9 @@
     /** Do not store this controller, as it may change at runtime. */
     @NonNull public TaskbarUIController uiController = TaskbarUIController.DEFAULT;
 
+    private boolean mAreAllControllersInitialized;
+    private final List<Runnable> mPostInitCallbacks = new ArrayList<>();
+
     public TaskbarControllers(TaskbarActivityContext taskbarActivityContext,
             TaskbarDragController taskbarDragController,
             TaskbarNavButtonController navButtonController,
@@ -81,6 +89,8 @@
      * in constructors for now, as some controllers may still be waiting for init().
      */
     public void init(TaskbarSharedState sharedState) {
+        mAreAllControllersInitialized = false;
+
         taskbarDragController.init(this);
         navbarButtonsViewController.init(this);
         rotationButtonController.init();
@@ -93,6 +103,16 @@
         taskbarStashController.init(this, sharedState);
         taskbarEduController.init(this);
         taskbarPopupController.init(this);
+
+        mAreAllControllersInitialized = true;
+        for (Runnable postInitCallback : mPostInitCallbacks) {
+            postInitCallback.run();
+        }
+        mPostInitCallbacks.clear();
+    }
+
+    public void onConfigurationChanged(@Config int configChanges) {
+        navbarButtonsViewController.onConfigurationChanged(configChanges);
     }
 
     /**
@@ -110,4 +130,17 @@
         taskbarAutohideSuspendController.onDestroy();
         taskbarPopupController.onDestroy();
     }
+
+    /**
+     * If all controllers are already initialized, runs the given callback immediately. Otherwise,
+     * queues it to run after calling init() on all controllers. This should likely be used in any
+     * case where one controller is telling another controller to do something inside init().
+     */
+    public void runAfterInit(Runnable callback) {
+        if (mAreAllControllersInitialized) {
+            callback.run();
+        } else {
+            mPostInitCallbacks.add(callback);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 248c40d..806b390 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -44,6 +44,7 @@
     private final AnimatedFloat mKeyguardBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
     private final AnimatedFloat mNotificationShadeBgTaskbar = new AnimatedFloat(
             this::updateBackgroundAlpha);
+    private final AnimatedFloat mImeBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
     // Used to hide our background color when someone else (e.g. ScrimView) is handling it.
     private final AnimatedFloat mBgOverride = new AnimatedFloat(this::updateBackgroundAlpha);
 
@@ -74,6 +75,7 @@
         mBgTaskbar.value = 1;
         mKeyguardBgTaskbar.value = 1;
         mNotificationShadeBgTaskbar.value = 1;
+        mImeBgTaskbar.value = 1;
         mBgOverride.value = 1;
         updateBackgroundAlpha();
     }
@@ -108,6 +110,10 @@
         return mNotificationShadeBgTaskbar;
     }
 
+    public AnimatedFloat getImeBgTaskbar() {
+        return mImeBgTaskbar;
+    }
+
     public AnimatedFloat getOverrideBackgroundAlpha() {
         return mBgOverride;
     }
@@ -119,7 +125,7 @@
     private void updateBackgroundAlpha() {
         final float bgNavbar = mBgNavbar.value;
         final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value
-                * mNotificationShadeBgTaskbar.value;
+                * mNotificationShadeBgTaskbar.value * mImeBgTaskbar.value;
         mLastSetBackgroundAlpha = mBgOverride.value * Math.max(bgNavbar, bgTaskbar);
         mTaskbarDragLayer.setTaskbarBackgroundAlpha(mLastSetBackgroundAlpha);
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 8a0e39f..b57efd9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -19,7 +19,6 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 
-import static com.android.launcher3.testing.TestProtocol.TASKBAR_WINDOW_CRASH;
 import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
@@ -108,6 +107,11 @@
                 if ((configDiff & configsRequiringRecreate) != 0) {
                     // Color has changed, recreate taskbar to reload background color & icons.
                     recreateTaskbar();
+                } else {
+                    // Config change might be handled without re-creating the taskbar
+                    if (mTaskbarActivityContext != null) {
+                        mTaskbarActivityContext.onConfigurationChanged(configDiff);
+                    }
                 }
                 mOldConfig = newConfig;
             }
@@ -210,8 +214,6 @@
     }
 
     private void recreateTaskbar() {
-        Log.d(TASKBAR_WINDOW_CRASH, "Recreating taskbar: mTaskbarActivityContext="
-                + mTaskbarActivityContext);
         destroyExistingTaskbar();
 
         DeviceProfile dp =
@@ -234,7 +236,6 @@
             mTaskbarActivityContext.setUIController(
                     createTaskbarUIControllerForActivity(mActivity));
         }
-        Log.d(TASKBAR_WINDOW_CRASH, "Finished recreating taskbar");
     }
 
     public void onSystemUiFlagsChanged(int systemUiStateFlags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 6dcfe56..37a9b5e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -184,9 +184,12 @@
         }
         mContainer.updateHotseatItems(hotseatItemInfos);
 
-        mControllers.taskbarStashController.updateStateForFlag(
-                TaskbarStashController.FLAG_STASHED_IN_APP_EMPTY, isHotseatEmpty);
-        mControllers.taskbarStashController.applyState();
+        final boolean finalIsHotseatEmpty = isHotseatEmpty;
+        mControllers.runAfterInit(() -> {
+            mControllers.taskbarStashController.updateStateForFlag(
+                    TaskbarStashController.FLAG_STASHED_IN_APP_EMPTY, finalIsHotseatEmpty);
+            mControllers.taskbarStashController.applyState();
+        });
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index a8a0b59..ae23eda 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -16,9 +16,11 @@
 
 package com.android.launcher3.taskbar;
 
-import static android.view.Display.DEFAULT_DISPLAY;
 
-import android.view.inputmethod.InputMethodManager;
+import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
+import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY;
+
+import android.os.Bundle;
 
 import androidx.annotation.IntDef;
 
@@ -35,11 +37,9 @@
  * Controller for 3 button mode in the taskbar.
  * Handles all the functionality of the various buttons, making/routing the right calls into
  * launcher or sysui/system.
- *
- * TODO: Create callbacks to hook into UI layer since state will change for more context buttons/
- *       assistant invocation.
  */
 public class TaskbarNavButtonController {
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {
             BUTTON_BACK,
@@ -47,7 +47,6 @@
             BUTTON_RECENTS,
             BUTTON_IME_SWITCH,
             BUTTON_A11Y,
-            BUTTON_A11Y_LONG_CLICK
     })
 
     public @interface TaskbarButton {}
@@ -57,7 +56,6 @@
     static final int BUTTON_RECENTS = BUTTON_HOME << 1;
     static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1;
     static final int BUTTON_A11Y = BUTTON_IME_SWITCH << 1;
-    static final int BUTTON_A11Y_LONG_CLICK = BUTTON_A11Y << 1;
 
     private final TouchInteractionService mService;
 
@@ -82,9 +80,22 @@
             case BUTTON_A11Y:
                 notifyImeClick(false /* longClick */);
                 break;
-            case BUTTON_A11Y_LONG_CLICK:
+        }
+    }
+
+    public boolean onButtonLongClick(@TaskbarButton int buttonType) {
+        switch (buttonType) {
+            case BUTTON_HOME:
+                startAssistant();
+                return true;
+            case BUTTON_A11Y:
                 notifyImeClick(true /* longClick */);
-                break;
+                return true;
+            case BUTTON_BACK:
+            case BUTTON_IME_SWITCH:
+            case BUTTON_RECENTS:
+            default:
+                return false;
         }
     }
 
@@ -113,4 +124,11 @@
             systemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId());
         }
     }
+
+    private void startAssistant() {
+        Bundle args = new Bundle();
+        args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
+        SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
+        systemUiProxy.startAssistant(args);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 60842c8..8965dc4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -19,6 +19,7 @@
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
 import android.animation.Animator;
@@ -48,11 +49,13 @@
     public static final int FLAG_STASHED_IN_APP_PINNED = 1 << 2; // app pinning
     public static final int FLAG_STASHED_IN_APP_EMPTY = 1 << 3; // no hotseat icons
     public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 4; // setup wizard and AllSetActivity
-    public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 5;
+    public static final int FLAG_STASHED_IN_APP_IME = 1 << 5; // IME is visible
+    public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 6;
 
     // If we're in an app and any of these flags are enabled, taskbar should be stashed.
     public static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
-            | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP;
+            | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
+            | FLAG_STASHED_IN_APP_IME;
 
     /**
      * How long to stash/unstash when manually invoked via long press.
@@ -60,6 +63,11 @@
     public static final long TASKBAR_STASH_DURATION = 300;
 
     /**
+     * How long to stash/unstash when keyboard is appearing/disappearing.
+     */
+    private static final long TASKBAR_STASH_DURATION_FOR_IME = 80;
+
+    /**
      * The scale TaskbarView animates to when being stashed.
      */
     private static final float STASHED_TASKBAR_SCALE = 0.5f;
@@ -100,6 +108,7 @@
     private TaskbarControllers mControllers;
     // Taskbar background properties.
     private AnimatedFloat mTaskbarBackgroundOffset;
+    private AnimatedFloat mTaskbarImeBgAlpha;
     // TaskbarView icon properties.
     private AlphaProperty mIconAlphaForStash;
     private AnimatedFloat mIconScaleForStash;
@@ -113,6 +122,8 @@
     private int mState;
 
     private @Nullable AnimatorSet mAnimator;
+    private boolean mIsSystemGestureInProgress;
+    private boolean mIsImeShowing;
 
     // Evaluate whether the handle should be stashed
     private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
@@ -137,6 +148,7 @@
 
         TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
         mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset();
+        mTaskbarImeBgAlpha = dragLayerController.getImeBgTaskbar();
 
         TaskbarViewController taskbarViewController = controllers.taskbarViewController;
         mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().getProperty(
@@ -271,17 +283,21 @@
      * Create a stash animation and save to {@link #mAnimator}.
      * @param isStashed whether it's a stash animation or an unstash animation
      * @param duration duration of the animation
+     * @param startDelay how many milliseconds to delay the animation after starting it.
      */
-    private void createAnimToIsStashed(boolean isStashed, long duration) {
+    private void createAnimToIsStashed(boolean isStashed, long duration, long startDelay) {
         if (mAnimator != null) {
             mAnimator.cancel();
         }
         mAnimator = new AnimatorSet();
 
         if (!supportsVisualStashing()) {
-            // Just hide/show the icons instead of stashing into a handle.
+            // Just hide/show the icons and background instead of stashing into a handle.
             mAnimator.play(mIconAlphaForStash.animateToValue(isStashed ? 0 : 1)
                     .setDuration(duration));
+            mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
+                    hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
+            mAnimator.setStartDelay(startDelay);
             mAnimator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
@@ -332,11 +348,8 @@
             );
         }
 
-        Animator stashedHandleRevealAnim = mControllers.stashedHandleViewController
-                .createRevealAnimToIsStashed(isStashed);
-        if (stashedHandleRevealAnim != null) {
-            fullLengthAnimatorSet.play(stashedHandleRevealAnim);
-        }
+        fullLengthAnimatorSet.play(mControllers.stashedHandleViewController
+                .createRevealAnimToIsStashed(isStashed));
         // Return the stashed handle to its default scale in case it was changed as part of the
         // feedforward hint. Note that the reveal animation above also visually scales it.
         fullLengthAnimatorSet.play(mTaskbarStashedHandleHintScale.animateToValue(1f));
@@ -348,6 +361,7 @@
 
         mAnimator.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
                 secondHalfAnimatorSet);
+        mAnimator.setStartDelay(startDelay);
         mAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
@@ -406,6 +420,10 @@
         mStatePropertyHolder.setState(mState, duration, true);
     }
 
+    public void applyState(long duration, long startDelay) {
+        mStatePropertyHolder.setState(mState, duration, startDelay, true);
+    }
+
     public Animator applyStateWithoutStart() {
         return applyStateWithoutStart(TASKBAR_STASH_DURATION);
     }
@@ -414,11 +432,50 @@
         return mStatePropertyHolder.setState(mState, duration, false);
     }
 
+    /**
+     * Should be called when a system gesture starts and settles, so we can defer updating
+     * FLAG_STASHED_IN_APP_IME until after the gesture transition completes.
+     */
+    public void setSystemGestureInProgress(boolean inProgress) {
+        mIsSystemGestureInProgress = inProgress;
+        // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
+        if (!mIsSystemGestureInProgress) {
+            updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+            applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme());
+        }
+    }
+
+    /**
+     * When hiding the IME, delay the unstash animation to align with the end of the transition.
+     */
+    private long getTaskbarStashStartDelayForIme() {
+        if (mIsImeShowing) {
+            // Only delay when IME is exiting, not entering.
+            return 0;
+        }
+        // This duration is based on input_method_extract_exit.xml.
+        long imeExitDuration = mControllers.taskbarActivityContext.getResources()
+                .getInteger(android.R.integer.config_shortAnimTime);
+        return imeExitDuration - TASKBAR_STASH_DURATION_FOR_IME;
+    }
+
     /** Called when some system ui state has changed. (See SYSUI_STATE_... in QuickstepContract) */
     public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
+        long animDuration = TASKBAR_STASH_DURATION;
+        long startDelay = 0;
+
         updateStateForFlag(FLAG_STASHED_IN_APP_PINNED,
                 hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING));
-        applyState(skipAnim ? 0 : TASKBAR_STASH_DURATION);
+
+        // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
+        mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
+        if (!mIsSystemGestureInProgress) {
+            updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+            animDuration = TASKBAR_STASH_DURATION_FOR_IME;
+            startDelay = getTaskbarStashStartDelayForIme();
+        }
+
+        applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay);
     }
 
     /**
@@ -474,16 +531,34 @@
             mStashCondition = stashCondition;
         }
 
+        /**
+         * @see #setState(int, long, long, boolean) with a default startDelay = 0.
+         */
         public Animator setState(int flags, long duration, boolean start) {
+            return setState(flags, duration, 0 /* startDelay */, start);
+        }
+
+        /**
+         * Applies the latest state, potentially calling onStateChangeApplied() and creating a new
+         * animation (stored in mAnimator) which is started if {@param start} is true.
+         * @param flags The latest flags to apply (see the top of this file).
+         * @param duration The length of the animation.
+         * @param startDelay How long to delay the animation after calling start().
+         * @param start Whether to start mAnimator immediately.
+         * @return mAnimator if mIsStashed changed, else null.
+         */
+        public Animator setState(int flags, long duration, long startDelay, boolean start) {
+            int changedFlags = mPrevFlags ^ flags;
             if (mPrevFlags != flags) {
-                int changedFlags = mPrevFlags ^ flags;
                 onStateChangeApplied(changedFlags);
                 mPrevFlags = flags;
             }
             boolean isStashed = mStashCondition.test(flags);
             if (mIsStashed != isStashed) {
                 mIsStashed = isStashed;
-                createAnimToIsStashed(mIsStashed, duration);
+
+                // This sets mAnimator.
+                createAnimToIsStashed(mIsStashed, duration, startDelay);
                 if (start) {
                     mAnimator.start();
                 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index d8360e0..f713dca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.taskbar;
 
 import android.graphics.Rect;
+import android.view.View;
 
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -29,7 +30,12 @@
 
     public static final TaskbarUIController DEFAULT = new TaskbarUIController();
 
-    protected void init(TaskbarControllers taskbarControllers) { }
+    // Initialized in init.
+    protected TaskbarControllers mControllers;
+
+    protected void init(TaskbarControllers taskbarControllers) {
+        mControllers = taskbarControllers;
+    }
 
     protected void onDestroy() { }
 
@@ -46,4 +52,16 @@
     }
 
     public void onTaskbarIconLaunched(WorkspaceItemInfo item) { }
+
+    public View getRootView() {
+        return mControllers.taskbarActivityContext.getDragLayer();
+    }
+
+    /**
+     * Called when swiping from the bottom nav region in fully gestural mode.
+     * @param inProgress True if the animation started, false if we just settled on an end target.
+     */
+    public void setSystemGestureInProgress(boolean inProgress) {
+        mControllers.taskbarStashController.setSystemGestureInProgress(inProgress);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 96eac51..a4d899c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -46,12 +46,11 @@
     private static final Runnable NO_OP = () -> { };
 
     public static final int ALPHA_INDEX_HOME = 0;
-    public static final int ALPHA_INDEX_IME = 1;
-    public static final int ALPHA_INDEX_KEYGUARD = 2;
-    public static final int ALPHA_INDEX_STASH = 3;
-    public static final int ALPHA_INDEX_RECENTS_DISABLED = 4;
-    public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 5;
-    private static final int NUM_ALPHA_CHANNELS = 6;
+    public static final int ALPHA_INDEX_KEYGUARD = 1;
+    public static final int ALPHA_INDEX_STASH = 2;
+    public static final int ALPHA_INDEX_RECENTS_DISABLED = 3;
+    public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4;
+    private static final int NUM_ALPHA_CHANNELS = 5;
 
     private final TaskbarActivityContext mActivity;
     private final TaskbarView mTaskbarView;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 2fa8b07..c85b256 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
@@ -35,6 +34,7 @@
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
 import android.util.FloatProperty;
+import android.util.Pair;
 
 import androidx.annotation.NonNull;
 
@@ -73,8 +73,6 @@
         getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
         RECENTS_GRID_PROGRESS.set(mRecentsView,
                 state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
-
-        applySplitScrollOffset(state);
     }
 
     @Override
@@ -104,11 +102,13 @@
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
         PagedOrientationHandler orientationHandler =
                 ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
-        FloatProperty taskViewsFloat = orientationHandler.getSplitSelectTaskOffset(
-                TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
-                mLauncher.getDeviceProfile());
-        setter.setFloat(mRecentsView, taskViewsFloat,
+        Pair<FloatProperty, FloatProperty> taskViewsFloat =
+                orientationHandler.getSplitSelectTaskOffset(
+                        TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
+                        mLauncher.getDeviceProfile());
+        setter.setFloat(mRecentsView, taskViewsFloat.first,
                 toState.getSplitSelectTranslation(mLauncher), LINEAR);
+        setter.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR);
 
         setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
                 config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
@@ -120,16 +120,6 @@
         boolean showAsGrid = toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile());
         setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
                 showAsGrid ? INSTANT : FINAL_FRAME);
-
-        applySplitScrollOffset(toState);
-    }
-
-    private void applySplitScrollOffset(@NonNull final LauncherState state) {
-        if (state == OVERVIEW_SPLIT_SELECT) {
-            mRecentsView.applySplitPrimaryScrollOffset();
-        } else {
-            mRecentsView.resetSplitPrimaryScrollOffset();
-        }
     }
 
     abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 1f744e1..b21d677 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -63,6 +63,9 @@
         }
         setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig(), state);
         mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
+        // In Overview, we may be layering app surfaces behind Launcher, so we need to notify
+        // DepthController to prevent optimizations which might occlude the layers behind
+        mLauncher.getDepthController().setHasContentBehindLauncher(state.overviewUi);
     }
 
     @Override
@@ -78,13 +81,19 @@
             builder.addListener(
                     AnimatorListeners.forSuccessCallback(mRecentsView::resetTaskVisuals));
         }
+        // In Overview, we may be layering app surfaces behind Launcher, so we need to notify
+        // DepthController to prevent optimizations which might occlude the layers behind
+        builder.addListener(AnimatorListeners.forSuccessCallback(() ->
+                mLauncher.getDepthController().setHasContentBehindLauncher(toState.overviewUi)));
 
         // Create or dismiss split screen select animations
         LauncherState currentState = mLauncher.getStateManager().getState();
         if (isSplitSelectionState(toState) && !isSplitSelectionState(currentState)) {
             builder.add(mRecentsView.createSplitSelectInitAnimation().buildAnim());
+            mRecentsView.applySplitPrimaryScrollOffset();
         } else if (!isSplitSelectionState(toState) && isSplitSelectionState(currentState)) {
             builder.add(mRecentsView.cancelSplitSelect(true).buildAnim());
+            mRecentsView.resetSplitPrimaryScrollOffset();
         }
 
         setAlphas(builder, config, toState);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index d396018..a4eff87 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -21,13 +21,11 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.SystemProperties;
-import android.view.View;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.util.Themes;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.util.LayoutUtils;
@@ -66,10 +64,7 @@
     @Override
     public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
         RecentsView recentsView = launcher.getOverviewPanel();
-        Workspace workspace = launcher.getWorkspace();
-        View workspacePage = workspace.getPageAt(workspace.getCurrentPage());
-        float workspacePageWidth = workspacePage != null && workspacePage.getWidth() != 0
-                ? workspacePage.getWidth() : launcher.getDeviceProfile().availableWidthPx;
+        float workspacePageWidth = launcher.getDeviceProfile().getWorkspaceWidth();
         recentsView.getTaskSize(sTempRect);
         float scale = (float) sTempRect.width() / workspacePageWidth;
         float parallaxFactor = 0.5f;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 45b2081..c9cbba1 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -121,8 +121,8 @@
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.function.Consumer;
 
 /**
@@ -687,14 +687,17 @@
     }
 
     private void onAnimatorPlaybackControllerCreated(AnimatorControllerWithResistance anim) {
+        boolean isFirstCreation = mLauncherTransitionController == null;
         mLauncherTransitionController = anim;
-        mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> {
-            // Wait until the gesture is started (touch slop was passed) to start in sync with
-            // mWindowTransitionController. This ensures we don't hide the taskbar background when
-            // long pressing to stash it, for instance.
-            mLauncherTransitionController.getNormalController().dispatchOnStart();
-            updateLauncherTransitionProgress();
-        });
+        if (isFirstCreation) {
+            mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> {
+                // Wait until the gesture is started (touch slop was passed) to start in sync with
+                // mWindowTransitionController. This ensures we don't hide the taskbar background
+                // when long pressing to stash it, for instance.
+                mLauncherTransitionController.getNormalController().dispatchOnStart();
+                updateLauncherTransitionProgress();
+            });
+        }
     }
 
     public Intent getLaunchIntent() {
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index e15aa92..a566765 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -30,6 +30,7 @@
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -398,6 +399,11 @@
      * (This is a hack to ensure Taskbar draws its background first to avoid flickering.)
      */
     public @Nullable View onSettledOnEndTarget(GestureState.GestureEndTarget endTarget) {
+        TaskbarUIController taskbarUIController = getTaskbarController();
+        if (taskbarUIController != null) {
+            taskbarUIController.setSystemGestureInProgress(false);
+            return taskbarUIController.getRootView();
+        }
         return null;
     }
 
@@ -533,6 +539,16 @@
             pa.addFloat(recentsView, RECENTS_SCALE_PROPERTY,
                     recentsView.getMaxScaleForFullScreen(), 1, LINEAR);
             pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
+
+            pa.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    TaskbarUIController taskbarUIController = getTaskbarController();
+                    if (taskbarUIController != null) {
+                        taskbarUIController.setSystemGestureInProgress(true);
+                    }
+                }
+            });
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index aa9435b..719c2ae 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -25,12 +25,10 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.content.Context;
 import android.graphics.Rect;
 import android.view.MotionEvent;
-import android.view.View;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
@@ -132,19 +130,6 @@
                 pa.addFloat(getDepthController(),
                         new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
                         fromDepthRatio, toDepthRatio, LINEAR);
-
-                pa.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        LauncherTaskbarUIController taskbarUIController =
-                                activity.getTaskbarUIController();
-                        if (taskbarUIController != null) {
-                            // Launcher's ScrimView will draw the background throughout the gesture.
-                            taskbarUIController.forceHideBackground(true);
-                        }
-                    }
-                });
-
             }
         };
 
@@ -366,16 +351,4 @@
                 return NORMAL;
         }
     }
-
-    @Override
-    public View onSettledOnEndTarget(@Nullable GestureEndTarget endTarget) {
-        View superRet = super.onSettledOnEndTarget(endTarget);
-        LauncherTaskbarUIController taskbarUIController = getTaskbarController();
-        if (taskbarUIController != null) {
-            // Start drawing taskbar's background again since launcher might stop drawing.
-            taskbarUIController.forceHideBackground(false);
-            return taskbarUIController.getRootView();
-        }
-        return superRet;
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 68b7558..a9fef41 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -571,12 +571,12 @@
 
     /** Start multiple tasks in split-screen simultaneously. */
     public void startTasks(int mainTaskId, Bundle mainOptions, int sideTaskId, Bundle sideOptions,
-            @SplitConfigurationOptions.StagePosition int sidePosition,
+            @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
             RemoteTransitionCompat remoteTransition) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startTasks(mainTaskId, mainOptions, sideTaskId, sideOptions,
-                        sidePosition, remoteTransition.getTransition());
+                        sidePosition, splitRatio, remoteTransition.getTransition());
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startTask");
             }
@@ -588,11 +588,11 @@
      */
     public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId,
             Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition,
-            RemoteAnimationAdapter adapter) {
+            float splitRatio, RemoteAnimationAdapter adapter) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId,
-                        sideOptions, sidePosition, adapter);
+                        sideOptions, sidePosition, splitRatio, adapter);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startTasksWithLegacyTransition");
             }
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 8c4ba97..cbdbdb5 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -72,7 +72,13 @@
         @Override
         public SystemShortcut getShortcut(BaseDraggingActivity activity,
                 TaskIdAttributeContainer taskContainer) {
-            return new AppInfo(activity, taskContainer.getItemInfo());
+            TaskView taskView = taskContainer.getTaskView();
+            AppInfo.SplitAccessibilityInfo accessibilityInfo =
+                    new AppInfo.SplitAccessibilityInfo(taskView.containsMultipleTasks(),
+                            TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()),
+                            taskContainer.getA11yNodeId()
+                    );
+            return new AppInfo(activity, taskContainer.getItemInfo(), accessibilityInfo);
         }
 
         @Override
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index e1894ae..707b905 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -22,7 +22,6 @@
 
 import static com.android.launcher3.config.FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.testing.TestProtocol.TASKBAR_WINDOW_CRASH;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -353,7 +352,6 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        Log.d(TASKBAR_WINDOW_CRASH, "TIS created");
         // Initialize anything here that is needed in direct boot mode.
         // Everything else should be initialized in onUserUnlocked() below.
         mMainChoreographer = Choreographer.getInstance();
@@ -507,7 +505,6 @@
     @Override
     public void onDestroy() {
         Log.d(TAG, "Touch service destroyed: user=" + getUserId());
-        Log.d(TASKBAR_WINDOW_CRASH, "TIS destroyed");
         sIsInitialized = false;
         if (mDeviceState.isUserUnlocked()) {
             mInputConsumer.unregisterInputConsumer();
diff --git a/quickstep/src/com/android/quickstep/ViewUtils.java b/quickstep/src/com/android/quickstep/ViewUtils.java
index 184ab17..e290be8 100644
--- a/quickstep/src/com/android/quickstep/ViewUtils.java
+++ b/quickstep/src/com/android/quickstep/ViewUtils.java
@@ -15,8 +15,10 @@
  */
 package com.android.quickstep;
 
+import android.graphics.HardwareRenderer;
 import android.os.Handler;
 import android.view.View;
+import android.view.ViewRootImpl;
 
 import com.android.launcher3.Utilities;
 import com.android.systemui.shared.system.ViewRootImplCompat;
@@ -45,9 +47,9 @@
         return new FrameHandler(view, onFinishRunnable, canceled).schedule();
     }
 
-    private static class FrameHandler implements LongConsumer {
+    private static class FrameHandler implements HardwareRenderer.FrameDrawingCallback {
 
-        final ViewRootImplCompat mViewRoot;
+        final ViewRootImpl mViewRoot;
         final Runnable mFinishCallback;
         final BooleanSupplier mCancelled;
         final Handler mHandler;
@@ -55,14 +57,14 @@
         int mDeferFrameCount = 1;
 
         FrameHandler(View view, Runnable finishCallback, BooleanSupplier cancelled) {
-            mViewRoot = new ViewRootImplCompat(view);
+            mViewRoot = view.getViewRootImpl();
             mFinishCallback = finishCallback;
             mCancelled = cancelled;
             mHandler = new Handler();
         }
 
         @Override
-        public void accept(long l) {
+        public void onFrameDraw(long frame) {
             Utilities.postAsyncCallback(mHandler, this::onFrame);
         }
 
@@ -83,7 +85,7 @@
         }
 
         private boolean schedule() {
-            if (mViewRoot.isValid()) {
+            if (mViewRoot.getView() != null) {
                 mViewRoot.registerRtFrameCallback(this);
                 mViewRoot.getView().invalidate();
                 return true;
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 0ae415d..5b4eb8b 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -16,7 +16,6 @@
 package com.android.quickstep.fallback;
 
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
-import static com.android.quickstep.ViewUtils.postFrameDrawn;
 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
 import static com.android.quickstep.fallback.RecentsState.HOME;
 import static com.android.quickstep.fallback.RecentsState.MODAL_TASK;
@@ -32,18 +31,19 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.quickstep.FallbackActivityInterface;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
-import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 
@@ -74,6 +74,7 @@
     @Override
     public void startHome() {
         mActivity.startHome();
+        AbstractFloatingView.closeAllOpenViews(mActivity, mActivity.isStarted());
     }
 
     /**
@@ -208,10 +209,6 @@
 
     @Override
     public void onStateTransitionStart(RecentsState toState) {
-        if (toState == HOME) {
-            // Clean-up logic that occurs when recents is no longer in use/visible.
-            reset();
-        }
         setOverviewStateEnabled(true);
         setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
         setOverviewFullscreenEnabled(toState.isFullScreen());
@@ -220,13 +217,17 @@
 
     @Override
     public void onStateTransitionComplete(RecentsState finalState) {
+        if (finalState == HOME) {
+            // Clean-up logic that occurs when recents is no longer in use/visible.
+            reset();
+        }
         boolean isOverlayEnabled = finalState == DEFAULT || finalState == MODAL_TASK;
         setOverlayEnabled(isOverlayEnabled);
         setFreezeViewVisibility(false);
 
         if (isOverlayEnabled) {
-            postFrameDrawn(this, () -> runActionOnRemoteHandles(remoteTargetHandle ->
-                    remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true)));
+            runActionOnRemoteHandles(remoteTargetHandle ->
+                    remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
index 510820a..162ace4 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
@@ -24,6 +24,8 @@
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 
+import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_GESTURE;
+import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY;
 import static com.android.launcher3.Utilities.squaredHypot;
 
 import android.animation.Animator;
@@ -64,8 +66,6 @@
     private static final String OPA_BUNDLE_TRIGGER = "triggered_by";
     // From //java/com/google/android/apps/gsa/assistant/shared/proto/opa_trigger.proto.
     private static final int OPA_BUNDLE_TRIGGER_DIAG_SWIPE_GESTURE = 83;
-    private static final String INVOCATION_TYPE_KEY = "invocation_type";
-    private static final int INVOCATION_TYPE_GESTURE = 1;
 
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index c784d82..d310893 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 
@@ -30,7 +31,6 @@
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 
-
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.statehandlers.DepthController;
@@ -95,7 +95,7 @@
     public void setSecondTaskId(Task task, Consumer<Boolean> callback) {
         mSecondTask = task;
         launchTasks(mInitialTask, mSecondTask, mStagePosition, callback,
-                false /* freezeTaskList */);
+                false /* freezeTaskList */, DEFAULT_SPLIT_RATIO);
     }
 
     /**
@@ -107,14 +107,15 @@
         TaskView.TaskIdAttributeContainer[] taskIdAttributeContainers =
                 groupedTaskView.getTaskIdAttributeContainers();
         launchTasks(taskIdAttributeContainers[0].getTask(), taskIdAttributeContainers[1].getTask(),
-                taskIdAttributeContainers[0].getStagePosition(), callback, freezeTaskList);
+                taskIdAttributeContainers[0].getStagePosition(), callback, freezeTaskList,
+                groupedTaskView.getSplitRatio());
     }
 
     /**
      * @param stagePosition representing location of task1
      */
     public void launchTasks(Task task1, Task task2, @StagePosition int stagePosition,
-            Consumer<Boolean> callback, boolean freezeTaskList) {
+            Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
         // Assume initial task is for top/left part of screen
         final int[] taskIds = stagePosition == STAGE_POSITION_TOP_OR_LEFT
                 ? new int[]{task1.key.id, task2.key.id}
@@ -123,7 +124,7 @@
             RemoteSplitLaunchTransitionRunner animationRunner =
                     new RemoteSplitLaunchTransitionRunner(task1, task2);
             mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],
-                    null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
+                    null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, splitRatio,
                     new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR,
                             ActivityThread.currentActivityThread().getApplicationThread()));
         } else {
@@ -140,7 +141,7 @@
             }
             mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(),
                     taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
-                    adapter);
+                    splitRatio, adapter);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java b/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
index 3b4fd31..3b1c150 100644
--- a/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
+++ b/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
@@ -22,10 +22,10 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.View;
+import android.view.ViewRootImpl;
 
 import com.android.quickstep.RemoteAnimationTargets.ReleaseCheck;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
-import com.android.systemui.shared.system.ViewRootImplCompat;
 
 import java.util.function.Consumer;
 
@@ -41,7 +41,7 @@
     private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0;
 
     private final SurfaceControl mBarrierSurfaceControl;
-    private final ViewRootImplCompat mTargetViewRootImpl;
+    private final ViewRootImpl mTargetViewRootImpl;
     private final Handler mApplyHandler;
 
     private int mLastSequenceNumber = 0;
@@ -50,8 +50,8 @@
      * @param targetView The view in the surface that acts as synchronization anchor.
      */
     public SurfaceTransactionApplier(View targetView) {
-        mTargetViewRootImpl = new ViewRootImplCompat(targetView);
-        mBarrierSurfaceControl = mTargetViewRootImpl.getRenderSurfaceControl();
+        mTargetViewRootImpl = targetView.getViewRootImpl();
+        mBarrierSurfaceControl = mTargetViewRootImpl.getSurfaceControl();
         mApplyHandler = new Handler(this::onApplyMessage);
     }
 
@@ -109,7 +109,7 @@
         if (targetView == null) {
             // No target view, no applier
             callback.accept(null);
-        } else if (new ViewRootImplCompat(targetView).isValid()) {
+        } else if (targetView.isAttachedToWindow()) {
             // Already attached, we're good to go
             callback.accept(new SurfaceTransactionApplier(targetView));
         } else {
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
index c3b166f..adea1a4 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
@@ -136,7 +136,7 @@
     }
 
     /** Returns the maximum corner radius of {@param drawable}. */
-    private static float getMaxRadius(Drawable drawable) {
+    private static float getMaxRadius(@Nullable Drawable drawable) {
         if (!(drawable instanceof GradientDrawable)) return 0;
         float[] cornerRadii = ((GradientDrawable) drawable).getCornerRadii();
         float cornerRadius = ((GradientDrawable) drawable).getCornerRadius();
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 4771d1e..00f541d 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -1,5 +1,6 @@
 package com.android.quickstep.views;
 
+import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 
@@ -122,6 +123,14 @@
         invalidate();
     }
 
+    public float getSplitRatio() {
+        if (mSplitBoundsConfig != null) {
+            return mSplitBoundsConfig.appsStackedVertically
+                    ? mSplitBoundsConfig.topTaskPercent : mSplitBoundsConfig.leftTaskPercent;
+        }
+        return DEFAULT_SPLIT_RATIO;
+    }
+
     @Override
     public boolean offerTouchToChildren(MotionEvent event) {
         computeAndSetIconTouchDelegate(mIconView2, mIcon2CenterCoords, mIcon2TouchDelegate);
@@ -157,7 +166,8 @@
     @Override
     public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
         getRecentsView().getSplitPlaceholder().launchTasks(mTask, mSecondaryTask,
-                STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList);
+                STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList,
+                getSplitRatio());
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java
index ccb1a99..5895c05 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.java
+++ b/quickstep/src/com/android/quickstep/views/IconView.java
@@ -87,6 +87,14 @@
         return mDrawable;
     }
 
+    public int getDrawableWidth() {
+        return mDrawableWidth;
+    }
+
+    public int getDrawableHeight() {
+        return mDrawableHeight;
+    }
+
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 3cba392..3e06f55 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
-import static com.android.quickstep.ViewUtils.postFrameDrawn;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -33,6 +32,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.statehandlers.DepthController;
@@ -71,6 +71,7 @@
     @Override
     public void startHome() {
         mActivity.getStateManager().goToState(NORMAL);
+        AbstractFloatingView.closeAllOpenViews(mActivity, mActivity.isStarted());
     }
 
     @Override
@@ -93,10 +94,6 @@
 
     @Override
     public void onStateTransitionStart(LauncherState toState) {
-        if (toState == NORMAL || toState == SPRING_LOADED) {
-            // Clean-up logic that occurs when recents is no longer in use/visible.
-            reset();
-        }
         setOverviewStateEnabled(toState.overviewUi);
         setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
         setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
@@ -105,13 +102,17 @@
 
     @Override
     public void onStateTransitionComplete(LauncherState finalState) {
+        if (finalState == NORMAL || finalState == SPRING_LOADED) {
+            // Clean-up logic that occurs when recents is no longer in use/visible.
+            reset();
+        }
         boolean isOverlayEnabled = finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK;
         setOverlayEnabled(isOverlayEnabled);
         setFreezeViewVisibility(false);
 
         if (isOverlayEnabled) {
-            postFrameDrawn(this, () -> runActionOnRemoteHandles(remoteTargetHandle ->
-                    remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true)));
+            runActionOnRemoteHandles(remoteTargetHandle ->
+                    remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index ddd2a82..9cf76b3 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -107,6 +107,7 @@
 import android.widget.OverScroller;
 import android.widget.Toast;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.core.graphics.ColorUtils;
@@ -181,6 +182,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
@@ -822,7 +824,7 @@
     @Override
     public void onTaskIconChanged(String pkg, UserHandle user) {
         for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView tv = getTaskViewAt(i);
+            TaskView tv = requireTaskViewAt(i);
             Task task = tv.getTask();
             if (task != null && task.key != null && pkg.equals(task.key.getPackageName())
                     && task.key.userId == user.getIdentifier()) {
@@ -1104,7 +1106,7 @@
         }
 
         for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView taskView = getTaskViewAt(i);
+            TaskView taskView = requireTaskViewAt(i);
             int[] taskIds = taskView.getTaskIds();
             if (taskIds[0] == taskId || taskIds[1] == taskId) {
                 return taskView;
@@ -1181,7 +1183,7 @@
         if (showAsGrid()) {
             int taskCount = getTaskViewCount();
             for (int i = 0; i < taskCount; i++) {
-                TaskView taskView = getTaskViewAt(i);
+                TaskView taskView = requireTaskViewAt(i);
                 if (isTaskViewVisible(taskView) && taskView.offerTouchToChildren(ev)) {
                     // Keep consuming events to pass to delegate
                     return true;
@@ -1418,7 +1420,7 @@
             if (runningTaskId != -1) {
                 targetPage = indexOfChild(newRunningTaskView);
             } else if (getTaskViewCount() > 0) {
-                targetPage = indexOfChild(getTaskViewAt(0));
+                targetPage = indexOfChild(requireTaskViewAt(0));
             }
         } else if (currentTaskId != -1) {
             currentTaskView = getTaskViewByTaskId(currentTaskId);
@@ -1452,7 +1454,7 @@
 
     private void removeTasksViewsAndClearAllButton() {
         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
-            removeView(getTaskViewAt(i));
+            removeView(requireTaskViewAt(i));
         }
         if (indexOfChild(mClearAllButton) != -1) {
             removeView(mClearAllButton);
@@ -1498,7 +1500,7 @@
 
     public void resetTaskVisuals() {
         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
-            TaskView taskView = getTaskViewAt(i);
+            TaskView taskView = requireTaskViewAt(i);
             if (mIgnoreResetTaskId != taskView.getTaskIds()[0]) {
                 taskView.resetViewTransforms();
                 taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
@@ -1508,6 +1510,16 @@
             }
         }
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            // resetTaskVisuals is called at the end of dismiss animation which could update
+            // primary and secondary translation of the live tile cut out. We will need to do so
+            // here accordingly.
+            runActionOnRemoteHandles(remoteTargetHandle -> {
+                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
             // null.
             if (!mRunningTaskShowScreenshot) {
@@ -1529,7 +1541,7 @@
         mFullscreenProgress = fullscreenProgress;
         int taskCount = getTaskViewCount();
         for (int i = 0; i < taskCount; i++) {
-            getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
+            requireTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
         }
         mClearAllButton.setFullscreenProgress(fullscreenProgress);
 
@@ -1656,7 +1668,7 @@
 
         float accumulatedTranslationX = 0;
         for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = getTaskViewAt(i);
+            TaskView taskView = requireTaskViewAt(i);
             taskView.updateTaskSize();
             taskView.getPrimaryNonGridTranslationProperty().set(taskView, accumulatedTranslationX);
             taskView.getSecondaryNonGridTranslationProperty().set(taskView, 0f);
@@ -1802,7 +1814,7 @@
 
         // Update the task data for the in/visible children
         for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView taskView = getTaskViewAt(i);
+            TaskView taskView = requireTaskViewAt(i);
             Task task = taskView.getTask();
             int index = indexOfChild(taskView);
             boolean visible;
@@ -1945,7 +1957,7 @@
         }
 
         for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView taskView = getTaskViewAt(i);
+            TaskView taskView = requireTaskViewAt(i);
             if (taskView.getTaskViewId() == taskViewId) {
                 return taskView;
             }
@@ -2043,7 +2055,7 @@
         int runningIndex = getCurrentPage();
         AnimatorSet as = new AnimatorSet();
         for (int i = 0; i < getTaskViewCount(); i++) {
-            View taskView = getTaskViewAt(i);
+            View taskView = requireTaskViewAt(i);
             if (runningIndex == i && taskView.getAlpha() != 0) {
                 continue;
             }
@@ -2054,7 +2066,7 @@
 
     private void updateChildTaskOrientations() {
         for (int i = 0; i < getTaskViewCount(); i++) {
-            getTaskViewAt(i).setOrientationState(mOrientationState);
+            requireTaskViewAt(i).setOrientationState(mOrientationState);
         }
         TaskMenuView tv = (TaskMenuView) getTopOpenViewWithType(mActivity, TYPE_TASK_MENU);
         if (tv != null) {
@@ -2259,7 +2271,7 @@
             mTaskIconScaledDown = isScaledDown;
             int taskCount = getTaskViewCount();
             for (int i = 0; i < taskCount; i++) {
-                getTaskViewAt(i).setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
+                requireTaskViewAt(i).setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
             }
         }
     }
@@ -2275,7 +2287,7 @@
         mTaskIconScaledDown = false;
         int taskCount = getTaskViewCount();
         for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = getTaskViewAt(i);
+            TaskView taskView = requireTaskViewAt(i);
             taskView.setIconScaleAnimStartProgress(0f);
             taskView.animateIconScaleAndDimIntoView();
         }
@@ -2347,7 +2359,7 @@
             mTopRowIdSet.clear();
         }
         for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = getTaskViewAt(i);
+            TaskView taskView = requireTaskViewAt(i);
             int taskWidthAndSpacing = taskView.getLayoutParams().width + mPageSpacing;
             // Evenly distribute tasks between rows unless rearranging due to task dismissal, in
             // which case keep tasks in their respective rows. For the running task, don't join
@@ -2412,7 +2424,7 @@
                         if (j == focusedTaskIndex) {
                             continue;
                         }
-                        widthOffset += getTaskViewAt(j).getLayoutParams().width + mPageSpacing;
+                        widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing;
                     }
 
                     float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset;
@@ -2431,7 +2443,7 @@
                         if (j == focusedTaskIndex) {
                             continue;
                         }
-                        widthOffset += getTaskViewAt(j).getLayoutParams().width + mPageSpacing;
+                        widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing;
                     }
 
                     float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset;
@@ -2509,7 +2521,7 @@
         }
 
         for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = getTaskViewAt(i);
+            TaskView taskView = requireTaskViewAt(i);
             taskView.setGridTranslationX(gridTranslations[i] - snappedTaskGridTranslationX
                     + snappedTaskNonGridScrollAdjustment);
         }
@@ -2550,7 +2562,7 @@
         mGridProgress = gridProgress;
 
         for (int i = 0; i < taskCount; i++) {
-            getTaskViewAt(i).setGridProgress(gridProgress);
+            requireTaskViewAt(i).setGridProgress(gridProgress);
         }
         mClearAllButton.setGridProgress(gridProgress);
     }
@@ -2612,10 +2624,8 @@
                         clampToProgress(FINAL_FRAME, 0, 0.5f));
             });
         }
-        boolean isTaskInBottomGridRow = showAsGrid() && !mTopRowIdSet.contains(
-                taskView.getTaskViewId()) && taskView.getTaskViewId() != mFocusedTaskViewId;
         anim.setFloat(taskView, VIEW_ALPHA, 0,
-                clampToProgress(isTaskInBottomGridRow ? ACCEL : FINAL_FRAME, 0, 0.5f));
+                clampToProgress(isOnGridBottomRow(taskView) ? ACCEL : FINAL_FRAME, 0, 0.5f));
         FloatProperty<TaskView> secondaryViewTranslate =
                 taskView.getSecondaryDissmissTranslationProperty();
         int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
@@ -2715,7 +2725,7 @@
                         mTopRowIdSet.size() > 0 && mTopRowIdSet.size() >= (taskCount - 1) / 2f;
                 // Pick the next focused task from the preferred row.
                 for (int i = 0; i < taskCount; i++) {
-                    TaskView taskView = getTaskViewAt(i);
+                    TaskView taskView = requireTaskViewAt(i);
                     if (taskView == dismissedTaskView) {
                         continue;
                     }
@@ -2818,7 +2828,7 @@
                                 + (taskCount - 1) * halfAdditionalDismissTranslationOffset,
                         END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1);
                 for (int i = 0; i < taskCount; i++) {
-                    TaskView taskView = getTaskViewAt(i);
+                    TaskView taskView = requireTaskViewAt(i);
                     anim.setFloat(taskView, TaskView.GRID_END_TRANSLATION_X, longGridRowWidthDiff,
                             clampToProgress(LINEAR, dismissTranslationInterpolationEnd, 1));
                     dismissTranslationInterpolationEnd = Utilities.boundToRange(
@@ -3120,14 +3130,10 @@
                         }
                     } else {
                         // Update focus task and its size.
-                        if (finalIsFocusedTaskDismissed) {
-                            if (finalNextFocusedTaskView != null) {
-                                mFocusedTaskViewId = finalNextFocusedTaskView.getTaskViewId();
-                                mTopRowIdSet.remove(mFocusedTaskViewId);
-                                finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
-                            } else {
-                                mFocusedTaskViewId = -1;
-                            }
+                        if (finalIsFocusedTaskDismissed && finalNextFocusedTaskView != null) {
+                            mFocusedTaskViewId = finalNextFocusedTaskView.getTaskViewId();
+                            mTopRowIdSet.remove(mFocusedTaskViewId);
+                            finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
                         }
                         updateTaskSize(/*isTaskDismissal=*/ true);
                         updateChildTaskOrientations();
@@ -3138,7 +3144,7 @@
                             // Rebalance tasks in the grid
                             int highestVisibleTaskIndex = getHighestVisibleTaskIndex();
                             if (highestVisibleTaskIndex < Integer.MAX_VALUE) {
-                                TaskView taskView = getTaskViewAt(highestVisibleTaskIndex);
+                                TaskView taskView = requireTaskViewAt(highestVisibleTaskIndex);
 
                                 boolean shouldRebalance;
                                 int screenStart = mOrientationHandler.getPrimaryScroll(
@@ -3191,6 +3197,7 @@
                                 }
                             }
                         }
+                        pageBeginTransition();
                         setCurrentPage(pageToSnapTo);
                         // Update various scroll-dependent UI.
                         dispatchScrollChanged();
@@ -3235,7 +3242,7 @@
         IntArray topArray = new IntArray(mTopRowIdSet.size());
         int taskViewCount = getTaskViewCount();
         for (int i = 0; i < taskViewCount; i++) {
-            int taskViewId = getTaskViewAt(i).getTaskViewId();
+            int taskViewId = requireTaskViewAt(i).getTaskViewId();
             if (mTopRowIdSet.contains(taskViewId)) {
                 topArray.add(taskViewId);
             }
@@ -3254,7 +3261,7 @@
         IntArray bottomArray = new IntArray(bottomRowIdArraySize);
         int taskViewCount = getTaskViewCount();
         for (int i = 0; i < taskViewCount; i++) {
-            int taskViewId = getTaskViewAt(i).getTaskViewId();
+            int taskViewId = requireTaskViewAt(i).getTaskViewId();
             if (!mTopRowIdSet.contains(taskViewId) && taskViewId != mFocusedTaskViewId) {
                 bottomArray.add(taskViewId);
             }
@@ -3324,7 +3331,7 @@
 
         int count = getTaskViewCount();
         for (int i = 0; i < count; i++) {
-            addDismissedTaskAnimations(getTaskViewAt(i), duration, anim);
+            addDismissedTaskAnimations(requireTaskViewAt(i), duration, anim);
         }
 
         mPendingAnimation = anim;
@@ -3447,7 +3454,7 @@
         mContentAlpha = alpha;
         int runningTaskId = getTaskIdsForRunningTaskView()[0];
         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
-            TaskView child = getTaskViewAt(i);
+            TaskView child = requireTaskViewAt(i);
             int[] childTaskIds = child.getTaskIds();
             if (!mRunningTaskTileHidden ||
                     (childTaskIds[0] != runningTaskId && childTaskIds[1] != runningTaskId)) {
@@ -3548,6 +3555,14 @@
         return child instanceof TaskView ? (TaskView) child : null;
     }
 
+    /**
+     * A version of {@link #getTaskViewAt} when the caller is sure about the input index.
+     */
+    @NonNull
+    private TaskView requireTaskViewAt(int index) {
+        return Objects.requireNonNull(getTaskViewAt(index));
+    }
+
     public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) {
         mOnEmptyMessageUpdatedListener = listener;
     }
@@ -3764,7 +3779,7 @@
     protected void setTaskViewsResistanceTranslation(float translation) {
         mTaskViewsSecondaryTranslation = translation;
         for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView task = getTaskViewAt(i);
+            TaskView task = requireTaskViewAt(i);
             task.getTaskResistanceTranslationProperty().set(task, translation / getScaleY());
         }
         runActionOnRemoteHandles(
@@ -3774,14 +3789,14 @@
 
     private void updateTaskViewsSnapshotRadius() {
         for (int i = 0; i < getTaskViewCount(); i++) {
-            getTaskViewAt(i).updateSnapshotRadius();
+            requireTaskViewAt(i).updateSnapshotRadius();
         }
     }
 
     protected void setTaskViewsPrimarySplitTranslation(float translation) {
         mTaskViewsPrimarySplitTranslation = translation;
         for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView task = getTaskViewAt(i);
+            TaskView task = requireTaskViewAt(i);
             task.getPrimarySplitTranslationProperty().set(task, translation);
         }
     }
@@ -3789,7 +3804,7 @@
     protected void setTaskViewsSecondarySplitTranslation(float translation) {
         mTaskViewsSecondarySplitTranslation = translation;
         for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView taskView = getTaskViewAt(i);
+            TaskView taskView = requireTaskViewAt(i);
             if (taskView == mSplitHiddenTaskView) {
                 continue;
             }
@@ -3803,7 +3818,7 @@
     public void applySplitPrimaryScrollOffset() {
         if (isSplitPlaceholderFirstInGrid()) {
             for (int i = 0; i < getTaskViewCount(); i++) {
-                getTaskViewAt(i).setSplitScrollOffsetPrimary(mSplitPlaceholderSize);
+                requireTaskViewAt(i).setSplitScrollOffsetPrimary(mSplitPlaceholderSize);
             }
         } else if (isSplitPlaceholderLastInGrid()) {
             mClearAllButton.setSplitSelectScrollOffsetPrimary(-mSplitPlaceholderSize);
@@ -3845,7 +3860,7 @@
      */
     public void resetSplitPrimaryScrollOffset() {
         for (int i = 0; i < getTaskViewCount(); i++) {
-            getTaskViewAt(i).setSplitScrollOffsetPrimary(0);
+            requireTaskViewAt(i).setSplitScrollOffsetPrimary(0);
         }
         mClearAllButton.setSplitSelectScrollOffsetPrimary(0);
     }
@@ -4021,6 +4036,7 @@
                 //  * Focused Task
                 updateGridProperties();
                 resetFromSplitSelectionState();
+                updateScrollSynchronously();
             }
         });
 
@@ -4029,7 +4045,7 @@
 
     /** TODO(b/181707736) More gracefully handle exiting split selection state */
     private void resetFromSplitSelectionState() {
-        if (!showAsGrid()) {
+        if (!mActivity.getDeviceProfile().overviewShowAsGrid) {
             int pageToSnapTo = mCurrentPage;
             if (mSplitHiddenTaskViewIndex <= pageToSnapTo) {
                 pageToSnapTo += 1;
@@ -4042,7 +4058,6 @@
         resetTaskVisuals();
         mSplitHiddenTaskViewIndex = -1;
         if (mSplitHiddenTaskView != null) {
-            mSplitHiddenTaskView.setTranslationY(0);
             mSplitHiddenTaskView.setVisibility(VISIBLE);
             mSplitHiddenTaskView = null;
         }
@@ -4072,8 +4087,8 @@
         mTaskViewDeadZoneRect.setEmpty();
         int count = getTaskViewCount();
         if (count > 0) {
-            final View taskView = getTaskViewAt(0);
-            getTaskViewAt(count - 1).getHitRect(mTaskViewDeadZoneRect);
+            final View taskView = requireTaskViewAt(0);
+            requireTaskViewAt(count - 1).getHitRect(mTaskViewDeadZoneRect);
             mTaskViewDeadZoneRect.union(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
                     taskView.getBottom());
         }
@@ -4503,9 +4518,8 @@
     }
 
     private int getFirstViewIndex() {
-        return mShowAsGridLastOnLayout && mFocusedTaskViewId != -1
-                ? indexOfChild(getFocusedTaskView())
-                : 0;
+        TaskView focusedTaskView = mShowAsGridLastOnLayout ? getFocusedTaskView() : null;
+        return focusedTaskView != null ? indexOfChild(focusedTaskView) : 0;
     }
 
     private int getLastViewIndex() {
@@ -4555,7 +4569,7 @@
 
         final int taskCount = getTaskViewCount();
         for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = getTaskViewAt(i);
+            TaskView taskView = requireTaskViewAt(i);
             float scrollDiff = taskView.getScrollAdjustment(showAsFullscreen, showAsGrid);
             int pageScroll = newPageScrolls[i] + (int) scrollDiff;
             if ((mIsRtl && pageScroll < clearAllScroll + clearAllWidth)
@@ -4681,6 +4695,15 @@
         return position != -1 ? position : bottomRowIdArray.indexOf(taskView.getTaskViewId());
     }
 
+    /**
+     * @return true if the task in on the top of the grid
+     */
+    public boolean isOnGridBottomRow(TaskView taskView) {
+        return showAsGrid()
+                && !mTopRowIdSet.contains(taskView.getTaskViewId())
+                && taskView.getTaskViewId() != mFocusedTaskViewId;
+    }
+
     public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
         float degreesRotated;
         if (navbarRotation == 0) {
@@ -4714,7 +4737,7 @@
         int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1;
         int taskCount = getTaskViewCount();
         for (int i = 0; i < taskCount; i++) {
-            getTaskViewAt(i).setOverlayEnabled(i == overlayEnabledPage);
+            requireTaskViewAt(i).setOverlayEnabled(i == overlayEnabledPage);
         }
     }
 
@@ -4868,7 +4891,7 @@
         mColorTint = tintAmount;
 
         for (int i = 0; i < getTaskViewCount(); i++) {
-            getTaskViewAt(i).setColorTint(mColorTint, mTintingColor);
+            requireTaskViewAt(i).setColorTint(mColorTint, mTintingColor);
         }
 
         Drawable scrimBg = mActivity.getScrimView().getBackground();
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index cd1691b..06a5793 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -45,7 +45,10 @@
     companion object {
         const val TAG = "TaskMenuViewWithArrow"
 
-        fun showForTask(taskContainer: TaskIdAttributeContainer): Boolean {
+        fun showForTask(
+            taskContainer: TaskIdAttributeContainer,
+            alignSecondRow: Boolean = false
+        ): Boolean {
             val activity = BaseDraggingActivity
                 .fromContext<BaseDraggingActivity>(taskContainer.taskView.context)
             val taskMenuViewWithArrow = activity.layoutInflater
@@ -55,7 +58,7 @@
                     false
                 ) as TaskMenuViewWithArrow<*>
 
-            return taskMenuViewWithArrow.populateAndShowForTask(taskContainer)
+            return taskMenuViewWithArrow.populateAndShowForTask(taskContainer, alignSecondRow)
         }
     }
 
@@ -78,6 +81,9 @@
         CLOSE_FADE_DURATION = CLOSE_CHILD_FADE_DURATION
     }
 
+    private var alignSecondRow: Boolean = false
+    private val extraSpaceForSecondRowAlignment: Int
+        get() = if (alignSecondRow) optionMeasuredHeight else 0
     private val menuWidth = context.resources.getDimensionPixelSize(R.dimen.task_menu_width_grid)
 
     private lateinit var taskView: TaskView
@@ -91,6 +97,10 @@
         else
             0
 
+    private var iconView: IconView? = null
+    private var scrim: View? = null
+    private val scrimAlpha = 0.8f
+
     override fun isOfType(type: Int): Boolean = type and TYPE_TASK_MENU != 0
 
     override fun getTargetObjectLocation(outPos: Rect?) {
@@ -112,18 +122,35 @@
         optionLayout = findViewById(KtR.id.menu_option_layout)
     }
 
-    private fun populateAndShowForTask(taskContainer: TaskIdAttributeContainer): Boolean {
+    private fun populateAndShowForTask(
+        taskContainer: TaskIdAttributeContainer,
+        alignSecondRow: Boolean
+    ): Boolean {
         if (isAttachedToWindow) {
             return false
         }
 
         taskView = taskContainer.taskView
         this.taskContainer = taskContainer
+        this.alignSecondRow = alignSecondRow
         if (!populateMenu()) return false
+        addScrim()
         show()
         return true
     }
 
+    private fun addScrim() {
+        scrim = View(context).apply {
+            layoutParams = FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.MATCH_PARENT
+            )
+            setBackgroundColor(Themes.getAttrColor(context, R.attr.overviewScrimColor))
+            alpha = 0f
+        }
+        popupContainer.addView(scrim)
+    }
+
     /** @return true if successfully able to populate task view menu, false otherwise
      */
     private fun populateMenu(): Boolean {
@@ -180,18 +207,50 @@
     }
 
     override fun onCreateOpenAnimation(anim: AnimatorSet) {
-        anim.play(
-            ObjectAnimator.ofFloat(
-                taskContainer.thumbnailView, TaskThumbnailView.DIM_ALPHA,
-                TaskView.MAX_PAGE_SCRIM_ALPHA
+        scrim?.let {
+            anim.play(
+                ObjectAnimator.ofFloat(it, View.ALPHA, 0f, scrimAlpha)
+                    .setDuration(OPEN_DURATION.toLong())
             )
-        )
+        }
     }
 
     override fun onCreateCloseAnimation(anim: AnimatorSet) {
-        anim.play(
-            ObjectAnimator.ofFloat(taskContainer.thumbnailView, TaskThumbnailView.DIM_ALPHA, 0f)
-        )
+        scrim?.let {
+            anim.play(
+                ObjectAnimator.ofFloat(it, View.ALPHA, scrimAlpha, 0f)
+                    .setDuration(CLOSE_DURATION.toLong())
+            )
+        }
+    }
+
+    override fun closeComplete() {
+        super.closeComplete()
+        popupContainer.removeView(scrim)
+        popupContainer.removeView(iconView)
+    }
+
+    /**
+     * Copy the iconView from taskView to dragLayer so it can stay on top of the scrim.
+     * It needs to be called after [getTargetObjectLocation] because [mTempRect] needs to be
+     * populated.
+     */
+    private fun copyIconToDragLayer(insets: Rect) {
+        iconView = IconView(context).apply {
+            layoutParams = FrameLayout.LayoutParams(
+                taskContainer.iconView.width,
+                taskContainer.iconView.height
+            )
+            x = mTempRect.left.toFloat() - insets.left
+            y = mTempRect.top.toFloat() - insets.top
+            drawable = taskContainer.iconView.drawable
+            setDrawableSize(
+                taskContainer.iconView.drawableWidth,
+                taskContainer.iconView.drawableHeight
+            )
+        }
+
+        popupContainer.addView(iconView)
     }
 
     /**
@@ -217,7 +276,10 @@
         val dragLayer: InsettableFrameLayout = popupContainer
         val insets = dragLayer.insets
 
-        // Put to the right of the icon if there is space, which means left aligned with the menu
+        copyIconToDragLayer(insets)
+
+        // Put this menu to the right of the icon if there is space,
+        // which means the arrow is left aligned with the menu
         val rightAlignedMenuStartX = mTempRect.left - widthWithArrow
         val leftAlignedMenuStartX = mTempRect.right + extraHorizontalSpace
         mIsLeftAligned = if (mIsRtl) {
@@ -229,18 +291,17 @@
 
         var menuStartX = if (mIsLeftAligned) leftAlignedMenuStartX else rightAlignedMenuStartX
 
-        // Offset y so that the arrow and first row are center-aligned with the original icon.
+        // Offset y so that the arrow and row are center-aligned with the original icon.
         val iconHeight = mTempRect.height()
-        val optionHeight = optionMeasuredHeight
-        val yOffset = (optionHeight - iconHeight) / 2
-        var menuStartY = mTempRect.top - yOffset
+        val yOffset = (optionMeasuredHeight - iconHeight) / 2
+        var menuStartY = mTempRect.top - yOffset - extraSpaceForSecondRowAlignment
 
         // Insets are added later, so subtract them now.
         menuStartX -= insets.left
         menuStartY -= insets.top
 
-        setX(menuStartX.toFloat())
-        setY(menuStartY.toFloat())
+        x = menuStartX.toFloat()
+        y = menuStartY.toFloat()
 
         val lp = layoutParams as FrameLayout.LayoutParams
         val arrowLp = mArrow.layoutParams as FrameLayout.LayoutParams
@@ -251,7 +312,8 @@
     override fun addArrow() {
         popupContainer.addView(mArrow)
         mArrow.x = getArrowX()
-        mArrow.y = y + (optionMeasuredHeight / 2) - (mArrowHeight / 2)
+        mArrow.y = y + (optionMeasuredHeight / 2) - (mArrowHeight / 2) +
+                extraSpaceForSecondRowAlignment
 
         updateArrowColor()
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 1ff2a88..f7f5efc 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -30,6 +30,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -38,6 +39,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.annotation.IdRes;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
@@ -597,7 +599,9 @@
         if (confirmSecondSplitSelectApp()) {
             return;
         }
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
+        RecentsView recentsView = getRecentsView();
+        RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask() && remoteTargetHandles != null) {
             if (!mIsClickableAsLiveTile) {
                 return;
             }
@@ -610,9 +614,7 @@
             }
 
             mIsClickableAsLiveTile = false;
-            RecentsView recentsView = getRecentsView();
             RemoteAnimationTargets targets;
-            RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
             if (remoteTargetHandles.length == 1) {
                 targets = remoteTargetHandles[0].getTransformParams().getTargetSet();
             } else {
@@ -840,7 +842,9 @@
         TaskIdAttributeContainer menuContainer =
                 mTaskIdAttributeContainer[iconView == mIconView ? 0 : 1];
         if (mActivity.getDeviceProfile().overviewShowAsGrid) {
-            return TaskMenuViewWithArrow.Companion.showForTask(menuContainer);
+            boolean alignSecondRow = getRecentsView().isOnGridBottomRow(menuContainer.getTaskView())
+                    && mActivity.getDeviceProfile().isLandscape;
+            return TaskMenuViewWithArrow.Companion.showForTask(menuContainer, alignSecondRow);
         } else {
             return TaskMenuView.showForTask(menuContainer);
         }
@@ -1302,10 +1306,14 @@
                         getContext().getText(R.string.accessibility_close)));
 
         final Context context = getContext();
-        // TODO(b/200609838) Determine which task to run A11y action on when in split screen
-        for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
-                mActivity.getDeviceProfile(), mTaskIdAttributeContainer[0])) {
-            info.addAction(s.createAccessibilityAction(context));
+        for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) {
+            if (taskContainer == null) {
+                continue;
+            }
+            for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+                    mActivity.getDeviceProfile(), taskContainer)) {
+                info.addAction(s.createAccessibilityAction(context));
+            }
         }
 
         if (mDigitalWellBeingToast.hasLimit()) {
@@ -1336,12 +1344,16 @@
             return true;
         }
 
-        // TODO(b/200609838) Determine which task to run A11y action on when in split screen
-        for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
-                mActivity.getDeviceProfile(), mTaskIdAttributeContainer[0])) {
-            if (s.hasHandlerForAction(action)) {
-                s.onClick(this);
-                return true;
+        for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) {
+            if (taskContainer == null) {
+                continue;
+            }
+            for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+                    mActivity.getDeviceProfile(), taskContainer)) {
+                if (s.hasHandlerForAction(action)) {
+                    s.onClick(this);
+                    return true;
+                }
             }
         }
 
@@ -1558,7 +1570,6 @@
                 mScale = previewWidth / (previewWidth + currentInsetsLeft + currentInsetsRight);
             }
         }
-
     }
 
     public class TaskIdAttributeContainer {
@@ -1567,6 +1578,8 @@
         private final IconView mIconView;
         /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */
         private @SplitConfigurationOptions.StagePosition int mStagePosition;
+        @IdRes
+        private final int mA11yNodeId;
 
         public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView,
                 IconView iconView, int stagePosition) {
@@ -1574,6 +1587,8 @@
             this.mThumbnailView = thumbnailView;
             this.mIconView = iconView;
             this.mStagePosition = stagePosition;
+            this.mA11yNodeId = (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) ?
+                    R.id.split_bottomRight_appInfo : R.id.split_topLeft_appInfo;
         }
 
         public TaskThumbnailView getThumbnailView() {
@@ -1603,5 +1618,9 @@
         void setStagePosition(@SplitConfigurationOptions.StagePosition int stagePosition) {
             this.mStagePosition = stagePosition;
         }
+
+        public int getA11yNodeId() {
+            return mA11yNodeId;
+        }
     }
 }
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index ea6ddbe..878ef3f 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -329,6 +329,9 @@
     <!-- Size of the maximum radius for the enforced rounded rectangles. -->
     <dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
 
+<!-- Base Swipe Detector, speed in dp/s -->
+    <dimen name="base_swift_detector_fling_release_velocity">1dp</dimen>
+
 <!-- Overview placeholder to compile in Launcher3 without Quickstep -->
     <dimen name="task_thumbnail_icon_size">0dp</dimen>
     <dimen name="task_thumbnail_icon_drawable_size">0dp</dimen>
diff --git a/res/values/id.xml b/res/values/id.xml
index ebc4075..508caff 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -21,6 +21,10 @@
     <item type="id" name="view_type_widgets_list" />
     <item type="id" name="view_type_widgets_header" />
     <item type="id" name="view_type_widgets_search_header" />
+    <!--  Used for A11y actions in staged split to identify each task uniquely  -->
+    <item type="id" name="split_topLeft_appInfo" />
+    <item type="id" name="split_bottomRight_appInfo" />
+
 
     <!--  Do not change, must be kept in sync with sysui navbar button IDs for tests!  -->
     <item type="id" name="home" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5f53d4e..868b5f3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -40,9 +40,10 @@
     <!-- Options for recent tasks -->
     <!-- Title for an option to enter split screen mode for a given app -->
     <string name="recent_task_option_split_screen">Split screen</string>
-    <string translatable="false" name="split_screen_position_top">Split top</string>
-    <string translatable="false" name="split_screen_position_left">Split left</string>
-    <string translatable="false" name="split_screen_position_right">Split right</string>
+    <string name="split_screen_position_top">Split top</string>
+    <string name="split_screen_position_left">Split left</string>
+    <string name="split_screen_position_right">Split right</string>
+    <string name="split_app_info_accessibility">App info for %1$s</string>
 
     <!-- Widgets -->
     <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 5a6365a..f016965 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -48,7 +48,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.FolderIcon;
@@ -296,7 +296,7 @@
 
     @Override
     public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
-        if (delegate instanceof LauncherAccessibilityDelegate) {
+        if (delegate instanceof BaseAccessibilityDelegate) {
             super.setAccessibilityDelegate(delegate);
         } else {
             // NO-OP
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 5e67bf0..9fad2eb 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -814,15 +814,23 @@
         Point padding = getTotalWorkspacePadding();
 
         int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
-        int cellLayoutTotalPadding =
-                isTwoPanels ? 4 * cellLayoutPaddingLeftRightPx : 2 * cellLayoutPaddingLeftRightPx;
-        int screenWidthPx = availableWidthPx - padding.x - cellLayoutTotalPadding;
+        int screenWidthPx = getWorkspaceWidth(padding);
         result.x = calculateCellWidth(screenWidthPx, cellLayoutBorderSpacePx.x, numColumns);
         result.y = calculateCellHeight(availableHeightPx - padding.y
                 - cellLayoutBottomPaddingPx, cellLayoutBorderSpacePx.y, inv.numRows);
         return result;
     }
 
+    public int getWorkspaceWidth() {
+        return getWorkspaceWidth(getTotalWorkspacePadding());
+    }
+
+    public int getWorkspaceWidth(Point workspacePadding) {
+        int cellLayoutTotalPadding =
+                isTwoPanels ? 4 * cellLayoutPaddingLeftRightPx : 2 * cellLayoutPaddingLeftRightPx;
+        return availableWidthPx - workspacePadding.x - cellLayoutTotalPadding;
+    }
+
     public Point getTotalWorkspacePadding() {
         updateWorkspacePadding();
         return new Point(workspacePadding.left + workspacePadding.right,
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index d844b87..2ebedf7 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -286,7 +286,11 @@
         mExtraAttrs = closestProfile.extraAttrs;
 
         iconSize = displayOption.iconSizes;
-        iconBitmapSize = ResourceUtils.pxFromDp(iconSize[INDEX_DEFAULT], metrics);
+        float maxIconSize = iconSize[0];
+        for (int i = 1; i < iconSize.length; i++) {
+            maxIconSize = Math.max(maxIconSize, iconSize[i]);
+        }
+        iconBitmapSize = ResourceUtils.pxFromDp(maxIconSize, metrics);
         fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
 
         iconTextSize = displayOption.textSizes;
@@ -555,13 +559,10 @@
         }
         out.multiply(1.0f / weights);
 
-        // Since the bitmaps are persisted, ensure that the default bitmap size is same as
+        // Since the bitmaps are persisted, ensure that all bitmap sizes are not larger than
         // predefined size to avoid cache invalidation
-        out.iconSizes[INDEX_DEFAULT] =
-                closestPoint.iconSizes[INDEX_DEFAULT];
-        for (int i = INDEX_DEFAULT + 1; i < COUNT_SIZES; i++) {
-            out.iconSizes[i] = Math.min(out.iconSizes[i],
-                    out.iconSizes[INDEX_DEFAULT]);
+        for (int i = INDEX_DEFAULT; i < COUNT_SIZES; i++) {
+            out.iconSizes[i] = Math.min(out.iconSizes[i], closestPoint.iconSizes[i]);
         }
 
         return out;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0656125..ab9b980 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -115,8 +115,8 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.accessibility.BaseAccessibilityDelegate.LauncherAction;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.LauncherAction;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AllAppsTransitionController;
diff --git a/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
new file mode 100644
index 0000000..14b2431
--- /dev/null
+++ b/src/com/android/launcher3/accessibility/BaseAccessibilityDelegate.java
@@ -0,0 +1,192 @@
+/*
+ * 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.launcher3.accessibility;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.ActivityContext;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class BaseAccessibilityDelegate<T extends Context & ActivityContext>
+        extends View.AccessibilityDelegate implements DragController.DragListener {
+
+    public enum DragType {
+        ICON,
+        FOLDER,
+        WIDGET
+    }
+
+    public static class DragInfo {
+        public DragType dragType;
+        public ItemInfo info;
+        public View item;
+    }
+
+    protected final SparseArray<LauncherAction> mActions = new SparseArray<>();
+    protected final T mContext;
+
+    protected DragInfo mDragInfo = null;
+
+    protected BaseAccessibilityDelegate(T context) {
+        mContext = context;
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(host, info);
+        if (host.getTag() instanceof ItemInfo) {
+            ItemInfo item = (ItemInfo) host.getTag();
+
+            List<LauncherAction> actions = new ArrayList<>();
+            getSupportedActions(host, item, actions);
+            actions.forEach(la -> info.addAction(la.accessibilityAction));
+
+            if (!itemSupportsLongClick(host, item)) {
+                info.setLongClickable(false);
+                info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
+            }
+        }
+    }
+
+    /**
+     * Adds all the accessibility actions that can be handled.
+     */
+    protected abstract void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out);
+
+    private boolean itemSupportsLongClick(View host, ItemInfo info) {
+        return PopupContainerWithArrow.canShow(host, info);
+    }
+
+    protected boolean itemSupportsAccessibleDrag(ItemInfo item) {
+        if (item instanceof WorkspaceItemInfo) {
+            // Support the action unless the item is in a context menu.
+            return item.screenId >= 0
+                    && item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+        }
+        return (item instanceof LauncherAppWidgetInfo)
+                || (item instanceof FolderInfo);
+    }
+
+    @Override
+    public boolean performAccessibilityAction(View host, int action, Bundle args) {
+        if ((host.getTag() instanceof ItemInfo)
+                && performAction(host, (ItemInfo) host.getTag(), action, false)) {
+            return true;
+        }
+        return super.performAccessibilityAction(host, action, args);
+    }
+
+    protected abstract boolean performAction(
+            View host, ItemInfo item, int action, boolean fromKeyboard);
+
+    @Thunk
+    protected void announceConfirmation(String confirmation) {
+        mContext.getDragLayer().announceForAccessibility(confirmation);
+
+    }
+
+    public boolean isInAccessibleDrag() {
+        return mDragInfo != null;
+    }
+
+    public DragInfo getDragInfo() {
+        return mDragInfo;
+    }
+
+    /**
+     * @param clickedTarget the actual view that was clicked
+     * @param dropLocation relative to {@param clickedTarget}. If provided, its center is used
+     * as the actual drop location otherwise the views center is used.
+     */
+    public void handleAccessibleDrop(View clickedTarget, Rect dropLocation,
+            String confirmation) {
+        if (!isInAccessibleDrag()) return;
+
+        int[] loc = new int[2];
+        if (dropLocation == null) {
+            loc[0] = clickedTarget.getWidth() / 2;
+            loc[1] = clickedTarget.getHeight() / 2;
+        } else {
+            loc[0] = dropLocation.centerX();
+            loc[1] = dropLocation.centerY();
+        }
+
+        mContext.getDragLayer().getDescendantCoordRelativeToSelf(clickedTarget, loc);
+        mContext.getDragController().completeAccessibleDrag(loc);
+
+        if (!TextUtils.isEmpty(confirmation)) {
+            announceConfirmation(confirmation);
+        }
+    }
+
+    protected abstract boolean beginAccessibleDrag(View item, ItemInfo info, boolean fromKeyboard);
+
+
+    @Override
+    public void onDragEnd() {
+        mContext.getDragController().removeDragListener(this);
+        mDragInfo = null;
+    }
+
+    @Override
+    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+        // No-op
+    }
+
+    public class LauncherAction {
+        public final int keyCode;
+        public final AccessibilityNodeInfo.AccessibilityAction accessibilityAction;
+
+        private final BaseAccessibilityDelegate<T> mDelegate;
+
+        public LauncherAction(int id, int labelRes, int keyCode) {
+            this.keyCode = keyCode;
+            accessibilityAction = new AccessibilityNodeInfo.AccessibilityAction(
+                    id, mContext.getString(labelRes));
+            mDelegate = BaseAccessibilityDelegate.this;
+        }
+
+        /**
+         * Invokes the action for the provided host
+         */
+        public boolean invokeFromKeyboard(View host) {
+            if (host != null && host.getTag() instanceof ItemInfo) {
+                return mDelegate.performAction(
+                        host, (ItemInfo) host.getTag(), accessibilityAction.getId(), true);
+            } else {
+                return false;
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 157df5d..18c05eb 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -10,27 +10,19 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.os.Bundle;
 import android.os.Handler;
-import android.text.TextUtils;
 import android.util.Log;
-import android.util.SparseArray;
 import android.view.KeyEvent;
 import android.view.View;
-import android.view.View.AccessibilityDelegate;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.ButtonDropTarget;
 import com.android.launcher3.CellLayout;
-import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.dragndrop.DragController.DragListener;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.folder.Folder;
@@ -57,7 +49,7 @@
 import java.util.Collections;
 import java.util.List;
 
-public class LauncherAccessibilityDelegate extends AccessibilityDelegate implements DragListener {
+public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate<Launcher> {
 
     private static final String TAG = "LauncherAccessibilityDelegate";
 
@@ -73,25 +65,8 @@
     public static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts;
     public static final int SHORTCUTS_AND_NOTIFICATIONS = R.id.action_shortcuts_and_notifications;
 
-    public enum DragType {
-        ICON,
-        FOLDER,
-        WIDGET
-    }
-
-    public static class DragInfo {
-        public DragType dragType;
-        public ItemInfo info;
-        public View item;
-    }
-
-    protected final SparseArray<LauncherAction> mActions = new SparseArray<>();
-    protected final Launcher mLauncher;
-
-    private DragInfo mDragInfo = null;
-
     public LauncherAccessibilityDelegate(Launcher launcher) {
-        mLauncher = launcher;
+        super(launcher);
 
         mActions.put(REMOVE, new LauncherAction(
                 REMOVE, R.string.remove_drop_target_label, KeyEvent.KEYCODE_X));
@@ -116,25 +91,6 @@
     }
 
     @Override
-    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfo(host, info);
-        if (host.getTag() instanceof ItemInfo) {
-            ItemInfo item = (ItemInfo) host.getTag();
-
-            List<LauncherAction> actions = new ArrayList<>();
-            getSupportedActions(host, item, actions);
-            actions.forEach(la -> info.addAction(la.accessibilityAction));
-
-            if (!itemSupportsLongClick(host, item)) {
-                info.setLongClickable(false);
-                info.removeAction(AccessibilityAction.ACTION_LONG_CLICK);
-            }
-        }
-    }
-
-    /**
-     * Adds all the accessibility actions that can be handled.
-     */
     protected void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out) {
         // If the request came from keyboard, do not add custom shortcuts as that is already
         // exposed as a direct shortcut
@@ -143,7 +99,7 @@
                     ? SHORTCUTS_AND_NOTIFICATIONS : DEEP_SHORTCUTS));
         }
 
-        for (ButtonDropTarget target : mLauncher.getDropTargetBar().getDropTargets()) {
+        for (ButtonDropTarget target : mContext.getDropTargetBar().getDropTargets()) {
             if (target.supportsAccessibilityDrop(item, host)) {
                 out.add(mActions.get(target.getAccessibilityAction()));
             }
@@ -183,31 +139,7 @@
         return result;
     }
 
-    private boolean itemSupportsLongClick(View host, ItemInfo info) {
-        return PopupContainerWithArrow.canShow(host, info);
-    }
-
-    private boolean itemSupportsAccessibleDrag(ItemInfo item) {
-        if (item instanceof WorkspaceItemInfo) {
-            // Support the action unless the item is in a context menu.
-            return item.screenId >= 0 && item.container != Favorites.CONTAINER_HOTSEAT_PREDICTION;
-        }
-        return (item instanceof LauncherAppWidgetInfo)
-                || (item instanceof FolderInfo);
-    }
-
     @Override
-    public boolean performAccessibilityAction(View host, int action, Bundle args) {
-        if ((host.getTag() instanceof ItemInfo)
-                && performAction(host, (ItemInfo) host.getTag(), action, false)) {
-            return true;
-        }
-        return super.performAccessibilityAction(host, action, args);
-    }
-
-    /**
-     * Performs the provided action on the host
-     */
     protected boolean performAction(final View host, final ItemInfo item, int action,
             boolean fromKeyboard) {
         if (action == ACTION_LONG_CLICK) {
@@ -226,36 +158,36 @@
             if (screenId == -1) {
                 return false;
             }
-            mLauncher.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
+            mContext.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
                 if (item instanceof AppInfo) {
                     WorkspaceItemInfo info = ((AppInfo) item).makeWorkspaceItem();
-                    mLauncher.getModelWriter().addItemToDatabase(info,
+                    mContext.getModelWriter().addItemToDatabase(info,
                             Favorites.CONTAINER_DESKTOP,
                             screenId, coordinates[0], coordinates[1]);
 
-                    mLauncher.bindItems(
+                    mContext.bindItems(
                             Collections.singletonList(info),
                             /* forceAnimateIcons= */ true,
                             /* focusFirstItemForAccessibility= */ true);
                     announceConfirmation(R.string.item_added_to_workspace);
                 } else if (item instanceof PendingAddItemInfo) {
                     PendingAddItemInfo info = (PendingAddItemInfo) item;
-                    Workspace workspace = mLauncher.getWorkspace();
+                    Workspace workspace = mContext.getWorkspace();
                     workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
-                    mLauncher.addPendingItem(info, Favorites.CONTAINER_DESKTOP,
+                    mContext.addPendingItem(info, Favorites.CONTAINER_DESKTOP,
                             screenId, coordinates, info.spanX, info.spanY);
                 }
                 else if (item instanceof WorkspaceItemInfo) {
                     WorkspaceItemInfo info = ((WorkspaceItemInfo) item).clone();
-                    mLauncher.getModelWriter().addItemToDatabase(info,
+                    mContext.getModelWriter().addItemToDatabase(info,
                             Favorites.CONTAINER_DESKTOP,
                             screenId, coordinates[0], coordinates[1]);
-                    mLauncher.bindItems(Collections.singletonList(info), true, true);
+                    mContext.bindItems(Collections.singletonList(info), true, true);
                 }
             }));
             return true;
         } else if (action == MOVE_TO_WORKSPACE) {
-            Folder folder = Folder.getOpen(mLauncher);
+            Folder folder = Folder.getOpen(mContext);
             folder.close(true);
             WorkspaceItemInfo info = (WorkspaceItemInfo) item;
             folder.getInfo().remove(info, false);
@@ -265,14 +197,14 @@
             if (screenId == -1) {
                 return false;
             }
-            mLauncher.getModelWriter().moveItemInDatabase(info,
+            mContext.getModelWriter().moveItemInDatabase(info,
                     Favorites.CONTAINER_DESKTOP,
                     screenId, coordinates[0], coordinates[1]);
 
             // Bind the item in next frame so that if a new workspace page was created,
             // it will get laid out.
             new Handler().post(() -> {
-                mLauncher.bindItems(Collections.singletonList(item), true);
+                mContext.bindItems(Collections.singletonList(item), true);
                 announceConfirmation(R.string.item_moved);
             });
             return true;
@@ -280,15 +212,15 @@
             final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item;
             List<OptionItem> actions = getSupportedResizeActions(host, info);
             Rect pos = new Rect();
-            mLauncher.getDragLayer().getDescendantRectRelativeToSelf(host, pos);
-            ArrowPopup popup = OptionsPopupView.show(mLauncher, new RectF(pos), actions, false);
+            mContext.getDragLayer().getDescendantRectRelativeToSelf(host, pos);
+            ArrowPopup popup = OptionsPopupView.show(mContext, new RectF(pos), actions, false);
             popup.requestFocus();
             popup.setOnCloseCallback(host::requestFocus);
             return true;
         } else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) {
             return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null;
         } else {
-            for (ButtonDropTarget dropTarget : mLauncher.getDropTargetBar().getDropTargets()) {
+            for (ButtonDropTarget dropTarget : mContext.getDropTargetBar().getDropTargets()) {
                 if (dropTarget.supportsAccessibilityDrop(item, host)
                         && action == dropTarget.getAccessibilityAction()) {
                     dropTarget.onAccessibilityDrop(host, item);
@@ -315,7 +247,7 @@
         if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0) {
             if (layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY) ||
                     layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY)) {
-                actions.add(new OptionItem(mLauncher,
+                actions.add(new OptionItem(mContext,
                         R.string.action_increase_width,
                         R.drawable.ic_widget_width_increase,
                         IGNORE,
@@ -323,7 +255,7 @@
             }
 
             if (info.spanX > info.minSpanX && info.spanX > 1) {
-                actions.add(new OptionItem(mLauncher,
+                actions.add(new OptionItem(mContext,
                         R.string.action_decrease_width,
                         R.drawable.ic_widget_width_decrease,
                         IGNORE,
@@ -334,7 +266,7 @@
         if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) {
             if (layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1) ||
                     layout.isRegionVacant(info.cellX, info.cellY - 1, info.spanX, 1)) {
-                actions.add(new OptionItem(mLauncher,
+                actions.add(new OptionItem(mContext,
                         R.string.action_increase_height,
                         R.drawable.ic_widget_height_increase,
                         IGNORE,
@@ -342,7 +274,7 @@
             }
 
             if (info.spanY > info.minSpanY && info.spanY > 1) {
-                actions.add(new OptionItem(mLauncher,
+                actions.add(new OptionItem(mContext,
                         R.string.action_decrease_height,
                         R.drawable.ic_widget_height_decrease,
                         IGNORE,
@@ -382,58 +314,20 @@
         }
 
         layout.markCellsAsOccupiedForView(host);
-        WidgetSizes.updateWidgetSizeRanges(((LauncherAppWidgetHostView) host), mLauncher,
+        WidgetSizes.updateWidgetSizeRanges(((LauncherAppWidgetHostView) host), mContext,
                 info.spanX, info.spanY);
         host.requestLayout();
-        mLauncher.getModelWriter().updateItemInDatabase(info);
-        announceConfirmation(mLauncher.getString(R.string.widget_resized, info.spanX, info.spanY));
+        mContext.getModelWriter().updateItemInDatabase(info);
+        announceConfirmation(mContext.getString(R.string.widget_resized, info.spanX, info.spanY));
         return true;
     }
 
     @Thunk void announceConfirmation(int resId) {
-        announceConfirmation(mLauncher.getResources().getString(resId));
+        announceConfirmation(mContext.getResources().getString(resId));
     }
 
-    @Thunk void announceConfirmation(String confirmation) {
-        mLauncher.getDragLayer().announceForAccessibility(confirmation);
-
-    }
-
-    public boolean isInAccessibleDrag() {
-        return mDragInfo != null;
-    }
-
-    public DragInfo getDragInfo() {
-        return mDragInfo;
-    }
-
-    /**
-     * @param clickedTarget the actual view that was clicked
-     * @param dropLocation relative to {@param clickedTarget}. If provided, its center is used
-     * as the actual drop location otherwise the views center is used.
-     */
-    public void handleAccessibleDrop(View clickedTarget, Rect dropLocation,
-            String confirmation) {
-        if (!isInAccessibleDrag()) return;
-
-        int[] loc = new int[2];
-        if (dropLocation == null) {
-            loc[0] = clickedTarget.getWidth() / 2;
-            loc[1] = clickedTarget.getHeight() / 2;
-        } else {
-            loc[0] = dropLocation.centerX();
-            loc[1] = dropLocation.centerY();
-        }
-
-        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(clickedTarget, loc);
-        mLauncher.getDragController().completeAccessibleDrag(loc);
-
-        if (!TextUtils.isEmpty(confirmation)) {
-            announceConfirmation(confirmation);
-        }
-    }
-
-    private boolean beginAccessibleDrag(View item, ItemInfo info, boolean fromKeyboard) {
+    @Override
+    protected boolean beginAccessibleDrag(View item, ItemInfo info, boolean fromKeyboard) {
         if (!itemSupportsAccessibleDrag(info)) {
             return false;
         }
@@ -449,8 +343,8 @@
         }
 
         Rect pos = new Rect();
-        mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
-        mLauncher.getDragController().addDragListener(this);
+        mContext.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
+        mContext.getDragController().addDragListener(this);
 
         DragOptions options = new DragOptions();
         options.isAccessibleDrag = true;
@@ -458,31 +352,20 @@
         options.simulatedDndStartPoint = new Point(pos.centerX(), pos.centerY());
 
         if (fromKeyboard) {
-            KeyboardDragAndDropView popup = (KeyboardDragAndDropView) mLauncher.getLayoutInflater()
-                    .inflate(R.layout.keyboard_drag_and_drop, mLauncher.getDragLayer(), false);
+            KeyboardDragAndDropView popup = (KeyboardDragAndDropView) mContext.getLayoutInflater()
+                    .inflate(R.layout.keyboard_drag_and_drop, mContext.getDragLayer(), false);
             popup.showForIcon(item, info, options);
         } else {
-            ItemLongClickListener.beginDrag(item, mLauncher, info, options);
+            ItemLongClickListener.beginDrag(item, mContext, info, options);
         }
         return true;
     }
 
-    @Override
-    public void onDragStart(DragObject dragObject, DragOptions options) {
-        // No-op
-    }
-
-    @Override
-    public void onDragEnd() {
-        mLauncher.getDragController().removeDragListener(this);
-        mDragInfo = null;
-    }
-
     /**
      * Find empty space on the workspace and returns the screenId.
      */
     protected int findSpaceOnWorkspace(ItemInfo info, int[] outCoordinates) {
-        Workspace workspace = mLauncher.getWorkspace();
+        Workspace workspace = mContext.getWorkspace();
         IntArray workspaceScreens = workspace.getScreenOrder();
         int screenId;
 
@@ -520,29 +403,4 @@
         }
         return screenId;
     }
-
-    public class LauncherAction {
-        public final int keyCode;
-        public final AccessibilityAction accessibilityAction;
-
-        private final LauncherAccessibilityDelegate mDelegate;
-
-        public LauncherAction(int id, int labelRes, int keyCode) {
-            this.keyCode = keyCode;
-            accessibilityAction = new AccessibilityAction(id, mLauncher.getString(labelRes));
-            mDelegate = LauncherAccessibilityDelegate.this;
-        }
-
-        /**
-         * Invokes the action for the provided host
-         */
-        public boolean invokeFromKeyboard(View host) {
-            if (host != null && host.getTag() instanceof ItemInfo) {
-                return mDelegate.performAction(
-                        host, (ItemInfo) host.getTag(), accessibilityAction.getId(), true);
-            } else {
-                return false;
-            }
-        }
-    }
 }
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index bf5a24b..fb847ec 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -71,12 +71,12 @@
             if (screenId == -1) {
                 return false;
             }
-            mLauncher.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
-                mLauncher.getModelWriter().addItemToDatabase(info,
+            mContext.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
+                mContext.getModelWriter().addItemToDatabase(info,
                         LauncherSettings.Favorites.CONTAINER_DESKTOP,
                         screenId, coordinates[0], coordinates[1]);
-                mLauncher.bindItems(Collections.singletonList(info), true);
-                AbstractFloatingView.closeAllOpenViews(mLauncher);
+                mContext.bindItems(Collections.singletonList(info), true);
+                AbstractFloatingView.closeAllOpenViews(mContext);
                 announceConfirmation(R.string.item_added_to_workspace);
             }));
             return true;
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index a331924..a8624dd 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -22,7 +22,7 @@
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.R;
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DragType;
+import com.android.launcher3.accessibility.BaseAccessibilityDelegate.DragType;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
diff --git a/src/com/android/launcher3/anim/RevealOutlineAnimation.java b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
index f99dabc..f5a746f 100644
--- a/src/com/android/launcher3/anim/RevealOutlineAnimation.java
+++ b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
@@ -26,9 +26,27 @@
     /** Sets the progress, from 0 to 1, of the reveal animation. */
     abstract void setProgress(float progress);
 
+    /**
+     * @see #createRevealAnimator(View, boolean, float) where startProgress is set to 0.
+     */
     public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) {
-        ValueAnimator va =
-                isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
+        return createRevealAnimator(revealView, isReversed, 0f /* startProgress */);
+    }
+
+    /**
+     * Animates the given View's ViewOutline according to {@link #setProgress(float)}.
+     * @param revealView The View whose outline we are animating.
+     * @param isReversed Whether we are hiding rather than revealing the View.
+     * @param startProgress The progress at which to start the newly created animation. Useful if
+     * the previous reveal animation was cancelled and we want to create a new animation where it
+     * left off. Note that if isReversed=true, we start at 1 - startProgress (and go to 0).
+     * @return The Animator, which the caller must start.
+     */
+    public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed,
+            float startProgress) {
+        ValueAnimator va = isReversed
+                ? ValueAnimator.ofFloat(1f - startProgress, 0f)
+                : ValueAnimator.ofFloat(startProgress, 1f);
         final float elevation = revealView.getElevation();
 
         va.addListener(new AnimatorListenerAdapter() {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 98cb5ae..1fa6360 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -95,6 +95,9 @@
     public static final BooleanFlag ENABLE_ONE_SEARCH = new DeviceFlag("ENABLE_ONE_SEARCH", false,
             "Use homescreen search box to complete allApps searches");
 
+    public static final BooleanFlag COLLECT_SEARCH_HISTORY = new DeviceFlag(
+            "COLLECT_SEARCH_HISTORY", false, "Allow launcher to collect search history for log");
+
     public static final BooleanFlag ENABLE_TWOLINE_ALLAPPS = getDebugFlag(
             "ENABLE_TWOLINE_ALLAPPS", false, "Enables two line label inside all apps.");
 
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 13ad90e..84612de 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -31,6 +31,8 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Workspace;
@@ -215,6 +217,18 @@
     }
 
     public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
+        addItem(context, item, newItem, null);
+    }
+
+    public synchronized void addItem(
+            Context context, ItemInfo item, boolean newItem, @Nullable LoaderMemoryLogger logger) {
+        if (logger != null) {
+            logger.addLog(
+                    Log.DEBUG,
+                    TAG,
+                    String.format("Adding item to ID map: %s", item.toString()),
+                    /* stackTrace= */ null);
+        }
         itemsIdMap.put(item.id, item);
         switch (item.itemType) {
             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 47df538..08b38e8 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -383,18 +383,23 @@
         info.cellY = getInt(cellYIndex);
     }
 
+    public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
+        checkAndAddItem(info, dataModel, null);
+    }
+
     /**
      * Adds the {@param info} to {@param dataModel} if it does not overlap with any other item,
      * otherwise marks it for deletion.
      */
-    public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
+    public void checkAndAddItem(
+            ItemInfo info, BgDataModel dataModel, LoaderMemoryLogger logger) {
         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
             // Ensure that it is a valid intent. An exception here will
             // cause the item loading to get skipped
             ShortcutKey.fromItemInfo(info);
         }
         if (checkItemPlacement(info)) {
-            dataModel.addItem(mContext, info, false);
+            dataModel.addItem(mContext, info, false, logger);
         } else {
             markDeleted("Item position overlap");
         }
diff --git a/src/com/android/launcher3/model/LoaderMemoryLogger.java b/src/com/android/launcher3/model/LoaderMemoryLogger.java
new file mode 100644
index 0000000..f48efcb
--- /dev/null
+++ b/src/com/android/launcher3/model/LoaderMemoryLogger.java
@@ -0,0 +1,91 @@
+/*
+ * 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.launcher3.model;
+
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+
+/**
+ * Helper logger that collects logs while {@code LoaderTask#run} executes and prints them all iff
+ * an exception is caught in {@code LoaderTask#run}.
+ */
+public class LoaderMemoryLogger {
+
+    private static final String TAG = "LoaderMemoryLogger";
+
+    private final ArrayList<LogEntry> mLogEntries = new ArrayList<>();
+
+    protected LoaderMemoryLogger() {}
+
+    protected void addLog(int logLevel, String tag, String log) {
+        addLog(logLevel, tag, log, null);
+    }
+
+    protected void addLog(
+            int logLevel, String tag, String log, Exception stackTrace) {
+        switch (logLevel) {
+            case Log.ASSERT:
+            case Log.ERROR:
+            case Log.DEBUG:
+            case Log.INFO:
+            case Log.VERBOSE:
+            case Log.WARN:
+                mLogEntries.add(new LogEntry(logLevel, tag, log, stackTrace));
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid log level provided: " + logLevel);
+
+        }
+    }
+
+    protected void clearLogs() {
+        mLogEntries.clear();
+    }
+
+    protected void printLogs() {
+        for (LogEntry logEntry : mLogEntries) {
+            String tag = String.format("%s: %s", TAG, logEntry.mLogTag);
+            String logString = logEntry.mStackStrace == null
+                    ? logEntry.mLogString
+                    : String.format(
+                            "%s\n%s",
+                            logEntry.mLogString,
+                            Log.getStackTraceString(logEntry.mStackStrace));
+
+            Log.println(logEntry.mLogLevel, tag, logString);
+        }
+        clearLogs();
+    }
+
+    private static class LogEntry {
+
+        protected final int mLogLevel;
+        protected final String mLogTag;
+        protected final String mLogString;
+        @Nullable protected final Exception mStackStrace;
+
+        protected LogEntry(
+                int logLevel, String logTag, String logString, @Nullable Exception stackStrace) {
+            mLogLevel = logLevel;
+            mLogTag = logTag;
+            mLogString = logString;
+            mStackStrace = stackStrace;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index a4f6f7a..2a0f9a6 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -52,6 +52,8 @@
 import android.util.LongSparseArray;
 import android.util.TimingLogger;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
@@ -197,11 +199,12 @@
 
         Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
         TimingLogger logger = new TimingLogger(TAG, "run");
+        LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
         try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
             List<ShortcutInfo> allShortcuts = new ArrayList<>();
             Trace.beginSection("LoadWorkspace");
             try {
-                loadWorkspace(allShortcuts);
+                loadWorkspace(allShortcuts, memoryLogger);
             } finally {
                 Trace.endSection();
             }
@@ -311,9 +314,13 @@
 
             mModelDelegate.modelLoadComplete();
             transaction.commit();
+            memoryLogger.clearLogs();
         } catch (CancellationException e) {
             // Loader stopped, ignore
             logASplit(logger, "Cancelled");
+        } catch (Exception e) {
+            memoryLogger.printLogs();
+            throw e;
         } finally {
             logger.dumpToLog();
         }
@@ -325,13 +332,21 @@
         this.notify();
     }
 
-    private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
+    private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, LoaderMemoryLogger logger) {
         loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI,
-                null /* selection */);
+                null /* selection */, logger);
     }
 
-    protected void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, Uri contentUri,
-            String selection) {
+    protected void loadWorkspace(
+            List<ShortcutInfo> allDeepShortcuts, Uri contentUri, String selection) {
+        loadWorkspace(allDeepShortcuts, contentUri, selection, null);
+    }
+
+    protected void loadWorkspace(
+            List<ShortcutInfo> allDeepShortcuts,
+            Uri contentUri,
+            String selection,
+            @Nullable LoaderMemoryLogger logger) {
         final Context context = mApp.getContext();
         final ContentResolver contentResolver = context.getContentResolver();
         final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
@@ -635,7 +650,7 @@
                                         }
                                 }
 
-                                c.checkAndAddItem(info, mBgDataModel);
+                                c.checkAndAddItem(info, mBgDataModel, logger);
                             } else {
                                 throw new RuntimeException("Unexpected null WorkspaceItemInfo");
                             }
@@ -654,7 +669,7 @@
                             // no special handling required for restored folders
                             c.markRestored();
 
-                            c.checkAndAddItem(folderInfo, mBgDataModel);
+                            c.checkAndAddItem(folderInfo, mBgDataModel, logger);
                             break;
 
                         case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 5ebbc67..08d3779 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -41,8 +41,8 @@
         implements View.OnClickListener {
 
     private final int mIconResId;
-    private final int mLabelResId;
-    private final int mAccessibilityActionId;
+    protected final int mLabelResId;
+    protected int mAccessibilityActionId;
 
     protected final T mTarget;
     protected final ItemInfo mItemInfo;
@@ -142,11 +142,43 @@
 
     public static class AppInfo<T extends Context & ActivityContext> extends SystemShortcut<T> {
 
+        @Nullable
+        private SplitAccessibilityInfo mSplitA11yInfo;
+
         public AppInfo(T target, ItemInfo itemInfo) {
             super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label, target,
                     itemInfo);
         }
 
+        /**
+         * Constructor used by overview for staged split to provide custom A11y information.
+         *
+         * Future improvements considerations:
+         * Have the logic in {@link #createAccessibilityAction(Context)} be moved to super
+         * call in {@link SystemShortcut#createAccessibilityAction(Context)} by having
+         * SystemShortcut be aware of TaskContainers and staged split.
+         * That way it could directly create the correct node info for any shortcut that supports
+         * split, but then we'll need custom resIDs for each pair of shortcuts.
+         */
+        public AppInfo(T target, ItemInfo itemInfo, SplitAccessibilityInfo accessibilityInfo) {
+            this(target, itemInfo);
+            mSplitA11yInfo = accessibilityInfo;
+            mAccessibilityActionId = accessibilityInfo.nodeId;
+        }
+
+        @Override
+        public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(
+                Context context) {
+            if (mSplitA11yInfo != null && mSplitA11yInfo.containsMultipleTasks) {
+                String accessibilityLabel = context.getString(R.string.split_app_info_accessibility,
+                        mSplitA11yInfo.taskTitle);
+                return new AccessibilityNodeInfo.AccessibilityAction(mAccessibilityActionId,
+                        accessibilityLabel);
+            } else {
+                return super.createAccessibilityAction(context);
+            }
+        }
+
         @Override
         public void onClick(View view) {
             dismissTaskMenuView(mTarget);
@@ -156,6 +188,19 @@
             mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
                     .log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP);
         }
+
+        public static class SplitAccessibilityInfo {
+            public final boolean containsMultipleTasks;
+            public final CharSequence taskTitle;
+            public final int nodeId;
+
+            public SplitAccessibilityInfo(boolean containsMultipleTasks,
+                    CharSequence taskTitle, int nodeId) {
+                this.containsMultipleTasks = containsMultipleTasks;
+                this.taskTitle = taskTitle;
+                this.nodeId = nodeId;
+            }
+        }
     }
 
     public static final Factory<BaseDraggingActivity> INSTALL = (activity, itemInfo) -> {
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 62b8a48..673b011 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -122,7 +122,6 @@
     public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
 
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
-    public static final String TASKBAR_WINDOW_CRASH = "b/201305599";
     public static final String TASK_VIEW_ID_CRASH = "b/195430732";
     public static final String NO_DROP_TARGET = "b/195031154";
     public static final String NULL_INT_SET = "b/200572078";
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
index 1276ece..52c3581 100644
--- a/src/com/android/launcher3/touch/BaseSwipeDetector.java
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -17,6 +17,7 @@
 
 import static android.view.MotionEvent.INVALID_POINTER_ID;
 
+import android.content.Context;
 import android.graphics.PointF;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -26,6 +27,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.R;
+
 import java.util.LinkedList;
 import java.util.Queue;
 
@@ -44,10 +47,9 @@
     private static final boolean DBG = false;
     private static final String TAG = "BaseSwipeDetector";
     private static final float ANIMATION_DURATION = 1200;
-    /** The minimum release velocity in pixels per millisecond that triggers fling.*/
-    private static final float RELEASE_VELOCITY_PX_MS = 1.0f;
     private static final PointF sTempPoint = new PointF();
 
+    private final float mReleaseVelocity;
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
     protected final boolean mIsRtl;
@@ -64,6 +66,7 @@
     private boolean mIsSettingState;
 
     protected boolean mIgnoreSlopWhenSettling;
+    protected Context mContext;
 
     private enum ScrollState {
         IDLE,
@@ -71,10 +74,14 @@
         SETTLING       // onDragEnd
     }
 
-    protected BaseSwipeDetector(@NonNull ViewConfiguration config, boolean isRtl) {
+    protected BaseSwipeDetector(@NonNull Context context, @NonNull ViewConfiguration config,
+            boolean isRtl) {
         mTouchSlop = config.getScaledTouchSlop();
         mMaxVelocity = config.getScaledMaximumFlingVelocity();
         mIsRtl = isRtl;
+        mContext = context;
+        mReleaseVelocity = mContext.getResources()
+                .getDimensionPixelSize(R.dimen.base_swift_detector_fling_release_velocity);
     }
 
     public static long calculateDuration(float velocity, float progressNeeded) {
@@ -120,7 +127,7 @@
     }
 
     public boolean isFling(float velocity) {
-        return Math.abs(velocity) > RELEASE_VELOCITY_PX_MS;
+        return Math.abs(velocity) > mReleaseVelocity;
     }
 
     public boolean onTouchEvent(MotionEvent ev) {
@@ -236,7 +243,7 @@
         } else {
             mSubtractDisplacement.x = mDisplacement.x > 0 ? mTouchSlop : -mTouchSlop;
             mSubtractDisplacement.y = mDisplacement.y > 0 ? mTouchSlop : -mTouchSlop;
-        } 
+        }
     }
 
     protected abstract boolean shouldScrollStart(PointF displacement);
diff --git a/src/com/android/launcher3/touch/BothAxesSwipeDetector.java b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
index 944391e..6e2f0d8 100644
--- a/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
+++ b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
@@ -21,7 +21,6 @@
 import android.view.ViewConfiguration;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.Utilities;
 
@@ -43,13 +42,7 @@
     private int mScrollDirections;
 
     public BothAxesSwipeDetector(@NonNull Context context, @NonNull Listener l) {
-        this(ViewConfiguration.get(context), l, Utilities.isRtl(context.getResources()));
-    }
-
-    @VisibleForTesting
-    protected BothAxesSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
-            boolean isRtl) {
-        super(config, isRtl);
+        super(context, ViewConfiguration.get(context), Utilities.isRtl(context.getResources()));
         mListener = l;
     }
 
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index e127074..c255225 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -36,6 +36,7 @@
 import android.graphics.RectF;
 import android.graphics.drawable.ShapeDrawable;
 import android.util.FloatProperty;
+import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.VelocityTracker;
@@ -462,8 +463,8 @@
     }
 
     @Override
-    public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
-            DeviceProfile deviceProfile) {
-        return primary;
+    public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
+            FloatProperty secondary, DeviceProfile deviceProfile) {
+        return new Pair<>(primary, secondary);
     }
 }
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index d954552..54b30fb 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -24,6 +24,7 @@
 import android.graphics.RectF;
 import android.graphics.drawable.ShapeDrawable;
 import android.util.FloatProperty;
+import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -113,8 +114,8 @@
     float getSecondaryValue(float x, float y);
 
     boolean isLayoutNaturalToLauncher();
-    FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
-            DeviceProfile deviceProfile);
+    Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
+            FloatProperty secondary, DeviceProfile deviceProfile);
     int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
     List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp);
     /**
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index fbc335c..e69944a 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -35,6 +35,7 @@
 import android.graphics.RectF;
 import android.graphics.drawable.ShapeDrawable;
 import android.util.FloatProperty;
+import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.VelocityTracker;
@@ -571,12 +572,12 @@
     }
 
     @Override
-    public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
-            DeviceProfile dp) {
-        if (dp.isLandscape) { // or seascape
-            return primary;
+    public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
+            FloatProperty secondary, DeviceProfile deviceProfile) {
+        if (deviceProfile.isLandscape) { // or seascape
+            return new Pair<>(primary, secondary);
         } else {
-            return secondary;
+            return new Pair<>(secondary, primary);
         }
     }
 }
diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
index f751b7d..5c599c0 100644
--- a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
+++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
@@ -106,13 +106,15 @@
 
     public SingleAxisSwipeDetector(@NonNull Context context, @NonNull Listener l,
             @NonNull Direction dir) {
-        this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
+        super(context, ViewConfiguration.get(context),  Utilities.isRtl(context.getResources()));
+        mListener = l;
+        mDir = dir;
     }
 
     @VisibleForTesting
-    protected SingleAxisSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
-            @NonNull Direction dir, boolean isRtl) {
-        super(config, isRtl);
+    protected SingleAxisSwipeDetector(@NonNull Context context, @NonNull ViewConfiguration config,
+            @NonNull Listener l, @NonNull Direction dir, boolean isRtl) {
+        super(context, config, isRtl);
         mListener = l;
         mDir = dir;
     }
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 53b1c3e..cb714b2 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -68,6 +68,11 @@
     public @interface StageType {}
     ///////////////////////////////////
 
+    /**
+     * Default split ratio for launching app pair from overview.
+     */
+    public static final float DEFAULT_SPLIT_RATIO = 0.5f;
+
     public static class SplitPositionOption {
         public final int iconResId;
         public final int textResId;
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 8b7ad46..27cf134 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -460,7 +460,7 @@
         if (mIconLoadResult != null && mIconLoadResult.isIconLoaded) {
             setVisibility(View.VISIBLE);
         }
-        if (!mIsOpening) {
+        if (!mIsOpening && mOriginalIcon != null) {
             // When closing an app, we want the item on the workspace to be invisible immediately
             setIconAndDotVisible(mOriginalIcon, false);
         }
diff --git a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
index 472e1a1..260f556 100644
--- a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
@@ -58,6 +59,7 @@
     private TouchEventGenerator mGenerator;
     private SingleAxisSwipeDetector mDetector;
     private int mTouchSlop;
+    Context mContext;
 
     @Mock
     private SingleAxisSwipeDetector.Listener mMockListener;
@@ -69,12 +71,13 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mGenerator = new TouchEventGenerator((ev) -> mDetector.onTouchEvent(ev));
-        ViewConfiguration orgConfig = ViewConfiguration
-                .get(InstrumentationRegistry.getTargetContext());
+        mContext = InstrumentationRegistry.getTargetContext();
+        ViewConfiguration orgConfig = ViewConfiguration.get(mContext);
         doReturn(orgConfig.getScaledMaximumFlingVelocity()).when(mMockConfig)
                 .getScaledMaximumFlingVelocity();
 
-        mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+        mDetector = new SingleAxisSwipeDetector(mContext,
+                mMockConfig, mMockListener, VERTICAL, false);
         mDetector.setDetectableScrollConditions(DIRECTION_BOTH, false);
         mTouchSlop = orgConfig.getScaledTouchSlop();
         doReturn(mTouchSlop).when(mMockConfig).getScaledTouchSlop();
@@ -84,7 +87,8 @@
 
     @Test
     public void testDragStart_verticalPositive() {
-        mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+        mDetector = new SingleAxisSwipeDetector(mContext,
+                mMockConfig, mMockListener, VERTICAL, false);
         mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100, 100 - mTouchSlop);
@@ -94,7 +98,8 @@
 
     @Test
     public void testDragStart_verticalNegative() {
-        mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+        mDetector = new SingleAxisSwipeDetector(mContext,
+                mMockConfig, mMockListener, VERTICAL, false);
         mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100, 100 + mTouchSlop);
@@ -112,7 +117,8 @@
 
     @Test
     public void testDragStart_horizontalPositive() {
-        mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, false);
+        mDetector = new SingleAxisSwipeDetector(mContext,
+                mMockConfig, mMockListener, HORIZONTAL, false);
         mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
 
         mGenerator.put(0, 100, 100);
@@ -123,7 +129,8 @@
 
     @Test
     public void testDragStart_horizontalNegative() {
-        mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, false);
+        mDetector = new SingleAxisSwipeDetector(mContext,
+                mMockConfig, mMockListener, HORIZONTAL, false);
         mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
 
         mGenerator.put(0, 100, 100);
@@ -134,7 +141,8 @@
 
     @Test
     public void testDragStart_horizontalRtlPositive() {
-        mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, true);
+        mDetector = new SingleAxisSwipeDetector(mContext,
+                mMockConfig, mMockListener, HORIZONTAL, true);
         mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
 
         mGenerator.put(0, 100, 100);
@@ -145,7 +153,8 @@
 
     @Test
     public void testDragStart_horizontalRtlNegative() {
-        mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, true);
+        mDetector = new SingleAxisSwipeDetector(mContext,
+                mMockConfig, mMockListener, HORIZONTAL, true);
         mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
 
         mGenerator.put(0, 100, 100);
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index d5479fb..3eb8cf1 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -212,7 +212,7 @@
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to get overview actions")) {
             verifyActiveContainer();
-            UiObject2 overviewActions = mLauncher.waitForLauncherObject("action_buttons");
+            UiObject2 overviewActions = mLauncher.waitForOverviewObject("action_buttons");
             return new OverviewActions(overviewActions, mLauncher);
         }
     }
@@ -224,19 +224,16 @@
         return mLauncher.hasLauncherObject(mLauncher.getOverviewObjectSelector("clear_all"));
     }
 
-    /* TODO(b/197630182): Once b/188790554 is fixed, remove instanceof check. Currently, when
-        swiping from app to overview in Fallback Recents, taskbar remains and no action buttons
-        are visible, so we are only testing Overview for now, not BaseOverview. */
     private void verifyActionsViewVisibility() {
-        if (!(this instanceof Overview) || !hasTasks()) {
+        if (!hasTasks()) {
             return;
         }
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to assert overview actions view visibility")) {
             if (mLauncher.isTablet() && !isOverviewSnappedToFocusedTaskForTablet()) {
-                mLauncher.waitUntilLauncherObjectGone("action_buttons");
+                mLauncher.waitUntilOverviewObjectGone("action_buttons");
             } else {
-                mLauncher.waitForLauncherObject("action_buttons");
+                mLauncher.waitForOverviewObject("action_buttons");
             }
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 3485dd1..91b1bc7 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1029,6 +1029,10 @@
         waitUntilGoneBySelector(getLauncherObjectSelector(resId));
     }
 
+    void waitUntilOverviewObjectGone(String resId) {
+        waitUntilGoneBySelector(getOverviewObjectSelector(resId));
+    }
+
     void waitUntilLauncherObjectGone(BySelector selector) {
         waitUntilGoneBySelector(makeLauncherSelector(selector));
     }