Merge "Import translations. DO NOT MERGE ANYWHERE" into tm-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
index 9a682c6..7c3281a 100644
--- a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
+++ b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java
@@ -55,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) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 49d0873..da6dab1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -270,6 +270,7 @@
 
         @Override
         public void onClick(View view) {
+            // Initiate splitscreen from the in-app Taskbar or Taskbar All Apps
             Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
                     LogUtils.getShellShareableInstanceId();
             mTarget.getStatsLogManager().logger()
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/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index f5161aa..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;
 
 /**
@@ -79,14 +81,6 @@
     private static final int PER_PAGE_SCROLL_DURATION = 150;
     private static final int MAX_PAGE_SCROLL_DURATION = 750;
 
-    private static final int OVERVIEW_TO_SPLIT_ACTIONS_FADE_START = 0;
-    private static final int OVERVIEW_TO_SPLIT_ACTIONS_FADE_END = 83;
-
-    private static final float OVERVIEW_TO_SPLIT_ACTIONS_FADE_START_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_ACTIONS_FADE_START / SplitScreenSelectState.ENTER_DURATION;
-    private static final float OVERVIEW_TO_SPLIT_ACTIONS_FADE_END_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_ACTIONS_FADE_END / SplitScreenSelectState.ENTER_DURATION;
-
     // Due to use of physics, duration may differ between devices so we need to calculate and
     // cache the value.
     private int mHintToNormalDuration = -1;
@@ -197,9 +191,17 @@
         } 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,
-                    OVERVIEW_TO_SPLIT_ACTIONS_FADE_START_OFFSET,
-                    OVERVIEW_TO_SPLIT_ACTIONS_FADE_END_OFFSET));
+                    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 2bc3c3e..430053d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 
 import com.android.launcher3.Launcher;
+import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -26,10 +27,6 @@
  * pinned and user is selecting the second one
  */
 public class SplitScreenSelectState extends OverviewState {
-    public static final int ENTER_DURATION = 866;
-    public static final int EXIT_DURATION = 500;
-    // TODO: Add ability to differentiate between Split > Home and Split > Confirmed timings
-
     public SplitScreenSelectState(int id) {
         super(id);
     }
@@ -47,6 +44,8 @@
 
     @Override
     public int getTransitionDuration(Context context, boolean isToState) {
-        return isToState ? ENTER_DURATION : EXIT_DURATION;
+        return isToState
+                ? SplitAnimationTimings.ENTER_DURATION
+                : SplitAnimationTimings.ABORT_DURATION;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 4e635a7..24bb393 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1140,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
@@ -1219,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(
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 1e0ceed..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);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 93eb0f1..e32aaee 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -232,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/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/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/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 c211d7a..bf88702 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -1,9 +1,6 @@
 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_2;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 
@@ -31,10 +28,10 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.uioverrides.states.SplitScreenSelectState;
 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;
 
@@ -44,38 +41,14 @@
  * 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.
  *
  * TODO: Figure out how to copy thumbnail data from existing TaskView to this view.
  */
 public class FloatingTaskView extends FrameLayout {
-    private static final int OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_START = 0;
-    private static final int OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_END = 133;
-    private static final int OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_START = 167;
-    private static final int OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_END = 250;
-    private static final int OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_START = 0;
-    private static final int OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_END = 417;
-
-    private static final float OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_START_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_START
-                    / SplitScreenSelectState.ENTER_DURATION;
-    private static final float OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_END_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_END
-                    / SplitScreenSelectState.ENTER_DURATION;
-    private static final float OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_START_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_START
-                    / SplitScreenSelectState.ENTER_DURATION;
-    private static final float OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_END_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_END
-                    / SplitScreenSelectState.ENTER_DURATION;
-    private static final float OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_START_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_START
-                    / SplitScreenSelectState.ENTER_DURATION;
-    private static final float OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_END_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_END
-                    / SplitScreenSelectState.ENTER_DURATION;
 
     public static final FloatProperty<FloatingTaskView> PRIMARY_TRANSLATE_OFFSCREEN =
             new FloatProperty<FloatingTaskView>("floatingTaskPrimaryTranslateOffscreen") {
@@ -236,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];
@@ -251,49 +253,47 @@
         RectF floatingTaskViewBounds = new RectF();
 
         if (fadeWithThumbnail) {
-            // This code block runs when animating from Overview > OverviewSplitSelect
-            // And for the second thumbnail on confirm
+            // 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,
-                    OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_START_OFFSET,
-                    OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_END_OFFSET));
+                    timings.getPlaceholderFadeInStartOffset(),
+                    timings.getPlaceholderFadeInEndOffset()));
 
-            // SplitPlaceholderView: gray background fades in at the same time, then new icon fades
-            // in
-            animation.setViewAlpha(mSplitPlaceholderView, 1, clampToProgress(LINEAR,
-                    OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_START_OFFSET,
-                    OVERVIEW_TO_SPLIT_THUMBNAIL_FADE_TO_GRAY_END_OFFSET));
-            animation.setViewAlpha(mSplitPlaceholderView.getIconView(), 1, clampToProgress(
-                    LINEAR, OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_START_OFFSET,
-                    OVERVIEW_TO_SPLIT_DOCKED_ICON_FADE_IN_END_OFFSET));
+            // SplitPlaceholderView: gray background fades in at same time, then new icon fades in
+            fadeInSplitPlaceholder(animation, timings);
         } else if (isStagedTask) {
-            // This code block runs when animating from Normal > OverviewSplitSelect
-            // and for the first thumbnail on confirm
+            // 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 when split is initiated from homescreen / all apps
+            // Fade in the placeholder view during Normal > OverviewSplitSelect
             if (mSplitPlaceholderView.getAlpha() == 0) {
-                animation.setViewAlpha(mSplitPlaceholderView, 0.3f, INSTANT);
-                animation.setViewAlpha(mSplitPlaceholderView, 1, ACCEL);
+                mSplitPlaceholderView.getIconView().setAlpha(0);
+                fadeInSplitPlaceholder(animation, timings);
             }
+
+            // No-op for placeholder during OverviewSplitSelect > Confirmed, alpha should be set
         }
 
         MultiValueUpdateListener listener = new MultiValueUpdateListener() {
             // SplitPlaceholderView: rectangle translates and stretches to new position
             final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration,
-                    clampToProgress(DEACCEL_2, OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_START_OFFSET,
-                            OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_END_OFFSET));
+                    clampToProgress(timings.getStagedRectXInterpolator(),
+                            timings.getStagedRectSlideStartOffset(),
+                            timings.getStagedRectSlideEndOffset()));
             final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration,
-                    clampToProgress(DEACCEL_2, OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_START_OFFSET,
-                            OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_END_OFFSET));
+                    clampToProgress(timings.getStagedRectYInterpolator(),
+                            timings.getStagedRectSlideStartOffset(),
+                            timings.getStagedRectSlideEndOffset()));
             final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX, 0,
-                    animDuration, clampToProgress(DEACCEL_2,
-                    OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_START_OFFSET,
-                    OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_END_OFFSET));
+                    animDuration, clampToProgress(timings.getStagedRectScaleXInterpolator(),
+                    timings.getStagedRectSlideStartOffset(),
+                    timings.getStagedRectSlideEndOffset()));
             final FloatProp mTaskViewScaleY = new FloatProp(1f, prop.finalTaskViewScaleY, 0,
-                    animDuration, clampToProgress(DEACCEL_2,
-                    OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_START_OFFSET,
-                    OVERVIEW_TO_SPLIT_STAGED_RECT_SLIDE_END_OFFSET));
+                    animDuration, clampToProgress(timings.getStagedRectScaleYInterpolator(),
+                    timings.getStagedRectSlideStartOffset(),
+                    timings.getStagedRectSlideEndOffset()));
             @Override
             public void onUpdate(float percent, boolean initOnly) {
                 // Calculate the icon position.
@@ -305,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 44ea0a0..5bc7f18 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -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 364cf7b..bb8506d 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -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.
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 6a94094..b00794f 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -39,7 +39,7 @@
 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_85;
+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;
@@ -142,7 +142,6 @@
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.touch.OverScroll;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.uioverrides.states.SplitScreenSelectState;
 import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
@@ -175,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;
@@ -419,54 +419,6 @@
     private static final float ANIMATION_DISMISS_PROGRESS_MIDPOINT = 0.5f;
     private static final float END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.75f;
 
-    private static final int OVERVIEW_TO_SPLIT_THUMBNAIL_SLIDE_START = 67;
-    private static final int OVERVIEW_TO_SPLIT_THUMBNAIL_SLIDE_OFFSET = 16;
-    private static final int SPRING_DISMISS_TRANSLATION_DURATION = 500;
-
-    private static final float INITIAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_THUMBNAIL_SLIDE_START
-                    / SplitScreenSelectState.ENTER_DURATION;
-    private static final float ADDITIONAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_THUMBNAIL_SLIDE_OFFSET
-                    / SplitScreenSelectState.ENTER_DURATION;
-    private static final float END_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET =
-            (float) SPRING_DISMISS_TRANSLATION_DURATION
-                    / SplitScreenSelectState.ENTER_DURATION;
-
-    private static final int OVERVIEW_TO_SPLIT_ICON_FADE_START = 0;
-    private static final int OVERVIEW_TO_SPLIT_ICON_FADE_END = 83;
-    private static final int OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_START = 167;
-    private static final int OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_END = 250;
-    private static final int OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_START = 217;
-    private static final int OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_END = 300;
-    private static final int OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_START = 167;
-    private static final int OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_END = 500;
-
-    private static final float OVERVIEW_TO_SPLIT_ICON_FADE_START_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_ICON_FADE_START
-                    / SplitScreenSelectState.ENTER_DURATION;
-    private static final float OVERVIEW_TO_SPLIT_ICON_FADE_END_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_ICON_FADE_END
-                    / SplitScreenSelectState.ENTER_DURATION;
-    private static final float OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_START_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_START
-                    / SplitScreenSelectState.ENTER_DURATION;
-    private static final float OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_END_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_END
-                    / SplitScreenSelectState.ENTER_DURATION;
-    private static final float OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_START_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_START
-                    / SplitScreenSelectState.ENTER_DURATION;
-    private static final float OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_END_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_END
-                    / SplitScreenSelectState.ENTER_DURATION;
-    private static final float OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_START_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_START
-                    / SplitScreenSelectState.ENTER_DURATION;
-    private static final float OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_END_OFFSET =
-            (float) OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_END
-                    / SplitScreenSelectState.ENTER_DURATION;
-
     private static final float SIGNIFICANT_MOVE_SCREEN_WIDTH_PERCENTAGE = 0.15f;
 
     protected final RecentsOrientedState mOrientationState;
@@ -703,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(),
@@ -1292,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);
         }
@@ -2857,28 +2809,30 @@
         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
+            // Create the split select animation from Overview
             mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE);
             anim.setViewAlpha(mSplitHiddenTaskView.getIconView(), 0, clampToProgress(LINEAR,
-                    OVERVIEW_TO_SPLIT_ICON_FADE_START_OFFSET,
-                    OVERVIEW_TO_SPLIT_ICON_FADE_END_OFFSET));
+                    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 */);
         }
 
@@ -2887,15 +2841,15 @@
         mSplitInstructionsView = SplitInstructionsView.getSplitInstructionsView(mActivity);
         mSplitInstructionsView.setAlpha(0);
         anim.setViewAlpha(mSplitInstructionsView, 1, clampToProgress(LINEAR,
-                OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_START_OFFSET,
-                OVERVIEW_TO_SPLIT_INSTRUCTIONS_CONTAINER_FADE_IN_END_OFFSET));
+                timings.getInstructionsContainerFadeInStartOffset(),
+                timings.getInstructionsContainerFadeInEndOffset()));
         anim.setViewAlpha(mSplitInstructionsView.getTextView(), 1, clampToProgress(LINEAR,
-                OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_START_OFFSET,
-                OVERVIEW_TO_SPLIT_INSTRUCTIONS_TEXT_FADE_IN_END_OFFSET));
+                timings.getInstructionsTextFadeInStartOffset(),
+                timings.getInstructionsTextFadeInEndOffset()));
         anim.addFloat(mSplitInstructionsView, mSplitInstructionsView.UNFOLD, 0.1f, 1,
                 clampToProgress(EMPHASIZED_DECELERATE,
-                        OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_START_OFFSET,
-                        OVERVIEW_TO_SPLIT_INSTRUCTIONS_UNFOLD_END_OFFSET));
+                        timings.getInstructionsUnfoldStartOffset(),
+                        timings.getInstructionsUnfoldEndOffset()));
 
         InteractionJankMonitorWrapper.begin(this,
                 InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "First tile selected");
@@ -3184,24 +3138,26 @@
                         : 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(
-                                INITIAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                        + ADDITIONAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                        * staggerColumn, 0f, dismissTranslationInterpolationEnd)
+                                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(
-                                INITIAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                        + END_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                        + ADDITIONAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                        * staggerColumn,
-                        0f, dismissTranslationInterpolationEnd)
+                                timings.getGridSlideStartOffset()
+                                        + (timings.getGridSlideStaggerOffset() * staggerColumn)
+                                        + timings.getGridSlideDurationOffset(),
+                        0f,
+                        dismissTranslationInterpolationEnd)
                         : dismissTranslationInterpolationEnd;
-                Interpolator dismissInterpolator = isStagingFocusedTask ? OVERSHOOT_0_85 : LINEAR;
+                Interpolator dismissInterpolator = isStagingFocusedTask ? OVERSHOOT_0_75 : LINEAR;
 
                 if (taskView == nextFocusedTaskView) {
                     // Enlarge the task to be focused next, and translate into focus position.
@@ -4223,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;
@@ -4235,7 +4191,7 @@
                 secondTaskEndingBounds);
 
         mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
-        mFirstFloatingTaskView.addAnimation(pendingAnimation,
+        mFirstFloatingTaskView.addConfirmAnimation(pendingAnimation,
                 new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
                 false /* fadeWithThumbnail */, true /* isStagedTask */);
 
@@ -4244,20 +4200,22 @@
                 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");
 
@@ -4278,13 +4236,14 @@
             mFirstFloatingTaskView = null;
             mSecondFloatingTaskView = null;
             mSplitInstructionsView = null;
-            if (mSecondSplitHiddenView != null) {
-                mSecondSplitHiddenView.setVisibility(VISIBLE);
-                mSecondSplitHiddenView = null;
-            }
             mSplitSelectSource = null;
         }
 
+        if (mSecondSplitHiddenView != null) {
+            mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE);
+            mSecondSplitHiddenView = null;
+        }
+
         if (mSplitHiddenTaskViewIndex == -1) {
             return;
         }
@@ -4300,10 +4259,6 @@
         onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
         resetTaskVisuals();
         mSplitHiddenTaskViewIndex = -1;
-        if (mSplitHiddenTaskView != null) {
-            mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE);
-            mSplitHiddenTaskView = null;
-        }
     }
 
     private void safeRemoveDragLayerView(@Nullable View viewToRemove) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index ee8489d..5df03cc 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -387,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;
 
@@ -584,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;
     }
 
@@ -1516,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/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index d900c90..b55a1e4 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -83,7 +83,7 @@
         EXAGGERATED_EASE = new PathInterpolator(exaggeratedEase);
     }
 
-    public static final Interpolator OVERSHOOT_0_85 = new OvershootInterpolator(0.85f);
+    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/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/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/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/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 43766bf..e0df3f0 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -893,7 +893,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.
      */
@@ -1894,8 +1901,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(