Merge "Revert "Updating the scroll calculation from recyclerView to avoid view inflation"" into tm-qpr-dev
diff --git a/go/quickstep/res/values-my/strings.xml b/go/quickstep/res/values-my/strings.xml
index 0ca0e9c..cbb485a 100644
--- a/go/quickstep/res/values-my/strings.xml
+++ b/go/quickstep/res/values-my/strings.xml
@@ -5,7 +5,7 @@
     <string name="action_listen" msgid="2370304050784689486">"နားထောင်ရန်"</string>
     <string name="action_translate" msgid="8028378961867277746">"ဘာသာပြန်ရန်"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"ရပြီ"</string>
+    <string name="dialog_acknowledge" msgid="2804025517675853172">"နားလည်ပြီ"</string>
     <string name="dialog_cancel" msgid="6464336969134856366">"မလုပ်တော့"</string>
     <string name="dialog_settings" msgid="6564397136021186148">"ဆက်တင်များ"</string>
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"ဖန်သားပြင်ပေါ်ရှိ စာသားကို ဘာသာပြန်ပါ (သို့) နားထောင်ပါ"</string>
diff --git a/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java b/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java
index 0c1f05f..0b17a7b 100644
--- a/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java
+++ b/quickstep/ext_tests/src/com/android/quickstep/DebugQuickstepTestInformationHandler.java
@@ -15,24 +15,13 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
 import android.content.Context;
-import android.content.res.Resources;
 import android.os.Bundle;
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
 import com.android.launcher3.testing.DebugTestInformationHandler;
 import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.quickstep.TouchInteractionService.TISBinder;
-import com.android.quickstep.util.TISBindHelper;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
 
 /**
  * Class to handle requests from tests, including debug ones, to Quickstep Launcher builds.
@@ -49,78 +38,14 @@
     @Override
     public Bundle call(String method, String arg, @Nullable Bundle extras) {
         Bundle response = new Bundle();
-        switch (method) {
-            case TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING:
-                runOnTISBinder(tisBinder -> {
-                    enableManualTaskbarStashing(tisBinder, true);
-                });
-                return response;
-
-            case TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING:
-                runOnTISBinder(tisBinder -> {
-                    enableManualTaskbarStashing(tisBinder, false);
-                });
-                return response;
-
-            case TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED:
-                runOnTISBinder(tisBinder -> {
-                    enableManualTaskbarStashing(tisBinder, true);
-
-                    // Allow null-pointer to catch illegal states.
-                    tisBinder.getTaskbarManager().getCurrentActivityContext()
-                            .unstashTaskbarIfStashed();
-
-                    enableManualTaskbarStashing(tisBinder, false);
-                });
-                return response;
-
-            case TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT: {
-                final Resources resources = mContext.getResources();
-                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                        resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size));
-                return response;
-            }
-
-            case TestProtocol.REQUEST_RECREATE_TASKBAR:
-                // Allow null-pointer to catch illegal states.
-                runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbar());
-                return response;
-
-            default:
-                response = super.call(method, arg, extras);
-                if (response != null) return response;
-                return mDebugTestInformationHandler.call(method, arg, extras);
+        if (TestProtocol.REQUEST_RECREATE_TASKBAR.equals(method)) {
+            // Allow null-pointer to catch illegal states.
+            runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbar());
+            return response;
         }
-    }
-
-    private void enableManualTaskbarStashing(TISBinder tisBinder, boolean enable) {
-        // Allow null-pointer to catch illegal states.
-        tisBinder.getTaskbarManager().getCurrentActivityContext().enableManualStashingForTests(
-                enable);
-    }
-
-    /**
-     * Runs the given command on the UI thread, after ensuring we are connected to
-     * TouchInteractionService.
-     */
-    private void runOnTISBinder(Consumer<TISBinder> connectionCallback) {
-        try {
-            CountDownLatch countDownLatch = new CountDownLatch(1);
-            TISBindHelper helper = MAIN_EXECUTOR.submit(() ->
-                    new TISBindHelper(mContext, tisBinder -> {
-                        connectionCallback.accept(tisBinder);
-                        countDownLatch.countDown();
-                    })).get();
-            countDownLatch.await();
-            MAIN_EXECUTOR.submit(helper::onDestroy);
-        } catch (ExecutionException | InterruptedException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private interface UIThreadCommand {
-
-        void execute(Launcher launcher);
+        response = super.call(method, arg, extras);
+        if (response != null) return response;
+        return mDebugTestInformationHandler.call(method, arg, extras);
     }
 }
 
diff --git a/quickstep/res/drawable/ic_floating_task_button.xml b/quickstep/res/drawable/ic_floating_task_button.xml
new file mode 100644
index 0000000..e50f65c
--- /dev/null
+++ b/quickstep/res/drawable/ic_floating_task_button.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z"
+      android:fillColor="#636C6F"/>
+  <path
+      android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z"
+      android:fillColor="#636C6F"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
new file mode 100644
index 0000000..0c8543f
--- /dev/null
+++ b/quickstep/res/layout/task_desktop.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.quickstep.views.DesktopTaskView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:clipChildren="true"
+    android:clipToOutline="true"
+    android:defaultFocusHighlightEnabled="false"
+    android:focusable="true">
+
+    <!--
+         TODO(b249371338): DesktopTaskView extends from TaskView. TaskView expects TaskThumbnailView
+         and IconView with these ids to be present. Need to refactor RecentsView to accept child
+         views that do not inherint from TaskView only or create a generic TaskView that have
+         N number of tasks.
+     -->
+    <com.android.quickstep.views.TaskThumbnailView
+        android:id="@+id/snapshot"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <com.android.quickstep.views.IconView
+        android:id="@+id/icon"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:focusable="false"
+        android:importantForAccessibility="no"
+        android:visibility="gone" />
+
+</com.android.quickstep.views.DesktopTaskView>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 6ea7d8a..1789c2b 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -20,7 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="recent_task_option_pin" msgid="7929860679018978258">"ሰካ"</string>
-    <string name="recent_task_option_freeform" msgid="48863056265284071">"ነጻ ቅጽ"</string>
+    <string name="recent_task_option_freeform" msgid="48863056265284071">"ነፃ ቅጽ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ምንም የቅርብ ጊዜ ንጥሎች የሉም"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"የመተግበሪያ አጠቃቀም ቅንብሮች"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ሁሉንም አጽዳ"</string>
diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml
index 905fbda..bc5d02a 100644
--- a/quickstep/res/values-land/dimens.xml
+++ b/quickstep/res/values-land/dimens.xml
@@ -80,4 +80,8 @@
     <dimen name="taskbar_button_margin_6_5">219.6dp</dimen>
     <dimen name="taskbar_button_margin_4_5">84dp</dimen>
     <dimen name="taskbar_button_margin_4_4">79dp</dimen>
+    <dimen name="taskbar_contextual_button_margin">48dp</dimen>
+    <dimen name="taskbar_suw_frame">96dp</dimen>
+    <dimen name="taskbar_suw_insets">24dp</dimen>
+
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 3b4a28b..198a676 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -43,4 +43,9 @@
     <!-- Accessibility actions -->
     <item type="id" name="action_move_to_top_or_left" />
     <item type="id" name="action_move_to_bottom_or_right" />
+
+    <!-- The max scale for the wallpaper when it's zoomed in -->
+    <item name="config_wallpaperMaxScale" format="float" type="dimen">
+        @*android:dimen/config_wallpaperMaxScale
+    </item>
 </resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index c85e71c..3add4dc 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -258,7 +258,10 @@
     <dimen name="taskbar_contextual_button_padding">16dp</dimen>
     <dimen name="taskbar_contextual_padding_top">8dp</dimen>
     <dimen name="taskbar_nav_buttons_size">44dp</dimen>
-    <dimen name="taskbar_contextual_button_margin">47dp</dimen>
+    <dimen name="taskbar_split_instructions_margin">48dp</dimen>
+    <dimen name="taskbar_contextual_button_margin">120dp</dimen>
+    <dimen name="taskbar_suw_insets">48dp</dimen>
+    <dimen name="taskbar_suw_frame">48dp</dimen>
     <dimen name="taskbar_hotseat_nav_spacing">24dp</dimen>
     <dimen name="taskbar_contextual_buttons_size">35dp</dimen>
     <dimen name="taskbar_stashed_size">24dp</dimen>
@@ -268,6 +271,7 @@
     <dimen name="taskbar_stashed_handle_height">4dp</dimen>
     <dimen name="taskbar_edu_wave_anim_trans_y">25dp</dimen>
     <dimen name="taskbar_edu_wave_anim_trans_y_return_overshoot">4dp</dimen>
+    <dimen name="taskbar_edu_horizontal_margin">112dp</dimen>
     <dimen name="taskbar_nav_buttons_width_kids">88dp</dimen>
     <dimen name="taskbar_nav_buttons_height_kids">40dp</dimen>
     <dimen name="taskbar_nav_buttons_corner_radius_kids">40dp</dimen>
@@ -283,4 +287,9 @@
     <dimen name="taskbar_button_margin_4_5">47dp</dimen>
     <dimen name="taskbar_button_margin_4_4">47dp</dimen>
     <dimen name="taskbar_button_margin_default">47dp</dimen>
+
+    <!-- Launcher splash screen -->
+    <!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
+    <!--     starting_surface_exit_animation_window_shift_length -->
+    <dimen name="starting_surface_exit_animation_window_shift_length">20dp</dimen>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 521b3fe..9a1ed4d 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -42,7 +42,7 @@
 import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
-import static com.android.launcher3.statehandlers.DepthController.DEPTH;
+import static com.android.launcher3.statehandlers.DepthController.STATE_DEPTH;
 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
@@ -57,6 +57,8 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.app.ActivityOptions;
+import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -83,6 +85,7 @@
 import android.view.View;
 import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
+import android.view.WindowManager;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
@@ -123,8 +126,6 @@
 import com.android.quickstep.util.WorkspaceRevealAnim;
 import com.android.quickstep.views.FloatingWidgetView;
 import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.ActivityCompat;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.BlurUtils;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -134,7 +135,6 @@
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.RemoteTransitionCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
-import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.wm.shell.startingsurface.IStartingWindowListener;
 
 import java.util.ArrayList;
@@ -293,8 +293,10 @@
         RemoteAnimationAdapterCompat adapterCompat =
                 new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay,
                         mLauncher.getIApplicationThread());
-        return new ActivityOptionsWrapper(
-                ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), onEndCallback);
+        ActivityOptions options = ActivityOptions.makeRemoteAnimation(
+                adapterCompat.getWrapped(),
+                adapterCompat.getRemoteTransition().getTransition());
+        return new ActivityOptionsWrapper(options, onEndCallback);
     }
 
     /**
@@ -384,7 +386,7 @@
                 @Override
                 public void onAnimationStart(Animator animation) {
                     mLauncher.addOnResumeCallback(() ->
-                            ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH,
+                            ObjectAnimator.ofFloat(mLauncher.getDepthController(), STATE_DEPTH,
                                     mLauncher.getStateManager().getState().getDepth(
                                             mLauncher)).start());
                 }
@@ -408,7 +410,7 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 mLauncher.addOnResumeCallback(() ->
-                        ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH,
+                        ObjectAnimator.ofFloat(mLauncher.getDepthController(), STATE_DEPTH,
                                 mLauncher.getStateManager().getState().getDepth(
                                         mLauncher)).start());
             }
@@ -1051,7 +1053,7 @@
                 && BlurUtils.supportsBlursOnWindows();
 
         MyDepthController depthController = new MyDepthController(mLauncher);
-        ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, DEPTH,
+        ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, STATE_DEPTH,
                         BACKGROUND_APP.getDepth(mLauncher))
                 .setDuration(APP_LAUNCH_DURATION);
 
@@ -1092,8 +1094,8 @@
             mWallpaperOpenRunner = createWallpaperOpenRunner(false /* fromUnlock */);
 
             RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
-            definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
-                    WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
+            definition.addRemoteAnimation(WindowManager.TRANSIT_OLD_WALLPAPER_OPEN,
+                    WindowConfiguration.ACTIVITY_TYPE_STANDARD,
                     new RemoteAnimationAdapterCompat(
                             new LauncherAnimationRunner(mHandler, mWallpaperOpenRunner,
                                     false /* startAtFrontOfQueue */),
@@ -1103,7 +1105,7 @@
             if (KEYGUARD_ANIMATION.get()) {
                 mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */);
                 definition.addRemoteAnimation(
-                        WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+                        WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
                         new RemoteAnimationAdapterCompat(
                                 new LauncherAnimationRunner(
                                         mHandler, mKeyguardGoingAwayRunner,
@@ -1112,7 +1114,7 @@
                                 mLauncher.getIApplicationThread()));
             }
 
-            new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
+            mLauncher.registerRemoteAnimations(definition.getWrapped());
         }
     }
 
@@ -1148,7 +1150,7 @@
             return;
         }
         if (hasControlRemoteAppTransitionPermission()) {
-            new ActivityCompat(mLauncher).unregisterRemoteAnimations();
+            mLauncher.unregisterRemoteAnimations();
 
             // Also clear strong references to the runners registered with the remote animation
             // definition so we don't have to wait for the system gc
@@ -1594,7 +1596,8 @@
                             true /* animateOverviewScrim */, launcherView).getAnimators());
 
                     if (!areAllTargetsTranslucent(appTargets)) {
-                        anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH,
+                        anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController(),
+                                STATE_DEPTH,
                                 BACKGROUND_APP.getDepth(mLauncher), NORMAL.getDepth(mLauncher)));
                     }
 
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 351a3bc..c54d119 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
 import com.android.launcher3.anim.AlphaUpdateListener;
@@ -117,9 +118,14 @@
 
     @Override
     public int getExpectedHeight() {
-        return getVisibility() == GONE ? 0
-                : mActivityContext.getDeviceProfile().allAppsCellHeightPx + getPaddingTop()
-                        + getPaddingBottom();
+        DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
+        int iconHeight = deviceProfile.allAppsIconSizePx;
+        int iconPadding = deviceProfile.allAppsIconDrawablePaddingPx;
+        int textHeight = Utilities.calculateTextHeight(deviceProfile.allAppsIconTextSizePx);
+        int verticalPadding = getResources().getDimensionPixelSize(
+                R.dimen.all_apps_predicted_icon_vertical_padding);
+        int totalHeight = iconHeight + iconPadding + textHeight + verticalPadding * 2;
+        return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 2100834..80bdb6f 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.views.AbstractSlideInView;
@@ -192,7 +193,7 @@
             icon.setEnabled(false);
             icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
             icon.verifyHighRes();
-            CellLayout.LayoutParams lp = new CellLayout.LayoutParams(i, 0, 1, 1);
+            CellLayoutLayoutParams lp = new CellLayoutLayoutParams(i, 0, 1, 1);
             mSampleHotseat.addViewToCellLayout(icon, i, info.getViewId(), lp, true);
         }
     }
diff --git a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
index 7e3ee7d..bc3253f 100644
--- a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
@@ -27,6 +27,8 @@
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -52,7 +54,8 @@
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList apps) {
         Context context = app.getContext();
 
         // TODO: remove this
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index 9cd9d85..7a483a8 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -21,6 +21,8 @@
 import android.content.ComponentName;
 import android.text.TextUtils;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -52,7 +54,8 @@
      * workspace.
      */
     @Override
-    public void execute(LauncherAppState appState, BgDataModel dataModel, AllAppsList apps) {
+    public void execute(@NonNull final LauncherAppState appState,
+            @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
         Set<ComponentKey> widgetsInWorkspace = dataModel.appWidgets.stream().map(
                 widget -> new ComponentKey(widget.providerName, widget.user)).collect(
                 Collectors.toSet());
diff --git a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
index a0cf6cb..7c3281a 100644
--- a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
+++ b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
@@ -15,14 +15,16 @@
  */
 package com.android.launcher3.popup;
 
+import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
+
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
 import android.util.Log;
 import android.view.View;
 
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -53,6 +55,7 @@
 
         @Override
         public void onClick(View view) {
+            // Initiate splitscreen from the Home screen or Home All Apps
             Bitmap bitmap;
             Intent intent;
             if (mItemInfo instanceof WorkspaceItemInfo) {
@@ -70,9 +73,10 @@
             }
 
             RecentsView recentsView = mTarget.getOverviewPanel();
+            StatsLogManager.EventEnum splitEvent = getLogEventForPosition(mPosition.stagePosition);
             recentsView.initiateSplitSelect(
                     new SplitSelectSource(mOriginalView, new BitmapDrawable(bitmap), intent,
-                            mPosition, mItemInfo.user));
+                            mPosition, mItemInfo, splitEvent));
         }
     }
 
@@ -82,15 +86,18 @@
         public final Drawable drawable;
         public final Intent intent;
         public final SplitPositionOption position;
-        public final UserHandle user;
+        public final ItemInfo mItemInfo;
+        public final StatsLogManager.EventEnum splitEvent;
 
         public SplitSelectSource(View view, Drawable drawable, Intent intent,
-                SplitPositionOption position, UserHandle user) {
+                SplitPositionOption position, ItemInfo itemInfo,
+                StatsLogManager.EventEnum splitEvent) {
             this.view = view;
             this.drawable = drawable;
             this.intent = intent;
             this.position = position;
-            this.user = user;
+            this.mItemInfo = itemInfo;
+            this.splitEvent = splitEvent;
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 4b8b5f7..e3fd3f9 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -165,7 +165,8 @@
 
         float toDepth = toState.getDepth(mLauncher);
         if (Float.compare(mDepth, toDepth) != 0) {
-            animation.setFloat(this, DEPTH, toDepth, config.getInterpolator(ANIM_DEPTH, LINEAR));
+            animation.setFloat(this, STATE_DEPTH, toDepth,
+                    config.getInterpolator(ANIM_DEPTH, LINEAR));
         }
     }
 
@@ -179,7 +180,7 @@
     public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
         mIgnoreStateChangesDuringMultiWindowAnimation = true;
 
-        ObjectAnimator mwAnimation = ObjectAnimator.ofFloat(this, DEPTH,
+        ObjectAnimator mwAnimation = ObjectAnimator.ofFloat(this, STATE_DEPTH,
                 mLauncher.getStateManager().getState().getDepth(mLauncher, isInMultiWindowMode))
                 .setDuration(300);
         mwAnimation.addListener(new AnimatorListenerAdapter() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java b/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java
new file mode 100644
index 0000000..5f4d239
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 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.taskbar;
+
+import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.R;
+
+// TODO: This would be replaced by the thing that has the role and provides the intent.
+/**
+ * Helper to determine what intent should be used to display in a floating window, if one
+ * exists.
+ */
+public class FloatingTaskIntentResolver {
+    private static final String TAG = FloatingTaskIntentResolver.class.getSimpleName();
+
+    @Nullable
+    /** Gets an intent for a floating task, if one exists. */
+    public static Intent getIntent(Context context) {
+        PackageManager pm = context.getPackageManager();
+        String pkg = context.getString(R.string.floating_task_package);
+        String action = context.getString(R.string.floating_task_action);
+        if (TextUtils.isEmpty(pkg) || TextUtils.isEmpty(action)) {
+            Log.d(TAG, "intent could not be found, pkg= " + pkg + " action= " + action);
+            return null;
+        }
+        Intent intent = createIntent(pm, null, pkg, action);
+        if (intent != null) {
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            return intent;
+        }
+        Log.d(TAG, "No valid intent found!");
+        return null;
+    }
+
+    @Nullable
+    private static Intent createIntent(PackageManager pm, @Nullable String activityName,
+            String packageName, String action) {
+        if (TextUtils.isEmpty(activityName)) {
+            activityName = queryActivityForAction(pm, packageName, action);
+        }
+        if (TextUtils.isEmpty(activityName)) {
+            Log.d(TAG, "Activity name is empty even after action search: " + action);
+            return null;
+        }
+        ComponentName component = new ComponentName(packageName, activityName);
+        Intent intent = new Intent(action).setComponent(component).setPackage(packageName);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        Log.d(TAG, "createIntent returning: " + intent);
+        return intent;
+    }
+
+    @Nullable
+    private static String queryActivityForAction(PackageManager pm, String packageName,
+            String action) {
+        Intent intent = new Intent(action).setPackage(packageName);
+        ResolveInfo resolveInfo = pm.resolveActivity(intent, MATCH_DEFAULT_ONLY);
+        if (resolveInfo == null || resolveInfo.activityInfo == null) {
+            Log.d(TAG, "queryActivityForAction: + " + resolveInfo);
+            return null;
+        }
+        ActivityInfo info = resolveInfo.activityInfo;
+        if (!info.exported) {
+            Log.d(TAG, "queryActivityForAction: + " + info + " not exported");
+            return null;
+        }
+        if (!info.enabled) {
+            Log.d(TAG, "queryActivityForAction: + " + info + " not enabled");
+            return null;
+        }
+        return resolveInfo.activityInfo.name;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java b/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java
new file mode 100644
index 0000000..b15669b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 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.taskbar;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.icons.FastBitmapDrawable;
+
+/**
+ * Button in Taskbar that opens something in a floating task.
+ */
+public class LaunchFloatingTaskButton extends BubbleTextView {
+
+    public LaunchFloatingTaskButton(Context context) {
+        this(context, null);
+    }
+
+    public LaunchFloatingTaskButton(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LaunchFloatingTaskButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        Context theme = new ContextThemeWrapper(context, R.style.AllAppsButtonTheme);
+        Bitmap bitmap = LauncherAppState.getInstance(context).getIconCache().getIconFactory()
+                .createScaledBitmapWithShadow(
+                        theme.getDrawable(R.drawable.ic_floating_task_button));
+        setIcon(new FastBitmapDrawable(bitmap));
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 520487e..b9b4fc3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -15,9 +15,10 @@
  */
 package com.android.launcher3.taskbar;
 
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
+
 import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED;
 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
-import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -37,6 +38,7 @@
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfo;
@@ -115,7 +117,8 @@
 
     @Override
     protected boolean isTaskbarTouchable() {
-        return !mTaskbarLauncherStateController.isAnimatingToLauncher();
+        return !(mTaskbarLauncherStateController.isAnimatingToLauncher()
+                && mTaskbarLauncherStateController.goingToAlignedLauncherState());
     }
 
     public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) {
@@ -170,7 +173,7 @@
             }
         }
 
-        if (ENABLE_SHELL_TRANSITIONS
+        if (ENABLE_SHELL_TRANSITIONS && isResumed
                 && !mLauncher.getStateManager().getState().isTaskbarAlignedWithHotseat(mLauncher)) {
             // Launcher is resumed, but in a state where taskbar is still independent, so
             // ignore the state change.
@@ -290,9 +293,14 @@
     @Override
     public void setSystemGestureInProgress(boolean inProgress) {
         super.setSystemGestureInProgress(inProgress);
-        // Launcher's ScrimView will draw the background throughout the gesture. But once the
-        // gesture ends, start drawing taskbar's background again since launcher might stop drawing.
-        forceHideBackground(inProgress);
+        // TODO(b/250645563): Don't show round corners when leaving in-app state, and remove
+        // forceHideBackground call entirely.
+        if (!FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
+            // Launcher's ScrimView will draw the background throughout the gesture. But once the
+            // gesture ends, start drawing taskbar's background again since launcher might stop
+            // drawing.
+            forceHideBackground(inProgress);
+        }
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 4fda50e..339f3a9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -17,6 +17,7 @@
 
 import static android.view.View.AccessibilityDelegate;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
@@ -42,7 +43,6 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
 
 import android.animation.ArgbEvaluator;
 import android.animation.ObjectAnimator;
@@ -69,6 +69,7 @@
 import android.view.View.OnClickListener;
 import android.view.View.OnHoverListener;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
@@ -88,7 +89,6 @@
 import com.android.systemui.shared.rotation.RotationButton;
 import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -116,12 +116,18 @@
     private static final int FLAG_SCREEN_PINNING_ACTIVE = 1 << 11;
     private static final int FLAG_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 12;
     private static final int FLAG_SMALL_SCREEN = 1 << 13;
+    private static final int FLAG_SLIDE_IN_VIEW_VISIBLE = 1 << 14;
+
+    /** Flags where a UI could be over a slide in view, so the color override should be disabled. */
+    private static final int FLAGS_SLIDE_IN_VIEW_ICON_COLOR_OVERRIDE_DISABLED =
+            FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_VOICE_INTERACTION_WINDOW_SHOWING;
 
     private static final String NAV_BUTTONS_SEPARATE_WINDOW_TITLE = "Taskbar Nav Buttons";
 
     public static final int ALPHA_INDEX_IMMERSIVE_MODE = 0;
     public static final int ALPHA_INDEX_KEYGUARD_OR_DISABLE = 1;
-    private static final int NUM_ALPHA_CHANNELS = 2;
+    public static final int ALPHA_INDEX_SUW = 2;
+    private static final int NUM_ALPHA_CHANNELS = 3;
 
     private final ArrayList<StatePropertyHolder> mPropertyHolders = new ArrayList<>();
     private final ArrayList<ImageView> mAllButtons = new ArrayList<>();
@@ -135,6 +141,8 @@
     private final ViewGroup mStartContextualContainer;
     private final int mLightIconColor;
     private final int mDarkIconColor;
+    /** Color to use for navigation bar buttons, if a slide in view is visible. */
+    private final int mSlideInViewIconColor;
 
     private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
             this::updateNavButtonTranslationY);
@@ -149,6 +157,9 @@
             this::updateNavButtonDarkIntensity);
     private final AnimatedFloat mNavButtonDarkIntensityMultiplier = new AnimatedFloat(
             this::updateNavButtonDarkIntensity);
+    /** Overrides the navigation button color to {@code mSlideInViewIconColor} when {@code 1}. */
+    private final AnimatedFloat mSlideInViewNavButtonColorOverride = new AnimatedFloat(
+            this::updateNavButtonDarkIntensity);
     private final RotationButtonListener mRotationButtonListener = new RotationButtonListener();
 
     private final Rect mFloatingRotationButtonBounds = new Rect();
@@ -167,7 +178,7 @@
     // Variables for moving nav buttons to a separate window above IME
     private boolean mAreNavButtonsInSeparateWindow = false;
     private BaseDragLayer<TaskbarActivityContext> mSeparateWindowParent; // Initialized in init.
-    private final ViewTreeObserverWrapper.OnComputeInsetsListener mSeparateWindowInsetsComputer =
+    private final ViewTreeObserver.OnComputeInternalInsetsListener mSeparateWindowInsetsComputer =
             this::onComputeInsetsForSeparateWindow;
     private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
 
@@ -180,6 +191,8 @@
 
         mLightIconColor = context.getColor(R.color.taskbar_nav_icon_light_color);
         mDarkIconColor = context.getColor(R.color.taskbar_nav_icon_dark_color);
+        // Can precompute color since dark theme change recreates taskbar.
+        mSlideInViewIconColor = Utilities.isDarkTheme(context) ? mLightIconColor : mDarkIconColor;
     }
 
     /**
@@ -191,8 +204,10 @@
         DeviceProfile deviceProfile = mContext.getDeviceProfile();
         Resources resources = mContext.getResources();
         mNavButtonsView.getLayoutParams().height = !isPhoneMode(deviceProfile) ?
-                deviceProfile.taskbarSize :
-                resources.getDimensionPixelSize(R.dimen.taskbar_size);
+                mContext.isUserSetupComplete()
+                        ? deviceProfile.taskbarSize
+                        : resources.getDimensionPixelSize(R.dimen.taskbar_suw_frame)
+                : resources.getDimensionPixelSize(R.dimen.taskbar_size);
 
         mIsImeRenderingNavButtons =
                 InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
@@ -243,20 +258,23 @@
                 flags -> (flags & FLAG_IME_VISIBLE) != 0 && !isInKidsMode, AnimatedFloat.VALUE,
                 transForIme, defaultButtonTransY));
 
+        mPropertyHolders.add(new StatePropertyHolder(
+                mSlideInViewNavButtonColorOverride,
+                flags -> ((flags & FLAG_SLIDE_IN_VIEW_VISIBLE) != 0)
+                        && ((flags & FLAGS_SLIDE_IN_VIEW_ICON_COLOR_OVERRIDE_DISABLED) == 0)));
+
         if (alwaysShowButtons) {
             initButtons(mNavButtonContainer, mEndContextualContainer,
                     mControllers.navButtonController);
             updateButtonLayoutSpacing();
             updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneButtonNavMode(mContext));
             if (isInSetup) {
-                // Since setup wizard only has back button enabled, it looks strange to be
-                // end-aligned, so start-align instead.
-                FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
-                        mNavButtonContainer.getLayoutParams();
-                navButtonsLayoutParams.setMarginStart(navButtonsLayoutParams.getMarginEnd());
-                navButtonsLayoutParams.setMarginEnd(0);
-                navButtonsLayoutParams.gravity = Gravity.START;
-                mNavButtonContainer.requestLayout();
+                handleSetupUi();
+
+                // Hide back button in SUW if keyboard is showing (IME draws its own back).
+                mPropertyHolders.add(new StatePropertyHolder(
+                        mBackButtonAlpha.getProperty(ALPHA_INDEX_SUW),
+                        flags -> (flags & FLAG_IME_VISIBLE) == 0));
 
                 // TODO(b/210906568) Dark intensity is currently not propagated during setup, so set
                 //  it based on dark theme for now.
@@ -524,6 +542,12 @@
         applyState();
     }
 
+    /** {@code true} if a slide in view is currently visible over taskbar. */
+    public void setSlideInViewVisible(boolean isSlideInViewVisible) {
+        updateStateForFlag(FLAG_SLIDE_IN_VIEW_VISIBLE, isSlideInViewVisible);
+        applyState();
+    }
+
     /**
      * Returns true if IME bar is visible
      */
@@ -669,8 +693,11 @@
     private void updateNavButtonDarkIntensity() {
         float darkIntensity = mTaskbarNavButtonDarkIntensity.value
                 * mNavButtonDarkIntensityMultiplier.value;
-        int iconColor = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, mLightIconColor,
-                mDarkIconColor);
+        ArgbEvaluator argbEvaluator = ArgbEvaluator.getInstance();
+        int iconColor = (int) argbEvaluator.evaluate(
+                darkIntensity, mLightIconColor, mDarkIconColor);
+        iconColor = (int) argbEvaluator.evaluate(
+                mSlideInViewNavButtonColorOverride.value, iconColor, mSlideInViewIconColor);
         for (ImageView button : mAllButtons) {
             button.setImageTintList(ColorStateList.valueOf(iconColor));
         }
@@ -712,14 +739,38 @@
         if (mFloatingRotationButton != null) {
             mFloatingRotationButton.onConfigurationChanged(configChanges);
         }
+        if (!mContext.isUserSetupComplete()) {
+            handleSetupUi();
+        }
         updateButtonLayoutSpacing();
     }
 
+    private void handleSetupUi() {
+        // Since setup wizard only has back button enabled, it looks strange to be
+        // end-aligned, so start-align instead.
+        FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
+                mNavButtonContainer.getLayoutParams();
+        Resources resources = mContext.getResources();
+        DeviceProfile deviceProfile = mContext.getDeviceProfile();
+        int setupMargin = resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_margin);
+        navButtonsLayoutParams.setMarginStart(setupMargin);
+        navButtonsLayoutParams.bottomMargin = !deviceProfile.isLandscape
+                ? 0
+                : setupMargin -
+                        (resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2);
+        navButtonsLayoutParams.setMarginEnd(0);
+        navButtonsLayoutParams.gravity = Gravity.START;
+        mNavButtonContainer.setLayoutParams(navButtonsLayoutParams);
+        mNavButtonContainer.requestLayout();
+    }
+
     /**
-     * Adds the correct spacing to 3 button nav container. No-op if using gesture nav or kids mode.
+     * Adds the correct spacing to 3 button nav container. No-op if using gesture nav, setup
+     * is incomplete, or in kids mode.
      */
     private void updateButtonLayoutSpacing() {
-        if (!mContext.isThreeButtonNav() || mContext.isNavBarKidsModeActive()) {
+        if (!mContext.isThreeButtonNav() || mContext.isNavBarKidsModeActive()
+                || !mContext.isUserSetupComplete()) {
             return;
         }
 
@@ -827,14 +878,14 @@
         mSeparateWindowParent.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View view) {
-                ViewTreeObserverWrapper.addOnComputeInsetsListener(
-                        mSeparateWindowParent.getViewTreeObserver(), mSeparateWindowInsetsComputer);
+                mSeparateWindowParent.getViewTreeObserver().addOnComputeInternalInsetsListener(
+                        mSeparateWindowInsetsComputer);
             }
 
             @Override
             public void onViewDetachedFromWindow(View view) {
                 mSeparateWindowParent.removeOnAttachStateChangeListener(this);
-                ViewTreeObserverWrapper.removeOnComputeInsetsListener(
+                mSeparateWindowParent.getViewTreeObserver().removeOnComputeInternalInsetsListener(
                         mSeparateWindowInsetsComputer);
             }
         });
@@ -862,7 +913,7 @@
         mContext.getDragLayer().addView(mNavButtonsView);
     }
 
-    private void onComputeInsetsForSeparateWindow(ViewTreeObserverWrapper.InsetsInfo insetsInfo) {
+    private void onComputeInsetsForSeparateWindow(ViewTreeObserver.InternalInsetsInfo insetsInfo) {
         addVisibleButtonsRegion(mSeparateWindowParent, insetsInfo.touchableRegion);
         insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 8697b69..72c163e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -89,6 +89,7 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
 
 import java.io.PrintWriter;
@@ -148,11 +149,12 @@
         mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
         mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
                 () -> getPackageManager().isSafeMode());
-        mIsUserSetupComplete = SettingsCache.INSTANCE.get(this).getValue(
+        SettingsCache settingsCache = SettingsCache.INSTANCE.get(this);
+        mIsUserSetupComplete = settingsCache.getValue(
                 Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
-        mIsNavBarForceVisible = SettingsCache.INSTANCE.get(this).getValue(
+        mIsNavBarForceVisible = settingsCache.getValue(
                 Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
-        mIsNavBarKidsMode = SettingsCache.INSTANCE.get(this).getValue(
+        mIsNavBarKidsMode = settingsCache.getValue(
                 Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
 
         updateIconSize(resources);
@@ -197,7 +199,9 @@
                 new TaskbarViewController(this, taskbarView),
                 new TaskbarScrimViewController(this, taskbarScrimView),
                 new TaskbarUnfoldAnimationController(this, unfoldTransitionProgressProvider,
-                        mWindowManager, WindowManagerGlobal.getWindowManagerService()),
+                    mWindowManager,
+                    new RotationChangeProvider(WindowManagerGlobal.getWindowManagerService(), this,
+                        getMainExecutor())),
                 new TaskbarKeyguardController(this),
                 new StashedHandleViewController(this, stashedHandleView),
                 new TaskbarStashController(this),
@@ -614,6 +618,10 @@
                     resources.getDimensionPixelSize(R.dimen.taskbar_size) :
                     resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
         }
+
+        if (!isUserSetupComplete()) {
+            return getResources().getDimensionPixelSize(R.dimen.taskbar_suw_frame);
+        }
         return mDeviceProfile.taskbarSize + Math.max(getLeftCornerRadius(), getRightCornerRadius());
     }
 
@@ -778,8 +786,8 @@
      * stash/unstash the taskbar.
      */
     @VisibleForTesting
-    public void enableManualStashingForTests(boolean enableManualStashing) {
-        mControllers.taskbarStashController.enableManualStashingForTests(enableManualStashing);
+    public void enableManualStashingDuringTests(boolean enableManualStashing) {
+        mControllers.taskbarStashController.enableManualStashingDuringTests(enableManualStashing);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 2b80b75..707023b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -165,6 +165,7 @@
      * Cleans up all controllers.
      */
     public void onDestroy() {
+        mAreAllControllersInitialized = false;
         mSharedState = null;
 
         navbarButtonsViewController.onDestroy();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 97029fe..9a1e064 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -32,6 +32,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
+import android.util.Pair;
 import android.view.DragEvent;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
@@ -42,7 +43,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.InstanceId;
-import com.android.internal.logging.InstanceIdSequence;
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DragSource;
@@ -69,6 +69,7 @@
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.quickstep.util.LogUtils;
 import com.android.systemui.shared.recents.model.Task;
 
 import java.io.PrintWriter;
@@ -359,11 +360,11 @@
         }
 
         if (clipDescription != null && intent != null) {
+            Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
+                    LogUtils.getShellShareableInstanceId();
             // Need to share the same InstanceId between launcher3 and WM Shell (internal).
-            InstanceId internalInstanceId = new InstanceIdSequence(
-                    com.android.launcher3.logging.InstanceId.INSTANCE_ID_MAX).newInstanceId();
-            com.android.launcher3.logging.InstanceId launcherInstanceId =
-                    new com.android.launcher3.logging.InstanceId(internalInstanceId.getId());
+            InstanceId internalInstanceId = instanceIds.first;
+            com.android.launcher3.logging.InstanceId launcherInstanceId = instanceIds.second;
 
             intent.putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, internalInstanceId);
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index ee17ad0..7e75779 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -24,6 +24,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewTreeObserver;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -32,9 +33,6 @@
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInsetsListener;
 
 /**
  * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder.
@@ -42,7 +40,8 @@
 public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
 
     private final TaskbarBackgroundRenderer mBackgroundRenderer;
-    private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets;
+    private final ViewTreeObserver.OnComputeInternalInsetsListener mTaskbarInsetsComputer =
+            this::onComputeTaskbarInsets;
 
     // Initialized in init.
     private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks;
@@ -80,7 +79,7 @@
         mControllers = mControllerCallbacks.getTouchControllers();
     }
 
-    private void onComputeTaskbarInsets(InsetsInfo insetsInfo) {
+    private void onComputeTaskbarInsets(ViewTreeObserver.InternalInsetsInfo insetsInfo) {
         if (mControllerCallbacks != null) {
             mControllerCallbacks.updateInsetsTouchability(insetsInfo);
         }
@@ -88,7 +87,7 @@
 
     protected void onDestroy(boolean forceDestroy) {
         if (forceDestroy) {
-            ViewTreeObserverWrapper.removeOnComputeInsetsListener(mTaskbarInsetsComputer);
+            getViewTreeObserver().removeOnComputeInternalInsetsListener(mTaskbarInsetsComputer);
         }
     }
 
@@ -99,8 +98,7 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        ViewTreeObserverWrapper.addOnComputeInsetsListener(getViewTreeObserver(),
-                mTaskbarInsetsComputer);
+        getViewTreeObserver().addOnComputeInternalInsetsListener(mTaskbarInsetsComputer);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index ec9760c..025fe7a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -17,12 +17,12 @@
 
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.view.ViewTreeObserver;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.AnimatedFloat;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
 
 import java.io.PrintWriter;
 
@@ -157,9 +157,9 @@
 
         /**
          * Called to update the touchable insets.
-         * @see InsetsInfo#setTouchableInsets(int)
+         * @see ViewTreeObserver.InternalInsetsInfo#setTouchableInsets(int)
          */
-        public void updateInsetsTouchability(InsetsInfo insetsInfo) {
+        public void updateInsetsTouchability(ViewTreeObserver.InternalInsetsInfo insetsInfo) {
             mControllers.taskbarInsetsController.updateInsetsTouchability(insetsInfo);
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
index 32a3c10..95b93fe 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
@@ -90,6 +90,9 @@
             mTaskbarEduView = (TaskbarEduView) mActivity.getLayoutInflater().inflate(
                     R.layout.taskbar_edu, mActivity.getDragLayer(), false);
             mTaskbarEduView.init(new TaskbarEduCallbacks());
+            mControllers.navbarButtonsViewController.setSlideInViewVisible(true);
+            mTaskbarEduView.setOnCloseBeginListener(
+                    () -> mControllers.navbarButtonsViewController.setSlideInViewVisible(false));
             mTaskbarEduView.addOnCloseListener(() -> mTaskbarEduView = null);
             mTaskbarEduView.show();
             startAnim(createWaveAnim());
@@ -217,5 +220,9 @@
                         v -> mTaskbarEduView.snapToPage(currentPage + 1));
             }
         }
+
+        int getIconLayoutBoundsWidth() {
+            return mControllers.taskbarViewController.getIconLayoutBounds().width();
+        }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
index 89d67be..c0cbbd6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
@@ -39,6 +39,9 @@
 
     private final Rect mInsets = new Rect();
 
+    // Initialized in init.
+    private TaskbarEduController.TaskbarEduCallbacks mTaskbarEduCallbacks;
+
     private Button mStartButton;
     private Button mEndButton;
     private TaskbarEduPagedView mPagedView;
@@ -56,6 +59,7 @@
         if (mPagedView != null) {
             mPagedView.setControllerCallbacks(callbacks);
         }
+        mTaskbarEduCallbacks = callbacks;
     }
 
     @Override
@@ -101,6 +105,22 @@
                 Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0);
     }
 
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        int contentWidth = Math.min(getContentAreaWidth(), getMeasuredWidth());
+        contentWidth = Math.max(contentWidth, mTaskbarEduCallbacks.getIconLayoutBoundsWidth());
+        int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY);
+
+        mContent.measure(contentAreaWidthSpec, MeasureSpec.UNSPECIFIED);
+    }
+
+    private int getContentAreaWidth() {
+        return mTaskbarEduCallbacks.getIconLayoutBoundsWidth()
+                + getResources().getDimensionPixelSize(R.dimen.taskbar_edu_horizontal_margin) * 2;
+    }
+
     /** Show the Education flow. */
     public void show() {
         attachToContainer();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 48fde8f..079e8a1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -20,6 +20,11 @@
 import android.view.InsetsFrameProvider
 import android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES
 import android.view.InsetsState
+import android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT
+import android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR
+import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME
+import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD
 import android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION
@@ -29,9 +34,6 @@
 import com.android.launcher3.anim.AlphaUpdateListener
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
 import com.android.quickstep.KtR
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo
-import com.android.systemui.shared.system.WindowManagerWrapper
-import com.android.systemui.shared.system.WindowManagerWrapper.*
 import java.io.PrintWriter
 
 /**
@@ -42,7 +44,7 @@
     /** The bottom insets taskbar provides to the IME when IME is visible. */
     val taskbarHeightForIme: Int = context.resources.getDimensionPixelSize(
         KtR.dimen.taskbar_ime_size)
-    private val contentRegion: Region = Region()
+    private val touchableRegion: Region = Region()
     private val deviceProfileChangeListener = { _: DeviceProfile ->
         onTaskbarWindowHeightOrInsetsChanged()
     }
@@ -55,8 +57,7 @@
         this.controllers = controllers
         windowLayoutParams = context.windowLayoutParams
 
-        val wmWrapper: WindowManagerWrapper = getInstance()
-        wmWrapper.setProvidesInsetsTypes(
+        setProvidesInsetsTypes(
             windowLayoutParams,
             intArrayOf(
                 ITYPE_EXTRA_NAVIGATION_BAR,
@@ -76,10 +77,11 @@
     }
 
     fun onTaskbarWindowHeightOrInsetsChanged() {
-        var contentHeight = controllers.taskbarStashController.contentHeightToReportToApps
-        contentRegion.set(0, windowLayoutParams.height - contentHeight,
+        val touchableHeight = controllers.taskbarStashController.touchableHeight
+        touchableRegion.set(0, windowLayoutParams.height - touchableHeight,
             context.deviceProfile.widthPx, windowLayoutParams.height)
-        var tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
+        val contentHeight = controllers.taskbarStashController.contentHeightToReportToApps
+        val tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
         for (provider in windowLayoutParams.providedInsets) {
             if (provider.type == ITYPE_EXTRA_NAVIGATION_BAR) {
                 provider.insetsSize = Insets.of(0, 0, 0, contentHeight)
@@ -108,10 +110,22 @@
     }
 
     /**
-     * Called to update the touchable insets.
-     * @see InsetsInfo.setTouchableInsets
+     * Sets {@param providesInsetsTypes} as the inset types provided by {@param params}.
+     * @param params The window layout params.
+     * @param providesInsetsTypes The inset types we would like this layout params to provide.
      */
-    fun updateInsetsTouchability(insetsInfo: InsetsInfo) {
+    fun setProvidesInsetsTypes(params: WindowManager.LayoutParams, providesInsetsTypes: IntArray) {
+        params.providedInsets = arrayOfNulls<InsetsFrameProvider>(providesInsetsTypes.size);
+        for (i in providesInsetsTypes.indices) {
+            params.providedInsets[i] = InsetsFrameProvider(providesInsetsTypes[i]);
+        }
+    }
+
+    /**
+     * Called to update the touchable insets.
+     * @see InternalInsetsInfo.setTouchableInsets
+     */
+    fun updateInsetsTouchability(insetsInfo: ViewTreeObserver.InternalInsetsInfo) {
         insetsInfo.touchableRegion.setEmpty()
         // Always have nav buttons be touchable
         controllers.navbarButtonsViewController.addVisibleButtonsRegion(
@@ -120,18 +134,18 @@
         var insetsIsTouchableRegion = true
         if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
             // Let touches pass through us.
-            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+            insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
         } else if (controllers.navbarButtonsViewController.isImeVisible) {
-            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+            insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
         } else if (!controllers.uiController.isTaskbarTouchable) {
             // Let touches pass through us.
-            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+            insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
         } else if (controllers.taskbarDragController.isSystemDragInProgress) {
             // Let touches pass through us.
-            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+            insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
         } else if (AbstractFloatingView.hasOpenView(context, TYPE_TASKBAR_ALL_APPS)) {
             // Let touches pass through us.
-            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+            insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
         } else if (controllers.taskbarViewController.areIconsVisible()
             || AbstractFloatingView.hasOpenView(context, AbstractFloatingView.TYPE_ALL)
             || context.isNavBarKidsModeActive
@@ -139,15 +153,15 @@
             // Taskbar has some touchable elements, take over the full taskbar area
             insetsInfo.setTouchableInsets(
                 if (context.isTaskbarWindowFullscreen) {
-                    InsetsInfo.TOUCHABLE_INSETS_FRAME
+                    TOUCHABLE_INSETS_FRAME
                 } else {
-                    insetsInfo.touchableRegion.set(contentRegion)
-                    InsetsInfo.TOUCHABLE_INSETS_REGION
+                    insetsInfo.touchableRegion.set(touchableRegion)
+                    TOUCHABLE_INSETS_REGION
                 }
             )
             insetsIsTouchableRegion = false
         } else {
-            insetsInfo.setTouchableInsets(InsetsInfo.TOUCHABLE_INSETS_REGION)
+            insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
         }
         context.excludeFromMagnificationRegion(insetsIsTouchableRegion)
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 58c689b..de37b70 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -50,7 +50,6 @@
 import java.util.HashMap;
 import java.util.StringJoiner;
 import java.util.function.Consumer;
-import java.util.function.Supplier;
 
 /**
  * Track LauncherState, RecentsAnimation, resumed state for task bar in one place here and animate
@@ -65,15 +64,12 @@
     public static final int FLAG_RECENTS_ANIMATION_RUNNING = 1 << 1;
     public static final int FLAG_TRANSITION_STATE_RUNNING = 1 << 2;
 
+    private static final int FLAGS_LAUNCHER = FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING;
     /** Equivalent to an int with all 1s for binary operation purposes */
     private static final int FLAGS_ALL = ~0;
 
-    private final AnimatedFloat mIconAlignmentForResumedState =
-            new AnimatedFloat(this::onIconAlignmentRatioChangedForAppAndHomeTransition);
-    private final AnimatedFloat mIconAlignmentForGestureState =
-            new AnimatedFloat(this::onIconAlignmentRatioChangedForAppAndHomeTransition);
-    private final AnimatedFloat mIconAlignmentForLauncherState =
-            new AnimatedFloat(this::onIconAlignmentRatioChangedForStateTransition);
+    private final AnimatedFloat mIconAlignment =
+            new AnimatedFloat(this::onIconAlignmentRatioChanged);
 
     private TaskbarControllers mControllers;
     private AnimatedFloat mTaskbarBackgroundAlpha;
@@ -86,8 +82,7 @@
 
     private @Nullable TaskBarRecentsAnimationListener mTaskBarRecentsAnimationListener;
 
-    private boolean mIsAnimatingToLauncherViaGesture;
-    private boolean mIsAnimatingToLauncherViaResume;
+    private boolean mIsAnimatingToLauncher;
 
     private boolean mShouldDelayLauncherStateAnim;
 
@@ -148,8 +143,8 @@
         mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
         mIconAlphaForHome.setConsumer(mIconAlphaForHomeConsumer);
 
-        mIconAlignmentForResumedState.finishAnimation();
-        onIconAlignmentRatioChangedForAppAndHomeTransition();
+        mIconAlignment.finishAnimation();
+        onIconAlignmentRatioChanged();
 
         mLauncher.getStateManager().addStateListener(mStateListener);
 
@@ -165,9 +160,7 @@
     public void onDestroy() {
         mCanSyncViews = false;
 
-        mIconAlignmentForResumedState.finishAnimation();
-        mIconAlignmentForGestureState.finishAnimation();
-        mIconAlignmentForLauncherState.finishAnimation();
+        mIconAlignment.finishAnimation();
 
         mIconAlphaForHome.setConsumer(null);
         mLauncher.getHotseat().setIconsAlpha(1f);
@@ -187,6 +180,9 @@
         TaskbarStashController stashController = mControllers.taskbarStashController;
         stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
                 toState.isTaskbarStashed(mLauncher));
+        if (DEBUG) {
+            Log.d(TAG, "createAnimToLauncher - FLAG_IN_APP: " + false);
+        }
         stashController.updateStateForFlag(FLAG_IN_APP, false);
 
         updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, true);
@@ -201,7 +197,7 @@
     }
 
     public boolean isAnimatingToLauncher() {
-        return mIsAnimatingToLauncherViaResume || mIsAnimatingToLauncherViaGesture;
+        return mIsAnimatingToLauncher;
     }
 
     public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) {
@@ -261,11 +257,29 @@
     }
 
     private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) {
+        boolean goingToLauncher = isInLauncher();
+        final float toAlignment;
+        if (goingToLauncher) {
+            boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
+            boolean willStashVisually = isInStashedState
+                    && mControllers.taskbarStashController.supportsVisualStashing();
+            boolean isTaskbarAlignedWithHotseat =
+                    mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
+            toAlignment = isTaskbarAlignedWithHotseat && !willStashVisually ? 1 : 0;
+        } else {
+            toAlignment = 0;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "onStateChangeApplied - mState: " + getStateString(mState)
+                    + ", changedFlags: " + getStateString(changedFlags)
+                    + ", goingToLauncher: " + goingToLauncher
+                    + ", mLauncherState: " + mLauncherState
+                    + ", toAlignment: " + toAlignment);
+        }
         AnimatorSet animatorSet = new AnimatorSet();
 
         // Add the state animation first to ensure FLAG_IN_STASHED_LAUNCHER_STATE is set and we can
         // determine whether goingToUnstashedLauncherStateChanged.
-        boolean wasGoingToUnstashedLauncherState = goingToUnstashedLauncherState();
         if (hasAnyFlag(changedFlags, FLAG_TRANSITION_STATE_RUNNING)) {
             boolean committed = !hasAnyFlag(FLAG_TRANSITION_STATE_RUNNING);
             playStateTransitionAnim(animatorSet, duration, committed);
@@ -276,95 +290,69 @@
                 applyState(0 /* duration */);
             }
         }
-        boolean goingToUnstashedLauncherStateChanged = wasGoingToUnstashedLauncherState
-                != goingToUnstashedLauncherState();
 
-        boolean launcherStateChangedDuringAnimToResumeAlignment =
-                mIconAlignmentForResumedState.isAnimating() && goingToUnstashedLauncherStateChanged;
-        if (hasAnyFlag(changedFlags, FLAG_RESUMED)
-                || launcherStateChangedDuringAnimToResumeAlignment) {
-            boolean isResumed = isResumed();
-            // If launcher is resumed, we show the icons when going to an unstashed launcher state
-            // or launcher state is not changed (e.g. in overview, launcher is paused and resumed).
-            float toAlignmentForResumedState = isResumed && (goingToUnstashedLauncherState()
-                    || !goingToUnstashedLauncherStateChanged) ? 1 : 0;
-            // If we're already animating to the value, just leave it be instead of restarting it.
-            if (!mIconAlignmentForResumedState.isAnimatingToValue(toAlignmentForResumedState)) {
-                ObjectAnimator resumeAlignAnim = mIconAlignmentForResumedState
-                        .animateToValue(toAlignmentForResumedState)
-                        .setDuration(duration);
-                if (DEBUG) {
-                    Log.d(TAG, "mIconAlignmentForResumedState - "
-                            + mIconAlignmentForResumedState.value
-                            + " -> " + toAlignmentForResumedState + ": " + duration);
+        if (hasAnyFlag(changedFlags, FLAGS_LAUNCHER)) {
+            animatorSet.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mIsAnimatingToLauncher = false;
                 }
 
-                resumeAlignAnim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mIsAnimatingToLauncherViaResume = false;
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mIsAnimatingToLauncher = goingToLauncher;
+
+                    TaskbarStashController stashController =
+                            mControllers.taskbarStashController;
+                    if (DEBUG) {
+                        Log.d(TAG, "onAnimationStart - FLAG_IN_APP: " + !goingToLauncher);
                     }
-
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        mIsAnimatingToLauncherViaResume = isResumed;
-
-                        TaskbarStashController stashController =
-                                mControllers.taskbarStashController;
-                        stashController.updateStateForFlag(FLAG_IN_APP, !isResumed);
-                        stashController.applyState(duration);
-                    }
-                });
-                animatorSet.play(resumeAlignAnim);
-            }
-        }
-
-
-        boolean launcherStateChangedDuringAnimToGestureAlignment =
-                mIconAlignmentForGestureState.isAnimating() && goingToUnstashedLauncherStateChanged;
-        if (hasAnyFlag(changedFlags, FLAG_RECENTS_ANIMATION_RUNNING)
-                || launcherStateChangedDuringAnimToGestureAlignment) {
-            boolean isRecentsAnimationRunning = isRecentsAnimationRunning();
-            float toAlignmentForGestureState = isRecentsAnimationRunning
-                    && goingToUnstashedLauncherState() ? 1 : 0;
-            // If we're already animating to the value, just leave it be instead of restarting it.
-            if (!mIconAlignmentForGestureState.isAnimatingToValue(toAlignmentForGestureState)) {
-                Animator gestureAlignAnim = mIconAlignmentForGestureState
-                        .animateToValue(toAlignmentForGestureState);
-                if (isRecentsAnimationRunning) {
-                    gestureAlignAnim.setDuration(duration);
+                    stashController.updateStateForFlag(FLAG_IN_APP, !goingToLauncher);
+                    stashController.applyState(duration);
                 }
-                if (DEBUG) {
-                    Log.d(TAG, "mIconAlignmentForGestureState - "
-                            + mIconAlignmentForGestureState.value
-                            + " -> " + toAlignmentForGestureState + ": " + duration);
-                }
-                gestureAlignAnim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mIsAnimatingToLauncherViaGesture = false;
-                    }
+            });
 
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        mIsAnimatingToLauncherViaGesture = isRecentsAnimationRunning();
-                    }
-                });
-                animatorSet.play(gestureAlignAnim);
-            }
-        }
-
-        if (hasAnyFlag(changedFlags, FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING)) {
-            boolean goingToLauncher = hasAnyFlag(FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING);
             if (goingToLauncher) {
                 // Handle closing open popups when going home/overview
                 AbstractFloatingView.closeAllOpenViews(mControllers.taskbarActivityContext);
             }
-            animatorSet.play(mTaskbarBackgroundAlpha.animateToValue(goingToLauncher ? 0 : 1)
+        }
+
+        float backgroundAlpha =
+                goingToLauncher && mLauncherState.isTaskbarAlignedWithHotseat(mLauncher)
+                        ? 0 : 1;
+        // Don't animate if background has reached desired value.
+        if (mTaskbarBackgroundAlpha.isAnimating()
+                || mTaskbarBackgroundAlpha.value != backgroundAlpha) {
+            mTaskbarBackgroundAlpha.cancelAnimation();
+            if (DEBUG) {
+                Log.d(TAG, "onStateChangeApplied - taskbarBackgroundAlpha - "
+                        + mTaskbarBackgroundAlpha.value
+                        + " -> " + backgroundAlpha + ": " + duration);
+            }
+            animatorSet.play(mTaskbarBackgroundAlpha.animateToValue(backgroundAlpha)
                     .setDuration(duration));
         }
 
+        if (mIconAlignment.isAnimatingToValue(toAlignment)
+                || mIconAlignment.isSettledOnValue(toAlignment)) {
+            // Already at desired value, but make sure we run the callback at the end.
+            animatorSet.addListener(AnimatorListeners.forEndCallback(
+                    this::onIconAlignmentRatioChanged));
+        } else {
+            mIconAlignment.cancelAnimation();
+            ObjectAnimator iconAlignAnim = mIconAlignment
+                    .animateToValue(toAlignment)
+                    .setDuration(duration);
+            if (DEBUG) {
+                Log.d(TAG, "onStateChangeApplied - iconAlignment - "
+                        + mIconAlignment.value
+                        + " -> " + toAlignment + ": " + duration);
+            }
+            animatorSet.play(iconAlignAnim);
+        }
         animatorSet.setInterpolator(EMPHASIZED);
+
         if (start) {
             animatorSet.start();
         }
@@ -372,18 +360,13 @@
     }
 
     /** Returns whether we're going to a state where taskbar icons should align with launcher. */
-    private boolean goingToUnstashedLauncherState() {
-        return !mControllers.taskbarStashController.isInStashedLauncherState();
+    public boolean goingToAlignedLauncherState() {
+        return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
     }
 
     private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
             boolean committed) {
         boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
-        boolean willStashVisually =
-                isInStashedState && mControllers.taskbarStashController.supportsVisualStashing();
-        float toAlignment =
-                mLauncherState.isTaskbarAlignedWithHotseat(mLauncher) && !willStashVisually ? 1 : 0;
-
         TaskbarStashController stashController = mControllers.taskbarStashController;
         stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState);
         Animator stashAnimator = stashController.applyStateWithoutStart(duration);
@@ -406,55 +389,23 @@
             });
             animatorSet.play(stashAnimator);
         }
-        if (mIconAlignmentForLauncherState.value == toAlignment) {
-            // Already at expected value, but make sure we run the callback at the end.
-            animatorSet.addListener(AnimatorListeners.forEndCallback(
-                    this::onIconAlignmentRatioChangedForStateTransition));
-        }
-        if (!mIconAlignmentForLauncherState.isAnimatingToValue(toAlignment)) {
-            // If we're already animating to the value, just leave it be instead of restarting it.
-            mIconAlignmentForLauncherState.finishAnimation();
-            animatorSet.play(mIconAlignmentForLauncherState.animateToValue(toAlignment)
-                    .setDuration(duration));
-            if (DEBUG) {
-                Log.d(TAG, "mIconAlignmentForLauncherState - "
-                        + mIconAlignmentForLauncherState.value
-                        + " -> " + toAlignment + ": " + duration);
-            }
-            animatorSet.setInterpolator(EMPHASIZED);
-        }
     }
 
-    private boolean isResumed() {
-        return (mState & FLAG_RESUMED) != 0;
+    private boolean isInLauncher() {
+        return (mState & FLAGS_LAUNCHER) != 0;
     }
 
-    private boolean isRecentsAnimationRunning() {
-        return (mState & FLAG_RECENTS_ANIMATION_RUNNING) != 0;
-    }
-
-    private void onIconAlignmentRatioChangedForStateTransition() {
-        if (!isResumed() && mTaskBarRecentsAnimationListener == null) {
-            return;
-        }
-        onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioForLauncherState);
-    }
-
-    private void onIconAlignmentRatioChangedForAppAndHomeTransition() {
-        onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioBetweenAppAndHome);
-    }
-
-    private void onIconAlignmentRatioChanged(Supplier<AnimatedFloat> alignmentSupplier) {
-        if (mControllers == null) {
-            return;
-        }
-        AnimatedFloat animatedFloat = alignmentSupplier.get();
+    private void onIconAlignmentRatioChanged() {
         float currentValue = mIconAlphaForHome.getValue();
-        boolean taskbarWillBeVisible = animatedFloat.value < 1;
+        boolean taskbarWillBeVisible = mIconAlignment.value < 1;
         boolean firstFrameVisChanged = (taskbarWillBeVisible && Float.compare(currentValue, 1) != 0)
                 || (!taskbarWillBeVisible && Float.compare(currentValue, 0) != 0);
 
-        updateIconAlignment(animatedFloat.value, animatedFloat.getEndValue());
+        mControllers.taskbarViewController.setLauncherIconAlignment(
+                mIconAlignment.value, mIconAlignment.getEndValue(), mLauncher.getDeviceProfile());
+        mControllers.navbarButtonsViewController.updateTaskbarAlignment(mIconAlignment.value);
+        // Switch taskbar and hotseat in last frame
+        mIconAlphaForHome.setValue(taskbarWillBeVisible ? 1 : 0);
 
         // Sync the first frame where we swap taskbar and hotseat.
         if (firstFrameVisChanged && mCanSyncViews && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
@@ -464,28 +415,6 @@
         }
     }
 
-    private void updateIconAlignment(float alignment, Float endAlignment) {
-        mControllers.taskbarViewController.setLauncherIconAlignment(
-                alignment, endAlignment, mLauncher.getDeviceProfile());
-
-        // Switch taskbar and hotseat in last frame
-        setTaskbarViewVisible(alignment < 1);
-        mControllers.navbarButtonsViewController.updateTaskbarAlignment(alignment);
-    }
-
-    private AnimatedFloat getCurrentIconAlignmentRatioBetweenAppAndHome() {
-        return mIconAlignmentForResumedState.value > mIconAlignmentForGestureState.value
-                ? mIconAlignmentForResumedState : mIconAlignmentForGestureState;
-    }
-
-    private AnimatedFloat getCurrentIconAlignmentRatioForLauncherState() {
-        return mIconAlignmentForLauncherState;
-    }
-
-    private void setTaskbarViewVisible(boolean isVisible) {
-        mIconAlphaForHome.setValue(isVisible ? 1 : 0);
-    }
-
     private final class TaskBarRecentsAnimationListener implements
             RecentsAnimationCallbacks.RecentsAnimationListener {
         private final RecentsAnimationCallbacks mCallbacks;
@@ -515,11 +444,11 @@
             updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, false);
             updateStateForFlag(FLAG_RESUMED, launcherResumed);
             applyState();
-            // Set this last because applyState() might also animate it.
-            mIconAlignmentForResumedState.cancelAnimation();
-            mIconAlignmentForResumedState.updateValue(launcherResumed ? 1 : 0);
 
             TaskbarStashController controller = mControllers.taskbarStashController;
+            if (DEBUG) {
+                Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp);
+            }
             controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
             controller.applyState();
         }
@@ -527,29 +456,24 @@
 
     private static String getStateString(int flags) {
         StringJoiner str = new StringJoiner("|");
-        str.add((flags & FLAG_RESUMED) != 0 ? "FLAG_RESUMED" : "");
-        str.add((flags & FLAG_RECENTS_ANIMATION_RUNNING) != 0
-                ? "FLAG_RECENTS_ANIMATION_RUNNING" : "");
-        str.add((flags & FLAG_TRANSITION_STATE_RUNNING) != 0
-                ? "FLAG_TRANSITION_STATE_RUNNING" : "");
+        if ((flags & FLAG_RESUMED) != 0) {
+            str.add("FLAG_RESUMED");
+        }
+        if ((flags & FLAG_RECENTS_ANIMATION_RUNNING) != 0) {
+            str.add("FLAG_RECENTS_ANIMATION_RUNNING");
+        }
+        if ((flags & FLAG_TRANSITION_STATE_RUNNING) != 0) {
+            str.add("FLAG_TRANSITION_STATE_RUNNING");
+        }
         return str.toString();
     }
 
     protected void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarLauncherStateController:");
-
         pw.println(String.format(
-                "%s\tmIconAlignmentForResumedState=%.2f",
+                "%s\tmIconAlignment=%.2f",
                 prefix,
-                mIconAlignmentForResumedState.value));
-        pw.println(String.format(
-                "%s\tmIconAlignmentForGestureState=%.2f",
-                prefix,
-                mIconAlignmentForGestureState.value));
-        pw.println(String.format(
-                "%s\tmIconAlignmentForLauncherState=%.2f",
-                prefix,
-                mIconAlignmentForLauncherState.value));
+                mIconAlignment.value));
         pw.println(String.format(
                 "%s\tmTaskbarBackgroundAlpha=%.2f", prefix, mTaskbarBackgroundAlpha.value));
         pw.println(String.format(
@@ -558,13 +482,9 @@
         pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState)));
         pw.println(String.format("%s\tmLauncherState=%s", prefix, mLauncherState));
         pw.println(String.format(
-                "%s\tmIsAnimatingToLauncherViaGesture=%b",
+                "%s\tmIsAnimatingToLauncher=%b",
                 prefix,
-                mIsAnimatingToLauncherViaGesture));
-        pw.println(String.format(
-                "%s\tmIsAnimatingToLauncherViaResume=%b",
-                prefix,
-                mIsAnimatingToLauncherViaResume));
+                mIsAnimatingToLauncher));
         pw.println(String.format(
                 "%s\tmShouldDelayLauncherStateAnim=%b", prefix, mShouldDelayLauncherStateAnim));
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 7b4501a..da6dab1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -15,14 +15,20 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
+
+import android.content.ClipDescription;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.graphics.Point;
+import android.os.Bundle;
+import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.View;
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.logging.InstanceId;
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.LauncherSettings;
@@ -47,6 +53,7 @@
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.LogUtils;
 
 import java.io.PrintWriter;
 import java.util.HashMap;
@@ -263,8 +270,15 @@
 
         @Override
         public void onClick(View view) {
-            AbstractFloatingView.closeAllOpenViews(mTarget);
+            // Initiate splitscreen from the in-app Taskbar or Taskbar All Apps
+            Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
+                    LogUtils.getShellShareableInstanceId();
+            mTarget.getStatsLogManager().logger()
+                    .withItemInfo(mItemInfo)
+                    .withInstanceId(instanceIds.second)
+                    .log(getLogEventForPosition(mPosition.stagePosition));
 
+            AbstractFloatingView.closeAllOpenViews(mTarget);
             if (mItemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                 WorkspaceItemInfo workspaceItemInfo = (WorkspaceItemInfo) mItemInfo;
                 SystemUiProxy.INSTANCE.get(mTarget).startShortcut(
@@ -272,7 +286,8 @@
                         workspaceItemInfo.getDeepShortcutId(),
                         mPosition.stagePosition,
                         null,
-                        workspaceItemInfo.user);
+                        workspaceItemInfo.user,
+                        instanceIds.first);
             } else {
                 SystemUiProxy.INSTANCE.get(mTarget).startIntent(
                         mTarget.getSystemService(LauncherApps.class).getMainActivityLaunchIntent(
@@ -281,7 +296,8 @@
                                 mItemInfo.user),
                         new Intent(),
                         mPosition.stagePosition,
-                        null);
+                        null,
+                        instanceIds.first);
             }
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
index f131595..c10b57a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
@@ -17,24 +17,29 @@
 
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DEEP_SHORTCUTS;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.SHORTCUTS_AND_NOTIFICATIONS;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
 
 import android.content.Intent;
 import android.content.pm.LauncherApps;
+import android.util.Pair;
 import android.view.KeyEvent;
 import android.view.View;
 
+import com.android.internal.logging.InstanceId;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.util.ShortcutUtil;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.LogUtils;
 
 import java.util.List;
 
@@ -49,10 +54,12 @@
     public static final int MOVE_TO_BOTTOM_OR_RIGHT = R.id.action_move_to_bottom_or_right;
 
     private final LauncherApps mLauncherApps;
+    private final StatsLogManager mStatsLogManager;
 
     public TaskbarShortcutMenuAccessibilityDelegate(TaskbarActivityContext context) {
         super(context);
         mLauncherApps = context.getSystemService(LauncherApps.class);
+        mStatsLogManager = context.getStatsLogManager();
 
         mActions.put(DEEP_SHORTCUTS, new LauncherAction(DEEP_SHORTCUTS,
                 R.string.action_deep_shortcut, KeyEvent.KEYCODE_S));
@@ -82,7 +89,14 @@
                 && (action == MOVE_TO_TOP_OR_LEFT || action == MOVE_TO_BOTTOM_OR_RIGHT)) {
             WorkspaceItemInfo info = (WorkspaceItemInfo) item;
             int side = action == MOVE_TO_TOP_OR_LEFT
-                    ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
+                    ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT;
+
+            Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
+                    LogUtils.getShellShareableInstanceId();
+            mStatsLogManager.logger()
+                    .withItemInfo(item)
+                    .withInstanceId(instanceIds.second)
+                    .log(getLogEventForPosition(side));
 
             if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                 SystemUiProxy.INSTANCE.get(mContext).startShortcut(
@@ -90,14 +104,15 @@
                         info.getDeepShortcutId(),
                         side,
                         /* bundleOpts= */ null,
-                        info.user);
+                        info.user,
+                        instanceIds.first);
             } else {
                 SystemUiProxy.INSTANCE.get(mContext).startIntent(
                         mLauncherApps.getMainActivityLaunchIntent(
                                 item.getIntent().getComponent(),
                                 /* startActivityOptions= */null,
                                 item.user),
-                        new Intent(), side, null);
+                        new Intent(), side, null, instanceIds.first);
             }
             return true;
         } else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index d9d55e7..9fd2bf9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -32,6 +32,7 @@
 import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.util.Log;
+import android.view.InsetsController;
 import android.view.View;
 import android.view.ViewConfiguration;
 
@@ -46,11 +47,8 @@
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.SystemUiProxy;
-import com.android.systemui.shared.system.WindowManagerWrapper;
 
 import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.Optional;
 import java.util.StringJoiner;
 import java.util.function.IntPredicate;
 
@@ -69,7 +67,7 @@
     public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 4; // setup wizard and AllSetActivity
     public static final int FLAG_STASHED_IN_APP_IME = 1 << 5; // IME is visible
     public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 6;
-    public static final int FLAG_STASHED_IN_APP_ALL_APPS = 1 << 7; // All apps is visible.
+    public static final int FLAG_STASHED_IN_TASKBAR_ALL_APPS = 1 << 7; // All apps is visible.
     public static final int FLAG_IN_SETUP = 1 << 8; // In the Setup Wizard
     public static final int FLAG_STASHED_SMALL_SCREEN = 1 << 9; // phone screen gesture nav, stashed
 
@@ -79,8 +77,8 @@
     // If we're in an app and any of these flags are enabled, taskbar should be stashed.
     private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
             | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
-            | FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_APP_ALL_APPS |
-            FLAG_STASHED_SMALL_SCREEN;
+            | FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_TASKBAR_ALL_APPS
+            | FLAG_STASHED_SMALL_SCREEN;
 
     private static final int FLAGS_STASHED_IN_APP_IGNORING_IME =
             FLAGS_STASHED_IN_APP & ~FLAG_STASHED_IN_APP_IME;
@@ -90,13 +88,13 @@
     // Currently any flag that causes us to stash in an app is included, except for IME or All Apps
     // since those cover the underlying app anyway and thus the app shouldn't change insets.
     private static final int FLAGS_REPORT_STASHED_INSETS_TO_APP = FLAGS_STASHED_IN_APP
-            & ~FLAG_STASHED_IN_APP_IME & ~FLAG_STASHED_IN_APP_ALL_APPS;
+            & ~FLAG_STASHED_IN_APP_IME & ~FLAG_STASHED_IN_TASKBAR_ALL_APPS;
 
     /**
      * How long to stash/unstash when manually invoked via long press.
      */
     public static final long TASKBAR_STASH_DURATION =
-            WindowManagerWrapper.ANIMATION_DURATION_RESIZE;
+            InsetsController.ANIMATION_DURATION_RESIZE;
 
     /**
      * How long to stash/unstash when keyboard is appearing/disappearing.
@@ -162,7 +160,7 @@
     private boolean mIsImeShowing;
     private boolean mIsImeSwitcherShowing;
 
-    private boolean mEnableManualStashingForTests = false;
+    private boolean mEnableManualStashingDuringTests = false;
 
     // Evaluate whether the handle should be stashed
     private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
@@ -170,9 +168,11 @@
                 boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
                 boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
                 boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
+                boolean stashedInTaskbarAllApps =
+                        hasAnyFlag(flags, FLAG_STASHED_IN_TASKBAR_ALL_APPS);
                 boolean stashedForSmallScreen = hasAnyFlag(flags, FLAG_STASHED_SMALL_SCREEN);
                 return (inApp && stashedInApp) || (!inApp && stashedLauncherState)
-                        || stashedForSmallScreen;
+                        || stashedInTaskbarAllApps || stashedForSmallScreen;
             });
 
     public TaskbarStashController(TaskbarActivityContext activity) {
@@ -242,15 +242,16 @@
      */
     protected boolean supportsManualStashing() {
         return supportsVisualStashing()
-                && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || mEnableManualStashingForTests);
+                && isInApp()
+                && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || mEnableManualStashingDuringTests);
     }
 
     /**
      * Enables support for manual stashing. This should only be used to add this functionality
      * to Launcher specific tests.
      */
-    public void enableManualStashingForTests(boolean enableManualStashing) {
-        mEnableManualStashingForTests = enableManualStashing;
+    public void enableManualStashingDuringTests(boolean enableManualStashing) {
+        mEnableManualStashingDuringTests = enableManualStashing;
     }
 
     /**
@@ -319,6 +320,13 @@
     }
 
     /**
+     * Returns the height that taskbar will be touchable.
+     */
+    public int getTouchableHeight() {
+        return mIsStashed ? mStashedHeight : mUnstashedHeight;
+    }
+
+    /**
      * Returns the height that taskbar will inset when inside apps.
      * @see WindowInsets.Type#navigationBars()
      * @see WindowInsets.Type#systemBars()
@@ -347,6 +355,11 @@
             }
             return mStashedHeight;
         }
+
+        if (!mActivity.isUserSetupComplete()) {
+            // Special insets for SUW.
+            return mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_suw_insets);
+        }
         return mUnstashedHeight;
     }
 
@@ -546,13 +559,7 @@
     }
 
     private void addJankMonitorListener(AnimatorSet animator, boolean expanding) {
-        Optional<View> optionalView =
-                Arrays.stream(mControllers.taskbarViewController.getIconViews()).findFirst();
-        if (optionalView.isEmpty()) {
-            Log.wtf(TAG, "No views to start Interaction jank monitor with.", new Exception());
-            return;
-        }
-        View v = optionalView.get();
+        View v = mControllers.taskbarActivityContext.getDragLayer();
         int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND :
                 InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE;
         animator.addListener(new AnimatorListenerAdapter() {
@@ -667,7 +674,7 @@
             return;
         }
 
-        updateStateForFlag(FLAG_STASHED_IN_APP_ALL_APPS, false);
+        updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, false);
         if (applyState) {
             applyState(ALL_APPS.getTransitionDuration(
                     mControllers.taskbarActivityContext, false /* isToState */));
@@ -787,7 +794,7 @@
         appendFlag(str, flags, FLAG_STASHED_IN_APP_SETUP, "FLAG_STASHED_IN_APP_SETUP");
         appendFlag(str, flags, FLAG_STASHED_IN_APP_IME, "FLAG_STASHED_IN_APP_IME");
         appendFlag(str, flags, FLAG_IN_STASHED_LAUNCHER_STATE, "FLAG_IN_STASHED_LAUNCHER_STATE");
-        appendFlag(str, flags, FLAG_STASHED_IN_APP_ALL_APPS, "FLAG_STASHED_IN_APP_ALL_APPS");
+        appendFlag(str, flags, FLAG_STASHED_IN_TASKBAR_ALL_APPS, "FLAG_STASHED_IN_APP_ALL_APPS");
         appendFlag(str, flags, FLAG_IN_SETUP, "FLAG_IN_SETUP");
         return str.toString();
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
index 64a4fa7..4c937a7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
@@ -15,13 +15,13 @@
  */
 package com.android.launcher3.taskbar;
 
-import android.view.IWindowManager;
 import android.view.View;
 import android.view.WindowManager;
 
 import com.android.quickstep.util.LauncherViewsMoveFromCenterTranslationApplier;
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
 
@@ -41,16 +41,20 @@
 
     public TaskbarUnfoldAnimationController(BaseTaskbarContext context,
             ScopedUnfoldTransitionProgressProvider source,
-            WindowManager windowManager, IWindowManager iWindowManager) {
+            WindowManager windowManager,
+            RotationChangeProvider rotationChangeProvider) {
         mScopedUnfoldTransitionProgressProvider = source;
         mNaturalUnfoldTransitionProgressProvider =
-                new NaturalRotationUnfoldProgressProvider(context, iWindowManager, source);
+                new NaturalRotationUnfoldProgressProvider(context,
+                        rotationChangeProvider,
+                        source);
         mMoveFromCenterAnimator = new UnfoldMoveFromCenterAnimator(windowManager,
                 new LauncherViewsMoveFromCenterTranslationApplier());
     }
 
     /**
      * Initializes the controller
+     *
      * @param taskbarControllers references to all other taskbar controllers
      */
     public void init(TaskbarControllers taskbarControllers) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index dbf9759..bb82d19 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -16,10 +16,13 @@
 package com.android.launcher3.taskbar;
 
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.os.SystemProperties;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -53,6 +56,7 @@
  * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
  */
 public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable {
+    private static final String TAG = TaskbarView.class.getSimpleName();
 
     private static final float TASKBAR_BACKGROUND_LUMINANCE = 0.30f;
     public int mThemeIconsBackground;
@@ -81,6 +85,12 @@
 
     private View mQsb;
 
+    // Only non-null when device supports having a floating task.
+    private @Nullable BubbleTextView mFloatingTaskButton;
+    private @Nullable Intent mFloatingTaskIntent;
+    private static final boolean FLOATING_TASKS_ENABLED =
+            SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false);
+
     public TaskbarView(@NonNull Context context) {
         this(context, null);
     }
@@ -123,6 +133,19 @@
 
         // TODO: Disable touch events on QSB otherwise it can crash.
         mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
+
+        if (FLOATING_TASKS_ENABLED) {
+            mFloatingTaskIntent = FloatingTaskIntentResolver.getIntent(context);
+            if (mFloatingTaskIntent != null) {
+                mFloatingTaskButton = new LaunchFloatingTaskButton(context);
+                mFloatingTaskButton.setLayoutParams(
+                        new ViewGroup.LayoutParams(mIconTouchSize, mIconTouchSize));
+                mFloatingTaskButton.setPadding(mItemPadding, mItemPadding, mItemPadding,
+                        mItemPadding);
+            } else {
+                Log.d(TAG, "Floating tasks is enabled but no intent was found!");
+            }
+        }
     }
 
     private int getColorWithGivenLuminance(int color, float luminance) {
@@ -150,6 +173,10 @@
         if (mAllAppsButton != null) {
             mAllAppsButton.setOnClickListener(mControllerCallbacks.getAllAppsButtonClickListener());
         }
+        if (mFloatingTaskButton != null) {
+            mFloatingTaskButton.setOnClickListener(
+                    mControllerCallbacks.getFloatingTaskButtonListener(mFloatingTaskIntent));
+        }
     }
 
     private void removeAndRecycle(View view) {
@@ -174,6 +201,10 @@
         }
         removeView(mQsb);
 
+        if (mFloatingTaskButton != null) {
+            removeView(mFloatingTaskButton);
+        }
+
         for (int i = 0; i < hotseatItemInfos.length; i++) {
             ItemInfo hotseatItemInfo = hotseatItemInfos[i];
             if (hotseatItemInfo == null) {
@@ -255,6 +286,11 @@
             mQsb.setVisibility(View.INVISIBLE);
         }
 
+        if (mFloatingTaskButton != null) {
+            int index = Utilities.isRtl(getResources()) ? 0 : getChildCount();
+            addView(mFloatingTaskButton, index);
+        }
+
         mThemeIconsBackground = calculateThemeIconsBackground();
         setThemedIconsBackgroundColor(mThemeIconsBackground);
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 992aa4b..16dd90d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -24,6 +24,7 @@
 import static com.android.quickstep.AnimatedFloat.VALUE;
 
 import android.annotation.NonNull;
+import android.content.Intent;
 import android.graphics.Rect;
 import android.util.FloatProperty;
 import android.util.Log;
@@ -51,6 +52,7 @@
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.SystemUiProxy;
 
 import java.io.PrintWriter;
 import java.util.function.Predicate;
@@ -100,6 +102,9 @@
 
     private int mThemeIconsColor;
 
+    private final DeviceProfile.OnDeviceProfileChangeListener mDeviceProfileChangeListener =
+            dp -> commitRunningAppsToUI();
+
     public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
         mActivity = activity;
         mTaskbarView = taskbarView;
@@ -127,10 +132,13 @@
                 controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY();
         mTaskbarNavButtonTranslationYForInAppDisplay = controllers.navbarButtonsViewController
                 .getTaskbarNavButtonTranslationYForInAppDisplay();
+
+        mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
     }
 
     public void onDestroy() {
         LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
+        mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
         mModelCallbacks.unregisterListeners();
     }
 
@@ -427,6 +435,13 @@
             };
         }
 
+        public View.OnClickListener getFloatingTaskButtonListener(@NonNull Intent intent) {
+            return v -> {
+                SystemUiProxy proxy = SystemUiProxy.INSTANCE.get(v.getContext());
+                proxy.showFloatingTask(intent);
+            };
+        }
+
         public View.OnLongClickListener getIconOnLongClickListener() {
             return mControllers.taskbarDragController::startDragOnLongClick;
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt b/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt
index 81acda3..076900c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/VoiceInteractionWindowController.kt
@@ -77,7 +77,7 @@
             fadeStashedHandle.end()
         }
 
-        moveTaskbarBackgroundToAppropriateLayer()
+        moveTaskbarBackgroundToAppropriateLayer(skipAnim)
     }
 
     /**
@@ -86,24 +86,28 @@
      * OR
      * Removes the temporary window and show the TaskbarDragLayer background again.
      */
-    private fun moveTaskbarBackgroundToAppropriateLayer() {
+    private fun moveTaskbarBackgroundToAppropriateLayer(skipAnim: Boolean) {
         val taskbarBackgroundOverride = controllers.taskbarDragLayerController
             .overrideBackgroundAlpha
         val moveToLowerLayer = isVoiceInteractionWindowVisible
-        if (moveToLowerLayer) {
+        val onWindowsSynchronized = if (moveToLowerLayer) {
             // First add the temporary window, then hide the overlapping taskbar background.
-            context.addWindowView(separateWindowForTaskbarBackground, separateWindowLayoutParams)
-            ViewRootSync.synchronizeNextDraw(separateWindowForTaskbarBackground, context.dragLayer
-            ) {
-                taskbarBackgroundOverride.updateValue(0f)
-            }
+            context.addWindowView(separateWindowForTaskbarBackground, separateWindowLayoutParams);
+            { taskbarBackgroundOverride.updateValue(0f) }
         } else {
             // First reapply the original taskbar background, then remove the temporary window.
-            taskbarBackgroundOverride.updateValue(1f)
-            ViewRootSync.synchronizeNextDraw(separateWindowForTaskbarBackground, context.dragLayer
-            ) {
-                context.removeWindowView(separateWindowForTaskbarBackground)
-            }
+            taskbarBackgroundOverride.updateValue(1f);
+            { context.removeWindowView(separateWindowForTaskbarBackground) }
+        }
+
+        if (skipAnim) {
+            onWindowsSynchronized()
+        } else {
+            ViewRootSync.synchronizeNextDraw(
+                separateWindowForTaskbarBackground,
+                context.dragLayer,
+                onWindowsSynchronized
+            )
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
index c76180e..0372f67 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
@@ -17,15 +17,16 @@
 
 import static android.view.KeyEvent.ACTION_UP;
 import static android.view.KeyEvent.KEYCODE_BACK;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
-
 import android.content.Context;
 import android.graphics.Insets;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
 import android.view.WindowInsets;
 
 import com.android.launcher3.AbstractFloatingView;
@@ -40,6 +41,7 @@
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.taskbar.BaseTaskbarContext;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarControllers;
 import com.android.launcher3.taskbar.TaskbarDragController;
 import com.android.launcher3.taskbar.TaskbarStashController;
 import com.android.launcher3.testing.TestLogging;
@@ -47,9 +49,6 @@
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInsetsListener;
 
 /**
  * Window context for the taskbar all apps overlay.
@@ -74,7 +73,7 @@
     TaskbarAllAppsContext(
             TaskbarActivityContext taskbarContext,
             TaskbarAllAppsController windowController,
-            TaskbarStashController taskbarStashController) {
+            TaskbarControllers taskbarControllers) {
         super(taskbarContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null));
         mTaskbarContext = taskbarContext;
         mWindowController = windowController;
@@ -88,9 +87,10 @@
                 this,
                 slideInView,
                 windowController,
-                taskbarStashController);
+                taskbarControllers);
         mAppsView = slideInView.getAppsView();
 
+        TaskbarStashController taskbarStashController = taskbarControllers.taskbarStashController;
         mWillTaskbarBeVisuallyStashed = taskbarStashController.supportsVisualStashing();
         mStashedTaskbarHeight = taskbarStashController.getStashedHeight();
     }
@@ -163,7 +163,7 @@
 
     /** Root drag layer for this context. */
     private static class TaskbarAllAppsDragLayer extends
-            BaseDragLayer<TaskbarAllAppsContext> implements OnComputeInsetsListener {
+            BaseDragLayer<TaskbarAllAppsContext> implements OnComputeInternalInsetsListener {
 
         private TaskbarAllAppsDragLayer(Context context) {
             super(context, null, 1);
@@ -174,14 +174,13 @@
         @Override
         protected void onAttachedToWindow() {
             super.onAttachedToWindow();
-            ViewTreeObserverWrapper.addOnComputeInsetsListener(
-                    getViewTreeObserver(), this);
+            getViewTreeObserver().addOnComputeInternalInsetsListener(this);
         }
 
         @Override
         protected void onDetachedFromWindow() {
             super.onDetachedFromWindow();
-            ViewTreeObserverWrapper.removeOnComputeInsetsListener(this);
+            getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
         }
 
         @Override
@@ -207,7 +206,7 @@
         }
 
         @Override
-        public void onComputeInsets(InsetsInfo inoutInfo) {
+        public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
             if (mActivity.mDragController.isSystemDragInProgress()) {
                 inoutInfo.touchableRegion.setEmpty();
                 inoutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index 6c43e50..1671a0f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -144,9 +144,7 @@
         // to catch invalid states.
         mControllers.getSharedState().allAppsVisible = true;
 
-        mAllAppsContext = new TaskbarAllAppsContext(mTaskbarContext,
-                this,
-                mControllers.taskbarStashController);
+        mAllAppsContext = new TaskbarAllAppsContext(mTaskbarContext, this, mControllers);
         mAllAppsContext.getDragController().init(mControllers);
         TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
         Optional.ofNullable(mAllAppsContext.getSystemService(WindowManager.class))
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 0e62da3..9d48c8d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -30,13 +30,10 @@
 import com.android.launcher3.R;
 import com.android.launcher3.views.AbstractSlideInView;
 
-import java.util.Optional;
-
 /** Wrapper for taskbar all apps with slide-in behavior. */
 public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarAllAppsContext>
         implements Insettable, DeviceProfile.OnDeviceProfileChangeListener {
     private TaskbarAllAppsContainerView mAppsView;
-    private OnCloseListener mOnCloseBeginListener;
     private float mShiftRange;
 
     public TaskbarAllAppsSlideInView(Context context, AttributeSet attrs) {
@@ -72,14 +69,8 @@
         return mAppsView;
     }
 
-    /** Callback invoked when the view is beginning to close (e.g. close animation is started). */
-    void setOnCloseBeginListener(OnCloseListener onCloseBeginListener) {
-        mOnCloseBeginListener = onCloseBeginListener;
-    }
-
     @Override
     protected void handleClose(boolean animate) {
-        Optional.ofNullable(mOnCloseBeginListener).ifPresent(OnCloseListener::onSlideInViewClosed);
         handleClose(animate,
                 ALL_APPS.getTransitionDuration(mActivityContext, false /* isToState */));
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
index a39e872..128fa5e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -16,12 +16,14 @@
 package com.android.launcher3.taskbar.allapps;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_APP_ALL_APPS;
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_TASKBAR_ALL_APPS;
 import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.appprediction.AppsDividerView;
 import com.android.launcher3.appprediction.PredictionRowView;
+import com.android.launcher3.taskbar.NavbarButtonsViewController;
+import com.android.launcher3.taskbar.TaskbarControllers;
 import com.android.launcher3.taskbar.TaskbarStashController;
 
 /**
@@ -34,17 +36,19 @@
     private final TaskbarAllAppsSlideInView mSlideInView;
     private final TaskbarAllAppsContainerView mAppsView;
     private final TaskbarStashController mTaskbarStashController;
+    private final NavbarButtonsViewController mNavbarButtonsViewController;
 
     TaskbarAllAppsViewController(
             TaskbarAllAppsContext context,
             TaskbarAllAppsSlideInView slideInView,
             TaskbarAllAppsController windowController,
-            TaskbarStashController taskbarStashController) {
+            TaskbarControllers taskbarControllers) {
 
         mContext = context;
         mSlideInView = slideInView;
         mAppsView = mSlideInView.getAppsView();
-        mTaskbarStashController = taskbarStashController;
+        mTaskbarStashController = taskbarControllers.taskbarStashController;
+        mNavbarButtonsViewController = taskbarControllers.navbarButtonsViewController;
 
         setUpIconLongClick();
         setUpAppDivider();
@@ -80,10 +84,12 @@
     }
 
     private void setUpTaskbarStashing() {
-        mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_APP_ALL_APPS, true);
+        mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, true);
         mTaskbarStashController.applyState(
                 ALL_APPS.getTransitionDuration(mContext, true /* isToState */));
+        mNavbarButtonsViewController.setSlideInViewVisible(true);
         mSlideInView.setOnCloseBeginListener(() -> {
+            mNavbarButtonsViewController.setSlideInViewVisible(false);
             AbstractFloatingView.closeOpenContainer(
                     mContext, AbstractFloatingView.TYPE_ACTION_POPUP);
             // Post in case view is closing due to gesture navigation. If a gesture is in progress,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 2e4e739..8fb7030 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -88,6 +88,11 @@
             return;
         }
         setStateWithAnimationInternal(toState, config, builder);
+        builder.addEndListener(success -> {
+            if (!success) {
+                mRecentsView.reset();
+            }
+        });
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 5f3a990..bf0f8f7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -49,6 +49,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.IconNormalizer;
@@ -271,7 +272,7 @@
         mIsPinned = true;
         applyFromWorkspaceItem(info);
         setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
-        ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
+        ((CellLayoutLayoutParams) getLayoutParams()).canReorder = true;
         invalidate();
     }
 
@@ -280,7 +281,7 @@
      */
     public void finishBinding(OnLongClickListener longClickListener) {
         setOnLongClickListener(longClickListener);
-        ((CellLayout.LayoutParams) getLayoutParams()).canReorder = false;
+        ((CellLayoutLayoutParams) getLayoutParams()).canReorder = false;
         setTextVisibility(false);
         verifyHighRes();
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 585dec5..ee9845b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -27,9 +27,10 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_WIDGET_PICKER_DEPTH;
 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;
@@ -44,6 +45,7 @@
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
 import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.util.BaseDepthController.WIDGET_DEPTH;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
 
 import android.animation.AnimatorSet;
@@ -64,6 +66,7 @@
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
+import android.view.WindowManagerGlobal;
 import android.window.SplashScreen;
 
 import androidx.annotation.Nullable;
@@ -75,6 +78,7 @@
 import com.android.launcher3.QuickstepAccessibilityDelegate;
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -117,9 +121,6 @@
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.TouchController;
-import com.android.launcher3.util.UiThreadHelper;
-import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
-import com.android.launcher3.util.ViewCapture;
 import com.android.launcher3.widget.LauncherAppWidgetHost;
 import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.RecentsModel;
@@ -133,18 +134,20 @@
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.TISBindHelper;
+import com.android.quickstep.util.ViewCapture;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.unfold.UnfoldSharedComponent;
 import com.android.systemui.unfold.UnfoldTransitionFactory;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
 import com.android.systemui.unfold.config.UnfoldTransitionConfig;
 import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider;
 import com.android.systemui.unfold.system.DeviceStateManagerFoldProvider;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -160,11 +163,6 @@
             SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", false);
 
     public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
-    /**
-     * Reusable command for applying the shelf height on the background thread.
-     */
-    public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
-            SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
 
     private FixedContainerItems mAllAppsPredictions;
     private HotseatPredictionController mHotseatPredictionController;
@@ -178,6 +176,7 @@
     // Will be updated when dragging from taskbar.
     private @Nullable DragOptions mNextWorkspaceDragOptions = null;
     private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
+    private @Nullable RotationChangeProvider mRotationChangeProvider;
     private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
     /**
      * If Launcher restarted while in the middle of an Overview split select, it needs this data to
@@ -195,7 +194,7 @@
         RecentsView overviewPanel = (RecentsView) getOverviewPanel();
         SplitSelectStateController controller =
                 new SplitSelectStateController(this, mHandler, getStateManager(),
-                        getDepthController());
+                        getDepthController(), getStatsLogManager());
         overviewPanel.init(mActionsView, controller);
         mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
         mActionsView.updateVerticalMargin(DisplayController.getNavigationMode(this));
@@ -349,8 +348,7 @@
                 boolean visible = (state == NORMAL || state == OVERVIEW)
                         && (willUserBeActive || isUserActive())
                         && !profile.isVerticalBarLayout();
-                UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0,
-                        profile.hotseatBarSizePx);
+                SystemUiProxy.INSTANCE.get(this).setShelfHeight(visible, profile.hotseatBarSizePx);
             }
         }
         if (state == NORMAL && !inTransition) {
@@ -564,10 +562,8 @@
     @Override
     protected void onScreenOff() {
         super.onScreenOff();
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            RecentsView recentsView = getOverviewPanel();
-            recentsView.finishRecentsAnimation(true /* toRecents */, null);
-        }
+        RecentsView recentsView = getOverviewPanel();
+        recentsView.finishRecentsAnimation(true /* toRecents */, null);
     }
 
     /**
@@ -591,6 +587,13 @@
     public void onWidgetsTransition(float progress) {
         super.onWidgetsTransition(progress);
         onTaskbarInAppDisplayProgressUpdate(progress, WIDGETS_PAGE_PROGRESS_INDEX);
+        // Change of wallpaper depth in widget picker is disabled for tests as it causes flakiness
+        // on very slow cuttlefish devices.
+        if (ENABLE_WIDGET_PICKER_DEPTH.get() && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            WIDGET_DEPTH.set(getDepthController(),
+                    Utilities.mapToRange(progress, 0f, 1f, 0f, getDeviceProfile().bottomSheetDepth,
+                            EMPHASIZED));
+        }
     }
 
     private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) {
@@ -665,8 +668,8 @@
     private void initUnfoldTransitionProgressProvider() {
         final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
         if (config.isEnabled()) {
-            mUnfoldTransitionProgressProvider =
-                    UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
+            UnfoldSharedComponent unfoldComponent =
+                    UnfoldTransitionFactory.createUnfoldSharedComponent(
                             /* context= */ this,
                             config,
                             ProxyScreenStatusProvider.INSTANCE,
@@ -678,13 +681,21 @@
                             getMainThreadHandler(),
                             getMainExecutor(),
                             /* backgroundExecutor= */ THREAD_POOL_EXECUTOR,
-                            /* tracingTagPrefix= */ "launcher"
+                            /* tracingTagPrefix= */ "launcher",
+                            WindowManagerGlobal.getWindowManagerService()
                     );
 
+            mUnfoldTransitionProgressProvider = unfoldComponent.getUnfoldTransitionProvider()
+                    .orElseThrow(() -> new IllegalStateException(
+                            "Trying to create UnfoldTransitionProgressProvider when the "
+                                    + "transition is disabled"));
+
+            mRotationChangeProvider = unfoldComponent.getRotationChangeProvider();
             mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
-                    this,
+                    /* launcher= */ this,
                     getWindowManager(),
-                    mUnfoldTransitionProgressProvider
+                    mUnfoldTransitionProgressProvider,
+                    mRotationChangeProvider
             );
         }
     }
@@ -799,8 +810,8 @@
                         ? mAppTransitionManager.getActivityLaunchOptions(v)
                         : super.getActivityLaunchOptions(v, item);
         if (mLastTouchUpTime > 0) {
-            ActivityOptionsCompat.setLauncherSourceInfo(
-                    activityOptions.options, mLastTouchUpTime);
+            activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
+                    mLastTouchUpTime);
         }
         activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
         activityOptions.options.setLaunchDisplayId(
@@ -893,13 +904,13 @@
         // load in, and then proceed to OverviewSplitSelect.
         if (isInState(OVERVIEW_SPLIT_SELECT)) {
             SplitSelectStateController splitSelectStateController =
-                    ((RecentsView) getOverviewPanel()).getSplitPlaceholder();
+                    ((RecentsView) getOverviewPanel()).getSplitSelectController();
             // Launcher will restart in Overview and then transition to OverviewSplitSelect.
             outState.putIBinder(PENDING_SPLIT_SELECT_INFO, ObjectWrapper.wrap(
                     new PendingSplitSelectInfo(
                             splitSelectStateController.getInitialTaskId(),
-                            splitSelectStateController.getActiveSplitStagePosition()
-                    )
+                            splitSelectStateController.getActiveSplitStagePosition(),
+                            splitSelectStateController.getSplitEvent())
             ));
             outState.putInt(RUNTIME_STATE, OVERVIEW.ordinal);
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 5cddd07..0e1120b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -27,21 +27,25 @@
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION;
 import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
 
+import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
 import android.os.Build;
 import android.util.FloatProperty;
+import android.util.Log;
 import android.util.Pair;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.util.AnimUtils;
+import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.views.ClearAllButton;
 import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
@@ -71,7 +75,10 @@
         // DepthController to prevent optimizations which might occlude the layers behind
         mLauncher.getDepthController().setHasContentBehindLauncher(state.overviewUi);
 
-        handleSplitSelectionState(state, null);
+        PendingAnimation builder =
+                new PendingAnimation(state.getTransitionDuration(mLauncher, true));
+
+        handleSplitSelectionState(state, builder, /* animate */false);
     }
 
     @Override
@@ -83,6 +90,13 @@
             // While animating into recents, update the visible task data as needed
             builder.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
             mRecentsView.updateEmptyMessage();
+            // TODO(b/246283207): Remove logging once root cause of flake detected.
+            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                Log.d("b/246283207", "RecentsView#setStateWithAnimationInternal getCurrentPage(): "
+                                + mRecentsView.getCurrentPage()
+                                + ", getScrollForPage(getCurrentPage())): "
+                                + mRecentsView.getScrollForPage(mRecentsView.getCurrentPage()));
+            }
         } else {
             builder.addListener(
                     AnimatorListeners.forSuccessCallback(mRecentsView::resetTaskVisuals));
@@ -92,7 +106,7 @@
         builder.addListener(AnimatorListeners.forSuccessCallback(() ->
                 mLauncher.getDepthController().setHasContentBehindLauncher(toState.overviewUi)));
 
-        handleSplitSelectionState(toState, builder);
+        handleSplitSelectionState(toState, builder, /* animate */true);
 
         setAlphas(builder, config, toState);
         builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
@@ -105,8 +119,14 @@
      *                will add animations to builder.
      */
     private void handleSplitSelectionState(@NonNull LauncherState toState,
-            @Nullable PendingAnimation builder) {
-        boolean animate = builder != null;
+            @NonNull PendingAnimation builder, boolean animate) {
+        if (toState != OVERVIEW_SPLIT_SELECT) {
+            // Not going to split, nothing to do but ensure taskviews are at correct offset
+            mRecentsView.resetSplitPrimaryScrollOffset();
+            return;
+        }
+
+        // Create transition animations to split select
         PagedOrientationHandler orientationHandler =
                 ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
         Pair<FloatProperty, FloatProperty> taskViewsFloat =
@@ -114,25 +134,27 @@
                         TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
                         mLauncher.getDeviceProfile());
 
-        if (toState == OVERVIEW_SPLIT_SELECT) {
-            // Animation to "dismiss" selected taskView
-            PendingAnimation splitSelectInitAnimation = mRecentsView.createSplitSelectInitAnimation(
-                    toState.getTransitionDuration(mLauncher, true /* isToState */));
-            // Add properties to shift remaining taskViews to get out of placeholder view
-            splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.first,
-                    toState.getSplitSelectTranslation(mLauncher), LINEAR);
-            splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR);
+        SplitAnimationTimings timings =
+                AnimUtils.getDeviceOverviewToSplitTimings(mLauncher.getDeviceProfile().isTablet);
 
-            if (!animate) {
-                splitSelectInitAnimation.buildAnim().start();
-            } else {
-                builder.add(splitSelectInitAnimation.buildAnim());
-            }
+        mRecentsView.createSplitSelectInitAnimation(builder,
+                toState.getTransitionDuration(mLauncher, true /* isToState */));
+        // Shift tasks vertically downward to get out of placeholder view
+        builder.setFloat(mRecentsView, taskViewsFloat.first,
+                toState.getSplitSelectTranslation(mLauncher),
+                timings.getGridSlidePrimaryInterpolator());
+        // Zero out horizontal translation
+        builder.setFloat(mRecentsView, taskViewsFloat.second,
+                0,
+                timings.getGridSlideSecondaryInterpolator());
 
-            mRecentsView.applySplitPrimaryScrollOffset();
-        } else {
-            mRecentsView.resetSplitPrimaryScrollOffset();
+        if (!animate) {
+            AnimatorSet as = builder.buildAnim();
+            as.start();
+            as.end();
         }
+
+        mRecentsView.applySplitPrimaryScrollOffset();
     }
 
     private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index e21f14f..fd184c6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
 
 import android.content.Context;
@@ -26,7 +25,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Themes;
 
 /**
@@ -44,9 +42,9 @@
     @Override
     public <DEVICE_PROFILE_CONTEXT extends Context & DeviceProfileListenable>
     int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) {
-        return context.getDeviceProfile().isTablet
-                ? 500
-                :  isToState ? 600 : 300;
+        return isToState
+                ? context.getDeviceProfile().allAppsOpenDuration
+                : context.getDeviceProfile().allAppsCloseDuration;
     }
 
     @Override
@@ -83,15 +81,7 @@
     protected <DEVICE_PROFILE_CONTEXT extends Context & DeviceProfile.DeviceProfileListenable>
             float getDepthUnchecked(DEVICE_PROFILE_CONTEXT context) {
         if (context.getDeviceProfile().isTablet) {
-            // The goal is to set wallpaper to zoom at workspaceContentScale when in AllApps.
-            // When depth is 0, wallpaper zoom is set to maxWallpaperScale.
-            // When depth is 1, wallpaper zoom is set to 1.
-            // For depth to achieve zoom set to maxWallpaperScale * workspaceContentScale:
-            float maxWallpaperScale = context.getResources().getFloat(
-                    com.android.internal.R.dimen.config_wallpaperMaxScale);
-            return Utilities.mapToRange(
-                    maxWallpaperScale * context.getDeviceProfile().workspaceContentScale,
-                    maxWallpaperScale, 1f, 0f, 1f, LINEAR);
+            return context.getDeviceProfile().bottomSheetDepth;
         } else {
             // The scrim fades in at approximately 50% of the swipe gesture.
             // This means that the depth should be greater than 1, in order to fully zoom out.
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 6f07568..d075750 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -26,6 +26,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Themes;
 import com.android.quickstep.util.LayoutUtils;
@@ -104,7 +105,12 @@
 
     @Override
     public boolean isTaskbarStashed(Launcher launcher) {
-        return true;
+        return !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get();
+    }
+
+    @Override
+    public boolean isTaskbarAlignedWithHotseat(Launcher launcher) {
+        return !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get();
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 872e64a..5eeeb36 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -34,6 +34,7 @@
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_0_75;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
@@ -62,6 +63,7 @@
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.util.RecentsAtomicAnimationFactory;
+import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -111,19 +113,19 @@
                 config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
                 config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, EMPHASIZED_DECELERATE);
                 config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, FINAL_FRAME);
+
+                // Scroll RecentsView to page 0 as it goes offscreen, if necessary.
+                int numPagesToScroll = overview.getNextPage() - DEFAULT_PAGE;
+                long scrollDuration = Math.min(MAX_PAGE_SCROLL_DURATION,
+                        numPagesToScroll * PER_PAGE_SCROLL_DURATION);
+                config.duration = Math.max(config.duration, scrollDuration);
+                overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
             } else {
                 config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL);
                 config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
                 config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
             }
 
-            // Scroll RecentsView to page 0 as it goes offscreen, if necessary.
-            int numPagesToScroll = overview.getNextPage() - DEFAULT_PAGE;
-            long scrollDuration = Math.min(MAX_PAGE_SCROLL_DURATION,
-                    numPagesToScroll * PER_PAGE_SCROLL_DURATION);
-            config.duration = Math.max(config.duration, scrollDuration);
-            overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
-
             Workspace<?> workspace = mActivity.getWorkspace();
             // Start from a higher workspace scale, but only if we're invisible so we don't jump.
             boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
@@ -188,6 +190,22 @@
             AllAppsSwipeController.applyAllAppsToNormalConfig(mActivity, config);
         } else if (fromState == NORMAL && toState == ALL_APPS) {
             AllAppsSwipeController.applyNormalToAllAppsAnimConfig(mActivity, config);
+        } else if (fromState == OVERVIEW && toState == OVERVIEW_SPLIT_SELECT) {
+            SplitAnimationTimings timings = mActivity.getDeviceProfile().isTablet
+                    ? SplitAnimationTimings.TABLET_OVERVIEW_TO_SPLIT
+                    : SplitAnimationTimings.PHONE_OVERVIEW_TO_SPLIT;
+            config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR,
+                    timings.getActionsFadeStartOffset(),
+                    timings.getActionsFadeEndOffset()));
+        } else if ((fromState == NORMAL || fromState == ALL_APPS)
+                && toState == OVERVIEW_SPLIT_SELECT) {
+            // Splitting from Home is currently only available on tablets
+            SplitAnimationTimings timings = SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
+            config.setInterpolator(ANIM_SCRIM_FADE, clampToProgress(LINEAR,
+                    timings.getScrimFadeInStartOffset(),
+                    timings.getScrimFadeInEndOffset()));
+            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_0_75);
+            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_0_75);
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index e79d56b..8babd34 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -16,7 +16,10 @@
 
 package com.android.launcher3.uioverrides.states;
 
+import android.content.Context;
+
 import com.android.launcher3.Launcher;
+import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -38,4 +41,16 @@
         RecentsView recentsView = launcher.getOverviewPanel();
         return recentsView.getSplitSelectTranslation();
     }
+
+    @Override
+    public int getTransitionDuration(Context context, boolean isToState) {
+        boolean isTablet = ((Launcher) context).getDeviceProfile().isTablet;
+        if (isToState && isTablet) {
+            return SplitAnimationTimings.TABLET_ENTER_DURATION;
+        } else if (isToState && !isTablet) {
+            return SplitAnimationTimings.PHONE_ENTER_DURATION;
+        } else {
+            return SplitAnimationTimings.ABORT_DURATION;
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 30bb892..40dfd82 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -25,7 +25,6 @@
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_TRANSLATION;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
@@ -139,9 +138,7 @@
             AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher,
                     builder);
 
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                builder.addOnFrameCallback(recentsView::redrawLiveTile);
-            }
+            builder.addOnFrameCallback(recentsView::redrawLiveTile);
 
             AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_TASK_MENU);
         } else if (mStartState == ALL_APPS) {
@@ -182,11 +179,9 @@
         boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS
                 || (velocity < 0 && fling);
         if (success) {
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                RecentsView recentsView = mLauncher.getOverviewPanel();
-                recentsView.switchToScreenshot(null,
-                        () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
-            }
+            RecentsView recentsView = mLauncher.getOverviewPanel();
+            recentsView.switchToScreenshot(null,
+                    () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
             if (mStartState.overviewUi) {
                 new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState))
                         .animateWithVelocity(velocity);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index ca7f633..c49848a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -236,7 +236,8 @@
         PendingAnimation pa;
         if (goingUp) {
             currentInterpolator = Interpolators.LINEAR;
-            pa = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
+            pa = new PendingAnimation(maxDuration);
+            mRecentsView.createTaskDismissAnimation(pa, mTaskBeingDragged,
                     true /* animateTaskView */, true /* removeTask */, maxDuration,
                     false /* dismissingForSplitSelection*/);
 
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 81a5c1c..07ddcc8 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -26,7 +26,6 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
@@ -50,7 +49,6 @@
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FINISH_RECENTS_ANIMATION;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
 import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -77,6 +75,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnApplyWindowInsetsListener;
+import android.view.ViewGroup;
 import android.view.ViewTreeObserver.OnDrawListener;
 import android.view.ViewTreeObserver.OnScrollChangedListener;
 import android.view.WindowInsets;
@@ -87,6 +86,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
+import com.android.internal.util.LatencyTracker;
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
@@ -127,14 +127,18 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.startingsurface.SplashScreenExitAnimationUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Optional;
 import java.util.function.Consumer;
 
 /**
@@ -250,6 +254,13 @@
 
     private static final float MAX_QUICK_SWITCH_RECENTS_SCALE_PROGRESS = 0.07f;
 
+    // Controls task thumbnail splash's reveal animation after landing on a task from quickswitch.
+    // These values match WindowManager/Shell starting_window_app_reveal_* config values.
+    private static final int SPLASH_FADE_OUT_DURATION = 133;
+    private static final int SPLASH_APP_REVEAL_DELAY = 83;
+    private static final int SPLASH_APP_REVEAL_DURATION = 266;
+    private static final int SPLASH_ANIMATION_DURATION = 349;
+
     /**
      * Used as the page index for logging when we return to the last task at the end of the gesture.
      */
@@ -274,7 +285,7 @@
     private AnimatorControllerWithResistance mLauncherTransitionController;
     private boolean mHasEndedLauncherTransition;
 
-    private AnimationFactory mAnimationFactory = (t) -> { };
+    private AnimationFactory mAnimationFactory = (t, s) -> { };
 
     private boolean mWasLauncherAlreadyVisible;
 
@@ -287,6 +298,8 @@
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
 
+    private final int mSplashMainWindowShiftLength;
+
     private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
 
     private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
@@ -322,6 +335,9 @@
         mQuickSwitchScaleScrollThreshold = context.getResources().getDimension(
                 R.dimen.quick_switch_scaling_scroll_threshold);
 
+        mSplashMainWindowShiftLength = -context.getResources().getDimensionPixelSize(
+                R.dimen.starting_surface_exit_animation_window_shift_length);
+
         initAfterSubclassConstructor();
         initStateCallbacks();
     }
@@ -401,12 +417,6 @@
                 this::resetStateForAnimationCancel);
         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_FINISH_WITH_NO_END,
                 this::resetStateForAnimationCancel);
-
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
-                            | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
-                    (b) -> mRecentsView.setRunningTaskHidden(!b));
-        }
     }
 
     protected boolean onActivityInit(Boolean alreadyOnHome) {
@@ -509,7 +519,9 @@
             Runnable initAnimFactory = () -> {
                 mAnimationFactory = mActivityInterface.prepareRecentsUI(mDeviceState,
                         mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
-                maybeUpdateRecentsAttachedState(false /* animate */);
+                maybeUpdateRecentsAttachedState(
+                        false /* animate */,
+                        new ActiveGestureLog.CompoundString("on Launcher start (animate=false)"));
                 if (mGestureState.getEndTarget() != null) {
                     // Update the end target in case the gesture ended before we init.
                     mAnimationFactory.setEndTarget(mGestureState.getEndTarget());
@@ -584,14 +596,10 @@
     }
 
     private void onDeferredActivityLaunch() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mActivityInterface.switchRunningTaskViewToScreenshot(
-                    null, () -> {
-                        mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
-                    });
-        } else {
-            mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
-        }
+        mActivityInterface.switchRunningTaskViewToScreenshot(
+                null, () -> {
+                    mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
+                });
     }
 
     private void setupRecentsViewUi() {
@@ -618,12 +626,13 @@
     }
 
     private void initializeLauncherAnimationController() {
-        buildAnimationController();
+        buildAnimationController(new ActiveGestureLog.CompoundString(
+                "initializing launcher animation controller"));
 
         Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents",
                 TraceHelper.FLAG_IGNORE_BINDERS);
-        LatencyTrackerCompat.logToggleRecents(
-                mContext, (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
+        LatencyTracker.getInstance(mContext).logAction(LatencyTracker.ACTION_TOGGLE_RECENTS,
+                (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
         TraceHelper.INSTANCE.endSection(traceToken);
 
         // This method is only called when STATE_GESTURE_STARTED is set, so we can enable the
@@ -637,7 +646,11 @@
             @Override
             public void onMotionPauseDetected() {
                 mHasMotionEverBeenPaused = true;
-                maybeUpdateRecentsAttachedState(true/* animate */, true/* moveFocusedTask */);
+                maybeUpdateRecentsAttachedState(
+                        true/* animate */,
+                        true/* moveFocusedTask */,
+                        new ActiveGestureLog.CompoundString(
+                                "motion pause detected (animate=true)"));
                 performHapticFeedback();
             }
 
@@ -648,12 +661,13 @@
         };
     }
 
-    private void maybeUpdateRecentsAttachedState() {
-        maybeUpdateRecentsAttachedState(true /* animate */);
+    private void maybeUpdateRecentsAttachedState(ActiveGestureLog.CompoundString reason) {
+        maybeUpdateRecentsAttachedState(true /* animate */, reason.append(" (animate=true)"));
     }
 
-    private void maybeUpdateRecentsAttachedState(boolean animate) {
-        maybeUpdateRecentsAttachedState(animate, false /* moveFocusedTask */);
+    private void maybeUpdateRecentsAttachedState(
+            boolean animate, ActiveGestureLog.CompoundString reason) {
+        maybeUpdateRecentsAttachedState(animate, false /* moveFocusedTask */, reason);
     }
 
     /**
@@ -665,7 +679,8 @@
      * @param animate whether to animate when attaching RecentsView
      * @param moveFocusedTask whether to move focused task to front when attaching
      */
-    private void maybeUpdateRecentsAttachedState(boolean animate, boolean moveFocusedTask) {
+    private void maybeUpdateRecentsAttachedState(
+            boolean animate, boolean moveFocusedTask, ActiveGestureLog.CompoundString reason) {
         if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
             return;
         }
@@ -675,14 +690,25 @@
         final boolean recentsAttachedToAppWindow;
         if (mGestureState.getEndTarget() != null) {
             recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow;
+            reason.append("; gesture state end target != null (attached=")
+                    .append(Boolean.toString(recentsAttachedToAppWindow))
+                    .append(")");
         } else if (mContinuingLastGesture
                 && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
             recentsAttachedToAppWindow = true;
+            reason.append("; continuing last gesture (attached=true)");
         } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
             // The window is going away so make sure recents is always visible in this case.
             recentsAttachedToAppWindow = true;
+            reason.append("; make sure recents is always visible (attached=true)");
         } else {
             recentsAttachedToAppWindow = mHasMotionEverBeenPaused || mIsLikelyToStartNewTask;
+            reason.append(mHasMotionEverBeenPaused
+                            ? "; motion has been paused"
+                            : "; gesture is likely to start a new task")
+                    .append(" (attached=")
+                    .append(Boolean.toString(recentsAttachedToAppWindow))
+                    .append(")");
         }
         if (moveFocusedTask && !mAnimationFactory.hasRecentsEverAttachedToAppWindow()
                 && recentsAttachedToAppWindow) {
@@ -690,7 +716,8 @@
             // TaskView jumping to new position as we move the tasks.
             mRecentsView.moveFocusedTaskToFront();
         }
-        mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
+        mAnimationFactory.setRecentsAttachedToAppWindow(
+                recentsAttachedToAppWindow, animate, reason);
 
         // Reapply window transform throughout the attach animation, as the animation affects how
         // much the window is bound by overscroll (vs moving freely).
@@ -710,22 +737,29 @@
     }
 
     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
-        setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */);
+        setIsLikelyToStartNewTask(
+                isLikelyToStartNewTask,
+                true /* animate */,
+                new ActiveGestureLog.CompoundString(
+                        "setting gesture likely to start (animate=true)"));
     }
 
-    private void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask, boolean animate) {
+    private void setIsLikelyToStartNewTask(
+            boolean isLikelyToStartNewTask,
+            boolean animate,
+            ActiveGestureLog.CompoundString reason) {
         if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
             mIsLikelyToStartNewTask = isLikelyToStartNewTask;
-            maybeUpdateRecentsAttachedState(animate);
+            maybeUpdateRecentsAttachedState(animate, reason);
         }
     }
 
-    private void buildAnimationController() {
+    private void buildAnimationController(ActiveGestureLog.CompoundString reason) {
         if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
             return;
         }
         initTransitionEndpoints(mActivity.getDeviceProfile());
-        mAnimationFactory.createActivityInterface(mTransitionDragLength);
+        mAnimationFactory.createActivityInterface(mTransitionDragLength, reason);
     }
 
     /**
@@ -740,7 +774,7 @@
     @Override
     public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
         WindowInsets result = view.onApplyWindowInsets(windowInsets);
-        buildAnimationController();
+        buildAnimationController(new ActiveGestureLog.CompoundString("applying window insets"));
         // Reapply the current shift to ensure it takes new insets into account, e.g. when long
         // pressing to stash taskbar without moving the finger.
         updateFinalShift();
@@ -827,10 +861,6 @@
     public void onRecentsAnimationStart(RecentsAnimationController controller,
             RecentsAnimationTargets targets) {
         super.onRecentsAnimationStart(controller, targets);
-        ActiveGestureLog.INSTANCE.addLog(
-                /* event= */ "startRecentsAnimationCallback",
-                /* extras= */ targets.apps.length,
-                /* gestureEvent= */ START_RECENTS_ANIMATION);
         mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(mContext, targets);
         mRecentsAnimationController = controller;
         mRecentsAnimationTargets = targets;
@@ -881,11 +911,6 @@
         // properly cleaned up the screenshot without accidentally using it.
         mDeferredCleanupRecentsAnimationController = mRecentsAnimationController;
         mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
-
-        if (mRecentsAnimationTargets != null) {
-            setDividerShown(true /* shown */, false /* immediate */);
-        }
-
         // Defer clearing the controller and the targets until after we've updated the state
         mRecentsAnimationController = null;
         mRecentsAnimationTargets = null;
@@ -921,7 +946,10 @@
             });
         }
         notifyGestureStartedAsync();
-        setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
+        setIsLikelyToStartNewTask(
+                isLikelyToStartNewTask,
+                false /* animate */,
+                new ActiveGestureLog.CompoundString("on gesture started (animate=false)"));
         mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
         mGestureStarted = true;
         SystemUiProxy.INSTANCE.get(mContext).notifySwipeUpGestureStarted();
@@ -1003,7 +1031,8 @@
 
     private void onSettledOnEndTarget() {
         // Fast-finish the attaching animation if it's still running.
-        maybeUpdateRecentsAttachedState(false);
+        maybeUpdateRecentsAttachedState(false, new ActiveGestureLog.CompoundString(
+                "on settled on end target (animate=false)"));
         final GestureEndTarget endTarget = mGestureState.getEndTarget();
         // Wait until the given View (if supplied) draws before resuming the last task.
         View postResumeLastTask = mActivityInterface.onSettledOnEndTarget(endTarget);
@@ -1020,8 +1049,8 @@
         switch (endTarget) {
             case HOME:
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
-                // Notify swipe-to-home (recents animation) is finished
-                SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
+                // Notify the SysUI to use fade-in animation when entering PiP
+                SystemUiProxy.INSTANCE.get(mContext).setPipAnimationTypeToAlpha();
                 break;
             case RECENTS:
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
@@ -1309,7 +1338,8 @@
     @UiThread
     private void animateToProgressInternal(float start, float end, long duration,
             Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
-        maybeUpdateRecentsAttachedState();
+        maybeUpdateRecentsAttachedState(new ActiveGestureLog.CompoundString(
+                "animate to progress internal"));
 
         // If we are transitioning to launcher, then listen for the activity to be restarted while
         // the transition is in progress
@@ -1454,6 +1484,7 @@
         }
     }
 
+    @Nullable
     private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
             RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
         // Directly animate the app to PiP (picture-in-picture) mode
@@ -1479,6 +1510,10 @@
                         runningTaskTarget.taskInfo.pictureInPictureParams,
                         homeRotation,
                         hotseatKeepClearArea);
+        if (destinationBounds == null) {
+            // No destination bounds returned from SystemUI, bail early.
+            return null;
+        }
         final Rect appBounds = new Rect();
         final WindowConfiguration winConfig = taskInfo.configuration.windowConfiguration;
         // Adjust the appBounds for TaskBar by using the calculated window crop Rect
@@ -1623,7 +1658,9 @@
                     mRecentsView.post(mRecentsView::resetTaskVisuals);
                 }
                 // Make sure recents is in its final state
-                maybeUpdateRecentsAttachedState(false);
+                maybeUpdateRecentsAttachedState(
+                        false, new ActiveGestureLog.CompoundString(
+                                "setting up window animation (animate=false)"));
                 mActivityInterface.onSwipeUpToHomeComplete(mDeviceState);
             }
         });
@@ -1724,8 +1761,7 @@
     }
 
     private void invalidateHandler() {
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get() || !mActivityInterface.isInLiveTileMode()
-                || mGestureState.getEndTarget() != RECENTS) {
+        if (!mActivityInterface.isInLiveTileMode() || mGestureState.getEndTarget() != RECENTS) {
             mInputConsumerProxy.destroy();
             mTaskAnimationManager.setLiveTileCleanUpHandler(null);
         }
@@ -1770,10 +1806,6 @@
      * continued quick switch gesture, which cancels the previous handler but doesn't invalidate it.
      */
     private void resetLauncherListeners() {
-        // Reset the callback for deferred activity launches
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mActivityInterface.setOnDeferredActivityLaunchCallback(null);
-        }
         mActivity.getRootView().setOnApplyWindowInsetsListener(null);
 
         mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
@@ -1783,10 +1815,6 @@
         boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
         mActivityInterface.onTransitionCancelled(wasVisible, mGestureState.getEndTarget());
 
-        if (mRecentsAnimationTargets != null && wasVisible) {
-            setDividerShown(true /* shown */, true /* immediate */);
-        }
-
         // Leave the pending invisible flag, as it may be used by wallpaper open animation.
         if (mActivity != null) {
             mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
@@ -1799,7 +1827,6 @@
             mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
         } else {
             final int runningTaskId = mGestureState.getRunningTaskId();
-            final boolean refreshView = !ENABLE_QUICKSTEP_LIVE_TILE.get() /* refreshView */;
             boolean finishTransitionPosted = false;
             if (mRecentsAnimationController != null) {
                 // Update the screenshot of the task
@@ -1810,14 +1837,14 @@
                                 mRecentsAnimationController.screenshotTask(runningTaskId);
                         MAIN_EXECUTOR.execute(() -> {
                             mTaskSnapshot = taskSnapshot;
-                            if (!updateThumbnail(runningTaskId, refreshView)) {
+                            if (!updateThumbnail(runningTaskId, false /* refreshView */)) {
                                 setScreenshotCapturedState();
                             }
                         });
                     });
                     return;
                 }
-                finishTransitionPosted = updateThumbnail(runningTaskId, refreshView);
+                finishTransitionPosted = updateThumbnail(runningTaskId, false /* refreshView */);
             }
             if (!finishTransitionPosted) {
                 setScreenshotCapturedState();
@@ -1855,17 +1882,10 @@
     }
 
     private void finishCurrentTransitionToRecents() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
-            if (mRecentsAnimationController != null) {
-                mRecentsAnimationController.detachNavigationBarFromApp(true);
-            }
-        } else if (!hasTargets() || mRecentsAnimationController == null) {
-            // If there are no targets or the animation not started, then there is nothing to finish
-            mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
-        } else {
-            mRecentsAnimationController.finish(true /* toRecents */,
-                    () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
+        // TODO(b/245569277#comment2): enable once isFreeformActive is implemented
+        mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+        if (mRecentsAnimationController != null) {
+            mRecentsAnimationController.detachNavigationBarFromApp(true);
         }
         ActiveGestureLog.INSTANCE.addLog(
                 /* event= */ "finishRecentsAnimation",
@@ -1929,13 +1949,11 @@
         }
         endLauncherTransitionController();
         mRecentsView.onSwipeUpAnimationSuccess();
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mTaskAnimationManager.setLiveTileCleanUpHandler(() -> {
-                mRecentsView.cleanupRemoteTargets();
-                mInputConsumerProxy.destroy();
-            });
-            mTaskAnimationManager.enableLiveTileRestartListener();
-        }
+        mTaskAnimationManager.setLiveTileCleanUpHandler(() -> {
+            mRecentsView.cleanupRemoteTargets();
+            mInputConsumerProxy.destroy();
+        });
+        mTaskAnimationManager.enableLiveTileRestartListener();
 
         SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
         doLogGesture(RECENTS, mRecentsView.getCurrentPageTaskView());
@@ -2070,17 +2088,59 @@
     public void onTasksAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) {
         if (mRecentsAnimationController != null) {
             if (handleTaskAppeared(appearedTaskTargets)) {
-                mRecentsAnimationController.finish(false /* toRecents */,
-                        null /* onFinishComplete */);
-                mActivityInterface.onLaunchTaskSuccess();
-                ActiveGestureLog.INSTANCE.addLog(
-                        /* event= */ "finishRecentsAnimation",
-                        /* extras= */ false,
-                        /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
+                Optional<RemoteAnimationTargetCompat> taskTargetOptional =
+                        Arrays.stream(appearedTaskTargets)
+                                .filter(targetCompat ->
+                                        targetCompat.taskId == mGestureState.getLastStartedTaskId())
+                                .findFirst();
+                if (!taskTargetOptional.isPresent()) {
+                    finishRecentsAnimationOnTasksAppeared();
+                    return;
+                }
+                RemoteAnimationTargetCompat taskTarget = taskTargetOptional.get();
+                TaskView taskView = mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
+                if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) {
+                    finishRecentsAnimationOnTasksAppeared();
+                    return;
+                }
+
+                ViewGroup splashView = mActivity.getDragLayer();
+
+                // When revealing the app with launcher splash screen, make the app visible
+                // and behind the splash view before the splash is animated away.
+                SyncRtSurfaceTransactionApplierCompat surfaceApplier =
+                        new SyncRtSurfaceTransactionApplierCompat(splashView);
+                ArrayList<SurfaceParams> params = new ArrayList<>();
+                for (RemoteAnimationTargetCompat target : appearedTaskTargets) {
+                    SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash);
+                    builder.withAlpha(1);
+                    builder.withLayer(-1);
+                    params.add(builder.build());
+                }
+                surfaceApplier.scheduleApply(params.toArray(new SurfaceParams[0]));
+
+                SplashScreenExitAnimationUtils.startAnimations(splashView, taskTarget.leash,
+                        mSplashMainWindowShiftLength, new TransactionPool(), new Rect(),
+                        SPLASH_ANIMATION_DURATION, SPLASH_FADE_OUT_DURATION,
+                        /* iconStartAlpha= */ 0, /* brandingStartAlpha= */ 0,
+                        SPLASH_APP_REVEAL_DELAY, SPLASH_APP_REVEAL_DURATION,
+                        new AnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                finishRecentsAnimationOnTasksAppeared();
+                            }
+                        });
             }
         }
     }
 
+    private void finishRecentsAnimationOnTasksAppeared() {
+        if (mRecentsAnimationController != null) {
+            mRecentsAnimationController.finish(false /* toRecents */, null /* onFinishComplete */);
+        }
+        ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+    }
+
     /**
      * @return The index of the TaskView in RecentsView whose taskId matches the task that will
      * resume if we finish the controller.
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
index a166553..b06b894 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -135,6 +135,13 @@
     }
 
     /**
+     * Returns whether we are currently not animating, and the animation's value matches the given.
+     */
+    public boolean isSettledOnValue(float endValue) {
+        return !isAnimating() && value == endValue;
+    }
+
+    /**
      * Returns the value we are animating to, or {@code null} if we are not currently animating.
      */
     public Float getEndValue() {
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 315a91e..a343c2d 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -57,6 +57,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.views.ScrimView;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.views.RecentsView;
@@ -183,14 +184,6 @@
 
     public abstract void onLaunchTaskFailed();
 
-    public void onLaunchTaskSuccess() {
-        ACTIVITY_TYPE activity = getCreatedActivity();
-        if (activity == null) {
-            return;
-        }
-        activity.getStateManager().moveToRestState();
-    }
-
     /**
      * Closes any overlays.
      */
@@ -401,14 +394,16 @@
 
     public interface AnimationFactory {
 
-        void createActivityInterface(long transitionLength);
+        void createActivityInterface(long transitionLength, ActiveGestureLog.CompoundString reason);
 
         /**
          * @param attached Whether to show RecentsView alongside the app window. If false, recents
          *                 will be hidden by some property we can animate, e.g. alpha.
          * @param animate Whether to animate recents to/from its new attached state.
+         * @param reason Explanation for why this method is being called with the given param values
          */
-        default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
+        default void setRecentsAttachedToAppWindow(
+                boolean attached, boolean animate, ActiveGestureLog.CompoundString reason) { }
 
         default boolean isRecentsAttachedToAppWindow() {
             return false;
@@ -450,7 +445,8 @@
         }
 
         @Override
-        public void createActivityInterface(long transitionLength) {
+        public void createActivityInterface(
+                long transitionLength, ActiveGestureLog.CompoundString reason) {
             PendingAnimation pa = new PendingAnimation(transitionLength * 2);
             createBackgroundToOverviewAnim(mActivity, pa);
             AnimatorPlaybackController controller = pa.createPlaybackController();
@@ -473,13 +469,29 @@
             // (because we set the animation as the current state animation), so we reapply the
             // attached state here as well to ensure recents is shown/hidden appropriately.
             if (DisplayController.getNavigationMode(mActivity) == NavigationMode.NO_BUTTON) {
-                setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
+                setRecentsAttachedToAppWindow(
+                        mIsAttachedToWindow,
+                        false,
+                        reason.append("; reapplying the attached state (attached=")
+                                .append(Boolean.toString(mIsAttachedToWindow))
+                                .append(", animate=false)"));
             }
         }
 
         @Override
-        public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) {
+        public void setRecentsAttachedToAppWindow(
+                boolean attached, boolean animate, ActiveGestureLog.CompoundString reason) {
+            // TODO(b/244593270): remove these logs; too verbose
+            ActiveGestureLog.INSTANCE.addLog(
+                    new ActiveGestureLog.CompoundString("setRecentsAttachedToAppWindow: attached=")
+                            .append(Boolean.toString(attached))
+                            .append(", animate=")
+                            .append(Boolean.toString(animate))
+                            .append(", reason=")
+                            .append(reason));
             if (mIsAttachedToWindow == attached && animate) {
+                ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                        "setRecentsAttachedToAppWindow: exiting early"));
                 return;
             }
             mIsAttachedToWindow = attached;
@@ -496,9 +508,21 @@
                     .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
             if (!recentsView.isShown() && animate) {
                 ADJACENT_PAGE_HORIZONTAL_OFFSET.set(recentsView, fromTranslation);
+                ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                        "setRecentsAttachedToAppWindow: recents view not shown, setting ")
+                        .append("ADJACENT_PAGE_HORIZONTAL_OFFSET to ")
+                        .append(Float.toString(fromTranslation)));
             } else {
                 fromTranslation = ADJACENT_PAGE_HORIZONTAL_OFFSET.get(recentsView);
+                ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                        "setRecentsAttachedToAppWindow: updating fromTranslation to ")
+                        .append(Float.toString(fromTranslation)));
             }
+            ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                    "setRecentsAttachedToAppWindow: fromTranslation=")
+                    .append(Float.toString(fromTranslation))
+                    .append(", toTranslation=")
+                    .append(Float.toString(toTranslation)));
             if (!animate) {
                 ADJACENT_PAGE_HORIZONTAL_OFFSET.set(recentsView, toTranslation);
             } else {
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 466abbe..6e963f3 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
 import static com.android.quickstep.fallback.RecentsState.BACKGROUND_APP;
 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
@@ -120,8 +119,7 @@
     public RecentsView getVisibleRecentsView() {
         RecentsActivity activity = getCreatedActivity();
         if (activity != null) {
-            if (activity.hasBeenResumed()
-                    || (ENABLE_QUICKSTEP_LIVE_TILE.get() && isInLiveTileMode())) {
+            if (activity.hasBeenResumed() || isInLiveTileMode()) {
                 return activity.getOverviewPanel();
             }
         }
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 38bf1fd..bcd9687 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -20,6 +20,9 @@
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_HOME;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_LAST_TASK;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK;
 
 import android.annotation.Nullable;
 import android.annotation.TargetApi;
@@ -330,6 +333,20 @@
         ActiveGestureLog.INSTANCE.addLog(
                 /* event= */ "setEndTarget " + mEndTarget,
                 /* gestureEvent= */ SET_END_TARGET);
+        switch (mEndTarget) {
+            case HOME:
+                ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_HOME);
+                break;
+            case NEW_TASK:
+                ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_NEW_TASK);
+                break;
+            case LAST_TASK:
+                ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_LAST_TASK);
+                break;
+            case RECENTS:
+            default:
+                // No-Op
+        }
         if (isAtomic) {
             mStateCallback.setState(STATE_END_TARGET_ANIMATION_FINISHED);
         }
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 1127e2c..1cb17cb 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.LauncherState.QUICK_SWITCH;
 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.animation.Animator;
@@ -203,8 +202,7 @@
     private Launcher getVisibleLauncher() {
         Launcher launcher = getCreatedActivity();
         return (launcher != null) && launcher.isStarted()
-                && ((ENABLE_QUICKSTEP_LIVE_TILE.get() && isInLiveTileMode())
-                || launcher.hasBeenResumed()) ? launcher : null;
+                && (isInLiveTileMode() || launcher.hasBeenResumed()) ? launcher : null;
     }
 
     @Override
@@ -213,7 +211,7 @@
         if (launcher == null) {
             return false;
         }
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isInLiveTileMode()) {
+        if (isInLiveTileMode()) {
             RecentsView recentsView = getVisibleRecentsView();
             if (recentsView == null) {
                 return false;
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 36ca993..d1533f0 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -84,7 +84,8 @@
 
         final View workspaceView = findWorkspaceView(launchCookies,
                 mRecentsView.getRunningTaskView());
-        boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow();
+        boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow()
+                && workspaceView.getHeight() > 0;
 
         mActivity.getRootView().setForceHideBackArrow(true);
         if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 1e7e89e..875b72c 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -144,7 +144,7 @@
         RunnableList callbackList = null;
         if (taskView != null) {
             taskView.setEndQuickswitchCuj(true);
-            callbackList = taskView.launchTaskAnimated();
+            callbackList = taskView.launchTasks();
         }
 
         if (callbackList != null) {
@@ -193,7 +193,20 @@
             }
         }
 
-        if (activityInterface.switchToRecentsIfVisible(() -> scheduleNextTask(cmd))) {
+        final Runnable completeCallback = () -> {
+            if (cmd.type == TYPE_SHOW_NEXT_FOCUS) {
+                RecentsView rv = activityInterface.getVisibleRecentsView();
+                // When the overview is launched via alt tab (cmd type is TYPE_SHOW_NEXT_FOCUS),
+                // the touch mode somehow is not change to false by the Android framework.
+                // The subsequent tab to go through tasks in overview can only be dispatched to
+                // focuses views, while focus can only be requested in
+                // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
+                // here we launch overview from home.
+                rv.getViewRootImpl().touchModeChanged(false);
+            }
+            scheduleNextTask(cmd);
+        };
+        if (activityInterface.switchToRecentsIfVisible(completeCallback)) {
             // If successfully switched, wait until animation finishes
             return false;
         }
@@ -227,8 +240,11 @@
                 interactionHandler.onGestureCancelled();
                 cmd.removeListener(this);
 
-                RecentsView createdRecents =
-                        activityInterface.getCreatedActivity().getOverviewPanel();
+                T createdActivity = activityInterface.getCreatedActivity();
+                if (createdActivity == null) {
+                    return;
+                }
+                RecentsView createdRecents = createdActivity.getOverviewPanel();
                 if (createdRecents != null) {
                     createdRecents.onRecentsAnimationComplete();
                 }
@@ -268,6 +284,13 @@
             RecentsView rv =
                     mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
             if (rv != null) {
+                // When the overview is launched via alt tab (cmd type is TYPE_SHOW_NEXT_FOCUS),
+                // the touch mode somehow is not change to false by the Android framework.
+                // The subsequent tab to go through tasks in overview can only be dispatched to
+                // focuses views, while focus can only be requested in
+                // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
+                // here we launch overview with live tile.
+                rv.getViewRootImpl().touchModeChanged(false);
                 // Ensure that recents view has focus so that it receives the followup key inputs
                 TaskView taskView = rv.getNextTaskView();
                 if (taskView == null) {
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 9bd0cb9..b7cdecd 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -1,16 +1,25 @@
 package com.android.quickstep;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.app.Activity;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.os.Bundle;
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.R;
 import com.android.launcher3.testing.TestInformationHandler;
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.TISBindHelper;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
 
 public class QuickstepTestInformationHandler extends TestInformationHandler {
 
@@ -72,6 +81,37 @@
                         TestProtocol.REQUEST_HAS_TIS, true);
                 return response;
             }
+
+            case TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING:
+                runOnTISBinder(tisBinder -> {
+                    enableManualTaskbarStashing(tisBinder, true);
+                });
+                return response;
+
+            case TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING:
+                runOnTISBinder(tisBinder -> {
+                    enableManualTaskbarStashing(tisBinder, false);
+                });
+                return response;
+
+            case TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED:
+                runOnTISBinder(tisBinder -> {
+                    enableManualTaskbarStashing(tisBinder, true);
+
+                    // Allow null-pointer to catch illegal states.
+                    tisBinder.getTaskbarManager().getCurrentActivityContext()
+                            .unstashTaskbarIfStashed();
+
+                    enableManualTaskbarStashing(tisBinder, false);
+                });
+                return response;
+
+            case TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT: {
+                final Resources resources = mContext.getResources();
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size));
+                return response;
+            }
         }
 
         return super.call(method, arg, extras);
@@ -93,4 +133,30 @@
     protected boolean isLauncherInitialized() {
         return super.isLauncherInitialized() && TouchInteractionService.isInitialized();
     }
+
+    private void enableManualTaskbarStashing(
+            TouchInteractionService.TISBinder tisBinder, boolean enable) {
+        // Allow null-pointer to catch illegal states.
+        tisBinder.getTaskbarManager().getCurrentActivityContext().enableManualStashingDuringTests(
+                enable);
+    }
+
+    /**
+     * Runs the given command on the UI thread, after ensuring we are connected to
+     * TouchInteractionService.
+     */
+    protected void runOnTISBinder(Consumer<TouchInteractionService.TISBinder> connectionCallback) {
+        try {
+            CountDownLatch countDownLatch = new CountDownLatch(1);
+            TISBindHelper helper = MAIN_EXECUTOR.submit(() ->
+                    new TISBindHelper(mContext, tisBinder -> {
+                        connectionCallback.accept(tisBinder);
+                        countDownLatch.countDown();
+                    })).get();
+            countDownLatch.await();
+            MAIN_EXECUTOR.execute(helper::onDestroy);
+        } catch (ExecutionException | InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 5a9862c..7bcc661 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -17,9 +17,12 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
+import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
+import android.app.KeyguardManager;
 import android.os.Build;
 import android.os.Process;
 import android.os.RemoteException;
@@ -29,9 +32,9 @@
 
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.KeyguardManagerCompat;
 import com.android.wm.shell.recents.IRecentTasksListener;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
 import com.android.wm.shell.util.SplitBounds;
@@ -49,7 +52,7 @@
 
     private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0);
 
-    private final KeyguardManagerCompat mKeyguardManager;
+    private final KeyguardManager mKeyguardManager;
     private final LooperExecutor mMainThreadExecutor;
     private final SystemUiProxy mSysUiProxy;
 
@@ -66,8 +69,8 @@
     // Tasks are stored in order of least recently launched to most recently launched.
     private ArrayList<ActivityManager.RunningTaskInfo> mRunningTasks;
 
-    public RecentTasksList(LooperExecutor mainThreadExecutor,
-            KeyguardManagerCompat keyguardManager, SystemUiProxy sysUiProxy) {
+    public RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManager keyguardManager,
+            SystemUiProxy sysUiProxy) {
         mMainThreadExecutor = mainThreadExecutor;
         mKeyguardManager = keyguardManager;
         mChangeId = 1;
@@ -253,9 +256,11 @@
         };
 
         TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
+
         for (GroupedRecentTaskInfo rawTask : rawTasks) {
-            if (rawTask.getType() == GroupedRecentTaskInfo.TYPE_FREEFORM) {
-                // TODO: add entry for freeform tasks
+            if (DESKTOP_MODE_SUPPORTED && rawTask.getType() == TYPE_FREEFORM) {
+                GroupTask desktopTask = createDesktopTask(rawTask);
+                allTasks.add(desktopTask);
                 continue;
             }
             ActivityManager.RecentTaskInfo taskInfo1 = rawTask.getTaskInfo1();
@@ -283,6 +288,20 @@
         return allTasks;
     }
 
+    private DesktopTask createDesktopTask(GroupedRecentTaskInfo recentTaskInfo) {
+        ArrayList<Task> tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size());
+        for (ActivityManager.RecentTaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
+            Task.TaskKey key = new Task.TaskKey(taskInfo);
+            Task task = Task.from(key, taskInfo, false);
+            task.setLastSnapshotData(taskInfo);
+            task.positionInParent = taskInfo.positionInParent;
+            task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
+            // TODO(b/244348395): tasks should be sorted from oldest to most recently used
+            tasks.add(task);
+        }
+        return new DesktopTask(tasks);
+    }
+
     private SplitConfigurationOptions.SplitBounds convertSplitBounds(
             SplitBounds shellSplitBounds) {
         return shellSplitBounds == null ?
@@ -295,7 +314,7 @@
     private ArrayList<GroupTask> copyOf(ArrayList<GroupTask> tasks) {
         ArrayList<GroupTask> newTasks = new ArrayList<>();
         for (int i = 0; i < tasks.size(); i++) {
-            newTasks.add(new GroupTask(tasks.get(i)));
+            newTasks.add(tasks.get(i).copy());
         }
         return newTasks;
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 3e3a431..4f5e216 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -18,7 +18,6 @@
 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
 import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
@@ -30,6 +29,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
+import android.app.ActivityOptions;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Bundle;
@@ -77,7 +77,6 @@
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -131,7 +130,7 @@
 
         SplitSelectStateController controller =
                 new SplitSelectStateController(this, mHandler, getStateManager(),
-                        null /* depthController */);
+                        /* depthController */ null, getStatsLogManager());
         mDragLayer.recreateControllers();
         mFallbackRecentsView.init(mActionsView, controller);
 
@@ -263,8 +262,10 @@
                 wrapper, RECENTS_LAUNCH_DURATION,
                 RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION
                         - STATUS_BAR_TRANSITION_PRE_DELAY, getIApplicationThread());
-        final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper(
-                ActivityOptionsCompat.makeRemoteAnimation(adapterCompat),
+        final ActivityOptions options = ActivityOptions.makeRemoteAnimation(
+                adapterCompat.getWrapped(),
+                adapterCompat.getRemoteTransition().getTransition());
+        final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper(options,
                 onEndCallback);
         activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
         activityOptions.options.setLaunchDisplayId(
@@ -391,13 +392,9 @@
     }
 
     public void startHome() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            RecentsView recentsView = getOverviewPanel();
-            recentsView.switchToScreenshot(() -> recentsView.finishRecentsAnimation(true,
-                    this::startHomeInternal));
-        } else {
-            startHomeInternal();
-        }
+        RecentsView recentsView = getOverviewPanel();
+        recentsView.switchToScreenshot(() -> recentsView.finishRecentsAnimation(true,
+                this::startHomeInternal));
     }
 
     private void startHomeInternal() {
@@ -406,8 +403,10 @@
         RemoteAnimationAdapterCompat adapterCompat =
                 new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0,
                         getIApplicationThread());
-        startHomeIntentSafely(this,
-                ActivityOptionsCompat.makeRemoteAnimation(adapterCompat).toBundle());
+        ActivityOptions options = ActivityOptions.makeRemoteAnimation(
+                adapterCompat.getWrapped(),
+                adapterCompat.getRemoteTransition().getTransition());
+        startHomeIntentSafely(this, options.toBundle());
     }
 
     private final RemoteAnimationFactory mAnimationToHomeFactory =
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 887fd54..b233521 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
 
 import android.graphics.Rect;
 import android.util.ArraySet;
@@ -28,6 +29,7 @@
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -113,6 +115,10 @@
                     homeContentInsets, minimizedHomeBounds);
 
             Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+                ActiveGestureLog.INSTANCE.addLog(
+                        /* event= */ "RecentsAnimationCallbacks.onAnimationStart",
+                        /* extras= */ targets.apps.length,
+                        /* gestureEvent= */ START_RECENTS_ANIMATION);
                 for (RecentsAnimationListener listener : getListeners()) {
                     listener.onRecentsAnimationStart(mController, targets);
                 }
@@ -137,7 +143,8 @@
     @Override
     public void onTasksAppeared(RemoteAnimationTargetCompat[] apps) {
         Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
-            ActiveGestureLog.INSTANCE.addLog("onTasksAppeared");
+            ActiveGestureLog.INSTANCE.addLog("onTasksAppeared",
+                    ActiveGestureErrorDetector.GestureEvent.TASK_APPEARED);
             for (RecentsAnimationListener listener : getListeners()) {
                 listener.onTasksAppeared(apps);
             }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 3074dbb..3053474 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -22,6 +22,7 @@
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
+import android.app.KeyguardManager;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.Intent;
@@ -40,7 +41,6 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.KeyguardManagerCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
@@ -75,7 +75,8 @@
     private RecentsModel(Context context) {
         mContext = context;
         mTaskList = new RecentTasksList(MAIN_EXECUTOR,
-                new KeyguardManagerCompat(context), SystemUiProxy.INSTANCE.get(context));
+                context.getSystemService(KeyguardManager.class),
+                SystemUiProxy.INSTANCE.get(context));
 
         IconProvider iconProvider = new IconProvider(context);
         mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index b57f2ce..129b88e 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -18,6 +18,7 @@
 import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import android.app.ActivityManager;
 import android.app.PendingIntent;
@@ -31,8 +32,10 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
@@ -42,6 +45,10 @@
 import android.view.SurfaceControl;
 import android.window.IOnBackInvokedCallback;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import com.android.internal.logging.InstanceId;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -51,6 +58,8 @@
 import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
 import com.android.systemui.shared.system.smartspace.SmartspaceState;
 import com.android.wm.shell.back.IBackAnimation;
+import com.android.wm.shell.desktopmode.IDesktopMode;
+import com.android.wm.shell.floating.IFloatingTasks;
 import com.android.wm.shell.onehanded.IOneHanded;
 import com.android.wm.shell.pip.IPip;
 import com.android.wm.shell.pip.IPipAnimationListener;
@@ -75,15 +84,19 @@
     public static final MainThreadInitializedObject<SystemUiProxy> INSTANCE =
             new MainThreadInitializedObject<>(SystemUiProxy::new);
 
+    private static final int MSG_SET_SHELF_HEIGHT = 1;
+
     private ISystemUiProxy mSystemUiProxy;
     private IPip mPip;
     private ISysuiUnlockAnimationController mSysuiUnlockAnimationController;
     private ISplitScreen mSplitScreen;
+    private IFloatingTasks mFloatingTasks;
     private IOneHanded mOneHanded;
     private IShellTransitions mShellTransitions;
     private IStartingWindow mStartingWindow;
     private IRecentTasks mRecentTasks;
     private IBackAnimation mBackAnimation;
+    private IDesktopMode mDesktopMode;
     private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
         MAIN_EXECUTOR.execute(() -> clearProxy());
     };
@@ -103,13 +116,16 @@
     // Used to dedupe calls to SystemUI
     private int mLastShelfHeight;
     private boolean mLastShelfVisible;
-    private Context mContext;
+
+    private final Context mContext;
+    private final Handler mAsyncHandler;
 
     // TODO(141886704): Find a way to remove this
     private int mLastSystemUiStateFlags;
 
     public SystemUiProxy(Context context) {
         mContext = context;
+        mAsyncHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessageAsync);
     }
 
     @Override
@@ -152,24 +168,26 @@
     }
 
     public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen,
-            IOneHanded oneHanded, IShellTransitions shellTransitions,
+            IFloatingTasks floatingTasks, IOneHanded oneHanded, IShellTransitions shellTransitions,
             IStartingWindow startingWindow, IRecentTasks recentTasks,
             ISysuiUnlockAnimationController sysuiUnlockAnimationController,
-            IBackAnimation backAnimation) {
+            IBackAnimation backAnimation, IDesktopMode desktopMode) {
         unlinkToDeath();
         mSystemUiProxy = proxy;
         mPip = pip;
         mSplitScreen = splitScreen;
+        mFloatingTasks = floatingTasks;
         mOneHanded = oneHanded;
         mShellTransitions = shellTransitions;
         mStartingWindow = startingWindow;
         mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
         mRecentTasks = recentTasks;
         mBackAnimation = backAnimation;
+        mDesktopMode = desktopMode;
         linkToDeath();
         // re-attach the listeners once missing due to setProxy has not been initialized yet.
         if (mPipAnimationListener != null && mPip != null) {
-            setPinnedStackAnimationListener(mPipAnimationListener);
+            setPipAnimationListener(mPipAnimationListener);
         }
         if (mSplitScreenListener != null && mSplitScreen != null) {
             registerSplitScreenListener(mSplitScreenListener);
@@ -192,7 +210,7 @@
     }
 
     public void clearProxy() {
-        setProxy(null, null, null, null, null, null, null, null, null);
+        setProxy(null, null, null, null, null, null, null, null, null, null, null);
     }
 
     // TODO(141886704): Find a way to remove this
@@ -339,20 +357,6 @@
         }
     }
 
-    /**
-     * Notifies that swipe-to-home action is finished.
-     */
-    @Override
-    public void notifySwipeToHomeFinished() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.notifySwipeToHomeFinished();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call notifySwipeToHomeFinished", e);
-            }
-        }
-    }
-
     @Override
     public void notifyPrioritizedRotation(int rotation) {
         if (mSystemUiProxy != null) {
@@ -435,12 +439,20 @@
      * Sets the shelf height.
      */
     public void setShelfHeight(boolean visible, int shelfHeight) {
+        Message.obtain(mAsyncHandler, MSG_SET_SHELF_HEIGHT,
+                visible ? 1 : 0 , shelfHeight).sendToTarget();
+    }
+
+    @WorkerThread
+    private void setShelfHeightAsync(int visibleInt, int shelfHeight) {
+        boolean visible = visibleInt != 0;
         boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight;
-        if (mPip != null && changed) {
+        IPip pip = mPip;
+        if (pip != null && changed) {
             mLastShelfVisible = visible;
             mLastShelfHeight = shelfHeight;
             try {
-                mPip.setShelfHeight(visible, shelfHeight);
+                pip.setShelfHeight(visible, shelfHeight);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call setShelfHeight visible: " + visible
                         + " height: " + shelfHeight, e);
@@ -449,12 +461,12 @@
     }
 
     /**
-     * Sets listener to get pinned stack animation callbacks.
+     * Sets listener to get pip animation callbacks.
      */
-    public void setPinnedStackAnimationListener(IPipAnimationListener listener) {
+    public void setPipAnimationListener(IPipAnimationListener listener) {
         if (mPip != null) {
             try {
-                mPip.setPinnedStackAnimationListener(listener);
+                mPip.setPipAnimationListener(listener);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
             }
@@ -462,6 +474,10 @@
         mPipAnimationListener = listener;
     }
 
+    /**
+     * @return Destination bounds of auto-pip animation, {@code null} if the animation is not ready.
+     */
+    @Nullable
     public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
             PictureInPictureParams pictureInPictureParams, int launcherRotation,
             Rect hotseatKeepClearArea) {
@@ -492,6 +508,19 @@
         }
     }
 
+    /**
+     * Sets the next pip animation type to be the alpha animation.
+     */
+    public void setPipAnimationTypeToAlpha() {
+        if (mPip != null) {
+            try {
+                mPip.setPipAnimationTypeToAlpha();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call setPipAnimationTypeToAlpha", e);
+            }
+        }
+    }
+
     //
     // Splitscreen
     //
@@ -519,15 +548,43 @@
     }
 
     /** Start multiple tasks in split-screen simultaneously. */
-    public void startTasks(int mainTaskId, Bundle mainOptions, int sideTaskId, Bundle sideOptions,
-            @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
-            RemoteTransitionCompat remoteTransition) {
+    public void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2,
+            @SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
+            RemoteTransitionCompat remoteTransition, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
-                mSplitScreen.startTasks(mainTaskId, mainOptions, sideTaskId, sideOptions,
-                        sidePosition, splitRatio, remoteTransition.getTransition());
+                mSplitScreen.startTasks(taskId1, options1, taskId2, options2, splitPosition,
+                        splitRatio, remoteTransition.getTransition(), instanceId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startTask");
+                Log.w(TAG, "Failed call startTasks");
+            }
+        }
+    }
+
+    public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
+            Bundle options1, int taskId, Bundle options2,
+            @SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
+            RemoteTransitionCompat remoteTransition, InstanceId instanceId) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSplitScreen.startIntentAndTask(pendingIntent, fillInIntent, options1,
+                        taskId, options2, splitPosition, splitRatio,
+                        remoteTransition.getTransition(), instanceId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call startIntentAndTask");
+            }
+        }
+    }
+
+    public void startShortcutAndTask(ShortcutInfo shortcutInfo, Bundle options1, int taskId,
+            Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
+            float splitRatio, RemoteTransitionCompat remoteTransition, InstanceId instanceId) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSplitScreen.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
+                        splitPosition, splitRatio, remoteTransition.getTransition(), instanceId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call startShortcutAndTask");
             }
         }
     }
@@ -535,13 +592,13 @@
     /**
      * Start multiple tasks in split-screen simultaneously.
      */
-    public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId,
-            Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition,
-            float splitRatio, RemoteAnimationAdapter adapter) {
+    public void startTasksWithLegacyTransition(int taskId1, Bundle options1, int taskId2,
+            Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
+            float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
-                mSplitScreen.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId,
-                        sideOptions, sidePosition, splitRatio, adapter);
+                mSplitScreen.startTasksWithLegacyTransition(taskId1, options1, taskId2, options2,
+                        splitPosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startTasksWithLegacyTransition");
             }
@@ -549,27 +606,26 @@
     }
 
     public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
-            Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
-            @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
-            RemoteAnimationAdapter adapter) {
+            Intent fillInIntent, Bundle options1, int taskId, Bundle options2,
+            @SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
+            RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
-                        taskId, mainOptions, sideOptions, sidePosition, splitRatio, adapter);
+                        options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startIntentAndTaskWithLegacyTransition");
             }
         }
     }
 
-    public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, int taskId,
-            Bundle mainOptions, Bundle sideOptions,
-            @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
-            RemoteAnimationAdapter adapter) {
+    public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, Bundle options1,
+            int taskId, Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
+            float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
-                mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, taskId,
-                        mainOptions, sideOptions, sidePosition, splitRatio, adapter);
+                mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, options1,
+                        taskId, options2, splitPosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startShortcutAndTaskWithLegacyTransition");
             }
@@ -577,11 +633,11 @@
     }
 
     public void startShortcut(String packageName, String shortcutId, int position,
-            Bundle options, UserHandle user) {
+            Bundle options, UserHandle user, InstanceId instanceId) {
         if (mSplitScreen != null) {
             try {
                 mSplitScreen.startShortcut(packageName, shortcutId, position, options,
-                        user);
+                        user, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startShortcut");
             }
@@ -589,10 +645,10 @@
     }
 
     public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
-            Bundle options) {
+            Bundle options, InstanceId instanceId) {
         if (mSplitScreen != null) {
             try {
-                mSplitScreen.startIntent(intent, fillInIntent, position, options);
+                mSplitScreen.startIntent(intent, fillInIntent, position, options, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startIntent");
             }
@@ -638,6 +694,20 @@
     }
 
     //
+    // Floating tasks
+    //
+
+    public void showFloatingTask(Intent intent) {
+        if (mFloatingTasks != null) {
+            try {
+                mFloatingTasks.showTask(intent);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Launcher: Failed call showFloatingTask", e);
+            }
+        }
+    }
+
+    //
     // One handed
     //
 
@@ -856,4 +926,29 @@
         }
         return new ArrayList<>();
     }
+
+    private boolean handleMessageAsync(Message msg) {
+        switch (msg.what) {
+            case MSG_SET_SHELF_HEIGHT:
+                setShelfHeightAsync(msg.arg1, msg.arg2);
+                return true;
+        }
+
+        return false;
+    }
+
+    //
+    // Desktop Mode
+    //
+
+    /** Call shell to show all apps active on the desktop */
+    public void showDesktopApps() {
+        if (mDesktopMode != null) {
+            try {
+                mDesktopMode.showDesktopApps();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call showDesktopApps", e);
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 5fb806d..7f16565 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
@@ -36,12 +35,9 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
-import com.android.quickstep.util.ActiveGestureErrorDetector;
-import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.RemoteTransitionCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -75,7 +71,7 @@
                 return;
             }
             BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode()
+            if (activityInterface.isInLiveTileMode()
                     && activityInterface.getCreatedActivity() != null) {
                 RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
                 if (recentsView != null) {
@@ -138,8 +134,6 @@
                     // handling this call entirely
                     return;
                 }
-                ActiveGestureLog.INSTANCE.addLog("TaskAnimationManager.startRecentsAnimation",
-                        ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION);
                 mController = controller;
                 mTargets = targets;
                 mLastAppearedTaskTarget = mTargets.findTask(mLastGestureState.getRunningTaskId());
@@ -176,7 +170,7 @@
                                 .map(RemoteAnimationTargetCompat::unwrap)
                                 .toArray(RemoteAnimationTarget[]::new));
 
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode()
+                if (activityInterface.isInLiveTileMode()
                         && activityInterface.getCreatedActivity() != null) {
                     RecentsView recentsView =
                             activityInterface.getCreatedActivity().getOverviewPanel();
@@ -209,7 +203,7 @@
 
             @Override
             public boolean onSwitchToScreenshot(Runnable onFinished) {
-                if (!ENABLE_QUICKSTEP_LIVE_TILE.get() || !activityInterface.isInLiveTileMode()
+                if (!activityInterface.isInLiveTileMode()
                         || activityInterface.getCreatedActivity() == null) {
                     // No need to switch since tile is already a screenshot.
                     onFinished.run();
@@ -233,7 +227,8 @@
             RemoteTransitionCompat transition = new RemoteTransitionCompat(mCallbacks,
                     mController != null ? mController.getController() : null,
                     mCtx.getIApplicationThread());
-            final ActivityOptions options = ActivityOptionsCompat.makeRemoteTransition(transition);
+            final ActivityOptions options = ActivityOptions.makeRemoteTransition(
+                    transition.getTransition());
             // Allowing to pause Home if Home is top activity and Recents is not Home. So when user
             // start home when recents animation is playing, the home activity can be resumed again
             // to let the transition controller collect Home activity.
@@ -270,7 +265,7 @@
             return;
         }
         BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode()
+        if (activityInterface.isInLiveTileMode()
                 && activityInterface.getCreatedActivity() != null) {
             RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
             if (recentsView != null) {
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index e10cab6..d40f2ae 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -18,7 +18,6 @@
 
 import static android.view.Surface.ROTATION_0;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL;
 import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
 
@@ -174,14 +173,10 @@
          * @param callback callback to run, after switching to screenshot
          */
         public void endLiveTileMode(@NonNull Runnable callback) {
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                RecentsView recentsView = mThumbnailView.getTaskView().getRecentsView();
-                recentsView.switchToScreenshot(
-                        () -> recentsView.finishRecentsAnimation(true /* toRecents */,
-                                false /* shouldPip */, callback));
-            } else {
-                callback.run();
-            }
+            RecentsView recentsView = mThumbnailView.getTaskView().getRecentsView();
+            recentsView.switchToScreenshot(
+                    () -> recentsView.finishRecentsAnimation(true /* toRecents */,
+                            false /* shouldPip */, callback));
         }
 
         /**
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index a0860ee..eae79df 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -16,6 +16,8 @@
 
 package com.android.quickstep;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
 
@@ -26,9 +28,12 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.util.Log;
 import android.view.View;
 import android.view.WindowInsets;
+import android.view.WindowManagerGlobal;
 import android.window.SplashScreen;
 
 import androidx.annotation.Nullable;
@@ -52,8 +57,6 @@
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.shared.recents.view.RecentsTransition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
-import com.android.systemui.shared.system.WindowManagerWrapper;
 
 import java.util.Collections;
 import java.util.List;
@@ -121,6 +124,7 @@
     }
 
     class FreeformSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
+        private static final String TAG = "FreeformSystemShortcut";
 
         private Handler mHandler;
 
@@ -193,7 +197,7 @@
                                 taskId, thumbnail, taskBounds));
                     }
                 };
-                WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
+                overridePendingAppTransitionMultiThumbFuture(
                         future, animStartedListener, mHandler, true /* scaleUp */,
                         taskKey.displayId);
                 mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
@@ -201,8 +205,27 @@
             }
         }
 
+        /**
+         * Overrides a pending app transition.
+         */
+        private void overridePendingAppTransitionMultiThumbFuture(
+                AppTransitionAnimationSpecsFuture animationSpecFuture, Runnable animStartedCallback,
+                Handler animStartedCallbackHandler, boolean scaleUp, int displayId) {
+            try {
+                WindowManagerGlobal.getWindowManagerService()
+                        .overridePendingAppTransitionMultiThumbFuture(
+                                animationSpecFuture.getFuture(),
+                                RecentsTransition.wrapStartedListener(animStartedCallbackHandler,
+                                        animStartedCallback), scaleUp, displayId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to override pending app transition (multi-thumbnail future): ",
+                        e);
+            }
+        }
+
         private ActivityOptions makeLaunchOptions(Activity activity) {
-            ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions();
+            ActivityOptions activityOptions = ActivityOptions.makeBasic();
+            activityOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
             // Arbitrary bounds only because freeform is in dev mode right now
             final View decorView = activity.getWindow().getDecorView();
             final WindowInsets insets = decorView.getRootWindowInsets();
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 556b99e..df80e2f 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep;
 
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -35,8 +34,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.statehandlers.DepthController.DEPTH;
+import static com.android.launcher3.statehandlers.DepthController.STATE_DEPTH;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
@@ -304,9 +302,15 @@
             // to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
             //    Mt K(0)` K(t) Mt`
             TaskThumbnailView[] thumbnails = v.getThumbnails();
-            Matrix[] mt = new Matrix[simulatorCopies.length];
-            Matrix[] mti = new Matrix[simulatorCopies.length];
-            for (int i = 0; i < thumbnails.length; i++) {
+
+            // In case simulator copies and thumbnail size do no match, ensure we get the lesser.
+            // This ensures we do not create arrays with empty elements or attempt to references
+            // indexes out of array bounds.
+            final int matrixSize = Math.min(simulatorCopies.length, thumbnails.length);
+
+            Matrix[] mt = new Matrix[matrixSize];
+            Matrix[] mti = new Matrix[matrixSize];
+            for (int i = 0; i < matrixSize; i++) {
                 TaskThumbnailView ttv = thumbnails[i];
                 RectF localBounds = new RectF(0, 0,  ttv.getWidth(), ttv.getHeight());
                 float[] tvBoundsMapped = new float[]{0, 0,  ttv.getWidth(), ttv.getHeight()};
@@ -323,14 +327,14 @@
                 mti[i] = localMti;
             }
 
-            Matrix[] k0i = new Matrix[simulatorCopies.length];
-            for (int i = 0; i < simulatorCopies.length; i++) {
+            Matrix[] k0i = new Matrix[matrixSize];
+            for (int i = 0; i < matrixSize; i++) {
                 k0i[i] = new Matrix();
                 simulatorCopies[i].getTaskViewSimulator().getCurrentMatrix().invert(k0i[i]);
             }
             Matrix animationMatrix = new Matrix();
             out.addOnFrameCallback(() -> {
-                for (int i = 0; i < simulatorCopies.length; i++) {
+                for (int i = 0; i < matrixSize; i++) {
                     animationMatrix.set(mt[i]);
                     animationMatrix.postConcat(k0i[i]);
                     animationMatrix.postConcat(simulatorCopies[i]
@@ -367,7 +371,7 @@
         });
 
         if (depthController != null) {
-            out.setFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(baseActivity),
+            out.setFloat(depthController, STATE_DEPTH, BACKGROUND_APP.getDepth(baseActivity),
                     TOUCH_RESPONSE_INTERPOLATOR);
         }
     }
@@ -426,8 +430,10 @@
         TransitionInfo.Change splitRoot2 = null;
         for (int i = 0; i < transitionInfo.getChanges().size(); ++i) {
             final TransitionInfo.Change change = transitionInfo.getChanges().get(i);
-            final int taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1;
+            if (change.getTaskInfo() == null) continue;
+            final int taskId = change.getTaskInfo().taskId;
             final int mode = change.getMode();
+
             // Find the target tasks' root tasks since those are the split stages that need to
             // be animated (the tasks themselves are children and thus inherit animation).
             if (taskId == initialTaskId || taskId == secondTaskId) {
@@ -440,7 +446,7 @@
                             + "root of " + taskId + " is already visible or has broken hierarchy.");
                 }
             }
-            if (taskId == initialTaskId && initialTaskId != INVALID_TASK_ID) {
+            if (taskId == initialTaskId) {
                 splitRoot1 = transitionInfo.getChange(change.getParent());
             }
             if (taskId == secondTaskId) {
@@ -637,7 +643,7 @@
             };
         }
         pa.add(launcherAnim);
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1) {
+        if (recentsView.getRunningTaskIndex() != -1) {
             pa.addOnFrameCallback(recentsView::redrawLiveTile);
         }
         anim.play(pa.buildAnim());
@@ -668,7 +674,7 @@
         for (int i = 0; i < nonApps.length; ++i) {
             final RemoteAnimationTargetCompat targ = nonApps[i];
             final SurfaceControl leash = targ.leash;
-            if (targ.windowType == TYPE_DOCK_DIVIDER && leash != null) {
+            if (targ.windowType == TYPE_DOCK_DIVIDER && leash != null && leash.isValid()) {
                 auxiliarySurfaces.add(leash);
                 hasSurfaceToAnimate = true;
             }
@@ -681,7 +687,9 @@
         dockFadeAnimator.addUpdateListener(valueAnimator -> {
             float progress = valueAnimator.getAnimatedFraction();
             for (SurfaceControl leash : auxiliarySurfaces) {
-                t.setAlpha(leash, shown ? progress : 1 - progress);
+                if (leash != null && leash.isValid()) {
+                    t.setAlpha(leash, shown ? progress : 1 - progress);
+                }
             }
             t.apply();
         });
@@ -702,7 +710,9 @@
             public void onAnimationEnd(Animator animation) {
                 if (!shown) {
                     for (SurfaceControl leash : auxiliarySurfaces) {
-                        t.hide(leash);
+                        if (leash != null && leash.isValid()) {
+                            t.hide(leash);
+                        }
                     }
                     t.apply();
                 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 040c55b..1452c8f 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -20,23 +20,24 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.config.FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
 
 import android.annotation.TargetApi;
 import android.app.PendingIntent;
@@ -82,7 +83,6 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.util.ViewCapture;
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.AssistantInputConsumer;
@@ -101,6 +101,7 @@
 import com.android.quickstep.util.ProtoTracer;
 import com.android.quickstep.util.ProxyScreenStatusProvider;
 import com.android.quickstep.util.SplitScreenBounds;
+import com.android.quickstep.util.ViewCapture;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -110,6 +111,8 @@
 import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
 import com.android.systemui.shared.tracing.ProtoTraceable;
 import com.android.wm.shell.back.IBackAnimation;
+import com.android.wm.shell.desktopmode.IDesktopMode;
+import com.android.wm.shell.floating.IFloatingTasks;
 import com.android.wm.shell.onehanded.IOneHanded;
 import com.android.wm.shell.pip.IPip;
 import com.android.wm.shell.recents.IRecentTasks;
@@ -167,6 +170,8 @@
             IPip pip = IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP));
             ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(
                     KEY_EXTRA_SHELL_SPLIT_SCREEN));
+            IFloatingTasks floatingTasks = IFloatingTasks.Stub.asInterface(bundle.getBinder(
+                    KEY_EXTRA_SHELL_FLOATING_TASKS));
             IOneHanded onehanded = IOneHanded.Stub.asInterface(
                     bundle.getBinder(KEY_EXTRA_SHELL_ONE_HANDED));
             IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface(
@@ -177,13 +182,15 @@
                     ISysuiUnlockAnimationController.Stub.asInterface(
                             bundle.getBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER));
             IRecentTasks recentTasks = IRecentTasks.Stub.asInterface(
-                    bundle.getBinder(KEY_EXTRA_RECENT_TASKS));
+                    bundle.getBinder(KEY_EXTRA_SHELL_RECENT_TASKS));
             IBackAnimation backAnimation = IBackAnimation.Stub.asInterface(
                     bundle.getBinder(KEY_EXTRA_SHELL_BACK_ANIMATION));
+            IDesktopMode desktopMode = IDesktopMode.Stub.asInterface(
+                    bundle.getBinder(KEY_EXTRA_SHELL_DESKTOP_MODE));
             MAIN_EXECUTOR.execute(() -> {
                 SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
-                        splitscreen, onehanded, shellTransitions, startingWindow, recentTasks,
-                        launcherUnlockAnimationController, backAnimation);
+                        splitscreen, floatingTasks, onehanded, shellTransitions, startingWindow,
+                        recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode);
                 TouchInteractionService.this.initInputMonitor("TISBinder#onInitialize()");
                 preloadOverview(true /* fromInit */);
             });
@@ -709,16 +716,19 @@
     }
 
     public GestureState createGestureState(GestureState previousGestureState) {
-        GestureState gestureState = new GestureState(mOverviewComponentObserver,
-                ActiveGestureLog.INSTANCE.incrementLogId());
+        final GestureState gestureState;
         TopTaskTracker.CachedTaskInfo taskInfo;
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
+            gestureState = new GestureState(mOverviewComponentObserver,
+                    ActiveGestureLog.INSTANCE.getLogId());
             taskInfo = previousGestureState.getRunningTask();
             gestureState.updateRunningTask(taskInfo);
             gestureState.updateLastStartedTaskId(previousGestureState.getLastStartedTaskId());
             gestureState.updatePreviouslyAppearedTaskIds(
                     previousGestureState.getPreviouslyAppearedTaskIds());
         } else {
+            gestureState = new GestureState(mOverviewComponentObserver,
+                    ActiveGestureLog.INSTANCE.incrementLogId());
             taskInfo = TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false);
             gestureState.updateRunningTask(taskInfo);
         }
@@ -944,8 +954,7 @@
         boolean launcherResumedThroughShellTransition =
                 gestureState.getActivityInterface().isResumed()
                         && !previousGestureState.isRecentsAnimationRunning();
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()
-                && gestureState.getActivityInterface().isInLiveTileMode()) {
+        if (gestureState.getActivityInterface().isInLiveTileMode()) {
             return createOverviewInputConsumer(
                     previousGestureState,
                     gestureState,
@@ -1033,8 +1042,7 @@
                 previousGestureState.isRunningAnimationToLauncher();
         boolean forcingOverviewInputConsumer =
                 ASSISTANT_GIVES_LAUNCHER_FOCUS.get() && forceOverviewInputConsumer;
-        boolean isInLiveTileMode = ENABLE_QUICKSTEP_LIVE_TILE.get()
-                && gestureState.getActivityInterface().isInLiveTileMode();
+        boolean isInLiveTileMode = gestureState.getActivityInterface().isInLiveTileMode();
         reasonString.append(SUBSTRING_PREFIX)
                 .append(hasWindowFocus
                         ? "activity has window focus"
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 7337132..19a6c38 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -78,6 +78,11 @@
         }
         // While animating into recents, update the visible task data as needed
         setter.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
+        setter.addEndListener(success -> {
+            if (!success) {
+                mRecentsView.reset();
+            }
+        });
         mRecentsView.updateEmptyMessage();
 
         setProperties(toState, config, setter);
@@ -114,8 +119,11 @@
 
         RecentsState currentState = mActivity.getStateManager().getState();
         if (isSplitSelectionState(state) && !isSplitSelectionState(currentState)) {
-            setter.add(mRecentsView.createSplitSelectInitAnimation(
-                    state.getTransitionDuration(mActivity, true /* isToState */)).buildAnim());
+            int duration = state.getTransitionDuration(mActivity, true /* isToState */);
+            // TODO (b/246851887): Pass in setter as a NO_ANIM PendingAnimation instead
+            PendingAnimation pa = new PendingAnimation(duration);
+            mRecentsView.createSplitSelectInitAnimation(pa, duration);
+            setter.add(pa.buildAnim());
         }
 
         Pair<FloatProperty, FloatProperty> taskViewsFloat =
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 7c96bf8..e32aaee 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.popup.QuickstepSystemShortcut;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.util.SplitConfigurationOptions;
@@ -54,6 +55,8 @@
 public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsState>
         implements StateListener<RecentsState> {
 
+    private static final int TASK_DISMISS_DURATION = 150;
+
     @Nullable
     private Task mHomeTask;
 
@@ -105,8 +108,9 @@
         if (mHomeTask != null && endTarget == RECENTS && animatorSet != null) {
             TaskView tv = getTaskViewByTaskId(mHomeTask.key.id);
             if (tv != null) {
-                PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150,
-                        false /* dismissingForSplitSelection*/);
+                PendingAnimation pa = new PendingAnimation(TASK_DISMISS_DURATION);
+                createTaskDismissAnimation(pa, tv, true, false,
+                        TASK_DISMISS_DURATION, false /* dismissingForSplitSelection*/);
                 pa.addEndListener(e -> setCurrentTask(-1));
                 AnimatorPlaybackController controller = pa.createPlaybackController();
                 controller.dispatchOnStart();
@@ -210,8 +214,9 @@
 
     @Override
     public void initiateSplitSelect(TaskView taskView,
-            @SplitConfigurationOptions.StagePosition int stagePosition) {
-        super.initiateSplitSelect(taskView, stagePosition);
+            @SplitConfigurationOptions.StagePosition int stagePosition,
+            StatsLogManager.EventEnum splitEvent) {
+        super.initiateSplitSelect(taskView, stagePosition, splitEvent);
         mActivity.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
     }
 
@@ -227,11 +232,6 @@
     }
 
     @Override
-    public void onStateTransitionFailed(RecentsState toState) {
-        reset();
-    }
-
-    @Override
     public void onStateTransitionComplete(RecentsState finalState) {
         if (finalState == HOME) {
             // Clean-up logic that occurs when recents is no longer in use/visible.
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
index eca61bb..db4927a 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
@@ -15,8 +15,6 @@
  */
 package com.android.quickstep.fallback;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-
 import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
 import com.android.quickstep.RecentsActivity;
 
@@ -28,8 +26,7 @@
 
     @Override
     protected boolean isRecentsInteractive() {
-        return mActivity.hasWindowFocus() || (ENABLE_QUICKSTEP_LIVE_TILE.get()
-                && mActivity.getStateManager().getState().hasLiveTile());
+        return mActivity.hasWindowFocus() || mActivity.getStateManager().getState().hasLiveTile();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 6f35928..64165b6 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.inputconsumers;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.media.AudioManager;
@@ -100,27 +99,23 @@
 
     @Override
     public void onHoverEvent(MotionEvent ev) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mActivity.dispatchGenericMotionEvent(ev);
-        }
+        mActivity.dispatchGenericMotionEvent(ev);
     }
 
     @Override
     public void onKeyEvent(KeyEvent ev) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            switch (ev.getKeyCode()) {
-                case KeyEvent.KEYCODE_VOLUME_DOWN:
-                case KeyEvent.KEYCODE_VOLUME_UP:
-                case KeyEvent.KEYCODE_VOLUME_MUTE:
-                    MediaSessionManager mgr = mActivity.getSystemService(MediaSessionManager.class);
-                    mgr.dispatchVolumeKeyEventAsSystemService(ev,
-                            AudioManager.USE_DEFAULT_STREAM_TYPE);
-                    break;
-                default:
-                    break;
-            }
-            mActivity.dispatchKeyEvent(ev);
+        switch (ev.getKeyCode()) {
+            case KeyEvent.KEYCODE_VOLUME_DOWN:
+            case KeyEvent.KEYCODE_VOLUME_UP:
+            case KeyEvent.KEYCODE_VOLUME_MUTE:
+                MediaSessionManager mgr = mActivity.getSystemService(MediaSessionManager.class);
+                mgr.dispatchVolumeKeyEventAsSystemService(ev,
+                        AudioManager.USE_DEFAULT_STREAM_TYPE);
+                break;
+            default:
+                break;
         }
+        mActivity.dispatchKeyEvent(ev);
     }
 }
 
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 45c8036..a9ff0fb 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -190,7 +190,8 @@
                 getCardinality(info), // cardinality = 16;
                 info.getWidget().getSpanX(), // span_x = 17 [default = 1];
                 info.getWidget().getSpanY(), // span_y = 18 [default = 1];
-                getAttributes(info) /* attributes */
+                getAttributes(info) /* attributes = 19 [(log_mode) = MODE_BYTES] */,
+                false /* is_kids_mode = 20 */
         );
     }
 
@@ -336,8 +337,9 @@
                 appState.getModel().enqueueModelUpdateTask(
                         new BaseModelUpdateTask() {
                             @Override
-                            public void execute(LauncherAppState app, BgDataModel dataModel,
-                                    AllAppsList apps) {
+                            public void execute(@NonNull final LauncherAppState app,
+                                    @NonNull final BgDataModel dataModel,
+                                    @NonNull final AllAppsList apps) {
                                 FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container);
                                 write(event, applyOverwrites(mItemInfo.buildProto(folderInfo)));
                             }
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index 693ef10..53e0c2b 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -33,9 +33,10 @@
      * Enums associated to gesture navigation events.
      */
     public enum GestureEvent {
-        MOTION_DOWN, MOTION_UP, SET_END_TARGET, ON_SETTLED_ON_END_TARGET, START_RECENTS_ANIMATION,
+        MOTION_DOWN, MOTION_UP, SET_END_TARGET, SET_END_TARGET_HOME, SET_END_TARGET_LAST_TASK,
+        SET_END_TARGET_NEW_TASK, ON_SETTLED_ON_END_TARGET, START_RECENTS_ANIMATION,
         FINISH_RECENTS_ANIMATION, CANCEL_RECENTS_ANIMATION, SET_ON_PAGE_TRANSITION_END_CALLBACK,
-        CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT,
+        CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT, SCROLLER_ANIMATION_ABORTED, TASK_APPEARED,
 
         /**
          * These GestureEvents are specifically associated to state flags that get set in
@@ -123,6 +124,25 @@
                                         + "being set.",
                                 writer);
                         break;
+                    case SCROLLER_ANIMATION_ABORTED:
+                        errorDetected |= printErrorIfTrue(
+                                encounteredEvents.contains(GestureEvent.SET_END_TARGET_HOME)
+                                        && !encounteredEvents.contains(
+                                                GestureEvent.ON_SETTLED_ON_END_TARGET),
+                                /* errorMessage= */ prefix + "\t\trecents view scroller animation "
+                                        + "aborted after setting end target HOME, but before"
+                                        + " settling on end target.",
+                                writer);
+                        break;
+                    case TASK_APPEARED:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.SET_END_TARGET_LAST_TASK)
+                                        && !encounteredEvents.contains(
+                                        GestureEvent.SET_END_TARGET_NEW_TASK),
+                                /* errorMessage= */ prefix + "\t\tonTasksAppeared called "
+                                        + "before/without setting end target to last or new task",
+                                writer);
+                        break;
                     case STATE_GESTURE_COMPLETED:
                         errorDetected |= printErrorIfTrue(
                                 !encounteredEvents.contains(GestureEvent.MOTION_UP),
@@ -173,6 +193,7 @@
                         break;
                     case MOTION_DOWN:
                     case SET_END_TARGET:
+                    case SET_END_TARGET_HOME:
                     case START_RECENTS_ANIMATION:
                     case SET_ON_PAGE_TRANSITION_END_CALLBACK:
                     case CANCEL_CURRENT_ANIMATION:
@@ -277,6 +298,21 @@
                             + "the task screenshot wasn't cleaned up.",
                     writer);
 
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(
+                            GestureEvent.SET_END_TARGET_LAST_TASK)
+                            && !encounteredEvents.contains(GestureEvent.TASK_APPEARED),
+                    /* errorMessage= */ prefix + "\t\tend target set to last task, but "
+                            + "onTaskAppeared wasn't called.",
+                    writer);
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(
+                            GestureEvent.SET_END_TARGET_NEW_TASK)
+                            && !encounteredEvents.contains(GestureEvent.TASK_APPEARED),
+                    /* errorMessage= */ prefix + "\t\tend target set to new task, but "
+                            + "onTaskAppeared wasn't called.",
+                    writer);
+
             if (!errorDetected) {
                 writer.println(prefix + "\t\tNo errors detected.");
             }
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index 40eb31b..23fdd58 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -161,16 +161,14 @@
         ArrayList<EventLog> eventLogs = new ArrayList<>();
 
         for (int i = 0; i < logs.length; i++) {
-            EventLog eventLog = logs[(nextIndex + logs.length - i - 1) % logs.length];
+            EventLog eventLog = logs[(nextIndex + i) % logs.length];
             if (eventLog == null) {
                 continue;
             }
             eventLogs.add(eventLog);
             writer.println(prefix + "\tLogs for logId: " + eventLog.logId);
 
-            List<EventEntry> eventEntries = eventLog.eventEntries;
-            for (int j = eventEntries.size() - 1; j >= 0; j--) {
-                EventEntry eventEntry = eventEntries.get(j);
+            for (EventEntry eventEntry : eventLog.eventEntries) {
                 date.setTime(eventEntry.time);
 
                 StringBuilder msg = new StringBuilder(prefix + "\t\t").append(sdf.format(date))
@@ -215,6 +213,11 @@
         return mCurrentLogId++;
     }
 
+    /** Returns the current log ID. This should be used when a log trace is being reused. */
+    public int getLogId() {
+        return mCurrentLogId;
+    }
+
     private boolean isEntrySame(
             EventEntry entry,
             int type,
diff --git a/quickstep/src/com/android/quickstep/util/AnimUtils.java b/quickstep/src/com/android/quickstep/util/AnimUtils.java
new file mode 100644
index 0000000..b7b7825
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/AnimUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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;
+
+/**
+ * Utility class containing methods to help manage animations, interpolators, and timings.
+ */
+public class AnimUtils {
+    /**
+     * Fetches device-specific timings for the Overview > Split animation
+     * (splitscreen initiated from Overview).
+     */
+    public static SplitAnimationTimings getDeviceOverviewToSplitTimings(boolean isTablet) {
+        return isTablet
+                ? SplitAnimationTimings.TABLET_OVERVIEW_TO_SPLIT
+                : SplitAnimationTimings.PHONE_OVERVIEW_TO_SPLIT;
+    }
+
+    /**
+     * Fetches device-specific timings for the Split > Confirm animation
+     * (splitscreen confirmed by selecting a second app).
+     */
+    public static SplitAnimationTimings getDeviceSplitToConfirmTimings(boolean isTablet) {
+        return isTablet
+                ? SplitAnimationTimings.TABLET_SPLIT_TO_CONFIRM
+                : SplitAnimationTimings.PHONE_SPLIT_TO_CONFIRM;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/BaseDepthController.java b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
index 4030630..29ae9a1 100644
--- a/quickstep/src/com/android/quickstep/util/BaseDepthController.java
+++ b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
@@ -24,6 +24,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.MultiPropertyFactory;
 import com.android.systemui.shared.system.BlurUtils;
 
 /**
@@ -31,7 +32,7 @@
  */
 public class BaseDepthController {
 
-    public static final FloatProperty<BaseDepthController> DEPTH =
+    private static final FloatProperty<BaseDepthController> DEPTH =
             new FloatProperty<BaseDepthController>("depth") {
                 @Override
                 public void setValue(BaseDepthController depthController, float depth) {
@@ -44,6 +45,19 @@
                 }
             };
 
+    private static final MultiPropertyFactory<BaseDepthController> DEPTH_PROPERTY_FACTORY =
+            new MultiPropertyFactory<>("depthProperty", DEPTH, Float::max);
+
+    private static final int DEPTH_INDEX_STATE_TRANSITION = 1;
+    private static final int DEPTH_INDEX_WIDGET = 2;
+
+    /** Property to set the depth for state transition. */
+    public static final FloatProperty<BaseDepthController> STATE_DEPTH =
+            DEPTH_PROPERTY_FACTORY.get(DEPTH_INDEX_STATE_TRANSITION);
+    /** Property to set the depth for widget picker. */
+    public static final FloatProperty<BaseDepthController> WIDGET_DEPTH =
+            DEPTH_PROPERTY_FACTORY.get(DEPTH_INDEX_WIDGET);
+
     protected final Launcher mLauncher;
 
     /**
diff --git a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
index 143042f..2a513ee 100644
--- a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
@@ -16,12 +16,14 @@
 package com.android.quickstep.util;
 
 import android.annotation.CallSuper;
+import android.view.Surface.Rotation;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -32,15 +34,20 @@
 public abstract class BaseUnfoldMoveFromCenterAnimator implements TransitionProgressListener {
 
     private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimation;
+    private final RotationChangeProvider mRotationChangeProvider;
 
     private final Map<ViewGroup, Boolean> mOriginalClipToPadding = new HashMap<>();
     private final Map<ViewGroup, Boolean> mOriginalClipChildren = new HashMap<>();
 
+    private final UnfoldMoveFromCenterRotationListener mRotationListener =
+            new UnfoldMoveFromCenterRotationListener();
     private boolean mAnimationInProgress = false;
 
-    public BaseUnfoldMoveFromCenterAnimator(WindowManager windowManager) {
+    public BaseUnfoldMoveFromCenterAnimator(WindowManager windowManager,
+            RotationChangeProvider rotationChangeProvider) {
         mMoveFromCenterAnimation = new UnfoldMoveFromCenterAnimator(windowManager,
                 new LauncherViewsMoveFromCenterTranslationApplier());
+        mRotationChangeProvider = rotationChangeProvider;
     }
 
     @CallSuper
@@ -50,6 +57,7 @@
         mMoveFromCenterAnimation.updateDisplayProperties();
         onPrepareViewsForAnimation();
         onTransitionProgress(0f);
+        mRotationChangeProvider.addCallback(mRotationListener);
     }
 
     @CallSuper
@@ -62,6 +70,7 @@
     @Override
     public void onTransitionFinished() {
         mAnimationInProgress = false;
+        mRotationChangeProvider.removeCallback(mRotationListener);
         mMoveFromCenterAnimation.onTransitionFinished();
         clearRegisteredViews();
     }
@@ -109,4 +118,14 @@
             view.setClipChildren(originalClipChildren);
         }
     }
+
+    private class UnfoldMoveFromCenterRotationListener implements
+            RotationChangeProvider.RotationListener {
+
+        @Override
+        public void onRotationChanged(@Rotation int newRotation) {
+            mMoveFromCenterAnimation.updateDisplayProperties(newRotation);
+            updateRegisteredViewsIfNeeded();
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java
new file mode 100644
index 0000000..433d23f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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 com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
+
+import java.util.ArrayList;
+
+/**
+ * A {@link Task} container that can contain N number of tasks that are part of the desktop in
+ * recent tasks list.
+ */
+public class DesktopTask extends GroupTask {
+
+    public ArrayList<Task> tasks;
+
+    public DesktopTask(ArrayList<Task> tasks) {
+        super(tasks.get(0), null, null, TaskView.Type.DESKTOP);
+        this.tasks = tasks;
+    }
+
+    @Override
+    public boolean containsTask(int taskId) {
+        for (Task task : tasks) {
+            if (task.key.id == taskId) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean hasMultipleTasks() {
+        return true;
+    }
+
+    @Override
+    public DesktopTask copy() {
+        return new DesktopTask(tasks);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java
index f30d00c..2be4f0a 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.java
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.java
@@ -20,6 +20,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
+import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
 
 /**
@@ -27,24 +28,25 @@
  * are represented as an app-pair in the recents task list.
  */
 public class GroupTask {
-    public @NonNull Task task1;
-    public @Nullable Task task2;
-    public @Nullable
-    SplitBounds mSplitBounds;
+    @NonNull
+    public final Task task1;
+    @Nullable
+    public final Task task2;
+    @Nullable
+    public final SplitBounds mSplitBounds;
+    @TaskView.Type
+    public final int taskViewType;
 
-    public GroupTask(@NonNull Task t1, @Nullable Task t2,
-            @Nullable SplitBounds splitBounds) {
+    public GroupTask(@NonNull Task t1, @Nullable Task t2, @Nullable SplitBounds splitBounds) {
+        this(t1, t2, splitBounds, t2 != null ? TaskView.Type.GROUPED : TaskView.Type.SINGLE);
+    }
+
+    protected GroupTask(@NonNull Task t1, @Nullable Task t2, @Nullable SplitBounds splitBounds,
+            @TaskView.Type int taskViewType) {
         task1 = t1;
         task2 = t2;
         mSplitBounds = splitBounds;
-    }
-
-    public GroupTask(@NonNull GroupTask group) {
-        task1 = new Task(group.task1);
-        task2 = group.task2 != null
-                ? new Task(group.task2)
-                : null;
-        mSplitBounds = group.mSplitBounds;
+        this.taskViewType = taskViewType;
     }
 
     public boolean containsTask(int taskId) {
@@ -54,4 +56,14 @@
     public boolean hasMultipleTasks() {
         return task2 != null;
     }
+
+    /**
+     * Create a copy of this instance
+     */
+    public GroupTask copy() {
+        return new GroupTask(
+                new Task(task1),
+                task2 != null ? new Task(task2) : null,
+                mSplitBounds);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
index 97be437..f12074b 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -24,7 +24,6 @@
 import android.util.FloatProperty;
 import android.util.MathUtils;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 
 import androidx.core.view.OneShotPreDrawListener;
 
@@ -34,6 +33,7 @@
 import com.android.launcher3.util.HorizontalInsettableView;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
 
@@ -62,16 +62,17 @@
     public LauncherUnfoldAnimationController(
             Launcher launcher,
             WindowManager windowManager,
-            UnfoldTransitionProgressProvider unfoldTransitionProgressProvider) {
+            UnfoldTransitionProgressProvider unfoldTransitionProgressProvider,
+            RotationChangeProvider rotationChangeProvider) {
         mLauncher = launcher;
         mProgressProvider = new ScopedUnfoldTransitionProgressProvider(
                 unfoldTransitionProgressProvider);
         mUnfoldMoveFromCenterHotseatAnimator = new UnfoldMoveFromCenterHotseatAnimator(launcher,
-                windowManager);
+                windowManager, rotationChangeProvider);
         mUnfoldMoveFromCenterWorkspaceAnimator = new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
-                windowManager);
+                windowManager, rotationChangeProvider);
         mNaturalOrientationProgressProvider = new NaturalRotationUnfoldProgressProvider(launcher,
-                WindowManagerGlobal.getWindowManagerService(), mProgressProvider);
+                rotationChangeProvider, mProgressProvider);
         mNaturalOrientationProgressProvider.init();
 
         // Animated in all orientations
diff --git a/quickstep/src/com/android/quickstep/util/LogUtils.kt b/quickstep/src/com/android/quickstep/util/LogUtils.kt
new file mode 100644
index 0000000..bad8506
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/LogUtils.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 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.util.Pair
+import com.android.internal.logging.InstanceIdSequence
+import com.android.launcher3.logging.InstanceId
+
+object LogUtils {
+  /**
+   * @return a [Pair] of two InstanceIds but with different types, one that can be used by framework
+   * (if needing to pass through an intent or such) and one used in Launcher
+   */
+  @JvmStatic
+  fun getShellShareableInstanceId():
+    Pair<com.android.internal.logging.InstanceId, InstanceId> {
+    val internalInstanceId = InstanceIdSequence(InstanceId.INSTANCE_ID_MAX).newInstanceId()
+    val launcherInstanceId = InstanceId(internalInstanceId.id)
+    return Pair(internalInstanceId, launcherInstanceId)
+  }
+}
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java
new file mode 100644
index 0000000..e189a66
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the Overview > OverviewSplitSelect animation.
+ */
+abstract class OverviewToSplitTimings implements SplitAnimationTimings {
+    // Overwritten by device-specific timings
+    abstract public int getPlaceholderFadeInStart();
+    abstract public int getPlaceholderFadeInEnd();
+    abstract public int getPlaceholderIconFadeInStart();
+    abstract public int getPlaceholderIconFadeInEnd();
+    abstract public int getStagedRectSlideStart();
+    abstract public int getStagedRectSlideEnd();
+    abstract public int getGridSlideStart();
+    abstract public int getGridSlideStagger();
+    abstract public int getGridSlideDuration();
+
+    // Common timings
+    public int getIconFadeStart() { return 0; }
+    public int getIconFadeEnd() { return 83; }
+    public int getActionsFadeStart() { return 0; }
+    public int getActionsFadeEnd() { return 83; }
+    public int getInstructionsContainerFadeInStart() { return 167; }
+    public int getInstructionsContainerFadeInEnd() { return 250; }
+    public int getInstructionsTextFadeInStart() { return 217; }
+    public int getInstructionsTextFadeInEnd() { return 300; }
+    public int getInstructionsUnfoldStart() { return 167; }
+    public int getInstructionsUnfoldEnd() { return 500; }
+    public Interpolator getGridSlidePrimaryInterpolator() { return EMPHASIZED; }
+    public Interpolator getGridSlideSecondaryInterpolator() { return INSTANT; }
+
+    abstract public int getDuration();
+    abstract public Interpolator getStagedRectXInterpolator();
+    abstract public Interpolator getStagedRectYInterpolator();
+    abstract public Interpolator getStagedRectScaleXInterpolator();
+    abstract public Interpolator getStagedRectScaleYInterpolator();
+
+    public float getGridSlideStartOffset() {
+        return (float) getGridSlideStart() / getDuration();
+    }
+    public float getGridSlideStaggerOffset() {
+        return (float) getGridSlideStagger() / getDuration();
+    }
+    public float getGridSlideDurationOffset() {
+        return (float) getGridSlideDuration() / getDuration();
+    }
+    public float getActionsFadeStartOffset() {
+        return (float) getActionsFadeStart() / getDuration();
+    }
+    public float getActionsFadeEndOffset() {
+        return (float) getActionsFadeEnd() / getDuration();
+    }
+    public float getIconFadeStartOffset() {
+        return (float) getIconFadeStart() / getDuration();
+    }
+    public float getIconFadeEndOffset() {
+        return (float) getIconFadeEnd() / getDuration();
+    }
+    public float getInstructionsContainerFadeInStartOffset() {
+        return (float) getInstructionsContainerFadeInStart() / getDuration();
+    }
+    public float getInstructionsContainerFadeInEndOffset() {
+        return (float) getInstructionsContainerFadeInEnd() / getDuration();
+    }
+    public float getInstructionsTextFadeInStartOffset() {
+        return (float) getInstructionsTextFadeInStart() / getDuration();
+    }
+    public float getInstructionsTextFadeInEndOffset() {
+        return (float) getInstructionsTextFadeInEnd() / getDuration();
+    }
+    public float getInstructionsUnfoldStartOffset() {
+        return (float) getInstructionsUnfoldStart() / getDuration();
+    }
+    public float getInstructionsUnfoldEndOffset() {
+        return (float) getInstructionsUnfoldEnd() / getDuration();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/PhoneOverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/PhoneOverviewToSplitTimings.java
new file mode 100644
index 0000000..f1dde53
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/PhoneOverviewToSplitTimings.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the Overview > OverviewSplitSelect animation on phones.
+ */
+public class PhoneOverviewToSplitTimings
+        extends OverviewToSplitTimings implements SplitAnimationTimings {
+    public int getPlaceholderFadeInStart() { return 0; }
+    public int getPlaceholderFadeInEnd() { return 133; }
+    public int getPlaceholderIconFadeInStart() { return 83; }
+    public int getPlaceholderIconFadeInEnd() { return 167; }
+    public int getStagedRectSlideStart() { return 0; }
+    public int getStagedRectSlideEnd() { return 333; }
+    public int getGridSlideStart() { return 100; }
+    public int getGridSlideStagger() { return 0; }
+    public int getGridSlideDuration() { return 417; }
+
+    public int getDuration() { return PHONE_ENTER_DURATION; }
+    public Interpolator getStagedRectXInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectYInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectScaleXInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectScaleYInterpolator() { return EMPHASIZED; }
+}
diff --git a/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
new file mode 100644
index 0000000..3d9e09e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 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;
+
+/**
+ * Timings for the OverviewSplitSelect > confirmed animation on phones.
+ */
+public class PhoneSplitToConfirmTimings
+        extends SplitToConfirmTimings implements SplitAnimationTimings {
+    public int getPlaceholderFadeInStart() { return 0; }
+    public int getPlaceholderFadeInEnd() { return 133; }
+    public int getPlaceholderIconFadeInStart() { return 50; }
+    public int getPlaceholderIconFadeInEnd() { return 133; }
+    public int getStagedRectSlideStart() { return 0; }
+    public int getStagedRectSlideEnd() { return 333; }
+
+    public int getDuration() { return PHONE_CONFIRM_DURATION; }
+}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
new file mode 100644
index 0000000..2966fbb
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.view.animation.Interpolator;
+
+/**
+ * An interface that supports the centralization of timing information for splitscreen animations.
+ */
+public interface SplitAnimationTimings {
+    int TABLET_ENTER_DURATION = 866;
+    int TABLET_CONFIRM_DURATION = 383;
+
+    int PHONE_ENTER_DURATION = 517;
+    int PHONE_CONFIRM_DURATION = 333;
+
+    int ABORT_DURATION = 500;
+
+    SplitAnimationTimings TABLET_OVERVIEW_TO_SPLIT = new TabletOverviewToSplitTimings();
+    SplitAnimationTimings TABLET_HOME_TO_SPLIT = new TabletHomeToSplitTimings();
+    SplitAnimationTimings TABLET_SPLIT_TO_CONFIRM = new TabletSplitToConfirmTimings();
+
+    SplitAnimationTimings PHONE_OVERVIEW_TO_SPLIT = new PhoneOverviewToSplitTimings();
+    SplitAnimationTimings PHONE_SPLIT_TO_CONFIRM = new PhoneSplitToConfirmTimings();
+
+    // Shared methods
+    int getDuration();
+    int getPlaceholderFadeInStart();
+    int getPlaceholderFadeInEnd();
+    int getPlaceholderIconFadeInStart();
+    int getPlaceholderIconFadeInEnd();
+    int getStagedRectSlideStart();
+    int getStagedRectSlideEnd();
+    Interpolator getStagedRectXInterpolator();
+    Interpolator getStagedRectYInterpolator();
+    Interpolator getStagedRectScaleXInterpolator();
+    Interpolator getStagedRectScaleYInterpolator();
+    default float getPlaceholderFadeInStartOffset() {
+        return (float) getPlaceholderFadeInStart() / getDuration();
+    }
+    default float getPlaceholderFadeInEndOffset() {
+        return (float) getPlaceholderFadeInEnd() / getDuration();
+    }
+    default float getPlaceholderIconFadeInStartOffset() {
+        return (float) getPlaceholderIconFadeInStart() / getDuration();
+    }
+    default float getPlaceholderIconFadeInEndOffset() {
+        return (float) getPlaceholderIconFadeInEnd() / getDuration();
+    }
+    default float getStagedRectSlideStartOffset() {
+        return (float) getStagedRectSlideStart() / getDuration();
+    }
+    default float getStagedRectSlideEndOffset() {
+        return (float) getStagedRectSlideEnd() / getDuration();
+    }
+
+    // Defaults for OverviewToSplit
+    default float getGridSlideStartOffset() { return 0; }
+    default float getGridSlideStaggerOffset() { return 0; }
+    default float getGridSlideDurationOffset() { return 0; }
+    default float getActionsFadeStartOffset() { return 0; }
+    default float getActionsFadeEndOffset() { return 0; }
+    default float getIconFadeStartOffset() { return 0; }
+    default float getIconFadeEndOffset() { return 0; }
+    default float getInstructionsContainerFadeInStartOffset() { return 0; }
+    default float getInstructionsContainerFadeInEndOffset() { return 0; }
+    default float getInstructionsTextFadeInStartOffset() { return 0; }
+    default float getInstructionsTextFadeInEndOffset() { return 0; }
+    default float getInstructionsUnfoldStartOffset() { return 0; }
+    default float getInstructionsUnfoldEndOffset() { return 0; }
+    default Interpolator getGridSlidePrimaryInterpolator() { return LINEAR; }
+    default Interpolator getGridSlideSecondaryInterpolator() { return LINEAR; }
+
+    // Defaults for HomeToSplit
+    default float getScrimFadeInStartOffset() { return 0; }
+    default float getScrimFadeInEndOffset() { return 0; }
+
+    // Defaults for SplitToConfirm
+    default float getInstructionsFadeStartOffset() { return 0; }
+    default float getInstructionsFadeEndOffset() { return 0; }
+}
+
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 7efb1a5..f07f990 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -22,8 +22,6 @@
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 
 import android.annotation.NonNull;
 import android.app.ActivityOptions;
@@ -38,12 +36,16 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.view.RemoteAnimationAdapter;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.logging.InstanceId;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager;
@@ -74,10 +76,12 @@
 
     private final Context mContext;
     private final Handler mHandler;
+    private StatsLogManager mStatsLogManager;
     private final SystemUiProxy mSystemUiProxy;
     private final StateManager mStateManager;
     private final DepthController mDepthController;
     private @StagePosition int mStagePosition;
+    private ItemInfo mItemInfo;
     private Intent mInitialTaskIntent;
     private int mInitialTaskId = INVALID_TASK_ID;
     private int mSecondTaskId = INVALID_TASK_ID;
@@ -88,11 +92,14 @@
     /** If not null, this is the TaskView we want to launch from */
     @Nullable
     private GroupedTaskView mLaunchingTaskView;
+    /** Represents where split is intended to be invoked from. */
+    private StatsLogManager.EventEnum mSplitEvent;
 
     public SplitSelectStateController(Context context, Handler handler, StateManager stateManager,
-            DepthController depthController) {
+            DepthController depthController, StatsLogManager statsLogManager) {
         mContext = context;
         mHandler = handler;
+        mStatsLogManager = statsLogManager;
         mSystemUiProxy = SystemUiProxy.INSTANCE.get(mContext);
         mStateManager = stateManager;
         mDepthController = depthController;
@@ -101,19 +108,25 @@
     /**
      * To be called after first task selected
      */
-    public void setInitialTaskSelect(int taskId, @StagePosition int stagePosition) {
+    public void setInitialTaskSelect(int taskId, @StagePosition int stagePosition,
+            StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo) {
         mInitialTaskId = taskId;
-        mStagePosition = stagePosition;
-        mInitialTaskIntent = null;
-        mUser = null;
+        setInitialData(stagePosition, splitEvent, itemInfo);
     }
 
     public void setInitialTaskSelect(Intent intent, @StagePosition int stagePosition,
-            @Nullable UserHandle user) {
+            @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent) {
         mInitialTaskIntent = intent;
+        mUser = itemInfo.user;
+        mItemInfo = itemInfo;
+        setInitialData(stagePosition, splitEvent, itemInfo);
+    }
+
+    private void setInitialData(@StagePosition int stagePosition,
+            StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo) {
+        mItemInfo = itemInfo;
         mStagePosition = stagePosition;
-        mInitialTaskId = INVALID_TASK_ID;
-        mUser = user;
+        mSplitEvent = splitEvent;
     }
 
     /**
@@ -137,8 +150,16 @@
                 FLAG_MUTABLE, null /* options */, mUser)
                 : PendingIntent.getActivity(mContext, 0, mInitialTaskIntent, FLAG_MUTABLE));
 
+        Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
+                LogUtils.getShellShareableInstanceId();
         launchTasks(mInitialTaskId, pendingIntent, fillInIntent, mSecondTaskId, mStagePosition,
-                callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO);
+                callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
+                instanceIds.first);
+
+        mStatsLogManager.logger()
+                .withItemInfo(mItemInfo)
+                .withInstanceId(instanceIds.second)
+                .log(mSplitEvent);
     }
 
 
@@ -174,7 +195,7 @@
     public void launchTasks(int taskId1, int taskId2, @StagePosition int stagePosition,
             Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
         launchTasks(taskId1, null /* taskPendingIntent */, null /* fillInIntent */, taskId2,
-                stagePosition, callback, freezeTaskList, splitRatio);
+                stagePosition, callback, freezeTaskList, splitRatio, null);
     }
 
     /**
@@ -183,27 +204,46 @@
      * fill in intent with a taskId2 are set.
      * @param taskPendingIntent is null when split is initiated from Overview
      * @param stagePosition representing location of task1
+     * @param shellInstanceId loggingId to be used by shell, will be non-null for actions that create
+     *                   a split instance, null for cases that bring existing instaces to the
+     *                   foreground (quickswitch, launching previous pairs from overview)
      */
     public void launchTasks(int taskId1, @Nullable PendingIntent taskPendingIntent,
             @Nullable Intent fillInIntent, int taskId2, @StagePosition int stagePosition,
-            Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
+            Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio,
+            @Nullable InstanceId shellInstanceId) {
         TestLogging.recordEvent(
                 TestProtocol.SEQUENCE_MAIN, "launchSplitTasks");
-        // Assume initial task is for top/left part of screen
-        final int[] taskIds = stagePosition == STAGE_POSITION_TOP_OR_LEFT
-                ? new int[]{taskId1, taskId2}
-                : new int[]{taskId2, taskId1};
+        final ActivityOptions options1 = ActivityOptions.makeBasic();
+        if (freezeTaskList) {
+            options1.setFreezeRecentTasksReordering();
+        }
         if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
-            RemoteSplitLaunchTransitionRunner animationRunner =
+            final RemoteSplitLaunchTransitionRunner animationRunner =
                     new RemoteSplitLaunchTransitionRunner(taskId1, taskPendingIntent, taskId2,
                             callback);
-            mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],
-                    null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, splitRatio,
-                    new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR,
-                            ActivityThread.currentActivityThread().getApplicationThread()));
-            // TODO(b/237635859): handle intent/shortcut + task with shell transition
+            final RemoteTransitionCompat remoteTransition = new RemoteTransitionCompat(
+                    animationRunner, MAIN_EXECUTOR,
+                    ActivityThread.currentActivityThread().getApplicationThread());
+            if (taskPendingIntent == null) {
+                mSystemUiProxy.startTasks(taskId1, options1.toBundle(), taskId2,
+                        null /* options2 */, stagePosition, splitRatio, remoteTransition,
+                        shellInstanceId);
+            } else {
+                final ShortcutInfo shortcutInfo = getShortcutInfo(mInitialTaskIntent,
+                        taskPendingIntent.getCreatorUserHandle());
+                if (shortcutInfo != null) {
+                    mSystemUiProxy.startShortcutAndTask(shortcutInfo,
+                            options1.toBundle(), taskId2, null /* options2 */, stagePosition,
+                            splitRatio, remoteTransition, shellInstanceId);
+                } else {
+                    mSystemUiProxy.startIntentAndTask(taskPendingIntent,
+                            fillInIntent, options1.toBundle(), taskId2, null /* options2 */,
+                            stagePosition, splitRatio, remoteTransition, shellInstanceId);
+                }
+            }
         } else {
-            RemoteSplitLaunchAnimationRunner animationRunner =
+            final RemoteSplitLaunchAnimationRunner animationRunner =
                     new RemoteSplitLaunchAnimationRunner(taskId1, taskPendingIntent, taskId2,
                             callback);
             final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
@@ -211,25 +251,21 @@
                     300, 150,
                     ActivityThread.currentActivityThread().getApplicationThread());
 
-            ActivityOptions mainOpts = ActivityOptions.makeBasic();
-            if (freezeTaskList) {
-                mainOpts.setFreezeRecentTasksReordering();
-            }
             if (taskPendingIntent == null) {
-                mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(),
-                        taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
-                        splitRatio, adapter);
+                mSystemUiProxy.startTasksWithLegacyTransition(taskId1, options1.toBundle(),
+                        taskId2, null /* options2 */, stagePosition, splitRatio, adapter,
+                        shellInstanceId);
             } else {
                 final ShortcutInfo shortcutInfo = getShortcutInfo(mInitialTaskIntent,
                         taskPendingIntent.getCreatorUserHandle());
                 if (shortcutInfo != null) {
-                    mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(shortcutInfo, taskId2,
-                            mainOpts.toBundle(), null /* sideOptions */, stagePosition, splitRatio,
-                            adapter);
+                    mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
+                            options1.toBundle(), taskId2, null /* options2 */, stagePosition,
+                            splitRatio, adapter, shellInstanceId);
                 } else {
                     mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent,
-                            fillInIntent, taskId2, mainOpts.toBundle(), null /* sideOptions */,
-                            stagePosition, splitRatio, adapter);
+                            fillInIntent, options1.toBundle(), taskId2, null /* options2 */,
+                            stagePosition, splitRatio, adapter, shellInstanceId);
                 }
             }
         }
@@ -239,6 +275,10 @@
         return mStagePosition;
     }
 
+    public StatsLogManager.EventEnum getSplitEvent() {
+        return mSplitEvent;
+    }
+
     public void setRecentsAnimationRunning(boolean running) {
         this.mRecentsAnimationRunning = running;
     }
@@ -358,6 +398,8 @@
         mStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
         mRecentsAnimationRunning = false;
         mLaunchingTaskView = null;
+        mItemInfo = null;
+        mSplitEvent = null;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
new file mode 100644
index 0000000..f5b00cf
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the OverviewSplitSelect > confirmed animation.
+ */
+abstract class SplitToConfirmTimings implements SplitAnimationTimings {
+    // Overwritten by device-specific timings
+    abstract public int getPlaceholderFadeInStart();
+    abstract public int getPlaceholderFadeInEnd();
+    abstract public int getPlaceholderIconFadeInStart();
+    abstract public int getPlaceholderIconFadeInEnd();
+    abstract public int getStagedRectSlideStart();
+    abstract public int getStagedRectSlideEnd();
+
+    // Common timings
+    public int getInstructionsFadeStart() { return 0; }
+    public int getInstructionsFadeEnd() { return 67; }
+    public Interpolator getStagedRectXInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectYInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectScaleXInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectScaleYInterpolator() { return EMPHASIZED; }
+
+    abstract public int getDuration();
+
+    public float getInstructionsFadeStartOffset() {
+        return (float) getInstructionsFadeStart() / getDuration();
+    }
+    public float getInstructionsFadeEndOffset() {
+        return (float) getInstructionsFadeEnd() / getDuration();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index eec8582..ad54a70 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -122,8 +123,7 @@
             if (grid.isVerticalBarLayout()) {
                 for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
                     View child = hotseatIcons.getChildAt(i);
-                    CellLayout.LayoutParams lp =
-                            ((CellLayout.LayoutParams) child.getLayoutParams());
+                    CellLayoutLayoutParams lp = ((CellLayoutLayoutParams) child.getLayoutParams());
                     addStaggeredAnimationForView(child, lp.cellY + 1, totalRows, duration);
                 }
             } else {
@@ -193,7 +193,7 @@
         // Set up springs on workspace items.
         for (int i = itemsContainer.getChildCount() - 1; i >= 0; i--) {
             View child = itemsContainer.getChildAt(i);
-            CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
+            CellLayoutLayoutParams lp = ((CellLayoutLayoutParams) child.getLayoutParams());
             addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows, duration);
         }
 
diff --git a/quickstep/src/com/android/quickstep/util/TabletHomeToSplitTimings.java b/quickstep/src/com/android/quickstep/util/TabletHomeToSplitTimings.java
new file mode 100644
index 0000000..bf8612a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/TabletHomeToSplitTimings.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the Home > OverviewSplitSelect animation on tablets.
+ */
+public class TabletHomeToSplitTimings
+        extends TabletOverviewToSplitTimings implements SplitAnimationTimings {
+    @Override
+    public Interpolator getStagedRectXInterpolator() { return LINEAR; }
+    @Override
+    public Interpolator getStagedRectScaleXInterpolator() { return LINEAR; }
+    @Override
+    public Interpolator getStagedRectScaleYInterpolator() { return LINEAR; }
+
+    public int getScrimFadeInStart() { return 0; }
+    public int getScrimFadeInEnd() { return 167; }
+
+    public float getScrimFadeInStartOffset() {
+        return (float) getScrimFadeInStart() / getDuration();
+    }
+    public float getScrimFadeInEndOffset() {
+        return (float) getScrimFadeInEnd() / getDuration();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/TabletOverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/TabletOverviewToSplitTimings.java
new file mode 100644
index 0000000..cbf46bf
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/TabletOverviewToSplitTimings.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the Overview > OverviewSplitSelect animation on tablets.
+ */
+public class TabletOverviewToSplitTimings
+        extends OverviewToSplitTimings implements SplitAnimationTimings {
+    public int getPlaceholderFadeInStart() { return 0; }
+    public int getPlaceholderFadeInEnd() { return 133; }
+    public int getPlaceholderIconFadeInStart() { return 167; }
+    public int getPlaceholderIconFadeInEnd() { return 250; }
+    public int getStagedRectSlideStart() { return 0; }
+    public int getStagedRectSlideEnd() { return 417; }
+    public int getGridSlideStart() { return 67; }
+    public int getGridSlideStagger() { return 16; }
+    public int getGridSlideDuration() { return 500; }
+
+    public int getDuration() { return TABLET_ENTER_DURATION; }
+    public Interpolator getStagedRectXInterpolator() { return DEACCEL_2; }
+    public Interpolator getStagedRectYInterpolator() { return DEACCEL_2; }
+    public Interpolator getStagedRectScaleXInterpolator() { return DEACCEL_2; }
+    public Interpolator getStagedRectScaleYInterpolator() { return DEACCEL_2; }
+}
diff --git a/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
new file mode 100644
index 0000000..580cc99
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 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;
+
+/**
+ * Timings for the OverviewSplitSelect > confirmed animation on tablets.
+ */
+public class TabletSplitToConfirmTimings
+        extends SplitToConfirmTimings implements SplitAnimationTimings {
+    public int getPlaceholderFadeInStart() { return 0; }
+    public int getPlaceholderFadeInEnd() { return 133; }
+    public int getPlaceholderIconFadeInStart() { return 167; }
+    public int getPlaceholderIconFadeInEnd() { return 250; }
+    public int getStagedRectSlideStart() { return 0; }
+    public int getStagedRectSlideEnd() { return 383; }
+
+    public int getDuration() { return TABLET_CONFIRM_DURATION; }
+}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 0a49008..c03aa3f 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -15,7 +15,8 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -25,7 +26,6 @@
 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
 import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
-import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
 
 import android.animation.TimeInterpolator;
 import android.content.Context;
@@ -46,9 +46,9 @@
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.TaskAnimationManager;
-import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
 
@@ -317,9 +317,9 @@
             // mIsRecentsRtl is the inverse of TaskView RTL.
             boolean isRtlEnabled = !mIsRecentsRtl;
             mPositionHelper.updateThumbnailMatrix(
-                    mThumbnailPosition, mThumbnailData,
-                    mTaskRect.width(), mTaskRect.height(),
-                    mDp, mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
+                    mThumbnailPosition, mThumbnailData, mTaskRect.width(), mTaskRect.height(),
+                    mDp.widthPx, mDp.taskbarSize, mDp.isTablet,
+                    mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
             mPositionHelper.getMatrix().invert(mInversePositionMatrix);
             if (DEBUG) {
                 Log.d(TAG, " taskRect: " + mTaskRect);
@@ -392,7 +392,7 @@
                 .withCornerRadius(getCurrentCornerRadius());
 
         // If mDrawsBelowRecents is unset, no reordering will be enforced.
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mDrawsBelowRecents != null) {
+        if (mDrawsBelowRecents != null) {
             // In legacy transitions, the animation leashes remain in same hierarchy in the
             // TaskDisplayArea, so we don't want to bump the layer too high otherwise it will
             // conflict with layers that WM core positions (ie. the input consumers).  For shell
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
index dc97dd6..01a997a 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
@@ -21,6 +21,7 @@
 
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 
 /**
  * Animation that moves hotseat icons from center to the sides (final position)
@@ -29,8 +30,9 @@
 
     private final Launcher mLauncher;
 
-    public UnfoldMoveFromCenterHotseatAnimator(Launcher launcher, WindowManager windowManager) {
-        super(windowManager);
+    public UnfoldMoveFromCenterHotseatAnimator(Launcher launcher, WindowManager windowManager,
+            RotationChangeProvider rotationChangeProvider) {
+        super(windowManager, rotationChangeProvider);
         mLauncher = launcher;
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
index 354d157..95a4b8f 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
@@ -22,6 +22,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
+import com.android.systemui.unfold.updates.RotationChangeProvider;
 
 /**
  * Animation that moves launcher icons and widgets from center to the sides (final position)
@@ -30,8 +31,9 @@
 
     private final Launcher mLauncher;
 
-    public UnfoldMoveFromCenterWorkspaceAnimator(Launcher launcher, WindowManager windowManager) {
-        super(windowManager);
+    public UnfoldMoveFromCenterWorkspaceAnimator(Launcher launcher, WindowManager windowManager,
+            RotationChangeProvider rotationChangeProvider) {
+        super(windowManager, rotationChangeProvider);
         mLauncher = launcher;
     }
 
diff --git a/src/com/android/launcher3/util/ViewCapture.java b/quickstep/src/com/android/quickstep/util/ViewCapture.java
similarity index 73%
rename from src/com/android/launcher3/util/ViewCapture.java
rename to quickstep/src/com/android/quickstep/util/ViewCapture.java
index 0cf3ad7..6171c5d 100644
--- a/src/com/android/launcher3/util/ViewCapture.java
+++ b/quickstep/src/com/android/quickstep/util/ViewCapture.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.util;
+package com.android.quickstep.util;
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -44,6 +44,9 @@
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.LooperExecutor;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.view.ViewCaptureData.ExportedData;
 import com.android.launcher3.view.ViewCaptureData.FrameData;
 import com.android.launcher3.view.ViewCaptureData.ViewNode;
@@ -64,6 +67,10 @@
 
     private static final String TAG = "ViewCapture";
 
+    // These flags are copies of two private flags in the View class.
+    private static final int PFLAG_INVALIDATED = 0x80000000;
+    private static final int PFLAG_DIRTY_MASK = 0x00200000;
+
     // Number of frames to keep in memory
     private static final int MEMORY_SIZE = 2000;
     // Initial size of the reference pool. This is at least be 5 * total number of views in
@@ -184,6 +191,7 @@
         private final ViewRef mViewRef = new ViewRef();
 
         private int mFrameIndexBg = -1;
+        private boolean mIsFirstFrame = true;
         private final long[] mFrameTimesBg = new long[MEMORY_SIZE];
         private final ViewPropertyRef[] mNodesBg = new ViewPropertyRef[MEMORY_SIZE];
 
@@ -202,6 +210,7 @@
             Message m = Message.obtain(mHandler);
             m.obj = mViewRef.next;
             mHandler.sendMessage(m);
+            mIsFirstFrame = false;
             Trace.endSection();
         }
 
@@ -211,9 +220,9 @@
          */
         @WorkerThread
         private boolean captureViewPropertiesBg(Message msg) {
-            ViewRef start = (ViewRef) msg.obj;
+            ViewRef viewRefStart = (ViewRef) msg.obj;
             long time = msg.getWhen();
-            if (start == null) {
+            if (viewRefStart == null) {
                 return false;
             }
             mFrameIndexBg++;
@@ -224,12 +233,11 @@
 
             ViewPropertyRef recycle = mNodesBg[mFrameIndexBg];
 
-            ViewPropertyRef result = null;
+            ViewPropertyRef resultStart = null;
             ViewPropertyRef resultEnd = null;
 
-            ViewRef current = start;
-            ViewRef last = start;
-            while (current != null) {
+            ViewRef viewRefEnd = viewRefStart;
+            while (viewRefEnd != null) {
                 ViewPropertyRef propertyRef = recycle;
                 if (propertyRef == null) {
                     propertyRef = new ViewPropertyRef();
@@ -238,24 +246,64 @@
                     propertyRef.next = null;
                 }
 
-                propertyRef.transfer(current);
-                last = current;
-                current = current.next;
+                ViewPropertyRef copy = null;
+                if (viewRefEnd.childCount < 0) {
+                    copy = findInLastFrame(viewRefEnd.view.hashCode());
+                    viewRefEnd.childCount = (copy != null) ? copy.childCount : 0;
+                }
+                viewRefEnd.transferTo(propertyRef);
 
-                if (result == null) {
-                    result = propertyRef;
-                    resultEnd = result;
+                if (resultStart == null) {
+                    resultStart = propertyRef;
+                    resultEnd = resultStart;
                 } else {
                     resultEnd.next = propertyRef;
-                    resultEnd = propertyRef;
+                    resultEnd = resultEnd.next;
                 }
+
+                if (copy != null) {
+                    int pending = copy.childCount;
+                    while (pending > 0) {
+                        copy = copy.next;
+                        pending = pending - 1 + copy.childCount;
+
+                        propertyRef = recycle;
+                        if (propertyRef == null) {
+                            propertyRef = new ViewPropertyRef();
+                        } else {
+                            recycle = recycle.next;
+                            propertyRef.next = null;
+                        }
+
+                        copy.transferTo(propertyRef);
+
+                        resultEnd.next = propertyRef;
+                        resultEnd = resultEnd.next;
+                    }
+                }
+
+                if (viewRefEnd.next == null) {
+                    // The compiler will complain about using a non-final variable from
+                    // an outer class in a lambda if we pass in viewRefEnd directly.
+                    final ViewRef finalViewRefEnd = viewRefEnd;
+                    MAIN_EXECUTOR.execute(() -> addToPool(viewRefStart, finalViewRefEnd));
+                    break;
+                }
+                viewRefEnd = viewRefEnd.next;
             }
-            mNodesBg[mFrameIndexBg] = result;
-            ViewRef end = last;
-            MAIN_EXECUTOR.execute(() -> addToPool(start, end));
+            mNodesBg[mFrameIndexBg] = resultStart;
             return true;
         }
 
+        private ViewPropertyRef findInLastFrame(int hashCode) {
+            int lastFrameIndex = (mFrameIndexBg == 0) ? MEMORY_SIZE - 1 : mFrameIndexBg - 1;
+            ViewPropertyRef viewPropertyRef = mNodesBg[lastFrameIndex];
+            while (viewPropertyRef != null && viewPropertyRef.hashCode != hashCode) {
+                viewPropertyRef = viewPropertyRef.next;
+            }
+            return viewPropertyRef;
+        }
+
         void attachToRoot() {
             if (mRoot.isAttachedToWindow()) {
                 mRoot.getViewTreeObserver().addOnDrawListener(this);
@@ -311,8 +359,16 @@
             ref.view = view;
             start.next = ref;
             if (view instanceof ViewGroup) {
-                ViewRef result = ref;
                 ViewGroup parent = (ViewGroup) view;
+                // If a view has not changed since the last frame, we will copy
+                // its children from the last processed frame's data.
+                if ((view.mPrivateFlags & (PFLAG_INVALIDATED | PFLAG_DIRTY_MASK)) == 0
+                        && !mIsFirstFrame) {
+                    // A negative child count is the signal to copy this view from the last frame.
+                    ref.childCount = -parent.getChildCount();
+                    return ref;
+                }
+                ViewRef result = ref;
                 int childCount = ref.childCount = parent.getChildCount();
                 for (int i = 0; i < childCount; i++) {
                     result = captureViewTree(parent.getChildAt(i), result);
@@ -346,31 +402,26 @@
 
         public ViewPropertyRef next;
 
-        public void transfer(ViewRef viewRef) {
-            childCount = viewRef.childCount;
-
-            View view = viewRef.view;
-            viewRef.view = null;
-
-            clazz = view.getClass();
-            hashCode = view.hashCode();
-            id = view.getId();
-            left = view.getLeft();
-            top = view.getTop();
-            right = view.getRight();
-            bottom = view.getBottom();
-            scrollX = view.getScrollX();
-            scrollY = view.getScrollY();
-
-            translateX = view.getTranslationX();
-            translateY = view.getTranslationY();
-            scaleX = view.getScaleX();
-            scaleY = view.getScaleY();
-            alpha = view.getAlpha();
-
-            visibility = view.getVisibility();
-            willNotDraw = view.willNotDraw();
-            elevation = view.getElevation();
+        public void transferTo(ViewPropertyRef out) {
+            out.clazz = this.clazz;
+            out.hashCode = this.hashCode;
+            out.childCount = this.childCount;
+            out.id = this.id;
+            out.left = this.left;
+            out.top = this.top;
+            out.right = this.right;
+            out.bottom = this.bottom;
+            out.scrollX = this.scrollX;
+            out.scrollY = this.scrollY;
+            out.scaleX = this.scaleX;
+            out.scaleY = this.scaleY;
+            out.translateX = this.translateX;
+            out.translateY = this.translateY;
+            out.alpha = this.alpha;
+            out.visibility = this.visibility;
+            out.willNotDraw = this.willNotDraw;
+            out.clipChildren = this.clipChildren;
+            out.elevation = this.elevation;
         }
 
         /**
@@ -417,6 +468,33 @@
         public View view;
         public int childCount = 0;
         public ViewRef next;
+
+        public void transferTo(ViewPropertyRef out) {
+            out.childCount = this.childCount;
+
+            View view = this.view;
+            this.view = null;
+
+            out.clazz = view.getClass();
+            out.hashCode = view.hashCode();
+            out.id = view.getId();
+            out.left = view.getLeft();
+            out.top = view.getTop();
+            out.right = view.getRight();
+            out.bottom = view.getBottom();
+            out.scrollX = view.getScrollX();
+            out.scrollY = view.getScrollY();
+
+            out.translateX = view.getTranslationX();
+            out.translateY = view.getTranslationY();
+            out.scaleX = view.getScaleX();
+            out.scaleY = view.getScaleY();
+            out.alpha = view.getAlpha();
+            out.elevation = view.getElevation();
+
+            out.visibility = view.getVisibility();
+            out.willNotDraw = view.willNotDraw();
+        }
     }
 
     private static final class ViewIdProvider {
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
new file mode 100644
index 0000000..9874f96
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2022 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.views;
+
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RoundRectShape;
+import android.os.SystemProperties;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.RunnableList;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskThumbnailCache;
+import com.android.quickstep.util.CancellableTask;
+import com.android.quickstep.util.RecentsOrientedState;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * TaskView that contains all tasks that are part of the desktop.
+ */
+// TODO(b/249371338): TaskView needs to be refactored to have better support for N tasks.
+public class DesktopTaskView extends TaskView {
+
+    /** Flag to indicate whether desktop mode is available on the device */
+    public static final boolean DESKTOP_MODE_SUPPORTED = SystemProperties.getBoolean(
+            "persist.wm.debug.desktop_mode", false);
+
+    private static final String TAG = DesktopTaskView.class.getSimpleName();
+
+    private static final boolean DEBUG = true;
+
+    private List<Task> mTasks;
+
+    private final ArrayList<TaskThumbnailView> mSnapshotViews = new ArrayList<>();
+
+    /** Maps {@code taskIds} to corresponding {@link TaskThumbnailView}s */
+    private final SparseArray<TaskThumbnailView> mSnapshotViewMap = new SparseArray<>();
+
+    private final ArrayList<CancellableTask<?>> mPendingThumbnailRequests = new ArrayList<>();
+
+    public DesktopTaskView(Context context) {
+        this(context, null);
+    }
+
+    public DesktopTaskView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DesktopTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        float[] outerRadii = new float[8];
+        Arrays.fill(outerRadii, getTaskCornerRadius());
+        RoundRectShape shape = new RoundRectShape(outerRadii, null, null);
+        ShapeDrawable background = new ShapeDrawable(shape);
+        background.setTint(getResources().getColor(android.R.color.system_neutral2_300));
+        // TODO(b/244348395): this should be wallpaper
+        setBackground(background);
+
+        mSnapshotViews.add(mSnapshotView);
+    }
+
+    @Override
+    public void bind(Task task, RecentsOrientedState orientedState) {
+        bind(Collections.singletonList(task), orientedState);
+    }
+
+    /**
+     * Updates this desktop task to the gives task list defined in {@code tasks}
+     */
+    public void bind(List<Task> tasks, RecentsOrientedState orientedState) {
+        if (DEBUG) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("bind tasks=").append(tasks.size()).append("\n");
+            for (Task task : tasks) {
+                sb.append(" key=").append(task.key).append("\n");
+            }
+            Log.d(TAG, sb.toString());
+        }
+        if (tasks.isEmpty()) {
+            return;
+        }
+        cancelPendingLoadTasks();
+
+        mTasks = tasks;
+        mSnapshotViewMap.clear();
+
+        // Ensure there are equal number of snapshot views and tasks.
+        // More tasks than views, add views. More views than tasks, remove views.
+        // TODO(b/251586230): use a ViewPool for creating TaskThumbnailViews
+        if (mSnapshotViews.size() > mTasks.size()) {
+            int diff = mSnapshotViews.size() - mTasks.size();
+            for (int i = 0; i < diff; i++) {
+                TaskThumbnailView snapshotView = mSnapshotViews.remove(0);
+                removeView(snapshotView);
+            }
+        } else if (mSnapshotViews.size() < mTasks.size()) {
+            int diff = mTasks.size() - mSnapshotViews.size();
+            for (int i = 0; i < diff; i++) {
+                TaskThumbnailView snapshotView = new TaskThumbnailView(getContext());
+                mSnapshotViews.add(snapshotView);
+                addView(snapshotView, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+            }
+        }
+
+        for (int i = 0; i < mTasks.size(); i++) {
+            Task task = mTasks.get(i);
+            TaskThumbnailView snapshotView = mSnapshotViews.get(i);
+            snapshotView.bind(task);
+            mSnapshotViewMap.put(task.key.id, snapshotView);
+        }
+
+        updateTaskIdContainer();
+        updateTaskIdAttributeContainer();
+
+        setOrientationState(orientedState);
+    }
+
+    private void updateTaskIdContainer() {
+        // TODO(b/249371338): TaskView expects the array to have at least 2 elements.
+        // At least 2 elements in the array
+        mTaskIdContainer = new int[Math.max(mTasks.size(), 2)];
+        for (int i = 0; i < mTasks.size(); i++) {
+            mTaskIdContainer[i] = mTasks.get(i).key.id;
+        }
+    }
+
+    private void updateTaskIdAttributeContainer() {
+        // TODO(b/249371338): TaskView expects the array to have at least 2 elements.
+        // At least 2 elements in the array
+        mTaskIdAttributeContainer = new TaskIdAttributeContainer[Math.max(mTasks.size(), 2)];
+        for (int i = 0; i < mTasks.size(); i++) {
+            Task task = mTasks.get(i);
+            TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+            mTaskIdAttributeContainer[i] = createAttributeContainer(task, thumbnailView);
+        }
+    }
+
+    private TaskIdAttributeContainer createAttributeContainer(Task task,
+            TaskThumbnailView thumbnailView) {
+        return new TaskIdAttributeContainer(task, thumbnailView, null, STAGE_POSITION_UNDEFINED);
+    }
+
+    @Nullable
+    @Override
+    public Task getTask() {
+        // TODO(b/249371338): returning first task. This won't work well with multiple tasks.
+        return mTasks.size() > 0 ? mTasks.get(0) : null;
+    }
+
+    @Override
+    public TaskThumbnailView getThumbnail() {
+        // TODO(b/249371338): returning single thumbnail. This won't work well with multiple tasks.
+        Task task = getTask();
+        if (task != null) {
+            return mSnapshotViewMap.get(task.key.id);
+        }
+        return null;
+    }
+
+    @Override
+    public boolean containsTaskId(int taskId) {
+        // Thumbnail map contains taskId -> thumbnail map. Use the keys for contains
+        return mSnapshotViewMap.contains(taskId);
+    }
+
+    @Override
+    public void onTaskListVisibilityChanged(boolean visible, int changes) {
+        cancelPendingLoadTasks();
+        if (visible) {
+            RecentsModel model = RecentsModel.INSTANCE.get(getContext());
+            TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
+
+            if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+                for (Task task : mTasks) {
+                    CancellableTask<?> thumbLoadRequest =
+                            thumbnailCache.updateThumbnailInBackground(task, thumbnailData -> {
+                                TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+                                if (thumbnailView != null) {
+                                    thumbnailView.setThumbnail(task, thumbnailData);
+                                }
+                            });
+                    if (thumbLoadRequest != null) {
+                        mPendingThumbnailRequests.add(thumbLoadRequest);
+                    }
+                }
+            }
+        } else {
+            if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+                for (Task task : mTasks) {
+                    TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+                    if (thumbnailView != null) {
+                        thumbnailView.setThumbnail(null, null);
+                    }
+                    // Reset the task thumbnail ref
+                    task.thumbnail = null;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setOrientationState(RecentsOrientedState orientationState) {
+        // TODO(b/249371338): this copies logic from TaskView
+        PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
+        boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+        DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+
+        LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
+
+        int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
+        int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
+        int taskMargin = deviceProfile.overviewTaskMarginPx;
+
+        orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconHeight,
+                thumbnailTopMargin, isRtl);
+
+        LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
+        snapshotParams.topMargin = thumbnailTopMargin;
+
+        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+            TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i);
+            thumbnailView.setLayoutParams(snapshotParams);
+        }
+    }
+
+    @Override
+    protected void cancelPendingLoadTasks() {
+        for (CancellableTask<?> cancellableTask : mPendingThumbnailRequests) {
+            cancellableTask.cancel();
+        }
+        mPendingThumbnailRequests.clear();
+    }
+
+    @Override
+    public boolean offerTouchToChildren(MotionEvent event) {
+        return false;
+    }
+
+    @Override
+    protected boolean showTaskMenuWithContainer(IconView iconView) {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public RunnableList launchTaskAnimated() {
+        RunnableList endCallback = new RunnableList();
+        SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps();
+        RecentsView<?, ?> recentsView = getRecentsView();
+        recentsView.addSideTaskLaunchCallback(endCallback);
+        return endCallback;
+    }
+
+    @Override
+    public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
+        SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps();
+        callback.accept(true);
+    }
+
+    @Override
+    void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) {
+        // Sets new thumbnails based on the incoming data and refreshes the rest.
+        // Create a copy of the thumbnail map, so we can track thumbnails that need refreshing.
+        SparseArray<TaskThumbnailView> thumbnailsToRefresh = mSnapshotViewMap.clone();
+        if (thumbnailDatas != null) {
+            for (Task task : mTasks) {
+                int key = task.key.id;
+                TaskThumbnailView thumbnailView = thumbnailsToRefresh.get(key);
+                ThumbnailData thumbnailData = thumbnailDatas.get(key);
+                if (thumbnailView != null && thumbnailData != null) {
+                    thumbnailView.setThumbnail(task, thumbnailData);
+                    // Remove this thumbnail from the list that should be refreshed.
+                    thumbnailsToRefresh.remove(key);
+                }
+            }
+        }
+
+        // Refresh the rest that were not updated.
+        for (int i = 0; i < thumbnailsToRefresh.size(); i++) {
+            thumbnailsToRefresh.valueAt(i).refresh();
+        }
+    }
+
+    @Override
+    public TaskThumbnailView[] getThumbnails() {
+        TaskThumbnailView[] thumbnails = new TaskThumbnailView[mSnapshotViewMap.size()];
+        for (int i = 0; i < thumbnails.length; i++) {
+            thumbnails[i] = mSnapshotViewMap.valueAt(i);
+        }
+        return thumbnails;
+    }
+
+    @Override
+    public void onRecycle() {
+        resetPersistentViewTransforms();
+        // Clear any references to the thumbnail (it will be re-read either from the cache or the
+        // system on next bind)
+        for (Task task : mTasks) {
+            TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+            if (thumbnailView != null) {
+                thumbnailView.setThumbnail(task, null);
+            }
+        }
+        setOverlayEnabled(false);
+        onTaskListVisibilityChanged(false);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int containerWidth = MeasureSpec.getSize(widthMeasureSpec);
+        int containerHeight = MeasureSpec.getSize(heightMeasureSpec);
+
+        setMeasuredDimension(containerWidth, containerHeight);
+
+        int thumbnails = mSnapshotViewMap.size();
+        if (thumbnails == 0) {
+            return;
+        }
+
+        int windowWidth = mActivity.getDeviceProfile().widthPx;
+        int windowHeight = mActivity.getDeviceProfile().heightPx;
+
+        float scaleWidth = containerWidth / (float) windowWidth;
+        float scaleHeight = containerHeight / (float) windowHeight;
+
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onMeasure: container=[" + containerWidth + "," + containerHeight + "] window=["
+                            + windowWidth + "," + windowHeight + "] scale=[" + scaleWidth + ","
+                            + scaleHeight + "]");
+        }
+
+        // Desktop tile is a shrunk down version of launcher and freeform task thumbnails.
+        for (int i = 0; i < mTasks.size(); i++) {
+            Task task = mTasks.get(i);
+            Rect taskSize = task.appBounds;
+            if (taskSize == null) {
+                // Default to quarter of the desktop if we did not get app bounds.
+                taskSize = new Rect(0, 0, windowWidth / 4, windowHeight / 4);
+            }
+
+            int thumbWidth = (int) (taskSize.width() * scaleWidth);
+            int thumbHeight = (int) (taskSize.height() * scaleHeight);
+
+            TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+            if (thumbnailView != null) {
+                thumbnailView.measure(MeasureSpec.makeMeasureSpec(thumbWidth, MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(thumbHeight, MeasureSpec.EXACTLY));
+
+                // Position the task to the same position as it would be on the desktop
+                Point positionInParent = task.positionInParent;
+                if (positionInParent == null) {
+                    positionInParent = new Point(0, 0);
+                }
+                int taskX = (int) (positionInParent.x * scaleWidth);
+                int taskY = (int) (positionInParent.y * scaleHeight);
+                thumbnailView.setX(taskX);
+                thumbnailView.setY(taskY);
+
+                if (DEBUG) {
+                    Log.d(TAG, "onMeasure: task=" + task.key + " thumb=[" + thumbWidth + ","
+                            + thumbHeight + "]" + " pos=[" + taskX + "," + taskY + "]");
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setOverlayEnabled(boolean overlayEnabled) {
+        // Intentional no-op to prevent setting smart actions overlay on thumbnails
+    }
+
+    @Override
+    public void setFullscreenProgress(float progress) {
+        // TODO(b/249371338): this copies parent implementation and makes it work for N thumbs
+        progress = Utilities.boundToRange(progress, 0, 1);
+        mFullscreenProgress = progress;
+        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+            TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i);
+            thumbnailView.getTaskOverlay().setFullscreenProgress(progress);
+            updateSnapshotRadius();
+        }
+    }
+
+    @Override
+    protected void updateSnapshotRadius() {
+        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+            mSnapshotViewMap.valueAt(i).setFullscreenParams(mCurrentFullscreenParams);
+        }
+    }
+
+    @Override
+    protected void setIconAndDimTransitionProgress(float progress, boolean invert) {
+        // no-op
+    }
+
+    @Override
+    public void setColorTint(float amount, int tintColor) {
+        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+            mSnapshotViewMap.valueAt(i).setDimAlpha(amount);
+        }
+    }
+
+    @Override
+    protected void applyThumbnailSplashAlpha() {
+        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+            mSnapshotViewMap.valueAt(i).setSplashAlpha(mTaskThumbnailSplashAlpha);
+        }
+    }
+
+    @Override
+    void setThumbnailVisibility(int visibility) {
+        for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+            mSnapshotViewMap.valueAt(i).setVisibility(visibility);
+        }
+    }
+
+    @Override
+    protected boolean confirmSecondSplitSelectApp() {
+        // Desktop tile can't be in split screen
+        return false;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 76552a3..96504af 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -385,4 +385,12 @@
         mBanner.setLayerType(View.LAYER_TYPE_HARDWARE, layerPaint);
         mBanner.setLayerPaint(layerPaint);
     }
+
+    void setBannerVisibility(int visibility) {
+        if (mBanner == null) {
+            return;
+        }
+
+        mBanner.setVisibility(visibility);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index c3bf041..dc1ae52 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -1,9 +1,8 @@
 package com.android.quickstep.views;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
 
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -24,7 +23,6 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
@@ -32,7 +30,9 @@
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.util.AnimUtils;
 import com.android.quickstep.util.MultiValueUpdateListener;
+import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.util.TaskCornerRadius;
 import com.android.systemui.shared.system.QuickStepContract;
 
@@ -42,7 +42,8 @@
  * which will have the thumbnail from the provided existing TaskView overlaying the taskview itself.
  *
  * Can then animate the taskview using
- * {@link #addAnimation(PendingAnimation, RectF, Rect, boolean, boolean)}
+ * {@link #addStagingAnimation(PendingAnimation, RectF, Rect, boolean, boolean)} or
+ * {@link #addConfirmAnimation(PendingAnimation, RectF, Rect, boolean, boolean)}
  * giving a starting and ending bounds. Currently this is set to use the split placeholder view,
  * but it could be generified.
  *
@@ -124,7 +125,7 @@
 
         RecentsView recentsView = launcher.getOverviewPanel();
         mOrientationHandler = recentsView.getPagedOrientationHandler();
-        mStagePosition = recentsView.getSplitPlaceholder().getActiveSplitStagePosition();
+        mStagePosition = recentsView.getSplitSelectController().getActiveSplitStagePosition();
         mSplitPlaceholderView.setIcon(icon,
                 mContext.getResources().getDimensionPixelSize(R.dimen.split_placeholder_icon_size));
         mSplitPlaceholderView.getIconView().setRotation(mOrientationHandler.getDegreesRotated());
@@ -209,8 +210,50 @@
         layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
     }
 
-    public void addAnimation(PendingAnimation animation, RectF startingBounds, Rect endBounds,
-            boolean fadeWithThumbnail, boolean isStagedTask) {
+    /**
+     * Animates a FloatingTaskThumbnailView and its overlapping SplitPlaceholderView when a split
+     * is staged.
+     */
+    public void addStagingAnimation(PendingAnimation animation, RectF startingBounds,
+            Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask) {
+        boolean isTablet = mActivity.getDeviceProfile().isTablet;
+        boolean splittingFromOverview = fadeWithThumbnail;
+        SplitAnimationTimings timings;
+
+        if (isTablet && splittingFromOverview) {
+            timings = SplitAnimationTimings.TABLET_OVERVIEW_TO_SPLIT;
+        } else if (!isTablet && splittingFromOverview) {
+            timings = SplitAnimationTimings.PHONE_OVERVIEW_TO_SPLIT;
+        } else {
+            // Splitting from Home is currently only available on tablets
+            timings = SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
+        }
+
+        addAnimation(animation, startingBounds, endBounds, fadeWithThumbnail, isStagedTask,
+                timings);
+    }
+
+    /**
+     * Animates the FloatingTaskThumbnailView and SplitPlaceholderView for the two thumbnails
+     * when a split is confirmed.
+     */
+    public void addConfirmAnimation(PendingAnimation animation, RectF startingBounds,
+            Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask) {
+        SplitAnimationTimings timings =
+                AnimUtils.getDeviceSplitToConfirmTimings(mActivity.getDeviceProfile().isTablet);
+
+        addAnimation(animation, startingBounds, endBounds, fadeWithThumbnail, isStagedTask,
+                timings);
+    }
+
+    /**
+     * Sets up and builds a split staging animation.
+     * Called by {@link #addStagingAnimation(PendingAnimation, RectF, Rect, boolean, boolean)} and
+     * {@link #addConfirmAnimation(PendingAnimation, RectF, Rect, boolean, boolean)}.
+     */
+    public void addAnimation(PendingAnimation animation, RectF startingBounds,
+            Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask,
+            SplitAnimationTimings timings) {
         mFullscreenParams.setIsStagedTask(isStagedTask);
         final BaseDragLayer dragLayer = mActivity.getDragLayer();
         int[] dragLayerBounds = new int[2];
@@ -224,26 +267,47 @@
         RectF floatingTaskViewBounds = new RectF();
 
         if (fadeWithThumbnail) {
-            animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ALPHA_FLOAT,
-                    0, 1, ACCEL);
-            animation.addFloat(mThumbnailView, LauncherAnimUtils.VIEW_ALPHA,
-                    1, 0, DEACCEL_3);
+            // This code block runs for the placeholder view during Overview > OverviewSplitSelect
+            // and for the selected (secondary) thumbnail during OverviewSplitSelect > Confirmed
+
+            // FloatingTaskThumbnailView: thumbnail fades out to transparent
+            animation.setViewAlpha(mThumbnailView, 0, clampToProgress(LINEAR,
+                    timings.getPlaceholderFadeInStartOffset(),
+                    timings.getPlaceholderFadeInEndOffset()));
+
+            // SplitPlaceholderView: gray background fades in at same time, then new icon fades in
+            fadeInSplitPlaceholder(animation, timings);
         } else if (isStagedTask) {
-            // Fade in the placeholder view when split is initiated from homescreen / all apps
-            // icons.
+            // This code block runs for the placeholder view during Normal > OverviewSplitSelect
+            // and for the placeholder (primary) thumbnail during OverviewSplitSelect > Confirmed
+
+            // Fade in the placeholder view during Normal > OverviewSplitSelect
             if (mSplitPlaceholderView.getAlpha() == 0) {
-                animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ALPHA_FLOAT,
-                        0.3f, 1, ACCEL);
+                mSplitPlaceholderView.getIconView().setAlpha(0);
+                fadeInSplitPlaceholder(animation, timings);
             }
+
+            // No-op for placeholder during OverviewSplitSelect > Confirmed, alpha should be set
         }
 
         MultiValueUpdateListener listener = new MultiValueUpdateListener() {
-            final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration, LINEAR);
-            final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration, LINEAR);
+            // SplitPlaceholderView: rectangle translates and stretches to new position
+            final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration,
+                    clampToProgress(timings.getStagedRectXInterpolator(),
+                            timings.getStagedRectSlideStartOffset(),
+                            timings.getStagedRectSlideEndOffset()));
+            final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration,
+                    clampToProgress(timings.getStagedRectYInterpolator(),
+                            timings.getStagedRectSlideStartOffset(),
+                            timings.getStagedRectSlideEndOffset()));
             final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX, 0,
-                    animDuration, LINEAR);
+                    animDuration, clampToProgress(timings.getStagedRectScaleXInterpolator(),
+                    timings.getStagedRectSlideStartOffset(),
+                    timings.getStagedRectSlideEndOffset()));
             final FloatProp mTaskViewScaleY = new FloatProp(1f, prop.finalTaskViewScaleY, 0,
-                    animDuration, LINEAR);
+                    animDuration, clampToProgress(timings.getStagedRectScaleYInterpolator(),
+                    timings.getStagedRectSlideStartOffset(),
+                    timings.getStagedRectSlideEndOffset()));
             @Override
             public void onUpdate(float percent, boolean initOnly) {
                 // Calculate the icon position.
@@ -255,9 +319,19 @@
                 update(floatingTaskViewBounds, percent);
             }
         };
+
         transitionAnimator.addUpdateListener(listener);
     }
 
+    void fadeInSplitPlaceholder(PendingAnimation animation, SplitAnimationTimings timings) {
+        animation.setViewAlpha(mSplitPlaceholderView, 1, clampToProgress(LINEAR,
+                timings.getPlaceholderFadeInStartOffset(),
+                timings.getPlaceholderFadeInEndOffset()));
+        animation.setViewAlpha(mSplitPlaceholderView.getIconView(), 1, clampToProgress(LINEAR,
+                timings.getPlaceholderIconFadeInStartOffset(),
+                timings.getPlaceholderIconFadeInEndOffset()));
+    }
+
     void drawRoundedRect(Canvas canvas, Paint paint) {
         if (mFullscreenParams == null) {
             return;
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 0bed51d..71b0c60 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -1,5 +1,6 @@
 package com.android.quickstep.views;
 
+import static com.android.launcher3.AbstractFloatingView.getAnyView;
 import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
@@ -13,6 +14,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -162,6 +164,21 @@
         }
     }
 
+    @Override
+    protected boolean showTaskMenuWithContainer(IconView iconView) {
+        boolean showedTaskMenu = super.showTaskMenuWithContainer(iconView);
+        if (iconView == mIconView2 && showedTaskMenu && !mActivity.getDeviceProfile().isTablet) {
+            // Adjust the position of the secondary task's menu view (only on phones)
+            TaskMenuView taskMenuView = getAnyView(mActivity, AbstractFloatingView.TYPE_TASK_MENU);
+            DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+            getRecentsView().getPagedOrientationHandler()
+                    .setSecondaryTaskMenuPosition(mSplitBoundsConfig, this,
+                            deviceProfile, mTaskIdAttributeContainer[0].getThumbnailView(),
+                            taskMenuView);
+        }
+        return showedTaskMenu;
+    }
+
     @Nullable
     @Override
     public RunnableList launchTaskAnimated() {
@@ -174,7 +191,7 @@
         // Callbacks run from remote animation when recents animation not currently running
         InteractionJankMonitorWrapper.begin(this,
                 InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Enter form GroupedTaskView");
-        recentsView.getSplitPlaceholder().launchTasks(this /*groupedTaskView*/,
+        recentsView.getSplitSelectController().launchTasks(this /*groupedTaskView*/,
                 success -> {
                     endCallback.executeAllAndDestroy();
                     InteractionJankMonitorWrapper.end(
@@ -189,7 +206,7 @@
 
     @Override
     public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
-        getRecentsView().getSplitPlaceholder().launchTasks(mTask.key.id, mSecondaryTask.key.id,
+        getRecentsView().getSplitSelectController().launchTasks(mTask.key.id, mSecondaryTask.key.id,
                 STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList, getSplitRatio());
     }
 
@@ -208,16 +225,23 @@
     }
 
     @Override
+    public boolean containsTaskId(int taskId) {
+        return (mTask != null && mTask.key.id == taskId)
+                || (mSecondaryTask != null && mSecondaryTask.key.id == taskId);
+    }
+
+    @Override
     public TaskThumbnailView[] getThumbnails() {
         return new TaskThumbnailView[]{mSnapshotView, mSnapshotView2};
     }
 
     @Override
-    protected int getChildTaskIndexAtPosition(PointF position) {
-        if (isCoordInView(mIconView2, position) || isCoordInView(mSnapshotView2, position)) {
+    protected int getLastSelectedChildTaskIndex() {
+        if (isCoordInView(mIconView2, mLastTouchDownPosition)
+                || isCoordInView(mSnapshotView2, mLastTouchDownPosition)) {
             return 1;
         }
-        return super.getChildTaskIndexAtPosition(position);
+        return super.getLastSelectedChildTaskIndex();
     }
 
     private boolean isCoordInView(View v, PointF position) {
@@ -319,9 +343,26 @@
         mSnapshotView2.setSplashAlpha(mTaskThumbnailSplashAlpha);
     }
 
+    /**
+     *     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.
+     */
     @Override
     void setThumbnailVisibility(int visibility) {
-        super.setThumbnailVisibility(visibility);
-        mSnapshotView2.setVisibility(visibility);
+        if (visibility == VISIBLE) {
+            mSnapshotView.setVisibility(visibility);
+            mDigitalWellBeingToast.setBannerVisibility(visibility);
+            mSnapshotView2.setVisibility(visibility);
+            mDigitalWellBeingToast2.setBannerVisibility(visibility);
+        } else if (getLastSelectedChildTaskIndex() == 0) {
+            mSnapshotView.setVisibility(visibility);
+            mDigitalWellBeingToast.setBannerVisibility(visibility);
+        } else {
+            mSnapshotView2.setVisibility(visibility);
+            mDigitalWellBeingToast2.setBannerVisibility(visibility);
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index de7ccad..bb8506d 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -33,6 +33,7 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.popup.QuickstepSystemShortcut;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateListener;
@@ -94,7 +95,7 @@
             if (recoveryData.getStagedTaskId() == taskId) {
                 initiateSplitSelect(
                         getTaskViewByTaskId(recoveryData.getStagedTaskId()),
-                        recoveryData.getStagePosition()
+                        recoveryData.getStagePosition(), recoveryData.getSource()
                 );
                 mActivity.finishSplitSelectRecovery();
             }
@@ -104,7 +105,6 @@
     @Override
     public void reset() {
         super.reset();
-
         setLayoutRotation(Surface.ROTATION_0, Surface.ROTATION_0);
     }
 
@@ -120,11 +120,6 @@
     }
 
     @Override
-    public void onStateTransitionFailed(LauncherState toState) {
-        reset();
-    }
-
-    @Override
     public void onStateTransitionComplete(LauncherState finalState) {
         if (finalState == NORMAL || finalState == SPRING_LOADED) {
             // Clean-up logic that occurs when recents is no longer in use/visible.
@@ -192,8 +187,9 @@
 
     @Override
     public void initiateSplitSelect(TaskView taskView,
-            @SplitConfigurationOptions.StagePosition int stagePosition) {
-        super.initiateSplitSelect(taskView, stagePosition);
+            @SplitConfigurationOptions.StagePosition int stagePosition,
+            StatsLogManager.EventEnum splitEvent) {
+        super.initiateSplitSelect(taskView, stagePosition, splitEvent);
         mActivity.getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 08a17c4..514d5b9 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -31,6 +31,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
@@ -82,10 +83,11 @@
     private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3;
     private static final int INDEX_SHARE_TARGET_ALPHA = 4;
 
-    public @interface SplitButtonDisabledFlags { }
-
+    public @interface SplitButtonHiddenFlags { }
     public static final int FLAG_IS_NOT_TABLET = 1 << 0;
-    public static final int FLAG_SINGLE_TASK = 1 << 1;
+
+    public @interface SplitButtonDisabledFlags { }
+    public static final int FLAG_SINGLE_TASK = 1 << 0;
 
     private MultiValueAlpha mMultiValueAlpha;
     private Button mSplitButton;
@@ -96,6 +98,9 @@
     @ActionsDisabledFlags
     protected int mDisabledFlags;
 
+    @SplitButtonHiddenFlags
+    private int mSplitButtonHiddenFlags;
+
     @SplitButtonDisabledFlags
     private int mSplitButtonDisabledFlags;
 
@@ -191,20 +196,40 @@
         }
         boolean isEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
         LayoutUtils.setViewEnabled(this, isEnabled);
+        updateSplitButtonEnabledState();
     }
 
     /**
-     * Updates the proper flags to indicate whether the "Split screen" button should be enabled.
+     * Updates the proper flags to indicate whether the "Split screen" button should be hidden.
      *
-     * @param flag          The flag to update.
-     * @param enable        Whether to enable the disable flag: True will cause view to be disabled.
+     * @param flag   The flag to update.
+     * @param enable Whether to enable the hidden flag: True will cause view to be hidden.
      */
-    public void updateSplitButtonFlags(@SplitButtonDisabledFlags int flag, boolean enable) {
+    public void updateSplitButtonHiddenFlags(@SplitButtonHiddenFlags int flag, boolean enable) {
+        if (enable) {
+            mSplitButtonHiddenFlags |= flag;
+        } else {
+            mSplitButtonHiddenFlags &= ~flag;
+        }
+        if (mSplitButton == null) return;
+        boolean shouldBeVisible = mSplitButtonHiddenFlags == 0;
+        mSplitButton.setVisibility(shouldBeVisible ? VISIBLE : GONE);
+        findViewById(R.id.action_split_space).setVisibility(shouldBeVisible ? VISIBLE : GONE);
+    }
+
+    /**
+     * Updates the proper flags to indicate whether the "Split screen" button should be disabled.
+     *
+     * @param flag   The flag to update.
+     * @param enable Whether to enable the disable flag: True will cause view to be disabled.
+     */
+    public void updateSplitButtonDisabledFlags(@SplitButtonDisabledFlags int flag, boolean enable) {
         if (enable) {
             mSplitButtonDisabledFlags |= flag;
         } else {
             mSplitButtonDisabledFlags &= ~flag;
         }
+        updateSplitButtonEnabledState();
     }
 
     public AlphaProperty getContentAlpha() {
@@ -234,7 +259,9 @@
         // If in 3-button mode, shift action buttons to accommodate 3-button layout.
         // (Special exception for landscape tablets, where there is enough room and we don't need to
         // shift the action buttons.)
-        if (mDp.areNavButtonsInline && !largeScreenLandscape) {
+        if (mDp.areNavButtonsInline && !largeScreenLandscape
+                // If taskbar is in overview, overview action has dedicated space above nav buttons
+                && !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
             // Add extra horizontal spacing
             int additionalPadding = mDp.hotseatBarEndOffset;
             if (isLayoutRtl()) {
@@ -264,7 +291,8 @@
             return 0;
         }
 
-        if (!mDp.isGestureMode && mDp.isTaskbarPresent) {
+        if (!mDp.isGestureMode && mDp.isTaskbarPresent
+                && !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
             return mDp.getOverviewActionsClaimedSpaceBelow();
         }
 
@@ -289,16 +317,16 @@
     }
 
     /**
-     * Shows/hides the "Split" button based on the status of mHiddenFlags.
+     * Enables/disables the "Split" button based on the status of mSplitButtonDisabledFlags and
+     * mDisabledFlags.
      */
-    public void updateSplitButtonVisibility() {
+    private void updateSplitButtonEnabledState() {
         if (mSplitButton == null) {
             return;
         }
-        boolean shouldBeVisible = mSplitButtonDisabledFlags == 0
-                // and neither of these flags are active
-                && (mHiddenFlags & (HIDDEN_SPLIT_SCREEN | HIDDEN_SPLIT_SELECT_ACTIVE)) == 0;
-        mSplitButton.setVisibility(shouldBeVisible ? VISIBLE : GONE);
-        findViewById(R.id.action_split_space).setVisibility(shouldBeVisible ? VISIBLE : GONE);
+        boolean isParentEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
+        boolean shouldBeEnabled = mSplitButtonDisabledFlags == 0 && isParentEnabled;
+        mSplitButton.setEnabled(shouldBeEnabled);
     }
+
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index f8bf3d8..c8ef958 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -35,15 +35,17 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_0_75;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
-import static com.android.launcher3.statehandlers.DepthController.DEPTH;
+import static com.android.launcher3.statehandlers.DepthController.STATE_DEPTH;
 import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -52,6 +54,7 @@
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
+import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
 import static com.android.quickstep.views.OverviewActionsView.FLAG_IS_NOT_TABLET;
 import static com.android.quickstep.views.OverviewActionsView.FLAG_SINGLE_TASK;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
@@ -70,6 +73,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
+import android.app.WindowConfiguration;
 import android.content.Context;
 import android.content.LocusId;
 import android.content.res.Configuration;
@@ -132,6 +136,7 @@
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.cache.HandlerRunnable;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.popup.QuickstepSystemShortcut;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.BaseState;
@@ -165,9 +170,14 @@
 import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.TopTaskTracker;
 import com.android.quickstep.ViewUtils;
+import com.android.quickstep.util.ActiveGestureErrorDetector;
+import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.AnimUtils;
+import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.util.SplitScreenBounds;
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.SurfaceTransactionApplier;
@@ -188,6 +198,7 @@
 import com.android.wm.shell.pip.IPipAnimationListener;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
@@ -470,10 +481,11 @@
     private final InvariantDeviceProfile mIdp;
 
     /**
-     * Getting views should be done via {@link #getTaskViewFromPool(boolean)}
+     * Getting views should be done via {@link #getTaskViewFromPool(int)}
      */
     private final ViewPool<TaskView> mTaskViewPool;
     private final ViewPool<GroupedTaskView> mGroupedTaskViewPool;
+    private final ViewPool<DesktopTaskView> mDesktopTaskViewPool;
 
     private final TaskOverlayFactory mTaskOverlayFactory;
 
@@ -648,7 +660,7 @@
     @Nullable
     private TaskView mSplitHiddenTaskView;
     @Nullable
-    private View mSecondSplitHiddenView;
+    private TaskView mSecondSplitHiddenView;
     @Nullable
     private SplitBounds mSplitBoundsConfig;
     private final Toast mSplitToast = Toast.makeText(getContext(),
@@ -730,6 +742,8 @@
                 10 /* initial size */);
         mGroupedTaskViewPool = new ViewPool<>(context, this,
                 R.layout.task_grouped, 20 /* max size */, 10 /* initial size */);
+        mDesktopTaskViewPool = new ViewPool<>(context, this, R.layout.task_desktop,
+                5 /* max size */, 1 /* initial size */);
 
         mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
         setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
@@ -809,8 +823,7 @@
             }
             super.dispatchDraw(canvas);
         }
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
-                && mRemoteTargetHandles != null) {
+        if (mEnableDrawingLiveTile && mRemoteTargetHandles != null) {
             redrawLiveTile();
         }
     }
@@ -905,7 +918,7 @@
         mSplitSelectStateController = splitController;
     }
 
-    public SplitSelectStateController getSplitPlaceholder() {
+    public SplitSelectStateController getSplitSelectController() {
         return mSplitSelectStateController;
     }
 
@@ -933,7 +946,7 @@
                 .setSyncTransactionApplier(mSyncTransactionApplier));
         RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
         mIPipAnimationListener.setActivityAndRecentsView(mActivity, this);
-        SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
+        SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener(
                 mIPipAnimationListener);
         mOrientationState.initListeners();
         SplitScreenBounds.INSTANCE.addOnChangeListener(this);
@@ -952,7 +965,7 @@
                 .setSyncTransactionApplier(null));
         executeSideTaskLaunchCallback();
         RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
-        SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
+        SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener(null);
         SplitScreenBounds.INSTANCE.removeOnChangeListener(this);
         mIPipAnimationListener.setActivityAndRecentsView(null, null);
         mOrientationState.destroyListeners();
@@ -975,6 +988,8 @@
             }
             if (child instanceof GroupedTaskView) {
                 mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
+            } else if (child instanceof DesktopTaskView) {
+                mDesktopTaskViewPool.recycle((DesktopTaskView) taskView);
             } else {
                 mTaskViewPool.recycle(taskView);
             }
@@ -1193,8 +1208,7 @@
 
         for (int i = 0; i < getTaskViewCount(); i++) {
             TaskView taskView = requireTaskViewAt(i);
-            int[] taskIds = taskView.getTaskIds();
-            if (taskIds[0] == taskId || taskIds[1] == taskId) {
+            if (taskView.containsTaskId(taskId)) {
                 return taskView;
             }
         }
@@ -1236,6 +1250,8 @@
     @Override
     protected void onPageEndTransition() {
         super.onPageEndTransition();
+        ActiveGestureLog.INSTANCE.addLog(
+                "onPageEndTransition: current page index updated", getNextPage());
         if (isClearAllHidden() && !mActivity.getDeviceProfile().isTablet) {
             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
         }
@@ -1426,6 +1442,12 @@
         updateGridProperties();
     }
 
+    @Override
+    protected void onScrollerAnimationAborted() {
+        ActiveGestureLog.INSTANCE.addLog("scroller animation aborted",
+                ActiveGestureErrorDetector.GestureEvent.SCROLLER_ANIMATION_ABORTED);
+    }
+
     protected void applyLoadPlan(ArrayList<GroupTask> taskGroups) {
         if (mPendingAnimation != null) {
             mPendingAnimation.addEndListener(success -> applyLoadPlan(taskGroups));
@@ -1467,33 +1489,28 @@
         // Add views as children based on whether it's grouped or single task
         for (int i = taskGroups.size() - 1; i >= 0; i--) {
             GroupTask groupTask = taskGroups.get(i);
-            boolean hasMultipleTasks = groupTask.hasMultipleTasks();
-            TaskView taskView = getTaskViewFromPool(hasMultipleTasks);
+            TaskView taskView = getTaskViewFromPool(groupTask.taskViewType);
             addView(taskView);
 
-            if (hasMultipleTasks) {
+            if (taskView instanceof GroupedTaskView) {
                 boolean firstTaskIsLeftTopTask =
                         groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
                 Task leftTopTask = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
                 Task rightBottomTask = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
                 ((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
                         groupTask.mSplitBounds);
+            } else if (taskView instanceof DesktopTaskView) {
+                ((DesktopTaskView) taskView).bind(((DesktopTask) groupTask).tasks,
+                        mOrientationState);
             } else {
                 taskView.bind(groupTask.task1, mOrientationState);
             }
         }
+
         if (!taskGroups.isEmpty()) {
             addView(mClearAllButton);
         }
 
-        boolean settlingOnNewTask = mNextPage != INVALID_PAGE;
-        if (settlingOnNewTask) {
-            // Restore mCurrentPage but don't call setCurrentPage() as that clobbers the scroll.
-            mCurrentPage = previousCurrentPage;
-        } else {
-            setCurrentPage(previousCurrentPage);
-        }
-
         // Keep same previous focused task
         TaskView newFocusedTaskView = getTaskViewByTaskId(focusedTaskId);
         // If the list changed, maybe the focused task doesn't exist anymore
@@ -1518,21 +1535,36 @@
         }
 
         int targetPage = -1;
-        if (!settlingOnNewTask) {
+        if (mNextPage != INVALID_PAGE) {
+            // Restore mCurrentPage but don't call setCurrentPage() as that clobbers the scroll.
+            mCurrentPage = previousCurrentPage;
+            if (currentTaskId != -1) {
+                currentTaskView = getTaskViewByTaskId(currentTaskId);
+                if (currentTaskView != null) {
+                    targetPage = indexOfChild(currentTaskView);
+                }
+            }
+        } else {
             // Set the current page to the running task, but not if settling on new task.
             if (runningTaskId != -1) {
                 targetPage = indexOfChild(newRunningTaskView);
             } else if (getTaskViewCount() > 0) {
                 targetPage = indexOfChild(requireTaskViewAt(0));
             }
-        } else if (currentTaskId != -1) {
-            currentTaskView = getTaskViewByTaskId(currentTaskId);
-            if (currentTaskView != null) {
-                targetPage = indexOfChild(currentTaskView);
-            }
         }
         if (targetPage != -1 && mCurrentPage != targetPage) {
-            setCurrentPage(targetPage);
+            int finalTargetPage = targetPage;
+            runOnPageScrollsInitialized(() -> {
+                // TODO(b/246283207): Remove logging once root cause of flake detected.
+                if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                    Log.d("b/246283207", "RecentsView#applyLoadPlan() -> "
+                            + "previousCurrentPage: " + previousCurrentPage
+                            + ", targetPage: " + finalTargetPage
+                            + ", getScrollForPage(targetPage): "
+                            + getScrollForPage(finalTargetPage));
+                }
+                setCurrentPage(finalTargetPage);
+            });
         }
 
         if (mIgnoreResetTaskId != -1 &&
@@ -1613,22 +1645,20 @@
                 taskView.setTaskThumbnailSplashAlpha(mTaskThumbnailSplashAlpha);
             }
         }
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            // resetTaskVisuals is called at the end of dismiss animation which could update
-            // primary and secondary translation of the live tile cut out. We will need to do so
-            // here accordingly.
-            runActionOnRemoteHandles(remoteTargetHandle -> {
-                TaskViewSimulator simulator = remoteTargetHandle.getTaskViewSimulator();
-                simulator.taskPrimaryTranslation.value = 0;
-                simulator.taskSecondaryTranslation.value = 0;
-                simulator.fullScreenProgress.value = 0;
-                simulator.recentsViewScale.value = 1;
-            });
-            // Similar to setRunningTaskHidden below, reapply the state before runningTaskView is
-            // null.
-            if (!mRunningTaskShowScreenshot) {
-                setRunningTaskViewShowScreenshot(mRunningTaskShowScreenshot);
-            }
+        // resetTaskVisuals is called at the end of dismiss animation which could update
+        // primary and secondary translation of the live tile cut out. We will need to do so
+        // here accordingly.
+        runActionOnRemoteHandles(remoteTargetHandle -> {
+            TaskViewSimulator simulator = remoteTargetHandle.getTaskViewSimulator();
+            simulator.taskPrimaryTranslation.value = 0;
+            simulator.taskSecondaryTranslation.value = 0;
+            simulator.fullScreenProgress.value = 0;
+            simulator.recentsViewScale.value = 1;
+        });
+        // Similar to setRunningTaskHidden below, reapply the state before runningTaskView is
+        // null.
+        if (!mRunningTaskShowScreenshot) {
+            setRunningTaskViewShowScreenshot(mRunningTaskShowScreenshot);
         }
         if (mRunningTaskTileHidden) {
             setRunningTaskHidden(mRunningTaskTileHidden);
@@ -1708,6 +1738,7 @@
             // Changed orientations, update controllers so they intercept accordingly.
             mActivity.getDragLayer().recreateControllers();
             onOrientationChanged();
+            resetTaskVisuals();
         }
 
         boolean isInLandscape = mOrientationState.getTouchRotation() != ROTATION_0
@@ -1865,6 +1896,8 @@
                 mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, alphaValue);
         mActionsViewAlphaAnimatorFinalValue = alphaValue;
         mActionsViewAlphaAnimator.setDuration(duration);
+        // Set autocancel to prevent race-conditiony setting of alpha from other animations
+        mActionsViewAlphaAnimator.setAutoCancel(true);
         mActionsViewAlphaAnimator.start();
     }
 
@@ -2020,7 +2053,7 @@
         mFocusedTaskViewId = -1;
 
         if (mRecentsAnimationController != null) {
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) {
+            if (mEnableDrawingLiveTile) {
                 // We are still drawing the live tile, finish it now to clean up.
                 finishRecentsAnimation(true /* toRecents */, null);
             } else {
@@ -2101,10 +2134,19 @@
      * Handle the edge case where Recents could increment task count very high over long
      * period of device usage. Probably will never happen, but meh.
      */
-    private <T extends TaskView> T getTaskViewFromPool(boolean isGrouped) {
-        T taskView = isGrouped ?
-                (T) mGroupedTaskViewPool.getView() :
-                (T) mTaskViewPool.getView();
+    private TaskView getTaskViewFromPool(@TaskView.Type int type) {
+        TaskView taskView;
+        switch (type) {
+            case TaskView.Type.GROUPED:
+                taskView = mGroupedTaskViewPool.getView();
+                break;
+            case TaskView.Type.DESKTOP:
+                taskView = mDesktopTaskViewPool.getView();
+                break;
+            case TaskView.Type.SINGLE:
+            default:
+                taskView = mTaskViewPool.getView();
+        }
         taskView.setTaskViewId(mTaskViewIdCount);
         if (mTaskViewIdCount == Integer.MAX_VALUE) {
             mTaskViewIdCount = 0;
@@ -2259,9 +2301,6 @@
 
         setEnableFreeScroll(true);
         setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS);
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            setRunningTaskViewShowScreenshot(true);
-        }
         setRunningTaskHidden(false);
         animateUpTaskIconScale();
         animateActionsViewIn();
@@ -2299,12 +2338,19 @@
         }
         int runningTaskViewId = -1;
         boolean needGroupTaskView = runningTasks.length > 1;
+        boolean needDesktopTask = hasDesktopTask(runningTasks);
         if (shouldAddStubTaskView(runningTasks)) {
             boolean wasEmpty = getChildCount() == 0;
             // Add an empty view for now until the task plan is loaded and applied
             final TaskView taskView;
-            if (needGroupTaskView) {
-                taskView = getTaskViewFromPool(true);
+            if (needDesktopTask) {
+                taskView = getTaskViewFromPool(TaskView.Type.DESKTOP);
+                mTmpRunningTasks = Arrays.copyOf(runningTasks, runningTasks.length);
+                addView(taskView, 0);
+                ((DesktopTaskView) taskView).bind(Arrays.asList(mTmpRunningTasks),
+                        mOrientationState);
+            } else if (needGroupTaskView) {
+                taskView = getTaskViewFromPool(TaskView.Type.GROUPED);
                 mTmpRunningTasks = new Task[]{runningTasks[0], runningTasks[1]};
                 addView(taskView, 0);
                 // When we create a placeholder task view mSplitBoundsConfig will be null, but with
@@ -2313,7 +2359,7 @@
                 ((GroupedTaskView)taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1],
                         mOrientationState, mSplitBoundsConfig);
             } else {
-                taskView = getTaskViewFromPool(false);
+                taskView = getTaskViewFromPool(TaskView.Type.SINGLE);
                 addView(taskView, 0);
                 // The temporary running task is only used for the duration between the start of the
                 // gesture and the task list is loaded and applied
@@ -2348,6 +2394,18 @@
         reloadIfNeeded();
     }
 
+    private boolean hasDesktopTask(Task[] runningTasks) {
+        if (!DESKTOP_MODE_SUPPORTED) {
+            return false;
+        }
+        for (Task task : runningTasks) {
+            if (task.key.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Sets the running task id, cleaning up the old running task if necessary.
      */
@@ -2386,12 +2444,10 @@
     }
 
     private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mRunningTaskShowScreenshot = showScreenshot;
-            TaskView runningTaskView = getRunningTaskView();
-            if (runningTaskView != null) {
-                runningTaskView.setShowScreenshot(mRunningTaskShowScreenshot);
-            }
+        mRunningTaskShowScreenshot = showScreenshot;
+        TaskView runningTaskView = getRunningTaskView();
+        if (runningTaskView != null) {
+            runningTaskView.setShowScreenshot(mRunningTaskShowScreenshot);
         }
     }
 
@@ -2760,7 +2816,7 @@
         anim.setFloat(taskView, VIEW_ALPHA, 0,
                 clampToProgress(isOnGridBottomRow(taskView) ? ACCEL : FINAL_FRAME, 0, 0.5f));
         FloatProperty<TaskView> secondaryViewTranslate =
-                taskView.getSecondaryDissmissTranslationProperty();
+                taskView.getSecondaryDismissTranslationProperty();
         int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
         int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
 
@@ -2772,8 +2828,7 @@
         anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate,
                 verticalFactor * secondaryTaskDimension * 2).setDuration(duration), LINEAR, sp);
 
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
-                && taskView.isRunningTask()) {
+        if (mEnableDrawingLiveTile && taskView.isRunningTask()) {
             anim.addOnFrameCallback(() -> {
                 runActionOnRemoteHandles(
                         remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
@@ -2794,33 +2849,48 @@
         mOrientationHandler.getInitialSplitPlaceholderBounds(mSplitPlaceholderSize,
                 mSplitPlaceholderInset, mActivity.getDeviceProfile(),
                 mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
+        SplitAnimationTimings timings =
+                AnimUtils.getDeviceOverviewToSplitTimings(mActivity.getDeviceProfile().isTablet);
 
         RectF startingTaskRect = new RectF();
+        safeRemoveDragLayerView(mFirstFloatingTaskView);
         if (mSplitHiddenTaskView != null) {
-            // Split staging is initiated, hide the original TaskView thumbnail.
-            // Toggled back on in resetFromSplitSelectionState().
+            // Create the split select animation from Overview
             mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE);
-            anim.addFloat(mSplitHiddenTaskView, TaskView.ICON_ALPHA, 1, 0,
-                    clampToProgress(LINEAR, 0, 0.167f));
+            anim.setViewAlpha(mSplitHiddenTaskView.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.addAnimation(anim, startingTaskRect, mTempRect,
+            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.addAnimation(anim, startingTaskRect, mTempRect,
+            mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
                     false /* fadeWithThumbnail */, true /* isStagedTask */);
         }
 
+        // SplitInstructionsView: animate in
+        safeRemoveDragLayerView(mSplitInstructionsView);
         mSplitInstructionsView = SplitInstructionsView.getSplitInstructionsView(mActivity);
         mSplitInstructionsView.setAlpha(0);
-        anim.addFloat(mSplitInstructionsView, SplitInstructionsView.ALPHA_FLOAT, 0, 1, ACCEL);
+        anim.setViewAlpha(mSplitInstructionsView, 1, clampToProgress(LINEAR,
+                timings.getInstructionsContainerFadeInStartOffset(),
+                timings.getInstructionsContainerFadeInEndOffset()));
+        anim.setViewAlpha(mSplitInstructionsView.getTextView(), 1, clampToProgress(LINEAR,
+                timings.getInstructionsTextFadeInStartOffset(),
+                timings.getInstructionsTextFadeInEndOffset()));
+        anim.addFloat(mSplitInstructionsView, mSplitInstructionsView.UNFOLD, 0.1f, 1,
+                clampToProgress(EMPHASIZED_DECELERATE,
+                        timings.getInstructionsUnfoldStartOffset(),
+                        timings.getInstructionsUnfoldEndOffset()));
 
         InteractionJankMonitorWrapper.begin(this,
                 InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "First tile selected");
@@ -2847,17 +2917,16 @@
      * @param dismissingForSplitSelection task dismiss animation is used for entering split
      *                                    selection state from app icon
      */
-    public PendingAnimation createTaskDismissAnimation(TaskView dismissedTaskView,
+    public void createTaskDismissAnimation(PendingAnimation anim, TaskView dismissedTaskView,
             boolean animateTaskView, boolean shouldRemoveTask, long duration,
             boolean dismissingForSplitSelection) {
         if (mPendingAnimation != null) {
             mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd();
         }
-        PendingAnimation anim = new PendingAnimation(duration);
 
         int count = getPageCount();
         if (count == 0) {
-            return anim;
+            return;
         }
 
         boolean showAsGrid = showAsGrid();
@@ -2998,8 +3067,7 @@
                             dismissTranslationInterpolationEnd
                                     - halfAdditionalDismissTranslationOffset,
                             END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1);
-                    if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
-                            && taskView.isRunningTask()) {
+                    if (mEnableDrawingLiveTile && taskView.isRunningTask()) {
                         anim.addOnFrameCallback(() -> {
                             runActionOnRemoteHandles(
                                     remoteTargetHandle ->
@@ -3025,6 +3093,9 @@
             }
         }
 
+        SplitAnimationTimings splitTimings =
+                AnimUtils.getDeviceOverviewToSplitTimings(mActivity.getDeviceProfile().isTablet);
+
         int distanceFromDismissedTask = 0;
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
@@ -3067,11 +3138,31 @@
                     float additionalDismissDuration =
                             ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * Math.abs(
                                     i - dismissedIndex);
-                    anim.setFloat(child, translationProperty, scrollDiff, clampToProgress(LINEAR,
-                            Utilities.boundToRange(INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                    + additionalDismissDuration, 0f, 1f), 1));
-                    if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
-                            && child instanceof TaskView
+
+                    // We are in non-grid layout.
+                    // If dismissing for split select, use split timings.
+                    // If not, use dismiss timings.
+                    float animationStartProgress = isSplitSelectionActive()
+                            ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset(), 0f, 1f)
+                            : Utilities.boundToRange(
+                                    INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+                                            + additionalDismissDuration, 0f, 1f);
+
+                    float animationEndProgress = isSplitSelectionActive()
+                            ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset()
+                                            + splitTimings.getGridSlideDurationOffset(), 0f, 1f)
+                            : 1f;
+
+                    // Slide tiles in horizontally to fill dismissed area
+                    anim.setFloat(child, translationProperty, scrollDiff,
+                            clampToProgress(
+                                    splitTimings.getGridSlidePrimaryInterpolator(),
+                                    animationStartProgress,
+                                    animationEndProgress
+                            )
+                    );
+
+                    if (mEnableDrawingLiveTile && child instanceof TaskView
                             && ((TaskView) child).isRunningTask()) {
                         anim.addOnFrameCallback(() -> {
                             runActionOnRemoteHandles(
@@ -3102,11 +3193,35 @@
                 // Animate task with index >= dismissed index and in the same row as the
                 // dismissed index or next focused index. Offset successive task dismissal
                 // durations for a staggered effect.
-                float animationStartProgress = Utilities.boundToRange(
-                        INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                * ++distanceFromDismissedTask, 0f,
-                        dismissTranslationInterpolationEnd);
+                distanceFromDismissedTask++;
+                boolean isStagingFocusedTask =
+                        isFocusedTaskDismissed && nextFocusedTaskView == null;
+                int staggerColumn =  isStagingFocusedTask
+                        ? (int) Math.ceil(distanceFromDismissedTask / 2f)
+                        : distanceFromDismissedTask;
+                // Set timings based on if user is initiating splitscreen on the focused task,
+                // or splitting/dismissing some other task.
+                float animationStartProgress = isStagingFocusedTask
+                        ? Utilities.boundToRange(
+                                splitTimings.getGridSlideStartOffset()
+                                        + (splitTimings.getGridSlideStaggerOffset()
+                                        * staggerColumn),
+                        0f,
+                        dismissTranslationInterpolationEnd)
+                        : Utilities.boundToRange(
+                                INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+                                        + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+                                        * staggerColumn, 0f, dismissTranslationInterpolationEnd);
+                float animationEndProgress = isStagingFocusedTask
+                        ? Utilities.boundToRange(
+                                splitTimings.getGridSlideStartOffset()
+                                        + (splitTimings.getGridSlideStaggerOffset() * staggerColumn)
+                                        + splitTimings.getGridSlideDurationOffset(),
+                        0f,
+                        dismissTranslationInterpolationEnd)
+                        : dismissTranslationInterpolationEnd;
+                Interpolator dismissInterpolator = isStagingFocusedTask ? OVERSHOOT_0_75 : LINEAR;
+
                 if (taskView == nextFocusedTaskView) {
                     // Enlarge the task to be focused next, and translate into focus position.
                     float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width();
@@ -3121,7 +3236,7 @@
                     if (!nextFocusedTaskFromTop) {
                         secondaryTranslation -= mTopBottomRowHeightDiff;
                     }
-                    anim.setFloat(taskView, taskView.getSecondaryDissmissTranslationProperty(),
+                    anim.setFloat(taskView, taskView.getSecondaryDismissTranslationProperty(),
                             secondaryTranslation, clampToProgress(LINEAR, animationStartProgress,
                                     dismissTranslationInterpolationEnd));
                     anim.setFloat(taskView, TaskView.FOCUS_TRANSITION, 0f,
@@ -3129,7 +3244,7 @@
                 } else {
                     float primaryTranslation =
                             nextFocusedTaskView != null ? nextFocusedTaskWidth : dismissedTaskWidth;
-                    if (isFocusedTaskDismissed && nextFocusedTaskView == null) {
+                    if (isStagingFocusedTask) {
                         // Moves less if focused task is not in scroll position.
                         int focusedTaskScroll = getScrollForPage(dismissedIndex);
                         int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
@@ -3145,8 +3260,8 @@
 
                     anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
                             mIsRtl ? primaryTranslation : -primaryTranslation,
-                            clampToProgress(LINEAR, animationStartProgress,
-                                    dismissTranslationInterpolationEnd));
+                            clampToProgress(dismissInterpolator, animationStartProgress,
+                                    animationEndProgress));
                 }
             }
         }
@@ -3170,8 +3285,7 @@
         mPendingAnimation.addEndListener(new Consumer<Boolean>() {
             @Override
             public void accept(Boolean success) {
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
-                        && dismissedTaskView.isRunningTask() && success) {
+                if (mEnableDrawingLiveTile && dismissedTaskView.isRunningTask() && success) {
                     finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
                             () -> onEnd(success));
                 } else {
@@ -3188,8 +3302,7 @@
                 if (success) {
                     if (shouldRemoveTask) {
                         if (dismissedTaskView.getTask() != null) {
-                            if (ENABLE_QUICKSTEP_LIVE_TILE.get()
-                                    && dismissedTaskView.isRunningTask()) {
+                            if (dismissedTaskView.isRunningTask()) {
                                 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
                                         () -> removeTaskInternal(dismissedTaskViewId));
                             } else {
@@ -3378,7 +3491,6 @@
                 mPendingAnimation = null;
             }
         });
-        return anim;
     }
 
     /**
@@ -3391,10 +3503,9 @@
         boolean isCurrentSplit = getCurrentPageTaskView() instanceof GroupedTaskView;
         mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit);
         mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive());
-        mActionsView.updateSplitButtonFlags(FLAG_IS_NOT_TABLET,
+        mActionsView.updateSplitButtonHiddenFlags(FLAG_IS_NOT_TABLET,
                 !mActivity.getDeviceProfile().isTablet);
-        mActionsView.updateSplitButtonFlags(FLAG_SINGLE_TASK, getTaskViewCount() <= 1);
-        mActionsView.updateSplitButtonVisibility();
+        mActionsView.updateSplitButtonDisabledFlags(FLAG_SINGLE_TASK, getTaskViewCount() <= 1);
     }
 
     /**
@@ -3546,8 +3657,10 @@
     }
 
     public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
-        runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask,
-                DISMISS_TASK_DURATION, false /* dismissingForSplitSelection*/));
+        PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION);
+        createTaskDismissAnimation(pa, taskView, animateTaskView, removeTask, DISMISS_TASK_DURATION,
+                false /* dismissingForSplitSelection*/);
+        runDismissAnimation(pa);
     }
 
     @SuppressWarnings("unused")
@@ -3847,8 +3960,7 @@
                     ? ((TaskView) child).getPrimaryTaskOffsetTranslationProperty()
                     : mOrientationHandler.getPrimaryViewTranslate();
             translationProperty.set(child, totalTranslation);
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
-                    && i == getRunningTaskIndex()) {
+            if (mEnableDrawingLiveTile && i == getRunningTaskIndex()) {
                 runActionOnRemoteHandles(
                         remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
                                 .taskPrimaryTranslation.value = totalTranslation);
@@ -4056,37 +4168,47 @@
         }
     }
 
+    /**
+     * Primarily used by overview actions to initiate split from focused task, logs the source
+     * of split invocation as such.
+     */
     public void initiateSplitSelect(TaskView taskView) {
         int defaultSplitPosition = mOrientationHandler
                 .getDefaultSplitPosition(mActivity.getDeviceProfile());
-        initiateSplitSelect(taskView, defaultSplitPosition);
+        initiateSplitSelect(taskView, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT);
     }
 
-    public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition) {
+    public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition,
+            StatsLogManager.EventEnum splitEvent) {
         mSplitHiddenTaskView = taskView;
         mSplitSelectStateController.setInitialTaskSelect(taskView.getTask().key.id,
-                stagePosition);
+                stagePosition, splitEvent, taskView.getItemInfo());
         mSplitHiddenTaskViewIndex = indexOfChild(taskView);
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
-                    null /* onFinishComplete */);
-        }
+        finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
+                null /* onFinishComplete */);
     }
 
+    /**
+     * Called when staging a split from Home/AllApps, using the icon long-press menu.
+     */
     public void initiateSplitSelect(QuickstepSystemShortcut.SplitSelectSource splitSelectSource) {
         mSplitSelectSource = splitSelectSource;
         mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
-                splitSelectSource.position.stagePosition, splitSelectSource.user);
+                splitSelectSource.position.stagePosition, splitSelectSource.mItemInfo,
+                splitSelectSource.splitEvent);
     }
 
-    public PendingAnimation createSplitSelectInitAnimation(int duration) {
+    /**
+     * Modifies a PendingAnimation with the animations for entering split staging
+     */
+    public void createSplitSelectInitAnimation(PendingAnimation builder, int duration) {
         if (mSplitHiddenTaskView != null) {
-            return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration,
+            // Splitting from Overview
+            createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration,
                     true /* dismissingForSplitSelection*/);
         } else {
-            PendingAnimation anim = new PendingAnimation(duration);
-            createInitialSplitSelectAnimation(anim);
-            return anim;
+            // Splitting from Home
+            createInitialSplitSelectAnimation(builder);
         }
     }
 
@@ -4117,9 +4239,13 @@
         // TODO(194414938) starting bounds seem slightly off, investigate
         Rect firstTaskStartingBounds = new Rect();
         Rect firstTaskEndingBounds = mTempRect;
-        int duration = mActivity.getStateManager().getState().getTransitionDuration(mActivity,
-                false /* isToState */);
+
+        boolean isTablet = mActivity.getDeviceProfile().isTablet;
+        int duration = isTablet
+                ? SplitAnimationTimings.TABLET_CONFIRM_DURATION
+                : SplitAnimationTimings.PHONE_CONFIRM_DURATION;
         PendingAnimation pendingAnimation = new PendingAnimation(duration);
+        SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet);
 
         int halfDividerSize = getResources()
                 .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
@@ -4129,28 +4255,31 @@
                 secondTaskEndingBounds);
 
         mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
-        mFirstFloatingTaskView.addAnimation(pendingAnimation,
+        mFirstFloatingTaskView.addConfirmAnimation(pendingAnimation,
                 new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
                 false /* fadeWithThumbnail */, true /* isStagedTask */);
 
+        safeRemoveDragLayerView(mSecondFloatingTaskView);
         mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
                 thumbnailView, thumbnailView.getThumbnail(),
                 iconView.getDrawable(), secondTaskStartingBounds);
         mSecondFloatingTaskView.setAlpha(1);
-        mSecondFloatingTaskView.addAnimation(pendingAnimation, secondTaskStartingBounds,
+        mSecondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds,
                 secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);
+
+        pendingAnimation.setViewAlpha(mSplitInstructionsView, 0, clampToProgress(LINEAR,
+                timings.getInstructionsFadeStartOffset(),
+                timings.getInstructionsFadeEndOffset()));
+
         pendingAnimation.addEndListener(aBoolean -> {
             mSplitSelectStateController.launchSplitTasks(
                     aBoolean1 -> RecentsView.this.resetFromSplitSelectionState());
             InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
         });
-        if (containerTaskView.containsMultipleTasks()) {
-            // If we are launching from a child task, then only hide the thumbnail itself
-            mSecondSplitHiddenView = thumbnailView;
-        } else {
-            mSecondSplitHiddenView = containerTaskView;
-        }
-        mSecondSplitHiddenView.setVisibility(INVISIBLE);
+
+        mSecondSplitHiddenView = containerTaskView;
+        mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE);
+
         InteractionJankMonitorWrapper.begin(this,
                 InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Second tile selected");
 
@@ -4165,23 +4294,20 @@
     @SuppressLint("WrongCall")
     protected void resetFromSplitSelectionState() {
         if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1) {
-            if (mSplitInstructionsView != null) {
-                mActivity.getDragLayer().removeView(mSplitInstructionsView);
-                mSplitInstructionsView = null;
-            }
-            if (mFirstFloatingTaskView != null) {
-                mActivity.getDragLayer().removeView(mFirstFloatingTaskView);
-                mFirstFloatingTaskView = null;
-            }
-            if (mSecondFloatingTaskView != null) {
-                mActivity.getDragLayer().removeView(mSecondFloatingTaskView);
-                mSecondFloatingTaskView = null;
-                mSecondSplitHiddenView.setVisibility(VISIBLE);
-                mSecondSplitHiddenView = null;
-            }
+            safeRemoveDragLayerView(mFirstFloatingTaskView);
+            safeRemoveDragLayerView(mSecondFloatingTaskView);
+            safeRemoveDragLayerView(mSplitInstructionsView);
+            mFirstFloatingTaskView = null;
+            mSecondFloatingTaskView = null;
+            mSplitInstructionsView = null;
             mSplitSelectSource = null;
         }
 
+        if (mSecondSplitHiddenView != null) {
+            mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE);
+            mSecondSplitHiddenView = null;
+        }
+
         if (mSplitHiddenTaskViewIndex == -1) {
             return;
         }
@@ -4195,22 +4321,32 @@
             snapToPageImmediately(pageToSnapTo);
         }
         onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
+
+        // We are leaving split selection state, so it is safe to reset thumbnail translations for
+        // the next time split is invoked.
+        setTaskViewsPrimarySplitTranslation(0);
+        setTaskViewsSecondarySplitTranslation(0);
+
         resetTaskVisuals();
         mSplitHiddenTaskViewIndex = -1;
         if (mSplitHiddenTaskView != null) {
-            // Toggle thumbnail visibility back on (turned off in
-            // createInitialSplitSelectAnimation()).
             mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE);
             mSplitHiddenTaskView = null;
         }
     }
 
+    private void safeRemoveDragLayerView(@Nullable View viewToRemove) {
+        if (viewToRemove != null) {
+            mActivity.getDragLayer().removeView(viewToRemove);
+        }
+    }
+
     /**
      * Returns how much additional translation there should be for each of the child TaskViews.
      * Note that the translation can be its primary or secondary dimension.
      */
     public float getSplitSelectTranslation() {
-        int splitPosition = getSplitPlaceholder().getActiveSplitStagePosition();
+        int splitPosition = getSplitSelectController().getActiveSplitStagePosition();
         if (!shouldShiftThumbnailsForSplitSelect()) {
             return 0f;
         }
@@ -4336,9 +4472,7 @@
             anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex),
                     mOrientationHandler.getPrimaryViewTranslate(), primaryTranslation));
             int runningTaskIndex = getRunningTaskIndex();
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()
-                    && runningTaskIndex != -1
-                    && runningTaskIndex != taskIndex
+            if (runningTaskIndex != -1 && runningTaskIndex != taskIndex
                     && getRemoteTargetHandles() != null) {
                 for (RemoteTargetHandle remoteHandle : getRemoteTargetHandles()) {
                     anim.play(ObjectAnimator.ofFloat(
@@ -4416,7 +4550,7 @@
 
         DepthController depthController = getDepthController();
         if (depthController != null) {
-            ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController, DEPTH,
+            ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController, STATE_DEPTH,
                     BACKGROUND_APP.getDepth(mActivity));
             anim.play(depthAnimator);
         }
@@ -4427,12 +4561,10 @@
 
         mPendingAnimation = new PendingAnimation(duration);
         mPendingAnimation.add(anim);
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            runActionOnRemoteHandles(
-                    remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
-                            .addOverviewToAppAnim(mPendingAnimation, interpolator));
-            mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
-        }
+        runActionOnRemoteHandles(
+                remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
+                        .addOverviewToAppAnim(mPendingAnimation, interpolator));
+        mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
         mPendingAnimation.addEndListener(isSuccess -> {
             if (isSuccess) {
                 if (tv.getTaskIds()[1] != -1 && mRemoteTargetHandles != null) {
@@ -4444,7 +4576,7 @@
                                 dividerAnimator.end();
                             });
                 }
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && tv.isRunningTask()) {
+                if (tv.isRunningTask()) {
                     finishRecentsAnimation(false /* toRecents */, null);
                     onTaskLaunchAnimationEnd(true /* success */);
                 } else {
@@ -4617,7 +4749,7 @@
         if (sendUserLeaveHint) {
             // Notify the SysUI to use fade-in animation when entering PiP from live tile.
             final SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(getContext());
-            systemUiProxy.notifySwipeToHomeFinished();
+            systemUiProxy.setPipAnimationTypeToAlpha();
             systemUiProxy.setShelfHeight(true, mActivity.getDeviceProfile().hotseatBarSizePx);
             // Transaction to hide the task to avoid flicker for entering PiP from split-screen.
             // See also {@link AbsSwipeUpHandler#maybeFinishSwipeToHome}.
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index bcaa462..b0b111d 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -25,9 +25,11 @@
 import android.widget.FrameLayout;
 
 import androidx.annotation.Nullable;
+import androidx.appcompat.widget.AppCompatTextView;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.util.DisplayController;
 
@@ -40,18 +42,18 @@
  */
 public class SplitInstructionsView extends FrameLayout {
     private final StatefulActivity mLauncher;
+    private AppCompatTextView mTextView;
 
-    public static final FloatProperty<SplitInstructionsView> ALPHA_FLOAT =
-            new FloatProperty<SplitInstructionsView>("SplitInstructionsAlpha") {
+    public static final FloatProperty<SplitInstructionsView> UNFOLD =
+            new FloatProperty<SplitInstructionsView>("SplitInstructionsUnfold") {
                 @Override
                 public void setValue(SplitInstructionsView splitInstructionsView, float v) {
-                    splitInstructionsView.setVisibility(v != 0 ? VISIBLE : GONE);
-                    splitInstructionsView.setAlpha(v);
+                    splitInstructionsView.setScaleY(v);
                 }
 
                 @Override
                 public Float get(SplitInstructionsView splitInstructionsView) {
-                    return splitInstructionsView.getAlpha();
+                    return splitInstructionsView.getScaleY();
                 }
             };
 
@@ -77,6 +79,14 @@
                         false
                 );
 
+        splitInstructionsView.mTextView = splitInstructionsView.findViewById(
+                R.id.split_instructions_text);
+
+        // Since textview overlays base view, and we sometimes manipulate the alpha of each
+        // simultaneously, force overlapping rendering to false prevents redrawing of pixels,
+        // improving performance at the cost of some accuracy.
+        splitInstructionsView.forceHasOverlappingRendering(false);
+
         dragLayer.addView(splitInstructionsView);
         return splitInstructionsView;
     }
@@ -105,11 +115,13 @@
     int getThreeButtonNavShift() {
         DeviceProfile dp = mLauncher.getDeviceProfile();
         if ((DisplayController.getNavigationMode(getContext()) == THREE_BUTTONS)
-                && ((dp.isTwoPanels) || (dp.isTablet && !dp.isLandscape))) {
+                && ((dp.isTwoPanels) || (dp.isTablet && !dp.isLandscape))
+                // If taskbar is in overview, overview action has dedicated space above nav buttons
+                && !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
             int navButtonWidth = getResources().getDimensionPixelSize(
                     R.dimen.taskbar_nav_buttons_size);
             int extraMargin = getResources().getDimensionPixelSize(
-                    R.dimen.taskbar_contextual_button_margin);
+                    R.dimen.taskbar_split_instructions_margin);
             // Explanation: The 3-button nav for non-phones sits on one side of the screen, taking
             // up 3 buttons + a side margin worth of space. Our splitInstructionsView starts in the
             // center of the screen and we want to center it in the remaining space, therefore we
@@ -120,4 +132,8 @@
             return 0;
         }
     }
+
+    public AppCompatTextView getTextView() {
+        return mTextView;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
index 28080d4..08004dc 100644
--- a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
@@ -22,7 +22,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.util.FloatProperty;
 import android.util.TypedValue;
 import android.widget.FrameLayout;
 
@@ -33,20 +32,6 @@
     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Rect mTempRect = new Rect();
 
-    public static final FloatProperty<SplitPlaceholderView> ALPHA_FLOAT =
-            new FloatProperty<SplitPlaceholderView>("SplitViewAlpha") {
-                @Override
-                public void setValue(SplitPlaceholderView splitPlaceholderView, float v) {
-                    splitPlaceholderView.setVisibility(v != 0 ? VISIBLE : GONE);
-                    splitPlaceholderView.setAlpha(v);
-                }
-
-                @Override
-                public Float get(SplitPlaceholderView splitPlaceholderView) {
-                    return splitPlaceholderView.getAlpha();
-                }
-            };
-
     @Nullable
     private IconView mIconView;
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index c1711d1..6815745 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,8 +16,6 @@
 
 package com.android.quickstep.views;
 
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
 import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
 
 import android.animation.Animator;
@@ -156,23 +154,6 @@
                 mTaskContainer.getThumbnailView(), overscrollShift, deviceProfile));
         setY(pagedOrientationHandler.getTaskMenuY(
                 adjustedY, mTaskContainer.getThumbnailView(), overscrollShift));
-
-        // TODO(b/193432925) temporary menu placement for split screen task menus
-        TaskIdAttributeContainer[] taskIdAttributeContainers =
-                mTaskView.getTaskIdAttributeContainers();
-        if (taskIdAttributeContainers[0].getStagePosition() != STAGE_POSITION_UNDEFINED) {
-            if (mTaskContainer.getStagePosition() != STAGE_POSITION_BOTTOM_OR_RIGHT) {
-                return;
-            }
-            Rect r = new Rect();
-            mTaskContainer.getThumbnailView().getBoundsOnScreen(r);
-            if (deviceProfile.isLandscape) {
-                setX(r.left);
-            } else {
-                setY(r.top);
-
-            }
-        }
     }
 
     public void onRotationChanged() {
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index f4b3d98..6792dc5 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -19,8 +19,8 @@
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
+import static com.android.systemui.shared.recents.utilities.PreviewPositionHelper.MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT;
+import static com.android.systemui.shared.recents.utilities.Utilities.isRelativePercentDifferenceGreaterThan;
 
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -41,7 +41,6 @@
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Property;
-import android.view.Surface;
 import android.view.View;
 import android.widget.ImageView;
 
@@ -60,6 +59,7 @@
 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
 
 /**
  * A task in the Recents view.
@@ -67,7 +67,6 @@
 public class TaskThumbnailView extends View {
     private static final MainThreadInitializedObject<FullscreenDrawParams> TEMP_PARAMS =
             new MainThreadInitializedObject<>(FullscreenDrawParams::new);
-    private static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f;
 
     public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
             new FloatProperty<TaskThumbnailView>("dimAlpha") {
@@ -320,13 +319,11 @@
 
     public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
             float cornerRadius) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
-                canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
-                canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
-                        mDimmingPaintAfterClearing);
-                return;
-            }
+        if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
+            canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
+            canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
+                    mDimmingPaintAfterClearing);
+            return;
         }
 
         // Always draw the background since the snapshots might be translucent or partially empty
@@ -373,7 +370,7 @@
      * <p>We want to show the splash if the aspect ratio or rotation of the thumbnail would be
      * different from the task.
      */
-    boolean shouldShowSplashView() {
+    public boolean shouldShowSplashView() {
         return isThumbnailAspectRatioDifferentFromThumbnailData()
                 || isThumbnailRotationDifferentFromTask();
     }
@@ -421,7 +418,7 @@
         float thumbnailDataAspect =
                 mThumbnailData.thumbnail.getWidth() / (float) mThumbnailData.thumbnail.getHeight();
 
-        return Utilities.isRelativePercentDifferenceGreaterThan(thumbnailViewAspect,
+        return isRelativePercentDifferenceGreaterThan(thumbnailViewAspect,
                 thumbnailDataAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
     }
 
@@ -445,8 +442,8 @@
      */
     private void refreshOverlay() {
         if (mOverlayEnabled) {
-            getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
-                    mPreviewPositionHelper.mIsOrientationChanged);
+            getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.getMatrix(),
+                    mPreviewPositionHelper.isOrientationChanged());
         } else {
             getTaskOverlay().reset();
         }
@@ -467,18 +464,19 @@
     }
 
     private void updateThumbnailMatrix() {
-        mPreviewPositionHelper.mIsOrientationChanged = false;
+        mPreviewPositionHelper.setOrientationChanged(false);
         if (mBitmapShader != null && mThumbnailData != null) {
             mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
                     mThumbnailData.thumbnail.getHeight());
             int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
                     .getRecentsActivityRotation();
             boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+            DeviceProfile dp = mActivity.getDeviceProfile();
             mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
-                    getMeasuredWidth(), getMeasuredHeight(), mActivity.getDeviceProfile(),
-                    currentRotation, isRtl);
+                    getMeasuredWidth(), getMeasuredHeight(), dp.widthPx, dp.taskbarSize,
+                    dp.isTablet, currentRotation, isRtl);
 
-            mBitmapShader.setLocalMatrix(mPreviewPositionHelper.mMatrix);
+            mBitmapShader.setLocalMatrix(mPreviewPositionHelper.getMatrix());
             mPaint.setShader(mBitmapShader);
         }
         getTaskView().updateCurrentFullscreenParams(mPreviewPositionHelper);
@@ -518,199 +516,4 @@
         }
         return mThumbnailData.isRealSnapshot && !mTask.isLocked;
     }
-
-    /**
-     * Utility class to position the thumbnail in the TaskView
-     */
-    public static class PreviewPositionHelper {
-
-        private static final RectF EMPTY_RECT_F = new RectF();
-
-        // Contains the portion of the thumbnail that is unclipped when fullscreen progress = 1.
-        private final RectF mClippedInsets = new RectF();
-        private final Matrix mMatrix = new Matrix();
-        private boolean mIsOrientationChanged;
-
-        public Matrix getMatrix() {
-            return mMatrix;
-        }
-
-        /**
-         * Updates the matrix based on the provided parameters
-         */
-        public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
-                int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation,
-                boolean isRtl) {
-            boolean isRotated = false;
-            boolean isOrientationDifferent;
-
-            int thumbnailRotation = thumbnailData.rotation;
-            int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
-            RectF thumbnailClipHint = new RectF();
-            float canvasScreenRatio = canvasWidth / (float) dp.widthPx;
-            float scaledTaskbarSize = dp.taskbarSize * canvasScreenRatio;
-            thumbnailClipHint.bottom = dp.isTablet ? scaledTaskbarSize : 0;
-
-            float scale = thumbnailData.scale;
-            final float thumbnailScale;
-
-            // Landscape vs portrait change.
-            // Note: Disable rotation in grid layout.
-            boolean windowingModeSupportsRotation =
-                    thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN && !dp.isTablet;
-            isOrientationDifferent = isOrientationChange(deltaRotate)
-                    && windowingModeSupportsRotation;
-            if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
-                // If we haven't measured , skip the thumbnail drawing and only draw the background
-                // color
-                thumbnailScale = 0f;
-            } else {
-                // Rotate the screenshot if not in multi-window mode
-                isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
-
-                float surfaceWidth = thumbnailBounds.width() / scale;
-                float surfaceHeight = thumbnailBounds.height() / scale;
-                float availableWidth = surfaceWidth
-                        - (thumbnailClipHint.left + thumbnailClipHint.right);
-                float availableHeight = surfaceHeight
-                        - (thumbnailClipHint.top + thumbnailClipHint.bottom);
-
-                float canvasAspect = canvasWidth / (float) canvasHeight;
-                float availableAspect = isRotated
-                        ? availableHeight / availableWidth
-                        : availableWidth / availableHeight;
-                boolean isAspectLargelyDifferent =
-                        Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect,
-                                availableAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
-                if (isRotated && isAspectLargelyDifferent) {
-                    // Do not rotate thumbnail if it would not improve fit
-                    isRotated = false;
-                    isOrientationDifferent = false;
-                }
-
-                if (isAspectLargelyDifferent) {
-                    // Crop letterbox insets if insets isn't already clipped
-                    thumbnailClipHint.left = thumbnailData.letterboxInsets.left;
-                    thumbnailClipHint.right = thumbnailData.letterboxInsets.right;
-                    thumbnailClipHint.top = thumbnailData.letterboxInsets.top;
-                    thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom;
-                    availableWidth = surfaceWidth
-                            - (thumbnailClipHint.left + thumbnailClipHint.right);
-                    availableHeight = surfaceHeight
-                            - (thumbnailClipHint.top + thumbnailClipHint.bottom);
-                }
-
-                final float targetW, targetH;
-                if (isOrientationDifferent) {
-                    targetW = canvasHeight;
-                    targetH = canvasWidth;
-                } else {
-                    targetW = canvasWidth;
-                    targetH = canvasHeight;
-                }
-                float targetAspect = targetW / targetH;
-
-                // Update the clipHint such that
-                //   > the final clipped position has same aspect ratio as requested by canvas
-                //   > first fit the width and crop the extra height
-                //   > if that will leave empty space, fit the height and crop the width instead
-                float croppedWidth = availableWidth;
-                float croppedHeight = croppedWidth / targetAspect;
-                if (croppedHeight > availableHeight) {
-                    croppedHeight = availableHeight;
-                    if (croppedHeight < targetH) {
-                        croppedHeight = Math.min(targetH, surfaceHeight);
-                    }
-                    croppedWidth = croppedHeight * targetAspect;
-
-                    // One last check in case the task aspect radio messed up something
-                    if (croppedWidth > surfaceWidth) {
-                        croppedWidth = surfaceWidth;
-                        croppedHeight = croppedWidth / targetAspect;
-                    }
-                }
-
-                // Update the clip hints. Align to 0,0, crop the remaining.
-                if (isRtl) {
-                    thumbnailClipHint.left += availableWidth - croppedWidth;
-                    if (thumbnailClipHint.right < 0) {
-                        thumbnailClipHint.left += thumbnailClipHint.right;
-                        thumbnailClipHint.right = 0;
-                    }
-                } else {
-                    thumbnailClipHint.right += availableWidth - croppedWidth;
-                    if (thumbnailClipHint.left < 0) {
-                        thumbnailClipHint.right += thumbnailClipHint.left;
-                        thumbnailClipHint.left = 0;
-                    }
-                }
-                thumbnailClipHint.bottom += availableHeight - croppedHeight;
-                if (thumbnailClipHint.top < 0) {
-                    thumbnailClipHint.bottom += thumbnailClipHint.top;
-                    thumbnailClipHint.top = 0;
-                } else if (thumbnailClipHint.bottom < 0) {
-                    thumbnailClipHint.top += thumbnailClipHint.bottom;
-                    thumbnailClipHint.bottom = 0;
-                }
-
-                thumbnailScale = targetW / (croppedWidth * scale);
-            }
-
-            if (!isRotated) {
-                mMatrix.setTranslate(
-                        -thumbnailClipHint.left * scale,
-                        -thumbnailClipHint.top * scale);
-            } else {
-                setThumbnailRotation(deltaRotate, thumbnailBounds);
-            }
-
-            mClippedInsets.set(0, 0, 0, scaledTaskbarSize);
-
-            mMatrix.postScale(thumbnailScale, thumbnailScale);
-            mIsOrientationChanged = isOrientationDifferent;
-        }
-
-        private int getRotationDelta(int oldRotation, int newRotation) {
-            int delta = newRotation - oldRotation;
-            if (delta < 0) delta += 4;
-            return delta;
-        }
-
-        /**
-         * @param deltaRotation the number of 90 degree turns from the current orientation
-         * @return {@code true} if the change in rotation results in a shift from landscape to
-         * portrait or vice versa, {@code false} otherwise
-         */
-        private boolean isOrientationChange(int deltaRotation) {
-            return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
-        }
-
-        private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) {
-            float translateX = 0;
-            float translateY = 0;
-
-            mMatrix.setRotate(90 * deltaRotate);
-            switch (deltaRotate) { /* Counter-clockwise */
-                case Surface.ROTATION_90:
-                    translateX = thumbnailPosition.height();
-                    break;
-                case Surface.ROTATION_270:
-                    translateY = thumbnailPosition.width();
-                    break;
-                case Surface.ROTATION_180:
-                    translateX = thumbnailPosition.width();
-                    translateY = thumbnailPosition.height();
-                    break;
-            }
-            mMatrix.postTranslate(translateX, translateY);
-        }
-
-        /**
-         * Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
-         */
-        public RectF getInsetsToDrawInFullscreen(DeviceProfile dp) {
-            return dp.isTaskbarPresent && !dp.isTaskbarPresentInApps
-                    ? mClippedInsets : EMPTY_RECT_F;
-        }
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index a84ea63..35f0f5d 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -18,20 +18,19 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.widget.Toast.LENGTH_SHORT;
-import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR;
 
 import static com.android.launcher3.Utilities.comp;
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
+import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -48,6 +47,7 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.Handler;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Log;
@@ -93,13 +93,13 @@
 import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.TaskCornerRadius;
 import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -119,6 +119,8 @@
     private static final String TAG = TaskView.class.getSimpleName();
     private static final boolean DEBUG = false;
 
+    private static final RectF EMPTY_RECT_F = new RectF();
+
     public static final int FLAG_UPDATE_ICON = 1;
     public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1;
 
@@ -132,6 +134,17 @@
     @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL})
     public @interface TaskDataChanges {}
 
+    /**
+     * Type of task view
+     */
+    @Retention(SOURCE)
+    @IntDef({Type.SINGLE, Type.GROUPED, Type.DESKTOP})
+    public @interface Type {
+        int SINGLE = 1;
+        int GROUPED = 2;
+        int DESKTOP = 3;
+    }
+
     /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
     public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
 
@@ -323,25 +336,12 @@
                 }
             };
 
-    public static final FloatProperty<TaskView> ICON_ALPHA =
-            new FloatProperty<TaskView>("iconAlpha") {
-                @Override
-                public void setValue(TaskView taskView, float v) {
-                    taskView.mIconView.setAlpha(v);
-                }
-
-                @Override
-                public Float get(TaskView taskView) {
-                    return taskView.mIconView.getAlpha();
-                }
-            };
-
     @Nullable
     protected Task mTask;
     protected TaskThumbnailView mSnapshotView;
     protected IconView mIconView;
     protected final DigitalWellBeingToast mDigitalWellBeingToast;
-    private float mFullscreenProgress;
+    protected float mFullscreenProgress;
     private float mGridProgress;
     protected float mTaskThumbnailSplashAlpha;
     private float mNonGridScale = 1;
@@ -383,8 +383,8 @@
     /**
      * Index 0 will contain taskID of left/top task, index 1 will contain taskId of bottom/right
      */
-    protected final int[] mTaskIdContainer = new int[]{-1, -1};
-    protected final TaskIdAttributeContainer[] mTaskIdAttributeContainer =
+    protected int[] mTaskIdContainer = new int[]{-1, -1};
+    protected TaskIdAttributeContainer[] mTaskIdAttributeContainer =
             new TaskIdAttributeContainer[2];
 
     private boolean mShowScreenshot;
@@ -399,7 +399,7 @@
 
     private final float[] mIconCenterCoords = new float[2];
 
-    private final PointF mLastTouchDownPosition = new PointF();
+    protected final PointF mLastTouchDownPosition = new PointF();
 
     private boolean mIsClickableAsLiveTile = true;
 
@@ -535,6 +535,13 @@
     }
 
     /**
+     * Check if given {@code taskId} is tracked in this view
+     */
+    public boolean containsTaskId(int taskId) {
+        return mTask != null && mTask.key.id == taskId;
+    }
+
+    /**
      * @return integer array of two elements to be size consistent with max number of tasks possible
      *         index 0 will contain the taskId, index 1 will be -1 indicating a null taskID value
      */
@@ -573,6 +580,18 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
+        RecentsView recentsView = getRecentsView();
+        if (recentsView == null || getTask() == null) {
+            return false;
+        }
+        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
+            return false;
+        }
+
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mLastTouchDownPosition.set(ev.getX(), ev.getY());
         }
@@ -595,17 +614,20 @@
      * @return {@code true} if user is already in split select mode and this tap was to choose the
      *         second app. {@code false} otherwise
      */
-    private boolean confirmSecondSplitSelectApp() {
-        int index = getChildTaskIndexAtPosition(mLastTouchDownPosition);
+    protected boolean confirmSecondSplitSelectApp() {
+        int index = getLastSelectedChildTaskIndex();
         TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
-        return getRecentsView().confirmSplitSelect(this, container.getTask(),
-                container.getIconView(), container.getThumbnailView());
+        if (container != null) {
+            return getRecentsView().confirmSplitSelect(this, container.getTask(),
+                    container.getIconView(), container.getThumbnailView());
+        }
+        return false;
     }
 
     /**
-     * Returns the task under the given position in the local coordinates of this task view.
+     * Returns the task index of the last selected child task (0 or 1).
      */
-    protected int getChildTaskIndexAtPosition(PointF position) {
+    protected int getLastSelectedChildTaskIndex() {
         return 0;
     }
 
@@ -624,7 +646,7 @@
             if (ActivityManagerWrapper.getInstance()
                     .startActivityFromRecents(mTask.key, opts.options)) {
                 RecentsView recentsView = getRecentsView();
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskViewId() != -1) {
+                if (recentsView.getRunningTaskViewId() != -1) {
                     recentsView.onTaskLaunchedInLiveTileMode();
 
                     // Return a fresh callback in the live tile case, so that it's not accidentally
@@ -659,16 +681,14 @@
                     TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
 
             // Indicate success once the system has indicated that the transition has started
-            ActivityOptions opts = ActivityOptionsCompat.makeCustomAnimation(
-                    getContext(), 0, 0, () -> callback.accept(true), MAIN_EXECUTOR.getHandler());
+            ActivityOptions opts = makeCustomAnimation(getContext(), 0, 0,
+                    () -> callback.accept(true), MAIN_EXECUTOR.getHandler());
             opts.setLaunchDisplayId(
                     getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
             if (freezeTaskList) {
-                ActivityOptionsCompat.setFreezeRecentTasksList(opts);
+                opts.setFreezeRecentTasksReordering();
             }
-            // TODO(b/202826469): Replace setSplashScreenStyle with setDisableStartingWindow.
-            opts.setSplashScreenStyle(mSnapshotView.shouldShowSplashView()
-                    ? SPLASH_SCREEN_STYLE_SOLID_COLOR : opts.getSplashScreenStyle());
+            opts.setDisableStartingWindow(mSnapshotView.shouldShowSplashView());
             Task.TaskKey key = mTask.key;
             UI_HELPER_EXECUTOR.execute(() -> {
                 if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(key, opts)) {
@@ -687,14 +707,29 @@
     }
 
     /**
+     * Returns ActivityOptions for overriding task transition animation.
+     */
+    private ActivityOptions makeCustomAnimation(Context context, int enterResId,
+            int exitResId, final Runnable callback, final Handler callbackHandler) {
+        return ActivityOptions.makeCustomTaskAnimation(context, enterResId, exitResId,
+                callbackHandler,
+                elapsedRealTime -> {
+                    if (callback != null) {
+                        callbackHandler.post(callback);
+                    }
+                }, null /* finishedListener */);
+    }
+
+    /**
      * Launch of the current task (both live and inactive tasks) with an animation.
      */
-    public void launchTasks() {
+    public RunnableList launchTasks() {
         RecentsView recentsView = getRecentsView();
         RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask() && remoteTargetHandles != null) {
+        RunnableList runnableList = new RunnableList();
+        if (isRunningTask() && remoteTargetHandles != null) {
             if (!mIsClickableAsLiveTile) {
-                return;
+                return runnableList;
             }
 
             mIsClickableAsLiveTile = false;
@@ -721,7 +756,7 @@
                 // here, try to launch the task as a non live tile task.
                 launchTaskAnimated();
                 mIsClickableAsLiveTile = true;
-                return;
+                return runnableList;
             }
 
             AnimatorSet anim = new AnimatorSet();
@@ -746,13 +781,24 @@
                         launchTaskAnimated();
                     }
                     mIsClickableAsLiveTile = true;
+                    runEndCallback();
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    runEndCallback();
+                }
+
+                private void runEndCallback() {
+                    runnableList.executeAllAndDestroy();
                 }
             });
             anim.start();
             recentsView.onTaskLaunchedInLiveTileMode();
         } else {
-            launchTaskAnimated();
+            return launchTaskAnimated();
         }
+        return runnableList;
     }
 
     /**
@@ -1228,7 +1274,7 @@
                 DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
     }
 
-    public FloatProperty<TaskView> getSecondaryDissmissTranslationProperty() {
+    public FloatProperty<TaskView> getSecondaryDismissTranslationProperty() {
         return getPagedOrientationHandler().getSecondaryValue(
                 DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
     }
@@ -1483,7 +1529,8 @@
     }
 
     public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
-        getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition);
+        getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition,
+                getLogEventForPosition(splitPositionOption.stagePosition));
     }
 
     /**
@@ -1501,8 +1548,17 @@
         return display != null ? display.getDisplayId() : DEFAULT_DISPLAY;
     }
 
+    /**
+     *     Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
+     *     IconView is unaffected.
+     */
     void setThumbnailVisibility(int visibility) {
-        mSnapshotView.setVisibility(visibility);
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (child != mIconView) {
+                child.setVisibility(visibility);
+            }
+        }
     }
 
     /**
@@ -1530,7 +1586,7 @@
          */
         public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale,
                 int previewWidth, DeviceProfile dp, PreviewPositionHelper pph) {
-            RectF insets = pph.getInsetsToDrawInFullscreen(dp);
+            RectF insets = getInsetsToDrawInFullscreen(pph, dp);
 
             float currentInsetsLeft = insets.left * fullscreenProgress;
             float currentInsetsTop = insets.top * fullscreenProgress;
@@ -1549,6 +1605,14 @@
                 mScale = previewWidth / (previewWidth + currentInsetsLeft + currentInsetsRight);
             }
         }
+
+        /**
+         * Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
+         */
+        private static RectF getInsetsToDrawInFullscreen(PreviewPositionHelper pph, DeviceProfile dp) {
+            return dp.isTaskbarPresent && !dp.isTaskbarPresentInApps
+                    ? pph.getClippedInsets() : EMPTY_RECT_F;
+        }
     }
 
     public class TaskIdAttributeContainer {
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index f190e27..e2774c0 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -16,8 +16,6 @@
 
 package com.android.quickstep;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-
 import static org.junit.Assert.assertTrue;
 
 import android.os.SystemProperties;
@@ -79,8 +77,7 @@
 
     private boolean isInLiveTileMode(Launcher launcher,
             LauncherInstrumentation.ContainerType expectedContainerType) {
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()
-                || expectedContainerType != LauncherInstrumentation.ContainerType.OVERVIEW) {
+        if (expectedContainerType != LauncherInstrumentation.ContainerType.OVERVIEW) {
             return false;
         }
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
similarity index 64%
rename from quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt
rename to quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
index cf3c8c9..4785350 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskThumbnailViewTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
@@ -20,26 +20,34 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.launcher3.DeviceProfileBaseTest
-import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper
+import com.android.quickstep.views.TaskView.FullscreenDrawParams
 import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
 import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
 
 /**
- * Test for TaskThumbnailView class.
+ * Test for FullscreenDrawParams class.
  */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class TaskThumbnailViewTest : DeviceProfileBaseTest() {
+class FullscreenDrawParamsTest : DeviceProfileBaseTest() {
 
     private var mThumbnailData: ThumbnailData = mock(ThumbnailData::class.java)
 
     private val mPreviewPositionHelper = PreviewPositionHelper()
+    private lateinit var params: FullscreenDrawParams
+
+    @Before
+    fun setup() {
+        params = FullscreenDrawParams(context)
+    }
 
     @Test
-    fun getInsetsToDrawInFullscreen_clipTaskbarSizeFromBottomForTablets() {
+    fun setFullProgress_currentDrawnInsets_clipTaskbarSizeFromBottomForTablets() {
         initializeVarsForTablet()
         val dp = newDP()
         val previewRect = Rect(0, 0, 100, 100)
@@ -49,15 +57,18 @@
         val isRtl = false
 
         mPreviewPositionHelper.updateThumbnailMatrix(previewRect, mThumbnailData, canvasWidth,
-                canvasHeight, dp, currentRotation, isRtl)
+                canvasHeight, dp.widthPx, dp.taskbarSize, dp.isTablet, currentRotation,
+                isRtl)
+        params.setProgress(/* fullscreenProgress= */ 1.0f, /* parentScale= */ 1.0f,
+                /* taskViewScale= */ 1.0f,  /* previewWidth= */ 0, dp, mPreviewPositionHelper)
 
         val expectedClippedInsets = RectF(0f, 0f, 0f, dp.taskbarSize / 2f)
-        assertThat(mPreviewPositionHelper.getInsetsToDrawInFullscreen(dp))
+        assertThat(params.mCurrentDrawnInsets)
                 .isEqualTo(expectedClippedInsets)
     }
 
     @Test
-    fun getInsetsToDrawInFullscreen_doNotClipTaskbarSizeFromBottomForPhones() {
+    fun setFullProgress_currentDrawnInsets_doNotClipTaskbarSizeFromBottomForPhones() {
         initializeVarsForPhone()
         val dp = newDP()
         val previewRect = Rect(0, 0, 100, 100)
@@ -67,10 +78,13 @@
         val isRtl = false
 
         mPreviewPositionHelper.updateThumbnailMatrix(previewRect, mThumbnailData, canvasWidth,
-                canvasHeight, dp, currentRotation, isRtl)
+                canvasHeight, dp.widthPx, dp.taskbarSize, dp.isTablet, currentRotation,
+                isRtl)
+        params.setProgress(/* fullscreenProgress= */ 1.0f, /* parentScale= */ 1.0f,
+                /* taskViewScale= */ 1.0f,  /* previewWidth= */ 0, dp, mPreviewPositionHelper)
 
         val expectedClippedInsets = RectF(0f, 0f, 0f, 0f)
-        assertThat(mPreviewPositionHelper.getInsetsToDrawInFullscreen(dp))
+        assertThat(params.mCurrentDrawnInsets)
                 .isEqualTo(expectedClippedInsets)
     }
 }
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
index 262dc37..ed5526f 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -26,12 +26,12 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.app.KeyguardManager;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.launcher3.util.LooperExecutor;
 import com.android.quickstep.util.GroupTask;
-import com.android.systemui.shared.system.KeyguardManagerCompat;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
 
 import org.junit.Before;
@@ -56,8 +56,8 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class);
-        KeyguardManagerCompat mockKeyguardManagerCompat = mock(KeyguardManagerCompat.class);
-        mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManagerCompat,
+        KeyguardManager mockKeyguardManager = mock(KeyguardManager.class);
+        mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManager,
                 mockSystemUiProxy);
     }
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 42e9be3..cc561c6 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -309,6 +309,28 @@
         launchedAppState.switchToOverview();
     }
 
+    @Test
+    @ScreenRecord // b/242163205
+    public void testQuickSwitchToPreviousAppForTablet() throws Exception {
+        assumeTrue(mLauncher.isTablet());
+        startTestActivity(2);
+        startImeTestActivity();
+
+        // Set ignoreTaskbarVisibility to true to verify the task bar visibility explicitly.
+        mLauncher.setIgnoreTaskbarVisibility(true);
+
+        // Expect task bar invisible when the launched app was the IME activity.
+        LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
+        launchedAppState.assertTaskbarHidden();
+
+        // Quick-switch to the test app with swiping to right.
+        launchedAppState.quickSwitchToPreviousApp();
+
+        // Expect task bar visible when the launched app was the test activity.
+        launchedAppState = getAndAssertLaunchedApp();
+        launchedAppState.assertTaskbarVisible();
+    }
+
     private boolean isTestActivityRunning(int activityNumber) {
         return mDevice.wait(Until.hasObject(By.pkg(getAppPackageName())
                         .text("TestActivity" + activityNumber)),
diff --git a/res/drawable/page_indicator.xml b/res/drawable/page_indicator.xml
new file mode 100644
index 0000000..c0ccc49
--- /dev/null
+++ b/res/drawable/page_indicator.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="?attr/folderPaginationColor"/>
+    <size android:width="@dimen/page_indicator_size" android:height="@dimen/page_indicator_size"/>
+</shape>
\ No newline at end of file
diff --git a/res/layout/all_apps_personal_work_tabs.xml b/res/layout/all_apps_personal_work_tabs.xml
index d15b906..4459c87 100644
--- a/res/layout/all_apps_personal_work_tabs.xml
+++ b/res/layout/all_apps_personal_work_tabs.xml
@@ -22,6 +22,7 @@
     android:layout_gravity="center_horizontal"
     android:paddingTop="@dimen/all_apps_tabs_vertical_padding"
     android:paddingBottom="@dimen/all_apps_tabs_vertical_padding"
+    android:layout_marginTop="@dimen/all_apps_tabs_margin_top"
     android:orientation="horizontal"
     style="@style/TextHeadline">
 
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 4dee6e7..39f93d9 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -34,8 +34,7 @@
         android:clipChildren="false"
         android:orientation="horizontal"
         android:paddingLeft="12dp"
-        android:paddingRight="12dp"
-        android:alpha="0">
+        android:paddingRight="12dp">
 
         <com.android.launcher3.folder.FolderNameEditText
             android:id="@+id/folder_name"
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index eee5ae2..67998d5 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -21,9 +21,9 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
     <string name="work_folder_name" msgid="3753320833950115786">"কৰ্মস্থান"</string>
-    <string name="activity_not_found" msgid="8071924732094499514">"এপটো ইনষ্টল কৰা নহ\'ল।"</string>
-    <string name="activity_not_available" msgid="7456344436509528827">"এপটো নাই"</string>
-    <string name="safemode_shortcut_error" msgid="9160126848219158407">"ডাউনল’ড কৰা এপটোক সুৰক্ষিত ম\'ডত অক্ষম কৰা হ’ল"</string>
+    <string name="activity_not_found" msgid="8071924732094499514">"এপ্‌টো ইনষ্টল কৰা নহ\'ল।"</string>
+    <string name="activity_not_available" msgid="7456344436509528827">"এপ্‌টো নাই"</string>
+    <string name="safemode_shortcut_error" msgid="9160126848219158407">"ডাউনল’ড কৰা এপ্‌টোক সুৰক্ষিত ম\'ডত অক্ষম কৰা হ’ল"</string>
     <string name="safemode_widget_error" msgid="4863470563535682004">"ৱিজেটবোৰক সুৰক্ষিত ম\'ডত অক্ষম কৰা হ’ল"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"শ্বৰ্টকাট নাই"</string>
     <string name="home_screen" msgid="5629429142036709174">"গৃহ স্ক্ৰীন"</string>
@@ -109,7 +109,7 @@
     <string name="notification_dots_title" msgid="9062440428204120317">"জাননী বিন্দু"</string>
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"অন আছে"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"অফ আছে"</string>
-    <string name="title_missing_notification_access" msgid="7503287056163941064">"জাননী চাবলৈ অনুমতিৰ প্ৰয়োজন"</string>
+    <string name="title_missing_notification_access" msgid="7503287056163941064">"জাননীৰ এক্সেছৰ প্ৰয়োজন"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"জাননী সম্পৰ্কীয় বিন্দুবোৰ দেখুৱাবলৈ <xliff:g id="NAME">%1$s</xliff:g>ৰ বাবে এপৰ জাননীসমূহ অন কৰক"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"ছেটিং সলনি কৰক"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"জাননী বিন্দু দেখুৱাওক"</string>
@@ -119,8 +119,8 @@
     <string name="package_state_unknown" msgid="7592128424511031410">"অজ্ঞাত"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"আঁতৰাওক"</string>
     <string name="abandoned_search" msgid="891119232568284442">"সন্ধান কৰক"</string>
-    <string name="abandoned_promises_title" msgid="7096178467971716750">"এই এপটো ইনষ্টল কৰা হোৱা নাই"</string>
-    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"এই আইকনৰ এপটো ইনষ্টল কৰা হোৱা নাই। আপুনি এইটো আঁতৰাব পাৰে অথবা এপটো বিচাৰি মেনুৱেলভাৱে ইনষ্টল কৰিব পাৰে।"</string>
+    <string name="abandoned_promises_title" msgid="7096178467971716750">"এই এপ্‌টো ইনষ্টল কৰা হোৱা নাই"</string>
+    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"এই আইকনৰ এপ্‌টো ইনষ্টল কৰা হোৱা নাই। আপুনি এইটো আঁতৰাব পাৰে অথবা এপ্‌টো বিচাৰি মেনুৱেলভাৱে ইনষ্টল কৰিব পাৰে।"</string>
     <string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> ইনষ্টল কৰি থকা হৈছে, <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পূৰ্ণ হৈছে"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> ডাউনল’ড কৰি থকা হৈছে, <xliff:g id="PROGRESS">%2$s</xliff:g> সম্পূৰ্ণ হ’ল"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> ইনষ্টল হোৱালৈ অপেক্ষা কৰি থকা হৈছে"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 709160d..3a58b85 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -85,7 +85,7 @@
     <string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> телефон чалууларды аткарууга уруксаты жок"</string>
     <string name="gadget_error_text" msgid="740356548025791839">"Виджет жүктөлбөй жатат"</string>
     <string name="gadget_setup_text" msgid="8348374825537681407">"Виджеттин жөндөөлөрү"</string>
-    <string name="gadget_complete_setup_text" msgid="309040266978007925">"Жөндөп бүтүү үчүн таптап коюңуз"</string>
+    <string name="gadget_complete_setup_text" msgid="309040266978007925">"Аягына чейин тууралоо үчүн басып коюңуз"</string>
     <string name="uninstall_system_app_text" msgid="4172046090762920660">"Бул системдик колдонмо жана аны чечкенге болбойт."</string>
     <string name="folder_hint_text" msgid="5174843001373488816">"Аталышын түзөтүү"</string>
     <string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> өчүрүлгөн"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 48f7355..b553c90 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -120,7 +120,7 @@
     <string name="abandoned_clean_this" msgid="7610119707847920412">"काढा"</string>
     <string name="abandoned_search" msgid="891119232568284442">"शोधा"</string>
     <string name="abandoned_promises_title" msgid="7096178467971716750">"हा अ‍ॅप इंस्टॉल केलेला नाही"</string>
-    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"या चिन्हासाठी अ‍ॅप इंस्टॉल केलेला नाही. तुम्ही ते काढू शकता किंवा अ‍ॅपचा शोध घेऊ शकता आणि त्यास व्यक्तिचलितपणे इंस्टॉल करू शकता."</string>
+    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"या आयकनसाठी अ‍ॅप इंस्टॉल केलेले नाही. तुम्ही तो काढू शकता किंवा अ‍ॅपचा शोध घेऊन ते मॅन्युअली इंस्टॉल करू शकता."</string>
     <string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> इंस्टॉल करत आहे, <xliff:g id="PROGRESS">%2$s</xliff:g> पूर्ण झाले"</string>
     <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> डाउनलोड होत आहे , <xliff:g id="PROGRESS">%2$s</xliff:g> पूर्ण झाले"</string>
     <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> इंस्टॉल करण्याची प्रतिक्षा करत आहे"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 3cec97d..69157bc 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -54,7 +54,7 @@
     <string name="widget_education_header" msgid="4874760613775913787">"အသုံးဝင်သော အချက်အလက်များကို အလွယ်တကူ ရယူလိုက်ပါ"</string>
     <string name="widget_education_content" msgid="1731667670753497052">"အက်ပ်မဖွင့်ဘဲ အချက်အလက်များရယူရန် ပင်မစာမျက်နှာတွင် ဝိဂျက်များ ထည့်နိုင်သည်"</string>
     <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"ဝိဂျက် ဆက်တင်များကို ပြောင်းရန် တို့ပါ"</string>
-    <string name="widget_education_close_button" msgid="8676165703104836580">"ရပြီ"</string>
+    <string name="widget_education_close_button" msgid="8676165703104836580">"နားလည်ပြီ"</string>
     <string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"ဝိဂျက် ဆက်တင်များကို ပြောင်းပါ"</string>
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"ရှာဖွေမှု အက်ပ်များ"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"အက်ပ်များကို ဖွင့်နေသည်…"</string>
@@ -166,7 +166,7 @@
     <string name="work_apps_paused_body" msgid="261634750995824906">"သင်၏ အလုပ်သုံးအက်ပ်များက အကြောင်းကြားချက်များ ပို့ခြင်း၊ သင့်ဘက်ထရီ သုံးခြင်း (သို့) သင့်တည်နေရာ သုံးခြင်းတို့ မပြုလုပ်နိုင်ပါ"</string>
     <string name="work_apps_paused_content_description" msgid="5149623040804051095">"အလုပ်သုံးအက်ပ်များ ပိတ်ထားသည်။ သင်၏ အလုပ်သုံးအက်ပ်များက အကြောင်းကြားချက်များ ပို့ခြင်း၊ သင့်ဘက်ထရီ သုံးခြင်း (သို့) သင့်တည်နေရာ သုံးခြင်းတို့ မပြုလုပ်နိုင်ပါ"</string>
     <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"အလုပ်သုံးအက်ပ်များကို တံဆိပ်တပ်ထားပြီး သင်၏ IT စီမံခန့်ခွဲသူက မြင်နိုင်ပါသည်"</string>
-    <string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"ရပြီ"</string>
+    <string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"နားလည်ပြီ"</string>
     <string name="work_apps_pause_btn_text" msgid="4669288269140620646">"အလုပ်သုံးအက်ပ်များကို ခဏရပ်ရန်"</string>
     <string name="work_apps_enable_btn_text" msgid="1156432622148413741">"အလုပ်သုံးအက်ပ်များ ဖွင့်ရန်"</string>
     <string name="developer_options_filter_hint" msgid="5896817443635989056">"စစ်ထုတ်ရန်"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 812d249..c64e9b2 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -71,7 +71,7 @@
     <string name="all_apps_button_personal_label" msgid="1315764287305224468">"ବ୍ୟକ୍ତିଗତ ଆପ୍ ତାଲିକା"</string>
     <string name="all_apps_button_work_label" msgid="7270707118948892488">"କାର୍ଯ୍ୟକାରୀ ଆପ୍‌ ତାଲିକା"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"ବାହାର କରନ୍ତୁ"</string>
-    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ଅନଇନଷ୍ଟଲ୍‌ କରନ୍ତୁ"</string>
+    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ଅନଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"ଆପ୍‌ ସୂଚନା"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"ଇନଷ୍ଟଲ୍‌ କରନ୍ତୁ"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"ଆପ ପରାମର୍ଶ ଦିଅନ୍ତୁ ନାହିଁ"</string>
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
index 072b92d..e718d9c 100644
--- a/res/values-sw600dp/config.xml
+++ b/res/values-sw600dp/config.xml
@@ -18,4 +18,11 @@
     <!-- The duration of the PagedView page snap animation -->
     <integer name="config_pageSnapAnimationDuration">550</integer>
 
+    <!-- The duration of the Widget picker opening and closing animation -->
+    <integer name="config_bottomSheetOpenDuration">500</integer>
+    <integer name="config_bottomSheetCloseDuration">500</integer>
+
+    <!-- The duration of the AllApps opening and closing animation -->
+    <integer name="config_allAppsOpenDuration">@integer/config_bottomSheetOpenDuration</integer>
+    <integer name="config_allAppsCloseDuration">@integer/config_bottomSheetCloseDuration</integer>
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index e70466f..605278f 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -58,7 +58,7 @@
     <string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"విడ్జెట్ సెట్టింగ్‌లను మార్చండి"</string>
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"యాప్‌ల కోసం సెర్చ్ చేయండి"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"అప్లికేషన్‌లను లోడ్ చేస్తోంది…"</string>
-    <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"కి సరిపోలే అప్లికేషన్‌లేవీ కనుగొనబడలేదు"</string>
+    <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"కి మ్యాచ్ అయ్యే అప్లికేషన్‌లేవీ కనుగొనబడలేదు"</string>
     <string name="label_application" msgid="8531721983832654978">"యాప్"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"అన్ని యాప్‌లు"</string>
     <string name="notifications_header" msgid="1404149926117359025">"నోటిఫికేషన్‌లు"</string>
@@ -146,12 +146,12 @@
     <string name="create_folder_with" msgid="4050141361160214248">"ఈ పేరుతో ఫోల్డర్‌ను క్రియేట్ చేయండి: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_created" msgid="6409794597405184510">"ఫోల్డర్ క్రియేట్ చేయబడింది"</string>
     <string name="action_move_to_workspace" msgid="39528912300293768">"మొదటి స్క్రీన్‌కు తరలించండి"</string>
-    <string name="action_resize" msgid="1802976324781771067">"పరిమాణం మార్చు"</string>
+    <string name="action_resize" msgid="1802976324781771067">"సైజ్‌ మార్చు"</string>
     <string name="action_increase_width" msgid="8773715375078513326">"వెడల్పును పెంచు"</string>
     <string name="action_increase_height" msgid="459390020612501122">"ఎత్తును పెంచు"</string>
     <string name="action_decrease_width" msgid="1374549771083094654">"వెడల్పును తగ్గించు"</string>
     <string name="action_decrease_height" msgid="282377193880900022">"ఎత్తును తగ్గించు"</string>
-    <string name="widget_resized" msgid="9130327887929620">"విడ్జెట్ పరిమాణం వెడల్పు <xliff:g id="NUMBER_0">%1$s</xliff:g>కి, ఎత్తు <xliff:g id="NUMBER_1">%2$s</xliff:g>కి మార్చబడింది"</string>
+    <string name="widget_resized" msgid="9130327887929620">"విడ్జెట్ సైజ్‌ వెడల్పు <xliff:g id="NUMBER_0">%1$s</xliff:g>కి, ఎత్తు <xliff:g id="NUMBER_1">%2$s</xliff:g>కి మార్చబడింది"</string>
     <string name="action_deep_shortcut" msgid="2864038805849372848">"షార్ట్‌కట్స్"</string>
     <string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"షార్ట్‌కట్‌లు మరియు నోటిఫికేషన్‌లు"</string>
     <string name="action_dismiss_notification" msgid="5909461085055959187">"తీసివేయండి"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index d4c08d0..11b6e8c 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -193,4 +193,18 @@
     <dimen name="swipe_back_window_scale_x_margin">10dp</dimen>
     <dimen name="swipe_back_window_max_delta_y">160dp</dimen>
     <dimen name="swipe_back_window_corner_radius">40dp</dimen>
+
+    <!-- The duration of the bottom sheet opening and closing animation -->
+    <integer name="config_bottomSheetOpenDuration">267</integer>
+    <integer name="config_bottomSheetCloseDuration">267</integer>
+
+    <!-- The duration of the AllApps opening and closing animation -->
+    <integer name="config_allAppsOpenDuration">600</integer>
+    <integer name="config_allAppsCloseDuration">300</integer>
+
+    <!-- The max scale for the wallpaper when it's zoomed in -->
+    <item name="config_wallpaperMaxScale" format="float" type="dimen">0</item>
+
+    <string name="floating_task_package" translatable="false"></string>
+    <string name="floating_task_action" translatable="false"></string>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 2e886db..47584e2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -121,6 +121,7 @@
     <dimen name="all_apps_work_profile_tab_footer_bottom_padding">20dp</dimen>
     <dimen name="all_apps_tabs_button_horizontal_padding">4dp</dimen>
     <dimen name="all_apps_tabs_vertical_padding">6dp</dimen>
+    <dimen name="all_apps_tabs_margin_top">8dp</dimen>
     <dimen name="all_apps_divider_height">2dp</dimen>
     <dimen name="all_apps_divider_width">128dp</dimen>
     <dimen name="all_apps_content_fade_in_offset">150dp</dimen>
@@ -128,9 +129,9 @@
     <dimen name="all_apps_height_extra">6dp</dimen>
     <dimen name="all_apps_bottom_sheet_horizontal_padding">0dp</dimen>
     <dimen name="all_apps_paged_view_top_padding">40dp</dimen>
-    <dimen name="all_apps_personal_work_tabs_vertical_margin">16dp</dimen>
 
     <dimen name="all_apps_icon_drawable_padding">8dp</dimen>
+    <dimen name="all_apps_predicted_icon_vertical_padding">8dp</dimen>
     <!-- The size of corner radius of the arrow in the arrow toast. -->
     <dimen name="arrow_toast_corner_radius">2dp</dimen>
     <dimen name="arrow_toast_elevation">2dp</dimen>
@@ -247,6 +248,8 @@
 
     <!-- Folders -->
     <dimen name="page_indicator_dot_size">8dp</dimen>
+    <dimen name="page_indicator_size">10dp</dimen>
+
 
     <dimen name="folder_cell_x_padding">9dp</dimen>
     <dimen name="folder_cell_y_padding">6dp</dimen>
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index fbb0a57..555fbb4 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -32,6 +32,7 @@
 import androidx.annotation.Px;
 
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.logging.InstanceId;
@@ -265,7 +266,7 @@
             }
         }
 
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) mWidgetView.getLayoutParams();
         ItemInfo widgetInfo = (ItemInfo) mWidgetView.getTag();
         lp.cellX = lp.tmpCellX = widgetInfo.cellX;
         lp.cellY = lp.tmpCellY = widgetInfo.cellY;
@@ -405,7 +406,7 @@
      */
     private void resizeWidgetIfNeeded(boolean onDismiss) {
         ViewGroup.LayoutParams wlp = mWidgetView.getLayoutParams();
-        if (!(wlp instanceof CellLayout.LayoutParams)) {
+        if (!(wlp instanceof CellLayoutLayoutParams)) {
             return;
         }
         DeviceProfile dp = mLauncher.getDeviceProfile();
@@ -420,7 +421,7 @@
         mDirectionVector[0] = 0;
         mDirectionVector[1] = 0;
 
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) wlp;
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) wlp;
 
         int spanX = lp.cellHSpan;
         int spanY = lp.cellVSpan;
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index d34f535..83ff084 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -25,12 +25,16 @@
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.os.Bundle;
+import android.window.OnBackInvokedDispatcher;
 
 import androidx.annotation.IntDef;
 
 import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.ViewCache;
 import com.android.launcher3.views.AppLauncher;
@@ -172,6 +176,19 @@
     }
 
     @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (Utilities.ATLEAST_T) {
+            getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                    OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                    () -> {
+                        onBackPressed();
+                        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked");
+                    });
+        }
+    }
+
+    @Override
     protected void onStart() {
         addActivityFlags(ACTIVITY_STATE_STARTED);
         super.onStart();
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 5fb8925..c0a00c2 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -942,6 +942,11 @@
         return mIconSize;
     }
 
+    public boolean isDisplaySearchResult() {
+        return mDisplay == DISPLAY_SEARCH_RESULT ||
+                mDisplay == DISPLAY_SEARCH_RESULT_SMALL;
+    }
+
     private void updateTranslation() {
         super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
                 + mTranslationForMoveFromCenterAnimation.x
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 9f3e1fa..c1df403 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -61,6 +61,7 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.PreviewBackground;
@@ -130,7 +131,7 @@
 
     // These arrays are used to implement the drag visualization on x-large screens.
     // They are used as circular arrays, indexed by mDragOutlineCurrent.
-    @Thunk final CellLayout.LayoutParams[] mDragOutlines = new CellLayout.LayoutParams[4];
+    @Thunk final CellLayoutLayoutParams[] mDragOutlines = new CellLayoutLayoutParams[4];
     @Thunk final float[] mDragOutlineAlphas = new float[mDragOutlines.length];
     private final InterruptibleInOutAnimator[] mDragOutlineAnims =
             new InterruptibleInOutAnimator[mDragOutlines.length];
@@ -139,7 +140,7 @@
     private int mDragOutlineCurrent = 0;
     private final Paint mDragOutlinePaint = new Paint();
 
-    @Thunk final ArrayMap<LayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
+    @Thunk final ArrayMap<CellLayoutLayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
     @Thunk final ArrayMap<Reorderable, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
 
     private boolean mItemPlacementDirty = false;
@@ -269,7 +270,7 @@
         mDragCell[0] = mDragCell[1] = -1;
         mDragCellSpan[0] = mDragCellSpan[1] = -1;
         for (int i = 0; i < mDragOutlines.length; i++) {
-            mDragOutlines[i] = new CellLayout.LayoutParams(0, 0, 0, 0);
+            mDragOutlines[i] = new CellLayoutLayoutParams(0, 0, 0, 0);
         }
         mDragOutlinePaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor));
 
@@ -737,9 +738,19 @@
         return mContainerType == WORKSPACE;
     }
 
-    public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
-            boolean markCells) {
-        final LayoutParams lp = params;
+    /**
+     * Adds the given view to the CellLayout
+     *
+     * @param child view to add.
+     * @param index index of the CellLayout children where to add the view.
+     * @param childId id of the view.
+     * @param params represent the logic of the view on the CellLayout.
+     * @param markCells if the occupied cells should be marked or not
+     * @return if adding the view was successful
+     */
+    public boolean addViewToCellLayout(View child, int index, int childId,
+            CellLayoutLayoutParams params, boolean markCells) {
+        final CellLayoutLayoutParams lp = params;
 
         // Hotseat icons - remove text
         if (child instanceof BubbleTextView) {
@@ -1046,7 +1057,7 @@
         ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
 
         if (clc.indexOfChild(child) != -1 && (child instanceof Reorderable)) {
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
             final ItemInfo info = (ItemInfo) child.getTag();
             final Reorderable item = (Reorderable) child;
 
@@ -1153,7 +1164,7 @@
             mDragOutlineAnims[oldIndex].animateOut();
             mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
 
-            LayoutParams cell = mDragOutlines[mDragOutlineCurrent];
+            CellLayoutLayoutParams cell = mDragOutlines[mDragOutlineCurrent];
             cell.cellX = cellX;
             cell.cellY = cellY;
             cell.cellHSpan = spanX;
@@ -1708,7 +1719,7 @@
                 // cluster.
                 if (!cluster.views.contains(v) && v != dragView) {
                     if (cluster.isViewTouchingEdge(v, whichEdge)) {
-                        LayoutParams lp = (LayoutParams) v.getLayoutParams();
+                        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) v.getLayoutParams();
                         if (!lp.canReorder) {
                             // The push solution includes the all apps button, this is not viable.
                             fail = true;
@@ -1919,7 +1930,7 @@
         for (View child: solution.map.keySet()) {
             if (child == ignoreView) continue;
             CellAndSpan c = solution.map.get(child);
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
             r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY);
             if (Rect.intersects(r0, r1)) {
                 if (!lp.canReorder) {
@@ -2016,7 +2027,7 @@
         int childCount = mShortcutsAndWidgets.getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
             CellAndSpan c;
             if (temp) {
                 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
@@ -2034,7 +2045,7 @@
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
             if (child == dragView) continue;
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
             CellAndSpan c = solution.map.get(child);
             if (c != null) {
                 lp.tmpCellX = c.cellX;
@@ -2082,7 +2093,7 @@
                     != null && !solution.intersectingViews.contains(child);
 
 
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
             if (c != null && !skip && (child instanceof Reorderable)) {
                 ReorderPreviewAnimation rha = new ReorderPreviewAnimation((Reorderable) child,
                         mode, lp.cellX, lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
@@ -2273,7 +2284,7 @@
         int childCount = mShortcutsAndWidgets.getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
             ItemInfo info = (ItemInfo) child.getTag();
             // We do a null check here because the item info can be null in the case of the
             // AllApps button in the hotseat.
@@ -2298,7 +2309,8 @@
     private void setUseTempCoords(boolean useTempCoords) {
         int childCount = mShortcutsAndWidgets.getChildCount();
         for (int i = 0; i < childCount; i++) {
-            LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) mShortcutsAndWidgets.getChildAt(
+                    i).getLayoutParams();
             lp.useTmpCoords = useTempCoords;
         }
     }
@@ -2383,7 +2395,8 @@
         for (int i = 0; i < count; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
             if (child == dragView) continue;
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams
+                    lp = (CellLayoutLayoutParams) child.getLayoutParams();
             r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
             if (Rect.intersects(r0, r1)) {
                 mIntersectingViews.add(child);
@@ -2408,7 +2421,8 @@
             final int count = mShortcutsAndWidgets.getChildCount();
             for (int i = 0; i < count; i++) {
                 View child = mShortcutsAndWidgets.getChildAt(i);
-                LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                CellLayoutLayoutParams
+                        lp = (CellLayoutLayoutParams) child.getLayoutParams();
                 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
                     lp.tmpCellX = lp.cellX;
                     lp.tmpCellY = lp.cellY;
@@ -2685,7 +2699,8 @@
      */
     void onDropChild(View child) {
         if (child != null) {
-            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams
+                    lp = (CellLayoutLayoutParams) child.getLayoutParams();
             lp.dropped = true;
             child.requestLayout();
             markCellsAsOccupiedForView(child);
@@ -2727,7 +2742,8 @@
             return;
         }
         if (view == null || view.getParent() != mShortcutsAndWidgets) return;
-        LayoutParams lp = (LayoutParams) view.getLayoutParams();
+        CellLayoutLayoutParams
+                lp = (CellLayoutLayoutParams) view.getLayoutParams();
         mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
     }
 
@@ -2739,7 +2755,8 @@
             return;
         }
         if (view == null || view.getParent() != mShortcutsAndWidgets) return;
-        LayoutParams lp = (LayoutParams) view.getLayoutParams();
+        CellLayoutLayoutParams
+                lp = (CellLayoutLayoutParams) view.getLayoutParams();
         mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
     }
 
@@ -2763,165 +2780,17 @@
 
     @Override
     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
-        return new CellLayout.LayoutParams(getContext(), attrs);
+        return new CellLayoutLayoutParams(getContext(), attrs);
     }
 
     @Override
     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
-        return p instanceof CellLayout.LayoutParams;
+        return p instanceof CellLayoutLayoutParams;
     }
 
     @Override
     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return new CellLayout.LayoutParams(p);
-    }
-
-    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
-        /**
-         * Horizontal location of the item in the grid.
-         */
-        @ViewDebug.ExportedProperty
-        public int cellX;
-
-        /**
-         * Vertical location of the item in the grid.
-         */
-        @ViewDebug.ExportedProperty
-        public int cellY;
-
-        /**
-         * Temporary horizontal location of the item in the grid during reorder
-         */
-        public int tmpCellX;
-
-        /**
-         * Temporary vertical location of the item in the grid during reorder
-         */
-        public int tmpCellY;
-
-        /**
-         * Indicates that the temporary coordinates should be used to layout the items
-         */
-        public boolean useTmpCoords;
-
-        /**
-         * Number of cells spanned horizontally by the item.
-         */
-        @ViewDebug.ExportedProperty
-        public int cellHSpan;
-
-        /**
-         * Number of cells spanned vertically by the item.
-         */
-        @ViewDebug.ExportedProperty
-        public int cellVSpan;
-
-        /**
-         * Indicates whether the item will set its x, y, width and height parameters freely,
-         * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
-         */
-        public boolean isLockedToGrid = true;
-
-        /**
-         * Indicates whether this item can be reordered. Always true except in the case of the
-         * the AllApps button and QSB place holder.
-         */
-        public boolean canReorder = true;
-
-        // X coordinate of the view in the layout.
-        @ViewDebug.ExportedProperty
-        public int x;
-        // Y coordinate of the view in the layout.
-        @ViewDebug.ExportedProperty
-        public int y;
-
-        boolean dropped;
-
-        public LayoutParams(Context c, AttributeSet attrs) {
-            super(c, attrs);
-            cellHSpan = 1;
-            cellVSpan = 1;
-        }
-
-        public LayoutParams(ViewGroup.LayoutParams source) {
-            super(source);
-            cellHSpan = 1;
-            cellVSpan = 1;
-        }
-
-        public LayoutParams(LayoutParams source) {
-            super(source);
-            this.cellX = source.cellX;
-            this.cellY = source.cellY;
-            this.cellHSpan = source.cellHSpan;
-            this.cellVSpan = source.cellVSpan;
-        }
-
-        public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
-            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-            this.cellX = cellX;
-            this.cellY = cellY;
-            this.cellHSpan = cellHSpan;
-            this.cellVSpan = cellVSpan;
-        }
-
-        public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
-                int rowCount, Point borderSpace, @Nullable Rect inset) {
-            setup(cellWidth, cellHeight, invertHorizontally, colCount, rowCount, 1.0f, 1.0f,
-                    borderSpace, inset);
-        }
-
-        /**
-         * Use this method, as opposed to {@link #setup(int, int, boolean, int, int, Point, Rect)},
-         * if the view needs to be scaled.
-         *
-         * ie. In multi-window mode, we setup widgets so that they are measured and laid out
-         * using their full/invariant device profile sizes.
-         */
-        public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
-                int rowCount, float cellScaleX, float cellScaleY, Point borderSpace,
-                @Nullable Rect inset) {
-            if (isLockedToGrid) {
-                final int myCellHSpan = cellHSpan;
-                final int myCellVSpan = cellVSpan;
-                int myCellX = useTmpCoords ? tmpCellX : cellX;
-                int myCellY = useTmpCoords ? tmpCellY : cellY;
-
-                if (invertHorizontally) {
-                    myCellX = colCount - myCellX - cellHSpan;
-                }
-
-                int hBorderSpacing = (myCellHSpan - 1) * borderSpace.x;
-                int vBorderSpacing = (myCellVSpan - 1) * borderSpace.y;
-
-                float myCellWidth = ((myCellHSpan * cellWidth) + hBorderSpacing) / cellScaleX;
-                float myCellHeight = ((myCellVSpan * cellHeight) + vBorderSpacing) / cellScaleY;
-
-                width = Math.round(myCellWidth) - leftMargin - rightMargin;
-                height = Math.round(myCellHeight) - topMargin - bottomMargin;
-                x = leftMargin + (myCellX * cellWidth) + (myCellX * borderSpace.x);
-                y = topMargin + (myCellY * cellHeight) + (myCellY * borderSpace.y);
-
-                if (inset != null) {
-                    x -= inset.left;
-                    y -= inset.top;
-                    width += inset.left + inset.right;
-                    height += inset.top + inset.bottom;
-                }
-            }
-        }
-
-        /**
-         * Sets the position to the provided point
-         */
-        public void setCellXY(Point point) {
-            cellX = point.x;
-            cellY = point.y;
-        }
-
-        public String toString() {
-            return "(" + this.cellX + ", " + this.cellY + ")";
-        }
+        return new CellLayoutLayoutParams(p);
     }
 
     // This class stores info for two purposes:
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 0292e04..7881a26 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
 import static com.android.launcher3.Utilities.dpiFromPx;
 import static com.android.launcher3.Utilities.pxFromSp;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
 import static com.android.launcher3.icons.GraphicsUtils.getShapePath;
 import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
@@ -42,6 +43,7 @@
 
 import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.DevicePaddings.DevicePadding;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.model.data.ItemInfo;
@@ -181,11 +183,19 @@
     private final int hotseatQsbShadowHeight;
     public int hotseatBorderSpace;
 
+    // Bottom sheets
+    public int bottomSheetTopPadding;
+    public int bottomSheetOpenDuration;
+    public int bottomSheetCloseDuration;
+    public float bottomSheetWorkspaceScale;
+    public float bottomSheetDepth;
+
     // All apps
     public Point allAppsBorderSpacePx;
     public int allAppsShiftRange;
     public int allAppsTopPadding;
-    public int bottomSheetTopPadding;
+    public int allAppsOpenDuration;
+    public int allAppsCloseDuration;
     public int allAppsCellHeightPx;
     public int allAppsCellWidthPx;
     public int allAppsIconSizePx;
@@ -316,6 +326,21 @@
         bottomSheetTopPadding = mInsets.top // statusbar height
                 + res.getDimensionPixelSize(R.dimen.bottom_sheet_extra_top_padding)
                 + (isTablet ? 0 : edgeMarginPx); // phones need edgeMarginPx additional padding
+        bottomSheetOpenDuration = res.getInteger(R.integer.config_bottomSheetOpenDuration);
+        bottomSheetCloseDuration = res.getInteger(R.integer.config_bottomSheetCloseDuration);
+        if (isTablet) {
+            bottomSheetWorkspaceScale = workspaceContentScale;
+            // The goal is to set wallpaper to zoom at workspaceContentScale when in AllApps.
+            // When depth is 0, wallpaper zoom is set to maxWallpaperScale.
+            // When depth is 1, wallpaper zoom is set to 1.
+            // For depth to achieve zoom set to maxWallpaperScale * workspaceContentScale:
+            float maxWallpaperScale = res.getFloat(R.dimen.config_wallpaperMaxScale);
+            bottomSheetDepth = Utilities.mapToRange(maxWallpaperScale * workspaceContentScale,
+                    maxWallpaperScale, 1f, 0f, 1f, LINEAR);
+        } else {
+            bottomSheetWorkspaceScale = 1f;
+            bottomSheetDepth = 0f;
+        }
 
         folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);
         folderContentPaddingLeftRight =
@@ -474,6 +499,8 @@
             allAppsShiftRange =
                     res.getDimensionPixelSize(R.dimen.all_apps_starting_vertical_translate);
         }
+        allAppsOpenDuration = res.getInteger(R.integer.config_allAppsOpenDuration);
+        allAppsCloseDuration = res.getInteger(R.integer.config_allAppsCloseDuration);
 
         flingToDeleteThresholdVelocity = res.getDimensionPixelSize(
                 R.dimen.drag_flingToDeleteMinVelocity);
@@ -1245,12 +1272,17 @@
      * Returns the number of pixels required below OverviewActions excluding insets.
      */
     public int getOverviewActionsClaimedSpaceBelow() {
-        if (isTaskbarPresent && !isGestureMode) {
+        if (isTaskbarPresent && !isGestureMode
+                // If taskbar is in overview, overview action has dedicated space above nav buttons
+                && !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
             // Align vertically to where nav buttons are.
             return ((taskbarSize - overviewActionsHeight) / 2) + getTaskbarOffsetY();
         }
 
-        return isTaskbarPresent ? stashedTaskbarSize : mInsets.bottom;
+        if (isTaskbarPresent) {
+            return FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get() ? taskbarSize : stashedTaskbarSize;
+        }
+        return mInsets.bottom;
     }
 
     /** Gets the space that the overview actions will take, including bottom margin. */
@@ -1434,9 +1466,15 @@
         writer.println(prefix + pxToDpStr("folderTopPadding", folderContentPaddingTop));
 
         writer.println(prefix + pxToDpStr("bottomSheetTopPadding", bottomSheetTopPadding));
+        writer.println(prefix + "\tbottomSheetOpenDuration: " + bottomSheetOpenDuration);
+        writer.println(prefix + "\tbottomSheetCloseDuration: " + bottomSheetCloseDuration);
+        writer.println(prefix + "\tbottomSheetWorkspaceScale: " + bottomSheetWorkspaceScale);
+        writer.println(prefix + "\tbottomSheetDepth: " + bottomSheetDepth);
 
         writer.println(prefix + pxToDpStr("allAppsShiftRange", allAppsShiftRange));
         writer.println(prefix + pxToDpStr("allAppsTopPadding", allAppsTopPadding));
+        writer.println(prefix + "\tallAppsOpenDuration: " + allAppsOpenDuration);
+        writer.println(prefix + "\tallAppsCloseDuration: " + allAppsCloseDuration);
         writer.println(prefix + pxToDpStr("allAppsIconSizePx", allAppsIconSizePx));
         writer.println(prefix + pxToDpStr("allAppsIconTextSizePx", allAppsIconTextSizePx));
         writer.println(prefix + pxToDpStr("allAppsIconDrawablePaddingPx",
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 6bdfa1c..5a6b3ec 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -42,6 +42,7 @@
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
 import static com.android.launcher3.logging.StatsLogManager.EventEnum;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
@@ -479,7 +480,6 @@
         mAppWidgetHost = createAppWidgetHost();
         mAppWidgetHost.startListening();
 
-        inflateRootView(R.layout.launcher);
         setupViews();
         crossFadeWithPreviousAppearance();
         mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
@@ -1258,6 +1258,7 @@
      * Finds all the views we need and configure them properly.
      */
     protected void setupViews() {
+        inflateRootView(R.layout.launcher);
         mDragLayer = findViewById(R.id.drag_layer);
         mFocusHandler = mDragLayer.getFocusIndicatorHelper();
         mWorkspace = mDragLayer.findViewById(R.id.workspace);
@@ -1578,6 +1579,14 @@
         return mOldConfig.orientation;
     }
 
+    /**
+     * Whether keyboard sync is enabled for transitions between Home and All Apps.
+     * TODO(b/251387263): move this method inside an All Apps specific config class.
+     */
+    public boolean isKeyboardSyncEnabled() {
+        return false;
+    }
+
     @Override
     protected void onNewIntent(Intent intent) {
         if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
@@ -2741,6 +2750,8 @@
         getViewCache().setCacheSize(R.layout.folder_page, 2);
 
         TraceHelper.INSTANCE.endSection(traceToken);
+
+        mWorkspace.removeExtraEmptyScreen(true);
     }
 
     private boolean canAnimatePageChange() {
@@ -3234,12 +3245,10 @@
      * @param progress Transition progress from 0 to 1; where 0 => home and 1 => widgets.
      */
     public void onWidgetsTransition(float progress) {
-        if (mDeviceProfile.isTablet) {
-            float scale =
-                    Utilities.comp(Utilities.comp(mDeviceProfile.workspaceContentScale) * progress);
-            WORKSPACE_WIDGET_SCALE.set(getWorkspace(), scale);
-            HOTSEAT_WIDGET_SCALE.set(getHotseat(), scale);
-        }
+        float scale = Utilities.mapToRange(progress, 0f, 1f, 1f,
+                mDeviceProfile.bottomSheetWorkspaceScale, EMPHASIZED);
+        WORKSPACE_WIDGET_SCALE.set(getWorkspace(), scale);
+        HOTSEAT_WIDGET_SCALE.set(getHotseat(), scale);
     }
 
     private static class NonConfigInstance {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 68b7701..20df897 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -33,6 +33,7 @@
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
@@ -89,9 +90,11 @@
 
     static final String TAG = "Launcher.Model";
 
+    @NonNull
     private final LauncherAppState mApp;
+    @NonNull
     private final Object mLock = new Object();
-
+    @Nullable
     private LoaderTask mLoaderTask;
     private boolean mIsLoaderTaskRunning;
 
@@ -107,20 +110,25 @@
         }
     }
 
+    @NonNull
     private final ArrayList<Callbacks> mCallbacksList = new ArrayList<>(1);
 
     // < only access in worker thread >
+    @NonNull
     private final AllAppsList mBgAllAppsList;
 
     /**
      * All the static data should be accessed on the background thread, A lock should be acquired
      * on this object when accessing any data from this model.
      */
+    @NonNull
     private final BgDataModel mBgDataModel = new BgDataModel();
 
+    @NonNull
     private final ModelDelegate mModelDelegate;
 
     // Runnable to check if the shortcuts permission has changed.
+    @NonNull
     private final Runnable mDataValidationCheck = new Runnable() {
         @Override
         public void run() {
@@ -130,14 +138,16 @@
         }
     };
 
-    LauncherModel(Context context, LauncherAppState app, IconCache iconCache, AppFilter appFilter,
-            boolean isPrimaryInstance) {
+    LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app,
+            @NonNull final IconCache iconCache, @NonNull final AppFilter appFilter,
+            final boolean isPrimaryInstance) {
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
         mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel,
                 isPrimaryInstance);
     }
 
+    @NonNull
     public ModelDelegate getModelDelegate() {
         return mModelDelegate;
     }
@@ -145,52 +155,57 @@
     /**
      * Adds the provided items to the workspace.
      */
-    public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
+    public void addAndBindAddedWorkspaceItems(
+            @NonNull final List<Pair<ItemInfo, Object>> itemList) {
         for (Callbacks cb : getCallbacks()) {
             cb.preAddApps();
         }
         enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
     }
 
-    public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges,
-            @Nullable Callbacks owner) {
+    @NonNull
+    public ModelWriter getWriter(final boolean hasVerticalHotseat, final boolean verifyChanges,
+            @Nullable final Callbacks owner) {
         return new ModelWriter(mApp.getContext(), this, mBgDataModel,
                 hasVerticalHotseat, verifyChanges, owner);
     }
 
     @Override
-    public void onPackageChanged(String packageName, UserHandle user) {
+    public void onPackageChanged(
+            @NonNull final String packageName, @NonNull final UserHandle user) {
         int op = PackageUpdatedTask.OP_UPDATE;
         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
     }
 
     @Override
-    public void onPackageRemoved(String packageName, UserHandle user) {
+    public void onPackageRemoved(
+            @NonNull final String packageName, @NonNull final UserHandle user) {
         onPackagesRemoved(user, packageName);
     }
 
-    public void onPackagesRemoved(UserHandle user, String... packages) {
+    public void onPackagesRemoved(
+            @NonNull final UserHandle user, @NonNull final String... packages) {
         int op = PackageUpdatedTask.OP_REMOVE;
         FileLog.d(TAG, "package removed received " + TextUtils.join(",", packages));
         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
     }
 
     @Override
-    public void onPackageAdded(String packageName, UserHandle user) {
+    public void onPackageAdded(@NonNull final String packageName, @NonNull final UserHandle user) {
         int op = PackageUpdatedTask.OP_ADD;
         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
     }
 
     @Override
-    public void onPackagesAvailable(String[] packageNames, UserHandle user,
-            boolean replacing) {
+    public void onPackagesAvailable(@NonNull final String[] packageNames,
+            @NonNull final UserHandle user, final boolean replacing) {
         enqueueModelUpdateTask(
                 new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames));
     }
 
     @Override
-    public void onPackagesUnavailable(String[] packageNames, UserHandle user,
-            boolean replacing) {
+    public void onPackagesUnavailable(@NonNull final String[] packageNames,
+            @NonNull final UserHandle user, final boolean replacing) {
         if (!replacing) {
             enqueueModelUpdateTask(new PackageUpdatedTask(
                     PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames));
@@ -198,20 +213,22 @@
     }
 
     @Override
-    public void onPackagesSuspended(String[] packageNames, UserHandle user) {
+    public void onPackagesSuspended(
+            @NonNull final String[] packageNames, @NonNull final UserHandle user) {
         enqueueModelUpdateTask(new PackageUpdatedTask(
                 PackageUpdatedTask.OP_SUSPEND, user, packageNames));
     }
 
     @Override
-    public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
+    public void onPackagesUnsuspended(
+            @NonNull final String[] packageNames, @NonNull final UserHandle user) {
         enqueueModelUpdateTask(new PackageUpdatedTask(
                 PackageUpdatedTask.OP_UNSUSPEND, user, packageNames));
     }
 
     @Override
-    public void onPackageLoadingProgressChanged(
-                String packageName, UserHandle user, float progress) {
+    public void onPackageLoadingProgressChanged(@NonNull final String packageName,
+            @NonNull final UserHandle user, final float progress) {
         if (Utilities.ATLEAST_S) {
             enqueueModelUpdateTask(new PackageIncrementalDownloadUpdatedTask(
                     packageName, user, progress));
@@ -219,8 +236,8 @@
     }
 
     @Override
-    public void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts,
-            UserHandle user) {
+    public void onShortcutsChanged(@NonNull final String packageName,
+            @NonNull final List<ShortcutInfo> shortcuts, @NonNull final UserHandle user) {
         enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
     }
 
@@ -228,7 +245,8 @@
      * Called when the icon for an app changes, outside of package event
      */
     @WorkerThread
-    public void onAppIconChanged(String packageName, UserHandle user) {
+    public void onAppIconChanged(@NonNull final String packageName,
+            @NonNull final UserHandle user) {
         // Update the icon for the calendar package
         Context context = mApp.getContext();
         onPackageChanged(packageName, user);
@@ -256,7 +274,7 @@
         MODEL_EXECUTOR.execute(mModelDelegate::destroy);
     }
 
-    public void onBroadcastIntent(Intent intent) {
+    public void onBroadcastIntent(@NonNull final Intent intent) {
         if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
         final String action = intent.getAction();
         if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
@@ -322,7 +340,7 @@
     /**
      * Removes an existing callback
      */
-    public void removeCallbacks(Callbacks callbacks) {
+    public void removeCallbacks(@NonNull final Callbacks callbacks) {
         synchronized (mCallbacksList) {
             Preconditions.assertUIThread();
             if (mCallbacksList.remove(callbacks)) {
@@ -338,7 +356,7 @@
      * Adds a callbacks to receive model updates
      * @return true if workspace load was performed synchronously
      */
-    public boolean addCallbacksAndLoad(Callbacks callbacks) {
+    public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) {
         synchronized (mLock) {
             addCallbacks(callbacks);
             return startLoader(new Callbacks[] { callbacks });
@@ -349,7 +367,7 @@
     /**
      * Adds a callbacks to receive model updates
      */
-    public void addCallbacks(Callbacks callbacks) {
+    public void addCallbacks(@NonNull final Callbacks callbacks) {
         Preconditions.assertUIThread();
         synchronized (mCallbacksList) {
             if (TestProtocol.sDebugTracing) {
@@ -370,7 +388,7 @@
         return startLoader(new Callbacks[0]);
     }
 
-    private boolean startLoader(Callbacks[] newCallbacks) {
+    private boolean startLoader(@NonNull final Callbacks[] newCallbacks) {
         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
         ItemInstallQueue.INSTANCE.get(mApp.getContext())
                 .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
@@ -433,7 +451,7 @@
      * Loads the model if not loaded
      * @param callback called with the data model upon successful load or null on model thread.
      */
-    public void loadAsync(Consumer<BgDataModel> callback) {
+    public void loadAsync(@NonNull final Consumer<BgDataModel> callback) {
         synchronized (mLock) {
             if (!mModelLoaded && !mIsLoaderTaskRunning) {
                 startLoader();
@@ -443,11 +461,12 @@
     }
 
     @Override
-    public void onInstallSessionCreated(final PackageInstallInfo sessionInfo) {
+    public void onInstallSessionCreated(@NonNull final PackageInstallInfo sessionInfo) {
         if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
             enqueueModelUpdateTask(new BaseModelUpdateTask() {
                 @Override
-                public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+                public void execute(@NonNull final LauncherAppState app,
+                        @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
                     apps.addPromiseApp(app.getContext(), sessionInfo);
                     bindApplicationsIfNeeded();
                 }
@@ -456,10 +475,12 @@
     }
 
     @Override
-    public void onSessionFailure(String packageName, UserHandle user) {
+    public void onSessionFailure(@NonNull final String packageName,
+            @NonNull final UserHandle user) {
         enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
-            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            public void execute(@NonNull final LauncherAppState app,
+                    @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
                 final IntSet removedIds = new IntSet();
                 synchronized (dataModel) {
                     for (ItemInfo info : dataModel.itemsIdMap) {
@@ -483,7 +504,7 @@
     }
 
     @Override
-    public void onPackageStateChanged(PackageInstallInfo installInfo) {
+    public void onPackageStateChanged(@NonNull final PackageInstallInfo installInfo) {
         enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
     }
 
@@ -491,7 +512,8 @@
      * Updates the icons and label of all pending icons for the provided package name.
      */
     @Override
-    public void onUpdateSessionDisplay(PackageUserKey key, PackageInstaller.SessionInfo info) {
+    public void onUpdateSessionDisplay(@NonNull final PackageUserKey key,
+            @NonNull final PackageInstaller.SessionInfo info) {
         mApp.getIconCache().updateSessionCache(key, info);
 
         HashSet<String> packages = new HashSet<>();
@@ -502,9 +524,10 @@
 
     public class LoaderTransaction implements AutoCloseable {
 
+        @NonNull
         private final LoaderTask mTask;
 
-        private LoaderTransaction(LoaderTask task) throws CancellationException {
+        private LoaderTransaction(@NonNull final LoaderTask task) throws CancellationException {
             synchronized (mLock) {
                 if (mLoaderTask != task) {
                     throw new CancellationException("Loader already stopped");
@@ -534,7 +557,8 @@
         }
     }
 
-    public LoaderTransaction beginLoader(LoaderTask task) throws CancellationException {
+    public LoaderTransaction beginLoader(@NonNull final LoaderTask task)
+            throws CancellationException {
         return new LoaderTransaction(task);
     }
 
@@ -551,7 +575,8 @@
     /**
      * Called when the icons for packages have been updated in the icon cache.
      */
-    public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user) {
+    public void onPackageIconsUpdated(@NonNull final HashSet<String> updatedPackages,
+            @NonNull final UserHandle user) {
         // If any package icon has changed (app was updated while launcher was dead),
         // update the corresponding shortcuts.
         enqueueModelUpdateTask(new CacheDataUpdatedTask(
@@ -561,17 +586,19 @@
     /**
      * Called when the labels for the widgets has updated in the icon cache.
      */
-    public void onWidgetLabelsUpdated(HashSet<String> updatedPackages, UserHandle user) {
+    public void onWidgetLabelsUpdated(@NonNull final HashSet<String> updatedPackages,
+            @NonNull final UserHandle user) {
         enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
-            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            public void execute(@NonNull final LauncherAppState app,
+                    @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
                 dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, app);
                 bindUpdatedWidgets(dataModel);
             }
         });
     }
 
-    public void enqueueModelUpdateTask(ModelUpdateTask task) {
+    public void enqueueModelUpdateTask(@NonNull final ModelUpdateTask task) {
         if (mModelDestroyed) {
             return;
         }
@@ -585,7 +612,7 @@
      */
     public interface CallbackTask {
 
-        void execute(Callbacks callbacks);
+        void execute(@NonNull Callbacks callbacks);
     }
 
     /**
@@ -596,12 +623,14 @@
         /**
          * Called before the task is posted to initialize the internal state.
          */
-        void init(LauncherAppState app, LauncherModel model,
-                BgDataModel dataModel, AllAppsList allAppsList, Executor uiExecutor);
+        void init(@NonNull LauncherAppState app, @NonNull LauncherModel model,
+                @NonNull BgDataModel dataModel, @NonNull AllAppsList allAppsList,
+                @NonNull Executor uiExecutor);
 
     }
 
-    public void updateAndBindWorkspaceItem(WorkspaceItemInfo si, ShortcutInfo info) {
+    public void updateAndBindWorkspaceItem(@NonNull final WorkspaceItemInfo si,
+            @NonNull final ShortcutInfo info) {
         updateAndBindWorkspaceItem(() -> {
             si.updateFromDeepShortcutInfo(info, mApp.getContext());
             mApp.getIconCache().getShortcutIcon(si, info);
@@ -612,10 +641,12 @@
     /**
      * Utility method to update a shortcut on the background thread.
      */
-    public void updateAndBindWorkspaceItem(final Supplier<WorkspaceItemInfo> itemProvider) {
+    public void updateAndBindWorkspaceItem(
+            @NonNull final Supplier<WorkspaceItemInfo> itemProvider) {
         enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
-            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            public void execute(@NonNull final LauncherAppState app,
+                    @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
                 WorkspaceItemInfo info = itemProvider.get();
                 getModelWriter().updateItemInDatabase(info);
                 ArrayList<WorkspaceItemInfo> update = new ArrayList<>();
@@ -628,14 +659,16 @@
     public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) {
         enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
-            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            public void execute(@NonNull final LauncherAppState app,
+                    @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
                 dataModel.widgetsModel.update(app, packageUser);
                 bindUpdatedWidgets(dataModel);
             }
         });
     }
 
-    public void dumpState(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+    public void dumpState(@Nullable final String prefix, @Nullable final FileDescriptor fd,
+            @NonNull final PrintWriter writer, @NonNull final String[] args) {
         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
             writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
             for (AppInfo info : mBgAllAppsList.data) {
@@ -661,6 +694,7 @@
     /**
      * Returns an array of currently attached callbacks
      */
+    @NonNull
     public Callbacks[] getCallbacks() {
         synchronized (mCallbacksList) {
             return mCallbacksList.toArray(new Callbacks[mCallbacksList.size()]);
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index a20ff8c..58e85fe 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -85,6 +85,7 @@
 import java.util.Locale;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 public class LauncherProvider extends ContentProvider {
     private static final String TAG = "LauncherProvider";
@@ -944,16 +945,28 @@
             final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db,
                     Favorites.TABLE_NAME, Favorites.APPWIDGET_ID,
                     "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null));
+            boolean isAnyWidgetRemoved = false;
             for (int widgetId : allWidgets) {
                 if (!validWidgets.contains(widgetId)) {
                     try {
                         FileLog.d(TAG, "Deleting invalid widget " + widgetId);
                         host.deleteAppWidgetId(widgetId);
+                        isAnyWidgetRemoved = true;
                     } catch (RuntimeException e) {
                         // Ignore
                     }
                 }
             }
+            if (isAnyWidgetRemoved) {
+                final String allWidgetsIds = Arrays.stream(allWidgets).mapToObj(String::valueOf)
+                        .collect(Collectors.joining(",", "[", "]"));
+                final String validWidgetsIds = Arrays.stream(
+                        validWidgets.getArray().toArray()).mapToObj(String::valueOf)
+                        .collect(Collectors.joining(",", "[", "]"));
+                FileLog.d(TAG, "One or more widgets was removed. db_path=" + db.getPath()
+                        + " allWidgetsIds=" + allWidgetsIds
+                        + ", validWidgetsIds=" + validWidgetsIds);
+            }
         }
 
         /**
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 73be5be..eb68adb 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -259,8 +259,13 @@
         abortScrollerAnimation(true);
     }
 
+    protected void onScrollerAnimationAborted() {
+        // No-Op
+    }
+
     private void abortScrollerAnimation(boolean resetNextPage) {
         mScroller.abortAnimation();
+        onScrollerAnimationAborted();
         // We need to clean up the next page here to avoid computeScrollHelper from
         // updating current page on the pass.
         if (resetNextPage) {
@@ -555,11 +560,11 @@
             if (mAllowOverScroll) {
                 if (newPos < mMinScroll && oldPos >= mMinScroll) {
                     mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
-                    mScroller.abortAnimation();
+                    abortScrollerAnimation(false);
                     onEdgeAbsorbingScroll();
                 } else if (newPos > mMaxScroll && oldPos <= mMaxScroll) {
                     mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
-                    mScroller.abortAnimation();
+                    abortScrollerAnimation(false);
                     onEdgeAbsorbingScroll();
                 }
             }
@@ -569,7 +574,7 @@
             int finalPos = mOrientationHandler.getPrimaryValue(mScroller.getFinalX(),
                     mScroller.getFinalY());
             if (newPos == finalPos && mEdgeGlowLeft.isFinished() && mEdgeGlowRight.isFinished()) {
-                mScroller.abortAnimation();
+                abortScrollerAnimation(false);
             }
 
             invalidate();
@@ -767,6 +772,13 @@
         }
 
         if (mScroller.isFinished() && pageScrollChanged) {
+            // TODO(b/246283207): Remove logging once root cause of flake detected.
+            if (Utilities.IS_RUNNING_IN_TEST_HARNESS && !(this instanceof Workspace)) {
+                Log.d("b/246283207", this.getClass().getSimpleName() + "#onLayout() -> "
+                        + "if(mScroller.isFinished() && pageScrollChanged) -> getNextPage(): "
+                        + getNextPage() + ", getScrollForPage(getNextPage()): "
+                        + getScrollForPage(getNextPage()));
+            }
             setCurrentPage(getNextPage());
         }
         onPageScrollsInitialized();
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 486a68f..8b342ea 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -32,6 +32,7 @@
 import android.view.ViewGroup;
 
 import com.android.launcher3.CellLayout.ContainerType;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.views.ActivityContext;
@@ -80,7 +81,7 @@
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
-            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
 
             if ((lp.cellX <= cellX) && (cellX < lp.cellX + lp.cellHSpan)
                     && (lp.cellY <= cellY) && (cellY < lp.cellY + lp.cellVSpan)) {
@@ -107,7 +108,7 @@
     }
 
     public void setupLp(View child) {
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
         if (child instanceof NavigableAppWidgetHostView) {
             DeviceProfile profile = mActivity.getDeviceProfile();
             ((NavigableAppWidgetHostView) child).getWidgetInset(profile, mTempRect);
@@ -131,7 +132,7 @@
     }
 
     public void measureChild(View child) {
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
         final DeviceProfile dp = mActivity.getDeviceProfile();
 
         if (child instanceof NavigableAppWidgetHostView) {
@@ -175,7 +176,6 @@
         for (int i = 0; i < count; i++) {
             final View child = getChildAt(i);
             if (child.getVisibility() != GONE) {
-                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
                 layoutChild(child);
             }
         }
@@ -185,7 +185,7 @@
      * Core logic to layout a child for this ViewGroup.
      */
     public void layoutChild(View child) {
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
         if (child instanceof NavigableAppWidgetHostView) {
             NavigableAppWidgetHostView nahv = (NavigableAppWidgetHostView) child;
 
@@ -255,7 +255,7 @@
 
     @Override
     public void drawFolderLeaveBehindForIcon(FolderIcon child) {
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
         // While the folder is open, the position of the icon cannot change.
         lp.canReorder = false;
         if (mContainerType == HOTSEAT) {
@@ -266,7 +266,7 @@
 
     @Override
     public void clearFolderLeaveBehind(FolderIcon child) {
-        ((CellLayout.LayoutParams) child.getLayoutParams()).canReorder = true;
+        ((CellLayoutLayoutParams) child.getLayoutParams()).canReorder = true;
         if (mContainerType == HOTSEAT) {
             CellLayout cl = (CellLayout) getParent();
             cl.clearFolderLeaveBehind();
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 616b08a..f70511a 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -819,16 +819,6 @@
     }
 
     /**
-     * Compares the ratio of two quantities and returns whether that ratio is greater than the
-     * provided bound. Order of quantities does not matter. Bound should be a decimal representation
-     * of a percentage.
-     */
-    public static boolean isRelativePercentDifferenceGreaterThan(float first, float second,
-            float bound) {
-        return (Math.abs(first - second) / Math.abs((first + second) / 2.0f)) > bound;
-    }
-
-    /**
      * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually CCW. Parent
      * sizes represent the "space" that will rotate carrying inOutBounds along with it to determine
      * the final bounds.
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index b49d646..6aff83e 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -67,6 +67,7 @@
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.FolderDotInfo;
 import com.android.launcher3.dragndrop.DragController;
@@ -106,6 +107,7 @@
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.WallpaperOffsetInterpolator;
+import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.LauncherAppWidgetHost;
 import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -152,6 +154,8 @@
 
     public static final int DEFAULT_PAGE = 0;
 
+    private final int mAllAppsIconSize;
+
     private LayoutTransition mLayoutTransition;
     @Thunk final WallpaperManager mWallpaperManager;
 
@@ -286,7 +290,7 @@
         mLauncher = Launcher.getLauncher(context);
         mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
         mWallpaperManager = WallpaperManager.getInstance(context);
-
+        mAllAppsIconSize = mLauncher.getDeviceProfile().allAppsIconSizePx;
         mWallpaperOffset = new WallpaperOffsetInterpolator(this);
 
         setHapticFeedbackEnabled(false);
@@ -559,7 +563,7 @@
         }
 
         int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
-        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, cellHSpan, 1);
+        CellLayoutLayoutParams lp = new CellLayoutLayoutParams(0, 0, cellHSpan, 1);
         lp.canReorder = false;
         if (!firstPage.addViewToCellLayout(mQsb, 0, R.id.search_container_workspace, lp, true)) {
             Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
@@ -1671,8 +1675,14 @@
             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
         }
 
-        if (child instanceof BubbleTextView && !dragOptions.isAccessibleDrag) {
-            dragOptions.preDragCondition = ((BubbleTextView) child).startLongPressAction();
+        if (child instanceof BubbleTextView) {
+            BubbleTextView btv = (BubbleTextView) child;
+            if (!dragOptions.isAccessibleDrag) {
+                dragOptions.preDragCondition = btv.startLongPressAction();
+            }
+            if (btv.isDisplaySearchResult()) {
+                dragOptions.preDragEndScale = (float) mAllAppsIconSize / btv.getIconSize();
+            }
         }
 
         final DragView dv;
@@ -1796,7 +1806,7 @@
 
     boolean willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout) {
         if (dropOverView != null) {
-            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) dropOverView.getLayoutParams();
             if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) {
                 return false;
             }
@@ -1831,7 +1841,7 @@
     }
     boolean willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView) {
         if (dropOverView != null) {
-            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
+            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) dropOverView.getLayoutParams();
             if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) {
                 return false;
             }
@@ -2048,7 +2058,7 @@
                     }
 
                     // update the item's position after drop
-                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
+                    CellLayoutLayoutParams lp = (CellLayoutLayoutParams) cell.getLayoutParams();
                     lp.cellX = lp.tmpCellX = mTargetCell[0];
                     lp.cellY = lp.tmpCellY = mTargetCell[1];
                     lp.cellHSpan = item.spanX;
@@ -2074,7 +2084,7 @@
                     }
 
                     // If we can't find a drop location, we return the item to its original position
-                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
+                    CellLayoutLayoutParams lp = (CellLayoutLayoutParams) cell.getLayoutParams();
                     mTargetCell[0] = lp.cellX;
                     mTargetCell[1] = lp.cellY;
                     CellLayout layout = (CellLayout) cell.getParent().getParent();
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index 7e6e1b6..0b3a62f 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -19,6 +19,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.data.ItemInfo;
@@ -111,11 +112,11 @@
         }
 
         ViewGroup.LayoutParams genericLp = child.getLayoutParams();
-        CellLayout.LayoutParams lp;
-        if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
-            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
+        CellLayoutLayoutParams lp;
+        if (genericLp == null || !(genericLp instanceof CellLayoutLayoutParams)) {
+            lp = new CellLayoutLayoutParams(x, y, spanX, spanY);
         } else {
-            lp = (CellLayout.LayoutParams) genericLp;
+            lp = (CellLayoutLayoutParams) genericLp;
             lp.cellX = x;
             lp.cellY = y;
             lp.cellHSpan = spanX;
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 79214e8..dd47592 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -23,6 +23,7 @@
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
 import com.android.launcher3.dragndrop.DragView;
@@ -244,7 +245,7 @@
     }
 
     private boolean performResizeAction(int action, View host, LauncherAppWidgetInfo info) {
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) host.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) host.getLayoutParams();
         CellLayout layout = (CellLayout) host.getParent().getParent();
         layout.markCellsAsUnoccupiedForView(host);
 
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index aa9cfd1..08b42cd 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -84,6 +84,7 @@
 
     /** Invoke when the current search session is finished. */
     public void onClearSearchResult() {
+        getMainAdapterProvider().clearHighlightedItem();
         animateToSearchState(false);
         rebindAdapters();
     }
@@ -92,6 +93,7 @@
      * Sets results list for search
      */
     public void setSearchResults(ArrayList<AdapterItem> results) {
+        getMainAdapterProvider().clearHighlightedItem();
         if (getSearchResultList().setSearchResults(results)) {
             getSearchRecyclerView().onSearchResultsChanged();
         }
@@ -179,11 +181,6 @@
     }
 
     @Override
-    protected boolean shouldShowTabs() {
-        return super.shouldShowTabs() && !isSearching();
-    }
-
-    @Override
     public boolean isSearching() {
         return mIsSearching;
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 33d2f2b..fe0230a 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -40,25 +40,29 @@
         BaseAllAppsAdapter<T> {
 
     public static final String TAG = "AppsGridAdapter";
-    private final GridLayoutManager mGridLayoutMgr;
-    private final GridSpanSizer mGridSizer;
+    private final AppsGridLayoutManager mGridLayoutMgr;
 
     public AllAppsGridAdapter(T activityContext, LayoutInflater inflater,
             AlphabeticalAppsList apps, BaseAdapterProvider[] adapterProviders) {
         super(activityContext, inflater, apps, adapterProviders);
-        mGridSizer = new GridSpanSizer();
         mGridLayoutMgr = new AppsGridLayoutManager(mActivityContext);
-        mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
+        mGridLayoutMgr.setSpanSizeLookup(new GridSpanSizer());
         setAppsPerRow(activityContext.getDeviceProfile().numShownAllAppsColumns);
     }
 
     /**
      * Returns the grid layout manager.
      */
-    public RecyclerView.LayoutManager getLayoutManager() {
+    public AppsGridLayoutManager getLayoutManager() {
         return mGridLayoutMgr;
     }
 
+    /** @return the column index that the given adapter index falls. */
+    public int getSpanIndex(int adapterIndex) {
+        AppsGridLayoutManager lm = getLayoutManager();
+        return lm.getSpanSizeLookup().getSpanIndex(adapterIndex, lm.getSpanCount());
+    }
+
     /**
      * A subclass of GridLayoutManager that overrides accessibility values during app search.
      */
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 1cec9d0..001b494 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -39,10 +39,9 @@
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.util.MultiAdditivePropertyFactory;
+import com.android.launcher3.util.MultiPropertyFactory;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.views.ScrimView;
 
@@ -141,9 +140,9 @@
 
     private ScrimView mScrimView;
 
-    private final MultiAdditivePropertyFactory<View>
-            mAppsViewTranslationYPropertyFactory = new MultiAdditivePropertyFactory<>(
-            "appsViewTranslationY", View.TRANSLATION_Y);
+    private final MultiPropertyFactory<View>
+            mAppsViewTranslationYPropertyFactory = new MultiPropertyFactory<>(
+            "appsViewTranslationY", View.TRANSLATION_Y, Float::sum);
     private MultiValueAlpha mAppsViewAlpha;
 
     private boolean mIsTablet;
@@ -228,6 +227,11 @@
     public void setStateWithAnimation(LauncherState toState,
             StateAnimationConfig config, PendingAnimation builder) {
         if (mLauncher.isInState(ALL_APPS) && !ALL_APPS.equals(toState)) {
+            // For atomic animations, we close the keyboard immediately.
+            if (!config.userControlled && !mLauncher.isKeyboardSyncEnabled()) {
+                mLauncher.getAppsView().getSearchUiManager().getEditText().hideKeyboard();
+            }
+
             builder.addEndListener(success -> {
                 // Reset pull back progress and alpha after switching states.
                 ALL_APPS_PULL_BACK_TRANSLATION.set(this, 0f);
@@ -238,7 +242,7 @@
                 // the keyboard open and then changes their mind and swipes back up, we want the
                 // keyboard to remain open. However an onCancel signal is sent to the listeners
                 // (success = false), so we need to check for that.
-                if (success) {
+                if (config.userControlled && success) {
                     mLauncher.getAppsView().getSearchUiManager().getEditText().hideKeyboard();
                 }
             });
@@ -314,7 +318,6 @@
      * TODO: This logic should go in {@link LauncherState}
      */
     private void onProgressAnimationEnd() {
-        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
         if (Float.compare(mProgress, 1f) == 0) {
             mAppsView.reset(false /* animate */);
             mLauncher.getAppsView().getSearchUiManager().getEditText().hideKeyboard();
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index 879a159..70c1e18 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -88,6 +88,9 @@
     public static final float PULL_MULTIPLIER = .02f;
     public static final float FLING_VELOCITY_MULTIPLIER = 1200f;
 
+    // Render the header protection at all times to debug clipping issues.
+    private static final boolean DEBUG_HEADER_PROTECTION = false;
+
     private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Rect mInsets = new Rect();
 
@@ -128,7 +131,6 @@
     private final int mScrimColor;
     private final int mHeaderProtectionColor;
     protected final float mHeaderThreshold;
-    private int mHeaderBottomAdjustment;
     private ScrimView mScrimView;
     private int mHeaderColor;
     private int mTabsProtectionAlpha;
@@ -141,8 +143,6 @@
         mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
         mHeaderThreshold = getResources().getDimensionPixelSize(
                 R.dimen.dynamic_grid_cell_border_spacing);
-        mHeaderBottomAdjustment = getResources().getDimensionPixelSize(
-                R.dimen.all_apps_header_bottom_adjustment);
         mHeaderProtectionColor = Themes.getAttrColor(context, R.attr.allappsHeaderProtectionColor);
 
         mWorkManager = new WorkProfileManager(
@@ -491,6 +491,7 @@
 
         mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
         mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
+        mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
 
         if (mUsingTabs) {
             mAH.get(AdapterHolder.MAIN).setup(mViewPager.getChildAt(0), mPersonalMatcher);
@@ -523,15 +524,18 @@
 
         mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
         mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
+        mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
     }
 
     protected void updateSearchResultsVisibility() {
         if (isSearching()) {
             getSearchRecyclerView().setVisibility(VISIBLE);
             getAppsRecyclerViewContainer().setVisibility(GONE);
+            mHeader.setVisibility(GONE);
         } else {
             getSearchRecyclerView().setVisibility(GONE);
             getAppsRecyclerViewContainer().setVisibility(VISIBLE);
+            mHeader.setVisibility(VISIBLE);
         }
         if (mHeader.isSetUp()) {
             mHeader.setActiveRV(getCurrentPage());
@@ -725,17 +729,29 @@
         if (!mHeader.isHeaderProtectionSupported()) {
             return;
         }
-        mHeaderPaint.setColor(mHeaderColor);
-        mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor)));
+        if (DEBUG_HEADER_PROTECTION) {
+            mHeaderPaint.setColor(Color.MAGENTA);
+            mHeaderPaint.setAlpha(255);
+        } else {
+            mHeaderPaint.setColor(mHeaderColor);
+            mHeaderPaint.setAlpha((int) (getAlpha() * Color.alpha(mHeaderColor)));
+        }
         if (mHeaderPaint.getColor() != mScrimColor && mHeaderPaint.getColor() != 0) {
             int bottom = getHeaderBottom();
+            FloatingHeaderView headerView = getFloatingHeaderView();
             if (!mUsingTabs) {
-                bottom += getFloatingHeaderView().getPaddingBottom() - mHeaderBottomAdjustment;
+                // Add protection which is otherwise added when tabs scroll up.
+                bottom += headerView.getTabsAdditionalPaddingTop();
             }
             canvas.drawRect(0, 0, canvas.getWidth(), bottom, mHeaderPaint);
-            int tabsHeight = getFloatingHeaderView().getPeripheralProtectionHeight();
+            int tabsHeight = headerView.getPeripheralProtectionHeight();
             if (mTabsProtectionAlpha > 0 && tabsHeight != 0) {
-                mHeaderPaint.setAlpha((int) (getAlpha() * mTabsProtectionAlpha));
+                if (DEBUG_HEADER_PROTECTION) {
+                    mHeaderPaint.setColor(Color.BLUE);
+                    mHeaderPaint.setAlpha(255);
+                } else {
+                    mHeaderPaint.setAlpha((int) (getAlpha() * mTabsProtectionAlpha));
+                }
                 canvas.drawRect(0, bottom, canvas.getWidth(), bottom + tabsHeight, mHeaderPaint);
             }
         }
@@ -782,6 +798,17 @@
         return mActivityContext.getDeviceProfile().isTablet ? mBottomSheetBackground : this;
     }
 
+    /**
+     * Sets whether the view or its children should react to the window inset.
+     * Used for when exiting all apps -> workspace and determines if window inset
+     * should be applied.. ex) the work mode switch.
+     */
+    public void setApplyWindowInset(boolean shouldApplyWindowInset) {
+        if (mWorkManager.getWorkModeSwitch() != null) {
+            mWorkManager.getWorkModeSwitch().setApplyWindowInset(shouldApplyWindowInset);
+        }
+    }
+
     /** Holds a {@link BaseAllAppsAdapter} and related fields. */
     public class AdapterHolder {
         public static final int MAIN = 0;
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 8ec2aeb..f31379e 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -272,11 +272,12 @@
         }
         mMaxTranslation += mFloatingRowsHeight;
         if (!mTabsHidden) {
-            mMaxTranslation += mTabsAdditionalPaddingBottom;
+            mMaxTranslation += mTabsAdditionalPaddingBottom
+                    + getResources().getDimensionPixelSize(R.dimen.all_apps_tabs_margin_top);
         }
     }
 
-    public int getMaxTranslation() {
+    int getMaxTranslation() {
         if (mMaxTranslation == 0 && (mTabsHidden || mFloatingRowsCollapsed)) {
             return getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_bottom_padding);
         } else if (mMaxTranslation > 0 && mTabsHidden) {
@@ -333,7 +334,8 @@
 
         int clipTop = getPaddingTop() - mTabsAdditionalPaddingTop;
         if (mTabsHidden) {
-            clipTop += getPaddingBottom() - mTabsAdditionalPaddingBottom;
+            // Add back spacing that is otherwise covered by the tabs.
+            clipTop += mTabsAdditionalPaddingTop;
         }
         mRVClip.top = mTabsHidden || mFloatingRowsCollapsed ? clipTop : 0;
         mHeaderClip.top = clipTop;
@@ -404,6 +406,10 @@
         return mFloatingRowsHeight;
     }
 
+    int getTabsAdditionalPaddingTop() {
+        return mTabsAdditionalPaddingTop;
+    }
+
     int getTabsAdditionalPaddingBottom() {
         return mTabsAdditionalPaddingBottom;
     }
@@ -472,7 +478,7 @@
     /**
      * Returns visible height of FloatingHeaderView contents requiring header protection
      */
-    public int getPeripheralProtectionHeight() {
+    int getPeripheralProtectionHeight() {
         if (!mHeaderProtectionSupported) {
             return 0;
         }
diff --git a/src/com/android/launcher3/allapps/SearchTransitionController.java b/src/com/android/launcher3/allapps/SearchTransitionController.java
index 03529af..bde4fa6 100644
--- a/src/com/android/launcher3/allapps/SearchTransitionController.java
+++ b/src/com/android/launcher3/allapps/SearchTransitionController.java
@@ -20,30 +20,37 @@
 
 import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
 
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 
 import android.animation.ObjectAnimator;
 import android.graphics.drawable.Drawable;
 import android.util.FloatProperty;
+import android.util.Log;
 import android.view.View;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.model.data.ItemInfo;
 
 /** Coordinates the transition between Search and A-Z in All Apps. */
 public class SearchTransitionController {
 
+    private static final String LOG_TAG = "SearchTransitionCtrl";
+
     // Interpolator when the user taps the QSB while already in All Apps.
-    private static final Interpolator DEFAULT_INTERPOLATOR_WITHIN_ALL_APPS = DEACCEL_1_7;
+    private static final Interpolator INTERPOLATOR_WITHIN_ALL_APPS = DEACCEL_1_7;
     // Interpolator when the user taps the QSB from home screen, so transition to all apps is
     // happening simultaneously.
-    private static final Interpolator DEFAULT_INTERPOLATOR_TRANSITIONING_TO_ALL_APPS = LINEAR;
+    private static final Interpolator INTERPOLATOR_TRANSITIONING_TO_ALL_APPS = INSTANT;
 
     /**
      * These values represent points on the [0, 1] animation progress spectrum. They are used to
@@ -103,9 +110,11 @@
         boolean inAllApps = Launcher.getLauncher(
                 mAllAppsContainerView.getContext()).getStateManager().isInStableState(
                 LauncherState.ALL_APPS);
+        if (!inAllApps) {
+            duration = 0;  // Don't want to animate when coming from QSB.
+        }
         mSearchToAzAnimator.setDuration(duration).setInterpolator(
-                inAllApps ? DEFAULT_INTERPOLATOR_WITHIN_ALL_APPS
-                        : DEFAULT_INTERPOLATOR_TRANSITIONING_TO_ALL_APPS);
+                inAllApps ? INTERPOLATOR_WITHIN_ALL_APPS : INTERPOLATOR_TRANSITIONING_TO_ALL_APPS);
         mSearchToAzAnimator.addListener(forEndCallback(() -> mSearchToAzAnimator = null));
         if (!goingToSearch) {
             mSearchToAzAnimator.addListener(forSuccessCallback(() -> {
@@ -117,6 +126,7 @@
         mSearchToAzAnimator.addListener(forSuccessCallback(onEndRunnable));
 
         mAllAppsContainerView.getFloatingHeaderView().setFloatingRowsCollapsed(true);
+        mAllAppsContainerView.getFloatingHeaderView().setVisibility(VISIBLE);
         mAllAppsContainerView.getAppsRecyclerViewContainer().setVisibility(VISIBLE);
         getSearchRecyclerView().setVisibility(VISIBLE);
         getSearchRecyclerView().setChildAttachedConsumer(this::onSearchChildAttached);
@@ -144,8 +154,11 @@
             headerView.setAlpha(clampToProgress(searchToAzProgress, 0.8f, 1f));
 
             // Account for the additional padding added for the tabs.
-            appsTranslationY -=
-                    headerView.getPaddingTop() - headerView.getTabsAdditionalPaddingBottom();
+            appsTranslationY +=
+                    headerView.getTabsAdditionalPaddingBottom()
+                            + mAllAppsContainerView.getResources().getDimensionPixelOffset(
+                                    R.dimen.all_apps_tabs_margin_top)
+                            - headerView.getPaddingTop();
         }
 
         View appsContainer = mAllAppsContainerView.getAppsRecyclerViewContainer();
@@ -165,6 +178,7 @@
         int appRowHeight = 0;
         Integer top = null;
         SearchRecyclerView searchRecyclerView = getSearchRecyclerView();
+
         for (int i = 0; i < searchRecyclerView.getChildCount(); i++) {
             View searchResultView = searchRecyclerView.getChildAt(i);
             if (searchResultView == null) {
@@ -175,7 +189,9 @@
                 top = searchResultView.getTop();
             }
 
-            if (searchResultView instanceof BubbleTextView) {
+            if (searchResultView instanceof BubbleTextView
+                    && searchResultView.getTag() instanceof ItemInfo
+                    && ((ItemInfo) searchResultView.getTag()).itemType == ITEM_TYPE_APPLICATION) {
                 // The first app icon will set appRowHeight, which will also contribute to
                 // totalHeight. Additional app icons should remove the appRowHeight to remain in
                 // the same row as the first app.
@@ -185,6 +201,8 @@
                     totalHeight += appRowHeight;
                 }
                 // Don't scale/fade app row.
+                searchResultView.setScaleY(1);
+                searchResultView.setAlpha(1);
                 continue;
             }
 
@@ -220,19 +238,48 @@
             float scaleY = 1 - mSearchToAzProgress;
             int scaledHeight = (int) (searchResultView.getHeight() * scaleY);
             searchResultView.setScaleY(scaleY);
-            searchResultView.setY(top + totalHeight);
 
-            numSearchResultsAnimated++;
-            totalHeight += scaledHeight;
+            // For rows with multiple elements, only count the height once and translate elements to
+            // the same y position.
+            int y = top + totalHeight;
+            int spanIndex = getSpanIndex(searchRecyclerView, adapterPosition);
+            if (spanIndex > 0) {
+                // Continuation of an existing row; move this item into the row.
+                y -= scaledHeight;
+            } else {
+                // Start of a new row contributes to total height and animation stagger.
+                numSearchResultsAnimated++;
+                totalHeight += scaledHeight;
+            }
+            searchResultView.setY(y);
         }
 
         return totalHeight - appRowHeight;
     }
 
+    /** @return the column that the view at this position is found (0 assumed if indeterminate). */
+    private int getSpanIndex(SearchRecyclerView searchRecyclerView, int adapterPosition) {
+        if (adapterPosition == NO_POSITION) {
+            Log.w(LOG_TAG, "Can't determine span index - child not found in adapter");
+            return 0;
+        }
+        if (!(searchRecyclerView.getAdapter() instanceof AllAppsGridAdapter<?>)) {
+            Log.e(LOG_TAG, "Search RV doesn't have an AllAppsGridAdapter?");
+            // This case shouldn't happen, but for debug devices we will continue to create a more
+            // visible crash.
+            if (!Utilities.IS_DEBUG_DEVICE) {
+                return 0;
+            }
+        }
+        AllAppsGridAdapter<?> adapter = (AllAppsGridAdapter<?>) searchRecyclerView.getAdapter();
+        return adapter.getSpanIndex(adapterPosition);
+    }
+
     /** Called just before a child is attached to the SearchRecyclerView. */
     private void onSearchChildAttached(View child) {
         // Avoid allocating hardware layers for alpha changes.
         child.forceHasOverlappingRendering(false);
+        child.setPivotY(0);
         if (mSearchToAzProgress > 0) {
             // Before the child is rendered, apply the animation including it to avoid flicker.
             updateSearchRecyclerViewProgress();
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index fedc91f..0a938b2 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -52,6 +52,7 @@
     private int mFlags;
     private boolean mWorkEnabled;
     private boolean mOnWorkTab;
+    private boolean mApplyWindowInset;
 
     public WorkModeSwitch(Context context) {
         this(context, null, 0);
@@ -97,12 +98,10 @@
                 bottomMargin += dp.hotseatQsbHeight;
             }
 
-            if (!dp.isGestureMode) {
-                if (dp.isTaskbarPresent) {
-                    bottomMargin += dp.taskbarSize;
-                } else {
-                    bottomMargin += insets.bottom;
-                }
+            if (!dp.isGestureMode && dp.isTaskbarPresent) {
+                bottomMargin += dp.taskbarSize;
+            } else {
+                bottomMargin += insets.bottom;
             }
 
             lp.bottomMargin = bottomMargin;
@@ -170,12 +169,14 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        if (Utilities.ATLEAST_R && isEnabled()) {
+        if (!Utilities.ATLEAST_R || !mApplyWindowInset) {
+            return insets;
+        }
+        if (insets.isVisible(WindowInsets.Type.ime())) {
+            Insets keyboardInsets = insets.getInsets(WindowInsets.Type.ime());
+            setTranslationY(mInsets.bottom - keyboardInsets.bottom);
+        } else {
             setTranslationY(0);
-            if (insets.isVisible(WindowInsets.Type.ime())) {
-                Insets keyboardInsets = insets.getInsets(WindowInsets.Type.ime());
-                setTranslationY(mInsets.bottom - keyboardInsets.bottom);
-            }
         }
         return insets;
     }
@@ -197,4 +198,8 @@
     private void removeFlag(int flag) {
         mFlags &= ~flag;
     }
+
+    public void setApplyWindowInset(boolean applyWindowInset){
+        mApplyWindowInset = applyWindowInset;
+    }
 }
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 886460e..4c461aa 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -84,7 +84,10 @@
         mTextConversions = extractTextConversions(s);
     }
 
-    private static String[] extractTextConversions(CharSequence text) {
+    /**
+     * Extract text conversions from composing text and send them for search.
+     */
+    public static String[] extractTextConversions(CharSequence text) {
         if (text instanceof SpannableStringBuilder) {
             SpannableStringBuilder spanned = (SpannableStringBuilder) text;
             SuggestionSpan[] suggestionSpans =
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index d2d7a6c..ab47097 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -22,6 +22,7 @@
 import android.os.Handler;
 
 import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
@@ -68,7 +69,8 @@
     public void doSearch(String query, SearchCallback<AdapterItem> callback) {
         mAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
-            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            public void execute(@NonNull final LauncherAppState app,
+                    @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
                 ArrayList<AdapterItem> result = getTitleMatchResult(apps.data, query);
                 if (mAddNoResultsMessage && result.isEmpty()) {
                     result.add(getEmptyMessageAdapterItem(query));
diff --git a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
index a95bd51..4fb732d 100644
--- a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
@@ -84,4 +84,9 @@
     public RecyclerView.ItemDecoration getDecorator() {
         return mDecoration;
     }
+
+    @Override
+    public void clearHighlightedItem() {
+        mHighlightedView = null;
+    }
 }
diff --git a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
index bc52784..3890741 100644
--- a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
@@ -58,4 +58,9 @@
      * Returns the item decorator.
      */
     public abstract RecyclerView.ItemDecoration getDecorator();
+
+    /**
+     * Clear the highlighted view.
+     */
+    public abstract void clearHighlightedItem();
 }
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 12b4223..b55a1e4 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -83,6 +83,7 @@
         EXAGGERATED_EASE = new PathInterpolator(exaggeratedEase);
     }
 
+    public static final Interpolator OVERSHOOT_0_75 = new OvershootInterpolator(0.75f);
     public static final Interpolator OVERSHOOT_1_2 = new OvershootInterpolator(1.2f);
     public static final Interpolator OVERSHOOT_1_7 = new OvershootInterpolator(1.7f);
 
diff --git a/src/com/android/launcher3/celllayout/CellLayoutLayoutParams.java b/src/com/android/launcher3/celllayout/CellLayoutLayoutParams.java
new file mode 100644
index 0000000..b14ae8d
--- /dev/null
+++ b/src/com/android/launcher3/celllayout/CellLayoutLayoutParams.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2022 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.celllayout;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Represents the logic of where a view is in a CellLayout and its size
+ */
+public class CellLayoutLayoutParams extends ViewGroup.MarginLayoutParams {
+    /**
+     * Horizontal location of the item in the grid.
+     */
+    @ViewDebug.ExportedProperty
+    public int cellX;
+
+    /**
+     * Vertical location of the item in the grid.
+     */
+    @ViewDebug.ExportedProperty
+    public int cellY;
+
+    /**
+     * Temporary horizontal location of the item in the grid during reorder
+     */
+    public int tmpCellX;
+
+    /**
+     * Temporary vertical location of the item in the grid during reorder
+     */
+    public int tmpCellY;
+
+    /**
+     * Indicates that the temporary coordinates should be used to layout the items
+     */
+    public boolean useTmpCoords;
+
+    /**
+     * Number of cells spanned horizontally by the item.
+     */
+    @ViewDebug.ExportedProperty
+    public int cellHSpan;
+
+    /**
+     * Number of cells spanned vertically by the item.
+     */
+    @ViewDebug.ExportedProperty
+    public int cellVSpan;
+
+    /**
+     * Indicates whether the item will set its x, y, width and height parameters freely,
+     * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
+     */
+    public boolean isLockedToGrid = true;
+
+    /**
+     * Indicates whether this item can be reordered. Always true except in the case of the
+     * the AllApps button and QSB place holder.
+     */
+    public boolean canReorder = true;
+
+    // X coordinate of the view in the layout.
+    @ViewDebug.ExportedProperty
+    public int x;
+    // Y coordinate of the view in the layout.
+    @ViewDebug.ExportedProperty
+    public int y;
+
+    public boolean dropped;
+
+    public CellLayoutLayoutParams(Context c, AttributeSet attrs) {
+        super(c, attrs);
+        cellHSpan = 1;
+        cellVSpan = 1;
+    }
+
+    public CellLayoutLayoutParams(ViewGroup.LayoutParams source) {
+        super(source);
+        cellHSpan = 1;
+        cellVSpan = 1;
+    }
+
+    public CellLayoutLayoutParams(CellLayoutLayoutParams source) {
+        super(source);
+        this.cellX = source.cellX;
+        this.cellY = source.cellY;
+        this.cellHSpan = source.cellHSpan;
+        this.cellVSpan = source.cellVSpan;
+    }
+
+    public CellLayoutLayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
+        super(CellLayoutLayoutParams.MATCH_PARENT, CellLayoutLayoutParams.MATCH_PARENT);
+        this.cellX = cellX;
+        this.cellY = cellY;
+        this.cellHSpan = cellHSpan;
+        this.cellVSpan = cellVSpan;
+    }
+
+    /**
+     * Updates the {@link CellLayoutLayoutParams} with the right measures using their
+     * full/invariant device profile sizes.
+     */
+    public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
+            int rowCount, Point borderSpace, @Nullable Rect inset) {
+        setup(cellWidth, cellHeight, invertHorizontally, colCount, rowCount, 1.0f, 1.0f,
+                borderSpace, inset);
+    }
+
+    /**
+     * Use this method, as opposed to {@link #setup(int, int, boolean, int, int, Point, Rect)},
+     * if the view needs to be scaled.
+     *
+     * ie. In multi-window mode, we setup widgets so that they are measured and laid out
+     * using their full/invariant device profile sizes.
+     */
+    public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
+            int rowCount, float cellScaleX, float cellScaleY, Point borderSpace,
+            @Nullable Rect inset) {
+        if (isLockedToGrid) {
+            final int myCellHSpan = cellHSpan;
+            final int myCellVSpan = cellVSpan;
+            int myCellX = useTmpCoords ? tmpCellX : cellX;
+            int myCellY = useTmpCoords ? tmpCellY : cellY;
+
+            if (invertHorizontally) {
+                myCellX = colCount - myCellX - cellHSpan;
+            }
+
+            int hBorderSpacing = (myCellHSpan - 1) * borderSpace.x;
+            int vBorderSpacing = (myCellVSpan - 1) * borderSpace.y;
+
+            float myCellWidth = ((myCellHSpan * cellWidth) + hBorderSpacing) / cellScaleX;
+            float myCellHeight = ((myCellVSpan * cellHeight) + vBorderSpacing) / cellScaleY;
+
+            width = Math.round(myCellWidth) - leftMargin - rightMargin;
+            height = Math.round(myCellHeight) - topMargin - bottomMargin;
+            x = leftMargin + (myCellX * cellWidth) + (myCellX * borderSpace.x);
+            y = topMargin + (myCellY * cellHeight) + (myCellY * borderSpace.y);
+
+            if (inset != null) {
+                x -= inset.left;
+                y -= inset.top;
+                width += inset.left + inset.right;
+                height += inset.top + inset.bottom;
+            }
+        }
+    }
+
+    /**
+     * Sets the position to the provided point
+     */
+    public void setCellXY(Point point) {
+        cellX = point.x;
+        cellY = point.y;
+    }
+
+    /**
+     * @return the string representation of the position of the {@link CellLayoutLayoutParams}
+     */
+    public String toString() {
+        return "(" + this.cellX + ", " + this.cellY + ")";
+    }
+}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index ffce570..3a530af 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -37,7 +37,8 @@
 
     public static final String FLAGS_PREF_NAME = "featureFlags";
 
-    private FeatureFlags() { }
+    private FeatureFlags() {
+    }
 
     public static boolean showFlagTogglerUi(Context context) {
         return Utilities.IS_DEBUG_DEVICE && Utilities.isDevelopersOptionsEnabled(context);
@@ -61,7 +62,7 @@
      * To add a new flag that can be toggled through the flags UI:
      *
      * Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"),
-     *    and set a default value for the flag. This will be the default value on Debug builds.
+     * and set a default value for the flag. This will be the default value on Debug builds.
      */
     public static final BooleanFlag ENABLE_INPUT_CONSUMER_REASON_LOGGING = getDebugFlag(
             "ENABLE_INPUT_CONSUMER_REASON_LOGGING",
@@ -84,9 +85,6 @@
     public static final BooleanFlag KEYGUARD_ANIMATION = getDebugFlag(
             "KEYGUARD_ANIMATION", false, "Enable animation for keyguard going away on wallpaper");
 
-    public static final BooleanFlag ENABLE_QUICKSTEP_LIVE_TILE = getDebugFlag(
-            "ENABLE_QUICKSTEP_LIVE_TILE", true, "Enable live tile in Quickstep overview");
-
     public static final BooleanFlag ENABLE_DEVICE_SEARCH = new DeviceFlag(
             "ENABLE_DEVICE_SEARCH", true, "Allows on device search in all apps");
 
@@ -94,12 +92,16 @@
             getDebugFlag("ENABLE_FLOATING_SEARCH_BAR", false,
                     "Keep All Apps search bar at the bottom (but above keyboard if open)");
 
-    public static final BooleanFlag ENABLE_QUICK_SEARCH = new DeviceFlag("ENABLE_QUICK_SEARCH",
-            true, "Use quick search behavior.");
+    public static final BooleanFlag ENABLE_QUICK_LAUNCH_V2 = new DeviceFlag(
+            "ENABLE_QUICK_LAUNCH_V2", false, "Use quick launch v2 "
+            + "behavior. Quick search and quick launch v1 would be unavailable if this is enabled");
 
     public static final BooleanFlag ENABLE_HIDE_HEADER = new DeviceFlag("ENABLE_HIDE_HEADER",
             true, "Hide header on keyboard before typing in all apps");
 
+    public static final BooleanFlag ENABLE_HIDE_HEADER_STATIC = new DeviceFlag(
+            "ENABLE_HIDE_HEADER_STATIC", false, "Hide keyboard suggestion strip");
+
     public static final BooleanFlag COLLECT_SEARCH_HISTORY = new DeviceFlag(
             "COLLECT_SEARCH_HISTORY", false, "Allow launcher to collect search history for log");
 
@@ -157,14 +159,14 @@
     /**
      * Enables region sampling for text color: Needs system health assessment before turning on
      */
-    public static final BooleanFlag ENABLE_REGION_SAMPLING =  getDebugFlag(
+    public static final BooleanFlag ENABLE_REGION_SAMPLING = getDebugFlag(
             "ENABLE_REGION_SAMPLING", false,
             "Enable region sampling to determine color of text on screen.");
 
     public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS =
             getDebugFlag(
-            "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
-            "Always use hardware optimization for folder animations.");
+                    "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
+                    "Always use hardware optimization for folder animations.");
 
     public static final BooleanFlag SEPARATE_RECENTS_ACTIVITY = getDebugFlag(
             "SEPARATE_RECENTS_ACTIVITY", false,
@@ -238,6 +240,10 @@
             "ENABLE_ALL_APPS_ONE_SEARCH_IN_TASKBAR", false,
             "Enables One Search box in Taskbar All Apps.");
 
+    public static final BooleanFlag ENABLE_TASKBAR_IN_OVERVIEW = getDebugFlag(
+            "ENABLE_TASKBAR_IN_OVERVIEW", false,
+            "Enables accessing the system Taskbar in overview.");
+
     public static final BooleanFlag ENABLE_SPLIT_FROM_WORKSPACE = getDebugFlag(
             "ENABLE_SPLIT_FROM_WORKSPACE", true,
             "Enable initiating split screen from workspace.");
@@ -249,11 +255,6 @@
     public static final BooleanFlag ENABLE_ONE_SEARCH_MOTION = new DeviceFlag(
             "ENABLE_ONE_SEARCH_MOTION", true, "Enables animations in OneSearch.");
 
-    public static final BooleanFlag ENABLE_KEYBOARD_TRANSITION_SYNC = new DeviceFlag(
-            "ENABLE_KEYBOARD_TRANSITION_SYNC", IS_STUDIO_BUILD,
-            "Enable option to synchronize the keyboard open and close animations when transitioning"
-                    + " between home and all apps");
-
     public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = new DeviceFlag(
             "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", true,
             "Enable option to show keyboard when going to all-apps");
@@ -274,10 +275,6 @@
             "USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES", false,
             "Use local overrides for search request timeout");
 
-    public static final BooleanFlag USE_APP_SEARCH_FOR_WEB = getDebugFlag(
-            "USE_APP_SEARCH_FOR_WEB", false,
-            "Use app search to request zero state web suggestions");
-
     public static final BooleanFlag CONTINUOUS_VIEW_TREE_CAPTURE = getDebugFlag(
             "CONTINUOUS_VIEW_TREE_CAPTURE", false, "Capture View tree every frame");
 
@@ -285,13 +282,23 @@
             "FOLDABLE_WORKSPACE_REORDER", true,
             "In foldables, when reordering the icons and widgets, is now going to use both sides");
 
-    public static final BooleanFlag SHOW_SEARCH_EDUCARD_QSB = new DeviceFlag(
-            "SHOW_SEARCH_EDUCARD_QSB", false, "Shows Search Educard for QSB entry in OneSearch.");
+    public static final BooleanFlag ENABLE_WIDGET_PICKER_DEPTH = new DeviceFlag(
+            "ENABLE_WIDGET_PICKER_DEPTH", false, "Enable changing depth in widget picker.");
 
-    public static final BooleanFlag ENABLE_IME_LATENCY_LOGGER = getDebugFlag(
-            "ENABLE_IME_LATENCY_LOGGER", false,
-            "Enable option to log the keyboard latency for both atomic and controlled keyboard "
-                    + "animations on an EditText");
+    public static final BooleanFlag SHOW_DELIGHTFUL_PAGINATION_FOLDER = new DeviceFlag(
+            "SHOW_DELIGHTFUL_PAGINATION_FOLDER", false,
+            "Enable showing the new 'delightful pagination'"
+                    + " which is a brand new animation for folder pagination");
+
+    public static final BooleanFlag POPUP_MATERIAL_U = new DeviceFlag(
+            "POPUP_MATERIAL_U", false, "Switch popup UX to use material U");
+
+    public static final BooleanFlag SHOW_HOME_GARDENING = new DeviceFlag(
+            "SHOW_HOME_GARDENING", false,
+            "Show the new home gardening mode");
+
+    public static final BooleanFlag ENABLE_TRANSIENT_TASKBAR = getDebugFlag(
+            "ENABLE_TRANSIENT_TASKBAR", false, "Enables transient taskbar.");
 
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 466b268..05b1984 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -31,6 +31,7 @@
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps.PinItemRequest;
 import android.content.pm.ShortcutInfo;
 import android.content.res.Configuration;
@@ -62,6 +63,8 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.pm.PinRequestHelper;
+import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.views.AbstractSlideInView;
 import com.android.launcher3.views.BaseDragLayer;
@@ -136,13 +139,25 @@
         mAccessibilityManager =
                 getApplicationContext().getSystemService(AccessibilityManager.class);
 
-        if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) {
-            setupShortcut();
-        } else {
-            if (!setupWidget()) {
-                // TODO: show error toast?
-                finish();
-            }
+        PackageUserKey targetApp = null;
+        switch (mRequest.getRequestType()) {
+            case PinItemRequest.REQUEST_TYPE_SHORTCUT:
+                targetApp = setupShortcut();
+                break;
+            case PinItemRequest.REQUEST_TYPE_APPWIDGET:
+                targetApp = setupWidget();
+                break;
+        }
+        if (targetApp == null) {
+            // TODO: show error toast?
+            finish();
+            return;
+        }
+        ApplicationInfo info = new PackageManagerHelper(this)
+                .getApplicationInfo(targetApp.mPackageName, targetApp.mUser, 0);
+        if (info == null) {
+            finish();
+            return;
         }
 
         WidgetCellPreview previewContainer = mWidgetCell.findViewById(
@@ -156,8 +171,10 @@
             logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_START);
         }
 
+        // Set the label synchronously instead of via IconCache as this is the first thing
+        // user sees
         TextView widgetAppName = findViewById(R.id.widget_appName);
-        widgetAppName.setText(getApplicationInfo().labelRes);
+        widgetAppName.setText(info.loadLabel(getPackageManager()));
 
         mSlideInView = findViewById(R.id.add_item_bottom_sheet);
         mSlideInView.addOnCloseListener(this);
@@ -246,20 +263,23 @@
         }
     }
 
-    private void setupShortcut() {
+    private PackageUserKey setupShortcut() {
         PinShortcutRequestActivityInfo shortcutInfo =
                 new PinShortcutRequestActivityInfo(mRequest, this);
         mWidgetCell.getWidgetView().setTag(new PendingAddShortcutInfo(shortcutInfo));
         applyWidgetItemAsync(
                 () -> new WidgetItem(shortcutInfo, mApp.getIconCache(), getPackageManager()));
+        return new PackageUserKey(
+                mRequest.getShortcutInfo().getPackage(),
+                mRequest.getShortcutInfo().getUserHandle());
     }
 
-    private boolean setupWidget() {
+    private PackageUserKey setupWidget() {
         LauncherAppWidgetProviderInfo widgetInfo = LauncherAppWidgetProviderInfo
                 .fromProviderInfo(this, mRequest.getAppWidgetProviderInfo(this));
         if (widgetInfo.minSpanX > mIdp.numColumns || widgetInfo.minSpanY > mIdp.numRows) {
             // Cannot add widget
-            return false;
+            return null;
         }
         mWidgetCell.setRemoteViewsPreview(PinItemDragListener.getPreview(mRequest));
 
@@ -274,7 +294,7 @@
         mWidgetCell.getWidgetView().setTag(pendingInfo);
 
         applyWidgetItemAsync(() -> new WidgetItem(widgetInfo, mIdp, mApp.getIconCache()));
-        return true;
+        return new PackageUserKey(widgetInfo.provider.getPackageName(), widgetInfo.getUser());
     }
 
     private void applyWidgetItemAsync(final Supplier<WidgetItem> itemProvider) {
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 8616f35..5368397 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -31,6 +31,7 @@
 
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -90,6 +91,8 @@
 
     protected boolean mIsInPreDrag;
 
+    private final int DRAG_VIEW_SCALE_DURATION_MS = 500;
+
     /**
      * Interface to receive notifications when a drag starts or stops
      */
@@ -214,6 +217,15 @@
             mOptions.preDragCondition.onPreDragEnd(mDragObject, true /* dragStarted*/);
         }
         mIsInPreDrag = false;
+        if (mOptions.preDragEndScale != 0) {
+            mDragObject.dragView
+                    .animate()
+                    .scaleX(mOptions.preDragEndScale)
+                    .scaleY(mOptions.preDragEndScale)
+                    .setInterpolator(Interpolators.EMPHASIZED)
+                    .setDuration(DRAG_VIEW_SCALE_DURATION_MS)
+                    .start();
+        }
         mDragObject.dragView.onDragStart();
         for (DragListener listener : new ArrayList<>(mListeners)) {
             listener.onDragStart(mDragObject, mOptions);
@@ -295,9 +307,9 @@
                 } else if (mIsInPreDrag) {
                     animateDragViewToOriginalPosition(null, null, -1);
                 }
+                mDragObject.dragView.clearAnimation();
                 mDragObject.dragView = null;
             }
-
             // Only end the drag if we are not deferred
             if (!isDeferred) {
                 callOnDragEnd();
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 8eeca7d..4bea0ad 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -43,7 +43,6 @@
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.CellLayout;
 import com.android.launcher3.DropTargetBar;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
@@ -51,6 +50,7 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.SpringProperty;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.Scrim;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
@@ -237,7 +237,7 @@
             View anchorView) {
 
         ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
-        CellLayout.LayoutParams lp =  (CellLayout.LayoutParams) child.getLayoutParams();
+        CellLayoutLayoutParams lp =  (CellLayoutLayoutParams) child.getLayoutParams();
         parentChildren.measureChild(child);
         parentChildren.layoutChild(child);
 
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index e8ff8da..1ff4335 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -40,6 +40,12 @@
     /** Determines when a pre-drag should transition to a drag. By default, this is immediate. */
     public PreDragCondition preDragCondition = null;
 
+    /**
+     * A drag scale that scales the original drag view size when the preDragCondition is met (or
+     * is ignored if preDragEndScale is 0).
+     */
+    public float preDragEndScale;
+
     /** Scale of the icons over the workspace icon size. */
     public float intrinsicIconScaleFactor = 1f;
 
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
index fb8a1bc..08e50dd 100644
--- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
@@ -20,12 +20,14 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.OnAlarmListener;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 
 public class SpringLoadedDragController implements OnAlarmListener {
     // how long the user must hover over a mini-screen before it unshrinks
-    final long ENTER_SPRING_LOAD_HOVER_TIME = 500;
-    final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950;
+    private static final long ENTER_SPRING_LOAD_HOVER_TIME = 500;
+    private static final long ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST = 1500;
+    private static final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950;
 
     Alarm mAlarm;
 
@@ -39,6 +41,13 @@
         mAlarm.setOnAlarmListener(this);
     }
 
+    private long getEnterSpringLoadHoverTime() {
+        // Some TAPL tests are flaky on Cuttlefish with a low waiting time
+        return Utilities.IS_RUNNING_IN_TEST_HARNESS
+                ? ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST
+                : ENTER_SPRING_LOAD_HOVER_TIME;
+    }
+
     public void cancel() {
         mAlarm.cancelAlarm();
     }
@@ -46,8 +55,8 @@
     // Set a new alarm to expire for the screen that we are hovering over now
     public void setAlarm(CellLayout cl) {
         mAlarm.cancelAlarm();
-        mAlarm.setAlarm((cl == null) ? ENTER_SPRING_LOAD_CANCEL_HOVER_TIME :
-            ENTER_SPRING_LOAD_HOVER_TIME);
+        mAlarm.setAlarm((cl == null) ? ENTER_SPRING_LOAD_CANCEL_HOVER_TIME
+                : getEnterSpringLoadHoverTime());
         mScreen = cl;
     }
 
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 61ffd9d..d5ef9df 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -43,6 +43,7 @@
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PropertyResetListener;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
 
@@ -215,6 +216,7 @@
         final int footerStartDelay;
         if (isLargeFolder()) {
             if (mIsOpening) {
+                mFolder.mFooter.setAlpha(0);
                 footerAlphaDuration = LARGE_FOLDER_FOOTER_DURATION;
                 footerStartDelay = mDuration - footerAlphaDuration;
             } else {
@@ -341,7 +343,7 @@
         ShortcutAndWidgetContainer cwc = mContent.getPageAt(0).getShortcutsAndWidgets();
         for (int i = 0; i < numItemsInPreview; ++i) {
             final BubbleTextView btv = itemsInPreview.get(i);
-            CellLayout.LayoutParams btvLp = (CellLayout.LayoutParams) btv.getLayoutParams();
+            CellLayoutLayoutParams btvLp = (CellLayoutLayoutParams) btv.getLayoutParams();
 
             // Calculate the final values in the LayoutParams.
             btvLp.isLockedToGrid = true;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 33e0902..e8759da 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -57,6 +57,7 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.dot.FolderDotInfo;
 import com.android.launcher3.dragndrop.BaseItemDragListener;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -277,7 +278,7 @@
 
     public void onDragEnter(ItemInfo dragInfo) {
         if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return;
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) getLayoutParams();
         CellLayout cl = (CellLayout) getParent().getParent();
 
         mBackground.animateToAccept(cl, lp.cellX, lp.cellY);
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 5021644..bf59594 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -24,6 +24,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.LauncherAppState;
@@ -192,7 +193,8 @@
 
     private class FolderNameWorker extends BaseModelUpdateTask {
         @Override
-        public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        public void execute(@NonNull final LauncherAppState app,
+                @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
             mFolderInfos = dataModel.folders.clone();
             mAppInfos = Arrays.asList(apps.copyData());
         }
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 3d5aef5..0bcb97a 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -202,7 +203,7 @@
     public void addViewForRank(View view, WorkspaceItemInfo item, int rank) {
         int pageNo = rank / mOrganizer.getMaxItemsPerPage();
 
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) view.getLayoutParams();
         lp.setCellXY(mOrganizer.getPosForRank(rank));
         getPageAt(pageNo).addViewToCellLayout(view, -1, item.getViewId(), lp, true);
     }
@@ -218,9 +219,9 @@
         textView.setOnClickListener(ItemClickHandler.INSTANCE);
         textView.setOnLongClickListener(mFolder);
         textView.setOnFocusChangeListener(mFocusIndicatorHelper);
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) textView.getLayoutParams();
+        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) textView.getLayoutParams();
         if (lp == null) {
-            textView.setLayoutParams(new CellLayout.LayoutParams(
+            textView.setLayoutParams(new CellLayoutLayoutParams(
                     item.cellX, item.cellY, item.spanX, item.spanY));
         } else {
             lp.cellX = item.cellX;
@@ -314,7 +315,7 @@
             }
 
             if (v != null) {
-                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
+                CellLayoutLayoutParams lp = (CellLayoutLayoutParams) v.getLayoutParams();
                 ItemInfo info = (ItemInfo) v.getTag();
                 lp.setCellXY(mOrganizer.getPosForRank(rank));
                 currentPage.addViewToCellLayout(v, -1, info.getViewId(), lp, true);
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index f027b33..7457f30 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -16,24 +16,16 @@
 
 package com.android.launcher3.graphics;
 
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.BlurMaskFilter;
 import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.view.View;
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.FastBitmapDrawable;
@@ -41,8 +33,6 @@
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
-import java.nio.ByteBuffer;
-
 /**
  * A utility class to generate preview bitmap for dragging.
  */
@@ -57,9 +47,6 @@
 
     public final int blurSizeOutline;
 
-    private OutlineGeneratorCallback mOutlineGeneratorCallback;
-    public Bitmap generatedDragOutline;
-
     public DragPreviewProvider(View view) {
         this(view, view.getContext());
     }
@@ -129,15 +116,6 @@
         return null;
     }
 
-    public final void generateDragOutline(Bitmap preview) {
-        if (FeatureFlags.IS_STUDIO_BUILD && mOutlineGeneratorCallback != null) {
-            throw new RuntimeException("Drag outline generated twice");
-        }
-
-        mOutlineGeneratorCallback = new OutlineGeneratorCallback(preview);
-        UI_HELPER_EXECUTOR.post(mOutlineGeneratorCallback);
-    }
-
     protected static Rect getDrawableBounds(Drawable d) {
         Rect bounds = new Rect();
         d.copyBounds(bounds);
@@ -184,92 +162,4 @@
     protected Bitmap convertPreviewToAlphaBitmap(Bitmap preview) {
         return preview.copy(Bitmap.Config.ALPHA_8, true);
     }
-
-    private class OutlineGeneratorCallback implements Runnable {
-
-        private final Bitmap mPreviewSnapshot;
-        private final Context mContext;
-        private final boolean mIsIcon;
-
-        OutlineGeneratorCallback(Bitmap preview) {
-            mPreviewSnapshot = preview;
-            mContext = mView.getContext();
-            mIsIcon = mView instanceof BubbleTextView;
-        }
-
-        @Override
-        public void run() {
-            Bitmap preview = convertPreviewToAlphaBitmap(mPreviewSnapshot);
-            if (mIsIcon) {
-                int size = ActivityContext.lookupContext(mContext).getDeviceProfile().iconSizePx;
-                preview = Bitmap.createScaledBitmap(preview, size, size, false);
-            }
-            //else case covers AppWidgetHost (doesn't drag/drop across different device profiles)
-
-            // We start by removing most of the alpha channel so as to ignore shadows, and
-            // other types of partial transparency when defining the shape of the object
-            byte[] pixels = new byte[preview.getWidth() * preview.getHeight()];
-            ByteBuffer buffer = ByteBuffer.wrap(pixels);
-            buffer.rewind();
-            preview.copyPixelsToBuffer(buffer);
-
-            for (int i = 0; i < pixels.length; i++) {
-                if ((pixels[i] & 0xFF) < 188) {
-                    pixels[i] = 0;
-                }
-            }
-
-            buffer.rewind();
-            preview.copyPixelsFromBuffer(buffer);
-
-            final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
-            Canvas canvas = new Canvas();
-
-            // calculate the outer blur first
-            paint.setMaskFilter(new BlurMaskFilter(blurSizeOutline, BlurMaskFilter.Blur.OUTER));
-            int[] outerBlurOffset = new int[2];
-            Bitmap thickOuterBlur = preview.extractAlpha(paint, outerBlurOffset);
-
-            paint.setMaskFilter(new BlurMaskFilter(
-                    mContext.getResources().getDimension(R.dimen.blur_size_thin_outline),
-                    BlurMaskFilter.Blur.OUTER));
-            int[] brightOutlineOffset = new int[2];
-            Bitmap brightOutline = preview.extractAlpha(paint, brightOutlineOffset);
-
-            // calculate the inner blur
-            canvas.setBitmap(preview);
-            canvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT);
-            paint.setMaskFilter(new BlurMaskFilter(blurSizeOutline, BlurMaskFilter.Blur.NORMAL));
-            int[] thickInnerBlurOffset = new int[2];
-            Bitmap thickInnerBlur = preview.extractAlpha(paint, thickInnerBlurOffset);
-
-            // mask out the inner blur
-            paint.setMaskFilter(null);
-            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
-            canvas.setBitmap(thickInnerBlur);
-            canvas.drawBitmap(preview, -thickInnerBlurOffset[0],
-                    -thickInnerBlurOffset[1], paint);
-            canvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(), paint);
-            canvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1], paint);
-
-            // draw the inner and outer blur
-            paint.setXfermode(null);
-            canvas.setBitmap(preview);
-            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
-            canvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1],
-                    paint);
-            canvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1], paint);
-
-            // draw the bright outline
-            canvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1], paint);
-
-            // cleanup
-            canvas.setBitmap(null);
-            brightOutline.recycle();
-            thickOuterBlur.recycle();
-            thickInnerBlur.recycle();
-
-            generatedDragOutline = preview;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index c1bab54..482e923 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -72,6 +72,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceLayoutManager;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.icons.BaseIconFactory;
@@ -532,8 +533,8 @@
             CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID);
             View qsb = mHomeElementInflater.inflate(R.layout.qsb_preview, firstScreen,
                     false);
-            CellLayout.LayoutParams lp =
-                    new CellLayout.LayoutParams(0, 0, firstScreen.getCountX(), 1);
+            CellLayoutLayoutParams lp = new CellLayoutLayoutParams(0, 0, firstScreen.getCountX(),
+                    1);
             lp.canReorder = false;
             firstScreen.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true);
         }
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 651372f..22627b4 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -593,6 +593,12 @@
 
         @UiEvent(doc = "User tapped on Share app system shortcut.")
         LAUNCHER_SYSTEM_SHORTCUT_APP_SHARE_TAP(1075),
+
+        @UiEvent(doc = "User has invoked split to right half from an app icon menu")
+        LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM(1199),
+
+        @UiEvent(doc = "User has invoked split to left half from an app icon menu")
+        LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP(1200)
         ;
 
         // ADD MORE
@@ -738,8 +744,9 @@
             HOT(2),
             TIMEOUT(3),
             FAIL(4),
-            COLD_USERWAITING(5);
-
+            COLD_USERWAITING(5),
+            ATOMIC(6),
+            CONTROLLED(7);
             private final int mId;
 
             LatencyType(int id) {
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 4c0f1ae..0d978e1 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -23,6 +23,9 @@
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel.CallbackTask;
 import com.android.launcher3.LauncherSettings;
@@ -42,6 +45,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Task to add auto-created workspace items.
@@ -50,14 +54,16 @@
 
     private static final String LOG = "AddWorkspaceItemsTask";
 
+    @NonNull
     private final List<Pair<ItemInfo, Object>> mItemList;
 
+    @NonNull
     private final WorkspaceItemSpaceFinder mItemSpaceFinder;
 
     /**
      * @param itemList items to add on the workspace
      */
-    public AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList) {
+    public AddWorkspaceItemsTask(@NonNull final List<Pair<ItemInfo, Object>> itemList) {
         this(itemList, new WorkspaceItemSpaceFinder());
     }
 
@@ -65,14 +71,15 @@
      * @param itemList items to add on the workspace
      * @param itemSpaceFinder inject WorkspaceItemSpaceFinder dependency for testing
      */
-    public AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList,
-            WorkspaceItemSpaceFinder itemSpaceFinder) {
+    public AddWorkspaceItemsTask(@NonNull final List<Pair<ItemInfo, Object>> itemList,
+            @NonNull final WorkspaceItemSpaceFinder itemSpaceFinder) {
         mItemList = itemList;
         mItemSpaceFinder = itemSpaceFinder;
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList apps) {
         if (mItemList.isEmpty()) {
             return;
         }
@@ -98,7 +105,8 @@
                     }
 
                     // b/139663018 Short-circuit this logic if the icon is a system app
-                    if (PackageManagerHelper.isSystemApp(app.getContext(), item.getIntent())) {
+                    if (PackageManagerHelper.isSystemApp(app.getContext(),
+                            Objects.requireNonNull(item.getIntent()))) {
                         if (TestProtocol.sDebugTracing) {
                             Log.d(TestProtocol.MISSING_PROMISE_ICON,
                                     LOG + " Item is a system app.");
@@ -159,7 +167,7 @@
                         continue;
                     }
 
-                    List<LauncherActivityInfo> activities = launcherApps
+                    List<LauncherActivityInfo> activities = Objects.requireNonNull(launcherApps)
                             .getActivityList(packageName, item.user);
                     boolean hasActivity = activities != null && !activities.isEmpty();
 
@@ -218,7 +226,7 @@
         if (!addedItemsFinal.isEmpty()) {
             scheduleCallbackTask(new CallbackTask() {
                 @Override
-                public void execute(Callbacks callbacks) {
+                public void execute(@NonNull Callbacks callbacks) {
                     final ArrayList<ItemInfo> addAnimated = new ArrayList<>();
                     final ArrayList<ItemInfo> addNotAnimated = new ArrayList<>();
                     if (!addedItemsFinal.isEmpty()) {
@@ -243,7 +251,8 @@
      * Returns true if the shortcuts already exists on the workspace. This must be called after
      * the workspace has been loaded. We identify a shortcut by its intent.
      */
-    protected boolean shortcutExists(BgDataModel dataModel, Intent intent, UserHandle user) {
+    protected boolean shortcutExists(@NonNull final BgDataModel dataModel,
+            @Nullable final Intent intent, @NonNull final UserHandle user) {
         final String compPkgName, intentWithPkg, intentWithoutPkg;
         if (intent == null) {
             // Skip items with null intents
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 95150dc..6da948c 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -66,7 +66,10 @@
     /** The list off all apps. */
     public final ArrayList<AppInfo> data = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER);
 
+    @NonNull
     private IconCache mIconCache;
+
+    @NonNull
     private AppFilter mAppFilter;
 
     private boolean mDataChanged = false;
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index 2a6a691..5b6f9f6 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -17,6 +17,7 @@
 
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppState;
@@ -47,14 +48,17 @@
     private static final boolean DEBUG_TASKS = false;
     private static final String TAG = "BaseModelUpdateTask";
 
+    // Nullabilities are explicitly omitted here because these are late-init fields,
+    // They will be non-null after init(), which is always the case in enqueueModelUpdateTask().
     private LauncherAppState mApp;
     private LauncherModel mModel;
     private BgDataModel mDataModel;
     private AllAppsList mAllAppsList;
     private Executor mUiExecutor;
 
-    public void init(LauncherAppState app, LauncherModel model,
-            BgDataModel dataModel, AllAppsList allAppsList, Executor uiExecutor) {
+    public void init(@NonNull final LauncherAppState app, @NonNull final LauncherModel model,
+            @NonNull final BgDataModel dataModel, @NonNull final AllAppsList allAppsList,
+            @NonNull final Executor uiExecutor) {
         mApp = app;
         mModel = model;
         mDataModel = dataModel;
@@ -64,7 +68,7 @@
 
     @Override
     public final void run() {
-        if (!mModel.isModelLoaded()) {
+        if (!Objects.requireNonNull(mModel).isModelLoaded()) {
             if (DEBUG_TASKS) {
                 Log.d(TAG, "Ignoring model task since loader is pending=" + this);
             }
@@ -77,13 +81,13 @@
     /**
      * Execute the actual task. Called on the worker thread.
      */
-    public abstract void execute(
-            LauncherAppState app, BgDataModel dataModel, AllAppsList apps);
+    public abstract void execute(@NonNull LauncherAppState app,
+            @NonNull BgDataModel dataModel, @NonNull AllAppsList apps);
 
     /**
      * Schedules a {@param task} to be executed on the current callbacks.
      */
-    public final void scheduleCallbackTask(final CallbackTask task) {
+    public final void scheduleCallbackTask(@NonNull final CallbackTask task) {
         for (final Callbacks cb : mModel.getCallbacks()) {
             mUiExecutor.execute(() -> task.execute(cb));
         }
@@ -95,7 +99,7 @@
         return mModel.getWriter(false /* hasVerticalHotseat */, false /* verifyChanges */, null);
     }
 
-    public void bindUpdatedWorkspaceItems(List<WorkspaceItemInfo> allUpdates) {
+    public void bindUpdatedWorkspaceItems(@NonNull final List<WorkspaceItemInfo> allUpdates) {
         // Bind workspace items
         List<WorkspaceItemInfo> workspaceUpdates = allUpdates.stream()
                 .filter(info -> info.id != ItemInfo.NO_ID)
@@ -113,18 +117,18 @@
                 .forEach(this::bindExtraContainerItems);
     }
 
-    public void bindExtraContainerItems(FixedContainerItems item) {
+    public void bindExtraContainerItems(@NonNull final FixedContainerItems item) {
         FixedContainerItems copy = item.clone();
         scheduleCallbackTask(c -> c.bindExtraContainerItems(copy));
     }
 
-    public void bindDeepShortcuts(BgDataModel dataModel) {
+    public void bindDeepShortcuts(@NonNull final BgDataModel dataModel) {
         final HashMap<ComponentKey, Integer> shortcutMapCopy =
                 new HashMap<>(dataModel.deepShortcutMap);
         scheduleCallbackTask(callbacks -> callbacks.bindDeepShortcutMap(shortcutMapCopy));
     }
 
-    public void bindUpdatedWidgets(BgDataModel dataModel) {
+    public void bindUpdatedWidgets(@NonNull final BgDataModel dataModel) {
         final ArrayList<WidgetsListBaseEntry> widgets =
                 dataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
         scheduleCallbackTask(c -> c.bindAllWidgets(widgets));
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index f644d49..57fefaa 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -18,6 +18,8 @@
 import android.content.ComponentName;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.icons.IconCache;
@@ -35,17 +37,23 @@
     public static final int OP_SESSION_UPDATE = 2;
 
     private final int mOp;
+
+    @NonNull
     private final UserHandle mUser;
+
+    @NonNull
     private final HashSet<String> mPackages;
 
-    public CacheDataUpdatedTask(int op, UserHandle user, HashSet<String> packages) {
+    public CacheDataUpdatedTask(final int op, @NonNull final UserHandle user,
+            @NonNull final HashSet<String> packages) {
         mOp = op;
         mUser = user;
         mPackages = packages;
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList apps) {
         IconCache iconCache = app.getIconCache();
         ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>();
 
@@ -65,7 +73,7 @@
         bindApplicationsIfNeeded();
     }
 
-    public boolean isValidShortcut(WorkspaceItemInfo si) {
+    public boolean isValidShortcut(@NonNull final WorkspaceItemInfo si) {
         switch (mOp) {
             case OP_CACHE_UPDATE:
                 return true;
diff --git a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
index c0dc34a..b9fba9d 100644
--- a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
@@ -17,6 +17,8 @@
 
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -31,19 +33,24 @@
  */
 public class PackageIncrementalDownloadUpdatedTask extends BaseModelUpdateTask {
 
+    @NonNull
     private final UserHandle mUser;
+
     private final int mProgress;
+
+    @NonNull
     private final String mPackageName;
 
-    public PackageIncrementalDownloadUpdatedTask(
-            String packageName, UserHandle user, float progress) {
+    public PackageIncrementalDownloadUpdatedTask(@NonNull final String packageName,
+            @NonNull final UserHandle user, final float progress) {
         mUser = user;
         mProgress = 1 - progress > 0.001 ? (int) (100 * progress) : 100;
         mPackageName = packageName;
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
+    public void execute(@NonNull LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList appsList) {
         PackageInstallInfo downloadInfo = new PackageInstallInfo(
                 mPackageName,
                 PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING,
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index b74d0fc..76a87ed 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -18,6 +18,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -33,14 +35,16 @@
  */
 public class PackageInstallStateChangedTask extends BaseModelUpdateTask {
 
+    @NonNull
     private final PackageInstallInfo mInstallInfo;
 
-    public PackageInstallStateChangedTask(PackageInstallInfo installInfo) {
+    public PackageInstallStateChangedTask(@NonNull final PackageInstallInfo installInfo) {
         mInstallInfo = installInfo;
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList apps) {
         if (mInstallInfo.state == PackageInstallInfo.STATUS_INSTALLED) {
             try {
                 // For instant apps we do not get package-add. Use setting events to update
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index a9d272e..3d9d81f 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -29,6 +29,8 @@
 import android.os.UserManager;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
@@ -80,17 +82,23 @@
     public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
 
     private final int mOp;
+
+    @NonNull
     private final UserHandle mUser;
+
+    @NonNull
     private final String[] mPackages;
 
-    public PackageUpdatedTask(int op, UserHandle user, String... packages) {
+    public PackageUpdatedTask(final int op, @NonNull final UserHandle user,
+            @NonNull final String... packages) {
         mOp = op;
         mUser = user;
         mPackages = packages;
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList appsList) {
         final Context context = app.getContext();
         final IconCache iconCache = app.getIconCache();
 
diff --git a/src/com/android/launcher3/model/ReloadStringCacheTask.java b/src/com/android/launcher3/model/ReloadStringCacheTask.java
index f4d4298..34f7057 100644
--- a/src/com/android/launcher3/model/ReloadStringCacheTask.java
+++ b/src/com/android/launcher3/model/ReloadStringCacheTask.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.model;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 
 /**
@@ -22,14 +24,17 @@
  * {@link android.app.admin.DevicePolicyManager#ACTION_DEVICE_POLICY_RESOURCE_UPDATED}.
  */
 public class ReloadStringCacheTask extends BaseModelUpdateTask {
+
+    @NonNull
     private ModelDelegate mModelDelegate;
 
-    public ReloadStringCacheTask(ModelDelegate modelDelegate) {
+    public ReloadStringCacheTask(@NonNull final ModelDelegate modelDelegate) {
         mModelDelegate = modelDelegate;
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList appsList) {
         synchronized (dataModel) {
             mModelDelegate.loadStringCache(dataModel.stringCache);
             StringCache cloneSC = dataModel.stringCache.clone();
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 1026e0b..a6a04a7 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -19,6 +19,8 @@
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -38,13 +40,20 @@
  */
 public class ShortcutsChangedTask extends BaseModelUpdateTask {
 
+    @NonNull
     private final String mPackageName;
+
+    @NonNull
     private final List<ShortcutInfo> mShortcuts;
+
+    @NonNull
     private final UserHandle mUser;
+
     private final boolean mUpdateIdMap;
 
-    public ShortcutsChangedTask(String packageName, List<ShortcutInfo> shortcuts,
-            UserHandle user, boolean updateIdMap) {
+    public ShortcutsChangedTask(@NonNull final String packageName,
+            @NonNull final List<ShortcutInfo> shortcuts, @NonNull final UserHandle user,
+            final boolean updateIdMap) {
         mPackageName = packageName;
         mShortcuts = shortcuts;
         mUser = user;
@@ -52,7 +61,8 @@
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList apps) {
         final Context context = app.getContext();
         // Find WorkspaceItemInfo's that have changed on the workspace.
         ArrayList<WorkspaceItemInfo> matchingWorkspaceItems = new ArrayList<>();
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 1565b19..63ca35b 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -21,6 +21,8 @@
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -40,16 +42,18 @@
  */
 public class UserLockStateChangedTask extends BaseModelUpdateTask {
 
+    @NonNull
     private final UserHandle mUser;
     private boolean mIsUserUnlocked;
 
-    public UserLockStateChangedTask(UserHandle user, boolean isUserUnlocked) {
+    public UserLockStateChangedTask(@NonNull final UserHandle user, final boolean isUserUnlocked) {
         mUser = user;
         mIsUserUnlocked = isUserUnlocked;
     }
 
     @Override
-    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+    public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
+            @NonNull final AllAppsList apps) {
         Context context = app.getContext();
 
         HashMap<ShortcutKey, ShortcutInfo> pinnedShortcuts = new HashMap<>();
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 466f63f..a8e9eb5 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -350,9 +350,11 @@
                 break;
             case ITEM_TYPE_TASK:
                 itemBuilder
-                        .setTask(LauncherAtom.Task.newBuilder()
-                                .setComponentName(getTargetComponent().flattenToShortString())
-                                .setIndex(screenId));
+                        .setTask(nullableComponent
+                                .map(component -> LauncherAtom.Task.newBuilder()
+                                        .setComponentName(component.flattenToShortString())
+                                        .setIndex(screenId))
+                                .orElse(LauncherAtom.Task.newBuilder()));
                 break;
             default:
                 break;
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 76a0c4d..e5fb015 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.model.data;
 
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 
@@ -220,10 +219,10 @@
     /** Creates an intent to that launches the app store at this app's page. */
     @Nullable
     public Intent getMarketIntent(Context context) {
-        ComponentName componentName = getTargetComponent();
+        String targetPackage = getTargetPackage();
 
-        return componentName != null
-                ? new PackageManagerHelper(context).getMarketIntent(componentName.getPackageName())
+        return targetPackage != null
+                ? new PackageManagerHelper(context).getMarketIntent(targetPackage)
                 : null;
     }
 
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index 1f16474..59ef320 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -24,7 +24,6 @@
 import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -76,6 +75,7 @@
     /**
      * The intent used to start the application.
      */
+    @NonNull
     public Intent intent;
 
     /**
@@ -148,7 +148,7 @@
     }
 
     @Override
-    @Nullable
+    @NonNull
     public Intent getIntent() {
         return intent;
     }
@@ -166,7 +166,8 @@
         return isPromise() && !hasStatusFlag(FLAG_SUPPORTS_WEB_UI);
     }
 
-    public void updateFromDeepShortcutInfo(ShortcutInfo shortcutInfo, Context context) {
+    public void updateFromDeepShortcutInfo(@NonNull final ShortcutInfo shortcutInfo,
+            @NonNull final Context context) {
         // {@link ShortcutInfo#getActivity} can change during an update. Recreate the intent
         intent = ShortcutKey.makeIntent(shortcutInfo);
         title = shortcutInfo.getShortLabel();
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 29eefe2..b4cb0ee 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.pageindicators;
 
+import static com.android.launcher3.config.FeatureFlags.SHOW_DELIGHTFUL_PAGINATION_FOLDER;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -28,6 +30,7 @@
 import android.graphics.Paint;
 import android.graphics.Paint.Style;
 import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.Property;
 import android.view.View;
@@ -37,6 +40,7 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.util.Themes;
 
 /**
@@ -53,12 +57,16 @@
     private static final int ENTER_ANIMATION_STAGGERED_DELAY = 150;
     private static final int ENTER_ANIMATION_DURATION = 400;
 
-    private static final int DOT_ACTIVE_ALPHA = 255;
-    private static final int DOT_INACTIVE_ALPHA = 128;
+    private static final int PAGE_INDICATOR_ALPHA = 255;
+    private static final int DOT_ALPHA = 128;
+    private static final int DOT_GAP_FACTOR = 3;
+    private static final float DOT_GAP_FACTOR_FLOAT = 3.8f;
 
     // This value approximately overshoots to 1.5 times the original size.
     private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f;
 
+    private static final float INDICATOR_ROTATION = 180f;
+
     private static final RectF sTempRect = new RectF();
 
     private static final Property<PageIndicatorDots, Float> CURRENT_POSITION
@@ -76,21 +84,27 @@
         }
     };
 
-    private final Paint mCirclePaint;
+    private final Paint mPaginationPaint;
+    private final Drawable mPageIndicatorDrawable;
     private final float mDotRadius;
+    private final float mCircleGap;
+    private final float mPageIndicatorSize;
+    private final float mPageIndicatorRadius;
     private final boolean mIsRtl;
 
     private int mNumPages;
     private int mActivePage;
+    private int mCurrentScroll;
+    private int mTotalScroll;
 
     /**
      * The current position of the active dot including the animation progress.
      * For ex:
-     *   0.0  => Active dot is at position 0
-     *   0.33 => Active dot is at position 0 and is moving towards 1
-     *   0.50 => Active dot is at position [0, 1]
-     *   0.77 => Active dot has left position 0 and is collapsing towards position 1
-     *   1.0  => Active dot is at position 1
+     * 0.0  => Active dot is at position 0
+     * 0.33 => Active dot is at position 0 and is moving towards 1
+     * 0.50 => Active dot is at position [0, 1]
+     * 0.77 => Active dot has left position 0 and is collapsing towards position 1
+     * 1.0  => Active dot is at position 1
      */
     private float mCurrentPosition;
     private float mFinalPosition;
@@ -109,37 +123,66 @@
     public PageIndicatorDots(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
-        mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mCirclePaint.setStyle(Style.FILL);
-        mCirclePaint.setColor(Themes.getAttrColor(context, R.attr.folderPaginationColor));
+        mPaginationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPaginationPaint.setStyle(Style.FILL);
+        mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.folderPaginationColor));
         mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
-        setOutlineProvider(new MyOutlineProver());
 
+
+        if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+            mPageIndicatorSize = getResources().getDimension(
+                    R.dimen.page_indicator_size);
+            mPageIndicatorRadius = mPageIndicatorSize / 2;
+            mPageIndicatorDrawable = context.getDrawable(R.drawable.page_indicator);
+            mPageIndicatorDrawable.setBounds(0, 0, (int) mPageIndicatorSize,
+                    (int) mPageIndicatorSize);
+            mCircleGap = DOT_GAP_FACTOR_FLOAT * mDotRadius;
+
+        } else {
+            mPageIndicatorSize = 0;
+            mPageIndicatorRadius = 0;
+            mPageIndicatorDrawable = null;
+            mCircleGap = DOT_GAP_FACTOR * mDotRadius;
+        }
+        if (!SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+            setOutlineProvider(new MyOutlineProver());
+        }
         mIsRtl = Utilities.isRtl(getResources());
     }
 
     @Override
     public void setScroll(int currentScroll, int totalScroll) {
-        if (mNumPages > 1) {
-            if (mIsRtl) {
-                currentScroll = totalScroll - currentScroll;
-            }
-            int scrollPerPage = totalScroll / (mNumPages - 1);
-            int pageToLeft = currentScroll / scrollPerPage;
-            int pageToLeftScroll = pageToLeft * scrollPerPage;
-            int pageToRightScroll = pageToLeftScroll + scrollPerPage;
+        if (mNumPages <= 1) {
+            mCurrentScroll = 0;
+            return;
+        }
 
-            float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
-            if (currentScroll < pageToLeftScroll + scrollThreshold) {
-                // scroll is within the left page's threshold
-                animateToPosition(pageToLeft);
-            } else if (currentScroll > pageToRightScroll - scrollThreshold) {
-                // scroll is far enough from left page to go to the right page
-                animateToPosition(pageToLeft + 1);
-            } else {
-                // scroll is between left and right page
-                animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
-            }
+        if (mIsRtl) {
+            currentScroll = totalScroll - currentScroll;
+        }
+
+        if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+            mCurrentScroll = currentScroll;
+            mTotalScroll = totalScroll;
+            invalidate();
+            return;
+        }
+
+        int scrollPerPage = totalScroll / (mNumPages - 1);
+        int pageToLeft = currentScroll / scrollPerPage;
+        int pageToLeftScroll = pageToLeft * scrollPerPage;
+        int pageToRightScroll = pageToLeftScroll + scrollPerPage;
+
+        float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
+        if (currentScroll < pageToLeftScroll + scrollThreshold) {
+            // scroll is within the left page's threshold
+            animateToPosition(pageToLeft);
+        } else if (currentScroll > pageToRightScroll - scrollThreshold) {
+            // scroll is far enough from left page to go to the right page
+            animateToPosition(pageToLeft + 1);
+        } else {
+            // scroll is between left and right page
+            animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
         }
     }
 
@@ -177,7 +220,7 @@
     }
 
     public void playEntryAnimation() {
-        int count  = mEntryAnimationRadiusFactors.length;
+        int count = mEntryAnimationRadiusFactors.length;
         if (count == 0) {
             mEntryAnimationRadiusFactors = null;
             invalidate();
@@ -231,16 +274,16 @@
         // Add extra spacing of mDotRadius on all sides so than entry animation could be run.
         int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ?
                 MeasureSpec.getSize(widthMeasureSpec) : (int) ((mNumPages * 3 + 2) * mDotRadius);
-        int height= MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ?
-                MeasureSpec.getSize(heightMeasureSpec) : (int) (4 * mDotRadius);
+        int height = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY
+                ? MeasureSpec.getSize(heightMeasureSpec) : (int) (4 * mDotRadius);
         setMeasuredDimension(width, height);
     }
 
     @Override
     protected void onDraw(Canvas canvas) {
         // Draw all page indicators;
-        float circleGap = 3 * mDotRadius;
-        float startX = (getWidth() - mNumPages * circleGap + mDotRadius) / 2;
+        float circleGap = mCircleGap;
+        float startX = (getWidth() - (mNumPages * circleGap) + mDotRadius) / 2;
 
         float x = startX + mDotRadius;
         float y = getHeight() / 2;
@@ -252,43 +295,123 @@
                 circleGap = -circleGap;
             }
             for (int i = 0; i < mEntryAnimationRadiusFactors.length; i++) {
-                mCirclePaint.setAlpha(i == mActivePage ? DOT_ACTIVE_ALPHA : DOT_INACTIVE_ALPHA);
-                canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i], mCirclePaint);
+                mPaginationPaint.setAlpha(i == mActivePage ? PAGE_INDICATOR_ALPHA : DOT_ALPHA);
+                if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+                    if (i != mActivePage) {
+                        canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i],
+                                mPaginationPaint);
+                    } else {
+                        drawPageIndicator(canvas, mEntryAnimationRadiusFactors[i]);
+                    }
+                } else {
+                    canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i],
+                            mPaginationPaint);
+                }
                 x += circleGap;
             }
         } else {
-            mCirclePaint.setAlpha(DOT_INACTIVE_ALPHA);
+            // Here we draw the dots
+            mPaginationPaint.setAlpha(DOT_ALPHA);
             for (int i = 0; i < mNumPages; i++) {
-                canvas.drawCircle(x, y, mDotRadius, mCirclePaint);
+                if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+                    canvas.drawCircle(x, y, getRadius(x), mPaginationPaint);
+                } else {
+                    canvas.drawCircle(x, y, mDotRadius, mPaginationPaint);
+                }
                 x += circleGap;
             }
 
-            mCirclePaint.setAlpha(DOT_ACTIVE_ALPHA);
-            canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mCirclePaint);
+            // Here we draw the current page indicator
+            mPaginationPaint.setAlpha(PAGE_INDICATOR_ALPHA);
+            if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+                drawPageIndicator(canvas, 1);
+            } else {
+                canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
+            }
         }
     }
 
+    /**
+     * Draws the page indicator, denoting the currently selected page
+     *
+     * @param canvas is used to draw the page indicator and to rotate it as we scroll
+     * @param scale  is used to set the scale of our canvas
+     */
+    private void drawPageIndicator(Canvas canvas, float scale) {
+        RectF currRect = getActiveRect();
+
+        // saves the canvas so we can later restore it to its original scale
+        canvas.save();
+
+        // Moves the canvas to start at the top left corner of the page indicator
+        canvas.translate(currRect.left, currRect.top);
+
+        // Scales the canvas in place to animate the indicator on entry
+        canvas.scale(scale, scale, mPageIndicatorRadius, mPageIndicatorRadius);
+
+        int scrollPerPage = getScrollPerPage();
+        // This IF is to avoid division by 0
+        if (scrollPerPage != 0) {
+            int delta = mCurrentScroll % scrollPerPage;
+            canvas.rotate((INDICATOR_ROTATION * delta) / scrollPerPage,
+                    mPageIndicatorRadius, mPageIndicatorRadius);
+        }
+
+        mPageIndicatorDrawable.draw(canvas);
+        canvas.restore();
+    }
+
+    /**
+     * Returns the radius of the circle based on how close the page indicator is to it
+     *
+     * @param dotPositionX is the position the dot is located at in the x-axis
+     */
+    private float getRadius(float dotPositionX) {
+
+        float startXIndicator =
+                ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2) - getOffset();
+        float indicatorPosition = startXIndicator + getIndicatorScrollDistance()
+                + mPageIndicatorRadius;
+
+        // If the indicator gets close enough to a dot then we change the radius
+        // of the dot based on how close the indicator is to it.
+        float dotDistance = Math.abs(indicatorPosition - dotPositionX);
+        if (dotDistance <= mCircleGap) {
+            return Utilities.mapToRange(dotDistance, 0, mCircleGap, 0f, mDotRadius,
+                    Interpolators.LINEAR);
+        }
+        return mDotRadius;
+    }
+
     private RectF getActiveRect() {
         float startCircle = (int) mCurrentPosition;
         float delta = mCurrentPosition - startCircle;
         float diameter = 2 * mDotRadius;
-        float circleGap = 3 * mDotRadius;
-        float startX = (getWidth() - mNumPages * circleGap + mDotRadius) / 2;
+        float startX;
 
-        sTempRect.top = getHeight() * 0.5f - mDotRadius;
-        sTempRect.bottom = getHeight() * 0.5f + mDotRadius;
-        sTempRect.left = startX + startCircle * circleGap;
-        sTempRect.right = sTempRect.left + diameter;
-
-        if (delta < SHIFT_PER_ANIMATION) {
-            // dot is capturing the right circle.
-            sTempRect.right += delta * circleGap * 2;
+        if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+            startX = ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2) - getOffset();
+            sTempRect.top = (getHeight() - mPageIndicatorSize) * 0.5f;
+            sTempRect.bottom = (getHeight() + mPageIndicatorSize) * 0.5f;
+            sTempRect.left = startX + getIndicatorScrollDistance();
+            sTempRect.right = sTempRect.left + mPageIndicatorSize;
         } else {
-            // Dot is leaving the left circle.
-            sTempRect.right += circleGap;
+            startX = ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2);
+            sTempRect.top = (getHeight() * 0.5f) - mDotRadius;
+            sTempRect.bottom = (getHeight() * 0.5f) + mDotRadius;
+            sTempRect.left = startX + (startCircle * mCircleGap);
+            sTempRect.right = sTempRect.left + diameter;
 
-            delta -= SHIFT_PER_ANIMATION;
-            sTempRect.left += delta * circleGap * 2;
+            if (delta < SHIFT_PER_ANIMATION) {
+                // dot is capturing the right circle.
+                sTempRect.right += delta * mCircleGap * 2;
+            } else {
+                // Dot is leaving the left circle.
+                sTempRect.right += mCircleGap;
+
+                delta -= SHIFT_PER_ANIMATION;
+                sTempRect.left += delta * mCircleGap * 2;
+            }
         }
 
         if (mIsRtl) {
@@ -296,9 +419,33 @@
             sTempRect.right = getWidth() - sTempRect.left;
             sTempRect.left = sTempRect.right - rectWidth;
         }
+
         return sTempRect;
     }
 
+    /**
+     * The offset between the radius of the dot and the midpoint of the indicator so that
+     * the indicator is centered in with the indicator circles
+     */
+    private float getOffset() {
+        return mPageIndicatorRadius - mDotRadius;
+    }
+
+    /**
+     * Returns an int that is the amount we need to scroll per page
+     */
+    private int getScrollPerPage() {
+        return mNumPages > 1 ? mTotalScroll / (mNumPages - 1) : 0;
+    }
+
+    /**
+     * The current scroll adjusted for the distance the indicator needs to travel on the screen
+     */
+    private float getIndicatorScrollDistance() {
+        int scrollPerPage = getScrollPerPage();
+        return scrollPerPage != 0 ? ((float) mCurrentScroll / scrollPerPage) * mCircleGap : 0;
+    }
+
     private class MyOutlineProver extends ViewOutlineProvider {
 
         @Override
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index b695194..16bb868 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -30,6 +30,7 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.WorkerThread;
 
@@ -51,38 +52,50 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Utility class to tracking install sessions
  */
 public class InstallSessionHelper {
 
+    @NonNull
     private static final String LOG = "InstallSessionHelper";
 
     // Set<String> of session ids of promise icons that have been added to the home screen
     // as FLAG_PROMISE_NEW_INSTALLS.
+    @NonNull
     protected static final String PROMISE_ICON_IDS = "promise_icon_ids";
 
     private static final boolean DEBUG = false;
 
+    @NonNull
     public static final MainThreadInitializedObject<InstallSessionHelper> INSTANCE =
             new MainThreadInitializedObject<>(InstallSessionHelper::new);
 
+    @Nullable
     private final LauncherApps mLauncherApps;
+
+    @NonNull
     private final Context mAppContext;
 
+    @NonNull
     private final PackageInstaller mInstaller;
+
+    @NonNull
     private final HashMap<String, Boolean> mSessionVerifiedMap = new HashMap<>();
 
+    @Nullable
     private IntSet mPromiseIconIds;
 
-    public InstallSessionHelper(Context context) {
+    public InstallSessionHelper(@NonNull final Context context) {
         mInstaller = context.getPackageManager().getPackageInstaller();
         mAppContext = context.getApplicationContext();
         mLauncherApps = context.getSystemService(LauncherApps.class);
     }
 
     @WorkerThread
+    @NonNull
     private IntSet getPromiseIconIds() {
         Preconditions.assertWorkerThread();
         if (mPromiseIconIds != null) {
@@ -108,6 +121,7 @@
         return mPromiseIconIds;
     }
 
+    @NonNull
     public HashMap<PackageUserKey, SessionInfo> getActiveSessions() {
         HashMap<PackageUserKey, SessionInfo> activePackages = new HashMap<>();
         for (SessionInfo info : getAllVerifiedSessions()) {
@@ -117,6 +131,7 @@
         return activePackages;
     }
 
+    @Nullable
     public SessionInfo getActiveSessionInfo(UserHandle user, String pkg) {
         for (SessionInfo info : getAllVerifiedSessions()) {
             boolean match = pkg.equals(info.getAppPackageName());
@@ -136,11 +151,13 @@
                 .apply();
     }
 
-    SessionInfo getVerifiedSessionInfo(int sessionId) {
+    @Nullable
+    SessionInfo getVerifiedSessionInfo(final int sessionId) {
         return verify(mInstaller.getSessionInfo(sessionId));
     }
 
-    private SessionInfo verify(SessionInfo sessionInfo) {
+    @Nullable
+    private SessionInfo verify(@Nullable final SessionInfo sessionInfo) {
         if (sessionInfo == null
                 || sessionInfo.getInstallerPackageName() == null
                 || TextUtils.isEmpty(sessionInfo.getAppPackageName())) {
@@ -167,9 +184,10 @@
         return mSessionVerifiedMap.get(pkg) ? sessionInfo : null;
     }
 
+    @NonNull
     public List<SessionInfo> getAllVerifiedSessions() {
         List<SessionInfo> list = new ArrayList<>(Utilities.ATLEAST_Q
-                ? mLauncherApps.getAllPackageInstallerSessions()
+                ? Objects.requireNonNull(mLauncherApps).getAllPackageInstallerSessions()
                 : mInstaller.getAllSessions());
         Iterator<SessionInfo> it = list.iterator();
         while (it.hasNext()) {
@@ -201,12 +219,12 @@
     }
 
     @WorkerThread
-    public boolean promiseIconAddedForId(int sessionId) {
+    public boolean promiseIconAddedForId(final int sessionId) {
         return getPromiseIconIds().contains(sessionId);
     }
 
     @WorkerThread
-    public void removePromiseIconId(int sessionId) {
+    public void removePromiseIconId(final int sessionId) {
         if (promiseIconAddedForId(sessionId)) {
             getPromiseIconIds().getArray().removeValue(sessionId);
             updatePromiseIconPrefs();
@@ -222,7 +240,7 @@
      * - A promise icon for the session has not already been created
      */
     @WorkerThread
-    void tryQueuePromiseAppIcon(PackageInstaller.SessionInfo sessionInfo) {
+    void tryQueuePromiseAppIcon(@Nullable final PackageInstaller.SessionInfo sessionInfo) {
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.MISSING_PROMISE_ICON, LOG + " tryQueuePromiseAppIcon"
                     + ", SessionCommitReceiveEnabled" + SessionCommitReceiver.isEnabled(mAppContext)
@@ -244,7 +262,7 @@
         }
     }
 
-    public boolean verifySessionInfo(PackageInstaller.SessionInfo sessionInfo) {
+    public boolean verifySessionInfo(@Nullable final PackageInstaller.SessionInfo sessionInfo) {
         if (TestProtocol.sDebugTracing) {
             boolean appNotInstalled = sessionInfo == null
                     || !new PackageManagerHelper(mAppContext)
@@ -267,14 +285,15 @@
                         sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
     }
 
-    public InstallSessionTracker registerInstallTracker(InstallSessionTracker.Callback callback) {
+    public InstallSessionTracker registerInstallTracker(
+            @Nullable final InstallSessionTracker.Callback callback) {
         InstallSessionTracker tracker = new InstallSessionTracker(
                 this, callback, mInstaller, mLauncherApps);
         tracker.register();
         return tracker;
     }
 
-    public static UserHandle getUserHandle(SessionInfo info) {
+    public static UserHandle getUserHandle(@NonNull final SessionInfo info) {
         return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle();
     }
 }
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index b16aaa2..aeaa320 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -28,12 +28,15 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.lang.ref.WeakReference;
+import java.util.Objects;
 
 @WorkerThread
 public class InstallSessionTracker extends PackageInstaller.SessionCallback {
@@ -41,14 +44,22 @@
     // Lazily initialized
     private SparseArray<PackageUserKey> mActiveSessions = null;
 
+    @NonNull
     private final WeakReference<InstallSessionHelper> mWeakHelper;
+
+    @NonNull
     private final WeakReference<Callback> mWeakCallback;
+
+    @NonNull
     private final PackageInstaller mInstaller;
+
+    @Nullable
     private final LauncherApps mLauncherApps;
 
 
-    InstallSessionTracker(InstallSessionHelper installerCompat, Callback callback,
-            PackageInstaller installer, LauncherApps launcherApps) {
+    InstallSessionTracker(@Nullable final InstallSessionHelper installerCompat,
+            @Nullable final Callback callback, @NonNull final PackageInstaller installer,
+            @Nullable LauncherApps launcherApps) {
         mWeakHelper = new WeakReference<>(installerCompat);
         mWeakCallback = new WeakReference<>(callback);
         mInstaller = installer;
@@ -56,7 +67,7 @@
     }
 
     @Override
-    public void onCreated(int sessionId) {
+    public void onCreated(final int sessionId) {
         InstallSessionHelper helper = mWeakHelper.get();
         Callback callback = mWeakCallback.get();
         if (TestProtocol.sDebugTracing) {
@@ -80,7 +91,7 @@
     }
 
     @Override
-    public void onFinished(int sessionId, boolean success) {
+    public void onFinished(final int sessionId, final boolean success) {
         InstallSessionHelper helper = mWeakHelper.get();
         Callback callback = mWeakCallback.get();
         if (callback == null || helper == null) {
@@ -108,7 +119,7 @@
     }
 
     @Override
-    public void onProgressChanged(int sessionId, float progress) {
+    public void onProgressChanged(final int sessionId, final float progress) {
         InstallSessionHelper helper = mWeakHelper.get();
         Callback callback = mWeakCallback.get();
         if (callback == null || helper == null) {
@@ -121,10 +132,10 @@
     }
 
     @Override
-    public void onActiveChanged(int sessionId, boolean active) { }
+    public void onActiveChanged(final int sessionId, final boolean active) { }
 
     @Override
-    public void onBadgingChanged(int sessionId) {
+    public void onBadgingChanged(final int sessionId) {
         InstallSessionHelper helper = mWeakHelper.get();
         Callback callback = mWeakCallback.get();
         if (callback == null || helper == null) {
@@ -136,8 +147,9 @@
         }
     }
 
-    private SessionInfo pushSessionDisplayToLauncher(
-            int sessionId, InstallSessionHelper helper, Callback callback) {
+    @Nullable
+    private SessionInfo pushSessionDisplayToLauncher(final int sessionId,
+            @NonNull final InstallSessionHelper helper, @NonNull final Callback callback) {
         SessionInfo session = helper.getVerifiedSessionInfo(sessionId);
         if (session != null && session.getAppPackageName() != null) {
             PackageUserKey key =
@@ -149,7 +161,9 @@
         return null;
     }
 
-    private SparseArray<PackageUserKey> getActiveSessionMap(InstallSessionHelper helper) {
+    @NonNull
+    private SparseArray<PackageUserKey> getActiveSessionMap(
+            @NonNull final InstallSessionHelper helper) {
         if (mActiveSessions == null) {
             mActiveSessions = new SparseArray<>();
             helper.getActiveSessions().forEach(
@@ -162,7 +176,8 @@
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
             mInstaller.registerSessionCallback(this, MODEL_EXECUTOR.getHandler());
         } else {
-            mLauncherApps.registerPackageInstallerSessionCallback(MODEL_EXECUTOR, this);
+            Objects.requireNonNull(mLauncherApps).registerPackageInstallerSessionCallback(
+                    MODEL_EXECUTOR, this);
         }
     }
 
@@ -170,18 +185,18 @@
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
             mInstaller.unregisterSessionCallback(this);
         } else {
-            mLauncherApps.unregisterPackageInstallerSessionCallback(this);
+            Objects.requireNonNull(mLauncherApps).unregisterPackageInstallerSessionCallback(this);
         }
     }
 
     public interface Callback {
 
-        void onSessionFailure(String packageName, UserHandle user);
+        void onSessionFailure(@NonNull String packageName, @NonNull UserHandle user);
 
-        void onUpdateSessionDisplay(PackageUserKey key, SessionInfo info);
+        void onUpdateSessionDisplay(@NonNull PackageUserKey key, @NonNull SessionInfo info);
 
-        void onPackageStateChanged(PackageInstallInfo info);
+        void onPackageStateChanged(@NonNull PackageInstallInfo info);
 
-        void onInstallSessionCreated(PackageInstallInfo info);
+        void onInstallSessionCreated(@NonNull PackageInstallInfo info);
     }
 }
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 54e8e5b..86277a7 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -343,11 +343,6 @@
                 onStateTransitionEnd(state);
             }
 
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                super.onAnimationCancel(animation);
-                onStateTransitionFailed(state);
-            }
         };
     }
 
@@ -360,12 +355,6 @@
         }
     }
 
-    private void onStateTransitionFailed(STATE_TYPE state) {
-        for (int i = mListeners.size() - 1; i >= 0; i--) {
-            mListeners.get(i).onStateTransitionFailed(state);
-        }
-    }
-
     private void onStateTransitionEnd(STATE_TYPE state) {
         // Only change the stable states after the transitions have finished
         if (state != mCurrentStableState) {
@@ -600,11 +589,6 @@
 
         default void onStateTransitionStart(STATE_TYPE toState) { }
 
-        /**
-         * If the state transition animation fails (e.g. is canceled by the user), this fires.
-         */
-        default void onStateTransitionFailed(STATE_TYPE toState) { }
-
         default void onStateTransitionComplete(STATE_TYPE finalState) { }
     }
 
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index fd8b2e5..b94ea07 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -21,17 +21,23 @@
 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
 
 import static com.android.launcher3.Utilities.dpiFromPx;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
 
+import android.app.Activity;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Handler;
+import android.os.Message;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.UiThreadHelper;
 
 /**
  * Utility class to manage launcher rotation
@@ -39,8 +45,6 @@
 public class RotationHelper implements OnSharedPreferenceChangeListener,
         DisplayController.DisplayInfoChangeListener {
 
-    private static final String TAG = "RotationHelper";
-
     public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
 
     /**
@@ -58,8 +62,10 @@
     public static final int REQUEST_ROTATE = 1;
     public static final int REQUEST_LOCK = 2;
 
+    @Nullable
     private BaseActivity mActivity;
     private SharedPreferences mSharedPrefs = null;
+    private final Handler mRequestOrientationHandler;
 
     private boolean mIgnoreAutoRotateSettings;
     private boolean mForceAllowRotationForTesting;
@@ -89,6 +95,8 @@
 
     public RotationHelper(BaseActivity activity) {
         mActivity = activity;
+        mRequestOrientationHandler =
+                new Handler(UI_HELPER_EXECUTOR.getLooper(), this::setOrientationAsync);
     }
 
     private void setIgnoreAutoRotateSettings(boolean ignoreAutoRotateSettings) {
@@ -202,10 +210,19 @@
         }
         if (activityFlags != mLastActivityFlags) {
             mLastActivityFlags = activityFlags;
-            UiThreadHelper.setOrientationAsync(mActivity, activityFlags);
+            mRequestOrientationHandler.sendEmptyMessage(activityFlags);
         }
     }
 
+    @WorkerThread
+    private boolean setOrientationAsync(Message msg) {
+        Activity activity = mActivity;
+        if (activity != null) {
+            activity.setRequestedOrientation(msg.what);
+        }
+        return true;
+    }
+
     /**
      * @return how many factors {@param newRotation} is rotated 90 degrees clockwise.
      * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise...
diff --git a/src/com/android/launcher3/testing/TestInformationProvider.java b/src/com/android/launcher3/testing/TestInformationProvider.java
index bcc7c2d..5444d92 100644
--- a/src/com/android/launcher3/testing/TestInformationProvider.java
+++ b/src/com/android/launcher3/testing/TestInformationProvider.java
@@ -21,10 +21,14 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
+import android.util.Log;
 
 import com.android.launcher3.Utilities;
 
 public class TestInformationProvider extends ContentProvider {
+
+    private static final String TAG = "TestInformationProvider";
+
     @Override
     public boolean onCreate() {
         return true;
@@ -60,7 +64,13 @@
         if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
             TestInformationHandler handler = TestInformationHandler.newInstance(getContext());
             handler.init(getContext());
-            return handler.call(method, arg, extras);
+
+            Bundle response =  handler.call(method, arg, extras);
+            if (response == null) {
+                Log.e(TAG, "Couldn't handle method: " + method + "; current handler="
+                        + handler.getClass().getSimpleName());
+            }
+            return response;
         }
         return null;
     }
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 5fa30bc..b4be061 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -248,13 +248,19 @@
             final Launcher launcher = Launcher.getLauncher(context);
             if (shortcut.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
                     && shortcut.isDisabledVersionLower()) {
+                final Intent marketIntent = shortcut.getMarketIntent(context);
+                // No market intent means no target package for the shortcut, which should be an
+                // issue. Falling back to showing toast messages.
+                if (marketIntent == null) {
+                    return false;
+                }
 
                 new AlertDialog.Builder(context)
                         .setTitle(R.string.dialog_update_title)
                         .setMessage(R.string.dialog_update_message)
                         .setPositiveButton(R.string.dialog_update, (d, i) -> {
                             // Direct the user to the play store to update the app
-                            context.startActivity(shortcut.getMarketIntent(context));
+                            context.startActivity(marketIntent);
                         })
                         .setNeutralButton(R.string.dialog_remove, (d, i) -> {
                             // Remove the icon if launcher is successfully initialized
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 73f994f..1421ece 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -27,7 +27,6 @@
 import android.view.View.OnLongClickListener;
 
 import com.android.launcher3.CellLayout;
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.dragndrop.DragController;
@@ -118,10 +117,7 @@
             }
         });
 
-        DeviceProfile grid = launcher.getDeviceProfile();
-        DragOptions options = new DragOptions();
-        options.intrinsicIconScaleFactor = (float) grid.allAppsIconSizePx / grid.iconSizePx;
-        launcher.getWorkspace().beginDragShared(v, launcher.getAppsView(), options);
+        launcher.getWorkspace().beginDragShared(v, launcher.getAppsView(), new DragOptions());
         return false;
     }
 
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 9ac1c0e..ceebc2e 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -376,6 +376,19 @@
         return isRtl ? 1 : -1;
     }
 
+    @Override
+    public void setSecondaryTaskMenuPosition(SplitBounds splitBounds, View taskView,
+            DeviceProfile deviceProfile, View primarySnaphotView, View taskMenuView) {
+        float topLeftTaskPlusDividerPercent = splitBounds.appsStackedVertically
+                ? (splitBounds.topTaskPercent + splitBounds.dividerHeightPercent)
+                : (splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent);
+        FrameLayout.LayoutParams snapshotParams =
+                (FrameLayout.LayoutParams) primarySnaphotView.getLayoutParams();
+        float additionalOffset = (taskView.getHeight() - snapshotParams.topMargin)
+                * topLeftTaskPlusDividerPercent;
+        taskMenuView.setY(taskMenuView.getY() + additionalOffset);
+    }
+
     /* -------------------- */
 
     @Override
@@ -440,7 +453,7 @@
     public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
             int splitInstructionsWidth, int threeButtonNavShift) {
         out.setPivotX(0);
-        out.setPivotY(0);
+        out.setPivotY(splitInstructionsHeight);
         out.setRotation(getDegreesRotated());
         int distanceToEdge = out.getResources().getDimensionPixelSize(
                 R.dimen.split_instructions_bottom_margin_phone_landscape);
@@ -448,8 +461,8 @@
         int insetCorrectionX = dp.getInsets().left;
         // Center the view in case of unbalanced insets on top or bottom of screen
         int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
-        out.setTranslationX(splitInstructionsHeight + distanceToEdge - insetCorrectionX);
-        out.setTranslationY(((splitInstructionsHeight - splitInstructionsWidth) / 2f)
+        out.setTranslationX(distanceToEdge - insetCorrectionX);
+        out.setTranslationY(((-splitInstructionsHeight - splitInstructionsWidth) / 2f)
                 + insetCorrectionY);
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
         // Setting gravity to LEFT instead of the lint-recommended START because we always want this
@@ -491,9 +504,9 @@
             DeviceProfile dp, boolean isRtl) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
-        int dividerBar = splitBoundsConfig.appsStackedVertically
-                ? splitBoundsConfig.visualDividerBounds.height()
-                : splitBoundsConfig.visualDividerBounds.width();
+        int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
+                ? splitBoundsConfig.dividerHeightPercent
+                : splitBoundsConfig.dividerWidthPercent));
         int primarySnapshotHeight;
         int primarySnapshotWidth;
         int secondarySnapshotHeight;
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 1a8d355..cbcb700 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -231,6 +231,14 @@
     int getTaskDragDisplacementFactor(boolean isRtl);
 
     /**
+     * Calls the corresponding {@link View#setX(float)} or {@link View#setY(float)}
+     * on {@param taskMenuView} by taking the space needed by {@param primarySnapshotView} into
+     * account.
+     * This is expected to only be called for secondary (bottom/right) tasks.
+     */
+    void setSecondaryTaskMenuPosition(SplitBounds splitBounds, View taskView,
+            DeviceProfile deviceProfile, View primarySnaphotView, View taskMenuView);
+    /**
      * Maps the velocity from the coordinate plane of the foreground app to that
      * of Launcher's (which now will always be portrait)
      */
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 07d1839..5efebaa 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -50,6 +50,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
@@ -316,6 +317,26 @@
     }
 
     @Override
+    public void setSecondaryTaskMenuPosition(SplitBounds splitBounds, View taskView,
+            DeviceProfile deviceProfile, View primarySnaphotView, View taskMenuView) {
+        float topLeftTaskPlusDividerPercent = splitBounds.appsStackedVertically
+                ? (splitBounds.topTaskPercent + splitBounds.dividerHeightPercent)
+                : (splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent);
+        FrameLayout.LayoutParams snapshotParams =
+                (FrameLayout.LayoutParams) primarySnaphotView.getLayoutParams();
+        float additionalOffset;
+        if (deviceProfile.isLandscape) {
+            additionalOffset = (taskView.getWidth() - snapshotParams.leftMargin)
+                    * topLeftTaskPlusDividerPercent;
+            taskMenuView.setX(taskMenuView.getX() + additionalOffset);
+        } else {
+            additionalOffset = (taskView.getHeight() - snapshotParams.topMargin)
+                    * topLeftTaskPlusDividerPercent;
+            taskMenuView.setY(taskMenuView.getY() + additionalOffset);
+        }
+    }
+
+    @Override
     public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
             int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
             View[] thumbnailViews, int desiredTaskId, View banner) {
@@ -500,11 +521,13 @@
     public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
             int splitInstructionsWidth, int threeButtonNavShift) {
         out.setPivotX(0);
-        out.setPivotY(0);
+        out.setPivotY(splitInstructionsHeight);
         out.setRotation(getDegreesRotated());
         int distanceToEdge;
         if ((DisplayController.getNavigationMode(out.getContext()) == THREE_BUTTONS)
-                && (dp.isTwoPanels || dp.isTablet)) {
+                && (dp.isTwoPanels || dp.isTablet)
+                // If taskbar is in overview, overview action has dedicated space above nav buttons
+                && !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()) {
             // If 3-button nav is active, align the splitInstructionsView with it.
             distanceToEdge = dp.getTaskbarOffsetY()
                     + ((dp.taskbarSize - splitInstructionsHeight) / 2);
@@ -541,8 +564,12 @@
         int insetCorrectionX = (dp.getInsets().right - dp.getInsets().left) / 2;
         // Adjust for any insets on the bottom edge
         int insetCorrectionY = dp.getInsets().bottom;
+        // Adjust for taskbar in overview
+        int taskbarCorrectionY =
+                dp.isTaskbarPresent && FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get()
+                        ? dp.taskbarSize : 0;
         out.setTranslationX(insetCorrectionX + threeButtonNavShift);
-        out.setTranslationY(-distanceToEdge + insetCorrectionY);
+        out.setTranslationY(-distanceToEdge + insetCorrectionY - taskbarCorrectionY);
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
         lp.gravity = CENTER_HORIZONTAL | BOTTOM;
         out.setLayoutParams(lp);
@@ -591,15 +618,17 @@
 
         if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
             if (isLandscape) {
-                outRect.right = outRect.left + (int) (outRect.width() * topLeftTaskPercent);
+                outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent);
             } else {
-                outRect.bottom = outRect.top + (int) (outRect.height() * topLeftTaskPercent);
+                outRect.bottom = outRect.top + Math.round(outRect.height() * topLeftTaskPercent);
             }
         } else {
             if (isLandscape) {
-                outRect.left += (int) (outRect.width() * (topLeftTaskPercent + dividerBarPercent));
+                outRect.left += Math.round(outRect.width()
+                        * (topLeftTaskPercent + dividerBarPercent));
             } else {
-                outRect.top += (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
+                outRect.top += Math.round(outRect.height()
+                        * (topLeftTaskPercent + dividerBarPercent));
             }
         }
     }
@@ -610,9 +639,9 @@
             DeviceProfile dp, boolean isRtl) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
-        int dividerBar = splitBoundsConfig.appsStackedVertically
-                ? (int) (splitBoundsConfig.dividerHeightPercent * parentHeight)
-                : (int) (splitBoundsConfig.dividerWidthPercent * parentWidth);
+        int dividerBar = Math.round(splitBoundsConfig.appsStackedVertically
+                ? splitBoundsConfig.dividerHeightPercent * totalThumbnailHeight
+                : splitBoundsConfig.dividerWidthPercent * parentWidth);
         int primarySnapshotHeight;
         int primarySnapshotWidth;
         int secondarySnapshotHeight;
@@ -621,7 +650,7 @@
                 splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent;
         if (dp.isLandscape) {
             primarySnapshotHeight = totalThumbnailHeight;
-            primarySnapshotWidth = (int) (parentWidth * taskPercent);
+            primarySnapshotWidth = Math.round(parentWidth * taskPercent);
 
             secondarySnapshotHeight = totalThumbnailHeight;
             secondarySnapshotWidth = parentWidth - primarySnapshotWidth - dividerBar;
@@ -636,12 +665,22 @@
             secondarySnapshot.setTranslationY(spaceAboveSnapshot);
         } else {
             primarySnapshotWidth = parentWidth;
-            primarySnapshotHeight = (int) (totalThumbnailHeight * taskPercent);
+            primarySnapshotHeight = Math.round(totalThumbnailHeight * taskPercent);
 
             secondarySnapshotWidth = parentWidth;
             secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
             int translationY = primarySnapshotHeight + spaceAboveSnapshot + dividerBar;
             secondarySnapshot.setTranslationY(translationY);
+
+            FrameLayout.LayoutParams primaryParams =
+                    (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
+            FrameLayout.LayoutParams secondaryParams =
+                    (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
+            secondaryParams.topMargin = 0;
+            primaryParams.topMargin = spaceAboveSnapshot;
+
+            // Reset unused translations
+            primarySnapshot.setTranslationY(0);
             secondarySnapshot.setTranslationX(0);
             primarySnapshot.setTranslationX(0);
         }
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 387e980..a616a8b 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -121,9 +121,9 @@
         // the screen. This is to preserve consistency when the user rotates: From the user's POV,
         // the primary should always be on the left.
         if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
-            outRect.top += (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
+            outRect.top += (int) (outRect.height() * ((1 - topLeftTaskPercent)));
         } else {
-            outRect.bottom = outRect.top + (int) (outRect.height() * topLeftTaskPercent);
+            outRect.bottom -= (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
         }
     }
 
@@ -190,7 +190,7 @@
     public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
             int splitInstructionsWidth, int threeButtonNavShift) {
         out.setPivotX(0);
-        out.setPivotY(0);
+        out.setPivotY(splitInstructionsHeight);
         out.setRotation(getDegreesRotated());
         int distanceToEdge = out.getResources().getDimensionPixelSize(
                 R.dimen.split_instructions_bottom_margin_phone_landscape);
@@ -198,9 +198,8 @@
         int insetCorrectionX = dp.getInsets().right;
         // Center the view in case of unbalanced insets on top or bottom of screen
         int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
-        out.setTranslationX(splitInstructionsWidth - splitInstructionsHeight - distanceToEdge
-                + insetCorrectionX);
-        out.setTranslationY(((splitInstructionsHeight + splitInstructionsWidth) / 2f)
+        out.setTranslationX(splitInstructionsWidth - distanceToEdge + insetCorrectionX);
+        out.setTranslationY(((-splitInstructionsHeight + splitInstructionsWidth) / 2f)
                 + insetCorrectionY);
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
         // Setting gravity to RIGHT instead of the lint-recommended END because we always want this
@@ -267,6 +266,49 @@
         secondaryIconView.setLayoutParams(secondaryIconParams);
     }
 
+    @Override
+    public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
+            int parentWidth, int parentHeight, SplitBounds splitBoundsConfig, DeviceProfile dp,
+            boolean isRtl) {
+        FrameLayout.LayoutParams primaryParams =
+                (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
+        FrameLayout.LayoutParams secondaryParams =
+                (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
+
+        // Swap the margins that are set in TaskView#setRecentsOrientedState()
+        secondaryParams.topMargin = dp.overviewTaskThumbnailTopMarginPx;
+        primaryParams.topMargin = 0;
+
+        // Measure and layout the thumbnails bottom up, since the primary is on the visual left
+        // (portrait bottom) and secondary is on the right (portrait top)
+        int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
+        int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
+        int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
+                ? splitBoundsConfig.dividerHeightPercent
+                : splitBoundsConfig.dividerWidthPercent));
+        int primarySnapshotHeight;
+        int primarySnapshotWidth;
+        int secondarySnapshotHeight;
+        int secondarySnapshotWidth;
+
+        float taskPercent = splitBoundsConfig.appsStackedVertically ?
+                splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent;
+        primarySnapshotWidth = parentWidth;
+        primarySnapshotHeight = (int) (totalThumbnailHeight * (taskPercent));
+
+        secondarySnapshotWidth = parentWidth;
+        secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
+        secondarySnapshot.setTranslationY(0);
+        primarySnapshot.setTranslationY(secondarySnapshotHeight + spaceAboveSnapshot + dividerBar);
+        primarySnapshot.measure(
+                View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY));
+        secondarySnapshot.measure(
+                View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
+                        View.MeasureSpec.EXACTLY));
+    }
+
     /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
 
     @Override
diff --git a/src/com/android/launcher3/util/LogConfig.java b/src/com/android/launcher3/util/LogConfig.java
index 1cb0752..5abf95c 100644
--- a/src/com/android/launcher3/util/LogConfig.java
+++ b/src/com/android/launcher3/util/LogConfig.java
@@ -45,4 +45,14 @@
      * When turned on, we enable IME related latency related logging.
      */
     public static final String IME_LATENCY_LOGGING = "ImeLatencyLogging";
+
+    /**
+     * When turned on, we enable web suggest appSearch related logging.
+     */
+    public static final String WEB_APP_SEARCH_LOGGING = "WebAppSearchLogging";
+
+    /**
+     * When turned on, we enable quick launch v2 related logging.
+     */
+    public static final String QUICK_LAUNCH_V2 = "QuickLaunchV2";
 }
diff --git a/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java b/src/com/android/launcher3/util/MultiPropertyFactory.java
similarity index 70%
rename from src/com/android/launcher3/util/MultiAdditivePropertyFactory.java
rename to src/com/android/launcher3/util/MultiPropertyFactory.java
index 50f7027..e7a7785 100644
--- a/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java
+++ b/src/com/android/launcher3/util/MultiPropertyFactory.java
@@ -20,7 +20,6 @@
 import android.util.FloatProperty;
 import android.util.Log;
 import android.util.Property;
-import android.view.View;
 
 /**
  * Allows to combine multiple values set by several sources.
@@ -30,43 +29,55 @@
  * time.
  *
  * This class behaves similarly to [MultiValueAlpha], but is meant to be more abstract and reusable.
- * It sets the addition of all values.
+ * It aggregate all values using the provided [aggregator].
  *
  * @param <T> Type where to apply the property.
  */
-public class MultiAdditivePropertyFactory<T extends View> {
+public class MultiPropertyFactory<T> {
 
     private static final boolean DEBUG = false;
-    private static final String TAG = "MultiAdditivePropertyFactory";
+    private static final String TAG = "MultiPropertyFactory";
     private final String mName;
-    private final ArrayMap<Integer, MultiAdditiveProperty> mProperties =
-            new ArrayMap<>();
+    private final ArrayMap<Integer, MultiProperty> mProperties = new ArrayMap<>();
 
     // This is an optimization for cases when set is called repeatedly with the same setterIndex.
     private float mAggregationOfOthers = 0f;
     private Integer mLastIndexSet = -1;
-    private final Property<View, Float> mProperty;
+    private final Property<T, Float> mProperty;
+    private final FloatBiFunction mAggregator;
 
-    public MultiAdditivePropertyFactory(String name, Property<View, Float> property) {
+    /**
+     * Represents a function that accepts two float and produces a float.
+     */
+    public interface FloatBiFunction {
+        /**
+         * Applies this function to the given arguments.
+         */
+        float apply(float a, float b);
+    }
+
+    public MultiPropertyFactory(String name, Property<T, Float> property,
+            FloatBiFunction aggregator) {
         mName = name;
         mProperty = property;
+        mAggregator = aggregator;
     }
 
     /** Returns the [MultiFloatProperty] associated with [inx], creating it if not present. */
-    public MultiAdditiveProperty get(Integer index) {
+    public MultiProperty get(Integer index) {
         return mProperties.computeIfAbsent(index,
-                (k) -> new MultiAdditiveProperty(index, mName + "_" + index));
+                (k) -> new MultiProperty(index, mName + "_" + index));
     }
 
     /**
      * Each [setValue] will be aggregated with the other properties values created by the
      * corresponding factory.
      */
-    class MultiAdditiveProperty extends FloatProperty<T> {
+    class MultiProperty extends FloatProperty<T> {
         private final int mInx;
         private float mValue = 0f;
 
-        MultiAdditiveProperty(int inx, String name) {
+        MultiProperty(int inx, String name) {
             super(name);
             mInx = inx;
         }
@@ -77,12 +88,13 @@
                 mAggregationOfOthers = 0f;
                 mProperties.forEach((key, property) -> {
                     if (key != mInx) {
-                        mAggregationOfOthers += property.mValue;
+                        mAggregationOfOthers =
+                                mAggregator.apply(mAggregationOfOthers, property.mValue);
                     }
                 });
                 mLastIndexSet = mInx;
             }
-            float lastAggregatedValue = mAggregationOfOthers + newValue;
+            float lastAggregatedValue = mAggregator.apply(mAggregationOfOthers, newValue);
             mValue = newValue;
             apply(obj, lastAggregatedValue);
 
@@ -94,13 +106,13 @@
         }
 
         @Override
-        public Float get(T view) {
+        public Float get(T object) {
             // The scale of the view should match mLastAggregatedValue. Still, if it has been
             // changed without using this property, it can differ. As this get method is usually
             // used to set the starting point on an animation, this would result in some jumps
             // when the view scale is different than the last aggregated value. To stay on the
             // safe side, let's return the real view scale.
-            return mProperty.get(view);
+            return mProperty.get(object);
         }
 
         @Override
@@ -109,7 +121,7 @@
         }
     }
 
-    protected void apply(View view, float value) {
-        mProperty.set(view, value);
+    protected void apply(T object, float value) {
+        mProperty.set(object, value);
     }
 }
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index f42d304..12e8b54 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -43,6 +43,9 @@
 import android.util.Pair;
 import android.widget.Toast;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -54,6 +57,7 @@
 
 import java.net.URISyntaxException;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Utility methods using package manager
@@ -62,22 +66,28 @@
 
     private static final String TAG = "PackageManagerHelper";
 
+    @NonNull
     private final Context mContext;
+
+    @NonNull
     private final PackageManager mPm;
+
+    @NonNull
     private final LauncherApps mLauncherApps;
 
-    public PackageManagerHelper(Context context) {
+    public PackageManagerHelper(@NonNull final Context context) {
         mContext = context;
         mPm = context.getPackageManager();
-        mLauncherApps = context.getSystemService(LauncherApps.class);
+        mLauncherApps = Objects.requireNonNull(context.getSystemService(LauncherApps.class));
     }
 
     /**
      * Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't
      * guarantee that the app is on SD card.
      */
-    public boolean isAppOnSdcard(String packageName, UserHandle user) {
-        ApplicationInfo info = getApplicationInfo(
+    public boolean isAppOnSdcard(@NonNull final String packageName,
+            @NonNull final UserHandle user) {
+        final ApplicationInfo info = getApplicationInfo(
                 packageName, user, PackageManager.MATCH_UNINSTALLED_PACKAGES);
         return info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
     }
@@ -86,23 +96,27 @@
      * Returns whether the target app is suspended for a given user as per
      * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
      */
-    public boolean isAppSuspended(String packageName, UserHandle user) {
-        ApplicationInfo info = getApplicationInfo(packageName, user, 0);
+    public boolean isAppSuspended(@NonNull final String packageName,
+            @NonNull final UserHandle user) {
+        final ApplicationInfo info = getApplicationInfo(packageName, user, 0);
         return info != null && isAppSuspended(info);
     }
 
     /**
      * Returns whether the target app is installed for a given user
      */
-    public boolean isAppInstalled(String packageName, UserHandle user) {
-        ApplicationInfo info = getApplicationInfo(packageName, user, 0);
+    public boolean isAppInstalled(@NonNull final String packageName,
+            @NonNull final UserHandle user) {
+        final ApplicationInfo info = getApplicationInfo(packageName, user, 0);
         return info != null;
     }
 
     /**
      * Returns the application info for the provided package or null
      */
-    public ApplicationInfo getApplicationInfo(String packageName, UserHandle user, int flags) {
+    @Nullable
+    public ApplicationInfo getApplicationInfo(@NonNull final String packageName,
+            @NonNull final UserHandle user, final int flags) {
         try {
             ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
             return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
@@ -116,7 +130,8 @@
         return mPm.isSafeMode();
     }
 
-    public Intent getAppLaunchIntent(String pkg, UserHandle user) {
+    @Nullable
+    public Intent getAppLaunchIntent(@Nullable final String pkg, @NonNull final UserHandle user) {
         List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(pkg, user);
         return activities.isEmpty() ? null :
                 AppInfo.makeLaunchIntent(activities.get(0));
@@ -251,7 +266,8 @@
         return packageFilter;
     }
 
-    public static boolean isSystemApp(Context context, Intent intent) {
+    public static boolean isSystemApp(@NonNull final Context context,
+            @NonNull final Intent intent) {
         PackageManager pm = context.getPackageManager();
         ComponentName cn = intent.getComponent();
         String packageName = null;
diff --git a/src/com/android/launcher3/util/PendingSplitSelectInfo.java b/src/com/android/launcher3/util/PendingSplitSelectInfo.java
index ed02465..58c3be5 100644
--- a/src/com/android/launcher3/util/PendingSplitSelectInfo.java
+++ b/src/com/android/launcher3/util/PendingSplitSelectInfo.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.util;
 
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
 
 /**
@@ -28,10 +29,13 @@
 
     private final int mStagedTaskId;
     private final int mStagePosition;
+    private final StatsLogManager.EventEnum mSource;
 
-    public PendingSplitSelectInfo(int stagedTaskId, int stagePosition) {
+    public PendingSplitSelectInfo(int stagedTaskId, int stagePosition,
+            StatsLogManager.EventEnum source) {
         this.mStagedTaskId = stagedTaskId;
         this.mStagePosition = stagePosition;
+        this.mSource = source;
     }
 
     public int getStagedTaskId() {
@@ -41,4 +45,8 @@
     public @StagePosition int getStagePosition() {
         return mStagePosition;
     }
+
+    public StatsLogManager.EventEnum getSource() {
+        return mSource;
+    }
 }
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index f14d985..88e1b22 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -16,12 +16,17 @@
 
 package com.android.launcher3.util;
 
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM;
+
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.graphics.Rect;
 
 import androidx.annotation.IntDef;
 
+import com.android.launcher3.logging.StatsLogManager;
+
 import java.lang.annotation.Retention;
 
 public final class SplitConfigurationOptions {
@@ -170,4 +175,10 @@
         @StageType
         public int stageType = STAGE_TYPE_UNDEFINED;
     }
+
+    public static StatsLogManager.EventEnum getLogEventForPosition(@StagePosition int position) {
+        return position == STAGE_POSITION_TOP_OR_LEFT
+                ? LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP
+                : LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM;
+    }
 }
diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java
deleted file mode 100644
index 706b51f..0000000
--- a/src/com/android/launcher3/util/UiThreadHelper.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2017 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.util;
-
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-
-/**
- * Utility class for offloading some class from UI thread
- */
-public class UiThreadHelper {
-
-    private static final MainThreadInitializedObject<Handler> HANDLER =
-            new MainThreadInitializedObject<>(
-                    c -> new Handler(UI_HELPER_EXECUTOR.getLooper(), new UiCallbacks(c)));
-
-    private static final int MSG_SET_ORIENTATION = 1;
-    private static final int MSG_RUN_COMMAND = 2;
-
-    public static void setOrientationAsync(Activity activity, int orientation) {
-        Message.obtain(HANDLER.get(activity), MSG_SET_ORIENTATION, orientation, 0, activity)
-                .sendToTarget();
-    }
-
-    public static void runAsyncCommand(Context context, AsyncCommand command, int arg1, int arg2) {
-        Message.obtain(HANDLER.get(context), MSG_RUN_COMMAND, arg1, arg2, command).sendToTarget();
-    }
-
-    private static class UiCallbacks implements Handler.Callback {
-
-        private final Context mContext;
-
-        UiCallbacks(Context context) {
-            mContext = context;
-        }
-
-        @Override
-        public boolean handleMessage(Message message) {
-            switch (message.what) {
-                case MSG_SET_ORIENTATION:
-                    ((Activity) message.obj).setRequestedOrientation(message.arg1);
-                    return true;
-                case MSG_RUN_COMMAND:
-                    ((AsyncCommand) message.obj).execute(mContext, message.arg1, message.arg2);
-                    return true;
-            }
-            return false;
-        }
-    }
-
-    public interface AsyncCommand {
-        void execute(Context proxy, int arg1, int arg2);
-    }
-}
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 47503b1..f73347a 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -33,6 +33,8 @@
 import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
@@ -41,6 +43,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Extension of {@link AbstractFloatingView} with common methods for sliding in from bottom.
@@ -79,6 +82,7 @@
     protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED;
 
     protected boolean mNoIntercept;
+    protected @Nullable OnCloseListener mOnCloseBeginListener;
     protected List<OnCloseListener> mOnCloseListeners = new ArrayList<>();
 
     public AbstractSlideInView(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -204,6 +208,11 @@
         }
     }
 
+    /** Callback invoked when the view is beginning to close (e.g. close animation is started). */
+    public void setOnCloseBeginListener(@Nullable OnCloseListener onCloseBeginListener) {
+        mOnCloseBeginListener = onCloseBeginListener;
+    }
+
     /** Registers an {@link OnCloseListener}. */
     public void addOnCloseListener(OnCloseListener listener) {
         mOnCloseListeners.add(listener);
@@ -213,6 +222,8 @@
         if (!mIsOpen) {
             return;
         }
+        Optional.ofNullable(mOnCloseBeginListener).ifPresent(OnCloseListener::onSlideInViewClosed);
+
         if (!animate) {
             mOpenCloseAnimator.cancel();
             setTranslationShift(TRANSLATION_SHIFT_CLOSED);
diff --git a/src/com/android/launcher3/views/AllAppsButton.java b/src/com/android/launcher3/views/AllAppsButton.java
index b1e69c7..ab8e5db 100644
--- a/src/com/android/launcher3/views/AllAppsButton.java
+++ b/src/com/android/launcher3/views/AllAppsButton.java
@@ -45,5 +45,6 @@
         Bitmap bitmap = LauncherAppState.getInstance(context).getIconCache().getIconFactory()
                 .createScaledBitmapWithShadow(theme.getDrawable(R.drawable.ic_all_apps_button));
         setIcon(new FastBitmapDrawable(bitmap));
+        setContentDescription(context.getString(R.string.all_apps_button_label));
     }
 }
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 800b1f6..1e154a2 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -273,6 +273,12 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (ev.getActionIndex() > 0) {
+            // This means there is multiple touch inputs, ignore it, we could also cancel the
+            // previous touch but the user might cancel the drag by accident.
+            return true;
+        }
+
         switch (ev.getAction()) {
             case ACTION_DOWN: {
                 if ((mTouchDispatchState & TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS) != 0) {
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index efc83eb..55af622 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.views;
 
+import static android.view.Gravity.LEFT;
+
 import static com.android.launcher3.Utilities.getBadge;
 import static com.android.launcher3.Utilities.getFullDrawable;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -181,8 +183,10 @@
         updatePosition(positionOut, lp);
         setLayoutParams(lp);
 
-        mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
-        mBtvDrawable.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
+        // For code simplicity, we always layout the child views using Gravity.LEFT
+        // and manually handle RTL for FloatingIconView when positioning it on the screen.
+        mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height, LEFT));
+        mBtvDrawable.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height, LEFT));
     }
 
     private void updatePosition(RectF pos, InsettableFrameLayout.LayoutParams lp) {
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 5cffd48..2ac1e94 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.widget;
 
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Paint;
@@ -26,6 +28,7 @@
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
 import android.view.WindowInsets;
+import android.view.animation.Interpolator;
 import android.widget.Toast;
 
 import androidx.annotation.GuardedBy;
@@ -246,6 +249,12 @@
         return true;
     }
 
+    @Override
+    protected Interpolator getIdleInterpolator() {
+        return mActivityContext.getDeviceProfile().isTablet
+                ? EMPHASIZED : super.getIdleInterpolator();
+    }
+
     //
     // Drag related handling methods that implement {@link DragSource} interface.
     //
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
index 5ce8fcf..3e80699 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
@@ -323,6 +323,7 @@
         if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
             // First, we clear any previously cached content from existing widgets
             mCachedRemoteViews.clear();
+            mDeferredViews.clear();
             // Then we proceed to cache the content from the widgets
             for (int i = 0; i < mViews.size(); i++) {
                 final int appWidgetId = mViews.keyAt(i);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 0d9198f..da8e25c 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -85,7 +85,6 @@
         implements ProviderChangedListener, OnActivePageChangedListener,
         WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener {
 
-    private static final long DEFAULT_OPEN_DURATION = 267;
     private static final long FADE_IN_DURATION = 150;
     private static final long EDUCATION_TIP_DELAY_MS = 200;
     private static final long EDUCATION_DIALOG_DELAY_MS = 500;
@@ -582,7 +581,7 @@
             mOpenCloseAnimator.setValues(
                     PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
             mOpenCloseAnimator
-                    .setDuration(DEFAULT_OPEN_DURATION)
+                    .setDuration(mActivityContext.getDeviceProfile().bottomSheetOpenDuration)
                     .setInterpolator(AnimationUtils.loadInterpolator(
                             getContext(), android.R.interpolator.linear_out_slow_in));
             mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@@ -603,7 +602,7 @@
 
     @Override
     protected void handleClose(boolean animate) {
-        handleClose(animate, DEFAULT_OPEN_DURATION);
+        handleClose(animate, mActivityContext.getDeviceProfile().bottomSheetCloseDuration);
     }
 
     @Override
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
index 9daea94..a581f91 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -42,9 +42,9 @@
     @Override
     public <DEVICE_PROFILE_CONTEXT extends Context & DeviceProfileListenable>
     int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) {
-        return !context.getDeviceProfile().isTablet && isToState
-                ? 600
-                : isToState ? 500 : 300;
+        return isToState
+                ? context.getDeviceProfile().allAppsOpenDuration
+                : context.getDeviceProfile().allAppsCloseDuration;
     }
 
     @Override
diff --git a/tests/Android.bp b/tests/Android.bp
index 1584308..39bd307 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -60,6 +60,7 @@
       "src/com/android/launcher3/testcomponent/CustomShortcutConfigActivity.java",
       "src/com/android/launcher3/testcomponent/TestCommandReceiver.java",
       "src/com/android/launcher3/testcomponent/TestLauncherActivity.java",
+      "src/com/android/launcher3/testcomponent/ImeTestActivity.java",
     ],
 }
 
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 9cc3aed..ae1060e 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -277,6 +277,16 @@
             </intent-filter>
         </activity-alias>
 
+        <activity android:name="com.android.launcher3.testcomponent.ImeTestActivity"
+            android:label="ImeTestActivity"
+            android:icon="@drawable/test_theme_icon"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
         <!-- [b/197780098] Disable eager initialization of Jetpack libraries. -->
         <provider
             android:name="androidx.startup.InitializationProvider"
diff --git a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
new file mode 100644
index 0000000..8ce932d
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.celllayout;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.util.ContentWriter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+public class FavoriteItemsTransaction {
+    private ArrayList<ItemInfo> mItemsToSubmit;
+    private Context mContext;
+    private ContentResolver mResolver;
+    public AbstractLauncherUiTest mTest;
+
+    public FavoriteItemsTransaction(Context context, AbstractLauncherUiTest test) {
+        mItemsToSubmit = new ArrayList<>();
+        mContext = context;
+        mResolver = mContext.getContentResolver();
+        mTest = test;
+    }
+
+    public FavoriteItemsTransaction addItem(ItemInfo itemInfo) {
+        this.mItemsToSubmit.add(itemInfo);
+        return this;
+    }
+
+    public FavoriteItemsTransaction removeLast() {
+        this.mItemsToSubmit.remove(this.mItemsToSubmit.size() - 1);
+        return this;
+    }
+
+    /**
+     * Commits all the ItemInfo into the database of Favorites
+     **/
+    public void commit() throws ExecutionException, InterruptedException {
+        List<ContentValues> values = new ArrayList<>();
+        for (ItemInfo item : this.mItemsToSubmit) {
+            ContentWriter writer = new ContentWriter(mContext);
+            item.onAddToDatabase(writer);
+            writer.put(LauncherSettings.Favorites._ID, item.id);
+            values.add(writer.getValues(mContext));
+        }
+        // Submit the icons to the database in the model thread to prevent race conditions
+        MODEL_EXECUTOR.submit(() -> mResolver.bulkInsert(LauncherSettings.Favorites.CONTENT_URI,
+                values.toArray(new ContentValues[0]))).get();
+        // Reload the state of the Launcher
+        MAIN_EXECUTOR.submit(() -> LauncherAppState.getInstance(
+                mContext).getModel().forceReload()).get();
+    }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
index 93fbf97..2846cae 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
@@ -47,6 +47,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.Map;
+import java.util.concurrent.ExecutionException;
 
 
 @SmallTest
@@ -58,7 +59,7 @@
 
     private static final String TAG = ReorderWidgets.class.getSimpleName();
 
-    TestWorkspaceBuilder mBoardBuilder;
+    TestWorkspaceBuilder mWorkspaceBuilder;
 
     private View getViewAt(int cellX, int cellY) {
         return getFromLauncher(l -> l.getWorkspace().getScreenWithId(
@@ -76,7 +77,7 @@
 
     @Before
     public void setup() throws Throwable {
-        mBoardBuilder = new TestWorkspaceBuilder(this);
+        mWorkspaceBuilder = new TestWorkspaceBuilder(this, mTargetContext);
         TaplTestsLauncher3.initialize(this);
         clearHomescreen();
     }
@@ -87,7 +88,7 @@
     private boolean validateBoard(CellLayoutBoard board) {
         boolean match = true;
         Point cellDimensions = getCellDimensions();
-        for (CellLayoutBoard.WidgetRect widgetRect: board.getWidgets()) {
+        for (CellLayoutBoard.WidgetRect widgetRect : board.getWidgets()) {
             if (widgetRect.shouldIgnore()) {
                 continue;
             }
@@ -108,10 +109,13 @@
         return match;
     }
 
-    private void runTestCase(ReorderTestCase testCase) {
+    private void runTestCase(ReorderTestCase testCase)
+            throws ExecutionException, InterruptedException {
         Point mainWidgetCellPos = testCase.mStart.getMain();
 
-        mBoardBuilder.buildBoard(testCase.mStart);
+        FavoriteItemsTransaction transaction =
+                new FavoriteItemsTransaction(mTargetContext, this);
+        mWorkspaceBuilder.buildFromBoard(testCase.mStart, transaction).commit();
 
         Widget widget = mLauncher.getWorkspace().getWidgetAtCell(mainWidgetCellPos.x,
                 mainWidgetCellPos.y);
@@ -132,7 +136,8 @@
      *
      * @param testCaseMap map containing all the tests per grid size (Point)
      */
-    private void runTestCaseMap(Map<Point, ReorderTestCase> testCaseMap, String testName) {
+    private void runTestCaseMap(Map<Point, ReorderTestCase> testCaseMap, String testName)
+            throws ExecutionException, InterruptedException {
         Point iconGridDimensions = mLauncher.getWorkspace().getIconGridDimensions();
         Log.d(TAG, "Running test " + testName + " for grid " + iconGridDimensions);
         Assume.assumeTrue(
@@ -143,26 +148,26 @@
 
     @ScreenRecord // b/242323136
     @Test
-    public void simpleReorder() {
+    public void simpleReorder()  throws ExecutionException, InterruptedException {
         runTestCaseMap(SimpleReorderCase.TEST_BY_GRID_SIZE,
                 SimpleReorderCase.class.getSimpleName());
     }
 
     @ScreenRecord // b/242323136
     @Test
-    public void pushTest() {
+    public void pushTest()  throws ExecutionException, InterruptedException {
         runTestCaseMap(PushReorderCase.TEST_BY_GRID_SIZE, PushReorderCase.class.getSimpleName());
     }
 
     @ScreenRecord // b/242323136
     @Test
-    public void fullReorder() {
+    public void fullReorder()  throws ExecutionException, InterruptedException {
         runTestCaseMap(FullReorderCase.TEST_BY_GRID_SIZE, FullReorderCase.class.getSimpleName());
     }
 
     @ScreenRecord // b/242323136
     @Test
-    public void moveOutReorder() {
+    public void moveOutReorder()  throws ExecutionException, InterruptedException {
         runTestCaseMap(MoveOutReorderCase.TEST_BY_GRID_SIZE,
                 MoveOutReorderCase.class.getSimpleName());
     }
diff --git a/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java b/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
index 10e399d..16448af 100644
--- a/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
+++ b/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
@@ -15,9 +15,12 @@
  */
 package com.android.launcher3.celllayout;
 
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
 import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
 
 import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
 import android.graphics.Rect;
 import android.os.Process;
 import android.os.UserHandle;
@@ -25,8 +28,10 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
@@ -35,6 +40,7 @@
 
 public class TestWorkspaceBuilder {
 
+    private static final String TAG = "CellLayoutBoardBuilder";
     private static final ComponentName APP_COMPONENT_NAME = new ComponentName(
             "com.google.android.calculator", "com.android.calculator2.Calculator");
 
@@ -42,69 +48,118 @@
 
     private UserHandle mMyUser;
 
-    public TestWorkspaceBuilder(AbstractLauncherUiTest test) {
+    private Context mContext;
+    private ContentResolver mResolver;
+
+    public TestWorkspaceBuilder(AbstractLauncherUiTest test, Context context) {
         mTest = test;
         mMyUser = Process.myUserHandle();
+        mContext = context;
+        mResolver = mContext.getContentResolver();
     }
 
-    private static final String TAG = "CellLayoutBoardBuilder";
-
     /**
      * Fills the given rect in WidgetRect with 1x1 widgets. This is useful to equalize cases.
      */
-    private void fillWithWidgets(CellLayoutBoard.WidgetRect widgetRect) {
+    private FavoriteItemsTransaction fillWithWidgets(CellLayoutBoard.WidgetRect widgetRect,
+            FavoriteItemsTransaction transaction) {
         int initX = widgetRect.getCellX();
         int initY = widgetRect.getCellY();
         for (int x = initX; x < initX + widgetRect.getSpanX(); x++) {
             for (int y = initY; y < initY + widgetRect.getSpanY(); y++) {
                 try {
                     // this widgets are filling, we don't care if we can't place them
-                    addWidgetInCell(
+                    ItemInfo item = createWidgetInCell(
                             new CellLayoutBoard.WidgetRect(CellLayoutBoard.CellType.IGNORE,
                                     new Rect(x, y, x, y))
                     );
+                    transaction.addItem(item);
                 } catch (Exception e) {
                     Log.d(TAG, "Unable to place filling widget at " + x + "," + y);
                 }
             }
         }
+        return transaction;
     }
 
-    private void addWidgetInCell(CellLayoutBoard.WidgetRect widgetRect) {
+    private int getID() {
+        return LauncherSettings.Settings.call(
+                        mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
+                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+    }
+
+    private AppInfo getApp() {
+        return new AppInfo(APP_COMPONENT_NAME, "test icon", mMyUser,
+                AppInfo.makeLaunchIntent(APP_COMPONENT_NAME));
+    }
+
+    private void addCorrespondingWidgetRect(CellLayoutBoard.WidgetRect widgetRect,
+            FavoriteItemsTransaction transaction) {
+        if (widgetRect.mType == 'x') {
+            fillWithWidgets(widgetRect, transaction);
+        } else {
+            transaction.addItem(createWidgetInCell(widgetRect));
+        }
+    }
+
+    /**
+     * Builds the given board into the transaction
+     */
+    public FavoriteItemsTransaction buildFromBoard(CellLayoutBoard board,
+            FavoriteItemsTransaction transaction) {
+        board.getWidgets().forEach(
+                (widgetRect) -> addCorrespondingWidgetRect(widgetRect, transaction));
+        board.getIcons().forEach((iconPoint) ->
+                transaction.addItem(createIconInCell(iconPoint))
+        );
+        return transaction;
+    }
+
+    /**
+     * Fills the hotseat row with apps instead of suggestions, for this to work the workspace should
+     * be clean otherwise this doesn't overrides the existing icons.
+     */
+    public FavoriteItemsTransaction fillHotseatIcons(FavoriteItemsTransaction transaction) {
+        int hotseatCount = InvariantDeviceProfile.INSTANCE.get(mContext).numShownHotseatIcons;
+        for (int i = 0; i < hotseatCount; i++) {
+            transaction.addItem(getHotseatValues(i));
+        }
+        return transaction;
+    }
+
+    private ItemInfo createWidgetInCell(CellLayoutBoard.WidgetRect widgetRect) {
         LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(mTest, false);
         LauncherAppWidgetInfo item = createWidgetInfo(info,
                 ApplicationProvider.getApplicationContext(), true);
-
+        item.id = getID();
         item.cellX = widgetRect.getCellX();
         item.cellY = widgetRect.getCellY();
         item.spanX = widgetRect.getSpanX();
         item.spanY = widgetRect.getSpanY();
-        mTest.addItemToScreen(item);
+        item.screenId = FIRST_SCREEN_ID;
+        return item;
     }
 
-    private void addIconInCell(CellLayoutBoard.IconPoint iconPoint) {
-        AppInfo appInfo = new AppInfo(APP_COMPONENT_NAME, "test icon", mMyUser,
-                AppInfo.makeLaunchIntent(APP_COMPONENT_NAME));
-
-        appInfo.cellX = iconPoint.getCoord().x;
-        appInfo.cellY = iconPoint.getCoord().y;
-        appInfo.minSpanY = appInfo.minSpanX = appInfo.spanX = appInfo.spanY = 1;
-        appInfo.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
-        appInfo.componentName = APP_COMPONENT_NAME;
-
-        mTest.addItemToScreen(new WorkspaceItemInfo(appInfo));
+    private ItemInfo createIconInCell(CellLayoutBoard.IconPoint iconPoint) {
+        WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
+        item.id = getID();
+        item.screenId = FIRST_SCREEN_ID;
+        item.cellX = iconPoint.getCoord().x;
+        item.cellY = iconPoint.getCoord().y;
+        item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
+        item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+        return item;
     }
 
-    private void addCorrespondingWidgetRect(CellLayoutBoard.WidgetRect widgetRect) {
-        if (widgetRect.mType == 'x') {
-            fillWithWidgets(widgetRect);
-        } else {
-            addWidgetInCell(widgetRect);
-        }
-    }
-
-    public void buildBoard(CellLayoutBoard board) {
-        board.getWidgets().forEach(this::addCorrespondingWidgetRect);
-        board.getIcons().forEach(this::addIconInCell);
+    private ItemInfo getHotseatValues(int x) {
+        WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
+        item.id = getID();
+        item.cellX = x;
+        item.cellY = 0;
+        item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
+        item.rank = x;
+        item.screenId = x;
+        item.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+        return item;
     }
 }
diff --git a/tests/src/com/android/launcher3/celllayout/testcases/FullReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/FullReorderCase.java
index 94e55cf..a98882c 100644
--- a/tests/src/com/android/launcher3/celllayout/testcases/FullReorderCase.java
+++ b/tests/src/com/android/launcher3/celllayout/testcases/FullReorderCase.java
@@ -31,13 +31,13 @@
             + "xxxxx\n"
             + "222mm\n"
             + "222mm\n"
-            + "ad111\n"
-            + "bc111";
+            + "ii111\n"
+            + "ii111";
     private static final Point MOVE_TO_5x5 = new Point(0, 4);
     private static final String END_BOARD_STR_5x5 = ""
             + "xxxxx\n"
-            + "222ad\n"
-            + "222bc\n"
+            + "222ii\n"
+            + "222ii\n"
             + "mm111\n"
             + "mm111";
     private static final ReorderTestCase TEST_CASE_5x5 = new ReorderTestCase(START_BOARD_STR_5x5,
@@ -50,13 +50,13 @@
             + "xxxxxx\n"
             + "2222mm\n"
             + "2222mm\n"
-            + "ad1111\n"
-            + "bc1111";
+            + "ii1111\n"
+            + "ii1111";
     private static final Point MOVE_TO_6x5 = new Point(0, 4);
     private static final String END_BOARD_STR_6x5 = ""
             + "xxxxxx\n"
-            + "2222ad\n"
-            + "2222bc\n"
+            + "2222ii\n"
+            + "2222ii\n"
             + "mm1111\n"
             + "mm1111";
     private static final ReorderTestCase TEST_CASE_6x5 = new ReorderTestCase(START_BOARD_STR_6x5,
@@ -68,13 +68,13 @@
     private static final String START_BOARD_STR_4x4 = ""
             + "xxxx\n"
             + "22mm\n"
-            + "admm\n"
-            + "bc11";
+            + "iimm\n"
+            + "ii11";
     private static final Point MOVE_TO_4x4 = new Point(0, 3);
     private static final String END_BOARD_STR_4x4 = ""
             + "xxxx\n"
-            + "22ad\n"
-            + "mmbc\n"
+            + "22ii\n"
+            + "mmii\n"
             + "mm11";
 
     private static final ReorderTestCase TEST_CASE_4x4 = new ReorderTestCase(START_BOARD_STR_4x4,
diff --git a/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java
index a222d3d..047d342 100644
--- a/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java
+++ b/tests/src/com/android/launcher3/celllayout/testcases/MoveOutReorderCase.java
@@ -63,25 +63,7 @@
             MOVE_TO_6x5,
             END_BOARD_STR_6x5);
 
-    /** 4x4 Test
-     **/
-    private static final String START_BOARD_STR_4x4 = ""
-            + "xxxx\n"
-            + "34-m\n"
-            + "3511\n"
-            + "3211";
-    private static final Point MOVE_TO_4x4 = new Point(1, 2);
-    private static final String END_BOARD_STR_4x4 = ""
-            + "xxxx\n"
-            + "345-\n"
-            + "3m11\n"
-            + "3211";
-    private static final ReorderTestCase TEST_CASE_4x4 = new ReorderTestCase(START_BOARD_STR_4x4,
-            MOVE_TO_4x4,
-            END_BOARD_STR_4x4);
-
     public static final Map<Point, ReorderTestCase> TEST_BY_GRID_SIZE =
             Map.of(new Point(5, 5), TEST_CASE_5x5,
-                    new Point(6, 5), TEST_CASE_6x5,
-                    new Point(4, 4), TEST_CASE_4x4);
+                    new Point(6, 5), TEST_CASE_6x5);
 }
diff --git a/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java b/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java
index e16ff42..38c9aee 100644
--- a/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java
+++ b/tests/src/com/android/launcher3/celllayout/testcases/PushReorderCase.java
@@ -64,25 +64,7 @@
             MOVE_TO_6x5,
             END_BOARD_STR_6x5);
 
-    /** 4x4 Test
-     **/
-    private static final String START_BOARD_STR_4x4 = ""
-            + "xxxx\n"
-            + "222m\n"
-            + "-111\n"
-            + "----";
-    private static final Point MOVE_TO_4x4 = new Point(2, 1);
-    private static final String END_BOARD_STR_4x4 = ""
-            + "xxxx\n"
-            + "--m-\n"
-            + "222-\n"
-            + "-111";
-    private static final ReorderTestCase TEST_CASE_4x4 = new ReorderTestCase(START_BOARD_STR_4x4,
-            MOVE_TO_4x4,
-            END_BOARD_STR_4x4);
-
     public static final Map<Point, ReorderTestCase> TEST_BY_GRID_SIZE =
             Map.of(new Point(5, 5), TEST_CASE_5x5,
-                    new Point(6, 5), TEST_CASE_6x5,
-                    new Point(4, 4), TEST_CASE_4x4);
+                    new Point(6, 5), TEST_CASE_6x5);
 }
diff --git a/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
index 9c6d102..d3ce67c 100644
--- a/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
+++ b/tests/src/com/android/launcher3/testcomponent/BaseTestingActivity.java
@@ -24,7 +24,9 @@
 import android.os.Bundle;
 import android.util.TypedValue;
 import android.view.View;
+import android.view.WindowInsets;
 import android.widget.Button;
+import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.LinearLayout.LayoutParams;
 
@@ -81,6 +83,20 @@
         mView.addView(button, lp);
     }
 
+    protected void addEditor(String initText, String hint, boolean requestIme) {
+        EditText editText = new EditText(this);
+        editText.setHint(hint);
+        editText.setText(initText);
+
+        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+        lp.bottomMargin = mMargin;
+        mView.addView(editText, lp);
+        if (requestIme) {
+            editText.requestFocus();
+            mView.getWindowInsetsController().show(WindowInsets.Type.ime());
+        }
+    }
+
     @Override
     protected void onResume() {
         super.onResume();
diff --git a/tests/src/com/android/launcher3/testcomponent/ImeTestActivity.java b/tests/src/com/android/launcher3/testcomponent/ImeTestActivity.java
new file mode 100644
index 0000000..43952d5
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/ImeTestActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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.testcomponent;
+
+import android.os.Bundle;
+
+public class ImeTestActivity extends OtherBaseTestingActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // Requests to focus an editor and show IME for test.
+        addEditor("Focused editor for test", "Focused editor for test", true);
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index a66b09a..32b62fb 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -151,6 +151,8 @@
                     device.executeShellCommand(
                             "am dumpheap " + device.getLauncherPackageName() + " " + fileName);
                 }
+                Log.d(TAG, "Saved leak dump, the leak is still present: "
+                        + !launcher.noLeakedActivities());
                 sDumpWasGenerated = true;
                 result = "saved memory dump as an artifact";
             } catch (Throwable e) {
@@ -477,6 +479,16 @@
                 false /* newTask */);
     }
 
+    public static void startImeTestActivity() {
+        final String packageName = getAppPackageName();
+        final Intent intent = getInstrumentation().getContext().getPackageManager().
+                getLaunchIntentForPackage(packageName);
+        intent.setComponent(new ComponentName(packageName,
+                "com.android.launcher3.testcomponent.ImeTestActivity"));
+        startIntent(intent, By.pkg(packageName).text("ImeTestActivity"),
+                false /* newTask */);
+    }
+
     private static void startIntent(Intent intent, BySelector selector, boolean newTask) {
         intent.addCategory(Intent.CATEGORY_LAUNCHER);
         if (newTask) {
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index be49974..a3eee00 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -18,8 +18,6 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
-import android.platform.test.annotations.IwTest;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -31,6 +29,7 @@
 
 import android.content.Intent;
 import android.graphics.Point;
+import android.platform.test.annotations.IwTest;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -437,6 +436,23 @@
 
     @Test
     @PortraitLandscape
+    public void testDragAndCancelAppIcon() {
+        final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(GMAIL_APP_NAME);
+        Point positionBeforeDrag =
+                mLauncher.getWorkspace().getWorkspaceIconsPositions().get(GMAIL_APP_NAME);
+        assertNotNull("App not found in Workspace before dragging.", positionBeforeDrag);
+
+        mLauncher.getWorkspace().dragAndCancelAppIcon(homeAppIcon);
+
+        Point positionAfterDrag =
+                mLauncher.getWorkspace().getWorkspaceIconsPositions().get(GMAIL_APP_NAME);
+        assertNotNull("App not found in Workspace after dragging.", positionAfterDrag);
+        assertEquals("App not returned to same position in Workspace after drag & cancel",
+                positionBeforeDrag, positionAfterDrag);
+    }
+
+    @Test
+    @PortraitLandscape
     public void testDeleteFromWorkspace() throws Exception {
         // test delete both built-in apps and user-installed app from workspace
         for (String appName : new String[]{"Gmail", "Play Store", APP_NAME}) {
diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
index 3324959..e7e551f 100644
--- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -47,6 +47,7 @@
 import android.test.mock.MockContentResolver;
 import android.util.ArrayMap;
 
+import androidx.annotation.NonNull;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.uiautomator.UiDevice;
 
@@ -194,8 +195,9 @@
         Executor mockExecutor = mock(Executor.class);
         model.enqueueModelUpdateTask(new ModelUpdateTask() {
             @Override
-            public void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel,
-                    AllAppsList allAppsList, Executor uiExecutor) {
+            public void init(@NonNull final LauncherAppState app,
+                    @NonNull final LauncherModel model, @NonNull final BgDataModel dataModel,
+                    @NonNull final AllAppsList allAppsList, @NonNull final Executor uiExecutor) {
                 task.init(app, model, dataModel, allAppsList, mockExecutor);
             }
 
diff --git a/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt b/tests/src/com/android/launcher3/util/MultiPropertyFactoryTest.kt
similarity index 75%
rename from tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt
rename to tests/src/com/android/launcher3/util/MultiPropertyFactoryTest.kt
index 309d055..a4f189c 100644
--- a/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt
+++ b/tests/src/com/android/launcher3/util/MultiPropertyFactoryTest.kt
@@ -16,26 +16,32 @@
 
 package com.android.launcher3.util
 
-import android.view.View
+import android.util.FloatProperty
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 
-/** Unit tests for [MultiAdditivePropertyFactory] */
+/** Unit tests for [MultiPropertyFactory] */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class MultiAdditivePropertyTest {
+class MultiPropertyFactoryTest {
 
     private val received = mutableListOf<Float>()
 
-    private val factory =
-        object : MultiAdditivePropertyFactory<View?>("Test", View.TRANSLATION_X) {
-            override fun apply(obj: View?, value: Float) {
-                received.add(value)
-            }
+    private val receiveProperty: FloatProperty<Any> = object : FloatProperty<Any>("receive") {
+        override fun setValue(obj: Any?, value: Float) {
+            received.add(value)
         }
+        override fun get(o: Any): Float {
+            return 0f
+        }
+    }
+
+    private val factory = MultiPropertyFactory("depth_property", receiveProperty) {
+        x: Float, y: Float -> x + y
+    }
 
     private val p1 = factory.get(1)
     private val p2 = factory.get(2)
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index eb8d055..15705e7 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -206,21 +206,30 @@
                 MotionEvent.ACTION_UP, end, gestureScope);
     }
 
+    /**
+     * Quick switching to the app with swiping to right.
+     */
     @NonNull
     public LaunchedAppState quickSwitchToPreviousApp() {
-        boolean toRight = true;
-        quickSwitch(toRight);
+        quickSwitch(true /* toRight */);
         return new LaunchedAppState(mLauncher);
     }
 
+    /**
+     * Quick switching to the app with swiping to left.
+     */
     @NonNull
     public LaunchedAppState quickSwitchToPreviousAppSwipeLeft() {
-        boolean toRight = false;
-        quickSwitch(toRight);
+        quickSwitch(false /* toRight */);
         return new LaunchedAppState(mLauncher);
     }
 
-    @NonNull
+    /**
+     * Making swipe gesture to quick-switch app tasks.
+     *
+     * @param toRight {@code true} means swiping right, {@code false} means swiping left.
+     * @throws {@link AssertionError} when failing to verify the visible UI in the container.
+     */
     private void quickSwitch(boolean toRight) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index a17651b..4b02ecc 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -72,6 +72,16 @@
     }
 
     /**
+     * Waits for the taskbar to be visible, or fails.
+     */
+    public void assertTaskbarVisible() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "waiting for taskbar to be visible")) {
+            mLauncher.waitForLauncherObject(TASKBAR_RES_ID);
+        }
+    }
+
+    /**
      * Returns the Taskbar in a visible state.
      *
      * The taskbar must already be hidden when calling this method.
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 1fb8cc7..56ba9ea 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -68,7 +68,6 @@
 import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.testing.shared.TestInformationRequest;
 import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.systemui.shared.system.ContextUtils;
 import com.android.systemui.shared.system.QuickStepContract;
 
 import org.junit.Assert;
@@ -111,8 +110,11 @@
     static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
     static final Pattern EVENT_TOUCH_CANCEL_TIS = getTouchEventPatternTIS("ACTION_CANCEL");
 
-    static final Pattern EVENT_KEY_BACK_DOWN = getKeyEventPattern("ACTION_DOWN", "KEYCODE_BACK");
-    static final Pattern EVENT_KEY_BACK_UP = getKeyEventPattern("ACTION_UP", "KEYCODE_BACK");
+    private static final Pattern EVENT_KEY_BACK_DOWN =
+            getKeyEventPattern("ACTION_DOWN", "KEYCODE_BACK");
+    private static final Pattern EVENT_KEY_BACK_UP =
+            getKeyEventPattern("ACTION_UP", "KEYCODE_BACK");
+    private static final Pattern EVENT_ON_BACK_INVOKED = Pattern.compile("onBackInvoked");
 
     private final String mLauncherPackage;
     private Boolean mIsLauncher3;
@@ -136,6 +138,15 @@
         OUTSIDE_WITH_KEYCODE,
     }
 
+    /**
+     * Represents a point in the code at which a callback can run.
+     */
+    public enum CALLBACK_RUN_POINT {
+        CALLBACK_HOLD_BEFORE_DROP
+    }
+
+    private Consumer<CALLBACK_RUN_POINT> mCallbackAtRunPoint = null;
+
     // Base class for launcher containers.
     abstract static class VisibleContainer {
         protected final LauncherInstrumentation mLauncher;
@@ -183,13 +194,14 @@
     private final Deque<String> mDiagnosticContext = new LinkedList<>();
     private Function<Long, String> mSystemHealthSupplier;
 
+    private boolean mIgnoreTaskbarVisibility = false;
+
     private Consumer<ContainerType> mOnSettledStateAction;
 
     private LogEventChecker mEventChecker;
 
     private boolean mCheckEventsForSuccessfulGestures = false;
     private Runnable mOnLauncherCrashed;
-
     private static Pattern getTouchEventPattern(String prefix, String action) {
         // The pattern includes checks that we don't get a multi-touch events or other surprises.
         return Pattern.compile(
@@ -237,7 +249,7 @@
         // Launcher package. As during inproc tests the tested launcher may not be selected as the
         // current launcher, choosing target package for inproc. For out-of-proc, use the installed
         // launcher package.
-        mLauncherPackage = testPackage.equals(targetPackage)
+        mLauncherPackage = testPackage.equals(targetPackage) || isGradleInstrumentation()
                 ? getLauncherPackageName()
                 : targetPackage;
 
@@ -263,7 +275,7 @@
                 SystemClock.sleep(5000);
             } else {
                 try {
-                    final int userId = ContextUtils.getUserId(getContext());
+                    final int userId = getContext().getUserId();
                     final String launcherPidCommand = "pidof " + pi.packageName;
                     final String initialPid = mDevice.executeShellCommand(launcherPidCommand)
                             .replaceAll("\\s", "");
@@ -284,6 +296,20 @@
         }
     }
 
+    /**
+     * Gradle only supports out of process instrumentation. The test package is automatically
+     * generated by appending `.test` to the target package.
+     */
+    private boolean isGradleInstrumentation() {
+        final String testPackage = getContext().getPackageName();
+        final String targetPackage = mInstrumentation.getTargetContext().getPackageName();
+        final String testSuffix = ".test";
+
+        return testPackage.endsWith(testSuffix) && testPackage.length() > testSuffix.length()
+            && testPackage.substring(0, testPackage.length() - testSuffix.length())
+            .equals(targetPackage);
+    }
+
     public void enableCheckEventsForSuccessfulGestures() {
         mCheckEventsForSuccessfulGestures = true;
     }
@@ -664,6 +690,18 @@
         }
     }
 
+    /**
+     * Whether to ignore verifying the task bar visibility during instrumenting.
+     *
+     * @param ignoreTaskbarVisibility {@code true} will ignore the instrumentation implicitly
+     *                                            verifying the task bar visibility with
+     *                                            {@link VisibleContainer#verifyActiveContainer}.
+     *                                            {@code false} otherwise.
+     */
+    public void setIgnoreTaskbarVisibility(boolean ignoreTaskbarVisibility) {
+        mIgnoreTaskbarVisibility = ignoreTaskbarVisibility;
+    }
+
     public void setExpectedRotation(int expectedRotation) {
         mExpectedRotation = expectedRotation;
     }
@@ -782,6 +820,9 @@
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
                     waitUntilLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
 
+                    if (mIgnoreTaskbarVisibility) {
+                        return null;
+                    }
                     if (isTablet() && !isFallbackOverview()) {
                         waitForLauncherObject(TASKBAR_RES_ID);
                     } else {
@@ -891,7 +932,14 @@
     }
 
     /**
-     * Presses nav bar home button.
+     * Goes to home by swiping up in zero-button mode or pressing Home button.
+     * Calling it after another TAPL call is safe because all TAPL methods wait for the animations
+     * to finish.
+     * When calling it after a non-TAPL method, make sure that all animations have already
+     * completed, otherwise it may detect the current state (for example "Application" or "Home")
+     * incorrectly.
+     * The method expects either app or Launcher to be active when it's called. Other states, such
+     * as visible notification shade are not supported.
      *
      * @return the Workspace object.
      */
@@ -984,8 +1032,12 @@
                 }
             }
             if (launcherVisible) {
-                expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_DOWN);
-                expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP);
+                if (getContext().getApplicationInfo().isOnBackInvokedCallbackEnabled()) {
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ON_BACK_INVOKED);
+                } else {
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_DOWN);
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP);
+                }
             }
         }
     }
@@ -1888,8 +1940,9 @@
 
     /**
      * Taps outside container to dismiss.
+     *
      * @param container container to be dismissed
-     * @param tapRight tap on the right of the container if true, or left otherwise
+     * @param tapRight  tap on the right of the container if true, or left otherwise
      */
     void touchOutsideContainer(UiObject2 container, boolean tapRight) {
         try (LauncherInstrumentation.Closable c = addContextLayer(
@@ -1907,4 +1960,20 @@
                     LauncherInstrumentation.GestureScope.INSIDE);
         }
     }
+
+    /**
+     * Sets the consumer to run callbacks at all run-points.
+     */
+    public void setRunPointCallback(Consumer<CALLBACK_RUN_POINT> callback) {
+        mCallbackAtRunPoint = callback;
+    }
+
+    /**
+     * Runs the callback at the specified point if it exists.
+     */
+    void runCallbackIfActive(CALLBACK_RUN_POINT runPoint) {
+        if (mCallbackAtRunPoint != null) {
+            mCallbackAtRunPoint.accept(runPoint);
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 5fab7eb..425a90a 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -18,6 +18,7 @@
 
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_SCROLLED;
 
+import static com.android.launcher3.tapl.LauncherInstrumentation.CALLBACK_RUN_POINT.CALLBACK_HOLD_BEFORE_DROP;
 import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
@@ -302,6 +303,31 @@
     }
 
     /**
+     * Drag the appIcon from the workspace and cancel by dragging icon to corner of screen where no
+     * drop point exists.
+     *
+     * @param homeAppIcon to be dragged.
+     */
+    @NonNull
+    public Workspace dragAndCancelAppIcon(HomeAppIcon homeAppIcon) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "dragging app icon across workspace")) {
+            dragIconToWorkspace(
+                    mLauncher,
+                    homeAppIcon,
+                    () -> new Point(0, 0),
+                    () -> mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT),
+                    null);
+
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "dragged the app across workspace")) {
+                return new Workspace(mLauncher);
+            }
+        }
+    }
+
+    /**
      * Delete the appIcon from the workspace.
      *
      * @param homeAppIcon to be deleted.
@@ -475,7 +501,9 @@
             // Since the destination can be on another page, we need to drag to the edge first
             // until we reach the target page
             while (targetDest.x > displayX || targetDest.x < 0) {
-                int edgeX = targetDest.x > 0 ? displayX : 0;
+                // Don't drag all the way to the edge to prevent touch events from getting out of
+                //screen bounds.
+                int edgeX = targetDest.x > 0 ? displayX - 1 : 1;
                 Point screenEdge = new Point(edgeX, targetDest.y);
                 Point finalDragStart = dragStart;
                 executeAndWaitForPageScroll(launcher,
@@ -491,6 +519,7 @@
             launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, isDecelerating,
                     downTime, SystemClock.uptimeMillis(), false,
                     LauncherInstrumentation.GestureScope.INSIDE);
+            launcher.runCallbackIfActive(CALLBACK_HOLD_BEFORE_DROP);
             dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
         }
     }