Merge "Allow Taskbar stashing for external tests" into tm-qpr-dev
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/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index e7a6cfe..9a1ed4d 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -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);
     }
 
     /**
@@ -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
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/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/taskbar/FloatingTaskIntentResolver.java b/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java
new file mode 100644
index 0000000..c62493c
--- /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);
+        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..a219ac6 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;
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 4fda50e..a90af04 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;
@@ -167,7 +167,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();
 
@@ -827,14 +827,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 +862,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/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/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 48fde8f..7b2b7ec 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
 
 /**
@@ -55,8 +57,7 @@
         this.controllers = controllers
         windowLayoutParams = context.windowLayoutParams
 
-        val wmWrapper: WindowManagerWrapper = getInstance()
-        wmWrapper.setProvidesInsetsTypes(
+        setProvidesInsetsTypes(
             windowLayoutParams,
             intArrayOf(
                 ITYPE_EXTRA_NAVIGATION_BAR,
@@ -108,10 +109,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 +133,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 +152,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
+                    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/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 419b7a4..4b0adb1 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;
 
@@ -96,7 +94,7 @@
      * 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.
@@ -546,13 +544,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() {
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..00d5083 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;
@@ -427,6 +429,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/allapps/TaskbarAllAppsContext.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
index c76180e..a1385c4 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;
@@ -47,9 +48,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.
@@ -163,7 +161,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 +172,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 +204,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/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/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 12d7a98..1316ddd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -121,7 +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.ViewCapture;
 import com.android.launcher3.widget.LauncherAppWidgetHost;
 import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.RecentsModel;
@@ -135,11 +134,11 @@
 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.UnfoldTransitionFactory;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
@@ -192,7 +191,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));
@@ -587,6 +586,8 @@
     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,
@@ -800,8 +801,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(
@@ -899,8 +900,8 @@
             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..0723f8a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -27,13 +27,13 @@
 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.Pair;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimatorListeners;
@@ -71,7 +71,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
@@ -92,7 +95,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 +108,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 +123,20 @@
                         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);
+        mRecentsView.createSplitSelectInitAnimation(builder,
+                toState.getTransitionDuration(mLauncher, true /* isToState */));
+        // Add properties to shift remaining taskViews to get out of placeholder view
+        builder.setFloat(mRecentsView, taskViewsFloat.first,
+                toState.getSplitSelectTranslation(mLauncher), LINEAR);
+        builder.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR);
 
-            if (!animate) {
-                splitSelectInitAnimation.buildAnim().start();
-            } else {
-                builder.add(splitSelectInitAnimation.buildAnim());
-            }
-
-            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/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 872e64a..f75a404 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;
 
 /**
@@ -188,6 +190,18 @@
             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 = SplitAnimationTimings.OVERVIEW_TO_SPLIT;
+            config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR,
+                    timings.getActionsFadeStartOffset(),
+                    timings.getActionsFadeEndOffset()));
+        } else if (fromState == NORMAL && toState == OVERVIEW_SPLIT_SELECT) {
+            SplitAnimationTimings timings = SplitAnimationTimings.NORMAL_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..430053d 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,11 @@
         RecentsView recentsView = launcher.getOverviewPanel();
         return recentsView.getSplitSelectTranslation();
     }
+
+    @Override
+    public int getTransitionDuration(Context context, boolean isToState) {
+        return isToState
+                ? SplitAnimationTimings.ENTER_DURATION
+                : SplitAnimationTimings.ABORT_DURATION;
+    }
 }
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..24bb393 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -50,7 +50,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;
@@ -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,7 +127,6 @@
 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.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -622,8 +621,8 @@
 
         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
@@ -827,10 +826,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 +876,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;
@@ -1150,6 +1140,13 @@
             boolean isCancel) {
         long duration = MAX_SWIPE_DURATION;
         float currentShift = mCurrentShift.value;
+        boolean recentsVisible = mRecentsView != null
+                && (mRecentsView.getWindowVisibility() == View.VISIBLE);
+        if (!recentsVisible) {
+            // We've hit a case where Launcher is been stopped mid-gesture, in this case, force
+            // a LAST_TASK end target
+            isCancel = true;
+        }
         final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
                 isFling, isCancel);
         // Set the state, but don't notify until the animation completes
@@ -1229,7 +1226,7 @@
 
         // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
         // or resumeLastTask().
-        if (mRecentsView != null) {
+        if (recentsVisible) {
             ActiveGestureLog.INSTANCE.trackEvent(ActiveGestureErrorDetector.GestureEvent
                     .SET_ON_PAGE_TRANSITION_END_CALLBACK);
             mRecentsView.setOnPageTransitionEndCallback(
@@ -1454,6 +1451,7 @@
         }
     }
 
+    @Nullable
     private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
             RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
         // Directly animate the app to PiP (picture-in-picture) mode
@@ -1479,6 +1477,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
@@ -1783,10 +1785,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);
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 703db07..bcd9687 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -21,6 +21,8 @@
 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;
@@ -335,9 +337,13 @@
             case HOME:
                 ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_HOME);
                 break;
-            case RECENTS:
             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
         }
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 5a9862c..dea4e48 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -20,6 +20,7 @@
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
+import android.app.KeyguardManager;
 import android.os.Build;
 import android.os.Process;
 import android.os.RemoteException;
@@ -31,7 +32,6 @@
 import com.android.launcher3.util.SplitConfigurationOptions;
 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 +49,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 +66,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;
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 3e3a431..c879494 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -30,6 +30,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 +78,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 +131,7 @@
 
         SplitSelectStateController controller =
                 new SplitSelectStateController(this, mHandler, getStateManager(),
-                        null /* depthController */);
+                        /* depthController */ null, getStatsLogManager());
         mDragLayer.recreateControllers();
         mFallbackRecentsView.init(mActionsView, controller);
 
@@ -263,8 +263,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(
@@ -406,8 +408,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 3a7ade3..dcf685a 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -45,8 +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;
@@ -56,6 +58,7 @@
 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.floating.IFloatingTasks;
 import com.android.wm.shell.onehanded.IOneHanded;
 import com.android.wm.shell.pip.IPip;
 import com.android.wm.shell.pip.IPipAnimationListener;
@@ -86,6 +89,7 @@
     private IPip mPip;
     private ISysuiUnlockAnimationController mSysuiUnlockAnimationController;
     private ISplitScreen mSplitScreen;
+    private IFloatingTasks mFloatingTasks;
     private IOneHanded mOneHanded;
     private IShellTransitions mShellTransitions;
     private IStartingWindow mStartingWindow;
@@ -162,7 +166,7 @@
     }
 
     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) {
@@ -170,6 +174,7 @@
         mSystemUiProxy = proxy;
         mPip = pip;
         mSplitScreen = splitScreen;
+        mFloatingTasks = floatingTasks;
         mOneHanded = oneHanded;
         mShellTransitions = shellTransitions;
         mStartingWindow = startingWindow;
@@ -202,7 +207,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);
     }
 
     // TODO(141886704): Find a way to remove this
@@ -480,6 +485,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) {
@@ -539,11 +548,11 @@
     /** 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) {
+            RemoteTransitionCompat remoteTransition, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startTasks(mainTaskId, mainOptions, sideTaskId, sideOptions,
-                        sidePosition, splitRatio, remoteTransition.getTransition());
+                        sidePosition, splitRatio, remoteTransition.getTransition(), instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startTask");
             }
@@ -555,11 +564,11 @@
      */
     public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId,
             Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition,
-            float splitRatio, RemoteAnimationAdapter adapter) {
+            float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId,
-                        sideOptions, sidePosition, splitRatio, adapter);
+                        sideOptions, sidePosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startTasksWithLegacyTransition");
             }
@@ -569,11 +578,12 @@
     public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
             Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
             @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
-            RemoteAnimationAdapter adapter) {
+            RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
-                        taskId, mainOptions, sideOptions, sidePosition, splitRatio, adapter);
+                        taskId, mainOptions, sideOptions, sidePosition, splitRatio, adapter,
+                        instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startIntentAndTaskWithLegacyTransition");
             }
@@ -583,11 +593,11 @@
     public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, int taskId,
             Bundle mainOptions, Bundle sideOptions,
             @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
-            RemoteAnimationAdapter adapter) {
+            RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (mSystemUiProxy != null) {
             try {
                 mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, taskId,
-                        mainOptions, sideOptions, sidePosition, splitRatio, adapter);
+                        mainOptions, sideOptions, sidePosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call startShortcutAndTaskWithLegacyTransition");
             }
@@ -595,11 +605,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");
             }
@@ -607,10 +617,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");
             }
@@ -656,6 +666,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
     //
 
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 5fb806d..b9a1b06 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -36,12 +36,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;
@@ -138,8 +135,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());
@@ -233,7 +228,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.
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 cc7a356..a96524e 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -668,7 +668,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 +681,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 +704,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..e207a1b 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -28,6 +28,7 @@
 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_FLOATING_TASKS;
 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;
@@ -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,7 @@
 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.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 +169,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(
@@ -182,8 +186,8 @@
                     bundle.getBinder(KEY_EXTRA_SHELL_BACK_ANIMATION));
             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);
                 TouchInteractionService.this.initInputMonitor("TISBinder#onInitialize()");
                 preloadOverview(true /* fromInit */);
             });
@@ -709,16 +713,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);
         }
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/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 45c8036..37a28e5 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -336,8 +336,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 d2d1233..53e0c2b 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -33,10 +33,10 @@
      * Enums associated to gesture navigation events.
      */
     public enum GestureEvent {
-        MOTION_DOWN, MOTION_UP, SET_END_TARGET, SET_END_TARGET_HOME, 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,
-        SCROLLER_ANIMATION_ABORTED,
+        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, SCROLLER_ANIMATION_ABORTED, TASK_APPEARED,
 
         /**
          * These GestureEvents are specifically associated to state flags that get set in
@@ -134,6 +134,15 @@
                                         + " 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),
@@ -289,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/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/NormalToSplitTimings.java b/quickstep/src/com/android/quickstep/util/NormalToSplitTimings.java
new file mode 100644
index 0000000..1bf5a5d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/NormalToSplitTimings.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.LINEAR;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the Normal > OverviewSplitSelect animation.
+ */
+public class NormalToSplitTimings extends OverviewToSplitTimings 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/OverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java
new file mode 100644
index 0000000..f44796b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+public class 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 getActionsFadeStart() { return 0; }
+    public int getActionsFadeEnd() { return 83; }
+    public int getIconFadeStart() { return 0; }
+    public int getIconFadeEnd() { 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 int getDuration() { return 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; }
+
+    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/SplitAnimationTimings.java b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
new file mode 100644
index 0000000..133be03
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
@@ -0,0 +1,87 @@
+/*
+ * 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.view.animation.Interpolator;
+
+/**
+ * An interface that supports the centralization of timing information for splitscreen animations.
+ */
+public interface SplitAnimationTimings {
+    int ENTER_DURATION = 866;
+    int ABORT_DURATION = 500;
+    int CONFIRM_DURATION = 383;
+
+    SplitAnimationTimings OVERVIEW_TO_SPLIT = new OverviewToSplitTimings();
+    SplitAnimationTimings NORMAL_TO_SPLIT = new NormalToSplitTimings();
+    SplitAnimationTimings SPLIT_TO_CONFIRM = new SplitToConfirmTimings();
+
+    // 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; }
+
+    // Defaults for NormalToSplit
+    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..efbe783 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -38,12 +38,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 +78,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 +94,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 +110,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 +152,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 +197,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,10 +206,14 @@
      * 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
@@ -200,7 +227,8 @@
             mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],
                     null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, splitRatio,
                     new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR,
-                            ActivityThread.currentActivityThread().getApplicationThread()));
+                            ActivityThread.currentActivityThread().getApplicationThread()),
+                    shellInstanceId);
             // TODO(b/237635859): handle intent/shortcut + task with shell transition
         } else {
             RemoteSplitLaunchAnimationRunner animationRunner =
@@ -218,18 +246,18 @@
             if (taskPendingIntent == null) {
                 mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(),
                         taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
-                        splitRatio, adapter);
+                        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);
+                            adapter, shellInstanceId);
                 } else {
                     mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent,
                             fillInIntent, taskId2, mainOpts.toBundle(), null /* sideOptions */,
-                            stagePosition, splitRatio, adapter);
+                            stagePosition, splitRatio, adapter, shellInstanceId);
                 }
             }
         }
@@ -239,6 +267,10 @@
         return mStagePosition;
     }
 
+    public StatsLogManager.EventEnum getSplitEvent() {
+        return mSplitEvent;
+    }
+
     public void setRecentsAnimationRunning(boolean running) {
         this.mRecentsAnimationRunning = running;
     }
@@ -358,6 +390,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..84cfb0b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
@@ -0,0 +1,48 @@
+/*
+ * 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 OverviewSplitSelect > confirmed animation.
+ */
+public class 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 getInstructionsFadeStart() { return 0; }
+    public int getInstructionsFadeEnd() { return 67; }
+
+    public int getDuration() { return CONFIRM_DURATION; }
+    public Interpolator getStagedRectXInterpolator() { return LINEAR; }
+    public Interpolator getStagedRectYInterpolator() { return LINEAR; }
+    public Interpolator getStagedRectScaleXInterpolator() { return LINEAR; }
+    public Interpolator getStagedRectScaleYInterpolator() { return LINEAR; }
+
+    public float getInstructionsFadeStartOffset() {
+        return (float) getInstructionsFadeStart() / getDuration();
+    }
+    public float getInstructionsFadeEndOffset() {
+        return (float) getInstructionsFadeEnd() / getDuration();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 0a49008..1a026fc 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep.util;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
@@ -25,7 +27,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;
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..cfcfce0 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,27 @@
 
         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.next = this.next;
+            out.elevation = this.elevation;
         }
 
         /**
@@ -417,6 +469,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/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..bf88702 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;
@@ -33,6 +31,7 @@
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.views.BaseDragLayer;
 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 +41,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.
  *
@@ -209,8 +209,37 @@
         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) {
+        SplitAnimationTimings timings = fadeWithThumbnail
+                ? SplitAnimationTimings.OVERVIEW_TO_SPLIT
+                : SplitAnimationTimings.NORMAL_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) {
+        addAnimation(animation, startingBounds, endBounds, fadeWithThumbnail, isStagedTask,
+                SplitAnimationTimings.SPLIT_TO_CONFIRM);
+    }
+
+    /**
+     * 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 +253,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 +305,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 3920c56..5bc7f18 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -167,7 +167,7 @@
     @Override
     protected boolean showTaskMenuWithContainer(IconView iconView) {
         boolean showedTaskMenu = super.showTaskMenuWithContainer(iconView);
-        if (iconView == mIconView2 && showedTaskMenu && !isGridTask()) {
+        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();
@@ -230,11 +230,12 @@
     }
 
     @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) {
@@ -336,9 +337,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/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 38e5acb..b00794f 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -35,11 +35,14 @@
 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;
@@ -132,6 +135,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;
@@ -170,6 +174,7 @@
 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;
@@ -650,7 +655,7 @@
     @Nullable
     private TaskView mSplitHiddenTaskView;
     @Nullable
-    private View mSecondSplitHiddenView;
+    private TaskView mSecondSplitHiddenView;
     @Nullable
     private SplitBounds mSplitBoundsConfig;
     private final Toast mSplitToast = Toast.makeText(getContext(),
@@ -1239,7 +1244,7 @@
     protected void onPageEndTransition() {
         super.onPageEndTransition();
         ActiveGestureLog.INSTANCE.addLog(
-                "onPageEndTransition: current page index updated", mCurrentPage);
+                "onPageEndTransition: current page index updated", getNextPage());
         if (isClearAllHidden() && !mActivity.getDeviceProfile().isTablet) {
             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
         }
@@ -2804,33 +2809,47 @@
         mOrientationHandler.getInitialSplitPlaceholderBounds(mSplitPlaceholderSize,
                 mSplitPlaceholderInset, mActivity.getDeviceProfile(),
                 mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
+        SplitAnimationTimings timings = SplitAnimationTimings.OVERVIEW_TO_SPLIT;
 
         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");
@@ -2857,17 +2876,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();
@@ -3112,11 +3130,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.
+                SplitAnimationTimings timings = SplitAnimationTimings.OVERVIEW_TO_SPLIT;
+                float animationStartProgress = isStagingFocusedTask
+                        ? Utilities.boundToRange(
+                                timings.getGridSlideStartOffset()
+                                        + (timings.getGridSlideStaggerOffset() * staggerColumn),
+                        0f,
+                        dismissTranslationInterpolationEnd)
+                        : Utilities.boundToRange(
+                                INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+                                        + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+                                        * staggerColumn, 0f, dismissTranslationInterpolationEnd);
+                float animationEndProgress = isStagingFocusedTask
+                        ? Utilities.boundToRange(
+                                timings.getGridSlideStartOffset()
+                                        + (timings.getGridSlideStaggerOffset() * staggerColumn)
+                                        + timings.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();
@@ -3139,7 +3181,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);
@@ -3155,8 +3197,8 @@
 
                     anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
                             mIsRtl ? primaryTranslation : -primaryTranslation,
-                            clampToProgress(LINEAR, animationStartProgress,
-                                    dismissTranslationInterpolationEnd));
+                            clampToProgress(dismissInterpolator, animationStartProgress,
+                                    animationEndProgress));
                 }
             }
         }
@@ -3388,7 +3430,6 @@
                 mPendingAnimation = null;
             }
         });
-        return anim;
     }
 
     /**
@@ -3556,8 +3597,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")
@@ -4066,16 +4109,21 @@
         }
     }
 
+    /**
+     * 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 */,
@@ -4086,17 +4134,21 @@
     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);
         }
     }
 
@@ -4127,9 +4179,9 @@
         // TODO(194414938) starting bounds seem slightly off, investigate
         Rect firstTaskStartingBounds = new Rect();
         Rect firstTaskEndingBounds = mTempRect;
-        int duration = mActivity.getStateManager().getState().getTransitionDuration(mActivity,
-                false /* isToState */);
-        PendingAnimation pendingAnimation = new PendingAnimation(duration);
+        PendingAnimation pendingAnimation =
+                new PendingAnimation(SplitAnimationTimings.CONFIRM_DURATION);
+        SplitAnimationTimings timings = SplitAnimationTimings.SPLIT_TO_CONFIRM;
 
         int halfDividerSize = getResources()
                 .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
@@ -4139,28 +4191,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");
 
@@ -4175,23 +4230,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;
         }
@@ -4207,11 +4259,11 @@
         onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
         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);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index bcaa462..27ec01a 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -25,6 +25,7 @@
 import android.widget.FrameLayout;
 
 import androidx.annotation.Nullable;
+import androidx.appcompat.widget.AppCompatTextView;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
@@ -40,18 +41,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 +78,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;
     }
@@ -120,4 +129,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/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index f4b3d98..d7a8599 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -16,11 +16,11 @@
 
 package com.android.quickstep.views;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 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 android.content.Context;
 import android.graphics.Bitmap;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 26bae35..5df03cc 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -32,6 +32,7 @@
 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 +49,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;
@@ -99,7 +101,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.ActivityOptionsCompat;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -323,19 +324,6 @@
                 }
             };
 
-    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;
@@ -399,7 +387,7 @@
 
     private final float[] mIconCenterCoords = new float[2];
 
-    private final PointF mLastTouchDownPosition = new PointF();
+    protected final PointF mLastTouchDownPosition = new PointF();
 
     private boolean mIsClickableAsLiveTile = true;
 
@@ -596,16 +584,16 @@
      *         second app. {@code false} otherwise
      */
     private boolean confirmSecondSplitSelectApp() {
-        int index = getChildTaskIndexAtPosition(mLastTouchDownPosition);
+        int index = getLastSelectedChildTaskIndex();
         TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
         return getRecentsView().confirmSplitSelect(this, container.getTask(),
                 container.getIconView(), container.getThumbnailView());
     }
 
     /**
-     * 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;
     }
 
@@ -659,12 +647,12 @@
                     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()
@@ -687,6 +675,20 @@
     }
 
     /**
+     * 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 RunnableList launchTasks() {
@@ -1495,7 +1497,8 @@
     }
 
     public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
-        getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition);
+        getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition,
+                getLogEventForPosition(splitPositionOption.stagePosition));
     }
 
     /**
@@ -1513,8 +1516,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);
+            }
+        }
     }
 
     /**
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/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/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/config.xml b/res/values/config.xml
index d3f5033..11b6e8c 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -204,4 +204,7 @@
 
     <!-- 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..5dae9f2 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_current_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/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/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index 747b755..2f927d3 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -24,7 +24,6 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -92,8 +91,7 @@
     protected int getAvailableScrollHeight() {
         // AvailableScrollHeight = Total height of the all items - first page height
         int firstPageHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
-        int totalHeightOfAllItems = getItemsHeight(/* untilIndex= */ getAdapter().getItemCount());
-        int availableScrollHeight = totalHeightOfAllItems - firstPageHeight;
+        int availableScrollHeight = computeVerticalScrollRange() - firstPageHeight;
         return Math.max(0, availableScrollHeight);
     }
 
@@ -146,10 +144,7 @@
 
         // IF scroller is at the very top OR there is no scroll bar because there is probably not
         // enough items to scroll, THEN it's okay for the container to be pulled down.
-        if (getCurrentScrollY() == 0) {
-            return true;
-        }
-        return getAdapter() == null || getAdapter().getItemCount() == 0;
+        return computeVerticalScrollOffset() == 0;
     }
 
     /**
@@ -160,53 +155,6 @@
     }
 
     /**
-     * @return the scroll top of this recycler view.
-     */
-    public int getCurrentScrollY() {
-        Adapter adapter = getAdapter();
-        if (adapter == null) {
-            return -1;
-        }
-        if (adapter.getItemCount() == 0 || getChildCount() == 0) {
-            return -1;
-        }
-
-        int itemPosition = NO_POSITION;
-        View child = null;
-
-        LayoutManager layoutManager = getLayoutManager();
-        if (layoutManager instanceof LinearLayoutManager) {
-            // Use the LayoutManager as the source of truth for visible positions. During
-            // animations, the view group child may not correspond to the visible views that appear
-            // at the top.
-            itemPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
-            child = layoutManager.findViewByPosition(itemPosition);
-        }
-
-        if (child == null) {
-            // If the layout manager returns null for any reason, which can happen before layout
-            // has occurred for the position, then look at the child of this view as a ViewGroup.
-            child = getChildAt(0);
-            itemPosition = getChildAdapterPosition(child);
-        }
-        if (itemPosition == NO_POSITION) {
-            return -1;
-        }
-        return getPaddingTop() + getItemsHeight(itemPosition)
-                - layoutManager.getDecoratedTop(child);
-    }
-
-    /**
-     * Returns the sum of the height, in pixels, of this list adapter's items from index
-     * 0 (inclusive) until {@code untilIndex} (exclusive). If untilIndex is same as the itemCount,
-     * it returns the full height of all the items.
-     *
-     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
-     * sum of all items' height.
-     */
-    protected abstract int getItemsHeight(int untilIndex);
-
-    /**
      * Maps the touch (from 0..1) to the adapter position that should be visible.
      * <p>Override in each subclass of this base class.
      */
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ce95b15..5e2187e 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2780,7 +2780,7 @@
             View v = getFirstMatch(Collections.singletonList(activeRecyclerView),
                     preferredItem, packageAndUserAndApp);
 
-            if (v != null && activeRecyclerView.getCurrentScrollY() > 0) {
+            if (v != null && activeRecyclerView.computeVerticalScrollOffset() > 0) {
                 RectF locationBounds = new RectF();
                 FloatingIconView.getLocationBoundsForView(this, v, false, locationBounds,
                         new Rect());
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/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..e0b4951 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -26,7 +26,9 @@
 import androidx.core.view.accessibility.AccessibilityRecordCompat;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
 
+import com.android.launcher3.util.ScrollableLayoutManager;
 import com.android.launcher3.views.ActivityContext;
 
 import java.util.List;
@@ -62,10 +64,10 @@
     /**
      * A subclass of GridLayoutManager that overrides accessibility values during app search.
      */
-    public class AppsGridLayoutManager extends GridLayoutManager {
+    public class AppsGridLayoutManager extends ScrollableLayoutManager {
 
         public AppsGridLayoutManager(Context context) {
-            super(context, 1, GridLayoutManager.VERTICAL, false);
+            super(context);
         }
 
         @Override
@@ -125,6 +127,15 @@
             }
             return extraRows;
         }
+
+        @Override
+        protected int incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos) {
+            AllAppsGridAdapter.AdapterItem item = mApps.getAdapterItems().get(position);
+            // only account for the first icon in the row since they are the same size within a row
+            return (isIconViewType(item.viewType) && item.rowAppIndex != 0)
+                    ? heightUntilLastPos
+                    : (heightUntilLastPos + mCachedSizes.get(item.viewType));
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 21a7dfb..3d06fb5 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.allapps;
 
-import static android.view.View.MeasureSpec.UNSPECIFIED;
-
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SCROLLED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END;
@@ -26,7 +24,6 @@
 import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.util.SparseIntArray;
 
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -49,40 +46,10 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_LATENCY = Utilities.isPropertyEnabled(SEARCH_LOGGING);
 
-    protected AlphabeticalAppsList<?> mApps;
     protected final int mNumAppsPerRow;
-
-    // The specific view heights that we use to calculate scroll
-    private final SparseIntArray mViewHeights = new SparseIntArray();
-    private final SparseIntArray mCachedScrollPositions = new SparseIntArray();
     private final AllAppsFastScrollHelper mFastScrollHelper;
 
-
-    private final AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() {
-        public void onChanged() {
-            mCachedScrollPositions.clear();
-        }
-
-        @Override
-        public void onItemRangeChanged(int positionStart, int itemCount) {
-            onChanged();
-        }
-
-        @Override
-        public void onItemRangeInserted(int positionStart, int itemCount) {
-            onChanged();
-        }
-
-        @Override
-        public void onItemRangeRemoved(int positionStart, int itemCount) {
-            onChanged();
-        }
-
-        @Override
-        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
-            onChanged();
-        }
-    };
+    protected AlphabeticalAppsList<?> mApps;
 
     public AllAppsRecyclerView(Context context) {
         this(context, null);
@@ -122,12 +89,8 @@
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows
                 * (mNumAppsPerRow + 1));
-
-        mViewHeights.clear();
-        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, grid.allAppsCellHeightPx);
     }
 
-
     @Override
     public void onDraw(Canvas c) {
         if (DEBUG) {
@@ -200,17 +163,6 @@
     }
 
     @Override
-    public void setAdapter(Adapter adapter) {
-        if (getAdapter() != null) {
-            getAdapter().unregisterAdapterDataObserver(mObserver);
-        }
-        super.setAdapter(adapter);
-        if (adapter != null) {
-            adapter.registerAdapterDataObserver(mObserver);
-        }
-    }
-
-    @Override
     protected boolean isPaddingOffsetRequired() {
         return true;
     }
@@ -231,13 +183,13 @@
         List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
 
         // Skip early if there are no items or we haven't been measured
-        if (items.isEmpty() || mNumAppsPerRow == 0) {
+        if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
             mScrollbar.setThumbOffsetY(-1);
             return;
         }
 
         // Skip early if, there no child laid out in the container.
-        int scrollY = getCurrentScrollY();
+        int scrollY = computeVerticalScrollOffset();
         if (scrollY < 0) {
             mScrollbar.setThumbOffsetY(-1);
             return;
@@ -292,51 +244,6 @@
         }
     }
 
-    @Override
-    protected int getItemsHeight(int position) {
-        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
-        AllAppsGridAdapter.AdapterItem posItem = position < items.size()
-                ? items.get(position) : null;
-        int y = mCachedScrollPositions.get(position, -1);
-        if (y < 0) {
-            y = 0;
-            for (int i = 0; i < position; i++) {
-                AllAppsGridAdapter.AdapterItem item = items.get(i);
-                if (AllAppsGridAdapter.isIconViewType(item.viewType)) {
-                    // Break once we reach the desired row
-                    if (posItem != null && posItem.viewType == item.viewType &&
-                            posItem.rowIndex == item.rowIndex) {
-                        break;
-                    }
-                    // Otherwise, only account for the first icon in the row since they are the same
-                    // size within a row
-                    if (item.rowAppIndex == 0) {
-                        y += mViewHeights.get(item.viewType, 0);
-                    }
-                } else {
-                    // Rest of the views span the full width
-                    int elHeight = mViewHeights.get(item.viewType);
-                    if (elHeight == 0) {
-                        ViewHolder holder = findViewHolderForAdapterPosition(i);
-                        if (holder == null) {
-                            holder = getAdapter().createViewHolder(this, item.viewType);
-                            getAdapter().onBindViewHolder(holder, i);
-                            holder.itemView.measure(UNSPECIFIED, UNSPECIFIED);
-                            elHeight = holder.itemView.getMeasuredHeight();
-
-                            getRecycledViewPool().putRecycledView(holder);
-                        } else {
-                            elHeight = holder.itemView.getMeasuredHeight();
-                        }
-                    }
-                    y += elHeight;
-                }
-            }
-            mCachedScrollPositions.put(position, y);
-        }
-        return y;
-    }
-
     public int getScrollBarTop() {
         return getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding);
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 22e8bcf..391de8d 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -228,6 +228,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 && !FeatureFlags.ENABLE_KEYBOARD_TRANSITION_SYNC.get()) {
+                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 +243,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();
                 }
             });
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index 879a159..3983f40 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -103,7 +103,8 @@
             new RecyclerView.OnScrollListener() {
                 @Override
                 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
-                    updateHeaderScroll(((AllAppsRecyclerView) recyclerView).getCurrentScrollY());
+                    updateHeaderScroll(
+                            ((AllAppsRecyclerView) recyclerView).computeVerticalScrollOffset());
                 }
             };
 
@@ -491,6 +492,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,6 +525,7 @@
 
         mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView);
         mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
+        mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
     }
 
     protected void updateSearchResultsVisibility() {
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 8ec2aeb..1ef5498 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -68,7 +68,7 @@
                         mAnimator.cancel();
                     }
 
-                    int current = -mCurrentRV.getCurrentScrollY();
+                    int current = -mCurrentRV.computeVerticalScrollOffset();
                     boolean headerCollapsed = mHeaderCollapsed;
                     moved(current);
                     applyVerticalMove();
@@ -272,7 +272,8 @@
         }
         mMaxTranslation += mFloatingRowsHeight;
         if (!mTabsHidden) {
-            mMaxTranslation += mTabsAdditionalPaddingBottom;
+            mMaxTranslation += mTabsAdditionalPaddingBottom
+                    + getResources().getDimensionPixelSize(R.dimen.all_apps_tabs_margin_top);
         }
     }
 
diff --git a/src/com/android/launcher3/allapps/SearchTransitionController.java b/src/com/android/launcher3/allapps/SearchTransitionController.java
index 03529af..da8902d 100644
--- a/src/com/android/launcher3/allapps/SearchTransitionController.java
+++ b/src/com/android/launcher3/allapps/SearchTransitionController.java
@@ -35,6 +35,7 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
 
 /** Coordinates the transition between Search and A-Z in All Apps. */
 public class SearchTransitionController {
@@ -144,8 +145,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();
@@ -233,6 +237,7 @@
     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/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/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 4b647cd..406496b 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -274,10 +274,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");
 
@@ -293,10 +289,13 @@
             "Enable option to log the keyboard latency for both atomic and controlled keyboard "
                     + "animations on an EditText");
 
-    // Change of wallpaper depth in widget picker is disabled for tests as it causes flakiness on
-    // very slow cuttlefish devices.
     public static final BooleanFlag ENABLE_WIDGET_PICKER_DEPTH = new DeviceFlag(
-            "ENABLE_WIDGET_PICKER_DEPTH", true, "Enable changing depth in widget picker.");
+            "ENABLE_WIDGET_PICKER_DEPTH", false, "Enable changing depth in widget picker.");
+
+    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 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/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/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 651372f..32237e2 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
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/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..1798536 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;
@@ -37,6 +39,7 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.util.Themes;
 
 /**
@@ -55,6 +58,7 @@
 
     private static final int DOT_ACTIVE_ALPHA = 255;
     private static final int DOT_INACTIVE_ALPHA = 128;
+    private static final int DOT_GAP_FACTOR = 3;
 
     // This value approximately overshoots to 1.5 times the original size.
     private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f;
@@ -76,21 +80,25 @@
         }
     };
 
-    private final Paint mCirclePaint;
+    private final Paint mPaginationPaint;
     private final float mDotRadius;
+    private final float mCircleGap;
+    private final float mPageIndicatorSize;
     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 +117,51 @@
     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());
-
+        mCircleGap = DOT_GAP_FACTOR * mDotRadius;
+        mPageIndicatorSize = getResources().getDimension(
+                R.dimen.page_indicator_current_page_indicator_size);
+        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) {
+            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 +199,7 @@
     }
 
     public void playEntryAnimation() {
-        int count  = mEntryAnimationRadiusFactors.length;
+        int count = mEntryAnimationRadiusFactors.length;
         if (count == 0) {
             mEntryAnimationRadiusFactors = null;
             invalidate();
@@ -231,16 +253,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 +274,88 @@
                 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 ? DOT_ACTIVE_ALPHA : DOT_INACTIVE_ALPHA);
+                if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+                    canvas.drawCircle(x, y, getRadius(x) * mEntryAnimationRadiusFactors[i],
+                            mPaginationPaint);
+                } else {
+                    canvas.drawCircle(x, y, mDotRadius * mEntryAnimationRadiusFactors[i],
+                            mPaginationPaint);
+                }
                 x += circleGap;
             }
         } else {
-            mCirclePaint.setAlpha(DOT_INACTIVE_ALPHA);
+            mPaginationPaint.setAlpha(DOT_INACTIVE_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);
+            mPaginationPaint.setAlpha(DOT_ACTIVE_ALPHA);
+
+            if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+                canvas.drawRect(getActiveRect(), mPaginationPaint);
+            } else {
+                canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
+            }
         }
     }
 
+    /**
+     * 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()
+                + (mPageIndicatorSize / 2);
+
+        // 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 +363,26 @@
             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 (mPageIndicatorSize / 2) - mDotRadius;
+    }
+
+    /**
+     * The current scroll adjusted for the distance the indicator needs to travel on the screen
+     */
+    private float getIndicatorScrollDistance() {
+        float scrollPerPage = mNumPages > 1 ? mTotalScroll / (mNumPages - 1) : 0;
+        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 c408904..b94ea07 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -24,12 +24,16 @@
 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;
@@ -58,6 +62,7 @@
     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;
@@ -209,8 +214,12 @@
         }
     }
 
+    @WorkerThread
     private boolean setOrientationAsync(Message msg) {
-        mActivity.setRequestedOrientation(msg.what);
+        Activity activity = mActivity;
+        if (activity != null) {
+            activity.setRequestedOrientation(msg.what);
+        }
         return true;
     }
 
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index d3c9bc9..269baf0 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -112,12 +112,12 @@
 
             case TestProtocol.REQUEST_APPS_LIST_SCROLL_Y: {
                 return getLauncherUIProperty(Bundle::putInt,
-                        l -> l.getAppsView().getActiveRecyclerView().getCurrentScrollY());
+                        l -> l.getAppsView().getActiveRecyclerView().computeVerticalScrollOffset());
             }
 
             case TestProtocol.REQUEST_WIDGETS_SCROLL_Y: {
                 return getLauncherUIProperty(Bundle::putInt,
-                        l -> WidgetsFullSheet.getWidgetsView(l).getCurrentScrollY());
+                        l -> WidgetsFullSheet.getWidgetsView(l).computeVerticalScrollOffset());
             }
 
             case TestProtocol.REQUEST_TARGET_INSETS: {
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/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 89c300d..9afca4f 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -453,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);
@@ -461,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
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 450205d..bc1b634 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -520,7 +520,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;
         if ((DisplayController.getNavigationMode(out.getContext()) == THREE_BUTTONS)
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 387e980..55bb5e8 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -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
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/ScrollableLayoutManager.java b/src/com/android/launcher3/util/ScrollableLayoutManager.java
new file mode 100644
index 0000000..17eaefd
--- /dev/null
+++ b/src/com/android/launcher3/util/ScrollableLayoutManager.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.util;
+
+import android.content.Context;
+import android.util.SparseIntArray;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+import androidx.recyclerview.widget.RecyclerView.State;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+/**
+ * Extension of {@link GridLayoutManager} with support for smooth scrolling
+ */
+public class ScrollableLayoutManager extends GridLayoutManager {
+
+    // keyed on item type
+    protected final SparseIntArray mCachedSizes = new SparseIntArray();
+
+    private RecyclerView mRv;
+
+    /**
+     * Precalculated total height keyed on the item position. This is always incremental.
+     * Subclass can override {@link #incrementTotalHeight} to incorporate the layout logic.
+     * For example all-apps should have same values for items in same row,
+     *     sample values: 0, 10, 10, 10, 10, 20, 20, 20, 20
+     * whereas widgets will have strictly increasing values
+     *     sample values: 0, 10, 50, 60, 110
+     */
+
+    //
+    private int[] mTotalHeightCache = new int[1];
+    private int mLastValidHeightIndex = 0;
+
+    public ScrollableLayoutManager(Context context) {
+        super(context, 1, GridLayoutManager.VERTICAL, false);
+    }
+
+    @Override
+    public void onAttachedToWindow(RecyclerView view) {
+        super.onAttachedToWindow(view);
+        mRv = view;
+    }
+
+    @Override
+    public void layoutDecorated(@NonNull View child, int left, int top, int right, int bottom) {
+        super.layoutDecorated(child, left, top, right, bottom);
+        mCachedSizes.put(
+                mRv.getChildViewHolder(child).getItemViewType(), child.getMeasuredHeight());
+    }
+
+    @Override
+    public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right,
+            int bottom) {
+        super.layoutDecoratedWithMargins(child, left, top, right, bottom);
+        mCachedSizes.put(
+                mRv.getChildViewHolder(child).getItemViewType(), child.getMeasuredHeight());
+    }
+
+    @Override
+    public int computeVerticalScrollExtent(State state) {
+        return mRv == null ? 0 : mRv.getHeight();
+    }
+
+    @Override
+    public int computeVerticalScrollOffset(State state) {
+        Adapter adapter = mRv == null ? null : mRv.getAdapter();
+        if (adapter == null) {
+            return 0;
+        }
+        if (adapter.getItemCount() == 0 || getChildCount() == 0) {
+            return 0;
+        }
+        View child = getChildAt(0);
+        ViewHolder holder = mRv.findContainingViewHolder(child);
+        if (holder == null) {
+            return 0;
+        }
+        int itemPosition = holder.getLayoutPosition();
+        if (itemPosition < 0) {
+            return 0;
+        }
+        return getPaddingTop() + getItemsHeight(adapter, itemPosition) - getDecoratedTop(child);
+    }
+
+    @Override
+    public int computeVerticalScrollRange(State state) {
+        Adapter adapter = mRv == null ? null : mRv.getAdapter();
+        return adapter == null ? 0 : getItemsHeight(adapter, adapter.getItemCount());
+    }
+
+    /**
+     * Returns the sum of the height, in pixels, of this list adapter's items from index
+     * 0 (inclusive) until {@code untilIndex} (exclusive). If untilIndex is same as the itemCount,
+     * it returns the full height of all the items.
+     *
+     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
+     * sum of all items' height.
+     */
+    private int getItemsHeight(Adapter adapter, int untilIndex) {
+        final int totalItems = adapter.getItemCount();
+        if (mTotalHeightCache.length < (totalItems + 1)) {
+            mTotalHeightCache = new int[totalItems + 1];
+            mLastValidHeightIndex = 0;
+        }
+        if (untilIndex > totalItems) {
+            untilIndex = totalItems;
+        } else if (untilIndex < 0) {
+            untilIndex = 0;
+        }
+        if (untilIndex <= mLastValidHeightIndex) {
+            return mTotalHeightCache[untilIndex];
+        }
+
+        int totalItemsHeight = mTotalHeightCache[mLastValidHeightIndex];
+        for (int i = mLastValidHeightIndex; i < untilIndex; i++) {
+            totalItemsHeight = incrementTotalHeight(adapter, i, totalItemsHeight);
+            mTotalHeightCache[i + 1] = totalItemsHeight;
+        }
+        mLastValidHeightIndex = untilIndex;
+        return totalItemsHeight;
+    }
+
+    /**
+     * The current implementation assumes a linear list with every item taking up the whole row.
+     * Subclasses should override this method to account for any spanning logic
+     */
+    protected int incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos) {
+        return heightUntilLastPos + mCachedSizes.get(adapter.getItemViewType(position));
+    }
+
+    private void invalidateScrollCache() {
+        mLastValidHeightIndex = 0;
+    }
+
+    @Override
+    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
+        super.onItemsAdded(recyclerView, positionStart, itemCount);
+        invalidateScrollCache();
+    }
+
+    @Override
+    public void onItemsChanged(RecyclerView recyclerView) {
+        super.onItemsChanged(recyclerView);
+        invalidateScrollCache();
+    }
+
+    @Override
+    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
+        super.onItemsRemoved(recyclerView, positionStart, itemCount);
+        invalidateScrollCache();
+    }
+
+    @Override
+    public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
+        super.onItemsMoved(recyclerView, from, to, itemCount);
+        invalidateScrollCache();
+    }
+
+    @Override
+    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
+            Object payload) {
+        super.onItemsUpdated(recyclerView, positionStart, itemCount, payload);
+        invalidateScrollCache();
+    }
+}
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/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/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/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 40e4ce1..3af2e3c 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -119,7 +119,6 @@
     // prevent jumping, this offset is applied as the user scrolls.
     protected int mTouchOffsetY;
     protected int mThumbOffsetY;
-    protected int mRvOffsetY;
 
     // Fast scroller popup
     private TextView mPopupView;
@@ -207,16 +206,11 @@
 
     public void setThumbOffsetY(int y) {
         if (mThumbOffsetY == y) {
-            int rvCurrentOffsetY = mRv.getCurrentScrollY();
-            if (mRvOffsetY != rvCurrentOffsetY) {
-                mRvOffsetY = mRv.getCurrentScrollY();
-            }
             return;
         }
         updatePopupY(y);
         mThumbOffsetY = y;
         invalidate();
-        mRvOffsetY = mRv.getCurrentScrollY();
     }
 
     public int getThumbOffsetY() {
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/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 35fa7a4..5969e3e 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.util.AttributeSet;
-import android.util.SparseIntArray;
 import android.view.MotionEvent;
 
 import androidx.recyclerview.widget.LinearLayoutManager;
@@ -28,6 +27,7 @@
 
 import com.android.launcher3.FastScrollRecyclerView;
 import com.android.launcher3.R;
+import com.android.launcher3.util.ScrollableLayoutManager;
 
 /**
  * The widgets recycler view.
@@ -42,14 +42,6 @@
     private boolean mTouchDownOnScroller;
     private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
 
-    /**
-     * There is always 1 or 0 item of VIEW_TYPE_WIDGETS_LIST. Other types have fixes sizes, so the
-     * the size can be used for all other items of same type. Caching the last know size for
-     * VIEW_TYPE_WIDGETS_LIST allows us to use it to estimate full size even when
-     * VIEW_TYPE_WIDGETS_LIST is not visible on the screen.
-     */
-    private final SparseIntArray mCachedSizes = new SparseIntArray();
-
     public WidgetsRecyclerView(Context context) {
         this(context, null);
     }
@@ -68,9 +60,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        // create a layout manager with Launcher's context so that scroll position
-        // can be preserved during screen rotation.
-        setLayoutManager(new LinearLayoutManager(getContext()));
+        setLayoutManager(new ScrollableLayoutManager(getContext()));
     }
 
     @Override
@@ -114,7 +104,7 @@
         }
 
         // Skip early if, there no child laid out in the container.
-        int scrollY = getCurrentScrollY();
+        int scrollY = computeVerticalScrollOffset();
         if (scrollY < 0) {
             mScrollbar.setThumbOffsetY(-1);
             return;
@@ -164,39 +154,6 @@
     }
 
     /**
-     * Returns the sum of the height, in pixels, of this list adapter's items from index 0 until
-     * {@code untilIndex}.
-     *
-     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
-     * sum of all items' height.
-     */
-    @Override
-    protected int getItemsHeight(int untilIndex) {
-        // Initialize cache
-        int childCount = getChildCount();
-        int startPosition;
-        if (childCount > 0
-                && ((startPosition = getChildAdapterPosition(getChildAt(0))) != NO_POSITION)) {
-            int loopCount = Math.min(getChildCount(), getAdapter().getItemCount() - startPosition);
-            for (int i = 0; i < loopCount; i++) {
-                mCachedSizes.put(
-                        mAdapter.getItemViewType(startPosition + i),
-                        getChildAt(i).getMeasuredHeight());
-            }
-        }
-
-        if (untilIndex > mAdapter.getItems().size()) {
-            untilIndex = mAdapter.getItems().size();
-        }
-        int totalItemsHeight = 0;
-        for (int i = 0; i < untilIndex; i++) {
-            int type = mAdapter.getItemViewType(i);
-            totalItemsHeight += mCachedSizes.get(type);
-        }
-        return totalItemsHeight;
-    }
-
-    /**
      * Provides dimensions of the header view that is shown at the top of a
      * {@link WidgetsRecyclerView}.
      */
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index a66b09a..2d519d9 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) {
@@ -525,7 +527,7 @@
     }
 
     protected int getAllAppsScroll(Launcher launcher) {
-        return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
+        return launcher.getAppsView().getActiveRecyclerView().computeVerticalScrollOffset();
     }
 
     private void checkLauncherIntegrity(
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index be49974..07bfe4c 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -303,7 +303,7 @@
     }
 
     private int getWidgetsScroll(Launcher launcher) {
-        return getWidgetsView(launcher).getCurrentScrollY();
+        return getWidgetsView(launcher).computeVerticalScrollOffset();
     }
 
     private boolean isOptionsPopupVisible(Launcher launcher) {
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/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 1fb8cc7..3986df6 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;
@@ -237,7 +239,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 +265,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 +286,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;
     }
@@ -891,7 +907,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 +1007,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 +1915,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(
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 5fab7eb..2c9fdb3 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -475,7 +475,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,