Merge "Remove redundant instances of depth controller." into udc-dev
diff --git a/quickstep/res/drawable/redesigned_default_sandbox_app_icon.xml b/quickstep/res/drawable/redesigned_default_sandbox_app_icon.xml
deleted file mode 100644
index 2ab6749..0000000
--- a/quickstep/res/drawable/redesigned_default_sandbox_app_icon.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2023 The Android Open Source Project
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-          http://www.apache.org/licenses/LICENSE-2.0
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-    <solid android:color="?attr/surfaceHome" />
-</shape>
diff --git a/quickstep/res/drawable/hotseat_icon_home.xml b/quickstep/res/drawable/redesigned_hotseat_icon.xml
similarity index 94%
rename from quickstep/res/drawable/hotseat_icon_home.xml
rename to quickstep/res/drawable/redesigned_hotseat_icon.xml
index 9ef4863..535756d 100644
--- a/quickstep/res/drawable/hotseat_icon_home.xml
+++ b/quickstep/res/drawable/redesigned_hotseat_icon.xml
@@ -16,6 +16,5 @@
 <shape
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
-    <solid android:color="?attr/surfaceHome" />
     <corners android:radius="@dimen/gesture_tutorial_hotseat_icon_corner_radius" />
 </shape>
\ No newline at end of file
diff --git a/quickstep/res/layout-land/redesigned_gesture_tutorial_mock_hotseat.xml b/quickstep/res/layout-land/redesigned_gesture_tutorial_mock_hotseat.xml
index af86ae7..3b484dc 100644
--- a/quickstep/res/layout-land/redesigned_gesture_tutorial_mock_hotseat.xml
+++ b/quickstep/res/layout-land/redesigned_gesture_tutorial_mock_hotseat.xml
@@ -40,28 +40,28 @@
         android:id="@+id/hotseat_icon_1"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_2"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_3"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_4"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout-land/redesigned_gesture_tutorial_tablet_mock_hotseat.xml b/quickstep/res/layout-land/redesigned_gesture_tutorial_tablet_mock_hotseat.xml
index 983c15b..850f6e3 100644
--- a/quickstep/res/layout-land/redesigned_gesture_tutorial_tablet_mock_hotseat.xml
+++ b/quickstep/res/layout-land/redesigned_gesture_tutorial_tablet_mock_hotseat.xml
@@ -39,42 +39,42 @@
         android:id="@+id/hotseat_search_bar"
         android:layout_width="200dp"
         android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_1"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_2"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_3"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_4"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_5"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/redesigned_gesture_tutorial_foldable_mock_hotseat.xml b/quickstep/res/layout/redesigned_gesture_tutorial_foldable_mock_hotseat.xml
index b41eb8d..821628a 100644
--- a/quickstep/res/layout/redesigned_gesture_tutorial_foldable_mock_hotseat.xml
+++ b/quickstep/res/layout/redesigned_gesture_tutorial_foldable_mock_hotseat.xml
@@ -26,7 +26,7 @@
         android:id="@+id/hotseat_search_bar"
         android:layout_width="0dp"
         android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true"
 
         app:layout_constraintTop_toTopOf="parent"
@@ -50,35 +50,35 @@
         android:id="@+id/hotseat_icon_1"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_2"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_3"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_4"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_5"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
index 6c4ca2e..7d5505e 100644
--- a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
@@ -96,15 +96,6 @@
         android:layout_height="match_parent"
         android:background="@drawable/gesture_tutorial_ripple" />
 
-    <include
-        android:id="@+id/gesture_tutorial_fake_taskbar_view"
-        layout="@layout/gesture_tutorial_tablet_mock_taskbar"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        android:layout_centerHorizontal="true"
-        android:layout_marginBottom="@dimen/gesture_tutorial_taskbar_margin_bottom" />
-
     <ImageView
         android:id="@+id/gesture_tutorial_edge_gesture_video"
         android:layout_width="match_parent"
diff --git a/quickstep/res/layout/redesigned_gesture_tutorial_mock_hotseat.xml b/quickstep/res/layout/redesigned_gesture_tutorial_mock_hotseat.xml
index e93a0fc..f5145ba 100644
--- a/quickstep/res/layout/redesigned_gesture_tutorial_mock_hotseat.xml
+++ b/quickstep/res/layout/redesigned_gesture_tutorial_mock_hotseat.xml
@@ -39,35 +39,36 @@
         android:id="@+id/hotseat_icon_1"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_2"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_3"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_4"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
+        android:id="@+id/hotseat_search_bar"
         android:layout_width="0dp"
         android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
         android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true"
 
         app:layout_constraintEnd_toEndOf="parent"
diff --git a/quickstep/res/layout/redesigned_gesture_tutorial_tablet_mock_hotseat.xml b/quickstep/res/layout/redesigned_gesture_tutorial_tablet_mock_hotseat.xml
index b41eb8d..821628a 100644
--- a/quickstep/res/layout/redesigned_gesture_tutorial_tablet_mock_hotseat.xml
+++ b/quickstep/res/layout/redesigned_gesture_tutorial_tablet_mock_hotseat.xml
@@ -26,7 +26,7 @@
         android:id="@+id/hotseat_search_bar"
         android:layout_width="0dp"
         android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true"
 
         app:layout_constraintTop_toTopOf="parent"
@@ -50,35 +50,35 @@
         android:id="@+id/hotseat_icon_1"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_2"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_3"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_4"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
     <View
         android:id="@+id/hotseat_icon_5"
         android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
         android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
-        android:background="@drawable/hotseat_icon_home"
+        android:background="@drawable/redesigned_hotseat_icon"
         android:clipToOutline="true" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index 8cf6837..c2b4d92 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -23,7 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Forma livre"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nenhum item recente"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configurações de uso do app"</string>
-    <string name="recents_clear_all" msgid="5328176793634888831">"Limpar tudo"</string>
+    <string name="recents_clear_all" msgid="5328176793634888831">"Remover tudo"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Apps recentes"</string>
     <string name="task_view_closed" msgid="9170038230110856166">"Tarefa encerrada"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index b1faf88..42e51cd 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1705,6 +1705,7 @@
                     QuickStepContract.getWindowCornerRadius(mLauncher),
                     false /* fromPredictiveBack */);
 
+            TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets, false, null);
             mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
             result.setAnimation(pair.second, mLauncher);
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
index 08857b7..8a11b57 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
@@ -27,11 +27,13 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.constraintlayout.widget.ConstraintLayout;
 
 import com.android.launcher3.R;
+import com.android.launcher3.util.Preconditions;
 import com.android.quickstep.util.BorderAnimator;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -43,7 +45,9 @@
  */
 public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
 
-    @NonNull private final BorderAnimator mBorderAnimator;
+    @ColorInt private final int mBorderColor;
+
+    @Nullable private BorderAnimator mBorderAnimator;
 
     @Nullable private ImageView mThumbnailView1;
     @Nullable private ImageView mThumbnailView2;
@@ -74,29 +78,9 @@
                 attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
 
         setWillNotDraw(false);
-        Resources resources = context.getResources();
-        mBorderAnimator = new BorderAnimator(
-                /* borderBoundsBuilder= */ bounds -> bounds.set(0, 0, getWidth(), getHeight()),
-                /* borderWidthPx= */ resources.getDimensionPixelSize(
-                        R.dimen.keyboard_quick_switch_border_width),
-                /* borderRadiusPx= */ resources.getDimensionPixelSize(
-                        R.dimen.keyboard_quick_switch_task_view_radius),
-                /* borderColor= */ ta.getColor(
-                        R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR),
-                /* invalidateViewCallback= */ KeyboardQuickSwitchTaskView.this::invalidate,
-                /* viewScaleTargetProvider= */ new BorderAnimator.ViewScaleTargetProvider() {
-                    @NonNull
-                    @Override
-                    public View getContainerView() {
-                        return KeyboardQuickSwitchTaskView.this;
-                    }
 
-                    @NonNull
-                    @Override
-                    public View getContentView() {
-                        return mContent;
-                    }
-                });
+        mBorderColor = ta.getColor(
+                R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR);
         ta.recycle();
     }
 
@@ -108,17 +92,34 @@
         mIcon1 = findViewById(R.id.icon1);
         mIcon2 = findViewById(R.id.icon2);
         mContent = findViewById(R.id.content);
+
+        Resources resources = mContext.getResources();
+
+        Preconditions.assertNotNull(mContent);
+        mBorderAnimator = new BorderAnimator(
+                /* borderRadiusPx= */ resources.getDimensionPixelSize(
+                        R.dimen.keyboard_quick_switch_task_view_radius),
+                /* borderColor= */ mBorderColor,
+                /* borderAnimationParams= */ new BorderAnimator.ScalingParams(
+                        /* borderWidthPx= */ resources.getDimensionPixelSize(
+                                R.dimen.keyboard_quick_switch_border_width),
+                        /* boundsBuilder= */ bounds -> bounds.set(
+                                0, 0, getWidth(), getHeight()),
+                        /* targetView= */ this,
+                        /* contentView= */ mContent));
     }
 
-    @NonNull
+    @Nullable
     protected Animator getFocusAnimator(boolean focused) {
-        return mBorderAnimator.buildAnimator(focused);
+        return mBorderAnimator == null ? null : mBorderAnimator.buildAnimator(focused);
     }
 
     @Override
     public void draw(Canvas canvas) {
         super.draw(canvas);
-        mBorderAnimator.drawBorder(canvas);
+        if (mBorderAnimator != null) {
+            mBorderAnimator.drawBorder(canvas);
+        }
     }
 
     protected void setThumbnails(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 0a83279..040b8f7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -153,6 +153,11 @@
             return false;
         }
         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onTaskbarItemLongClick");
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.TWO_TASKBAR_LONG_CLICKS,
+                    "TaskbarDragController.startDragOnLongClick",
+                    new Throwable());
+        }
         BubbleTextView btv = (BubbleTextView) view;
         mActivity.onDragStart();
         btv.post(() -> {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 1ceb653..8e1059b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -54,6 +54,10 @@
     // Initialized in init.
     private TaskbarControllers mControllers;
 
+    // Used to defer any UI updates during the SUW unstash animation.
+    private boolean mDeferUpdatesForSUW;
+    private Runnable mDeferredUpdates;
+
     public TaskbarModelCallbacks(
             TaskbarActivityContext context, TaskbarView container) {
         mContext = context;
@@ -194,10 +198,38 @@
         }
         hotseatItemInfos = mControllers.taskbarRecentAppsController
                 .updateHotseatItemInfos(hotseatItemInfos);
+
+        if (mDeferUpdatesForSUW) {
+            ItemInfo[] finalHotseatItemInfos = hotseatItemInfos;
+            mDeferredUpdates = () -> {
+                updateHotseatItemsAndBackground(finalHotseatItemInfos);
+            };
+        } else {
+            updateHotseatItemsAndBackground(hotseatItemInfos);
+        }
+    }
+
+    private void updateHotseatItemsAndBackground(ItemInfo[] hotseatItemInfos) {
         mContainer.updateHotseatItems(hotseatItemInfos);
         mControllers.taskbarViewController.updateIconsBackground();
     }
 
+    /**
+     * This is used to defer UI updates after SUW builds the unstash animation.
+     * @param defer if true, defers updates to the UI
+     *              if false, posts updates (if any) to the UI
+     */
+    public void setDeferUpdatesForSUW(boolean defer) {
+        mDeferUpdatesForSUW = defer;
+
+        if (!mDeferUpdatesForSUW) {
+            if (mDeferredUpdates != null) {
+                mContainer.post(mDeferredUpdates);
+                mDeferredUpdates = null;
+            }
+        }
+    }
+
     @Override
     public void onRunningTasksChanged() {
         updateRunningApps();
@@ -232,5 +264,7 @@
             pw.println(
                     String.format("%s\tpredicted items count=%s", prefix, mPredictedItems.size()));
         }
+        pw.println(String.format("%s\tmDeferUpdatesForSUW=%b", prefix, mDeferUpdatesForSUW));
+        pw.println(String.format("%s\tupdates pending=%b", prefix, (mDeferredUpdates != null)));
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 6f82c7d..00e14ad 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -585,10 +585,14 @@
      *                            actually be used since this animation tracks a swipe progress.
      */
     protected void addUnstashToHotseatAnimation(AnimatorSet animation, int placeholderDuration) {
+        // Defer any UI updates now to avoid the UI becoming stale when the animation plays.
+        mControllers.taskbarViewController.setDeferUpdatesForSUW(true);
         createAnimToIsStashed(
                 /* isStashed= */ false,
                 placeholderDuration,
                 TRANSITION_UNSTASH_SUW_MANUAL);
+        animation.addListener(AnimatorListeners.forEndCallback(
+                () -> mControllers.taskbarViewController.setDeferUpdatesForSUW(false)));
         animation.play(mAnimator);
     }
 
@@ -804,7 +808,7 @@
         }
 
         mControllers.taskbarViewController.addRevealAnimToIsStashed(skippable, isStashed, duration,
-                EMPHASIZED);
+                EMPHASIZED, animationType == TRANSITION_UNSTASH_SUW_MANUAL);
 
         play(skippable, mControllers.stashedHandleViewController
                 .createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 7429185..4abd995 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -30,6 +30,7 @@
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM;
 
+import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
@@ -286,7 +287,7 @@
     }
 
     private ValueAnimator createRevealAnimForView(View view, boolean isStashed, float newWidth,
-            boolean isQsb) {
+            boolean isQsb, boolean dispatchOnAnimationStart) {
         Rect viewBounds = new Rect(0, 0, view.getWidth(), view.getHeight());
         int centerY = viewBounds.centerY();
         int halfHandleHeight = mStashedHandleHeight / 2;
@@ -318,8 +319,24 @@
                 : 0f;
         float stashedRadius = stashedRect.height() / 2f;
 
-        return new RoundedRectRevealOutlineProvider(radius, stashedRadius, viewBounds, stashedRect)
+        ValueAnimator reveal = new RoundedRectRevealOutlineProvider(radius,
+                stashedRadius, viewBounds, stashedRect)
                 .createRevealAnimator(view, !isStashed, 0);
+        // SUW animation does not dispatch animation start until *after* the animation is complete.
+        // In order to work properly, the reveal animation start needs to be called immediately.
+        if (dispatchOnAnimationStart) {
+            for (Animator.AnimatorListener listener : reveal.getListeners()) {
+                listener.onAnimationStart(reveal);
+            }
+        }
+        return reveal;
+    }
+
+    /**
+     * Defers any updates to the UI for the setup wizard animation.
+     */
+    public void setDeferUpdatesForSUW(boolean defer) {
+        mModelCallbacks.setDeferUpdatesForSUW(defer);
     }
 
     /**
@@ -332,7 +349,7 @@
      * @param interpolator The interpolator to use for all animations.
      */
     public void addRevealAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
-            Interpolator interpolator) {
+            Interpolator interpolator, boolean dispatchOnAnimationStart) {
         AnimatorSet reveal = new AnimatorSet();
 
         Rect stashedBounds = new Rect();
@@ -349,8 +366,8 @@
             boolean isQsb = child == mTaskbarView.getQsb();
 
             // Crop the icons to/from the nav handle shape.
-            reveal.play(createRevealAnimForView(child, isStashed, newChildWidth, isQsb)
-                    .setDuration(duration));
+            reveal.play(createRevealAnimForView(child, isStashed, newChildWidth, isQsb,
+                    dispatchOnAnimationStart).setDuration(duration));
 
             // Translate the icons to/from their locations as the "nav handle."
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
index 5902912..a642693 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
@@ -27,7 +27,6 @@
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarControllers;
 import com.android.launcher3.taskbar.TaskbarDragController;
-import com.android.launcher3.taskbar.TaskbarStashController;
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.taskbar.allapps.TaskbarAllAppsContainerView;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
@@ -45,8 +44,6 @@
     private final TaskbarDragController mDragController;
     private final TaskbarOverlayDragLayer mDragLayer;
 
-    // We automatically stash taskbar when All Apps is opened in gesture navigation mode.
-    private final boolean mWillTaskbarBeVisuallyStashed;
     private final int mStashedTaskbarHeight;
     private final TaskbarUIController mUiController;
 
@@ -60,18 +57,11 @@
         mDragController = new TaskbarDragController(this);
         mDragController.init(controllers);
         mDragLayer = new TaskbarOverlayDragLayer(this);
-
-        TaskbarStashController taskbarStashController = controllers.taskbarStashController;
-        mWillTaskbarBeVisuallyStashed = taskbarStashController.supportsVisualStashing();
-        mStashedTaskbarHeight = taskbarStashController.getStashedHeight();
+        mStashedTaskbarHeight = controllers.taskbarStashController.getStashedHeight();
 
         mUiController = controllers.uiController;
     }
 
-    boolean willTaskbarBeVisuallyStashed() {
-        return mWillTaskbarBeVisuallyStashed;
-    }
-
     int getStashedTaskbarHeight() {
         return mStashedTaskbarHeight;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
index b4ec682..add7279 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java
@@ -32,6 +32,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
 
@@ -185,7 +186,7 @@
      * 2) Sets tappableInsets bottom inset to 0.
      */
     private WindowInsets updateInsetsDueToStashing(WindowInsets oldInsets) {
-        if (!mActivity.willTaskbarBeVisuallyStashed()) {
+        if (!DisplayController.isTransientTaskbar(mActivity)) {
             return oldInsets;
         }
         WindowInsets.Builder updatedInsetsBuilder = new WindowInsets.Builder(oldInsets);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 79a301a..95c2326 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -1282,7 +1282,8 @@
                             getActivityLaunchOptions(taskView, null).options));
             return;
         }
-        mSplitSelectStateController.launchTasks(
+        mSplitSelectStateController.launchExistingSplitPair(
+                null /* launchingTaskView */,
                 groupTask.task1.key.id,
                 groupTask.task2.key.id,
                 SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index db38cb6..0012d47 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -131,6 +131,11 @@
     }
 
     @Override
+    protected int getHotseatIconColor() {
+        return getExitingAppColor();
+    }
+
+    @Override
     public void onBackGestureAttempted(BackGestureResult result) {
         if (isGestureCompleted()) {
             return;
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index d031677..593e6d9 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -134,7 +134,7 @@
     }
 
     @Override
-    protected int getExitingAppColor() {
+    protected int getHotseatIconColor() {
         return mTutorialFragment.mRootView.mColorOnSurfaceOverview;
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 06d957a..36655d2 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -29,6 +29,7 @@
 import android.annotation.RawRes;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.graphics.Color;
 import android.graphics.Outline;
 import android.graphics.Rect;
 import android.graphics.drawable.AnimatedVectorDrawable;
@@ -107,7 +108,7 @@
     @Nullable View mHotseatIconView;
     final ClipIconView mFakeIconView;
     final FrameLayout mFakeTaskView;
-    final AnimatedTaskbarView mFakeTaskbarView;
+    @Nullable final AnimatedTaskbarView mFakeTaskbarView;
     final AnimatedTaskView mFakePreviousTaskView;
     final View mRippleView;
     final RippleDrawable mRippleDrawable;
@@ -149,7 +150,8 @@
         mFakeHotseatView = rootView.findViewById(R.id.gesture_tutorial_fake_hotseat_view);
         mFakeIconView = rootView.findViewById(R.id.gesture_tutorial_fake_icon_view);
         mFakeTaskView = rootView.findViewById(R.id.gesture_tutorial_fake_task_view);
-        mFakeTaskbarView = rootView.findViewById(R.id.gesture_tutorial_fake_taskbar_view);
+        mFakeTaskbarView = ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
+                ? null : rootView.findViewById(R.id.gesture_tutorial_fake_taskbar_view);
         mFakePreviousTaskView =
                 rootView.findViewById(R.id.gesture_tutorial_fake_previous_task_view);
         mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view);
@@ -269,12 +271,19 @@
     protected abstract int getSwipeActionColor();
 
     @ColorInt
-    protected abstract int getExitingAppColor();
+    protected int getExitingAppColor() {
+        return Color.TRANSPARENT;
+    }
+
+    @ColorInt
+    protected int getHotseatIconColor() {
+        return Color.TRANSPARENT;
+    }
 
     @DrawableRes
     public int getMockAppIconResId() {
         return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
-                ? R.drawable.redesigned_default_sandbox_app_icon
+                ? R.drawable.redesigned_hotseat_icon
                 : R.drawable.default_sandbox_app_icon;
     }
 
@@ -463,7 +472,7 @@
             mFakeTaskView.removeCallbacks(mFakeTaskViewCallback);
             mFakeTaskViewCallback = null;
         }
-        if (mFakeTaskbarViewCallback != null) {
+        if (mFakeTaskbarViewCallback != null && mFakeTaskbarView != null) {
             mFakeTaskbarView.removeCallbacks(mFakeTaskbarViewCallback);
             mFakeTaskbarViewCallback = null;
         }
@@ -621,7 +630,7 @@
     }
 
     void hideFakeTaskbar(boolean animateToHotseat) {
-        if (!mTutorialFragment.isLargeScreen()) {
+        if (!mTutorialFragment.isLargeScreen() || mFakeTaskbarView == null) {
             return;
         }
         if (mFakeTaskbarViewCallback != null) {
@@ -635,7 +644,7 @@
     }
 
     void showFakeTaskbar(boolean animateFromHotseat) {
-        if (!mTutorialFragment.isLargeScreen()) {
+        if (!mTutorialFragment.isLargeScreen() || mFakeTaskbarView == null) {
             return;
         }
         if (mFakeTaskbarViewCallback != null) {
@@ -670,6 +679,11 @@
         }
     }
 
+    private void updateHotseatChildViewColor(@Nullable View child) {
+        if (child == null) return;
+        child.getBackground().setTint(getHotseatIconColor());
+    }
+
     private void updateDrawables() {
         if (mContext != null) {
             mTutorialFragment.getRootView().setBackground(AppCompatResources.getDrawable(
@@ -689,6 +703,12 @@
 
             if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
                 mExitingAppView.setBackgroundColor(getExitingAppColor());
+                updateHotseatChildViewColor(mFakeIconView);
+                updateHotseatChildViewColor(mFakeHotseatView.findViewById(R.id.hotseat_icon_2));
+                updateHotseatChildViewColor(mFakeHotseatView.findViewById(R.id.hotseat_icon_3));
+                updateHotseatChildViewColor(mFakeHotseatView.findViewById(R.id.hotseat_icon_4));
+                updateHotseatChildViewColor(mFakeHotseatView.findViewById(R.id.hotseat_icon_5));
+                updateHotseatChildViewColor(mFakeHotseatView.findViewById(R.id.hotseat_search_bar));
             }
         }
     }
@@ -712,8 +732,10 @@
                         ? R.dimen.gesture_tutorial_tablet_feedback_margin_top
                         : R.dimen.gesture_tutorial_feedback_margin_top);
 
-        mFakeTaskbarView.setVisibility((mTutorialFragment.isLargeScreen()
-                && !ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) ? View.VISIBLE : GONE);
+        if (mFakeTaskbarView != null) {
+            mFakeTaskbarView.setVisibility(
+                    mTutorialFragment.isLargeScreen() ? View.VISIBLE : GONE);
+        }
 
         RelativeLayout.LayoutParams hotseatLayoutParams =
                 (RelativeLayout.LayoutParams) mFakeHotseatView.getLayoutParams();
diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.java b/quickstep/src/com/android/quickstep/util/BorderAnimator.java
index c43fb27..011d45c 100644
--- a/quickstep/src/com/android/quickstep/util/BorderAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.java
@@ -20,6 +20,7 @@
 import android.annotation.ColorInt;
 import android.annotation.Nullable;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.view.View;
@@ -37,8 +38,8 @@
  * <p>
  * To use this class:
  * 1. Create an instance in the target view. NOTE: The border will animate outwards from the
- *      provided border bounds. If the border will not be visible outside of those bounds, then a
- *      {@link ViewScaleTargetProvider} must be provided in the constructor.
+ *      provided border bounds. See {@link SimpleParams} and {@link ScalingParams} to determine
+ *      which would be best for your target view.
  * 2. Override the target view's {@link android.view.View#draw(Canvas)} method and call
  *      {@link BorderAnimator#drawBorder(Canvas)} after {@code super.draw(canvas)}.
  * 3. Call {@link BorderAnimator#buildAnimator(boolean)} and start the animation or call
@@ -46,7 +47,7 @@
  */
 public final class BorderAnimator {
 
-    public static final int DEFAULT_BORDER_COLOR = 0xffffffff;
+    public static final int DEFAULT_BORDER_COLOR = Color.WHITE;
 
     private static final long DEFAULT_APPEARANCE_ANIMATION_DURATION_MS = 300;
     private static final long DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS = 133;
@@ -54,68 +55,44 @@
 
     @NonNull private final AnimatedFloat mBorderAnimationProgress = new AnimatedFloat(
             this::updateOutline);
-    @NonNull private final Rect mBorderBounds = new Rect();
-    @NonNull private final BorderBoundsBuilder mBorderBoundsBuilder;
-    @Px private final int mBorderWidthPx;
     @Px private final int mBorderRadiusPx;
-    @NonNull private final Runnable mInvalidateViewCallback;
-    @Nullable private final ViewScaleTargetProvider mViewScaleTargetProvider;
+    @NonNull private final BorderAnimationParams mBorderAnimationParams;
     private final long mAppearanceDurationMs;
     private final long mDisappearanceDurationMs;
     @NonNull private final Interpolator mInterpolator;
     @NonNull private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
-    private float mAlignmentAdjustment;
-
     @Nullable private Animator mRunningBorderAnimation;
 
     public BorderAnimator(
-            @NonNull BorderBoundsBuilder borderBoundsBuilder,
-            int borderWidthPx,
-            int borderRadiusPx,
+            @Px int borderRadiusPx,
             @ColorInt int borderColor,
-            @NonNull Runnable invalidateViewCallback) {
-        this(borderBoundsBuilder,
-                borderWidthPx,
-                borderRadiusPx,
+            @NonNull BorderAnimationParams borderAnimationParams) {
+        this(borderRadiusPx,
                 borderColor,
-                invalidateViewCallback,
-                /* viewScaleTargetProvider= */ null);
-    }
-
-    public BorderAnimator(
-            @NonNull BorderBoundsBuilder borderBoundsBuilder,
-            int borderWidthPx,
-            int borderRadiusPx,
-            @ColorInt int borderColor,
-            @NonNull Runnable invalidateViewCallback,
-            @Nullable ViewScaleTargetProvider viewScaleTargetProvider) {
-        this(borderBoundsBuilder,
-                borderWidthPx,
-                borderRadiusPx,
-                borderColor,
-                invalidateViewCallback,
-                viewScaleTargetProvider,
+                borderAnimationParams,
                 DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
                 DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS,
                 DEFAULT_INTERPOLATOR);
     }
 
+    /**
+     * @param borderRadiusPx the radius of the border's corners, in pixels
+     * @param borderColor the border's color
+     * @param borderAnimationParams params for handling different target view layout situation.
+     * @param appearanceDurationMs appearance animation duration, in milliseconds
+     * @param disappearanceDurationMs disappearance animation duration, in milliseconds
+     * @param interpolator animation interpolator
+     */
     public BorderAnimator(
-            @NonNull BorderBoundsBuilder borderBoundsBuilder,
-            int borderWidthPx,
-            int borderRadiusPx,
+            @Px int borderRadiusPx,
             @ColorInt int borderColor,
-            @NonNull Runnable invalidateViewCallback,
-            @Nullable ViewScaleTargetProvider viewScaleTargetProvider,
+            @NonNull BorderAnimationParams borderAnimationParams,
             long appearanceDurationMs,
             long disappearanceDurationMs,
             @NonNull Interpolator interpolator) {
-        mBorderBoundsBuilder = borderBoundsBuilder;
-        mBorderWidthPx = borderWidthPx;
         mBorderRadiusPx = borderRadiusPx;
-        mInvalidateViewCallback = invalidateViewCallback;
-        mViewScaleTargetProvider = viewScaleTargetProvider;
+        mBorderAnimationParams = borderAnimationParams;
         mAppearanceDurationMs = appearanceDurationMs;
         mDisappearanceDurationMs = disappearanceDurationMs;
         mInterpolator = interpolator;
@@ -128,15 +105,11 @@
     private void updateOutline() {
         float interpolatedProgress = mInterpolator.getInterpolation(
                 mBorderAnimationProgress.value);
-        float borderWidth = mBorderWidthPx * interpolatedProgress;
-        // Outset the border by half the width to create an outwards-growth animation
-        mAlignmentAdjustment = (-borderWidth / 2f)
-                // Inset the border if we are scaling the container up
-                + (mViewScaleTargetProvider == null ? 0 : mBorderWidthPx);
 
+        mBorderAnimationParams.setProgress(interpolatedProgress);
         mBorderPaint.setAlpha(Math.round(255 * interpolatedProgress));
-        mBorderPaint.setStrokeWidth(borderWidth);
-        mInvalidateViewCallback.run();
+        mBorderPaint.setStrokeWidth(mBorderAnimationParams.getBorderWidth());
+        mBorderAnimationParams.mTargetView.invalidate();
     }
 
     /**
@@ -146,16 +119,14 @@
      * calling super.
      */
     public void drawBorder(Canvas canvas) {
-        // Increase the radius if we are scaling the container up
-        float radiusAdjustment = mViewScaleTargetProvider == null
-                ? -mAlignmentAdjustment : mAlignmentAdjustment;
+        float alignmentAdjustment = mBorderAnimationParams.getAlignmentAdjustment();
         canvas.drawRoundRect(
-                /* left= */ mBorderBounds.left + mAlignmentAdjustment,
-                /* top= */ mBorderBounds.top + mAlignmentAdjustment,
-                /* right= */ mBorderBounds.right - mAlignmentAdjustment,
-                /* bottom= */ mBorderBounds.bottom - mAlignmentAdjustment,
-                /* rx= */ mBorderRadiusPx + radiusAdjustment,
-                /* ry= */ mBorderRadiusPx + radiusAdjustment,
+                /* left= */ mBorderAnimationParams.mBorderBounds.left + alignmentAdjustment,
+                /* top= */ mBorderAnimationParams.mBorderBounds.top + alignmentAdjustment,
+                /* right= */ mBorderAnimationParams.mBorderBounds.right - alignmentAdjustment,
+                /* bottom= */ mBorderAnimationParams.mBorderBounds.bottom - alignmentAdjustment,
+                /* rx= */ mBorderRadiusPx + mBorderAnimationParams.getRadiusAdjustment(),
+                /* ry= */ mBorderRadiusPx + mBorderAnimationParams.getRadiusAdjustment(),
                 /* paint= */ mBorderPaint);
     }
 
@@ -164,7 +135,6 @@
      */
     @NonNull
     public Animator buildAnimator(boolean isAppearing) {
-        mBorderBoundsBuilder.updateBorderBounds(mBorderBounds);
         mRunningBorderAnimation = mBorderAnimationProgress.animateToValue(isAppearing ? 1f : 0f);
         mRunningBorderAnimation.setDuration(
                 isAppearing ? mAppearanceDurationMs : mDisappearanceDurationMs);
@@ -172,7 +142,7 @@
         mRunningBorderAnimation.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                setViewScales();
+                mBorderAnimationParams.onShowBorder();
             }
         });
         mRunningBorderAnimation.addListener(
@@ -181,7 +151,7 @@
                     if (isAppearing) {
                         return;
                     }
-                    resetViewScales();
+                    mBorderAnimationParams.onHideBorder();
                 }));
 
         return mRunningBorderAnimation;
@@ -196,56 +166,15 @@
         if (mRunningBorderAnimation != null) {
             mRunningBorderAnimation.end();
         }
-        mBorderBoundsBuilder.updateBorderBounds(mBorderBounds);
         if (visible) {
-            setViewScales();
+            mBorderAnimationParams.onShowBorder();
         }
         mBorderAnimationProgress.updateValue(visible ? 1f : 0f);
         if (!visible) {
-            resetViewScales();
+            mBorderAnimationParams.onHideBorder();
         }
     }
 
-    private void setViewScales() {
-        if (mViewScaleTargetProvider == null) {
-            return;
-        }
-        View container = mViewScaleTargetProvider.getContainerView();
-        float width = container.getWidth();
-        float height = container.getHeight();
-        // scale up just enough to make room for the border
-        float scaleX = 1f + ((2 * mBorderWidthPx) / width);
-        float scaleY = 1f + ((2 * mBorderWidthPx) / height);
-
-        container.setPivotX(width / 2);
-        container.setPivotY(height / 2);
-        container.setScaleX(scaleX);
-        container.setScaleY(scaleY);
-
-        View contentView = mViewScaleTargetProvider.getContentView();
-        contentView.setPivotX(contentView.getWidth() / 2f);
-        contentView.setPivotY(contentView.getHeight() / 2f);
-        contentView.setScaleX(1f / scaleX);
-        contentView.setScaleY(1f / scaleY);
-    }
-
-    private void resetViewScales() {
-        if (mViewScaleTargetProvider == null) {
-            return;
-        }
-        View container = mViewScaleTargetProvider.getContainerView();
-        container.setPivotX(container.getWidth());
-        container.setPivotY(container.getHeight());
-        container.setScaleX(1f);
-        container.setScaleY(1f);
-
-        View contentView = mViewScaleTargetProvider.getContentView();
-        contentView.setPivotX(contentView.getWidth() / 2f);
-        contentView.setPivotY(contentView.getHeight() / 2f);
-        contentView.setScaleX(1f);
-        contentView.setScaleY(1f);
-    }
-
     /**
      * Callback to update the border bounds when building this animation.
      */
@@ -258,23 +187,166 @@
     }
 
     /**
-     * Provider for scaling target views for the beginning and end of this animation.
+     * Params for handling different target view layout situation.
      */
-    public interface ViewScaleTargetProvider {
+    private abstract static class BorderAnimationParams {
+
+        @NonNull private final Rect mBorderBounds = new Rect();
+        @NonNull private final BorderBoundsBuilder mBoundsBuilder;
+
+        @NonNull final View mTargetView;
+        @Px final int mBorderWidthPx;
+
+        private float mAnimationProgress = 0f;
+        @Nullable private View.OnLayoutChangeListener mLayoutChangeListener;
 
         /**
-         * Returns the content view's container. This view will be scaled up to make room for the
-         * border.
+         * @param borderWidthPx the width of the border, in pixels
+         * @param boundsBuilder callback to update the border bounds
+         * @param targetView the view that will be drawing the border
          */
-        @NonNull
-        View getContainerView();
+        private BorderAnimationParams(
+                @Px int borderWidthPx,
+                @NonNull BorderBoundsBuilder boundsBuilder,
+                @NonNull View targetView) {
+            mBorderWidthPx = borderWidthPx;
+            mBoundsBuilder = boundsBuilder;
+            mTargetView = targetView;
+        }
+
+        private void setProgress(float progress) {
+            mAnimationProgress = progress;
+        }
+
+        private float getBorderWidth() {
+            return mBorderWidthPx * mAnimationProgress;
+        }
+
+        float getAlignmentAdjustment() {
+            // Outset the border by half the width to create an outwards-growth animation
+            return (-getBorderWidth() / 2f) + getAlignmentAdjustmentInset();
+        }
+
+
+        void onShowBorder() {
+            if (mLayoutChangeListener == null) {
+                mLayoutChangeListener =
+                        (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                            onShowBorder();
+                            mTargetView.invalidate();
+                        };
+                mTargetView.addOnLayoutChangeListener(mLayoutChangeListener);
+            }
+            mBoundsBuilder.updateBorderBounds(mBorderBounds);
+        }
+
+        void onHideBorder() {
+            if (mLayoutChangeListener != null) {
+                mTargetView.removeOnLayoutChangeListener(mLayoutChangeListener);
+                mLayoutChangeListener = null;
+            }
+        }
+
+        abstract int getAlignmentAdjustmentInset();
+
+        abstract float getRadiusAdjustment();
+    }
+
+    /**
+     * Use an instance of this {@link BorderAnimationParams} if the border can be drawn outside the
+     * target view's bounds without any additional logic.
+     */
+    public static final class SimpleParams extends BorderAnimationParams {
+
+        public SimpleParams(
+                @Px int borderWidthPx,
+                @NonNull BorderBoundsBuilder boundsBuilder,
+                @NonNull View targetView) {
+            super(borderWidthPx, boundsBuilder, targetView);
+        }
+
+        @Override
+        int getAlignmentAdjustmentInset() {
+            return 0;
+        }
+
+        @Override
+        float getRadiusAdjustment() {
+            return -getAlignmentAdjustment();
+        }
+    }
+
+    /**
+     * Use an instance of this {@link BorderAnimationParams} if the border would other be clipped by
+     * the target view's bound.
+     * <p>
+     * Note: using these params will set the scales and pivots of the
+     * container and content views, however will only reset the scales back to 1.
+     */
+    public static final class ScalingParams extends BorderAnimationParams {
+
+        @NonNull private final View mContentView;
 
         /**
-         * Returns the content view. This view will be scaled down reciprocally to the container's
-         * up-scaling to maintain its original size. This should be the view containing all of the
-         * content being surrounded by the border.
+         * @param targetView the view that will be drawing the border. this view will be scaled up
+         *                   to make room for the border
+         * @param contentView the view around which the border will be drawn. this view will be
+         *                    scaled down reciprocally to keep its original size and location.
          */
-        @NonNull
-        View getContentView();
+        public ScalingParams(
+                @Px int borderWidthPx,
+                @NonNull BorderBoundsBuilder boundsBuilder,
+                @NonNull View targetView,
+                @NonNull View contentView) {
+            super(borderWidthPx, boundsBuilder, targetView);
+            mContentView = contentView;
+        }
+
+        @Override
+        void onShowBorder() {
+            super.onShowBorder();
+            float width = mTargetView.getWidth();
+            float height = mTargetView.getHeight();
+            // Scale up just enough to make room for the border. Fail fast and fix the scaling
+            // onLayout.
+            float scaleX = width == 0 ? 1f : 1f + ((2 * mBorderWidthPx) / width);
+            float scaleY = height == 0 ? 1f : 1f + ((2 * mBorderWidthPx) / height);
+
+            mTargetView.setPivotX(width / 2);
+            mTargetView.setPivotY(height / 2);
+            mTargetView.setScaleX(scaleX);
+            mTargetView.setScaleY(scaleY);
+
+            mContentView.setPivotX(mContentView.getWidth() / 2f);
+            mContentView.setPivotY(mContentView.getHeight() / 2f);
+            mContentView.setScaleX(1f / scaleX);
+            mContentView.setScaleY(1f / scaleY);
+        }
+
+        @Override
+        void onHideBorder() {
+            super.onHideBorder();
+            mTargetView.setPivotX(mTargetView.getWidth());
+            mTargetView.setPivotY(mTargetView.getHeight());
+            mTargetView.setScaleX(1f);
+            mTargetView.setScaleY(1f);
+
+            mContentView.setPivotX(mContentView.getWidth() / 2f);
+            mContentView.setPivotY(mContentView.getHeight() / 2f);
+            mContentView.setScaleX(1f);
+            mContentView.setScaleY(1f);
+        }
+
+        @Override
+        int getAlignmentAdjustmentInset() {
+            // Inset the border since we are scaling the container up
+            return mBorderWidthPx;
+        }
+
+        @Override
+        float getRadiusAdjustment() {
+            // Increase the radius since we are scaling the container up
+            return getAlignmentAdjustment();
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index da81410..ee51af7 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -298,36 +298,6 @@
     }
 
     /**
-     * To be called when we want to launch split pairs from an existing GroupedTaskView.
-     */
-    public void launchTasks(GroupedTaskView groupedTaskView, Consumer<Boolean> callback,
-            boolean freezeTaskList) {
-        mLaunchingTaskView = groupedTaskView;
-        TaskView.TaskIdAttributeContainer[] taskIdAttributeContainers =
-                groupedTaskView.getTaskIdAttributeContainers();
-        launchTasks(taskIdAttributeContainers[0].getTask().key.id,
-                taskIdAttributeContainers[1].getTask().key.id,
-                taskIdAttributeContainers[0].getStagePosition(), callback, freezeTaskList,
-                groupedTaskView.getSplitRatio());
-    }
-
-    /**
-     * To be called when we want to launch split pairs from Overview when split is initiated from
-     * Overview.
-     */
-    public void launchTasks(int taskId1, int taskId2, @StagePosition int stagePosition,
-            Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
-        if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
-            mSplitSelectDataHolder.setInitialTaskSelect(null /*intent*/,
-                    stagePosition, null /*itemInfo*/, null /*splitEvent*/,
-                    taskId1);
-            mSplitSelectDataHolder.setSecondTask(taskId2);
-        }
-        launchTasks(taskId1, null /* intent1 */, taskId2, null /* intent2 */, stagePosition,
-                callback, freezeTaskList, splitRatio, null);
-    }
-
-    /**
      * To be called when we want to launch split pairs from Overview. Split can be initiated from
      * either Overview or home, or all apps. Either both taskIds are set, or a pending intent + a
      * fill in intent with a taskId2 are set.
@@ -510,6 +480,47 @@
         }
     }
 
+    /**
+     * Used to launch split screen from a split pair that already exists (usually accessible through
+     * Overview). This is different than
+     * {@link #launchTasks(int, Intent, int, Intent, int, Consumer, boolean, float, InstanceId)} in
+     * that this only launches split screen that are existing tasks. This doesn't determine which
+     * API should be used (i.e. launching split with existing tasks vs intents vs shortcuts, etc).
+     *
+     * <p/>
+     * NOTE: This is not to be used to launch AppPairs.
+     */
+    public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView,
+            int firstTaskId, int secondTaskId, @StagePosition int stagePosition,
+            Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
+        mLaunchingTaskView = groupedTaskView;
+        final ActivityOptions options1 = ActivityOptions.makeBasic();
+        if (freezeTaskList) {
+            options1.setFreezeRecentTasksReordering();
+        }
+        Bundle optionsBundle = options1.toBundle();
+
+        if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+            final RemoteSplitLaunchTransitionRunner animationRunner =
+                    new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback);
+            final RemoteTransition remoteTransition = new RemoteTransition(animationRunner,
+                    ActivityThread.currentActivityThread().getApplicationThread(),
+                    "LaunchSplitPair");
+            mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId,
+                    null /* options2 */, stagePosition, splitRatio,
+                    remoteTransition, null /*shellInstanceId*/);
+        } else {
+            final RemoteSplitLaunchAnimationRunner animationRunner =
+                    new RemoteSplitLaunchAnimationRunner(firstTaskId, secondTaskId, callback);
+            final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+                    animationRunner, 300, 150,
+                    ActivityThread.currentActivityThread().getApplicationThread());
+            mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle,
+                    secondTaskId, null /* options2 */, stagePosition,
+                    splitRatio, adapter, null /*shellInstanceId*/);
+        }
+    }
+
     private void launchIntentOrShortcut(Intent intent, UserHandle user, ActivityOptions options1,
             int taskId, @StagePosition int stagePosition, float splitRatio,
             RemoteTransition remoteTransition, @Nullable InstanceId shellInstanceId) {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index c6c84bd..c91b183 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -223,13 +223,11 @@
         // Callbacks run from remote animation when recents animation not currently running
         InteractionJankMonitorWrapper.begin(this,
                 InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Enter form GroupedTaskView");
-        recentsView.getSplitSelectController().launchTasks(this /*groupedTaskView*/,
-                success -> {
-                    endCallback.executeAllAndDestroy();
-                    InteractionJankMonitorWrapper.end(
-                            InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
-                },
-                false /* freezeTaskList */);
+        launchTask(success -> {
+            endCallback.executeAllAndDestroy();
+            InteractionJankMonitorWrapper.end(
+                    InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+        }, false /* freezeTaskList */);
 
         // Callbacks get run from recentsView for case when recents animation already running
         recentsView.addSideTaskLaunchCallback(endCallback);
@@ -238,9 +236,9 @@
 
     @Override
     public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
-        getRecentsView().getSplitSelectController().launchTasks(mTask.key.id, mSecondaryTask.key.id,
-                SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT, callback, isQuickswitch,
-                getSplitRatio());
+        getRecentsView().getSplitSelectController().launchExistingSplitPair(this, mTask.key.id,
+                mSecondaryTask.key.id, SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+                callback, isQuickswitch, getSplitRatio());
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index c47c946..6e7b6dc 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -445,13 +445,14 @@
         mBorderAnimator = !keyboardFocusHighlightEnabled
                 ? null
                 : new BorderAnimator(
-                        /* borderBoundsBuilder= */ this::updateBorderBounds,
-                        /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
-                                R.dimen.keyboard_quick_switch_border_width),
                         /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
                         /* borderColor= */ ta.getColor(
                                 R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR),
-                        /* invalidateViewCallback= */ TaskView.this::invalidate);
+                        /* borderAnimationParams= */ new BorderAnimator.SimpleParams(
+                                /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
+                                        R.dimen.keyboard_quick_switch_border_width),
+                                /* boundsBuilder= */ this::updateBorderBounds,
+                                /* targetView= */ this));
         ta.recycle();
     }
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 77c76d9..88cac97 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -428,6 +428,55 @@
     }
 
     @Test
+    @PortraitLandscape
+    public void testOverviewDeadzones() throws Exception {
+        startTestAppsWithCheck();
+
+        Overview overview = mLauncher.goHome().switchToOverview();
+        assertTrue("Launcher internal state should be Overview",
+                isInState(() -> LauncherState.OVERVIEW));
+        executeOnLauncher(
+                launcher -> assertTrue("Should have at least 3 tasks",
+                        getTaskCount(launcher) >= 3));
+
+        // It should not dismiss overview when tapping between tasks
+        overview.touchBetweenTasks();
+        overview = mLauncher.getOverview();
+        assertTrue("Launcher internal state should be Overview",
+                isInState(() -> LauncherState.OVERVIEW));
+
+        // Dismiss when tapping to the right of the focused task
+        overview.touchOutsideFirstTask();
+        assertTrue("Launcher internal state should be Home",
+                isInState(() -> LauncherState.NORMAL));
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testTaskbarDeadzonesForTablet() throws Exception {
+        assumeTrue(mLauncher.isTablet());
+
+        startTestAppsWithCheck();
+
+        Overview overview = mLauncher.goHome().switchToOverview();
+        assertTrue("Launcher internal state should be Overview",
+                isInState(() -> LauncherState.OVERVIEW));
+        executeOnLauncher(
+                launcher -> assertTrue("Should have at least 3 tasks",
+                        getTaskCount(launcher) >= 3));
+
+        // On persistent taskbar, it should not dismiss when tapping the taskbar
+        overview.touchTaskbarBottomCorner(/* tapRight= */ false);
+        assertTrue("Launcher internal state should be Overview",
+                isInState(() -> LauncherState.OVERVIEW));
+
+        // On persistent taskbar, it should not dismiss when tapping the taskbar
+        overview.touchTaskbarBottomCorner(/* tapRight= */ true);
+        assertTrue("Launcher internal state should be Overview",
+                isInState(() -> LauncherState.OVERVIEW));
+    }
+
+    @Test
     @ScreenRecord // b/242163205
     public void testDisableRotationCheckForPhone() throws Exception {
         assumeFalse(mLauncher.isTablet());
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index 9ec346a..47bf9e7 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -58,6 +58,7 @@
         <com.android.launcher3.views.RecyclerViewFastScroller
             android:id="@+id/fast_scroller"
             android:layout_width="@dimen/fastscroll_width"
+            android:elevation="1dp"
             android:layout_height="match_parent"
             android:layout_alignParentEnd="true"
             android:layout_alignParentTop="true"
diff --git a/res/layout/widgets_full_sheet_paged_view.xml b/res/layout/widgets_full_sheet_paged_view.xml
index 94f707a..069d4bc 100644
--- a/res/layout/widgets_full_sheet_paged_view.xml
+++ b/res/layout/widgets_full_sheet_paged_view.xml
@@ -48,7 +48,6 @@
         android:layout_height="wrap_content"
         android:layout_below="@id/collapse_handle"
         android:paddingBottom="0dp"
-        android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
         android:clipToOutline="true"
         android:orientation="vertical">
 
@@ -67,6 +66,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:elevation="0.1dp"
+            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
             android:background="?attr/widgetPickerPrimarySurfaceColor"
             android:paddingBottom="8dp"
             launcher:layout_sticky="true">
@@ -78,6 +78,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="8dp"
+            android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
             android:background="@drawable/widgets_surface_background"
             android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
             android:visibility="gone" />
@@ -89,6 +90,7 @@
             android:gravity="center_horizontal"
             android:orientation="horizontal"
             android:paddingVertical="8dp"
+            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
             android:background="?attr/widgetPickerPrimarySurfaceColor"
             style="@style/TextHeadline"
             launcher:layout_sticky="true">
diff --git a/res/layout/widgets_two_pane_sheet_paged_view.xml b/res/layout/widgets_two_pane_sheet_paged_view.xml
index 2e05d48..442957a 100644
--- a/res/layout/widgets_two_pane_sheet_paged_view.xml
+++ b/res/layout/widgets_two_pane_sheet_paged_view.xml
@@ -13,14 +13,14 @@
      limitations under the License.
 -->
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
+    xmlns:launcher="http://schemas.android.com/apk/res-auto">
 
     <FrameLayout
         android:id="@+id/widgets_two_pane_sheet_paged_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:gravity="start"
+        android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
         android:layout_gravity="start"
         android:layout_alignParentStart="true">
         <com.android.launcher3.widget.picker.WidgetPagedView
@@ -29,7 +29,6 @@
             android:layout_height="match_parent"
             android:clipToPadding="false"
             android:descendantFocusability="afterDescendants"
-            android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
             launcher:pageIndicator="@+id/tabs" >
 
             <com.android.launcher3.widget.picker.WidgetsRecyclerView
@@ -62,7 +61,6 @@
                 android:clipToPadding="false"
                 android:elevation="0.1dp"
                 android:paddingBottom="8dp"
-                android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
                 launcher:layout_sticky="true">
 
                 <include layout="@layout/widgets_search_bar" />
@@ -73,7 +71,6 @@
                 android:layout_height="match_parent"
                 android:id="@+id/suggestions_header"
                 android:layout_marginTop="8dp"
-                android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
                 android:orientation="horizontal"
                 android:background="?attr/widgetPickerPrimarySurfaceColor"
                 launcher:layout_sticky="true">
@@ -86,7 +83,6 @@
                 android:gravity="center_horizontal"
                 android:orientation="horizontal"
                 android:paddingVertical="8dp"
-                android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
                 android:background="?attr/widgetPickerPrimarySurfaceColor"
                 style="@style/TextHeadline"
                 launcher:layout_sticky="true">
diff --git a/res/layout/widgets_two_pane_sheet_recyclerview.xml b/res/layout/widgets_two_pane_sheet_recyclerview.xml
index f8d72e8..c9c855c 100644
--- a/res/layout/widgets_two_pane_sheet_recyclerview.xml
+++ b/res/layout/widgets_two_pane_sheet_recyclerview.xml
@@ -13,8 +13,7 @@
      limitations under the License.
 -->
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
+    xmlns:launcher="http://schemas.android.com/apk/res-auto">
 
     <FrameLayout
         android:id="@+id/widgets_two_pane_sheet_recyclerview"
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index 3dd8627..ec874b9 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -20,7 +20,6 @@
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.IntArray;
@@ -77,16 +76,8 @@
                 + "oldWidgetIds=" + IntArray.wrap(oldWidgetIds).toConcatString()
                 + ", newWidgetIds=" + IntArray.wrap(newWidgetIds).toConcatString());
 
-        try {
-            IntArray result = LauncherDbUtils.queryIntArray(false, controller.getDb(),
-                    Favorites.TABLE_NAME, Favorites.APPWIDGET_ID,
-                    Favorites.APPWIDGET_ID + "!=" + LauncherAppWidgetInfo.NO_ID, null, null);
-            // TODO(b/234700507): Remove the logs after the bug is fixed
-            Log.d(TAG, "restoreAppWidgetIds: all widget ids in database: "
-                    + result.toConcatString());
-        } catch (Exception ex) {
-            Log.e(TAG, "Getting widget ids from the database failed", ex);
-        }
+        // TODO(b/234700507): Remove the logs after the bug is fixed
+        logDatabaseWidgetInfo(controller);
 
         for (int i = 0; i < oldWidgetIds.length; i++) {
             Log.i(TAG, "Widget state restore id " + oldWidgetIds[i] + " => " + newWidgetIds[i]);
@@ -104,9 +95,13 @@
             // recreate the widget during loading with the correct host provider.
             long mainProfileId = UserCache.INSTANCE.get(context)
                     .getSerialNumberForUser(myUserHandle());
+            long controllerProfileId = controller.getSerialNumberForUser(myUserHandle());
             String oldWidgetId = Integer.toString(oldWidgetIds[i]);
             final String where = "appWidgetId=? and (restored & 1) = 1 and profileId=?";
-            final String[] args = new String[] { oldWidgetId, Long.toString(mainProfileId) };
+            String profileId = Long.toString(mainProfileId);
+            final String[] args = new String[] { oldWidgetId, profileId };
+            Log.d(TAG, "restoreAppWidgetIds: querying profile id=" + profileId
+                    + " with controller profile ID=" + controllerProfileId);
             int result = new ContentWriter(context,
                             new ContentWriter.CommitParams(controller, where, args))
                     .put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i])
@@ -135,4 +130,48 @@
             app.getModel().forceReload();
         }
     }
+
+    private static void logDatabaseWidgetInfo(ModelDbController controller) {
+        try (Cursor cursor = controller.getDb().query(Favorites.TABLE_NAME,
+                new String[]{Favorites.APPWIDGET_ID, Favorites.RESTORED, Favorites.PROFILE_ID},
+                Favorites.APPWIDGET_ID + "!=" + LauncherAppWidgetInfo.NO_ID, null,
+                null, null, null)) {
+            IntArray widgetIdList = new IntArray();
+            IntArray widgetRestoreList = new IntArray();
+            IntArray widgetProfileIdList = new IntArray();
+
+            if (cursor.moveToFirst()) {
+                final int widgetIdColumnIndex = cursor.getColumnIndex(Favorites.APPWIDGET_ID);
+                final int widgetRestoredColumnIndex = cursor.getColumnIndex(Favorites.RESTORED);
+                final int widgetProfileIdIndex = cursor.getColumnIndex(Favorites.PROFILE_ID);
+                while (!cursor.isAfterLast()) {
+                    int widgetId = cursor.getInt(widgetIdColumnIndex);
+                    int widgetRestoredFlag = cursor.getInt(widgetRestoredColumnIndex);
+                    int widgetProfileId = cursor.getInt(widgetProfileIdIndex);
+
+                    widgetIdList.add(widgetId);
+                    widgetRestoreList.add(widgetRestoredFlag);
+                    widgetProfileIdList.add(widgetProfileId);
+                    cursor.moveToNext();
+                }
+            }
+
+            StringBuilder builder = new StringBuilder();
+            builder.append("[");
+            for (int i = 0; i < widgetIdList.size(); i++) {
+                builder.append("[")
+                        .append(widgetIdList.get(i))
+                        .append(", ")
+                        .append(widgetRestoreList.get(i))
+                        .append(", ")
+                        .append(widgetProfileIdList.get(i))
+                        .append("]");
+            }
+            builder.append("]");
+            Log.d(TAG, "restoreAppWidgetIds: all widget ids in database: "
+                    + builder.toString());
+        } catch (Exception ex) {
+            Log.e(TAG, "Getting widget ids from the database failed", ex);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 26a1886..36e5e1b 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -31,6 +31,8 @@
 import android.view.View;
 import android.view.ViewDebug;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
@@ -230,11 +232,13 @@
         return textView;
     }
 
+    @Nullable
     @Override
     public CellLayout getPageAt(int index) {
         return (CellLayout) getChildAt(index);
     }
 
+    @Nullable
     public CellLayout getCurrentCellLayout() {
         return getPageAt(getNextPage());
     }
@@ -381,7 +385,7 @@
     }
 
     private View getViewInCurrentPage(ToIntFunction<ShortcutAndWidgetContainer> rankProvider) {
-        if (getChildCount() < 1) {
+        if (getChildCount() < 1 || getCurrentCellLayout() == null) {
             return null;
         }
         ShortcutAndWidgetContainer container = getCurrentCellLayout().getShortcutsAndWidgets();
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index a6e064a..c554def 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -136,6 +136,7 @@
         // Primary user ids
         long myProfileId = controller.getSerialNumberForUser(myUserHandle());
         long oldProfileId = getDefaultProfileId(db);
+        Log.d(TAG, "sanitizeDB: myProfileId=" + myProfileId + " oldProfileId=" + oldProfileId);
         LongSparseArray<Long> oldManagedProfileIds = getManagedProfileIds(db, oldProfileId);
         LongSparseArray<Long> profileMapping = new LongSparseArray<>(oldManagedProfileIds.size()
                 + 1);
@@ -148,6 +149,8 @@
             if (user != null) {
                 long newManagedProfileId = controller.getSerialNumberForUser(user);
                 profileMapping.put(oldManagedProfileId, newManagedProfileId);
+                Log.d(TAG, "sanitizeDB: managed profile id=" + oldManagedProfileId
+                        + " should be mapped to new id=" + newManagedProfileId);
             }
         }
 
diff --git a/src/com/android/launcher3/util/LogConfig.java b/src/com/android/launcher3/util/LogConfig.java
index 04f83b9..e5bbcb1 100644
--- a/src/com/android/launcher3/util/LogConfig.java
+++ b/src/com/android/launcher3/util/LogConfig.java
@@ -60,4 +60,9 @@
      * When turned on, we enable Gms Play related logging.
      */
     public static final String GMS_PLAY = "GmsPlay";
+
+    /**
+     * When turned on, we enable AGA related session summary logging.
+     */
+    public static final String AGA_SESSION_SUMMARY_LOG = "AGASessionSummaryLog";
 }
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 2b67cdd..bcad5de 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -154,6 +154,7 @@
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
     public static final String VIEW_AND_ACTIVITY_LEAKS = "b/260260325";
     public static final String WORK_TAB_MISSING = "b/243688989";
+    public static final String TWO_TASKBAR_LONG_CLICKS = "b/262282528";
 
     public static final String REQUEST_EMULATE_DISPLAY = "emulate-display";
     public static final String REQUEST_STOP_EMULATE_DISPLAY = "stop-emulate-display";
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index c83820b..026766c 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
 import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
+import static com.android.launcher3.util.TestUtil.installDummyAppForUser;
 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
@@ -39,6 +40,7 @@
 import com.android.launcher3.allapps.WorkProfileManager;
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.TestUtil;
 import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 
 import org.junit.After;
@@ -69,11 +71,11 @@
 
         String[] tokens = output.split("\\s+");
         mProfileUserId = Integer.parseInt(tokens[tokens.length - 1]);
-        output = mDevice.executeShellCommand("am start-user " + mProfileUserId);
         StringBuilder logStr = new StringBuilder().append("profileId: ").append(mProfileUserId);
         for (String str : tokens) {
             logStr.append(str).append("\n");
         }
+        installDummyAppForUser(mProfileUserId);
         updateWorkProfileSetupSuccessful("am start-user", output);
 
         Log.d(WORK_TAB_MISSING, "workProfileSuccessful? " + mWorkProfileSetupSuccessful +
@@ -101,6 +103,7 @@
             }
             launcher.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
         });
+        TestUtil.uninstallDummyApp();
         mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
     }
 
diff --git a/tests/src/com/android/launcher3/util/TestUtil.java b/tests/src/com/android/launcher3/util/TestUtil.java
index 4981795..f8cd995 100644
--- a/tests/src/com/android/launcher3/util/TestUtil.java
+++ b/tests/src/com/android/launcher3/util/TestUtil.java
@@ -58,8 +58,13 @@
 
 public class TestUtil {
     public static final String DUMMY_PACKAGE = "com.example.android.aardwolf";
+    public static final int DEFAULT_USER_ID = 0;
 
     public static void installDummyApp() throws IOException {
+        installDummyAppForUser(DEFAULT_USER_ID);
+    }
+
+    public static void installDummyAppForUser(int userId) throws IOException {
         // Copy apk from resources to a local file and install from there.
         final Resources resources = getContext().getResources();
         final InputStream in = resources.openRawResource(
@@ -80,7 +85,7 @@
             out.close();
 
             final String result = UiDevice.getInstance(getInstrumentation())
-                    .executeShellCommand("pm install " + apkFilename);
+                    .executeShellCommand("pm install --user " + userId + " " + apkFilename);
             Assert.assertTrue(
                     "Failed to install wellbeing test apk; make sure the device is rooted",
                     "Success".equals(result.replaceAll("\\s+", "")));
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 19e7b13..7ca6a06 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -2,7 +2,6 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
-import android.content.Context;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
 import android.util.Log;
@@ -54,27 +53,9 @@
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                boolean success = false;
                 try {
-                    mDevice.executeShellCommand("cmd statusbar tracing start");
                     FailureWatcher.super.apply(base, description).evaluate();
-                    success = true;
                 } finally {
-                    // Save artifact for Launcher Winscope trace.
-                    mDevice.executeShellCommand("cmd statusbar tracing stop");
-                    final Context nexusLauncherContext =
-                            getInstrumentation().getTargetContext()
-                                    .createPackageContext("com.google.android.apps.nexuslauncher",
-                                            0);
-                    final File launcherTrace =
-                            new File(nexusLauncherContext.getFilesDir(), "launcher_trace.pb");
-                    if (success) {
-                        mDevice.executeShellCommand("rm " + launcherTrace);
-                    } else {
-                        mDevice.executeShellCommand("mv " + launcherTrace + " "
-                                + diagFile(description, "LauncherWinscope", "pb"));
-                    }
-
                     // Detect touch events coming from physical screen.
                     if (mLauncher.hadNontestEvents()) {
                         throw new AssertionError(
diff --git a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
index e4713b2..0c65539 100644
--- a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
+++ b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
@@ -21,6 +21,7 @@
 import android.os.Bundle
 import androidx.test.core.app.ApplicationProvider
 import com.android.app.viewcapture.SimpleViewCapture
+import com.android.app.viewcapture.ViewCapture.MAIN_EXECUTOR
 import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter
 import org.junit.rules.TestRule
 import org.junit.runner.Description
@@ -69,7 +70,9 @@
                     // Clean up ViewCapture references here rather than in onActivityDestroyed so
                     // test code can access view hierarchy capture. onActivityDestroyed would delete
                     // view capture data before FailureWatcher could output it as a test artifact.
-                    windowListenerCloseables.onEach(SafeCloseable::close)
+                    // This is on the main thread to avoid a race condition where the onDrawListener
+                    // is removed while onDraw is running, resulting in an IllegalStateException.
+                    MAIN_EXECUTOR.execute { windowListenerCloseables.onEach(SafeCloseable::close) }
                 }
             }
         }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index afeb8d7..2c3c028 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -72,6 +72,44 @@
     }
 
     /**
+     * Flings backward (right) and waits the fling's end.
+     */
+    public void flingBackward() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            flingBackwardImpl();
+        }
+    }
+
+    private void flingBackwardImpl() {
+        try (LauncherInstrumentation.Closable c =
+                     mLauncher.addContextLayer("want to fling backward in overview")) {
+            LauncherInstrumentation.log("Overview.flingBackward before fling");
+            final UiObject2 overview = verifyActiveContainer();
+            final int rightMargin =
+                    mLauncher.getTargetInsets().right + mLauncher.getEdgeSensitivityWidth();
+            mLauncher.scroll(
+                    overview, Direction.RIGHT, new Rect(0, 0, rightMargin + 1, 0), 20, false);
+            try (LauncherInstrumentation.Closable c2 =
+                         mLauncher.addContextLayer("flung backwards")) {
+                verifyActiveContainer();
+                verifyActionsViewVisibility();
+            }
+        }
+    }
+
+    private OverviewTask flingToFirstTask() {
+        OverviewTask currentTask = getCurrentTask();
+
+        while (mLauncher.getRealDisplaySize().x - currentTask.getUiObject().getVisibleBounds().right
+                <= mLauncher.getOverviewPageSpacing()) {
+            flingBackwardImpl();
+            currentTask = getCurrentTask();
+        }
+
+        return currentTask;
+    }
+
+    /**
      * Dismissed all tasks by scrolling to Clear-all button and pressing it.
      */
     public void dismissAllTasks() {
@@ -94,23 +132,57 @@
     }
 
     /**
-     * Flings backward (right) and waits the fling's end.
+     * Touch to the right of current task. This should dismiss overview and go back to Workspace.
      */
-    public void flingBackward() {
+    public Workspace touchOutsideFirstTask() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
-             LauncherInstrumentation.Closable c =
-                     mLauncher.addContextLayer("want to fling backward in overview")) {
-            LauncherInstrumentation.log("Overview.flingBackward before fling");
-            final UiObject2 overview = verifyActiveContainer();
-            final int rightMargin =
-                    mLauncher.getTargetInsets().right + mLauncher.getEdgeSensitivityWidth();
-            mLauncher.scroll(
-                    overview, Direction.RIGHT, new Rect(0, 0, rightMargin + 1, 0), 20, false);
-            try (LauncherInstrumentation.Closable c2 =
-                         mLauncher.addContextLayer("flung backwards")) {
-                verifyActiveContainer();
-                verifyActionsViewVisibility();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "touching outside the focused task")) {
+
+            if (getTaskCount() < 2) {
+                throw new IllegalStateException(
+                        "Need to have at least 2 tasks");
             }
+
+            OverviewTask currentTask = flingToFirstTask();
+
+            mLauncher.touchOutsideContainer(currentTask.getUiObject(),
+                    /* tapRight= */ true,
+                    /* halfwayToEdge= */ false);
+
+            return new Workspace(mLauncher);
+        }
+    }
+
+    /**
+     * Touch between two tasks
+     */
+    public void touchBetweenTasks() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "touching outside the focused task")) {
+            if (getTaskCount() < 2) {
+                throw new IllegalStateException(
+                        "Need to have at least 2 tasks");
+            }
+
+            OverviewTask currentTask = flingToFirstTask();
+
+            mLauncher.touchOutsideContainer(currentTask.getUiObject(),
+                    /* tapRight= */ false,
+                    /* halfwayToEdge= */ false);
+        }
+    }
+
+    /**
+     * Touch either on the right or the left corner of the screen, 1 pixel from the bottom and
+     * from the sides.
+     */
+    public void touchTaskbarBottomCorner(boolean tapRight) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            Taskbar taskbar = new Taskbar(mLauncher);
+            taskbar.touchBottomCorner(tapRight);
+            verifyActiveContainer();
         }
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 80fded5..2adfc98 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -2014,21 +2014,41 @@
     }
 
     /**
-     * Taps outside container to dismiss.
+     * Taps outside container to dismiss, centered vertically and halfway to the edge of the screen.
      *
      * @param container container to be dismissed
      * @param tapRight  tap on the right of the container if true, or left otherwise
      */
     void touchOutsideContainer(UiObject2 container, boolean tapRight) {
+        touchOutsideContainer(container, tapRight, true);
+    }
+
+    /**
+     * Taps outside the container, to the right or left, and centered vertically.
+     *
+     * @param tapRight      if true touches to the right of the container, otherwise touches on left
+     * @param halfwayToEdge if true touches halfway to the screen edge, if false touches 1 px from
+     *                      container
+     */
+    void touchOutsideContainer(UiObject2 container, boolean tapRight, boolean halfwayToEdge) {
         try (LauncherInstrumentation.Closable c = addContextLayer(
                 "want to tap outside container on the " + (tapRight ? "right" : "left"))) {
             Rect containerBounds = getVisibleBounds(container);
+
+            int x;
+            if (halfwayToEdge) {
+                x = tapRight
+                        ? (containerBounds.right + getRealDisplaySize().x) / 2
+                        : containerBounds.left / 2;
+            } else {
+                x = tapRight
+                        ? containerBounds.right + 1
+                        : containerBounds.left - 1;
+            }
+            int y = containerBounds.top + containerBounds.height() / 2;
+
             final long downTime = SystemClock.uptimeMillis();
-            final Point tapTarget = new Point(
-                    tapRight
-                            ? (containerBounds.right + getRealDisplaySize().x) / 2
-                            : containerBounds.left / 2,
-                    containerBounds.top + 1);
+            final Point tapTarget = new Point(x, y);
             sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, tapTarget,
                     LauncherInstrumentation.GestureScope.INSIDE);
             sendPointer(downTime, downTime, MotionEvent.ACTION_UP, tapTarget,
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 90f3d13..39b93b4 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -71,6 +71,10 @@
         return mTask.getVisibleBounds().exactCenterX();
     }
 
+    UiObject2 getUiObject() {
+        return mTask;
+    }
+
     /**
      * Dismisses the task by swiping up.
      */
diff --git a/tests/tapl/com/android/launcher3/tapl/Taskbar.java b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
index 6ca7f4b..051630e 100644
--- a/tests/tapl/com/android/launcher3/tapl/Taskbar.java
+++ b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING;
 
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.view.MotionEvent;
@@ -122,4 +123,33 @@
         // Look for an icon with no text
         return By.clazz(TextView.class).text("");
     }
+
+    private Rect getVisibleBounds() {
+        return mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID).getVisibleBounds();
+    }
+
+    /**
+     * Touch either on the right or the left corner of the screen, 1 pixel from the bottom and
+     * from the sides.
+     */
+    void touchBottomCorner(boolean tapRight) {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to tap bottom corner on the " + (tapRight ? "right" : "left"))) {
+            final long downTime = SystemClock.uptimeMillis();
+            final Point tapTarget = new Point(
+                    tapRight
+                            ?
+                            getVisibleBounds().right
+                                    - mLauncher.getTargetInsets().right
+                                    - 1
+                            : getVisibleBounds().left
+                                    + 1,
+                    mLauncher.getRealDisplaySize().y - 1);
+
+            mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, tapTarget,
+                    LauncherInstrumentation.GestureScope.INSIDE);
+            mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP, tapTarget,
+                    LauncherInstrumentation.GestureScope.INSIDE);
+        }
+    }
 }