Merge "Avoid syncing voice interaction background window for skipAnim=true." into tm-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index a90af04..1c345a6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -116,6 +116,11 @@
     private static final int FLAG_SCREEN_PINNING_ACTIVE = 1 << 11;
     private static final int FLAG_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 12;
     private static final int FLAG_SMALL_SCREEN = 1 << 13;
+    private static final int FLAG_SLIDE_IN_VIEW_VISIBLE = 1 << 14;
+
+    /** Flags where a UI could be over a slide in view, so the color override should be disabled. */
+    private static final int FLAGS_SLIDE_IN_VIEW_ICON_COLOR_OVERRIDE_DISABLED =
+            FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_VOICE_INTERACTION_WINDOW_SHOWING;
 
     private static final String NAV_BUTTONS_SEPARATE_WINDOW_TITLE = "Taskbar Nav Buttons";
 
@@ -135,6 +140,8 @@
     private final ViewGroup mStartContextualContainer;
     private final int mLightIconColor;
     private final int mDarkIconColor;
+    /** Color to use for navigation bar buttons, if a slide in view is visible. */
+    private final int mSlideInViewIconColor;
 
     private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
             this::updateNavButtonTranslationY);
@@ -149,6 +156,9 @@
             this::updateNavButtonDarkIntensity);
     private final AnimatedFloat mNavButtonDarkIntensityMultiplier = new AnimatedFloat(
             this::updateNavButtonDarkIntensity);
+    /** Overrides the navigation button color to {@code mSlideInViewIconColor} when {@code 1}. */
+    private final AnimatedFloat mSlideInViewNavButtonColorOverride = new AnimatedFloat(
+            this::updateNavButtonDarkIntensity);
     private final RotationButtonListener mRotationButtonListener = new RotationButtonListener();
 
     private final Rect mFloatingRotationButtonBounds = new Rect();
@@ -180,6 +190,8 @@
 
         mLightIconColor = context.getColor(R.color.taskbar_nav_icon_light_color);
         mDarkIconColor = context.getColor(R.color.taskbar_nav_icon_dark_color);
+        // Can precompute color since dark theme change recreates taskbar.
+        mSlideInViewIconColor = Utilities.isDarkTheme(context) ? mLightIconColor : mDarkIconColor;
     }
 
     /**
@@ -243,6 +255,11 @@
                 flags -> (flags & FLAG_IME_VISIBLE) != 0 && !isInKidsMode, AnimatedFloat.VALUE,
                 transForIme, defaultButtonTransY));
 
+        mPropertyHolders.add(new StatePropertyHolder(
+                mSlideInViewNavButtonColorOverride,
+                flags -> ((flags & FLAG_SLIDE_IN_VIEW_VISIBLE) != 0)
+                        && ((flags & FLAGS_SLIDE_IN_VIEW_ICON_COLOR_OVERRIDE_DISABLED) == 0)));
+
         if (alwaysShowButtons) {
             initButtons(mNavButtonContainer, mEndContextualContainer,
                     mControllers.navButtonController);
@@ -524,6 +541,12 @@
         applyState();
     }
 
+    /** {@code true} if a slide in view is currently visible over taskbar. */
+    public void setSlideInViewVisible(boolean isSlideInViewVisible) {
+        updateStateForFlag(FLAG_SLIDE_IN_VIEW_VISIBLE, isSlideInViewVisible);
+        applyState();
+    }
+
     /**
      * Returns true if IME bar is visible
      */
@@ -669,8 +692,11 @@
     private void updateNavButtonDarkIntensity() {
         float darkIntensity = mTaskbarNavButtonDarkIntensity.value
                 * mNavButtonDarkIntensityMultiplier.value;
-        int iconColor = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, mLightIconColor,
-                mDarkIconColor);
+        ArgbEvaluator argbEvaluator = ArgbEvaluator.getInstance();
+        int iconColor = (int) argbEvaluator.evaluate(
+                darkIntensity, mLightIconColor, mDarkIconColor);
+        iconColor = (int) argbEvaluator.evaluate(
+                mSlideInViewNavButtonColorOverride.value, iconColor, mSlideInViewIconColor);
         for (ImageView button : mAllButtons) {
             button.setImageTintList(ColorStateList.valueOf(iconColor));
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
index 32a3c10..454a2a4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
@@ -90,6 +90,9 @@
             mTaskbarEduView = (TaskbarEduView) mActivity.getLayoutInflater().inflate(
                     R.layout.taskbar_edu, mActivity.getDragLayer(), false);
             mTaskbarEduView.init(new TaskbarEduCallbacks());
+            mControllers.navbarButtonsViewController.setSlideInViewVisible(true);
+            mTaskbarEduView.setOnCloseBeginListener(
+                    () -> mControllers.navbarButtonsViewController.setSlideInViewVisible(false));
             mTaskbarEduView.addOnCloseListener(() -> mTaskbarEduView = null);
             mTaskbarEduView.show();
             startAnim(createWaveAnim());
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
index a1385c4..0372f67 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.taskbar.BaseTaskbarContext;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarControllers;
 import com.android.launcher3.taskbar.TaskbarDragController;
 import com.android.launcher3.taskbar.TaskbarStashController;
 import com.android.launcher3.testing.TestLogging;
@@ -72,7 +73,7 @@
     TaskbarAllAppsContext(
             TaskbarActivityContext taskbarContext,
             TaskbarAllAppsController windowController,
-            TaskbarStashController taskbarStashController) {
+            TaskbarControllers taskbarControllers) {
         super(taskbarContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null));
         mTaskbarContext = taskbarContext;
         mWindowController = windowController;
@@ -86,9 +87,10 @@
                 this,
                 slideInView,
                 windowController,
-                taskbarStashController);
+                taskbarControllers);
         mAppsView = slideInView.getAppsView();
 
+        TaskbarStashController taskbarStashController = taskbarControllers.taskbarStashController;
         mWillTaskbarBeVisuallyStashed = taskbarStashController.supportsVisualStashing();
         mStashedTaskbarHeight = taskbarStashController.getStashedHeight();
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index 6c43e50..1671a0f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -144,9 +144,7 @@
         // to catch invalid states.
         mControllers.getSharedState().allAppsVisible = true;
 
-        mAllAppsContext = new TaskbarAllAppsContext(mTaskbarContext,
-                this,
-                mControllers.taskbarStashController);
+        mAllAppsContext = new TaskbarAllAppsContext(mTaskbarContext, this, mControllers);
         mAllAppsContext.getDragController().init(mControllers);
         TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
         Optional.ofNullable(mAllAppsContext.getSystemService(WindowManager.class))
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 0e62da3..9d48c8d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -30,13 +30,10 @@
 import com.android.launcher3.R;
 import com.android.launcher3.views.AbstractSlideInView;
 
-import java.util.Optional;
-
 /** Wrapper for taskbar all apps with slide-in behavior. */
 public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarAllAppsContext>
         implements Insettable, DeviceProfile.OnDeviceProfileChangeListener {
     private TaskbarAllAppsContainerView mAppsView;
-    private OnCloseListener mOnCloseBeginListener;
     private float mShiftRange;
 
     public TaskbarAllAppsSlideInView(Context context, AttributeSet attrs) {
@@ -72,14 +69,8 @@
         return mAppsView;
     }
 
-    /** Callback invoked when the view is beginning to close (e.g. close animation is started). */
-    void setOnCloseBeginListener(OnCloseListener onCloseBeginListener) {
-        mOnCloseBeginListener = onCloseBeginListener;
-    }
-
     @Override
     protected void handleClose(boolean animate) {
-        Optional.ofNullable(mOnCloseBeginListener).ifPresent(OnCloseListener::onSlideInViewClosed);
         handleClose(animate,
                 ALL_APPS.getTransitionDuration(mActivityContext, false /* isToState */));
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
index a39e872..b0d3528 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -22,6 +22,8 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.appprediction.AppsDividerView;
 import com.android.launcher3.appprediction.PredictionRowView;
+import com.android.launcher3.taskbar.NavbarButtonsViewController;
+import com.android.launcher3.taskbar.TaskbarControllers;
 import com.android.launcher3.taskbar.TaskbarStashController;
 
 /**
@@ -34,17 +36,19 @@
     private final TaskbarAllAppsSlideInView mSlideInView;
     private final TaskbarAllAppsContainerView mAppsView;
     private final TaskbarStashController mTaskbarStashController;
+    private final NavbarButtonsViewController mNavbarButtonsViewController;
 
     TaskbarAllAppsViewController(
             TaskbarAllAppsContext context,
             TaskbarAllAppsSlideInView slideInView,
             TaskbarAllAppsController windowController,
-            TaskbarStashController taskbarStashController) {
+            TaskbarControllers taskbarControllers) {
 
         mContext = context;
         mSlideInView = slideInView;
         mAppsView = mSlideInView.getAppsView();
-        mTaskbarStashController = taskbarStashController;
+        mTaskbarStashController = taskbarControllers.taskbarStashController;
+        mNavbarButtonsViewController = taskbarControllers.navbarButtonsViewController;
 
         setUpIconLongClick();
         setUpAppDivider();
@@ -83,7 +87,9 @@
         mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_APP_ALL_APPS, true);
         mTaskbarStashController.applyState(
                 ALL_APPS.getTransitionDuration(mContext, true /* isToState */));
+        mNavbarButtonsViewController.setSlideInViewVisible(true);
         mSlideInView.setOnCloseBeginListener(() -> {
+            mNavbarButtonsViewController.setSlideInViewVisible(false);
             AbstractFloatingView.closeOpenContainer(
                     mContext, AbstractFloatingView.TYPE_ACTION_POPUP);
             // Post in case view is closing due to gesture navigation. If a gesture is in progress,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 0723f8a..cbb9ad1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -42,6 +42,8 @@
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.util.AnimUtils;
+import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.views.ClearAllButton;
 import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
@@ -123,12 +125,18 @@
                         TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
                         mLauncher.getDeviceProfile());
 
+        SplitAnimationTimings timings =
+                AnimUtils.getDeviceOverviewToSplitTimings(mLauncher.getDeviceProfile().isTablet);
+
         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);
+                toState.getSplitSelectTranslation(mLauncher),
+                timings.getGridSlidePrimaryInterpolator());
+        builder.setFloat(mRecentsView, taskViewsFloat.second,
+                0,
+                timings.getGridSlideSecondaryInterpolator());
 
         if (!animate) {
             AnimatorSet as = builder.buildAnim();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index f75a404..bcd722f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -191,12 +191,15 @@
         } 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;
+            SplitAnimationTimings timings = mActivity.getDeviceProfile().isTablet
+                    ? SplitAnimationTimings.TABLET_OVERVIEW_TO_SPLIT
+                    : SplitAnimationTimings.PHONE_OVERVIEW_TO_SPLIT;
             config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR,
                     timings.getActionsFadeStartOffset(),
                     timings.getActionsFadeEndOffset()));
         } else if (fromState == NORMAL && toState == OVERVIEW_SPLIT_SELECT) {
-            SplitAnimationTimings timings = SplitAnimationTimings.NORMAL_TO_SPLIT;
+            // Splitting from Home is currently only available on tablets
+            SplitAnimationTimings timings = SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
             config.setInterpolator(ANIM_SCRIM_FADE, clampToProgress(LINEAR,
                     timings.getScrimFadeInStartOffset(),
                     timings.getScrimFadeInEndOffset()));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index 430053d..8babd34 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -44,8 +44,13 @@
 
     @Override
     public int getTransitionDuration(Context context, boolean isToState) {
-        return isToState
-                ? SplitAnimationTimings.ENTER_DURATION
-                : SplitAnimationTimings.ABORT_DURATION;
+        boolean isTablet = ((Launcher) context).getDeviceProfile().isTablet;
+        if (isToState && isTablet) {
+            return SplitAnimationTimings.TABLET_ENTER_DURATION;
+        } else if (isToState && !isTablet) {
+            return SplitAnimationTimings.PHONE_ENTER_DURATION;
+        } else {
+            return SplitAnimationTimings.ABORT_DURATION;
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index dea4e48..6b616b1 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -255,7 +255,8 @@
         TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
         for (GroupedRecentTaskInfo rawTask : rawTasks) {
             if (rawTask.getType() == GroupedRecentTaskInfo.TYPE_FREEFORM) {
-                // TODO: add entry for freeform tasks
+                GroupTask desktopTask = createDesktopTask(rawTask);
+                allTasks.add(desktopTask);
                 continue;
             }
             ActivityManager.RecentTaskInfo taskInfo1 = rawTask.getTaskInfo1();
@@ -283,6 +284,16 @@
         return allTasks;
     }
 
+    private GroupTask createDesktopTask(GroupedRecentTaskInfo taskInfo) {
+        // TODO(b/244348395): create a subclass of GroupTask for desktop tile
+        // We need a single task information as the primary task. Use the first task
+        Task.TaskKey key = new Task.TaskKey(taskInfo.getTaskInfo1());
+        Task task = new Task(key);
+        task.desktopTile = true;
+        task.topActivity = key.sourceComponent;
+        return new GroupTask(task, null, null);
+    }
+
     private SplitConfigurationOptions.SplitBounds convertSplitBounds(
             SplitBounds shellSplitBounds) {
         return shellSplitBounds == null ?
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index dcf685a..3b2df31 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -58,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.desktopmode.IDesktopMode;
 import com.android.wm.shell.floating.IFloatingTasks;
 import com.android.wm.shell.onehanded.IOneHanded;
 import com.android.wm.shell.pip.IPip;
@@ -95,6 +96,7 @@
     private IStartingWindow mStartingWindow;
     private IRecentTasks mRecentTasks;
     private IBackAnimation mBackAnimation;
+    private IDesktopMode mDesktopMode;
     private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
         MAIN_EXECUTOR.execute(() -> clearProxy());
     };
@@ -169,7 +171,7 @@
             IFloatingTasks floatingTasks, IOneHanded oneHanded, IShellTransitions shellTransitions,
             IStartingWindow startingWindow, IRecentTasks recentTasks,
             ISysuiUnlockAnimationController sysuiUnlockAnimationController,
-            IBackAnimation backAnimation) {
+            IBackAnimation backAnimation, IDesktopMode desktopMode) {
         unlinkToDeath();
         mSystemUiProxy = proxy;
         mPip = pip;
@@ -181,6 +183,7 @@
         mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
         mRecentTasks = recentTasks;
         mBackAnimation = backAnimation;
+        mDesktopMode = desktopMode;
         linkToDeath();
         // re-attach the listeners once missing due to setProxy has not been initialized yet.
         if (mPipAnimationListener != null && mPip != null) {
@@ -207,7 +210,7 @@
     }
 
     public void clearProxy() {
-        setProxy(null, null, null, null, null, null, null, null, null, null);
+        setProxy(null, null, null, null, null, null, null, null, null, null, null);
     }
 
     // TODO(141886704): Find a way to remove this
@@ -908,4 +911,19 @@
 
         return false;
     }
+
+    //
+    // Desktop Mode
+    //
+
+    /** Call shell to show all apps active on the desktop */
+    public void showDesktopApps() {
+        if (mDesktopMode != null) {
+            try {
+                mDesktopMode.showDesktopApps();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call showDesktopApps", e);
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index e207a1b..1999701 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_DESKTOP_MODE;
 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;
@@ -111,6 +112,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.desktopmode.IDesktopMode;
 import com.android.wm.shell.floating.IFloatingTasks;
 import com.android.wm.shell.onehanded.IOneHanded;
 import com.android.wm.shell.pip.IPip;
@@ -184,10 +186,12 @@
                     bundle.getBinder(KEY_EXTRA_RECENT_TASKS));
             IBackAnimation backAnimation = IBackAnimation.Stub.asInterface(
                     bundle.getBinder(KEY_EXTRA_SHELL_BACK_ANIMATION));
+            IDesktopMode desktopMode = IDesktopMode.Stub.asInterface(
+                    bundle.getBinder(KEY_EXTRA_SHELL_DESKTOP_MODE));
             MAIN_EXECUTOR.execute(() -> {
                 SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
                         splitscreen, floatingTasks, onehanded, shellTransitions, startingWindow,
-                        recentTasks, launcherUnlockAnimationController, backAnimation);
+                        recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode);
                 TouchInteractionService.this.initInputMonitor("TISBinder#onInitialize()");
                 preloadOverview(true /* fromInit */);
             });
diff --git a/quickstep/src/com/android/quickstep/util/AnimUtils.java b/quickstep/src/com/android/quickstep/util/AnimUtils.java
new file mode 100644
index 0000000..b7b7825
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/AnimUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+/**
+ * Utility class containing methods to help manage animations, interpolators, and timings.
+ */
+public class AnimUtils {
+    /**
+     * Fetches device-specific timings for the Overview > Split animation
+     * (splitscreen initiated from Overview).
+     */
+    public static SplitAnimationTimings getDeviceOverviewToSplitTimings(boolean isTablet) {
+        return isTablet
+                ? SplitAnimationTimings.TABLET_OVERVIEW_TO_SPLIT
+                : SplitAnimationTimings.PHONE_OVERVIEW_TO_SPLIT;
+    }
+
+    /**
+     * Fetches device-specific timings for the Split > Confirm animation
+     * (splitscreen confirmed by selecting a second app).
+     */
+    public static SplitAnimationTimings getDeviceSplitToConfirmTimings(boolean isTablet) {
+        return isTablet
+                ? SplitAnimationTimings.TABLET_SPLIT_TO_CONFIRM
+                : SplitAnimationTimings.PHONE_SPLIT_TO_CONFIRM;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java
index f44796b..d46c0f8 100644
--- a/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java
+++ b/quickstep/src/com/android/quickstep/util/OverviewToSplitTimings.java
@@ -16,27 +16,28 @@
 
 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; }
+abstract class OverviewToSplitTimings implements SplitAnimationTimings {
+    // Overwritten by device-specific timings
+    abstract public int getPlaceholderFadeInStart();
+    abstract public int getPlaceholderFadeInEnd();
+    abstract public int getPlaceholderIconFadeInStart();
+    abstract public int getPlaceholderIconFadeInEnd();
+    abstract public int getStagedRectSlideStart();
+    abstract public int getStagedRectSlideEnd();
+    abstract public int getGridSlideStart();
+    abstract public int getGridSlideStagger();
+    abstract public int getGridSlideDuration();
+
+    // Common timings
     public int getIconFadeStart() { return 0; }
     public int getIconFadeEnd() { return 83; }
+    public int getActionsFadeStart() { return 0; }
+    public int getActionsFadeEnd() { return 83; }
     public int getInstructionsContainerFadeInStart() { return 167; }
     public int getInstructionsContainerFadeInEnd() { return 250; }
     public int getInstructionsTextFadeInStart() { return 217; }
@@ -44,11 +45,13 @@
     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; }
+    abstract public int getDuration();
+    abstract public Interpolator getStagedRectXInterpolator();
+    abstract public Interpolator getStagedRectYInterpolator();
+    abstract public Interpolator getStagedRectScaleXInterpolator();
+    abstract public Interpolator getStagedRectScaleYInterpolator();
+    abstract public Interpolator getGridSlidePrimaryInterpolator();
+    abstract public Interpolator getGridSlideSecondaryInterpolator();
 
     public float getGridSlideStartOffset() {
         return (float) getGridSlideStart() / getDuration();
diff --git a/quickstep/src/com/android/quickstep/util/PhoneOverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/PhoneOverviewToSplitTimings.java
new file mode 100644
index 0000000..66c41d6
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/PhoneOverviewToSplitTimings.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the Overview > OverviewSplitSelect animation on phones.
+ */
+public class PhoneOverviewToSplitTimings
+        extends OverviewToSplitTimings implements SplitAnimationTimings {
+    public int getPlaceholderFadeInStart() { return 0; }
+    public int getPlaceholderFadeInEnd() { return 133; }
+    public int getPlaceholderIconFadeInStart() { return 83; }
+    public int getPlaceholderIconFadeInEnd() { return 167; }
+    public int getStagedRectSlideStart() { return 0; }
+    public int getStagedRectSlideEnd() { return 333; }
+    public int getGridSlideStart() { return 100; }
+    public int getGridSlideStagger() { return 0; }
+    public int getGridSlideDuration() { return 417; }
+
+    public int getDuration() { return PHONE_ENTER_DURATION; }
+    public Interpolator getStagedRectXInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectYInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectScaleXInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectScaleYInterpolator() { return EMPHASIZED; }
+    public Interpolator getGridSlidePrimaryInterpolator() { return EMPHASIZED; }
+    public Interpolator getGridSlideSecondaryInterpolator() { return INSTANT; }
+}
diff --git a/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
new file mode 100644
index 0000000..9e7351a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the OverviewSplitSelect > confirmed animation on phones.
+ */
+public class PhoneSplitToConfirmTimings
+        extends SplitToConfirmTimings implements SplitAnimationTimings {
+    public int getPlaceholderFadeInStart() { return 0; }
+    public int getPlaceholderFadeInEnd() { return 133; }
+    public int getPlaceholderIconFadeInStart() { return 50; }
+    public int getPlaceholderIconFadeInEnd() { return 133; }
+    public int getStagedRectSlideStart() { return 0; }
+    public int getStagedRectSlideEnd() { return 333; }
+
+    public int getDuration() { return PHONE_CONFIRM_DURATION; }
+    public Interpolator getStagedRectXInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectYInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectScaleXInterpolator() { return EMPHASIZED; }
+    public Interpolator getStagedRectScaleYInterpolator() { return EMPHASIZED; }
+}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
index 133be03..2966fbb 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationTimings.java
@@ -16,19 +16,28 @@
 
 package com.android.quickstep.util;
 
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
 import android.view.animation.Interpolator;
 
 /**
  * An interface that supports the centralization of timing information for splitscreen animations.
  */
 public interface SplitAnimationTimings {
-    int ENTER_DURATION = 866;
-    int ABORT_DURATION = 500;
-    int CONFIRM_DURATION = 383;
+    int TABLET_ENTER_DURATION = 866;
+    int TABLET_CONFIRM_DURATION = 383;
 
-    SplitAnimationTimings OVERVIEW_TO_SPLIT = new OverviewToSplitTimings();
-    SplitAnimationTimings NORMAL_TO_SPLIT = new NormalToSplitTimings();
-    SplitAnimationTimings SPLIT_TO_CONFIRM = new SplitToConfirmTimings();
+    int PHONE_ENTER_DURATION = 517;
+    int PHONE_CONFIRM_DURATION = 333;
+
+    int ABORT_DURATION = 500;
+
+    SplitAnimationTimings TABLET_OVERVIEW_TO_SPLIT = new TabletOverviewToSplitTimings();
+    SplitAnimationTimings TABLET_HOME_TO_SPLIT = new TabletHomeToSplitTimings();
+    SplitAnimationTimings TABLET_SPLIT_TO_CONFIRM = new TabletSplitToConfirmTimings();
+
+    SplitAnimationTimings PHONE_OVERVIEW_TO_SPLIT = new PhoneOverviewToSplitTimings();
+    SplitAnimationTimings PHONE_SPLIT_TO_CONFIRM = new PhoneSplitToConfirmTimings();
 
     // Shared methods
     int getDuration();
@@ -75,8 +84,10 @@
     default float getInstructionsTextFadeInEndOffset() { return 0; }
     default float getInstructionsUnfoldStartOffset() { return 0; }
     default float getInstructionsUnfoldEndOffset() { return 0; }
+    default Interpolator getGridSlidePrimaryInterpolator() { return LINEAR; }
+    default Interpolator getGridSlideSecondaryInterpolator() { return LINEAR; }
 
-    // Defaults for NormalToSplit
+    // Defaults for HomeToSplit
     default float getScrimFadeInStartOffset() { return 0; }
     default float getScrimFadeInEndOffset() { return 0; }
 
diff --git a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
index 84cfb0b..3026e98 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
@@ -16,28 +16,29 @@
 
 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; }
+abstract class SplitToConfirmTimings implements SplitAnimationTimings {
+    // Overwritten by device-specific timings
+    abstract public int getPlaceholderFadeInStart();
+    abstract public int getPlaceholderFadeInEnd();
+    abstract public int getPlaceholderIconFadeInStart();
+    abstract public int getPlaceholderIconFadeInEnd();
+    abstract public int getStagedRectSlideStart();
+    abstract public int getStagedRectSlideEnd();
+
+    // Common timings
     public int getInstructionsFadeStart() { return 0; }
     public int getInstructionsFadeEnd() { return 67; }
 
-    public 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; }
+    abstract public int getDuration();
+    abstract public Interpolator getStagedRectXInterpolator();
+    abstract public Interpolator getStagedRectYInterpolator();
+    abstract public Interpolator getStagedRectScaleXInterpolator();
+    abstract public Interpolator getStagedRectScaleYInterpolator();
 
     public float getInstructionsFadeStartOffset() {
         return (float) getInstructionsFadeStart() / getDuration();
diff --git a/quickstep/src/com/android/quickstep/util/NormalToSplitTimings.java b/quickstep/src/com/android/quickstep/util/TabletHomeToSplitTimings.java
similarity index 88%
rename from quickstep/src/com/android/quickstep/util/NormalToSplitTimings.java
rename to quickstep/src/com/android/quickstep/util/TabletHomeToSplitTimings.java
index 1bf5a5d..bf8612a 100644
--- a/quickstep/src/com/android/quickstep/util/NormalToSplitTimings.java
+++ b/quickstep/src/com/android/quickstep/util/TabletHomeToSplitTimings.java
@@ -21,9 +21,10 @@
 import android.view.animation.Interpolator;
 
 /**
- * Timings for the Normal > OverviewSplitSelect animation.
+ * Timings for the Home > OverviewSplitSelect animation on tablets.
  */
-public class NormalToSplitTimings extends OverviewToSplitTimings implements SplitAnimationTimings {
+public class TabletHomeToSplitTimings
+        extends TabletOverviewToSplitTimings implements SplitAnimationTimings {
     @Override
     public Interpolator getStagedRectXInterpolator() { return LINEAR; }
     @Override
diff --git a/quickstep/src/com/android/quickstep/util/TabletOverviewToSplitTimings.java b/quickstep/src/com/android/quickstep/util/TabletOverviewToSplitTimings.java
new file mode 100644
index 0000000..30defa7
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/TabletOverviewToSplitTimings.java
@@ -0,0 +1,46 @@
+/*
+ * 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 static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Timings for the Overview > OverviewSplitSelect animation on tablets.
+ */
+public class TabletOverviewToSplitTimings
+        extends OverviewToSplitTimings implements SplitAnimationTimings {
+    public int getPlaceholderFadeInStart() { return 0; }
+    public int getPlaceholderFadeInEnd() { return 133; }
+    public int getPlaceholderIconFadeInStart() { return 167; }
+    public int getPlaceholderIconFadeInEnd() { return 250; }
+    public int getStagedRectSlideStart() { return 0; }
+    public int getStagedRectSlideEnd() { return 417; }
+    public int getGridSlideStart() { return 67; }
+    public int getGridSlideStagger() { return 16; }
+    public int getGridSlideDuration() { return 500; }
+
+    public int getDuration() { return TABLET_ENTER_DURATION; }
+    public Interpolator getStagedRectXInterpolator() { return DEACCEL_2; }
+    public Interpolator getStagedRectYInterpolator() { return DEACCEL_2; }
+    public Interpolator getStagedRectScaleXInterpolator() { return DEACCEL_2; }
+    public Interpolator getStagedRectScaleYInterpolator() { return DEACCEL_2; }
+    public Interpolator getGridSlidePrimaryInterpolator() { return LINEAR; }
+    public Interpolator getGridSlideSecondaryInterpolator() { return LINEAR; }
+}
diff --git a/quickstep/src/com/android/quickstep/util/NormalToSplitTimings.java b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
similarity index 60%
copy from quickstep/src/com/android/quickstep/util/NormalToSplitTimings.java
copy to quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
index 1bf5a5d..3ea8466 100644
--- a/quickstep/src/com/android/quickstep/util/NormalToSplitTimings.java
+++ b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
@@ -21,23 +21,20 @@
 import android.view.animation.Interpolator;
 
 /**
- * Timings for the Normal > OverviewSplitSelect animation.
+ * Timings for the OverviewSplitSelect > confirmed animation on tablets.
  */
-public class NormalToSplitTimings extends OverviewToSplitTimings implements SplitAnimationTimings {
-    @Override
+public class TabletSplitToConfirmTimings
+        extends SplitToConfirmTimings implements SplitAnimationTimings {
+    public int getPlaceholderFadeInStart() { return 0; }
+    public int getPlaceholderFadeInEnd() { return 133; }
+    public int getPlaceholderIconFadeInStart() { return 167; }
+    public int getPlaceholderIconFadeInEnd() { return 250; }
+    public int getStagedRectSlideStart() { return 0; }
+    public int getStagedRectSlideEnd() { return 383; }
+
+    public int getDuration() { return TABLET_CONFIRM_DURATION; }
     public Interpolator getStagedRectXInterpolator() { return LINEAR; }
-    @Override
+    public Interpolator getStagedRectYInterpolator() { return LINEAR; }
     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/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index 1a02f03..dc1ae52 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -30,6 +30,7 @@
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.util.AnimUtils;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.util.TaskCornerRadius;
@@ -215,9 +216,19 @@
      */
     public void addStagingAnimation(PendingAnimation animation, RectF startingBounds,
             Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask) {
-        SplitAnimationTimings timings = fadeWithThumbnail
-                ? SplitAnimationTimings.OVERVIEW_TO_SPLIT
-                : SplitAnimationTimings.NORMAL_TO_SPLIT;
+        boolean isTablet = mActivity.getDeviceProfile().isTablet;
+        boolean splittingFromOverview = fadeWithThumbnail;
+        SplitAnimationTimings timings;
+
+        if (isTablet && splittingFromOverview) {
+            timings = SplitAnimationTimings.TABLET_OVERVIEW_TO_SPLIT;
+        } else if (!isTablet && splittingFromOverview) {
+            timings = SplitAnimationTimings.PHONE_OVERVIEW_TO_SPLIT;
+        } else {
+            // Splitting from Home is currently only available on tablets
+            timings = SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
+        }
+
         addAnimation(animation, startingBounds, endBounds, fadeWithThumbnail, isStagedTask,
                 timings);
     }
@@ -228,8 +239,11 @@
      */
     public void addConfirmAnimation(PendingAnimation animation, RectF startingBounds,
             Rect endBounds, boolean fadeWithThumbnail, boolean isStagedTask) {
+        SplitAnimationTimings timings =
+                AnimUtils.getDeviceSplitToConfirmTimings(mActivity.getDeviceProfile().isTablet);
+
         addAnimation(animation, startingBounds, endBounds, fadeWithThumbnail, isStagedTask,
-                SplitAnimationTimings.SPLIT_TO_CONFIRM);
+                timings);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 96607a0..2f91e16 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -171,6 +171,7 @@
 import com.android.quickstep.ViewUtils;
 import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.AnimUtils;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsOrientedState;
@@ -2775,7 +2776,7 @@
         anim.setFloat(taskView, VIEW_ALPHA, 0,
                 clampToProgress(isOnGridBottomRow(taskView) ? ACCEL : FINAL_FRAME, 0, 0.5f));
         FloatProperty<TaskView> secondaryViewTranslate =
-                taskView.getSecondaryDissmissTranslationProperty();
+                taskView.getSecondaryDismissTranslationProperty();
         int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
         int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
 
@@ -2809,7 +2810,8 @@
         mOrientationHandler.getInitialSplitPlaceholderBounds(mSplitPlaceholderSize,
                 mSplitPlaceholderInset, mActivity.getDeviceProfile(),
                 mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
-        SplitAnimationTimings timings = SplitAnimationTimings.OVERVIEW_TO_SPLIT;
+        SplitAnimationTimings timings =
+                AnimUtils.getDeviceOverviewToSplitTimings(mActivity.getDeviceProfile().isTablet);
 
         RectF startingTaskRect = new RectF();
         safeRemoveDragLayerView(mFirstFloatingTaskView);
@@ -3053,6 +3055,9 @@
             }
         }
 
+        SplitAnimationTimings splitTimings =
+                AnimUtils.getDeviceOverviewToSplitTimings(mActivity.getDeviceProfile().isTablet);
+
         int distanceFromDismissedTask = 0;
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
@@ -3095,9 +3100,29 @@
                     float additionalDismissDuration =
                             ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * Math.abs(
                                     i - dismissedIndex);
-                    anim.setFloat(child, translationProperty, scrollDiff, clampToProgress(LINEAR,
-                            Utilities.boundToRange(INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                    + additionalDismissDuration, 0f, 1f), 1));
+
+                    // We are in non-grid layout.
+                    // If dismissing for split select, use split timings.
+                    // If not, use dismiss timings.
+                    float animationStartProgress = isSplitSelectionActive()
+                            ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset(), 0f, 1f)
+                            : Utilities.boundToRange(
+                                    INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+                                            + additionalDismissDuration, 0f, 1f);
+
+                    float animationEndProgress = isSplitSelectionActive()
+                            ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset()
+                                            + splitTimings.getGridSlideDurationOffset(), 0f, 1f)
+                            : 1f;
+
+                    anim.setFloat(child, translationProperty, scrollDiff,
+                            clampToProgress(
+                                    splitTimings.getGridSlidePrimaryInterpolator(),
+                                    animationStartProgress,
+                                    animationEndProgress
+                            )
+                    );
+
                     if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
                             && child instanceof TaskView
                             && ((TaskView) child).isRunningTask()) {
@@ -3138,11 +3163,11 @@
                         : 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),
+                                splitTimings.getGridSlideStartOffset()
+                                        + (splitTimings.getGridSlideStaggerOffset()
+                                        * staggerColumn),
                         0f,
                         dismissTranslationInterpolationEnd)
                         : Utilities.boundToRange(
@@ -3151,9 +3176,9 @@
                                         * staggerColumn, 0f, dismissTranslationInterpolationEnd);
                 float animationEndProgress = isStagingFocusedTask
                         ? Utilities.boundToRange(
-                                timings.getGridSlideStartOffset()
-                                        + (timings.getGridSlideStaggerOffset() * staggerColumn)
-                                        + timings.getGridSlideDurationOffset(),
+                                splitTimings.getGridSlideStartOffset()
+                                        + (splitTimings.getGridSlideStaggerOffset() * staggerColumn)
+                                        + splitTimings.getGridSlideDurationOffset(),
                         0f,
                         dismissTranslationInterpolationEnd)
                         : dismissTranslationInterpolationEnd;
@@ -3173,7 +3198,7 @@
                     if (!nextFocusedTaskFromTop) {
                         secondaryTranslation -= mTopBottomRowHeightDiff;
                     }
-                    anim.setFloat(taskView, taskView.getSecondaryDissmissTranslationProperty(),
+                    anim.setFloat(taskView, taskView.getSecondaryDismissTranslationProperty(),
                             secondaryTranslation, clampToProgress(LINEAR, animationStartProgress,
                                     dismissTranslationInterpolationEnd));
                     anim.setFloat(taskView, TaskView.FOCUS_TRANSITION, 0f,
@@ -4179,9 +4204,13 @@
         // TODO(194414938) starting bounds seem slightly off, investigate
         Rect firstTaskStartingBounds = new Rect();
         Rect firstTaskEndingBounds = mTempRect;
-        PendingAnimation pendingAnimation =
-                new PendingAnimation(SplitAnimationTimings.CONFIRM_DURATION);
-        SplitAnimationTimings timings = SplitAnimationTimings.SPLIT_TO_CONFIRM;
+
+        boolean isTablet = mActivity.getDeviceProfile().isTablet;
+        int duration = isTablet
+                ? SplitAnimationTimings.TABLET_CONFIRM_DURATION
+                : SplitAnimationTimings.PHONE_CONFIRM_DURATION;
+        PendingAnimation pendingAnimation = new PendingAnimation(duration);
+        SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet);
 
         int halfDividerSize = getResources()
                 .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index a81f95f..a0f195c 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -88,6 +88,7 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskIconCache;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
@@ -708,6 +709,11 @@
         RecentsView recentsView = getRecentsView();
         RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
         RunnableList runnableList = new RunnableList();
+        if (mTask != null && mTask.desktopTile) {
+            // clicked on desktop
+            SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps();
+            return runnableList;
+        }
         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask() && remoteTargetHandles != null) {
             if (!mIsClickableAsLiveTile) {
                 return runnableList;
@@ -1255,7 +1261,7 @@
                 DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
     }
 
-    public FloatProperty<TaskView> getSecondaryDissmissTranslationProperty() {
+    public FloatProperty<TaskView> getSecondaryDismissTranslationProperty() {
         return getPagedOrientationHandler().getSecondaryValue(
                 DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
     }
diff --git a/res/drawable/page_indicator.xml b/res/drawable/page_indicator.xml
new file mode 100644
index 0000000..c0ccc49
--- /dev/null
+++ b/res/drawable/page_indicator.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="?attr/folderPaginationColor"/>
+    <size android:width="@dimen/page_indicator_size" android:height="@dimen/page_indicator_size"/>
+</shape>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5dae9f2..47584e2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -248,7 +248,7 @@
 
     <!-- Folders -->
     <dimen name="page_indicator_dot_size">8dp</dimen>
-    <dimen name="page_indicator_current_page_indicator_size">10dp</dimen>
+    <dimen name="page_indicator_size">10dp</dimen>
 
 
     <dimen name="folder_cell_x_padding">9dp</dimen>
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index e0b4951..368a373 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -42,25 +42,29 @@
         BaseAllAppsAdapter<T> {
 
     public static final String TAG = "AppsGridAdapter";
-    private final GridLayoutManager mGridLayoutMgr;
-    private final GridSpanSizer mGridSizer;
+    private final AppsGridLayoutManager mGridLayoutMgr;
 
     public AllAppsGridAdapter(T activityContext, LayoutInflater inflater,
             AlphabeticalAppsList apps, BaseAdapterProvider[] adapterProviders) {
         super(activityContext, inflater, apps, adapterProviders);
-        mGridSizer = new GridSpanSizer();
         mGridLayoutMgr = new AppsGridLayoutManager(mActivityContext);
-        mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
+        mGridLayoutMgr.setSpanSizeLookup(new GridSpanSizer());
         setAppsPerRow(activityContext.getDeviceProfile().numShownAllAppsColumns);
     }
 
     /**
      * Returns the grid layout manager.
      */
-    public RecyclerView.LayoutManager getLayoutManager() {
+    public AppsGridLayoutManager getLayoutManager() {
         return mGridLayoutMgr;
     }
 
+    /** @return the column index that the given adapter index falls. */
+    public int getSpanIndex(int adapterIndex) {
+        AppsGridLayoutManager lm = getLayoutManager();
+        return lm.getSpanSizeLookup().getSpanIndex(adapterIndex, lm.getSpanCount());
+    }
+
     /**
      * A subclass of GridLayoutManager that overrides accessibility values during app search.
      */
diff --git a/src/com/android/launcher3/allapps/SearchTransitionController.java b/src/com/android/launcher3/allapps/SearchTransitionController.java
index 8fc7965..a1f5bc6 100644
--- a/src/com/android/launcher3/allapps/SearchTransitionController.java
+++ b/src/com/android/launcher3/allapps/SearchTransitionController.java
@@ -29,6 +29,7 @@
 import android.animation.ObjectAnimator;
 import android.graphics.drawable.Drawable;
 import android.util.FloatProperty;
+import android.util.Log;
 import android.view.View;
 import android.view.animation.Interpolator;
 
@@ -36,10 +37,13 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 
 /** Coordinates the transition between Search and A-Z in All Apps. */
 public class SearchTransitionController {
 
+    private static final String LOG_TAG = "SearchTransitionCtrl";
+
     // Interpolator when the user taps the QSB while already in All Apps.
     private static final Interpolator INTERPOLATOR_WITHIN_ALL_APPS = DEACCEL_1_7;
     // Interpolator when the user taps the QSB from home screen, so transition to all apps is
@@ -171,6 +175,7 @@
         int appRowHeight = 0;
         Integer top = null;
         SearchRecyclerView searchRecyclerView = getSearchRecyclerView();
+
         for (int i = 0; i < searchRecyclerView.getChildCount(); i++) {
             View searchResultView = searchRecyclerView.getChildAt(i);
             if (searchResultView == null) {
@@ -226,15 +231,43 @@
             float scaleY = 1 - mSearchToAzProgress;
             int scaledHeight = (int) (searchResultView.getHeight() * scaleY);
             searchResultView.setScaleY(scaleY);
-            searchResultView.setY(top + totalHeight);
 
-            numSearchResultsAnimated++;
-            totalHeight += scaledHeight;
+            // For rows with multiple elements, only count the height once and translate elements to
+            // the same y position.
+            int y = top + totalHeight;
+            int spanIndex = getSpanIndex(searchRecyclerView, adapterPosition);
+            if (spanIndex > 0) {
+                // Continuation of an existing row; move this item into the row.
+                y -= scaledHeight;
+            } else {
+                // Start of a new row contributes to total height and animation stagger.
+                numSearchResultsAnimated++;
+                totalHeight += scaledHeight;
+            }
+            searchResultView.setY(y);
         }
 
         return totalHeight - appRowHeight;
     }
 
+    /** @return the column that the view at this position is found (0 assumed if indeterminate). */
+    private int getSpanIndex(SearchRecyclerView searchRecyclerView, int adapterPosition) {
+        if (adapterPosition == NO_POSITION) {
+            Log.w(LOG_TAG, "Can't determine span index - child not found in adapter");
+            return 0;
+        }
+        if (!(searchRecyclerView.getAdapter() instanceof AllAppsGridAdapter<?>)) {
+            Log.e(LOG_TAG, "Search RV doesn't have an AllAppsGridAdapter?");
+            // This case shouldn't happen, but for debug devices we will continue to create a more
+            // visible crash.
+            if (!Utilities.IS_DEBUG_DEVICE) {
+                return 0;
+            }
+        }
+        AllAppsGridAdapter<?> adapter = (AllAppsGridAdapter<?>) searchRecyclerView.getAdapter();
+        return adapter.getSpanIndex(adapterPosition);
+    }
+
     /** Called just before a child is attached to the SearchRecyclerView. */
     private void onSearchChildAttached(View child) {
         // Avoid allocating hardware layers for alpha changes.
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 4a47e98..50cf8d6 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -97,6 +97,9 @@
     public static final BooleanFlag ENABLE_HIDE_HEADER = new DeviceFlag("ENABLE_HIDE_HEADER",
             true, "Hide header on keyboard before typing in all apps");
 
+    public static final BooleanFlag ENABLE_HIDE_HEADER_STATIC = new DeviceFlag(
+            "ENABLE_HIDE_HEADER_STATIC", false, "Hide keyboard suggestion strip");
+
     public static final BooleanFlag COLLECT_SEARCH_HISTORY = new DeviceFlag(
             "COLLECT_SEARCH_HISTORY", false, "Allow launcher to collect search history for log");
 
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index fb51bf0..28e0b68 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -30,6 +30,7 @@
 import android.graphics.Paint;
 import android.graphics.Paint.Style;
 import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.Property;
 import android.view.View;
@@ -84,9 +85,11 @@
     };
 
     private final Paint mPaginationPaint;
+    private final Drawable mPageIndicatorDrawable;
     private final float mDotRadius;
     private final float mCircleGap;
     private final float mPageIndicatorSize;
+    private final float mPageIndicatorRadius;
     private final boolean mIsRtl;
 
     private int mNumPages;
@@ -125,13 +128,22 @@
         mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.folderPaginationColor));
         mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
 
+
         if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
+            mPageIndicatorSize = getResources().getDimension(
+                    R.dimen.page_indicator_size);
+            mPageIndicatorRadius = mPageIndicatorSize / 2;
+            mPageIndicatorDrawable = context.getDrawable(R.drawable.page_indicator);
+            mPageIndicatorDrawable.setBounds(0, 0, (int) mPageIndicatorSize,
+                    (int) mPageIndicatorSize);
             mCircleGap = DOT_GAP_FACTOR_FLOAT * mDotRadius;
+
         } else {
+            mPageIndicatorSize = 0;
+            mPageIndicatorRadius = 0;
+            mPageIndicatorDrawable = null;
             mCircleGap = DOT_GAP_FACTOR * mDotRadius;
         }
-        mPageIndicatorSize = getResources().getDimension(
-                R.dimen.page_indicator_current_page_indicator_size);
         if (!SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
             setOutlineProvider(new MyOutlineProver());
         }
@@ -308,16 +320,19 @@
             mPaginationPaint.setAlpha(PAGE_INDICATOR_ALPHA);
             if (SHOW_DELIGHTFUL_PAGINATION_FOLDER.get()) {
                 RectF currRect = getActiveRect();
-                int scrollPerPage = getScrollPerPage();
 
+                // Moves the canvas to start at the top left corner of the page indicator
+                canvas.translate(currRect.left, currRect.top);
+
+                int scrollPerPage = getScrollPerPage();
                 // This IF is to avoid division by 0
                 if (scrollPerPage != 0) {
                     int delta = mCurrentScroll % scrollPerPage;
                     canvas.rotate((INDICATOR_ROTATION * delta) / scrollPerPage,
-                            currRect.centerX(), currRect.centerY());
+                            mPageIndicatorRadius, mPageIndicatorRadius);
                 }
 
-                canvas.drawRect(currRect, mPaginationPaint);
+                mPageIndicatorDrawable.draw(canvas);
             } else {
                 canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
             }
@@ -334,7 +349,7 @@
         float startXIndicator =
                 ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2) - getOffset();
         float indicatorPosition = startXIndicator + getIndicatorScrollDistance()
-                + (mPageIndicatorSize / 2);
+                + mPageIndicatorRadius;
 
         // If the indicator gets close enough to a dot then we change the radius
         // of the dot based on how close the indicator is to it.
@@ -391,7 +406,7 @@
      * the indicator is centered in with the indicator circles
      */
     private float getOffset() {
-        return (mPageIndicatorSize / 2) - mDotRadius;
+        return mPageIndicatorRadius - mDotRadius;
     }
 
     /**
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index 47503b1..f73347a 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -33,6 +33,8 @@
 import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
@@ -41,6 +43,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Extension of {@link AbstractFloatingView} with common methods for sliding in from bottom.
@@ -79,6 +82,7 @@
     protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED;
 
     protected boolean mNoIntercept;
+    protected @Nullable OnCloseListener mOnCloseBeginListener;
     protected List<OnCloseListener> mOnCloseListeners = new ArrayList<>();
 
     public AbstractSlideInView(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -204,6 +208,11 @@
         }
     }
 
+    /** Callback invoked when the view is beginning to close (e.g. close animation is started). */
+    public void setOnCloseBeginListener(@Nullable OnCloseListener onCloseBeginListener) {
+        mOnCloseBeginListener = onCloseBeginListener;
+    }
+
     /** Registers an {@link OnCloseListener}. */
     public void addOnCloseListener(OnCloseListener listener) {
         mOnCloseListeners.add(listener);
@@ -213,6 +222,8 @@
         if (!mIsOpen) {
             return;
         }
+        Optional.ofNullable(mOnCloseBeginListener).ifPresent(OnCloseListener::onSlideInViewClosed);
+
         if (!animate) {
             mOpenCloseAnimator.cancel();
             setTranslationShift(TRANSLATION_SHIFT_CLOSED);