Merge "[Predictive Back] Fix bug where closing task bar's all apps will show scale animation even if predicitve back is disabled" into tm-qpr-dev
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index a645e58..cf58198 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -221,6 +221,12 @@
                 return response;
             }
 
+            case TestProtocol.REQUEST_USE_TAPL_WORKSPACE_LAYOUT: {
+                useTestWorkspaceLayout(
+                        LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TAPL);
+                return response;
+            }
+
             case TestProtocol.REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT: {
                 useTestWorkspaceLayout(null);
                 return response;
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index 02460e4..b1064f7 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -128,6 +128,11 @@
 
   // Bit encoded value to capture pinned and predicted taskbar positions.
   optional int32 cardinality = 2;
+
+  // Container where taskbar was invoked.
+  oneof ParentContainer {
+    TaskSwitcherContainer task_switcher_container = 3;
+  }
 }
 
 // Next value 44
diff --git a/quickstep/res/drawable/ic_desktop.xml b/quickstep/res/drawable/ic_desktop.xml
index dfaf8b8..8de275d 100644
--- a/quickstep/res/drawable/ic_desktop.xml
+++ b/quickstep/res/drawable/ic_desktop.xml
@@ -25,7 +25,7 @@
         android:translateX="6.0"
         android:translateY="6.0">
         <path
-            android:fillColor="?android:attr/textColorPrimary"
+            android:fillColor="@android:color/black"
             android:pathData="M5.958,37.708Q4.458,37.708 3.354,36.604Q2.25,35.5 2.25,34V18.292Q2.25,16.792 3.354,15.688Q4.458,14.583 5.958,14.583H9.5V5.958Q9.5,4.458 10.625,3.354Q11.75,2.25 13.208,2.25H34Q35.542,2.25 36.646,3.354Q37.75,4.458 37.75,5.958V21.667Q37.75,23.167 36.646,24.271Q35.542,25.375 34,25.375H30.5V34Q30.5,35.5 29.396,36.604Q28.292,37.708 26.792,37.708ZM5.958,34H26.792Q26.792,34 26.792,34Q26.792,34 26.792,34V21.542H5.958V34Q5.958,34 5.958,34Q5.958,34 5.958,34ZM30.5,21.667H34Q34,21.667 34,21.667Q34,21.667 34,21.667V9.208H13.208V14.583H26.833Q28.375,14.583 29.438,15.667Q30.5,16.75 30.5,18.25Z"/>
     </group>
 </vector>
diff --git a/quickstep/res/drawable/ic_empty_desktop.xml b/quickstep/res/drawable/ic_empty_desktop.xml
deleted file mode 100644
index cbf1856..0000000
--- a/quickstep/res/drawable/ic_empty_desktop.xml
+++ /dev/null
@@ -1,25 +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.
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="92dp"
-    android:height="80dp"
-    android:tint="?android:attr/textColorPrimary"
-    android:viewportHeight="80.0"
-    android:viewportWidth="92.0">
-    <path
-        android:fillColor="#AAFFFFFF"
-        android:pathData="M 14.365954,80 Q 10.981668,80 8.4908345,77.509166 6,75.018332 6,71.634046 V 36.193807 q 0,-3.384286 2.4908345,-5.87512 2.4908335,-2.493091 5.8751195,-2.493091 H 22.35738 V 8.365954 q 0,-3.3842855 2.538217,-5.8751198 Q 27.433811,0 30.723337,0 h 46.910711 q 3.479041,0 5.969878,2.4908342 2.490834,2.4908343 2.490834,5.8751198 v 35.442495 q 0,3.384286 -2.490834,5.87512 -2.490837,2.490835 -5.969878,2.490835 h -7.896671 v 19.459642 q 0,3.384286 -2.49083,5.87512 Q 64.755713,80 61.371423,80 Z m 0,-8.365954 h 47.005469 q 0,0 0,0 0,0 0,0 V 43.526426 H 14.365954 v 28.10762 q 0,0 0,0 0,0 0,0 z M 69.737377,43.808449 h 7.896671 q 0,0 0,0 0,0 0,0 V 15.698573 H 30.723337 v 12.127023 h 30.740592 q 3.479048,0 5.877376,2.445711 2.396072,2.443454 2.396072,5.82774 z" />
-</vector>
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
index f454835..2ec9d4c 100644
--- a/quickstep/res/layout/task_desktop.xml
+++ b/quickstep/res/layout/task_desktop.xml
@@ -32,19 +32,6 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
-    <TextView
-        android:id="@+id/empty_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:layout_marginTop="@dimen/overview_task_margin"
-        android:drawablePadding="@dimen/recents_empty_message_text_padding"
-        android:text="@string/recents_empty_desktop_message"
-        android:textColor="?android:textColorPrimary"
-        android:textSize="@dimen/recents_empty_message_text_size"
-        android:drawableTop="@drawable/ic_empty_desktop"
-        android:drawableTint="?android:attr/textColorPrimary" />
-
     <!--
          TODO(b249371338): DesktopTaskView extends from TaskView. TaskView expects TaskThumbnailView
          and IconView with these ids to be present. Need to refactor RecentsView to accept child
diff --git a/quickstep/res/values-sw600dp/config.xml b/quickstep/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..e1e442f
--- /dev/null
+++ b/quickstep/res/values-sw600dp/config.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<!-- Applies to large tablet screens portrait -->
+<resources>
+    <!-- Taskbar -->
+    <!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
+    <bool name="start_align_taskbar">true</bool>
+</resources>
\ No newline at end of file
diff --git a/quickstep/res/values-sw720dp-land/config.xml b/quickstep/res/values-sw720dp-land/config.xml
new file mode 100644
index 0000000..bf0f9ad
--- /dev/null
+++ b/quickstep/res/values-sw720dp-land/config.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<!-- Applies to large tablet screens landscape -->
+<resources>
+    <!-- Taskbar -->
+    <!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
+    <bool name="start_align_taskbar">false</bool>
+</resources>
\ No newline at end of file
diff --git a/quickstep/res/values-sw720dp/config.xml b/quickstep/res/values-sw720dp/config.xml
new file mode 100644
index 0000000..e1e442f
--- /dev/null
+++ b/quickstep/res/values-sw720dp/config.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<!-- Applies to large tablet screens portrait -->
+<resources>
+    <!-- Taskbar -->
+    <!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
+    <bool name="start_align_taskbar">true</bool>
+</resources>
\ No newline at end of file
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index d581582..b61bdfb 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -51,4 +51,8 @@
     </item>
 
     <string name="setup_wizard_pkg" translatable="false" />
+
+    <!-- Taskbar -->
+    <!-- Align the Taskbar to the start (Left/Right) of the device when 3 button nav is enabled. -->
+    <bool name="start_align_taskbar">false</bool>
 </resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 2b5975d..eb5fed2 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -30,9 +30,6 @@
     <!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
     <string name="recents_empty_message">No recent items</string>
 
-    <!-- Recents: The empty recents desktop tile string. [CHAR LIMIT=NONE] -->
-    <string name="recents_empty_desktop_message">No desktop items</string>
-
     <!-- Content description for the recent apps's accessibility option that opens its usage settings. [CHAR LIMIT=NONE] -->
     <string name="accessibility_app_usage_settings">App usage settings</string>
 
@@ -278,6 +275,10 @@
     <string name="taskbar_button_quick_settings">Quick Settings</string>
     <!-- Accessibility title for the Taskbar window. [CHAR_LIMIT=NONE] -->
     <string name="taskbar_a11y_title">Taskbar</string>
+    <!-- Accessibility title for the Taskbar window appeared. [CHAR_LIMIT=NONE] -->
+    <string name="taskbar_a11y_shown_title">Taskbar shown</string>
+    <!-- Accessibility title for the Taskbar window being close. [CHAR_LIMIT=NONE] -->
+    <string name="taskbar_a11y_hidden_title">Taskbar hidden</string>
     <!-- Accessibility title for the Taskbar window on phones. [CHAR_LIMIT=NONE] -->
     <string name="taskbar_phone_a11y_title">Navigation bar</string>
 
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 5ddf2a8..ea7eba3 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -634,7 +634,10 @@
         boolean appTargetsAreTranslucent = areAllTargetsTranslucent(appTargets);
 
         RectF launcherIconBounds = new RectF();
-        FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v,
+        FloatingIconView floatingView = getFloatingIconView(mLauncher, v,
+                mLauncher.getTaskbarUIController() == null
+                        ? null
+                        : mLauncher.getTaskbarUIController().findMatchingView(v),
                 !appTargetsAreTranslucent, launcherIconBounds, true /* isOpening */);
         Rect crop = new Rect();
         Matrix matrix = new Matrix();
@@ -1350,6 +1353,9 @@
                     isTransluscent, fallbackBackgroundColor);
         } else if (launcherView != null) {
             floatingIconView = getFloatingIconView(mLauncher, launcherView,
+                    mLauncher.getTaskbarUIController() == null
+                            ? null
+                            : mLauncher.getTaskbarUIController().findMatchingView(launcherView),
                     true /* hideOriginal */, targetRect, false /* isOpening */);
         } else {
             targetRect.set(getDefaultWindowTargetRect());
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 793c68e..95fea3e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -341,6 +341,11 @@
     }
 
     @Override
+    protected boolean isInOverview() {
+        return mTaskbarLauncherStateController.isInOverview();
+    }
+
+    @Override
     public RecentsView getRecentsView() {
         return mLauncher.getOverviewPanel();
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index bafd5b4..84bf02e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -288,14 +288,9 @@
             updateButtonLayoutSpacing();
             updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneButtonNavMode(mContext));
 
-            // Animate taskbar background when either..
-            // notification shade expanded AND not on keyguard
-            // back is visible for bouncer
             mPropertyHolders.add(new StatePropertyHolder(
                     mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
-                    flags -> ((flags & FLAG_NOTIFICATION_SHADE_EXPANDED) != 0
-                                && (flags & FLAG_KEYGUARD_VISIBLE) == 0)
-                            || (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
+                    flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
 
             // Rotation button
             RotationButton rotationButton = new RotationButtonImpl(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 2864ac7..57e11de 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -432,11 +432,16 @@
         }
         LauncherAtom.ContainerInfo oldContainer = itemInfoBuilder.getContainerInfo();
 
+        LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
+                LauncherAtom.TaskBarContainer.newBuilder();
+        if (mControllers.uiController.isInOverview()) {
+            taskbarBuilder.setTaskSwitcherContainer(
+                    LauncherAtom.TaskSwitcherContainer.newBuilder());
+        }
+
         if (oldContainer.hasPredictedHotseatContainer()) {
             LauncherAtom.PredictedHotseatContainer predictedHotseat =
                     oldContainer.getPredictedHotseatContainer();
-            LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
-                    LauncherAtom.TaskBarContainer.newBuilder();
 
             if (predictedHotseat.hasIndex()) {
                 taskbarBuilder.setIndex(predictedHotseat.getIndex());
@@ -449,8 +454,6 @@
                     .setTaskBarContainer(taskbarBuilder));
         } else if (oldContainer.hasHotseat()) {
             LauncherAtom.HotseatContainer hotseat = oldContainer.getHotseat();
-            LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
-                    LauncherAtom.TaskBarContainer.newBuilder();
 
             if (hotseat.hasIndex()) {
                 taskbarBuilder.setIndex(hotseat.getIndex());
@@ -462,8 +465,6 @@
             LauncherAtom.FolderContainer.Builder folderBuilder = oldContainer.getFolder()
                     .toBuilder();
             LauncherAtom.HotseatContainer hotseat = folderBuilder.getHotseat();
-            LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
-                    LauncherAtom.TaskBarContainer.newBuilder();
 
             if (hotseat.hasIndex()) {
                 taskbarBuilder.setIndex(hotseat.getIndex());
@@ -476,11 +477,11 @@
         } else if (oldContainer.hasAllAppsContainer()) {
             itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
                     .setAllAppsContainer(oldContainer.getAllAppsContainer().toBuilder()
-                            .setTaskbarContainer(LauncherAtom.TaskBarContainer.newBuilder())));
+                            .setTaskbarContainer(taskbarBuilder)));
         } else if (oldContainer.hasPredictionContainer()) {
             itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
                     .setPredictionContainer(oldContainer.getPredictionContainer().toBuilder()
-                            .setTaskbarContainer(LauncherAtom.TaskBarContainer.newBuilder())));
+                            .setTaskbarContainer(taskbarBuilder)));
         }
     }
 
@@ -588,10 +589,8 @@
         AnimatorSet anim = new AnimatorSet();
         anim.play(mControllers.taskbarViewController.getTaskbarIconAlpha().get(
                 TaskbarViewController.ALPHA_INDEX_NOTIFICATION_EXPANDED).animateToValue(alpha));
-        if (!isThreeButtonNav()) {
-            anim.play(mControllers.taskbarDragLayerController.getNotificationShadeBgTaskbar()
-                    .animateToValue(alpha));
-        }
+        anim.play(mControllers.taskbarDragLayerController.getNotificationShadeBgTaskbar()
+                .animateToValue(alpha));
         anim.start();
         if (skipAnim) {
             anim.end();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 5ac0570..4d163aa 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -406,6 +406,10 @@
         return mLauncherState != LauncherState.ALL_APPS;
     }
 
+    boolean isInOverview() {
+        return mLauncherState == LauncherState.OVERVIEW;
+    }
+
     private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
             boolean committed) {
         boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index babafd5..8c21778 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.taskbar;
 
 import static android.view.HapticFeedbackConstants.LONG_PRESS;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
 
 import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
@@ -43,6 +44,7 @@
 import android.view.InsetsController;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
@@ -189,6 +191,7 @@
     // Stashed handle properties.
     private MultiProperty mTaskbarStashedHandleAlpha;
     private AnimatedFloat mTaskbarStashedHandleHintScale;
+    private final AccessibilityManager mAccessibilityManager;
 
     /** Whether we are currently visually stashed (might change based on launcher state). */
     private boolean mIsStashed = false;
@@ -221,6 +224,7 @@
         mActivity = activity;
         mPrefs = LauncherPrefs.getPrefs(mActivity);
         mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
+        mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
         if (isPhoneMode()) {
             // DeviceProfile's taskbar vars aren't initialized w/ the flag off
             Resources resources = mActivity.getResources();
@@ -600,6 +604,7 @@
                 if (!mIsStashed) {
                     tryStartTaskbarTimeout();
                 }
+                mControllers.taskbarViewController.announceForAccessibility();
             }
         });
     }
@@ -722,8 +727,8 @@
                 skipInterpolator = FINAL_FRAME;
             }
         }
-        play(as, mControllers.taskbarViewController
-                .createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED);
+        mControllers.taskbarViewController.addRevealAnimToIsStashed(as, isStashed, duration,
+                EMPHASIZED);
 
         if (skipInterpolator != null) {
             as.setInterpolator(skipInterpolator);
@@ -1017,7 +1022,15 @@
         cancelTimeoutIfExists();
 
         mTimeoutAlarm.setOnAlarmListener(this::onTaskbarTimeout);
-        mTimeoutAlarm.setAlarm(NO_TOUCH_TIMEOUT_TO_STASH_MS);
+        mTimeoutAlarm.setAlarm(getTaskbarAutoHideTimeout());
+    }
+
+    /**
+     * returns appropriate timeout for taskbar to stash depending on accessibility being on/off.
+     */
+    private long getTaskbarAutoHideTimeout() {
+        return mAccessibilityManager.getRecommendedTimeoutMillis((int) NO_TOUCH_TIMEOUT_TO_STASH_MS,
+                FLAG_CONTENT_CONTROLS);
     }
 
     private void onTaskbarTimeout(Alarm alarm) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index b552e9b..4c6d3fa 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -17,6 +17,8 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
 
 import android.content.Intent;
@@ -166,6 +168,11 @@
         return true;
     }
 
+    /** Returns {@code true} if Taskbar is currently within overview. */
+    protected boolean isInOverview() {
+        return false;
+    }
+
     @CallSuper
     protected void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(String.format(
@@ -277,4 +284,30 @@
      * No-op if the view is not yet open.
      */
     public void launchSplitTasks(@NonNull View taskview, @NonNull GroupTask groupTask) { }
+
+    /**
+     * Returns the matching view (if any) in the taskbar.
+     * @param view The view to match.
+     */
+    public @Nullable View findMatchingView(View view) {
+        if (!(view.getTag() instanceof ItemInfo)) {
+            return null;
+        }
+        ItemInfo info = (ItemInfo) view.getTag();
+        if (info.container != CONTAINER_HOTSEAT && info.container != CONTAINER_HOTSEAT_PREDICTION) {
+            return null;
+        }
+
+        // Taskbar has the same items as the hotseat and we can use screenId to find the match.
+        int screenId = info.screenId;
+        View[] views = mControllers.taskbarViewController.getIconViews();
+        for (int i = views.length - 1; i >= 0; --i) {
+            if (views[i] != null
+                    && views[i].getTag() instanceof ItemInfo
+                    && ((ItemInfo) views[i].getTag()).screenId == screenId) {
+                return views[i];
+            }
+        }
+        return null;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 3d5089f..e66856a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -16,15 +16,18 @@
 package com.android.launcher3.taskbar;
 
 import static android.content.pm.PackageManager.FEATURE_PC;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 
 import androidx.annotation.LayoutRes;
@@ -91,6 +94,8 @@
 
     private float mTransientTaskbarAllAppsButtonTranslationXOffset;
 
+    private final boolean mStartAlignTaskbar;
+
     public TaskbarView(@NonNull Context context) {
         this(context, null);
     }
@@ -118,6 +123,8 @@
                 resources.getDimension(isTransientTaskbar
                         ? R.dimen.transient_taskbar_all_apps_button_translation_x_offset
                         : R.dimen.taskbar_all_apps_button_translation_x_offset);
+        mStartAlignTaskbar = mActivityContext.isThreeButtonNav()
+                && resources.getBoolean(R.bool.start_align_taskbar);
 
         int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
         int actualIconSize = mActivityContext.getDeviceProfile().iconSizePx;
@@ -153,7 +160,26 @@
 
         // TODO: Disable touch events on QSB otherwise it can crash.
         mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
+    }
 
+    @Override
+    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+        if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
+            announceForAccessibility(mContext.getString(R.string.taskbar_a11y_shown_title));
+        } else if (action == AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) {
+            announceForAccessibility(mContext.getString(R.string.taskbar_a11y_hidden_title));
+        }
+        return super.performAccessibilityActionInternal(action, arguments);
+
+    }
+
+    protected void announceAccessibilityChanges() {
+        this.performAccessibilityAction(
+                isVisibleToUser() ? AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
+                        : AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
+
+        ActivityContext.lookupContext(getContext()).getDragLayer()
+                .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
     }
 
     private int getColorWithGivenLuminance(int color, float luminance) {
@@ -163,6 +189,13 @@
         return ColorUtils.HSLToColor(colorHSL);
     }
 
+    /**
+     * Returns the icon touch size.
+     */
+    public int getIconTouchSize() {
+        return mIconTouchSize;
+    }
+
     private int calculateThemeIconsBackground() {
         int color = ThemedIconDrawable.getColors(mContext)[0];
         if (Utilities.isDarkTheme(mContext)) {
@@ -343,10 +376,22 @@
         boolean needMoreSpaceForNav = layoutRtl ?
                 navSpaceNeeded > (iconEnd - spaceNeeded) :
                 iconEnd > (right - navSpaceNeeded);
-        if (needMoreSpaceForNav) {
-            int offset = layoutRtl ?
-                    navSpaceNeeded - (iconEnd - spaceNeeded) :
-                    (right - navSpaceNeeded) - iconEnd;
+
+        if (mStartAlignTaskbar) {
+            // Taskbar is aligned to the start
+            int startSpacingPx = deviceProfile.inlineNavButtonsEndSpacingPx;
+
+            if (layoutRtl) {
+                iconEnd = right - startSpacingPx;
+            } else {
+                iconEnd = startSpacingPx + spaceNeeded;
+            }
+        } else if (needMoreSpaceForNav) {
+            // Add offset to account for nav bar when taskbar is centered
+            int offset = layoutRtl
+                    ? navSpaceNeeded - (iconEnd - spaceNeeded)
+                    : (right - navSpaceNeeded) - iconEnd;
+
             iconEnd += offset;
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 3143f23..50dbcc7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -63,7 +63,6 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.HorizontalInsettableView;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.MultiPropertyFactory;
@@ -91,6 +90,9 @@
     public static final int ALPHA_INDEX_SMALL_SCREEN = 6;
     private static final int NUM_ALPHA_CHANNELS = 7;
 
+    // This allows the icons on the edge to stay within the taskbar background bounds.
+    private static final float ICON_REVEAL_X_DURATION_MULTIPLIER = 0.8f;
+
     private final TaskbarActivityContext mActivity;
     private final TaskbarView mTaskbarView;
     private final MultiValueAlpha mTaskbarIconAlpha;
@@ -189,6 +191,13 @@
         mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
     }
 
+    /**
+     * Announcement for Accessibility when Taskbar stashes/unstashes.
+     */
+    public void announceForAccessibility() {
+        mTaskbarView.announceAccessibilityChanges();
+    }
+
     public void onDestroy() {
         LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
         mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
@@ -304,73 +313,87 @@
     /**
      * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the icon shape
      * and size.
+     * @param as The AnimatorSet to add all animations to.
      * @param isStashed When true, the icon crops vertically to the size of the stashed handle.
      *                  When false, the reverse happens.
+     * @param duration The duration of the animation.
+     * @param interpolator The interpolator to use for all animations.
      */
-    public AnimatorSet createRevealAnimToIsStashed(boolean isStashed) {
-        AnimatorSet as = new AnimatorSet();
+    public void addRevealAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
+            Interpolator interpolator) {
+        AnimatorSet reveal = new AnimatorSet();
 
         Rect stashedBounds = new Rect();
         mControllers.stashedHandleViewController.getStashedHandleBounds(stashedBounds);
 
-        int numChildren = mTaskbarView.getChildCount();
+        boolean isQsbInline = mActivity.getDeviceProfile().isQsbInline;
+        int numIcons = mTaskbarView.getChildCount() - (isQsbInline ? 1 : 0);
         // We do not actually modify the width of the icons, but we will use this width to position
         // the children to overlay the nav handle.
-        float virtualChildWidth = stashedBounds.width() / (float) numChildren;
+        float virtualChildWidth = stashedBounds.width() / (float) numIcons;
 
-        for (int i = numChildren - 1; i >= 0; i--) {
+        // All children move the same y-amount since they will be cropped to the same centerY.
+        float croppedTransY = mTaskbarView.getIconTouchSize() - stashedBounds.height();
+
+        boolean isRtl = Utilities.isRtl(mTaskbarView.getResources());
+        for (int i = mTaskbarView.getChildCount() - 1; i >= 0; i--) {
             View child = mTaskbarView.getChildAt(i);
-
-            if (child == mTaskbarView.getQsb()) {
-                continue;
-            }
+            boolean isQsb = child == mTaskbarView.getQsb();
 
             // Crop the icons to/from the nav handle shape.
-            as.play(createRevealAnimForView(child, isStashed));
+            reveal.play(createRevealAnimForView(child, isStashed).setDuration(duration));
 
             // Translate the icons to/from their locations as the "nav handle."
             // We look at 'left' and 'right' values to ensure that the children stay within the
             // bounds of the stashed handle.
-            float iconLeft = child.getLeft();
-            float newLeft = stashedBounds.left + (virtualChildWidth * i);
+
+            // All of the taskbar icons will overlap the entirety of the stashed handle
+            // And the QSB, if inline, will overlap part of stashed handle as well.
+            int positionInHandle = (isQsbInline && !isQsb)
+                    ? i + (isRtl ? 1 : -1)
+                    : i;
+            float currentPosition = isQsb ? child.getX() : child.getLeft();
+            float newPosition = stashedBounds.left + (virtualChildWidth * positionInHandle);
             final float croppedTransX;
-            if (iconLeft > newLeft) {
-                float newRight = stashedBounds.right - (virtualChildWidth * (numChildren - 1 - i));
-                croppedTransX = -(child.getLeft() + child.getWidth() - newRight);
+            if (currentPosition > newPosition) {
+                float newRight = stashedBounds.right - (virtualChildWidth
+                        * (numIcons - 1 - positionInHandle));
+                croppedTransX = -(currentPosition + child.getWidth() - newRight);
             } else {
-                croppedTransX = newLeft - iconLeft;
+                croppedTransX = newPosition - currentPosition;
             }
 
-            float croppedTransY = child.getHeight() - stashedBounds.height();
+            long transXDuration = (long) (duration * ICON_REVEAL_X_DURATION_MULTIPLIER);
+            float[] transX = isStashed
+                    ? new float[] {croppedTransX}
+                    : new float[] {croppedTransX, 0};
+            float[] transY = isStashed
+                    ? new float[] {croppedTransY}
+                    : new float[] {croppedTransY, 0};
+
             if (child instanceof Reorderable) {
                 MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
 
-                as.play(ObjectAnimator.ofFloat(mtd.getTranslationX(INDEX_TASKBAR_REVEAL_ANIM),
-                        MULTI_PROPERTY_VALUE, isStashed
-                                ? new float[] {croppedTransX}
-                                : new float[] {croppedTransX, 0}));
-                as.play(ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM),
-                        MULTI_PROPERTY_VALUE, isStashed
-                                ? new float[] {croppedTransY}
-                                : new float[] {croppedTransY, 0}));
+                reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationX(INDEX_TASKBAR_REVEAL_ANIM),
+                        MULTI_PROPERTY_VALUE, transX)
+                        .setDuration(transXDuration));
+                reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM),
+                        MULTI_PROPERTY_VALUE, transY));
                 as.addListener(forEndCallback(() ->
                         mtd.setTranslation(INDEX_TASKBAR_REVEAL_ANIM, 0, 0)));
             } else {
-                as.play(ObjectAnimator.ofFloat(child,
-                        VIEW_TRANSLATE_X, isStashed
-                                ? new float[] {croppedTransX}
-                                : new float[] {croppedTransX, 0}));
-                as.play(ObjectAnimator.ofFloat(child,
-                        VIEW_TRANSLATE_Y, isStashed
-                                ? new float[] {croppedTransY}
-                                : new float[] {croppedTransY, 0}));
+                reveal.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, transX)
+                        .setDuration(transXDuration));
+                reveal.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_Y, transY));
                 as.addListener(forEndCallback(() -> {
                     child.setTranslationX(0);
                     child.setTranslationY(0);
                 }));
             }
         }
-        return as;
+
+        reveal.setInterpolator(interpolator);
+        as.play(reveal);
     }
 
     /**
@@ -431,7 +454,6 @@
 
         for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
             View child = mTaskbarView.getChildAt(i);
-            int positionInHotseat;
             boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonView();
             if (!mIsHotseatIconOnTopWhenAligned) {
                 // When going to home, the EMPHASIZED interpolator in TaskbarLauncherStateController
@@ -439,10 +461,17 @@
                 // to avoid icons disappearing rather than fading out visually.
                 setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0.8f, 1f));
             } else if ((isAllAppsButton && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get())) {
-                setter.setViewAlpha(child, 0,
-                        isToHome
-                                ? Interpolators.clampToProgress(LINEAR, 0f, 0.17f)
-                                : Interpolators.clampToProgress(LINEAR, 0.72f, 0.84f));
+                if (!isToHome
+                        && mIsHotseatIconOnTopWhenAligned
+                        && mControllers.taskbarStashController.isStashed()) {
+                    // Prevent All Apps icon from appearing when going from hotseat to nav handle.
+                    setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0f, 0f));
+                } else {
+                    setter.setViewAlpha(child, 0,
+                            isToHome
+                                    ? Interpolators.clampToProgress(LINEAR, 0f, 0.17f)
+                                    : Interpolators.clampToProgress(LINEAR, 0.72f, 0.84f));
+                }
             }
 
             if (child == mTaskbarView.getQsb()) {
@@ -454,32 +483,36 @@
                 float childCenter = (child.getLeft() + child.getRight()) / 2f;
                 float halfQsbIconWidthDiff =
                         (launcherDp.hotseatQsbWidth - taskbarDp.iconSizePx) / 2f;
-                setter.addFloat(child, VIEW_TRANSLATE_X,
-                        isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff,
-                        hotseatIconCenter - childCenter, interpolator);
-
                 float scale = ((float) taskbarDp.iconSizePx) / launcherDp.hotseatQsbVisualHeight;
                 setter.addFloat(child, SCALE_PROPERTY, scale, 1f, interpolator);
 
-                setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
+                float fromX = isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff;
+                float toX = hotseatIconCenter - childCenter;
+                if (child instanceof Reorderable) {
+                    MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
+
+                    setter.addFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
+                            MULTI_PROPERTY_VALUE, fromX, toX, interpolator);
+                    setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM),
+                            MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator);
+                } else {
+                    setter.addFloat(child, VIEW_TRANSLATE_X, fromX, toX, interpolator);
+                    setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
+                }
 
                 if (mIsHotseatIconOnTopWhenAligned) {
                     setter.addFloat(child, VIEW_ALPHA, 0f, 1f,
                             isToHome
                                     ? Interpolators.clampToProgress(LINEAR, 0f, 0.35f)
-                                    : Interpolators.clampToProgress(LINEAR, 0.84f, 1f));
+                                    : mActivity.getDeviceProfile().isQsbInline
+                                            ? Interpolators.clampToProgress(LINEAR, 0f, 1f)
+                                            : Interpolators.clampToProgress(LINEAR, 0.84f, 1f));
                 }
                 setter.addOnFrameListener(animator -> AlphaUpdateListener.updateVisibility(child));
-
-                float qsbInsetFraction = halfQsbIconWidthDiff / launcherDp.hotseatQsbWidth;
-                if (child instanceof HorizontalInsettableView) {
-                    setter.addFloat((HorizontalInsettableView) child,
-                            HorizontalInsettableView.HORIZONTAL_INSETS, qsbInsetFraction, 0,
-                            interpolator);
-                }
                 continue;
             }
 
+            int positionInHotseat;
             if (isAllAppsButton) {
                 // Note that there is no All Apps button in the hotseat, this position is only used
                 // as its convenient for animation purposes.
@@ -496,18 +529,17 @@
             float hotseatIconCenter = hotseatPadding.left
                     + (hotseatCellSize + borderSpacing) * positionInHotseat
                     + hotseatCellSize / 2f;
-
             float childCenter = (child.getLeft() + child.getRight()) / 2f;
+            float toX = hotseatIconCenter - childCenter;
             if (child instanceof Reorderable) {
                 MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
 
                 setter.setFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
-                        MULTI_PROPERTY_VALUE, hotseatIconCenter - childCenter, interpolator);
+                        MULTI_PROPERTY_VALUE, toX, interpolator);
                 setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM),
                         MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator);
             } else {
-                setter.setFloat(child, VIEW_TRANSLATE_X,
-                        hotseatIconCenter - childCenter, interpolator);
+                setter.setFloat(child, VIEW_TRANSLATE_X, toX, interpolator);
                 setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
             }
             setter.setFloat(child, SCALE_PROPERTY, scaleUp, interpolator);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index a53f08a..2a46e08 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -20,11 +20,10 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
 
-import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
-import static com.android.launcher3.LauncherSettings.Animation.VIEW_BACKGROUND;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
@@ -40,6 +39,9 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
 import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
+import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
+import static com.android.launcher3.popup.SystemShortcut.INSTALL;
+import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX;
 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.MINUS_ONE_PAGE_PROGRESS_INDEX;
 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.WIDGETS_PAGE_PROGRESS_INDEX;
@@ -80,7 +82,6 @@
 import android.view.HapticFeedbackConstants;
 import android.view.RemoteAnimationTarget;
 import android.view.View;
-import android.view.WindowManagerGlobal;
 import android.window.BackEvent;
 import android.window.OnBackAnimationCallback;
 import android.window.OnBackInvokedDispatcher;
@@ -171,6 +172,7 @@
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.unfold.RemoteUnfoldSharedComponent;
 import com.android.systemui.unfold.UnfoldSharedComponent;
@@ -186,8 +188,11 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
@@ -385,22 +390,30 @@
 
     @Override
     public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
-        Stream<SystemShortcut.Factory> base = Stream.of(WellbeingModel.SHORTCUT_FACTORY);
-        if (ENABLE_SPLIT_FROM_WORKSPACE.get() && mDeviceProfile.isTablet) {
-            RecentsView recentsView = getOverviewPanel();
-            // TODO(b/266482558): Pull it out of PagedOrentationHandler for split from workspace.
-            List<SplitPositionOption> positions =
-                    recentsView.getPagedOrientationHandler().getSplitPositionOptions(
-                            mDeviceProfile);
-            List<SystemShortcut.Factory<QuickstepLauncher>> splitShortcuts = new ArrayList<>();
-            for (SplitPositionOption position : positions) {
-                splitShortcuts.add(getSplitSelectShortcutByPosition(position));
-            }
-            base = Stream.concat(base, splitShortcuts.stream());
+        // Order matters as it affects order of appearance in popup container
+        List<SystemShortcut.Factory> shortcuts = new ArrayList(Arrays.asList(
+                APP_INFO, WellbeingModel.SHORTCUT_FACTORY, mHotseatPredictionController));
+        shortcuts.addAll(getSplitShortcuts());
+        shortcuts.add(WIDGETS);
+        shortcuts.add(INSTALL);
+        return shortcuts.stream();
+    }
+
+    private List<SystemShortcut.Factory<QuickstepLauncher>> getSplitShortcuts() {
+
+        if (!ENABLE_SPLIT_FROM_WORKSPACE.get() || !mDeviceProfile.isTablet) {
+            return Collections.emptyList();
         }
-        return Stream.concat(
-                Stream.of(mHotseatPredictionController),
-                Stream.concat(base, super.getSupportedShortcuts()));
+        RecentsView recentsView = getOverviewPanel();
+        // TODO(b/266482558): Pull it out of PagedOrentationHandler for split from workspace.
+        List<SplitPositionOption> positions =
+                recentsView.getPagedOrientationHandler().getSplitPositionOptions(
+                        mDeviceProfile);
+        List<SystemShortcut.Factory<QuickstepLauncher>> splitShortcuts = new ArrayList<>();
+        for (SplitPositionOption position : positions) {
+            splitShortcuts.add(getSplitSelectShortcutByPosition(position));
+        }
+        return splitShortcuts;
     }
 
     /**
@@ -408,15 +421,18 @@
      */
     private void onStateOrResumeChanging(boolean inTransition) {
         LauncherState state = getStateManager().getState();
-        if (!ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
-            boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
-            if (started) {
-                DeviceProfile profile = getDeviceProfile();
-                boolean willUserBeActive =
-                        (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
-                boolean visible = (state == NORMAL || state == OVERVIEW)
-                        && (willUserBeActive || isUserActive())
-                        && !profile.isVerticalBarLayout();
+        boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
+        if (started) {
+            DeviceProfile profile = getDeviceProfile();
+            boolean willUserBeActive =
+                    (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
+            boolean visible = (state == NORMAL || state == OVERVIEW)
+                    && (willUserBeActive || isUserActive())
+                    && !profile.isVerticalBarLayout();
+            if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM)  {
+                SystemUiProxy.INSTANCE.get(this)
+                        .setLauncherKeepClearAreaHeight(visible, profile.hotseatBarSizePx);
+            } else {
                 SystemUiProxy.INSTANCE.get(this).setShelfHeight(visible, profile.hotseatBarSizePx);
             }
         }
@@ -1054,8 +1070,7 @@
             activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
                     mLastTouchUpTime);
         }
-        if (item != null && (item.animationType == DEFAULT_NO_ICON
-                || item.animationType == VIEW_BACKGROUND)) {
+        if (item != null && item.itemType == ITEM_TYPE_SEARCH_ACTION) {
             activityOptions.options.setSplashScreenStyle(
                     SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR);
         } else {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
index 177a399..d4944d0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
@@ -24,8 +24,6 @@
 
     public final boolean defaultValue;
 
-    boolean mHasBeenChangedAtLeastOnce;
-
     public DebugFlag(String key, String description, boolean defaultValue, boolean currentValue) {
         super(currentValue);
         this.key = key;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
index 4ca7e31..b7fb2ed 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
@@ -35,6 +35,9 @@
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
 
+import java.util.List;
+import java.util.Set;
+
 /**
  * Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
  */
@@ -50,31 +53,15 @@
 
         @Override
         public void putBoolean(String key, boolean value) {
-            for (DebugFlag flag : FlagsFactory.getDebugFlags()) {
-                if (flag.key.equals(key)) {
-                    SharedPreferences prefs = mContext.getSharedPreferences(
-                            FLAGS_PREF_NAME, Context.MODE_PRIVATE);
-                    SharedPreferences.Editor editor = prefs.edit();
-                    // We keep the key in the prefs even if it has the default value, because it's a
-                    // signal that it has been changed at one point.
-                    if (!prefs.contains(key) && value == flag.defaultValue) {
-                        editor.remove(key).apply();
-                        flag.mHasBeenChangedAtLeastOnce = false;
-                    } else {
-                        editor.putBoolean(key, value).apply();
-                        flag.mHasBeenChangedAtLeastOnce = true;
-                    }
-                    updateMenu();
-                }
-            }
+            mSharedPreferences.edit().putBoolean(key, value).apply();
+            updateMenu();
         }
 
         @Override
         public boolean getBoolean(String key, boolean defaultValue) {
             for (DebugFlag flag : FlagsFactory.getDebugFlags()) {
                 if (flag.key.equals(key)) {
-                    return mContext.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE)
-                            .getBoolean(key, flag.defaultValue);
+                    return mSharedPreferences.getBoolean(key, flag.defaultValue);
                 }
             }
             return defaultValue;
@@ -89,11 +76,22 @@
     }
 
     public void applyTo(PreferenceGroup parent) {
+        Set<String> modifiedPrefs = mSharedPreferences.getAll().keySet();
+        List<DebugFlag> flags = FlagsFactory.getDebugFlags();
+        flags.sort((f1, f2) -> {
+            // Sort first by any prefs that the user has changed, then alphabetically.
+            int changeComparison = Boolean.compare(
+                    modifiedPrefs.contains(f2.key), modifiedPrefs.contains(f1.key));
+            return changeComparison != 0
+                    ? changeComparison
+                    : f1.key.compareToIgnoreCase(f2.key);
+        });
+
         // For flag overrides we only want to store when the engineer chose to override the
         // flag with a different value than the default. That way, when we flip flags in
         // future, engineers will pick up the new value immediately. To accomplish this, we use a
         // custom preference data store.
-        for (DebugFlag flag : FlagsFactory.getDebugFlags()) {
+        for (DebugFlag flag : flags) {
             SwitchPreference switchPreference = new SwitchPreference(mContext);
             switchPreference.setKey(flag.key);
             switchPreference.setDefaultValue(flag.defaultValue);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
index 84b873d..888cc9d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -53,7 +53,6 @@
 
     private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
 
-
     private final Set<String> mKeySet = new HashSet<>();
     private boolean mRestartRequested = false;
 
@@ -75,7 +74,6 @@
                     .getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
             boolean currentValue = prefs.getBoolean(key, defaultValue);
             DebugFlag flag = new DebugFlag(key, description, defaultValue, currentValue);
-            flag.mHasBeenChangedAtLeastOnce = prefs.contains(key);
             sDebugFlags.add(flag);
             return flag;
         } else {
@@ -96,7 +94,6 @@
             boolean currentValue = prefs.getBoolean(key, defaultValue);
             DebugFlag flag = new DeviceFlag(key, description, defaultValue, currentValue,
                     defaultValueInCode);
-            flag.mHasBeenChangedAtLeastOnce = prefs.contains(key);
             sDebugFlags.add(flag);
             return flag;
         } else {
@@ -117,19 +114,9 @@
         if (!Utilities.IS_DEBUG_DEVICE) {
             return Collections.emptyList();
         }
-        List<DebugFlag> flags;
         synchronized (sDebugFlags) {
-            flags = new ArrayList<>(sDebugFlags);
+            return new ArrayList<>(sDebugFlags);
         }
-        flags.sort((f1, f2) -> {
-            // Sort first by any prefs that the user has changed, then alphabetically.
-            int changeComparison = Boolean.compare(
-                    f2.mHasBeenChangedAtLeastOnce, f1.mHasBeenChangedAtLeastOnce);
-            return changeComparison != 0
-                    ? changeComparison
-                    : f1.key.compareToIgnoreCase(f2.key);
-        });
-        return flags;
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index df95dc1..b7bafd8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -191,7 +191,7 @@
             // need to manually set the duration to a reasonable value.
             animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher, true /* isToState */));
         }
-        if (FeatureFlags.ENABLE_HAPTICS_ALL_APPS.get() &&
+        if (FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() &&
                 ((mFromState == NORMAL && mToState == ALL_APPS)
                         || (mFromState == ALL_APPS && mToState == NORMAL)) && isFling) {
             mVibratorWrapper.vibrateForDragBump();
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index bb781c8..3151374 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -105,6 +105,9 @@
     private HomeAnimationFactory createIconHomeAnimationFactory(View workspaceView) {
         RectF iconLocation = new RectF();
         FloatingIconView floatingIconView = getFloatingIconView(mActivity, workspaceView,
+                mActivity.getTaskbarUIController() == null
+                        ? null
+                        : mActivity.getTaskbarUIController().findMatchingView(workspaceView),
                 true /* hideOriginal */, iconLocation, false /* isOpening */);
 
         // We want the window alpha to be 0 once this threshold is met, so that the
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 86b02aa..7c09805 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -87,6 +87,7 @@
             new MainThreadInitializedObject<>(SystemUiProxy::new);
 
     private static final int MSG_SET_SHELF_HEIGHT = 1;
+    private static final int MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT = 2;
 
     private ISystemUiProxy mSystemUiProxy;
     private IPip mPip;
@@ -121,6 +122,10 @@
     private int mLastShelfHeight;
     private boolean mLastShelfVisible;
 
+    // Used to dedupe calls to SystemUI
+    private int mLastLauncherKeepClearAreaHeight;
+    private boolean mLastLauncherKeepClearAreaHeightVisible;
+
     private final Context mContext;
     private final Handler mAsyncHandler;
 
@@ -454,6 +459,33 @@
     }
 
     /**
+     * Sets the height of the keep clear area that is going to be reported by
+     * the Launcher for the Hotseat.
+     */
+    public void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+        Message.obtain(mAsyncHandler, MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT,
+                visible ? 1 : 0 , height).sendToTarget();
+    }
+
+    @WorkerThread
+    private void setLauncherKeepClearAreaHeight(int visibleInt, int height) {
+        boolean visible = visibleInt != 0;
+        boolean changed = visible != mLastLauncherKeepClearAreaHeightVisible
+                || height != mLastLauncherKeepClearAreaHeight;
+        IPip pip = mPip;
+        if (pip != null && changed) {
+            mLastLauncherKeepClearAreaHeightVisible = visible;
+            mLastLauncherKeepClearAreaHeight = height;
+            try {
+                pip.setLauncherKeepClearAreaHeight(visible, height);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call setLauncherKeepClearAreaHeight visible: " + visible
+                        + " height: " + height, e);
+            }
+        }
+    }
+
+    /**
      * Sets listener to get pip animation callbacks.
      */
     public void setPipAnimationListener(IPipAnimationListener listener) {
@@ -945,6 +977,9 @@
             case MSG_SET_SHELF_HEIGHT:
                 setShelfHeightAsync(msg.arg1, msg.arg2);
                 return true;
+            case MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT:
+                setLauncherKeepClearAreaHeight(msg.arg1, msg.arg2);
+                return true;
         }
 
         return false;
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 6d8ee10..6f502d0 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -302,7 +302,10 @@
 
         @Nullable
         public String getPackageName() {
-            return mTopTask == null ? null : mTopTask.baseActivity.getPackageName();
+            if (mTopTask == null || mTopTask.baseActivity == null) {
+                return null;
+            }
+            return mTopTask.baseActivity.getPackageName();
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java
index 433d23f..b3f5d82 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.java
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.java
@@ -16,6 +16,8 @@
 
 package com.android.quickstep.util;
 
+import androidx.annotation.NonNull;
+
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
 
@@ -27,9 +29,10 @@
  */
 public class DesktopTask extends GroupTask {
 
-    public ArrayList<Task> tasks;
+    @NonNull
+    public final ArrayList<Task> tasks;
 
-    public DesktopTask(ArrayList<Task> tasks) {
+    public DesktopTask(@NonNull ArrayList<Task> tasks) {
         super(tasks.get(0), null, null, TaskView.Type.DESKTOP);
         this.tasks = tasks;
     }
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
new file mode 100644
index 0000000..6dd67de
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -0,0 +1,163 @@
+/*
+ *  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.
+ *
+ */
+
+package com.android.quickstep.util
+
+import android.animation.ObjectAnimator
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.view.View
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
+import com.android.quickstep.views.TaskThumbnailView
+import com.android.quickstep.views.TaskView
+import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
+import java.util.function.Supplier
+
+/**
+ * Utils class to help run animations for initiating split screen from launcher.
+ * Will be expanded with future refactors. Works in conjunction with the state stored in
+ * [SplitSelectStateController]
+ */
+class SplitAnimationController(val splitSelectStateController: SplitSelectStateController) {
+    companion object {
+        // Break this out into maybe enums? Abstractions into its own classes? Tbd.
+        data class SplitAnimInitProps(
+                val originalView: View,
+                val originalBitmap: Bitmap?,
+                val iconDrawable: Drawable,
+                val fadeWithThumbnail: Boolean,
+                val isStagedTask: Boolean,
+                val iconView: View?
+        )
+    }
+
+    /**
+     * Returns different elements to animate for the initial split selection animation
+     * depending on the state of the surface from which the split was initiated
+     */
+    fun getFirstAnimInitViews(taskViewSupplier: Supplier<TaskView>,
+                              splitSelectSourceSupplier: Supplier<SplitSelectSource>)
+            : SplitAnimInitProps {
+        if (!splitSelectStateController.isAnimateCurrentTaskDismissal) {
+            // Initiating from home
+            val splitSelectSource = splitSelectSourceSupplier.get()
+            return SplitAnimInitProps(splitSelectSource.view, originalBitmap = null,
+                    splitSelectSource.drawable, fadeWithThumbnail = false, isStagedTask = true,
+                    iconView = null)
+        } else if (splitSelectStateController.isDismissingFromSplitPair) {
+            // Initiating split from overview, but on a split pair
+            val taskView = taskViewSupplier.get()
+            for (container : TaskIdAttributeContainer in taskView.taskIdAttributeContainers) {
+                if (container.task.key.id == splitSelectStateController.initialTaskId) {
+                    return SplitAnimInitProps(container.thumbnailView,
+                            container.thumbnailView.thumbnail, container.iconView.drawable!!,
+                            fadeWithThumbnail = true, isStagedTask = true,
+                            iconView = container.iconView
+                    )
+                }
+            }
+            throw IllegalStateException("Attempting to init split from existing split pair " +
+                    "without a valid taskIdAttributeContainer")
+        } else {
+            // Initiating split from overview on fullscreen task TaskView
+            val taskView = taskViewSupplier.get()
+            return SplitAnimInitProps(taskView.thumbnail, taskView.thumbnail.thumbnail,
+                    taskView.iconView.drawable!!, fadeWithThumbnail = true, isStagedTask = true,
+                    taskView.iconView
+            )
+        }
+    }
+
+    /**
+     * When selecting first app from split pair, second app's thumbnail remains. This animates
+     * the second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying
+     * it with [TaskThumbnailView]'s splashView. Adds animations to the provided builder.
+     * Note: The app that **was not** selected as the first split app should be the container that's
+     * passed through.
+     *
+     * @param builder Adds animation to this
+     * @param taskIdAttributeContainer container of the app that **was not** selected
+     * @param isPrimaryTaskSplitting if true, task that was split would be top/left in the pair
+     *                               (opposite of that representing [taskIdAttributeContainer])
+     */
+    fun addInitialSplitFromPair(taskIdAttributeContainer: TaskIdAttributeContainer,
+                                builder: PendingAnimation, deviceProfile: DeviceProfile,
+                                taskViewWidth: Int, taskViewHeight: Int,
+                                isPrimaryTaskSplitting: Boolean) {
+        val thumbnail = taskIdAttributeContainer.thumbnailView
+        val iconView: View = taskIdAttributeContainer.iconView
+        builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLASH_ALPHA, 1f))
+        thumbnail.setShowSplashForSplitSelection(true)
+        if (deviceProfile.isLandscape) {
+            // Center view first so scaling happens uniformly, alternatively we can move pivotX to 0
+            val centerThumbnailTranslationX: Float = (taskViewWidth - thumbnail.width) / 2f
+            val centerIconTranslationX: Float = (taskViewWidth - iconView.width) / 2f
+            val finalScaleX: Float = taskViewWidth.toFloat() / thumbnail.width
+            builder.add(ObjectAnimator.ofFloat(thumbnail,
+                    TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, centerThumbnailTranslationX))
+            // icons are anchored from Gravity.END, so need to use negative translation
+            builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
+                    -centerIconTranslationX))
+            builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_X, finalScaleX))
+
+            // Reset other dimensions
+            // TODO(b/271468547), can't set Y translate to 0, need to account for top space
+            thumbnail.scaleY = 1f
+            val translateYResetVal: Float = if (!isPrimaryTaskSplitting) 0f else
+                deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
+            builder.add(ObjectAnimator.ofFloat(thumbnail,
+                    TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y,
+                    translateYResetVal))
+        } else {
+            val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
+            // Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
+            // primary thumbnail has layout margin above it, so secondary thumbnail needs to take
+            // that into account. We should migrate to only using translations otherwise this
+            // asymmetry causes problems..
+
+            // Icon defaults to center | horizontal, we add additional translation for split
+            val centerIconTranslationX = 0f
+            var centerThumbnailTranslationY: Float
+
+            // TODO(b/271468547), primary thumbnail has layout margin above it, so secondary
+            //  thumbnail needs to take that into account. We should migrate to only using
+            //  translations otherwise this asymmetry causes problems..
+            if (isPrimaryTaskSplitting) {
+                centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
+                centerThumbnailTranslationY += deviceProfile.overviewTaskThumbnailTopMarginPx
+                        .toFloat()
+            } else {
+                centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
+            }
+            val finalScaleY: Float = thumbnailSize.toFloat() / thumbnail.height
+            builder.add(ObjectAnimator.ofFloat(thumbnail,
+                    TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, centerThumbnailTranslationY))
+
+            // icons are anchored from Gravity.END, so need to use negative translation
+            builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
+                    centerIconTranslationX))
+            builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_Y, finalScaleY))
+
+            // Reset other dimensions
+            thumbnail.scaleX = 1f
+            builder.add(ObjectAnimator.ofFloat(thumbnail,
+                    TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f))
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 5214f7c..1f4085f 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -82,6 +82,7 @@
     private final Context mContext;
     private final Handler mHandler;
     private final RecentsModel mRecentTasksModel;
+    private final SplitAnimationController mSplitAnimationController;
     private StatsLogManager mStatsLogManager;
     private final SystemUiProxy mSystemUiProxy;
     private final StateManager mStateManager;
@@ -96,6 +97,11 @@
     private boolean mRecentsAnimationRunning;
     /** If {@code true}, animates the existing task view split placeholder view */
     private boolean mAnimateCurrentTaskDismissal;
+    /**
+     * Acts as a subset of {@link #mAnimateCurrentTaskDismissal}, we can't be dismissing from a
+     * split pair task view without wanting to animate current task dismissal overall
+     */
+    private boolean mDismissingFromSplitPair;
     @Nullable
     private UserHandle mUser;
     /** If not null, this is the TaskView we want to launch from */
@@ -116,6 +122,7 @@
         mStateManager = stateManager;
         mDepthController = depthController;
         mRecentTasksModel = recentsModel;
+        mSplitAnimationController = new SplitAnimationController(this);
     }
 
     /**
@@ -399,6 +406,18 @@
         mAnimateCurrentTaskDismissal = animateCurrentTaskDismissal;
     }
 
+    public boolean isDismissingFromSplitPair() {
+        return mDismissingFromSplitPair;
+    }
+
+    public void setDismissingFromSplitPair(boolean dismissingFromSplitPair) {
+        mDismissingFromSplitPair = dismissingFromSplitPair;
+    }
+
+    public SplitAnimationController getSplitAnimationController() {
+        return mSplitAnimationController;
+    }
+
     /**
      * Requires Shell Transitions
      */
@@ -506,6 +525,7 @@
         mItemInfo = null;
         mSplitEvent = null;
         mAnimateCurrentTaskDismissal = false;
+        mDismissingFromSplitPair = false;
     }
 
     /**
@@ -532,6 +552,10 @@
         return mInitialTaskId;
     }
 
+    public int getSecondTaskId() {
+        return mSecondTaskId;
+    }
+
     private boolean isSecondTaskIntentSet() {
         return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null);
     }
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 14898b1..ccc2df6 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -92,7 +92,6 @@
     private final ArrayList<CancellableTask<?>> mPendingThumbnailRequests = new ArrayList<>();
 
     private View mBackgroundView;
-    private View mEmptyView;
 
     public DesktopTaskView(Context context) {
         this(context, null);
@@ -111,7 +110,6 @@
         super.onFinishInflate();
 
         mBackgroundView = findViewById(R.id.background);
-        mEmptyView = findViewById(R.id.empty_view);
 
         int topMarginPx =
                 mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
@@ -187,8 +185,6 @@
             mSnapshotViewMap.put(task.key.id, snapshotView);
         }
 
-        mEmptyView.setVisibility(mTasks.isEmpty() ? View.VISIBLE : View.GONE);
-
         updateTaskIdContainer();
         updateTaskIdAttributeContainer();
 
@@ -500,7 +496,7 @@
     }
 
     @Override
-    void setThumbnailVisibility(int visibility) {
+    void setThumbnailVisibility(int visibility, int taskId) {
         for (int i = 0; i < mSnapshotViewMap.size(); i++) {
             mSnapshotViewMap.valueAt(i).setVisibility(visibility);
         }
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
index adea1a4..4ea7753 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
@@ -27,8 +27,10 @@
 import android.view.ViewOutlineProvider;
 import android.widget.RemoteViews.RemoteViewOutlineProvider;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.R;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.RoundedCornerEnforcement;
 
@@ -65,14 +67,20 @@
         setClipToOutline(true);
     }
 
-    void init(LauncherAppWidgetHostView hostView, View backgroundView, float finalRadius,
-            int fallbackBackgroundColor) {
+    void init(LauncherAppWidgetHostView hostView, @NonNull View backgroundView,
+            float finalRadius, int fallbackBackgroundColor) {
         mFinalRadius = finalRadius;
         mSourceView = backgroundView;
         mInitialOutlineRadius = getOutlineRadius(hostView, backgroundView);
         mIsUsingFallback = false;
         if (isSupportedDrawable(backgroundView.getForeground())) {
-            mOriginalForeground = backgroundView.getForeground();
+            if (backgroundView.getTag(R.id.saved_floating_widget_foreground) == null) {
+                mOriginalForeground = backgroundView.getForeground();
+                backgroundView.setTag(R.id.saved_floating_widget_foreground, mOriginalForeground);
+            } else {
+                mOriginalForeground = (Drawable) backgroundView.getTag(
+                        R.id.saved_floating_widget_foreground);
+            }
             mForegroundProperties.init(
                     mOriginalForeground.getConstantState().newDrawable().mutate());
             setForeground(mForegroundProperties.mDrawable);
@@ -82,7 +90,13 @@
             mSourceView.setForeground(clipPlaceholder);
         }
         if (isSupportedDrawable(backgroundView.getBackground())) {
-            mOriginalBackground = backgroundView.getBackground();
+            if (backgroundView.getTag(R.id.saved_floating_widget_background) == null) {
+                mOriginalBackground = backgroundView.getBackground();
+                backgroundView.setTag(R.id.saved_floating_widget_background, mOriginalBackground);
+            } else {
+                mOriginalBackground = (Drawable) backgroundView.getTag(
+                        R.id.saved_floating_widget_background);
+            }
             mBackgroundProperties.init(
                     mOriginalBackground.getConstantState().newDrawable().mutate());
             setBackground(mBackgroundProperties.mDrawable);
@@ -115,6 +129,10 @@
     }
 
     void recycle() {
+        if (mSourceView != null) {
+            mSourceView.setTag(R.id.saved_floating_widget_foreground, null);
+            mSourceView.setTag(R.id.saved_floating_widget_background, null);
+        }
         mSourceView = null;
         mOriginalForeground = null;
         mOriginalBackground = null;
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index e9498fd..0e05032 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -1,5 +1,7 @@
 package com.android.quickstep.views;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
 import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 
@@ -25,6 +27,7 @@
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -267,6 +270,19 @@
 
     @Override
     protected int getLastSelectedChildTaskIndex() {
+        SplitSelectStateController splitSelectController =
+                getRecentsView().getSplitSelectController();
+        if (splitSelectController.isDismissingFromSplitPair()) {
+            // return the container index of the task that wasn't initially selected to split with
+            // because that is the only remaining app that can be selected. The coordinate checks
+            // below aren't reliable since both of those views may be gone/transformed
+            int initSplitTaskId = getThisTaskCurrentlyInSplitSelection();
+            if (initSplitTaskId != INVALID_TASK_ID) {
+                return initSplitTaskId == mTask.key.id ? 1 : 0;
+            }
+        }
+
+        // Check which of the two apps was selected
         if (isCoordInView(mIconView2, mLastTouchDownPosition)
                 || isCoordInView(mSnapshotView2, mLastTouchDownPosition)) {
             return 1;
@@ -296,9 +312,30 @@
         if (mSplitBoundsConfig == null || mSnapshotView == null || mSnapshotView2 == null) {
             return;
         }
-        getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView,
-                mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
-                mActivity.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+        int initSplitTaskId = getThisTaskCurrentlyInSplitSelection();
+        if (initSplitTaskId == INVALID_TASK_ID) {
+            getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView,
+                    mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
+                    mActivity.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+            // Should we be having a separate translation step apart from the measuring above?
+            // The following only applies to large screen for now, but for future reference
+            // we'd want to abstract this out in PagedViewHandlers to get the primary/secondary
+            // translation directions
+            mSnapshotView.applySplitSelectTranslateX(mSnapshotView.getTranslationX());
+            mSnapshotView.applySplitSelectTranslateY(mSnapshotView.getTranslationY());
+            mSnapshotView2.applySplitSelectTranslateX(mSnapshotView2.getTranslationX());
+            mSnapshotView2.applySplitSelectTranslateY(mSnapshotView2.getTranslationY());
+        } else {
+            // Currently being split with this taskView, let the non-split selected thumbnail
+            // take up full thumbnail area
+            TaskIdAttributeContainer container =
+                    mTaskIdAttributeContainer[initSplitTaskId == mTask.key.id ? 1 : 0];
+            container.getThumbnailView().measure(widthMeasureSpec,
+                    View.MeasureSpec.makeMeasureSpec(
+                            heightSize -
+                                    mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx,
+                            MeasureSpec.EXACTLY));
+        }
         updateIconPlacement();
     }
 
@@ -379,21 +416,27 @@
         mSnapshotView2.refreshSplashView();
     }
 
+    @Override
+    protected void resetViewTransforms() {
+        super.resetViewTransforms();
+        mSnapshotView2.resetViewTransforms();
+    }
+
     /**
-     *     Sets visibility for thumbnails and associated elements (DWB banners).
-     *     IconView is unaffected.
+     * Sets visibility for thumbnails and associated elements (DWB banners).
+     * IconView is unaffected.
      *
-     *     When setting INVISIBLE, sets the visibility for the last selected child task.
-     *     When setting VISIBLE (as a reset), sets the visibility for both tasks.
+     * When setting INVISIBLE, sets the visibility for the last selected child task.
+     * When setting VISIBLE (as a reset), sets the visibility for both tasks.
      */
     @Override
-    void setThumbnailVisibility(int visibility) {
+    void setThumbnailVisibility(int visibility, int taskId) {
         if (visibility == VISIBLE) {
             mSnapshotView.setVisibility(visibility);
             mDigitalWellBeingToast.setBannerVisibility(visibility);
             mSnapshotView2.setVisibility(visibility);
             mDigitalWellBeingToast2.setBannerVisibility(visibility);
-        } else if (getLastSelectedChildTaskIndex() == 0) {
+        } else if (taskId == getTaskIds()[0]) {
             mSnapshotView.setVisibility(visibility);
             mDigitalWellBeingToast.setBannerVisibility(visibility);
         } else {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index ac59403..614ef81 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -147,8 +147,6 @@
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.OverScroll;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.DynamicResource;
@@ -186,6 +184,7 @@
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps;
 import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.SurfaceTransaction;
@@ -194,6 +193,7 @@
 import com.android.quickstep.util.TaskVisualsChangeListener;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.util.VibrationConstants;
+import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
 import com.android.systemui.plugins.ResourceProvider;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -937,7 +937,7 @@
         if (mHandleTaskStackChanges) {
             TaskView taskView = getTaskViewByTaskId(taskId);
             if (taskView != null) {
-                for (TaskView.TaskIdAttributeContainer container :
+                for (TaskIdAttributeContainer container :
                         taskView.getTaskIdAttributeContainers()) {
                     if (container == null || taskId != container.getTask().key.id) {
                         continue;
@@ -1518,9 +1518,10 @@
         mMovingTaskView = null;
         runningTaskView.resetPersistentViewTransforms();
         int frontTaskIndex = 0;
-        if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED && !runningTaskView.isDesktopTask()) {
-            // If desktop mode is enabled, desktop task view is pinned at first position.
-            // Move running task to position 1
+        if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED && mDesktopTaskView != null
+                && !runningTaskView.isDesktopTask()) {
+            // If desktop mode is enabled, desktop task view is pinned at first position if present.
+            // Move running task to position 1.
             frontTaskIndex = 1;
         }
         addView(runningTaskView, frontTaskIndex);
@@ -1653,14 +1654,18 @@
 
         if (!taskGroups.isEmpty()) {
             addView(mClearAllButton);
-
-            if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED
-                    && !getSplitSelectController().isSplitSelectActive()) {
-                mDesktopTaskView = (DesktopTaskView) getTaskViewFromPool(TaskView.Type.DESKTOP);
-                // Always add a desktop task to the first position. Even if it is empty
-                addView(mDesktopTaskView, 0);
-                ArrayList<Task> tasks = desktopTask != null ? desktopTask.tasks : new ArrayList<>();
-                mDesktopTaskView.bind(tasks, mOrientationState);
+            if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+                // Check if we have apps on the desktop
+                if (desktopTask != null && !desktopTask.tasks.isEmpty()) {
+                    // If we are actively choosing apps for split, skip the desktop tile
+                    if (!getSplitSelectController().isSplitSelectActive()) {
+                        mDesktopTaskView = (DesktopTaskView) getTaskViewFromPool(
+                                TaskView.Type.DESKTOP);
+                        // Always add a desktop task to the first position
+                        addView(mDesktopTaskView, 0);
+                        mDesktopTaskView.bind(desktopTask.tasks, mOrientationState);
+                    }
+                }
             }
         }
 
@@ -3096,29 +3101,26 @@
 
         RectF startingTaskRect = new RectF();
         safeRemoveDragLayerView(mFirstFloatingTaskView);
+        SplitAnimInitProps splitAnimInitProps =
+                mSplitSelectStateController.getSplitAnimationController().getFirstAnimInitViews(
+                        () -> mSplitHiddenTaskView, () -> mSplitSelectSource);
         if (mSplitSelectStateController.isAnimateCurrentTaskDismissal()) {
             // Create the split select animation from Overview
-            mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE);
-            anim.setViewAlpha(mSplitHiddenTaskView.getIconView(), 0, clampToProgress(LINEAR,
+            mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE,
+                    mSplitSelectStateController.getInitialTaskId());
+            anim.setViewAlpha(splitAnimInitProps.getIconView(), 0, clampToProgress(LINEAR,
                     timings.getIconFadeStartOffset(),
                     timings.getIconFadeEndOffset()));
-            mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
-                    mSplitHiddenTaskView.getThumbnail(),
-                    mSplitHiddenTaskView.getThumbnail().getThumbnail(),
-                    mSplitHiddenTaskView.getIconView().getDrawable(), startingTaskRect);
-            mFirstFloatingTaskView.setAlpha(1);
-            mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
-                    true /* fadeWithThumbnail */, true /* isStagedTask */);
-        } else {
-            // Create the split select animation from Home
-            mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
-                    mSplitSelectSource.view, null /* thumbnail */,
-                    mSplitSelectSource.drawable, startingTaskRect);
-            mFirstFloatingTaskView.setAlpha(1);
-            mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
-                    false /* fadeWithThumbnail */, true /* isStagedTask */);
         }
 
+        mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
+                splitAnimInitProps.getOriginalView(),
+                splitAnimInitProps.getOriginalBitmap(),
+                splitAnimInitProps.getIconDrawable(), startingTaskRect);
+        mFirstFloatingTaskView.setAlpha(1);
+        mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
+                splitAnimInitProps.getFadeWithThumbnail(), splitAnimInitProps.isStagedTask());
+
         // Allow user to click staged app to launch into fullscreen
         if (ENABLE_LAUNCH_FROM_STAGED_APP.get()) {
             mFirstFloatingTaskView.setOnClickListener(this::animateToFullscreen);
@@ -4447,7 +4449,7 @@
         mTaskViewsSecondarySplitTranslation = translation;
         for (int i = 0; i < getTaskViewCount(); i++) {
             TaskView taskView = requireTaskViewAt(i);
-            if (taskView == mSplitHiddenTaskView) {
+            if (taskView == mSplitHiddenTaskView && !taskView.containsMultipleTasks()) {
                 continue;
             }
             taskView.getSecondarySplitTranslationProperty().set(taskView, translation);
@@ -4493,12 +4495,15 @@
      * Attempts to initiate split with an existing taskView, if one exists
      */
     public void initiateSplitSelect(SplitSelectSource splitSelectSource) {
-        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "enterSplitSelect");
         mSplitSelectSource = splitSelectSource;
         mSplitHiddenTaskView = getTaskViewByTaskId(splitSelectSource.alreadyRunningTaskId);
         mSplitHiddenTaskViewIndex = indexOfChild(mSplitHiddenTaskView);
         mSplitSelectStateController
                 .setAnimateCurrentTaskDismissal(splitSelectSource.animateCurrentTaskDismissal);
+
+        // Prevent dismissing whole task if we're only initiating from one of 2 tasks in split pair
+        mSplitSelectStateController.setDismissingFromSplitPair(mSplitHiddenTaskView != null
+                && mSplitHiddenTaskView.containsMultipleTasks());
         mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
                 splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
                 splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId);
@@ -4517,8 +4522,32 @@
      * Modifies a PendingAnimation with the animations for entering split staging
      */
     public void createSplitSelectInitAnimation(PendingAnimation builder, int duration) {
-        if (mSplitSelectStateController.isAnimateCurrentTaskDismissal()) {
-            // Splitting from Overview
+        boolean isInitiatingSplitFromTaskView =
+                mSplitSelectStateController.isAnimateCurrentTaskDismissal();
+        boolean isInitiatingTaskViewSplitPair =
+                mSplitSelectStateController.isDismissingFromSplitPair();
+        if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair) {
+            // Splitting from Overview for split pair task
+            createInitialSplitSelectAnimation(builder);
+
+            // Animate pair thumbnail into full thumbnail
+            boolean primaryTaskSelected =
+                    mSplitHiddenTaskView.getTaskIdAttributeContainers()[0].getTask().key.id ==
+                            mSplitSelectStateController.getInitialTaskId();
+            TaskIdAttributeContainer taskIdAttributeContainer = mSplitHiddenTaskView
+                    .getTaskIdAttributeContainers()[primaryTaskSelected ? 1 : 0];
+            TaskThumbnailView thumbnail = taskIdAttributeContainer.getThumbnailView();
+            mSplitSelectStateController.getSplitAnimationController()
+                    .addInitialSplitFromPair(taskIdAttributeContainer, builder,
+                            mActivity.getDeviceProfile(),
+                            mSplitHiddenTaskView.getWidth(), mSplitHiddenTaskView.getHeight(),
+                            primaryTaskSelected);
+            builder.addOnFrameCallback(() ->{
+                thumbnail.refreshSplashView();
+                mSplitHiddenTaskView.updateSnapshotRadius();
+            });
+        } else if (isInitiatingSplitFromTaskView) {
+            // Splitting from Overview for fullscreen task
             createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration,
                     true /* dismissingForSplitSelection*/);
         } else {
@@ -4606,7 +4635,8 @@
 
         mSecondSplitHiddenView = containerTaskView;
         if (mSecondSplitHiddenView != null) {
-            mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE);
+            mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE,
+                    mSplitSelectStateController.getSecondTaskId());
         }
 
         InteractionJankMonitorWrapper.begin(this,
@@ -4632,7 +4662,7 @@
         }
 
         if (mSecondSplitHiddenView != null) {
-            mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE);
+            mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
             mSecondSplitHiddenView = null;
         }
 
@@ -4658,7 +4688,7 @@
         resetTaskVisuals();
         mSplitHiddenTaskViewIndex = -1;
         if (mSplitHiddenTaskView != null) {
-            mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE);
+            mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
             mSplitHiddenTaskView = null;
         }
         if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
@@ -5201,7 +5231,7 @@
     }
 
     private int getFirstViewIndex() {
-        if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+        if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED && mDesktopTaskView != null) {
             // Desktop task is at position 0, that is the first view
             return 0;
         }
@@ -5528,7 +5558,7 @@
         }
 
         taskView.setShowScreenshot(true);
-        for (TaskView.TaskIdAttributeContainer container :
+        for (TaskIdAttributeContainer container :
                 taskView.getTaskIdAttributeContainers()) {
             if (container == null) {
                 continue;
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 432eadc..62a58f5 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -81,6 +81,47 @@
                 }
             };
 
+    public static final Property<TaskThumbnailView, Float> SPLASH_ALPHA =
+            new FloatProperty<TaskThumbnailView>("splashAlpha") {
+                @Override
+                public void setValue(TaskThumbnailView thumbnail, float splashAlpha) {
+                    thumbnail.setSplashAlpha(splashAlpha);
+                }
+
+                @Override
+                public Float get(TaskThumbnailView thumbnailView) {
+                    return thumbnailView.mSplashAlpha / 255f;
+                }
+            };
+
+    /** Use to animate thumbnail translationX while first app in split selection is initiated */
+    public static final Property<TaskThumbnailView, Float> SPLIT_SELECT_TRANSLATE_X =
+            new FloatProperty<TaskThumbnailView>("splitSelectTranslateX") {
+                @Override
+                public void setValue(TaskThumbnailView thumbnail, float splitSelectTranslateX) {
+                    thumbnail.applySplitSelectTranslateX(splitSelectTranslateX);
+                }
+
+                @Override
+                public Float get(TaskThumbnailView thumbnailView) {
+                    return thumbnailView.mSplitSelectTranslateX;
+                }
+            };
+
+    /** Use to animate thumbnail translationY while first app in split selection is initiated */
+    public static final Property<TaskThumbnailView, Float> SPLIT_SELECT_TRANSLATE_Y =
+            new FloatProperty<TaskThumbnailView>("splitSelectTranslateY") {
+                @Override
+                public void setValue(TaskThumbnailView thumbnail, float splitSelectTranslateY) {
+                    thumbnail.applySplitSelectTranslateY(splitSelectTranslateY);
+                }
+
+                @Override
+                public Float get(TaskThumbnailView thumbnailView) {
+                    return thumbnailView.mSplitSelectTranslateY;
+                }
+            };
+
     private final BaseActivity mActivity;
     @Nullable
     private TaskOverlay mOverlay;
@@ -111,6 +152,10 @@
     private int mSplashAlpha = 0;
 
     private boolean mOverlayEnabled;
+    /** Used as a placeholder when the original thumbnail animates out to. */
+    private boolean mShowSplashForSplitSelection;
+    private float mSplitSelectTranslateX;
+    private float mSplitSelectTranslateY;
 
     public TaskThumbnailView(Context context) {
         this(context, null);
@@ -342,10 +387,17 @@
 
         // Draw splash above thumbnail to hide inconsistencies in rotation and aspect ratios.
         if (shouldShowSplashView()) {
+            float cornerRadiusX = cornerRadius;
+            float cornerRadiusY = cornerRadius;
+            if (mShowSplashForSplitSelection) {
+                cornerRadiusX = cornerRadius / getScaleX();
+                cornerRadiusY = cornerRadius / getScaleY();
+            }
+
             // Always draw background for hiding inconsistencies, even if splash view is not yet
             // loaded (which can happen as task icons are loaded asynchronously in the background)
-            canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadius,
-                    cornerRadius, mSplashBackgroundPaint);
+            canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadiusX,
+                    cornerRadiusY, mSplashBackgroundPaint);
             if (mSplashView != null) {
                 mSplashView.layout((int) x, (int) (y + 1), (int) width, (int) height - 1);
                 mSplashView.draw(canvas);
@@ -353,6 +405,31 @@
         }
     }
 
+    /** See {@link #SPLIT_SELECT_TRANSLATE_X} */
+    protected void applySplitSelectTranslateX(float splitSelectTranslateX) {
+        mSplitSelectTranslateX = splitSelectTranslateX;
+        applyTranslateX();
+    }
+
+    /** See {@link #SPLIT_SELECT_TRANSLATE_Y} */
+    protected void applySplitSelectTranslateY(float splitSelectTranslateY) {
+        mSplitSelectTranslateY = splitSelectTranslateY;
+        applyTranslateY();
+    }
+
+    private void applyTranslateX() {
+        setTranslationX(mSplitSelectTranslateX);
+    }
+
+    private void applyTranslateY() {
+        setTranslationY(mSplitSelectTranslateY);
+    }
+
+    protected void resetViewTransforms() {
+        mSplitSelectTranslateX = 0;
+        mSplitSelectTranslateY = 0;
+    }
+
     public TaskView getTaskView() {
         return (TaskView) getParent();
     }
@@ -373,7 +450,12 @@
      */
     public boolean shouldShowSplashView() {
         return isThumbnailAspectRatioDifferentFromThumbnailData()
-                || isThumbnailRotationDifferentFromTask();
+                || isThumbnailRotationDifferentFromTask()
+                || mShowSplashForSplitSelection;
+    }
+
+    public void setShowSplashForSplitSelection(boolean showSplashForSplitSelection) {
+        mShowSplashForSplitSelection = showSplashForSplitSelection;
     }
 
     protected void refreshSplashView() {
@@ -396,7 +478,6 @@
 
         imageView.setScaleType(ImageView.ScaleType.MATRIX);
         Matrix matrix = new Matrix();
-
         float drawableWidth = mSplashViewDrawable.getIntrinsicWidth();
         float drawableHeight = mSplashViewDrawable.getIntrinsicHeight();
         float viewWidth = getMeasuredWidth();
@@ -408,12 +489,13 @@
         float nonGridScale = getTaskView() == null ? 1 : 1 / getTaskView().getNonGridScale();
         float recentsMaxScale = getTaskView() == null || getTaskView().getRecentsView() == null
                 ? 1 : 1 / getTaskView().getRecentsView().getMaxScaleForFullScreen();
-        float scale = nonGridScale * recentsMaxScale;
+        float scaleX = nonGridScale * recentsMaxScale * (1 / getScaleX());
+        float scaleY = nonGridScale * recentsMaxScale * (1 / getScaleY());
 
         // Center the image in the view.
         matrix.setTranslate(centeredDrawableLeft, centeredDrawableTop);
         // Apply scale transformation after translation, pivoting around center of view.
-        matrix.postScale(scale, scale, viewCenterX, viewCenterY);
+        matrix.postScale(scaleX, scaleY, viewCenterX, viewCenterY);
 
         imageView.setImageMatrix(matrix);
         mSplashView = imageView;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index ab72f2d..a06e1f8 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -16,6 +16,7 @@
 
 package com.android.quickstep.views;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.widget.Toast.LENGTH_SHORT;
 
@@ -41,6 +42,7 @@
 import android.animation.ObjectAnimator;
 import android.annotation.IdRes;
 import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Canvas;
@@ -706,9 +708,12 @@
         }
         SplitSelectStateController splitSelectStateController =
                 recentsView.getSplitSelectController();
-        if (splitSelectStateController.isSplitSelectActive() &&
-                splitSelectStateController.getInitialTaskId() == getTask().key.id) {
-            // Prevent taps on the this taskview if it's being animated into split select state
+        // Disable taps for split selection animation unless we have multiple tasks
+        boolean disableTapsForSplitSelect =
+                splitSelectStateController.isSplitSelectActive()
+                        && splitSelectStateController.getInitialTaskId() == getTask().key.id
+                        && !containsMultipleTasks();
+        if (disableTapsForSplitSelect) {
             return false;
         }
 
@@ -718,6 +723,25 @@
         return super.dispatchTouchEvent(ev);
     }
 
+    /**
+     * @return taskId that split selection was initiated with,
+     *         {@link ActivityTaskManager#INVALID_TASK_ID} if no tasks in this TaskView are part of
+     *         split selection
+     */
+    protected int getThisTaskCurrentlyInSplitSelection() {
+        SplitSelectStateController splitSelectController =
+                getRecentsView().getSplitSelectController();
+        int initSplitTaskId = INVALID_TASK_ID;
+        for (TaskIdAttributeContainer container : getTaskIdAttributeContainers()) {
+            int taskId = container.getTask().key.id;
+            if (taskId == splitSelectController.getInitialTaskId()) {
+                initSplitTaskId = taskId;
+                break;
+            }
+        }
+        return initSplitTaskId;
+    }
+
     private void onClick(View view) {
         if (getTask() == null) {
             return;
@@ -747,6 +771,8 @@
 
     /**
      * Returns the task index of the last selected child task (0 or 1).
+     * If we contain multiple tasks and this TaskView is used as part of split selection, the
+     * selected child task index will be that of the remaining task.
      */
     protected int getLastSelectedChildTaskIndex() {
         return 0;
@@ -1084,6 +1110,8 @@
         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
         int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
 
+        // TODO(b/271468547), we should default to setting trasnlations only on the snapshot instead
+        //  of a hybrid of both margins and translations
         LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
         snapshotParams.topMargin = thumbnailTopMargin;
         mSnapshotView.setLayoutParams(snapshotParams);
@@ -1179,6 +1207,7 @@
         setAlpha(mStableAlpha);
         setIconScaleAndDim(1);
         setColorTint(0, 0);
+        mSnapshotView.resetViewTransforms();
     }
 
     public void setStableAlpha(float parentAlpha) {
@@ -1720,10 +1749,12 @@
     }
 
     /**
-     *     Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
-     *     IconView is unaffected.
+     *  Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
+     *  IconView is unaffected.
+     *
+     * @param taskId is only used when setting visibility to a non-{@link View#VISIBLE} value
      */
-    void setThumbnailVisibility(int visibility) {
+    void setThumbnailVisibility(int visibility, int taskId) {
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
             if (child != mIconView) {
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
deleted file mode 100644
index f10b917..0000000
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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.
- */
-package com.android.quickstep;
-
-import android.content.Intent;
-
-import com.android.launcher3.ui.TaplTestsLauncher3;
-import com.android.launcher3.util.rule.TestStabilityRule;
-import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
-
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-
-public class TaplTestsSplitscreen extends AbstractQuickStepTest {
-    private static final String CALCULATOR_APP_NAME = "Calculator";
-    private static final String CALCULATOR_APP_PACKAGE =
-            resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
-
-    @Override
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-        TaplTestsLauncher3.initialize(this);
-
-        mLauncher.getWorkspace()
-                .deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0))
-                .switchToAllApps()
-                .getAppIcon(CALCULATOR_APP_NAME)
-                .dragToHotseat(0);
-
-        startAppFast(CALCULATOR_APP_PACKAGE);
-        mLauncher.enableBlockTimeout(true);
-        mLauncher.showTaskbarIfHidden();
-    }
-
-    @After
-    public void tearDown() {
-        mLauncher.enableBlockTimeout(false);
-    }
-
-    @Test
-    // TODO (b/270201357): When this test is proven stable, remove this TestStabilityRule and
-    // introduce into presubmit as well.
-    @TestStabilityRule.Stability(
-            flavors = TestStabilityRule.LOCAL | TestStabilityRule.PLATFORM_POSTSUBMIT)
-    @PortraitLandscape
-    @TaskbarModeSwitch
-    public void testSplitAppFromHomeWithItself() throws Exception {
-        Assume.assumeTrue(mLauncher.isTablet());
-
-        mLauncher.goHome()
-                .switchToAllApps()
-                .getAppIcon(CALCULATOR_APP_NAME)
-                .openMenu()
-                .getSplitScreenMenuItem()
-                .click();
-
-        mLauncher.getLaunchedAppState()
-                .getTaskbar()
-                .getAppIcon(CALCULATOR_APP_NAME)
-                .launchIntoSplitScreen();
-    }
-}
diff --git a/res/drawable/ic_split_horizontal.xml b/res/drawable/ic_split_horizontal.xml
index ee710d0..2efd2b9 100644
--- a/res/drawable/ic_split_horizontal.xml
+++ b/res/drawable/ic_split_horizontal.xml
@@ -1,9 +1,9 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="20dp"
-    android:height="16dp"
-    android:viewportWidth="20"
-    android:viewportHeight="16">
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
   <path
-      android:pathData="M18,14L13,14L13,2L18,2L18,14ZM20,14L20,2C20,0.9 19.1,-0 18,-0L13,-0C11.9,-0 11,0.9 11,2L11,14C11,15.1 11.9,16 13,16L18,16C19.1,16 20,15.1 20,14ZM7,14L2,14L2,2L7,2L7,14ZM9,14L9,2C9,0.9 8.1,-0 7,-0L2,-0C0.9,-0 -0,0.9 -0,2L-0,14C-0,15.1 0.9,16 2,16L7,16C8.1,16 9,15.1 9,14Z"
+      android:pathData="M4,6L9,6L9,18L4,18L4,6ZM2,6L2,18C2,19.1 2.9,20 4,20L9,20C10.1,20 11,19.1 11,18L11,6C11,4.9 10.1,4 9,4L4,4C2.9,4 2,4.9 2,6ZM15,6L20,6L20,18L15,18L15,6ZM13,6L13,18C13,19.1 13.9,20 15,20L20,20C21.1,20 22,19.1 22,18L22,6C22,4.9 21.1,4 20,4L15,4C13.9,4 13,4.9 13,6Z"
       android:fillColor="#000000"/>
 </vector>
diff --git a/res/layout/all_apps_icon_twoline.xml b/res/layout/all_apps_icon_twoline.xml
index 54c7147..b0d02c1 100644
--- a/res/layout/all_apps_icon_twoline.xml
+++ b/res/layout/all_apps_icon_twoline.xml
@@ -19,7 +19,6 @@
     android:id="@+id/icon"
     android:singleLine="false"
     android:lines="2"
-    android:inputType="textMultiLine"
     launcher:iconDisplay="all_apps"
     launcher:centerVertically="true" />
 
diff --git a/res/layout/system_shortcut_icons_container.xml b/res/layout/system_shortcut_icons_container.xml
index ee104d9..dc4fdb3 100644
--- a/res/layout/system_shortcut_icons_container.xml
+++ b/res/layout/system_shortcut_icons_container.xml
@@ -22,11 +22,4 @@
     android:orientation="horizontal"
     android:gravity="end|center_vertical"
     android:background="@drawable/single_item_primary"
-    android:elevation="@dimen/deep_shortcuts_elevation"
-    android:clipToPadding="true">
-
-    <Space android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:id="@+id/separator"/>
-</LinearLayout>
+    android:elevation="@dimen/deep_shortcuts_elevation"/>
diff --git a/res/layout/system_shortcut_icons_container_material_u.xml b/res/layout/system_shortcut_icons_container_material_u.xml
index afd11e6..70950ba 100644
--- a/res/layout/system_shortcut_icons_container_material_u.xml
+++ b/res/layout/system_shortcut_icons_container_material_u.xml
@@ -23,10 +23,4 @@
     android:orientation="horizontal"
     android:gravity="end|center_vertical"
     android:background="@drawable/popup_background_material_u"
-    android:elevation="@dimen/deep_shortcuts_elevation">
-
-    <Space android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:id="@+id/separator"/>
-</LinearLayout>
+    android:elevation="@dimen/deep_shortcuts_elevation"/>
diff --git a/res/layout/system_shortcut_rows_container_material_u.xml b/res/layout/system_shortcut_rows_container.xml
similarity index 100%
rename from res/layout/system_shortcut_rows_container_material_u.xml
rename to res/layout/system_shortcut_rows_container.xml
diff --git a/res/layout/system_shortcut_spacer.xml b/res/layout/system_shortcut_spacer.xml
new file mode 100644
index 0000000..60ea9ec
--- /dev/null
+++ b/res/layout/system_shortcut_spacer.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<Space
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dp"
+    android:layout_height="match_parent"
+    android:layout_weight="1"
+    android:id="@+id/shortcut_spacer"/>
\ No newline at end of file
diff --git a/res/values/id.xml b/res/values/id.xml
index 375750f..dc81944 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -38,4 +38,7 @@
     <item type="id" name="cache_entry_tag_id" />
 
     <item type="id" name="saved_clip_children_tag_id" />
+
+    <item type="id" name="saved_floating_widget_foreground" />
+    <item type="id" name="saved_floating_widget_background" />
 </resources>
diff --git a/res/xml/default_tapl_test_workspace.xml b/res/xml/default_tapl_test_workspace.xml
new file mode 100644
index 0000000..24d76f3
--- /dev/null
+++ b/res/xml/default_tapl_test_workspace.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<!-- Split display specific version of Launcher3/res/xml/default_workspace_4x4.xml -->
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+
+    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+    <favorite
+        launcher:container="-101"
+        launcher:screen="0"
+        launcher:x="0"
+        launcher:y="0"
+        launcher:className="com.google.android.apps.chrome.Main"
+        launcher:packageName="com.android.chrome" />
+
+</favorites>
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index eb6d096..9d5b08e 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -2,6 +2,7 @@
 
 import static android.os.Process.myUserHandle;
 
+import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
@@ -51,10 +52,10 @@
      */
     @WorkerThread
     public static void restoreAppWidgetIds(Context context, int[] oldWidgetIds, int[] newWidgetIds,
-            @NonNull LauncherWidgetHolder holder) {
+            @NonNull AppWidgetHost host) {
         if (WidgetsModel.GO_DISABLE_WIDGETS) {
             Log.e(TAG, "Skipping widget ID remap as widgets not supported");
-            holder.deleteHost();
+            host.deleteHost();
             return;
         }
         if (!RestoreDbTask.isPending(context)) {
@@ -63,7 +64,7 @@
             Log.e(TAG, "Skipping widget ID remap as DB already in use");
             for (int widgetId : newWidgetIds) {
                 Log.d(TAG, "Deleting widgetId: " + widgetId);
-                holder.deleteAppWidgetId(widgetId);
+                host.deleteAppWidgetId(widgetId);
             }
             return;
         }
@@ -100,7 +101,7 @@
                 try {
                     if (!cursor.moveToFirst()) {
                         // The widget no long exists.
-                        holder.deleteAppWidgetId(newWidgetIds[i]);
+                        host.deleteAppWidgetId(newWidgetIds[i]);
                     }
                 } finally {
                     cursor.close();
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 3eb03ed..801b740 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -52,8 +52,10 @@
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
 import com.android.launcher3.dragndrop.DraggableView;
@@ -71,6 +73,8 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.util.MultiTranslateDelegate;
+import com.android.launcher3.search.StringMatcherUtility;
+import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.ShortcutUtil;
 import com.android.launcher3.views.ActivityContext;
@@ -97,11 +101,19 @@
 
     private static final float MIN_LETTER_SPACING = -0.05f;
     private static final int MAX_SEARCH_LOOP_COUNT = 20;
+    private static final Character NEW_LINE = '\n';
+    private static final String EMPTY = "";
+    private static final StringMatcherUtility.StringMatcher MATCHER =
+            StringMatcherUtility.StringMatcher.getInstance();
 
     private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
 
     private float mScaleForReorderBounce = 1f;
 
+    private IntArray mBreakPointsIntArray;
+    private CharSequence mLastOriginalText;
+    private CharSequence mLastModifiedText;
+
     private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
             = new Property<BubbleTextView, Float>(Float.TYPE, "dotScale") {
         @Override
@@ -134,7 +146,7 @@
     private FastBitmapDrawable mIcon;
     private boolean mCenterVertically;
 
-    protected final int mDisplay;
+    protected int mDisplay;
 
     private final CheckLongPressHelper mLongPressHelper;
 
@@ -255,6 +267,8 @@
         mDotParams.scale = 0f;
         mForceHideDot = false;
         setBackground(null);
+        setSingleLine(true);
+        setMaxLines(1);
 
         setTag(null);
         if (mIconLoadRequest != null) {
@@ -382,8 +396,15 @@
     }
 
     @UiThread
-    private void applyLabel(ItemInfoWithIcon info) {
-        setText(info.title);
+    @VisibleForTesting
+    public void applyLabel(ItemInfoWithIcon info) {
+        CharSequence label = info.title;
+        if (label != null) {
+            mLastOriginalText = label;
+            mLastModifiedText = mLastOriginalText;
+            mBreakPointsIntArray = StringMatcherUtility.getListOfBreakpoints(label, MATCHER);
+            setText(label);
+        }
         if (info.contentDescription != null) {
             setContentDescription(info.isDisabled()
                     ? getContext().getString(R.string.disabled_app_label, info.contentDescription)
@@ -391,6 +412,12 @@
         }
     }
 
+    /** This is used for testing to forcefully set the display to ALL_APPS */
+    @VisibleForTesting
+    public void setDisplayAllApps() {
+        mDisplay = DISPLAY_ALL_APPS;
+    }
+
     /**
      * Overrides the default long press timeout.
      */
@@ -637,6 +664,27 @@
             setPadding(getPaddingLeft(), (height - cellHeightPx) / 2, getPaddingRight(),
                     getPaddingBottom());
         }
+        // only apply two line for all_apps
+        if (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get() && (mLastOriginalText != null)
+                && (mDisplay == DISPLAY_ALL_APPS)) {
+            CharSequence modifiedString = modifyTitleToSupportMultiLine(
+                    MeasureSpec.getSize(widthMeasureSpec) - getCompoundPaddingLeft()
+                            - getCompoundPaddingRight(),
+                    mLastOriginalText,
+                    getPaint(), mBreakPointsIntArray);
+            if (!TextUtils.equals(modifiedString, mLastModifiedText)) {
+                mLastModifiedText = modifiedString;
+                setText(modifiedString);
+                // if text contains NEW_LINE, set max lines to 2
+                if (TextUtils.indexOf(modifiedString, NEW_LINE) != -1) {
+                    setSingleLine(false);
+                    setMaxLines(2);
+                } else {
+                    setSingleLine(true);
+                    setMaxLines(1);
+                }
+            }
+        }
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
@@ -697,6 +745,73 @@
         return ObjectAnimator.ofFloat(this, TEXT_ALPHA_PROPERTY, toAlpha);
     }
 
+    /**
+     * Generate a new string that will support two line text depending on the current string.
+     * This method calculates the limited width of a text view and creates a string to fit as
+     * many words as it can until the limit is reached. Once the limit is reached, we decide to
+     * either return the original title or continue on a new line. How to get the new string is by
+     * iterating through the list of break points and determining if the strings between the break
+     * points can fit within the line it is in.
+     *  Example assuming each character takes up one spot:
+     *  title = "Battery Stats", breakpoint = [6], stringPtr = 0, limitedWidth = 7
+     *  We get the current word -> from sublist(0, breakpoint[i]+1) so sublist (0,7) -> Battery,
+     *  now stringPtr = 7 then from sublist(7) the current string is " Stats" and the runningWidth
+     *  at this point exceeds limitedWidth and so we put " Stats" onto the next line (after checking
+     *  if the first char is a SPACE, we trim to append "Stats". So resulting string would be
+     *  "Battery\nStats"
+     */
+    public static CharSequence modifyTitleToSupportMultiLine(int limitedWidth, CharSequence title,
+            TextPaint paint, IntArray breakPoints) {
+        // current title is less than the width allowed so we can just skip
+        if (title == null || paint.measureText(title, 0, title.length()) <= limitedWidth) {
+            return title;
+        }
+        float currentWordWidth, runningWidth = 0;
+        CharSequence currentWord;
+        StringBuilder newString = new StringBuilder();
+        int stringPtr = 0;
+        for (int i = 0; i < breakPoints.size()+1; i++) {
+            if (i < breakPoints.size()) {
+                currentWord = title.subSequence(stringPtr, breakPoints.get(i)+1);
+            } else {
+                // last word from recent breakpoint until the end of the string
+                currentWord = title.subSequence(stringPtr, title.length());
+            }
+            currentWordWidth = paint.measureText(currentWord,0, currentWord.length());
+            runningWidth += currentWordWidth;
+            if (runningWidth <= limitedWidth) {
+                newString.append(currentWord);
+            } else {
+                // there is no more space
+                if (i == 0) {
+                    // if the first words exceeds width, just return as the first line will ellipse
+                    return title;
+                } else {
+                    // If putting word onto a new line, make sure there is no space or new line
+                    // character in the beginning of the current word and just put in the rest of
+                    // the characters.
+                    CharSequence lastCharacters = title.subSequence(stringPtr, title.length());
+                    int beginningLetterType =
+                            Character.getType(Character.codePointAt(lastCharacters,0));
+                    if (beginningLetterType == Character.SPACE_SEPARATOR
+                            || beginningLetterType == Character.LINE_SEPARATOR) {
+                        lastCharacters = lastCharacters.length() > 1
+                                ? lastCharacters.subSequence(1, lastCharacters.length())
+                                : EMPTY;
+                    }
+                    newString.append(NEW_LINE).append(lastCharacters);
+                    return newString.toString();
+                }
+            }
+            if (i >= breakPoints.size()) {
+                // no need to look forward into the string if we've already finished processing
+                break;
+            }
+            stringPtr = breakPoints.get(i)+1;
+        }
+        return newString.toString();
+    }
+
     @Override
     public void cancelLongPress() {
         super.cancelLongPress();
@@ -717,7 +832,7 @@
                     || info.hasPromiseIconUi()
                     || (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0
                     || (ENABLE_DOWNLOAD_APP_UX_V2.get() && icon != null)) {
-                updateProgressBarUi(icon);
+                updateProgressBarUi(info.getProgressLevel() == 100 ? icon : null);
             }
         }
     }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 87ee4f6..d992ee0 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -193,7 +193,7 @@
     private final int mMinHotseatIconSpacePx;
     private final int mMinHotseatQsbWidthPx;
     private final int mMaxHotseatIconSpacePx;
-    private final int mInlineNavButtonsEndSpacingPx;
+    public final int inlineNavButtonsEndSpacingPx;
 
     // Bottom sheets
     public int bottomSheetTopPadding;
@@ -490,7 +490,7 @@
         hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
         updateHotseatSizes(pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
         if (areNavButtonsInline && !isPhone) {
-            mInlineNavButtonsEndSpacingPx =
+            inlineNavButtonsEndSpacingPx =
                     res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing);
             /*
              * 3 nav buttons +
@@ -499,9 +499,9 @@
              */
             hotseatBarEndOffset = 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
                     + 2 * res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween)
-                    + mInlineNavButtonsEndSpacingPx;
+                    + inlineNavButtonsEndSpacingPx;
         } else {
-            mInlineNavButtonsEndSpacingPx = 0;
+            inlineNavButtonsEndSpacingPx = 0;
             hotseatBarEndOffset = 0;
         }
 
@@ -662,7 +662,7 @@
         }
 
         // The side space with inline buttons should be what is defined in InvariantDeviceProfile
-        int sideSpacePx = mInlineNavButtonsEndSpacingPx;
+        int sideSpacePx = inlineNavButtonsEndSpacingPx;
         int maxHotseatWidthPx = availableWidthPx - sideSpacePx - hotseatBarEndOffset;
         int maxHotseatIconsWidthPx = maxHotseatWidthPx - (isQsbInline ? hotseatQsbWidth : 0);
         hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidthPx,
@@ -1320,7 +1320,7 @@
             int endSpacing;
             // Hotseat aligns to the left with nav buttons
             if (hotseatBarEndOffset > 0) {
-                startSpacing = mInlineNavButtonsEndSpacingPx;
+                startSpacing = inlineNavButtonsEndSpacingPx;
                 endSpacing = availableWidthPx - hotseatWidth - startSpacing + hotseatBorderSpace;
             } else {
                 startSpacing = (availableWidthPx - hotseatWidth) / 2;
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 767c3d9..e688709 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -107,6 +107,7 @@
 
     private static final int TEST_WORKSPACE_LAYOUT_RES_XML = R.xml.default_test_workspace;
     private static final int TEST2_WORKSPACE_LAYOUT_RES_XML = R.xml.default_test2_workspace;
+    private static final int TAPL_WORKSPACE_LAYOUT_RES_XML = R.xml.default_tapl_test_workspace;
 
     static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
 
@@ -410,6 +411,9 @@
                     case LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TEST2:
                         mDefaultWorkspaceLayoutOverride = TEST2_WORKSPACE_LAYOUT_RES_XML;
                         break;
+                    case LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TAPL:
+                        mDefaultWorkspaceLayoutOverride = TAPL_WORKSPACE_LAYOUT_RES_XML;
+                        break;
                     default:
                         mDefaultWorkspaceLayoutOverride = 0;
                         break;
@@ -550,39 +554,42 @@
             Log.d(TAG, "loading default workspace");
 
             LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
-            AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHolder);
-            if (loader == null) {
-                loader = AutoInstallsLayout.get(getContext(), widgetHolder, mOpenHelper);
-            }
-            if (loader == null) {
-                final Partner partner = Partner.get(getContext().getPackageManager());
-                if (partner != null) {
-                    int workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT);
-                    if (workspaceResId != 0) {
-                        loader = new DefaultLayoutParser(getContext(), widgetHolder,
-                                mOpenHelper, partner.getResources(), workspaceResId);
+            try {
+                AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHolder);
+                if (loader == null) {
+                    loader = AutoInstallsLayout.get(getContext(), widgetHolder, mOpenHelper);
+                }
+                if (loader == null) {
+                    final Partner partner = Partner.get(getContext().getPackageManager());
+                    if (partner != null) {
+                        int workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT);
+                        if (workspaceResId != 0) {
+                            loader = new DefaultLayoutParser(getContext(), widgetHolder,
+                                    mOpenHelper, partner.getResources(), workspaceResId);
+                        }
                     }
                 }
-            }
 
-            final boolean usingExternallyProvidedLayout = loader != null;
-            if (loader == null) {
-                loader = getDefaultLayoutParser(widgetHolder);
-            }
+                final boolean usingExternallyProvidedLayout = loader != null;
+                if (loader == null) {
+                    loader = getDefaultLayoutParser(widgetHolder);
+                }
 
-            // There might be some partially restored DB items, due to buggy restore logic in
-            // previous versions of launcher.
-            mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
-            // Populate favorites table with initial favorites
-            if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
-                    && usingExternallyProvidedLayout) {
-                // Unable to load external layout. Cleanup and load the internal layout.
+                // There might be some partially restored DB items, due to buggy restore logic in
+                // previous versions of launcher.
                 mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
-                mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
-                        getDefaultLayoutParser(widgetHolder));
+                // Populate favorites table with initial favorites
+                if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
+                        && usingExternallyProvidedLayout) {
+                    // Unable to load external layout. Cleanup and load the internal layout.
+                    mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
+                    mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
+                            getDefaultLayoutParser(widgetHolder));
+                }
+                clearFlagEmptyDbCreated();
+            } finally {
+                widgetHolder.destroy();
             }
-            clearFlagEmptyDbCreated();
-            widgetHolder.destroy();
         }
     }
 
@@ -957,8 +964,6 @@
                     allWidgets = holder.getAppWidgetIds();
                 } catch (IncompatibleClassChangeError e) {
                     Log.e(TAG, "getAppWidgetIds not supported", e);
-                    // Necessary to destroy the holder to free up possible activity context
-                    holder.destroy();
                     return;
                 }
                 final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db,
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 76cae6a..cef00d9 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -41,10 +41,6 @@
          * An animation using the view's background.
          */
         public static final int VIEW_BACKGROUND = 1;
-        /**
-         * The default animation for a given view/item info type, but without the splash icon.
-         */
-        public static final int DEFAULT_NO_ICON = 2;
     }
 
     /**
@@ -393,6 +389,7 @@
                 "set_use_test_workspace_layout_flag";
         public static final String ARG_DEFAULT_WORKSPACE_LAYOUT_TEST = "default_test_workspace";
         public static final String ARG_DEFAULT_WORKSPACE_LAYOUT_TEST2 = "default_test2_workspace";
+        public static final String ARG_DEFAULT_WORKSPACE_LAYOUT_TAPL = "default_tapl_workspace";
 
         public static final String METHOD_CLEAR_USE_TEST_WORKSPACE_LAYOUT_FLAG =
                 "clear_use_test_workspace_layout_flag";
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index e3bce87..b5bc60d 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3;
 
-import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
-
 import static com.android.launcher3.anim.Interpolators.SCROLL;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
@@ -52,7 +50,6 @@
 import android.widget.ScrollView;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -319,7 +316,6 @@
     /**
      * Returns an IntSet with the indices of the currently visible pages
      */
-    @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
     public IntSet getVisiblePageIndices() {
         return getPageIndices(mCurrentPage);
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 54bf6a8..85d7a05 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -355,7 +355,7 @@
             });
         }
 
-        if(FeatureFlags.ENABLE_HAPTICS_ALL_APPS.get() && config.userControlled
+        if(FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() && config.userControlled
                 && Utilities.ATLEAST_S) {
             if (toState == ALL_APPS) {
                 builder.addOnFrameListener(
@@ -385,8 +385,9 @@
         builder.add(anim);
 
         setAlphas(toState, config, builder);
-
-        if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL) && !(Utilities.ATLEAST_S)) {
+        // This controls both haptics for tapping on QSB and going to all apps.
+        if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL) &&
+                !FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get()) {
             mLauncher.getAppsView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
         }
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 7040de5..8fa4276 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.search.SearchAdapterProvider;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.views.ActivityContext;
@@ -140,7 +141,7 @@
     protected final OnClickListener mOnIconClickListener;
     protected OnLongClickListener mOnIconLongClickListener = INSTANCE_ALL_APPS;
     protected OnFocusChangeListener mIconFocusListener;
-    private final int mExtraHeight;
+    private final int mExtraTextHeight;
 
     public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater,
             AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider) {
@@ -152,7 +153,8 @@
         mOnIconClickListener = mActivityContext.getItemOnClickListener();
 
         mAdapterProvider = adapterProvider;
-        mExtraHeight = res.getDimensionPixelSize(R.dimen.all_apps_height_extra);
+        mExtraTextHeight = Utilities.calculateTextHeight(
+                mActivityContext.getDeviceProfile().allAppsIconTextSizePx);
     }
 
     /**
@@ -197,7 +199,7 @@
                 icon.getLayoutParams().height =
                         mActivityContext.getDeviceProfile().allAppsCellHeightPx;
                 if (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get()) {
-                    icon.getLayoutParams().height += mExtraHeight;
+                    icon.getLayoutParams().height += mExtraTextHeight;
                 }
                 return new ViewHolder(icon);
             case VIEW_TYPE_EMPTY_SEARCH:
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 98b61d1..d1aaef1 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -113,6 +113,10 @@
     public static final BooleanFlag ENABLE_TWOLINE_ALLAPPS = getDebugFlag(270390937,
             "ENABLE_TWOLINE_ALLAPPS", false, "Enables two line label inside all apps.");
 
+    public static final BooleanFlag ENABLE_TWOLINE_DEVICESEARCH = getDebugFlag(201388851,
+            "ENABLE_TWOLINE_DEVICESEARCH", false,
+            "Enable two line label for icons with labels on device search.");
+
     public static final BooleanFlag ENABLE_DEVICE_SEARCH_PERFORMANCE_LOGGING = getReleaseFlag(
             270391397, "ENABLE_DEVICE_SEARCH_PERFORMANCE_LOGGING", false,
             "Allows on device search in all apps logging");
@@ -352,8 +356,9 @@
             "Enable the ability to tap a staged app during split select to launch it in full screen"
     );
 
-    public static final BooleanFlag ENABLE_HAPTICS_ALL_APPS = getDebugFlag(270396358,
-            "ENABLE_HAPTICS_ALL_APPS", false, "Enables haptics opening/closing All apps");
+    public static final BooleanFlag ENABLE_PREMIUM_HAPTICS_ALL_APPS = getDebugFlag(270396358,
+            "ENABLE_PREMIUM_HAPTICS_ALL_APPS", false,
+            "Enables haptics opening/closing All apps");
 
     public static final BooleanFlag ENABLE_FORCED_MONO_ICON = getDebugFlag(270396209,
             "ENABLE_FORCED_MONO_ICON", false,
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index e983a30..be995bc 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -126,8 +126,14 @@
         mMaskHeight = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_DP,
                 view.getResources().getDisplayMetrics());
         mTopScrim = Themes.getAttrDrawable(view.getContext(), R.attr.workspaceStatusBarScrim);
-        mBottomMask = mTopScrim == null ? null : createDitheredAlphaMask();
-        mHideSysUiScrim = mTopScrim == null;
+        if (mTopScrim != null) {
+            mTopScrim.setDither(true);
+            mBottomMask = createDitheredAlphaMask();
+            mHideSysUiScrim = false;
+        } else {
+            mBottomMask = null;
+            mHideSysUiScrim = true;
+        }
 
         mDrawWallpaperScrim = FeatureFlags.ENABLE_WALLPAPER_SCRIM.get()
                 && !Themes.getAttrBoolean(view.getContext(), R.attr.isMainColorDark)
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index a0f21dc..be3a09b 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -60,9 +60,7 @@
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
 
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 
 /**
  * A container for shortcuts to deep links and notifications associated with an app.
@@ -337,23 +335,6 @@
     }
 
     /**
-     * Shows the popup at the desired location, optionally reversing the children.
-     * @param viewsToFlip number of views from the top to to flip in case of reverse order
-     */
-    protected void reorderAndShow(int viewsToFlip) {
-        setupForDisplay();
-        boolean reverseOrder = !ENABLE_MATERIAL_U_POPUP.get() && mIsAboveIcon;
-        if (reverseOrder) {
-            reverseOrder(viewsToFlip);
-        }
-        assignMarginsAndBackgrounds(this);
-        if (shouldAddArrow()) {
-            addArrow();
-        }
-        animateOpen();
-    }
-
-    /**
      * Shows the popup at the desired location.
      */
     public void show() {
@@ -372,22 +353,6 @@
         orientAboutObject();
     }
 
-    private void reverseOrder(int viewsToFlip) {
-        int count = getChildCount();
-        ArrayList<View> allViews = new ArrayList<>(count);
-        for (int i = 0; i < count; i++) {
-            if (i == viewsToFlip) {
-                Collections.reverse(allViews);
-            }
-            allViews.add(getChildAt(i));
-        }
-        Collections.reverse(allViews);
-        removeAllViews();
-        for (int i = 0; i < count; i++) {
-            addView(allViews.get(i));
-        }
-    }
-
     private int getArrowLeft() {
         if (mIsLeftAligned) {
             return mArrowOffsetHorizontal;
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 8fef5c6..c20ac17 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -227,17 +227,18 @@
         if (ENABLE_MATERIAL_U_POPUP.get()) {
             container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
                     R.layout.popup_container_material_u, launcher.getDragLayer(), false);
+            container.configureForLauncher(launcher);
             container.populateAndShowRowsMaterialU(icon, deepShortcutCount, systemShortcuts);
         } else {
             container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
                     R.layout.popup_container, launcher.getDragLayer(), false);
+            container.configureForLauncher(launcher);
             container.populateAndShow(
                     icon,
                     deepShortcutCount,
                     popupDataProvider.getNotificationKeysForItem(item),
                     systemShortcuts);
         }
-        container.configureForLauncher(launcher);
         launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
         container.requestFocus();
         return container;
@@ -257,14 +258,19 @@
         }
         // If there is only 1 shortcut, add it to its own container so it can show text and icon
         if (shortcuts.size() == 1) {
-            initializeSystemShortcut(R.layout.system_shortcut, this, shortcuts.get(0));
+            mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_rows_container,
+                    this, 0);
+            initializeSystemShortcut(R.layout.system_shortcut, mSystemShortcutContainer,
+                    shortcuts.get(0), false);
             return;
         }
-        mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons_container, this);
-        for (SystemShortcut shortcut : shortcuts) {
+        mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons_container, this, 0);
+        for (int i = 0; i < shortcuts.size(); i++) {
             initializeSystemShortcut(
-                    R.layout.system_shortcut_icon_only, mSystemShortcutContainer,
-                    shortcut);
+                    R.layout.system_shortcut_icon_only,
+                    mSystemShortcutContainer,
+                    shortcuts.get(i),
+                    i < shortcuts.size() - 1);
         }
     }
 
@@ -289,7 +295,6 @@
             }
             updateNotificationHeader();
         }
-        int viewsToFlip = getChildCount();
         mSystemShortcutContainer = this;
         if (mDeepShortcutContainer == null) {
             mDeepShortcutContainer = findViewById(R.id.deep_shortcuts_container);
@@ -314,8 +319,7 @@
             Optional<SystemShortcut.Widgets> widgetShortcutOpt = getWidgetShortcut(shortcuts);
             if (widgetShortcutOpt.isPresent()) {
                 if (mWidgetContainer == null) {
-                    mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container,
-                            this);
+                    mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container, this, 0);
                 }
                 initializeWidgetShortcut(mWidgetContainer, widgetShortcutOpt.get());
             }
@@ -324,14 +328,17 @@
         } else {
             mDeepShortcutContainer.setVisibility(View.GONE);
             if (!shortcuts.isEmpty()) {
-                for (SystemShortcut shortcut : shortcuts) {
-                    initializeSystemShortcut(R.layout.system_shortcut, this, shortcut);
+                for (int i = 0; i < shortcuts.size(); i++) {
+                    initializeSystemShortcut(
+                            R.layout.system_shortcut,
+                            this,
+                            shortcuts.get(i),
+                            i < shortcuts.size() - 1);
                 }
             }
         }
-
-        reorderAndShow(viewsToFlip);
-        showPopupContainer((ItemInfo) originalIcon.getTag(), notificationKeys);
+        show();
+        loadAppShortcuts((ItemInfo) originalIcon.getTag(), notificationKeys);
     }
 
     /**
@@ -351,19 +358,17 @@
             addAllShortcutsMaterialU(deepShortcutCount, systemShortcuts);
         } else if (!systemShortcuts.isEmpty()) {
             addSystemShortcutsMaterialU(systemShortcuts,
-                    R.layout.system_shortcut_rows_container_material_u,
+                    R.layout.system_shortcut_rows_container,
                     R.layout.system_shortcut);
         }
-
-        // no reversing needed for U design
-        reorderAndShow(0);
-        showPopupContainer((ItemInfo) originalIcon.getTag(), /* notificationKeys= */ emptyList());
+        show();
+        loadAppShortcuts((ItemInfo) originalIcon.getTag(), /* notificationKeys= */ emptyList());
     }
 
     /**
      * Animates and loads shortcuts on background thread for this popup container
      */
-    private void showPopupContainer(ItemInfo originalItemInfo,
+    private void loadAppShortcuts(ItemInfo originalItemInfo,
             List<NotificationKeyData> notificationKeys) {
 
         if (ATLEAST_P) {
@@ -390,7 +395,7 @@
         if (deepShortcutCount + systemShortcuts.size() <= SHORTCUT_COLLAPSE_THRESHOLD) {
             // add all system shortcuts including widgets shortcut to same container
             addSystemShortcutsMaterialU(systemShortcuts,
-                    R.layout.system_shortcut_rows_container_material_u,
+                    R.layout.system_shortcut_rows_container,
                     R.layout.system_shortcut);
             addDeepShortcutsMaterialU(deepShortcutCount);
             return;
@@ -458,8 +463,12 @@
             return;
         }
         mSystemShortcutContainer = inflateAndAdd(systemShortcutContainerLayout, this);
-        for (SystemShortcut shortcut : systemShortcuts) {
-            initializeSystemShortcut(systemShortcutLayout, mSystemShortcutContainer, shortcut);
+        for (int i = 0; i < systemShortcuts.size(); i++) {
+            initializeSystemShortcut(
+                    systemShortcutLayout,
+                    mSystemShortcutContainer,
+                    systemShortcuts.get(i),
+                    i < systemShortcuts.size() - 1);
         }
     }
 
@@ -533,20 +542,31 @@
     }
 
     protected void initializeWidgetShortcut(ViewGroup container, SystemShortcut info) {
-        View view = initializeSystemShortcut(R.layout.system_shortcut, container, info);
+        View view = initializeSystemShortcut(R.layout.system_shortcut, container, info, false);
         view.getLayoutParams().width = mContainerWidth;
     }
 
-    protected View initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info) {
-        View view = inflateAndAdd(
-                resId, container, getInsertIndexForSystemShortcut(container, info));
+    /**
+     * Initializes and adds View for given SystemShortcut to a container.
+     * @param resId Resource id to use for SystemShortcut View.
+     * @param container ViewGroup to add the shortcut View to as a parent
+     * @param info The SystemShortcut instance to create a View for.
+     * @param shouldAddSpacer If True, will add a spacer after the shortcut, when showing the
+     *                        SystemShortcut as an icon only. Used to space the shortcut icons
+     *                        evenly.
+     * @return The view inflated for the SystemShortcut
+     */
+    protected View initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info,
+            boolean shouldAddSpacer) {
+        View view = inflateAndAdd(resId, container);
         if (view instanceof DeepShortcutView) {
-            // Expanded system shortcut, with both icon and text shown on white background.
+            // System shortcut takes entire row with icon and text
             final DeepShortcutView shortcutView = (DeepShortcutView) view;
             info.setIconAndLabelFor(shortcutView.getIconView(), shortcutView.getBubbleText());
         } else if (view instanceof ImageView) {
-            // Only the system shortcut icon shows on a gray background header.
+            // System shortcut is just an icon
             info.setIconAndContentDescriptionFor((ImageView) view);
+            if (shouldAddSpacer) inflateAndAdd(R.layout.system_shortcut_spacer, container);
             view.setTooltipText(view.getContentDescription());
         }
         view.setTag(info);
@@ -555,17 +575,6 @@
     }
 
     /**
-     * Returns an index for inserting a shortcut into a container.
-     */
-    private int getInsertIndexForSystemShortcut(ViewGroup container, SystemShortcut shortcut) {
-        final View separator = container.findViewById(R.id.separator);
-
-        return separator != null && shortcut.isLeftGroup() ?
-                container.indexOfChild(separator) :
-                container.getChildCount();
-    }
-
-    /**
      * Determines when the deferred drag should be started.
      *
      * Current behavior:
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 2a452be..3a5ef10 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -21,8 +21,10 @@
 import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS;
 import static com.android.launcher3.LauncherPrefs.RESTORE_DEVICE;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
 
 import android.app.backup.BackupManager;
+import android.appwidget.AppWidgetHost;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -49,7 +51,6 @@
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.LogConfig;
-import com.android.launcher3.widget.LauncherWidgetHolder;
 
 import java.io.InvalidObjectException;
 import java.util.Arrays;
@@ -354,12 +355,11 @@
     private void restoreAppWidgetIdsIfExists(Context context) {
         LauncherPrefs lp = LauncherPrefs.get(context);
         if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) {
-            LauncherWidgetHolder holder = LauncherWidgetHolder.newInstance(context);
+            AppWidgetHost host = new AppWidgetHost(context, APPWIDGET_HOST_ID);
             AppWidgetsRestoredReceiver.restoreAppWidgetIds(context,
                     IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(),
                     IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(),
-                    holder);
-            holder.destroy();
+                    host);
         } else {
             FileLog.d(TAG, "No app widget ids to restore.");
         }
diff --git a/src/com/android/launcher3/search/StringMatcherUtility.java b/src/com/android/launcher3/search/StringMatcherUtility.java
index c66f3a1..28fc4f0 100644
--- a/src/com/android/launcher3/search/StringMatcherUtility.java
+++ b/src/com/android/launcher3/search/StringMatcherUtility.java
@@ -16,13 +16,20 @@
 
 package com.android.launcher3.search;
 
+import android.text.TextUtils;
+
+import com.android.launcher3.util.IntArray;
+
 import java.text.Collator;
+import java.util.stream.IntStream;
 
 /**
  * Utilities for matching query string to target string.
  */
 public class StringMatcherUtility {
 
+    private static final Character SPACE = ' ';
+
     /**
      * Returns {@code true} if {@code query} is a prefix of a substring in {@code target}. How to
      * break target to valid substring is defined in the given {@code matcher}.
@@ -59,6 +66,41 @@
     }
 
     /**
+     * Returns a list of breakpoints wherever the string contains a break. For example:
+     * "t-mobile" would have breakpoints at [0, 1]
+     * "Agar.io" would have breakpoints at [3, 4]
+     * "LEGO®Builder" would have a breakpoint at [4]
+     */
+    public static IntArray getListOfBreakpoints(CharSequence input, StringMatcher matcher) {
+        int inputLength = input.length();
+        if ((inputLength <= 2) || TextUtils.indexOf(input, SPACE) != -1) {
+            // when there is a space in the string, return a list where the elements are the
+            // position of the spaces - 1. This is to make the logic consistent where breakpoints
+            // are placed
+            return IntArray.wrap(IntStream.range(0, inputLength)
+                    .filter(i -> input.charAt(i) == SPACE)
+                    .map(i -> i - 1)
+                    .toArray());
+        }
+        IntArray listOfBreakPoints = new IntArray();
+        int prevType;
+        int thisType = Character.getType(Character.codePointAt(input, 0));
+        int nextType = Character.getType(Character.codePointAt(input, 1));
+        for (int i = 1; i < inputLength; i++) {
+            prevType = thisType;
+            thisType = nextType;
+            nextType = i < (inputLength - 1)
+                    ? Character.getType(Character.codePointAt(input, i + 1))
+                    : Character.UNASSIGNED;
+            if (matcher.isBreak(thisType, prevType, nextType)) {
+                // breakpoint is at previous
+                listOfBreakPoints.add(i-1);
+            }
+        }
+        return listOfBreakPoints;
+    }
+
+    /**
      * Performs locale sensitive string comparison using {@link Collator}.
      */
     public static class StringMatcher {
@@ -118,7 +160,11 @@
             }
             switch (thisType) {
                 case Character.UPPERCASE_LETTER:
-                    if (nextType == Character.UPPERCASE_LETTER) {
+                    // takes care of the case where there are consistent uppercase letters as well
+                    // as a special symbol following the capitalize letters for example: LEGO®
+                    if (nextType != Character.UPPERCASE_LETTER && nextType != Character.OTHER_SYMBOL
+                            && nextType != Character.DECIMAL_DIGIT_NUMBER
+                            && nextType != Character.UNASSIGNED) {
                         return true;
                     }
                     // Follow through
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 628aa9a..295baa3 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -635,6 +635,9 @@
                 primarySnapshot.setTranslationX(0);
             }
             secondarySnapshot.setTranslationY(spaceAboveSnapshot);
+
+            // Reset unused translations
+            primarySnapshot.setTranslationY(0);
         } else {
             int deviceHeightWithoutTaskbar = dp.availableHeightPx - dp.taskbarSize;
             float scale = (float) totalThumbnailHeight / deviceHeightWithoutTaskbar;
@@ -669,6 +672,10 @@
                 View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
                 View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
                         View.MeasureSpec.EXACTLY));
+        primarySnapshot.setScaleX(1);
+        secondarySnapshot.setScaleX(1);
+        primarySnapshot.setScaleY(1);
+        secondarySnapshot.setScaleY(1);
     }
 
     @Override
@@ -699,13 +706,13 @@
                     : deviceProfile.getInsets().left;
             int fullscreenMidpointFromBottom = ((deviceProfile.widthPx
                     - fullscreenInsetThickness) / 2);
-            float midpointFromBottomPct = (float) fullscreenMidpointFromBottom
+            float midpointFromEndPct = (float) fullscreenMidpointFromBottom
                     / deviceProfile.widthPx;
             float insetPct = (float) fullscreenInsetThickness / deviceProfile.widthPx;
             int spaceAboveSnapshots = 0;
             int overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots;
             int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness
-                    * midpointFromBottomPct);
+                    * midpointFromEndPct);
             int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
 
             if (deviceProfile.isSeascape()) {
diff --git a/src/com/android/launcher3/util/MultiTranslateDelegate.java b/src/com/android/launcher3/util/MultiTranslateDelegate.java
index 0b5bc8d..1cb7a45 100644
--- a/src/com/android/launcher3/util/MultiTranslateDelegate.java
+++ b/src/com/android/launcher3/util/MultiTranslateDelegate.java
@@ -33,7 +33,7 @@
     public static final int INDEX_REORDER_PREVIEW_OFFSET = 1;
     public static final int INDEX_MOVE_FROM_CENTER_ANIM = 2;
 
-    // Specific for icons and folders
+    // Specific for items in taskbar (icons, folders, qsb)
     public static final int INDEX_TASKBAR_ALIGNMENT_ANIM = 3;
     public static final int INDEX_TASKBAR_REVEAL_ANIM = 4;
 
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 382ba36..cb6a46c 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -238,8 +238,8 @@
     /** Return extra space revealed during predictive back animation. */
     @Px
     protected int getBottomOffsetPx() {
-        return (int) (getMeasuredHeight()
-                * (1 - PREDICTIVE_BACK_MIN_SCALE) / 2);
+        final int height = getMeasuredHeight();
+        return (int) ((height / PREDICTIVE_BACK_MIN_SCALE - height) / 2);
     }
 
     /**
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 10f40b7..b6f6223 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -45,7 +45,6 @@
 import android.view.WindowInsetsController;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Toast;
-import android.window.SplashScreen;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -321,14 +320,7 @@
             return false;
         }
 
-        Bundle optsBundle = null;
-        if (v != null) {
-            optsBundle = getActivityLaunchOptions(v, item).toBundle();
-        } else if (item != null && item.animationType == LauncherSettings.Animation.DEFAULT_NO_ICON
-                && Utilities.ATLEAST_T) {
-            optsBundle = ActivityOptions.makeBasic()
-                    .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR).toBundle();
-        }
+        Bundle optsBundle = (v != null) ? getActivityLaunchOptions(v, item).toBundle() : null;
         UserHandle user = item == null ? null : item.user;
 
         // Prepare intent
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 55af622..e233e46 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -95,6 +95,9 @@
     private ClipIconView mClipIconView;
     private @Nullable Drawable mBadge;
 
+    // A view whose visibility should update in sync with mOriginalIcon.
+    private @Nullable View mMatchVisibilityView;
+
     private View mOriginalIcon;
     private RectF mPositionOut;
     private Runnable mOnTargetChangeRunnable;
@@ -386,7 +389,7 @@
      * Checks if the icon result is loaded. If true, we set the icon immediately. Else, we add a
      * callback to set the icon once the icon result is loaded.
      */
-    private void checkIconResult(View originalView) {
+    private void checkIconResult() {
         CancellationSignal cancellationSignal = new CancellationSignal();
 
         if (mIconLoadResult == null) {
@@ -399,7 +402,7 @@
                 setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
                         mIconLoadResult.btvDrawable, mIconLoadResult.iconOffset);
                 setVisibility(VISIBLE);
-                setIconAndDotVisible(originalView, false);
+                updateViewsVisibility(false  /* isVisible */);
             } else {
                 mIconLoadResult.onIconLoaded = () -> {
                     if (cancellationSignal.isCanceled()) {
@@ -410,7 +413,7 @@
                             mIconLoadResult.btvDrawable, mIconLoadResult.iconOffset);
 
                     setVisibility(VISIBLE);
-                    setIconAndDotVisible(originalView, false);
+                    updateViewsVisibility(false  /* isVisible */);
                 };
                 mLoadIconSignal = cancellationSignal;
             }
@@ -481,9 +484,9 @@
             // No need to wait for icon load since we can display the BubbleTextView drawable.
             setVisibility(View.VISIBLE);
         }
-        if (!mIsOpening && mOriginalIcon != null) {
+        if (!mIsOpening) {
             // When closing an app, we want the item on the workspace to be invisible immediately
-            setIconAndDotVisible(mOriginalIcon, false);
+            updateViewsVisibility(false  /* isVisible */);
         }
     }
 
@@ -562,13 +565,14 @@
     /**
      * Creates a floating icon view for {@param originalView}.
      * @param originalView The view to copy
+     * @param secondView A view whose visibility should update in sync with originalView.
      * @param hideOriginal If true, it will hide {@param originalView} while this view is visible.
      *                     Else, we will not draw anything in this view.
      * @param positionOut Rect that will hold the size and position of v.
      * @param isOpening True if this view replaces the icon for app open animation.
      */
     public static FloatingIconView getFloatingIconView(Launcher launcher, View originalView,
-            boolean hideOriginal, RectF positionOut, boolean isOpening) {
+            @Nullable View secondView, boolean hideOriginal, RectF positionOut, boolean isOpening) {
         final DragLayer dragLayer = launcher.getDragLayer();
         ViewGroup parent = (ViewGroup) dragLayer.getParent();
         FloatingIconView view = launcher.getViewCache().getView(R.layout.floating_icon_view,
@@ -578,6 +582,7 @@
         // Init properties before getting the drawable.
         view.mIsOpening = isOpening;
         view.mOriginalIcon = originalView;
+        view.mMatchVisibilityView = secondView;
         view.mPositionOut = positionOut;
 
         // Get the drawable on the background thread
@@ -597,7 +602,8 @@
         view.matchPositionOf(launcher, originalView, isOpening, positionOut);
 
         // We need to add it to the overlay, but keep it invisible until animation starts..
-        setIconAndDotVisible(view, false);
+        view.setVisibility(View.INVISIBLE);
+
         parent.addView(view);
         dragLayer.addView(view.mListenerView);
         view.mListenerView.setListener(view::fastFinish);
@@ -606,7 +612,7 @@
             view.mEndRunnable = null;
 
             if (hideOriginal) {
-                setIconAndDotVisible(originalView, true);
+                view.updateViewsVisibility(true /* isVisible */);
                 view.finish(dragLayer);
             } else {
                 view.finish(dragLayer);
@@ -617,12 +623,21 @@
         // Must be called after the fastFinish listener and end runnable is created so that
         // the icon is not left in a hidden state.
         if (shouldLoadIcon) {
-            view.checkIconResult(originalView);
+            view.checkIconResult();
         }
 
         return view;
     }
 
+    private void updateViewsVisibility(boolean isVisible) {
+        if (mOriginalIcon != null) {
+            setIconAndDotVisible(mOriginalIcon, isVisible);
+        }
+        if (mMatchVisibilityView != null) {
+            setIconAndDotVisible(mMatchVisibilityView, isVisible);
+        }
+    }
+
     private void finish(DragLayer dragLayer) {
         ((ViewGroup) dragLayer.getParent()).removeView(this);
         dragLayer.removeView(mListenerView);
diff --git a/src/com/android/launcher3/views/IconButtonView.java b/src/com/android/launcher3/views/IconButtonView.java
index 71f6756..0ac1919 100644
--- a/src/com/android/launcher3/views/IconButtonView.java
+++ b/src/com/android/launcher3/views/IconButtonView.java
@@ -29,6 +29,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
 
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
@@ -78,6 +79,12 @@
         }
     }
 
+    @Override
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        super.onPopulateAccessibilityEvent(event);
+        event.getText().add(this.getContentDescription());
+    }
+
     /** Sets given Drawable as icon */
     public void setIconDrawable(@NonNull Drawable drawable) {
         ColorStateList tintList = getBackgroundTintList();
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index bea7517..8e67eb1 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -322,13 +322,6 @@
     }
 
     /**
-     * Delete the host
-     */
-    public void deleteHost() {
-        mWidgetHost.deleteHost();
-    }
-
-    /**
      * @return The app widget ids
      */
     @NonNull
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index c69ec2c..f036b3e 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -107,6 +107,7 @@
     public static final String REQUEST_CLEAR_DATA = "clear-data";
     public static final String REQUEST_USE_TEST_WORKSPACE_LAYOUT = "use-test-workspace-layout";
     public static final String REQUEST_USE_TEST2_WORKSPACE_LAYOUT = "use-test2-workspace-layout";
+    public static final String REQUEST_USE_TAPL_WORKSPACE_LAYOUT = "use-tapl-workspace-layout";
     public static final String REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT =
             "use-default-workspace-layout";
     public static final String REQUEST_HOTSEAT_ICON_NAMES = "get-hotseat-icon-names";
diff --git a/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java b/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
index 3b53255..0ba46f4 100644
--- a/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
+++ b/tests/src/com/android/launcher3/search/StringMatcherUtilityTest.java
@@ -15,8 +15,10 @@
  */
 package com.android.launcher3.search;
 
+import static com.android.launcher3.search.StringMatcherUtility.getListOfBreakpoints;
 import static com.android.launcher3.search.StringMatcherUtility.matches;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -25,6 +27,7 @@
 
 import com.android.launcher3.search.StringMatcherUtility.StringMatcher;
 import com.android.launcher3.search.StringMatcherUtility.StringMatcherSpace;
+import com.android.launcher3.util.IntArray;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -138,4 +141,96 @@
         assertFalse(matches("phant", "elephant", MATCHER_SPACE));
         assertFalse(matches("elephants", "elephant", MATCHER_SPACE));
     }
+
+    @Test
+    public void testStringWithProperBreaks() {
+        // empty string
+        assertEquals(IntArray.wrap(), getListOfBreakpoints("", MATCHER));
+
+        // should be "D Dz" that's why breakpoint is at 0
+        assertEquals(IntArray.wrap(0), getListOfBreakpoints("DDz", MATCHER));
+
+        // test all caps and all lower-case
+        assertEquals(IntArray.wrap(), getListOfBreakpoints("SNKRS", MATCHER));
+        assertEquals(IntArray.wrap(), getListOfBreakpoints("flutterappflorafy", MATCHER));
+        assertEquals(IntArray.wrap(), getListOfBreakpoints("LEGO®", MATCHER));
+
+        // test camel case
+        // breakpoint at 9 to be "flutterapp Florafy"
+        assertEquals(IntArray.wrap(9), getListOfBreakpoints("flutterappFlorafy", MATCHER));
+        // breakpoint at 4 to be "Metro Zone"
+        assertEquals(IntArray.wrap(4), getListOfBreakpoints("MetroZone", MATCHER));
+        // breakpoint at 4,5 to be "metro X Zone"
+        assertEquals(IntArray.wrap(4,5), getListOfBreakpoints("metroXZone", MATCHER));
+        // breakpoint at 0 to be "G Pay"
+        assertEquals(IntArray.wrap(0), getListOfBreakpoints("GPay", MATCHER));
+        // breakpoint at 4 to be "Whats App"
+        assertEquals(IntArray.wrap(4), getListOfBreakpoints("WhatsApp", MATCHER));
+        // breakpoint at 2 to be "aaa A"
+        assertEquals(IntArray.wrap(2), getListOfBreakpoints("aaaA", MATCHER));
+        // breakpoint at 4,12,16 to be "Other Launcher Test App"
+        assertEquals(IntArray.wrap(4,12,16),
+                getListOfBreakpoints("OtherLauncherTestApp", MATCHER));
+
+        // test with TITLECASE_LETTER
+        // should be "DDz" that's why there are no break points
+        assertEquals(IntArray.wrap(), getListOfBreakpoints("DDz", MATCHER));
+        // breakpoint at 0 to be "D DDž"
+        assertEquals(IntArray.wrap(0), getListOfBreakpoints("DDDž", MATCHER));
+        // breakpoint at 0 because there is a space to be "Dž DD"
+        assertEquals(IntArray.wrap(0), getListOfBreakpoints("Dž DD", MATCHER));
+        // breakpoint at 1 to be "Dw Dz"
+        assertEquals(IntArray.wrap(1), getListOfBreakpoints("DwDz", MATCHER));
+        // breakpoint at 0,2 to be "Dw Dz"
+        assertEquals(IntArray.wrap(0,2), getListOfBreakpoints("wDwDz", MATCHER));
+        // breakpoint at 1,3 to be "ᾋw Dw Dz"
+        assertEquals(IntArray.wrap(1,3), getListOfBreakpoints("ᾋwDwDz", MATCHER));
+        // breakpoint at 0,2,4 to be "ᾋ ᾋw Dw Dz"
+        assertEquals(IntArray.wrap(0,2,4), getListOfBreakpoints("ᾋᾋwDwDz", MATCHER));
+        // breakpoint at 0,2,4 to be "ᾋ ᾋw Dw Dz®"
+        assertEquals(IntArray.wrap(0,2,4), getListOfBreakpoints("ᾋᾋwDwDz®", MATCHER));
+
+        // test with numbers and symbols
+        // breakpoint at 3,11 to be "Test Activity 13"
+        assertEquals(IntArray.wrap(3,11), getListOfBreakpoints("TestActivity13", MATCHER));
+        // breakpoint at 3, 4, 12, 13 as the breakpoints are at the dashes
+        assertEquals(IntArray.wrap(3,4,12,13),
+                getListOfBreakpoints("Test-Activity-12", MATCHER));
+        // breakpoint at 1 to be "AA 2"
+        assertEquals(IntArray.wrap(1), getListOfBreakpoints("AA2", MATCHER));
+        // breakpoint at 1 to be "AAA 2"
+        assertEquals(IntArray.wrap(2), getListOfBreakpoints("AAA2", MATCHER));
+        // breakpoint at 1 to be "ab 2"
+        assertEquals(IntArray.wrap(1), getListOfBreakpoints("ab2", MATCHER));
+        // breakpoint at 1,2 to be "el 3 suhwee"
+        assertEquals(IntArray.wrap(1,2), getListOfBreakpoints("el3suhwee", MATCHER));
+        // breakpoint at 0,1 as the breakpoints are at '-'
+        assertEquals(IntArray.wrap(0,1), getListOfBreakpoints("t-mobile", MATCHER));
+        assertEquals(IntArray.wrap(0,1), getListOfBreakpoints("t-Mobile", MATCHER));
+        // breakpoint at 0,1,2 as the breakpoints are at '-'
+        assertEquals(IntArray.wrap(0,1,2), getListOfBreakpoints("t--Mobile", MATCHER));
+        // breakpoint at 1,2,3 as the breakpoints are at '-'
+        assertEquals(IntArray.wrap(1,2,3), getListOfBreakpoints("tr--Mobile", MATCHER));
+        // breakpoint at 3,4 as the breakpoints are at '.'
+        assertEquals(IntArray.wrap(3,4), getListOfBreakpoints("Agar.io", MATCHER));
+        assertEquals(IntArray.wrap(3,4), getListOfBreakpoints("Hole.Io", MATCHER));
+
+        // breakpoint at 0 to be "µ Torrent®"
+        assertEquals(IntArray.wrap(0), getListOfBreakpoints("µTorrent®", MATCHER));
+        // breakpoint at 4 to be "LEGO® Builder"
+        assertEquals(IntArray.wrap(4), getListOfBreakpoints("LEGO®Builder", MATCHER));
+        // breakpoint at 4 to be "LEGO® builder"
+        assertEquals(IntArray.wrap(4), getListOfBreakpoints("LEGO®builder", MATCHER));
+        // breakpoint at 4 to be "lego® builder"
+        assertEquals(IntArray.wrap(4), getListOfBreakpoints("lego®builder", MATCHER));
+
+        // test string with spaces - where the breakpoints are right before where the spaces are at
+        assertEquals(IntArray.wrap(3,8), getListOfBreakpoints("HEAD BALL 2", MATCHER));
+        assertEquals(IntArray.wrap(2,8),
+                getListOfBreakpoints("OFL Agent Application", MATCHER));
+        assertEquals(IntArray.wrap(0,2), getListOfBreakpoints("D D z", MATCHER));
+        assertEquals(IntArray.wrap(6), getListOfBreakpoints("Battery Stats", MATCHER));
+        assertEquals(IntArray.wrap(5,9,15),
+                getListOfBreakpoints("System UWB Field Test", MATCHER));
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
new file mode 100644
index 0000000..528f7ac
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -0,0 +1,264 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3.ui;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TWOLINE_ALLAPPS;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.view.ViewGroup;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.search.StringMatcherUtility;
+import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.views.BaseDragLayer;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for testing modifyTitleToSupportMultiLine() in BubbleTextView.java
+ * This class tests a couple of strings and uses the getMaxLines() to determine if the test passes.
+ * Verifying with getMaxLines() is sufficient since BubbleTextView can only be in one line or
+ * two lines, and this is enough to ensure whether the string should be specifically wrapped onto
+ * the second line and to ensure truncation.
+ */
+public class BubbleTextViewTest {
+
+    private static final StringMatcherUtility.StringMatcher
+            MATCHER = StringMatcherUtility.StringMatcher.getInstance();
+    private static final int ONE_LINE = 1;
+    private static final int TWO_LINE = 2;
+    private static final String TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT = "Battery Stats";
+    private static final String TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT =
+            "Battery\nStats";
+    private static final String TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT =
+            "flutterappflorafy";
+    private static final String TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT =
+            "System UWB Field Test";
+    private static final String TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT =
+            "System\nUWB Field Test";
+    private static final String TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT =
+            "LEGO®Builder";
+    private static final String TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT_RESULT =
+            "LEGO®\nBuilder";
+    private static final String EMPTY_STRING = "";
+    private static final int CHAR_CNT = 7;
+
+    private BubbleTextView mBubbleTextView;
+    private ItemInfoWithIcon mItemInfoWithIcon;
+    private Context mContext;
+    private int mLimitedWidth;
+
+    @Before
+    public void setUp() throws Exception {
+        Utilities.enableRunningInTestHarnessForTests();
+        mContext = new ActivityContextWrapper(getApplicationContext());
+        mBubbleTextView = new BubbleTextView(mContext);
+        mBubbleTextView.reset();
+        mBubbleTextView.setDisplayAllApps();
+        assertEquals(ONE_LINE, mBubbleTextView.getMaxLines());
+
+        BubbleTextView testView = new BubbleTextView(mContext);
+        testView.setTypeface(Typeface.MONOSPACE);
+        testView.setText("B");
+        // calculate the maxWidth of the textView by calculating the width of one monospace
+        // character * CHAR_CNT
+        mLimitedWidth =
+                (int) (testView.getPaint().measureText(testView.getText().toString()) * CHAR_CNT);
+        // needed otherwise there is a NPE during setText() on checkForRelayout()
+        mBubbleTextView.setLayoutParams(
+                new ViewGroup.LayoutParams(mLimitedWidth,
+                BaseDragLayer.LayoutParams.WRAP_CONTENT));
+        mItemInfoWithIcon = new ItemInfoWithIcon() {
+            @Override
+            public ItemInfoWithIcon clone() {
+                return null;
+            }
+        };
+    }
+
+    @Test
+    public void testEmptyString_flagOn() {
+        TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
+        mItemInfoWithIcon.title = EMPTY_STRING;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, 0);
+        mBubbleTextView.onPreDraw();
+        assertEquals(ONE_LINE, mBubbleTextView.getMaxLines());
+    }
+
+    @Test
+    public void testEmptyString_flagOff() {
+        TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
+        mItemInfoWithIcon.title = EMPTY_STRING;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, 0);
+        mBubbleTextView.onPreDraw();
+        assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
+    }
+
+    @Test
+    public void testStringWithSpaceLongerThanCharLimit_flagOn() {
+        TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
+        // test string: "Battery Stats"
+        mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, 0);
+        mBubbleTextView.onPreDraw();
+        assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
+    }
+
+    @Test
+    public void testStringWithSpaceLongerThanCharLimit_flagOff() {
+        TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
+        // test string: "Battery Stats"
+        mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, 0);
+        mBubbleTextView.onPreDraw();
+        assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
+    }
+
+    @Test
+    public void testLongStringNoSpaceLongerThanCharLimit_flagOn() {
+        TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
+        // test string: "flutterappflorafy"
+        mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, 0);
+        mBubbleTextView.onPreDraw();
+        assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
+    }
+
+    @Test
+    public void testLongStringNoSpaceLongerThanCharLimit_flagOff() {
+        TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
+        // test string: "flutterappflorafy"
+        mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, 0);
+        mBubbleTextView.onPreDraw();
+        assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
+    }
+
+    @Test
+    public void testLongStringWithSpaceLongerThanCharLimit_flagOn() {
+        TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
+        // test string: "System UWB Field Test"
+        mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, 0);
+        mBubbleTextView.onPreDraw();
+        assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
+    }
+
+    @Test
+    public void testLongStringWithSpaceLongerThanCharLimit_flagOff() {
+        TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
+        // test string: "System UWB Field Test"
+        mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, 0);
+        mBubbleTextView.onPreDraw();
+        assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
+    }
+
+    @Test
+    public void testLongStringSymbolLongerThanCharLimit_flagOn() {
+        TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true);
+        // test string: "LEGO®Builder"
+        mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, 0);
+        mBubbleTextView.onPreDraw();
+        assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
+    }
+
+    @Test
+    public void testLongStringSymbolLongerThanCharLimit_flagOff() {
+        TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false);
+        // test string: "LEGO®Builder"
+        mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, 0);
+        mBubbleTextView.onPreDraw();
+        assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
+    }
+
+    @Test
+    public void modifyTitleToSupportMultiLine_TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT() {
+        // test string: "Battery Stats"
+        IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
+                TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
+        CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
+                TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
+                breakPoints);
+        assertEquals(TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
+    }
+
+    @Test
+    public void modifyTitleToSupportMultiLine_TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT() {
+        // test string: "flutterappflorafy"
+        IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
+                TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
+        CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
+                TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
+                breakPoints);
+        assertEquals(TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, newString);
+    }
+
+    @Test
+    public void modifyTitleToSupportMultiLine_TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT() {
+        // test string: "System UWB Field Test"
+        IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
+                TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
+        CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
+                TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
+                breakPoints);
+        assertEquals(TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
+    }
+
+    @Test
+    public void modifyTitleToSupportMultiLine_TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT() {
+        // test string: "LEGO®Builder"
+        IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
+                TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT, MATCHER);
+        CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
+                TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
+                breakPoints);
+        assertEquals(TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 5f516eb..fef1708 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -225,6 +225,10 @@
     @Test
     @ScreenRecord // b/202433017
     public void testWorkspace() throws Exception {
+        // Make sure there is an instance of chrome on the hotseat
+        mLauncher.useTaplWorkspaceLayoutOnReload();
+        clearLauncherData();
+
         final Workspace workspace = mLauncher.getWorkspace();
 
         // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
index 667290f..82d9630 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
@@ -54,14 +54,5 @@
         return createMenuItem(menuItem);
     }
 
-    /**
-     * Returns a menu item that matches the text "Split screen". Fails if it doesn't exist.
-     */
-    public SplitScreenMenuItem getSplitScreenMenuItem() {
-        final UiObject2 menuItem = mLauncher.waitForObjectInContainer(mDeepShortcutsContainer,
-                AppIcon.getAppIconSelector("Split screen", mLauncher));
-        return new SplitScreenMenuItem(mLauncher, menuItem);
-    }
-
     protected abstract AppIconMenuItem createMenuItem(UiObject2 menuItem);
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 48e327f..3dcb437 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -76,27 +76,6 @@
         }
     }
 
-    /**
-     * Clicks a launcher object to initiate splitscreen, where the selected app will be one of two
-     * apps running on the screen. Should be called when Launcher is in a "split staging" state
-     * and is waiting for the user's selection of a second app. Expects a SPLIT_START_EVENT to be
-     * fired when the click is executed.
-     */
-    public LaunchedAppState launchIntoSplitScreen() {
-        try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
-                "want to launch split tasks from " + launchableType())) {
-            LauncherInstrumentation.log("Launchable.launch before click "
-                    + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
-
-            mLauncher.clickLauncherObject(mObject);
-
-            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, OverviewTask.SPLIT_START_EVENT);
-                return new LaunchedAppState(mLauncher);
-            }
-        }
-    }
-
     protected LaunchedAppState assertAppLaunched(BySelector selector) {
         mLauncher.assertTrue(
                 "App didn't start: (" + selector + ")",
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 52994a5..5c4b707 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1860,6 +1860,15 @@
         getTestInfo(TestProtocol.REQUEST_USE_TEST2_WORKSPACE_LAYOUT);
     }
 
+
+    /**
+     * Reloads the workspace with a test layout that includes the chrome Activity app icon on the
+     * hotseat.
+     */
+    public void useTaplWorkspaceLayoutOnReload() {
+        getTestInfo(TestProtocol.REQUEST_USE_TAPL_WORKSPACE_LAYOUT);
+    }
+
     /** Reloads the workspace with the default layout defined by the user's grid size selection. */
     public void useDefaultWorkspaceLayoutOnReload() {
         getTestInfo(TestProtocol.REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT);
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 90f3d13..adc993d 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -37,9 +37,10 @@
 public final class OverviewTask {
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
 
-    static final Pattern TASK_START_EVENT = Pattern.compile("startActivityFromRecentsAsync");
-    static final Pattern SPLIT_SELECT_EVENT = Pattern.compile("enterSplitSelect");
-    static final Pattern SPLIT_START_EVENT = Pattern.compile("launchSplitTasks");
+    static final Pattern TASK_START_EVENT =
+            Pattern.compile("startActivityFromRecentsAsync");
+    static final Pattern SPLIT_START_EVENT =
+            Pattern.compile("launchSplitTasks");
     private final LauncherInstrumentation mLauncher;
     private final UiObject2 mTask;
     private final BaseOverview mOverview;
diff --git a/tests/tapl/com/android/launcher3/tapl/SplitScreenMenuItem.java b/tests/tapl/com/android/launcher3/tapl/SplitScreenMenuItem.java
deleted file mode 100644
index 47cf20b..0000000
--- a/tests/tapl/com/android/launcher3/tapl/SplitScreenMenuItem.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.launcher3.tapl;
-
-import androidx.test.uiautomator.UiObject2;
-
-import com.android.launcher3.testing.shared.TestProtocol;
-
-/**
- * A class representing the "Split screen" menu item in the app long-press menu. Used for TAPL
- * testing in a similar way as other menu items {@link AppIconMenuItem}, but unlike AppIconMenuItem,
- * the split screen command does not trigger an app launch. Instead, it causes Launcher to shift to
- * a different state (OverviewSplitSelect).
- */
-public final class SplitScreenMenuItem {
-    private final LauncherInstrumentation mLauncher;
-    private final UiObject2 mObject;
-
-    SplitScreenMenuItem(LauncherInstrumentation launcher, UiObject2 object) {
-        mLauncher = launcher;
-        mObject = object;
-    }
-
-    /**
-     * Executes a click command on this menu item. Expects a SPLIT_SELECT_EVENT to be fired.
-     */
-    public void click() {
-        try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
-                "want to enter split select from app long-press menu")) {
-            LauncherInstrumentation.log("clicking on split screen menu item "
-                    + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
-
-            mLauncher.clickLauncherObject(mObject);
-
-            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, OverviewTask.SPLIT_SELECT_EVENT);
-                mLauncher.waitForLauncherObject("split_placeholder");
-            }
-        }
-    }
-}