Merge "Fix launch animation from bottom row and end of grid" into sc-v2-dev
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index aa31261..1c0c773 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -15,18 +15,10 @@
  */
 package com.android.launcher3.taskbar;
 
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
-import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
-import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION;
-import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
+import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.annotation.ColorInt;
 import android.graphics.Rect;
 import android.os.RemoteException;
@@ -44,28 +36,17 @@
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimatorListeners;
-import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.statemanager.StateManager;
-import com.android.launcher3.util.MultiValueAlpha;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.RecentsAnimationCallbacks;
-import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
-import com.android.quickstep.RecentsAnimationController;
-import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.recents.model.ThumbnailData;
 
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.Set;
-import java.util.function.Supplier;
 import java.util.stream.Stream;
 
 /**
@@ -77,82 +58,15 @@
 
     private final BaseQuickstepLauncher mLauncher;
 
-    private final AnimatedFloat mIconAlignmentForResumedState =
-            new AnimatedFloat(this::onIconAlignmentRatioChanged);
-    private final AnimatedFloat mIconAlignmentForGestureState =
-            new AnimatedFloat(this::onIconAlignmentRatioChanged);
-    private final AnimatedFloat mIconAlignmentForLauncherState =
-            new AnimatedFloat(this::onIconAlignmentRatioChangedForStateTransition);
-
     private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
             this::onStashedInAppChanged;
 
-    private final StateManager.StateListener<LauncherState> mStateListener =
-            new StateManager.StateListener<LauncherState>() {
-                private Animator mAnimator;
-
-                @Override
-                public void onStateTransitionStart(LauncherState toState) {
-                    // Stash animation from going to launcher should be already handled in
-                    // createAnimToLauncher.
-                    TaskbarStashController controller = mControllers.taskbarStashController;
-                    long duration = TASKBAR_STASH_DURATION;
-                    controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
-                            toState.isTaskbarStashed());
-                    Animator stashAnimator = controller.applyStateWithoutStart(duration);
-                    if (stashAnimator != null) {
-                        if (mAnimator != null) {
-                            mAnimator.cancel();
-                        }
-                        PendingAnimation pendingAnimation = new PendingAnimation(duration);
-                        pendingAnimation.add(stashAnimator);
-                        pendingAnimation.setFloat(mIconAlignmentForLauncherState,
-                                AnimatedFloat.VALUE, toState.isTaskbarStashed() ? 0 : 1,
-                                FAST_OUT_SLOW_IN);
-                        pendingAnimation.addListener(new AnimatorListenerAdapter() {
-                            @Override
-                            public void onAnimationStart(Animator animator) {
-                                mTargetStateOverrideForStateTransition = toState;
-                                // Copy hotseat alpha over to taskbar icons
-                                mIconAlphaForHome.setValue(mLauncher.getHotseat().getIconsAlpha());
-                                mLauncher.getHotseat().setIconsAlpha(0);
-                            }
-
-                            @Override
-                            public void onAnimationEnd(Animator animator) {
-                                if (toState.isTaskbarStashed()) {
-                                    // Reset hotseat alpha to default
-                                    mLauncher.getHotseat().setIconsAlpha(1);
-                                }
-                                mTargetStateOverrideForStateTransition = null;
-                                mAnimator = null;
-                            }
-                        });
-                        mAnimator = pendingAnimation.buildAnim();
-                        mAnimator.start();
-                    }
-                }
-
-                @Override
-                public void onStateTransitionComplete(LauncherState finalState) {
-                    TaskbarStashController controller = mControllers.taskbarStashController;
-                    controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
-                            finalState.isTaskbarStashed());
-                    controller.applyState();
-                }
-            };
-
     // Initialized in init.
     private TaskbarControllers mControllers;
-    private AnimatedFloat mTaskbarBackgroundAlpha;
     private AnimatedFloat mTaskbarOverrideBackgroundAlpha;
-    private AlphaProperty mIconAlphaForHome;
-    private boolean mIsAnimatingToLauncherViaResume;
-    private boolean mIsAnimatingToLauncherViaGesture;
     private TaskbarKeyguardController mKeyguardController;
-
-    private LauncherState mTargetStateOverride = null;
-    private LauncherState mTargetStateOverrideForStateTransition = null;
+    private final TaskbarLauncherStateController
+            mTaskbarLauncherStateController = new TaskbarLauncherStateController();
 
     private final DeviceProfile.OnDeviceProfileChangeListener mProfileChangeListener =
             new DeviceProfile.OnDeviceProfileChangeListener() {
@@ -171,37 +85,26 @@
     protected void init(TaskbarControllers taskbarControllers) {
         mControllers = taskbarControllers;
 
-        mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController
-                .getTaskbarBackgroundAlpha();
+        mTaskbarLauncherStateController.init(mControllers, mLauncher);
         mTaskbarOverrideBackgroundAlpha = mControllers.taskbarDragLayerController
                 .getOverrideBackgroundAlpha();
 
-        MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha();
-        mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
-
         mLauncher.setTaskbarUIController(this);
         mKeyguardController = taskbarControllers.taskbarKeyguardController;
 
         onLauncherResumedOrPaused(mLauncher.hasBeenResumed(), true /* fromInit */);
-        mIconAlignmentForResumedState.finishAnimation();
-        onIconAlignmentRatioChanged();
 
         onStashedInAppChanged(mLauncher.getDeviceProfile());
         mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
-        mLauncher.getStateManager().addStateListener(mStateListener);
         mLauncher.addOnDeviceProfileChangeListener(mProfileChangeListener);
     }
 
     @Override
     protected void onDestroy() {
         onLauncherResumedOrPaused(false);
-        mIconAlignmentForResumedState.finishAnimation();
-        mIconAlignmentForGestureState.finishAnimation();
-        mIconAlignmentForLauncherState.finishAnimation();
+        mTaskbarLauncherStateController.onDestroy();
 
         mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
-        mLauncher.getStateManager().removeStateListener(mStateListener);
-        mLauncher.getHotseat().setIconsAlpha(1f);
         mLauncher.setTaskbarUIController(null);
         mLauncher.removeOnDeviceProfileChangeListener(mProfileChangeListener);
         updateTaskTransitionSpec(true);
@@ -209,11 +112,7 @@
 
     @Override
     protected boolean isTaskbarTouchable() {
-        return !isAnimatingToLauncher();
-    }
-
-    private boolean isAnimatingToLauncher() {
-        return mIsAnimatingToLauncherViaResume || mIsAnimatingToLauncherViaGesture;
+        return !mTaskbarLauncherStateController.isAnimatingToLauncher();
     }
 
     @Override
@@ -240,24 +139,9 @@
             }
         }
 
-        long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION;
-        if (fromInit) {
-            // Since we are creating the starting state, we don't have a state to animate from, so
-            // set our state immediately.
-            duration = 0;
-        }
-        ObjectAnimator anim = mIconAlignmentForResumedState.animateToValue(
-                getCurrentIconAlignmentRatio(), isResumed ? 1 : 0)
-                .setDuration(duration);
-
-        anim.addListener(AnimatorListeners.forEndCallback(
-                () -> mIsAnimatingToLauncherViaResume = false));
-        anim.start();
-        mIsAnimatingToLauncherViaResume = isResumed;
-
-        TaskbarStashController stashController = mControllers.taskbarStashController;
-        stashController.updateStateForFlag(FLAG_IN_APP, !isResumed);
-        stashController.applyState(duration);
+        mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed);
+        mTaskbarLauncherStateController.applyState(
+                fromInit ? 0 : QuickstepTransitionManager.CONTENT_ALPHA_DURATION);
     }
 
     /**
@@ -268,77 +152,7 @@
      */
     public Animator createAnimToLauncher(@NonNull LauncherState toState,
             @NonNull RecentsAnimationCallbacks callbacks, long duration) {
-        AnimatorSet animatorSet = new AnimatorSet();
-        TaskbarStashController stashController = mControllers.taskbarStashController;
-        stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
-                toState.isTaskbarStashed());
-        if (toState.isTaskbarStashed()) {
-            animatorSet.play(stashController.applyStateWithoutStart(duration));
-        } else {
-            animatorSet.play(mIconAlignmentForGestureState
-                    .animateToValue(1)
-                    .setDuration(duration));
-        }
-        animatorSet.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animator) {
-                mTargetStateOverride = null;
-                animator.removeListener(this);
-            }
-
-            @Override
-            public void onAnimationStart(Animator animator) {
-                mTargetStateOverride = toState;
-                mIsAnimatingToLauncherViaGesture = true;
-                stashController.updateStateForFlag(FLAG_IN_APP, false);
-                stashController.applyState(duration);
-            }
-        });
-
-        TaskBarRecentsAnimationListener listener = new TaskBarRecentsAnimationListener(callbacks);
-        callbacks.addListener(listener);
-        RecentsView recentsView = mLauncher.getOverviewPanel();
-        recentsView.setTaskLaunchListener(() -> {
-            listener.endGestureStateOverride(true);
-            callbacks.removeListener(listener);
-        });
-
-        return animatorSet;
-    }
-
-    private float getCurrentIconAlignmentRatio() {
-        return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
-    }
-
-    private float getCurrentIconAlignmentRatioForLauncherState() {
-        return mIconAlignmentForLauncherState.value;
-    }
-
-    private void onIconAlignmentRatioChangedForStateTransition() {
-        onIconAlignmentRatioChanged(
-                mTargetStateOverrideForStateTransition != null
-                        ? mTargetStateOverrideForStateTransition
-                        : mLauncher.getStateManager().getState(),
-                this::getCurrentIconAlignmentRatioForLauncherState);
-    }
-
-    private void onIconAlignmentRatioChanged() {
-        onIconAlignmentRatioChanged(mTargetStateOverride != null ? mTargetStateOverride
-                : mLauncher.getStateManager().getState(), this::getCurrentIconAlignmentRatio);
-    }
-
-    private void onIconAlignmentRatioChanged(LauncherState state,
-            Supplier<Float> alignmentSupplier) {
-        if (mControllers == null) {
-            return;
-        }
-        float alignment = alignmentSupplier.get();
-        mControllers.taskbarViewController.setLauncherIconAlignment(
-                alignment, mLauncher.getDeviceProfile());
-
-        mTaskbarBackgroundAlpha.updateValue(1 - alignment);
-
-        setIconAlpha(state, alignment);
+        return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration);
     }
 
     /**
@@ -358,20 +172,6 @@
         return mControllers.taskbarActivityContext.getDragLayer();
     }
 
-    private void setIconAlpha(LauncherState state, float progress) {
-        if ((state.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
-            // If the hotseat icons are visible, then switch taskbar in last frame
-            setTaskbarViewVisible(progress < 1);
-        } else {
-            mIconAlphaForHome.setValue(1 - progress);
-        }
-    }
-
-    private void setTaskbarViewVisible(boolean isVisible) {
-        mIconAlphaForHome.setValue(isVisible ? 1 : 0);
-        mLauncher.getHotseat().setIconsAlpha(isVisible ? 0f : 1f);
-    }
-
     @Override
     protected void onStashedInAppChanged() {
         onStashedInAppChanged(mLauncher.getDeviceProfile());
@@ -451,35 +251,4 @@
         mLauncher.logAppLaunch(mControllers.taskbarActivityContext.getStatsLogManager(), item,
                 instanceId);
     }
-
-    private final class TaskBarRecentsAnimationListener implements RecentsAnimationListener {
-        private final RecentsAnimationCallbacks mCallbacks;
-
-        TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) {
-            mCallbacks = callbacks;
-        }
-
-        @Override
-        public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
-            endGestureStateOverride(true);
-        }
-
-        @Override
-        public void onRecentsAnimationFinished(RecentsAnimationController controller) {
-            endGestureStateOverride(!controller.getFinishTargetIsLauncher());
-        }
-
-        private void endGestureStateOverride(boolean finishedToApp) {
-            mCallbacks.removeListener(this);
-            mIsAnimatingToLauncherViaGesture = false;
-
-            mIconAlignmentForGestureState
-                    .animateToValue(0)
-                    .start();
-
-            TaskbarStashController controller = mControllers.taskbarStashController;
-            controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
-            controller.applyState();
-        }
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
new file mode 100644
index 0000000..152b255
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
+import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION;
+import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.RecentsAnimationCallbacks;
+import com.android.quickstep.RecentsAnimationController;
+import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+import java.util.HashMap;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Track LauncherState, RecentsAnimation, resumed state for task bar in one place here and animate
+ * the task bar accordingly.
+ */
+ public class TaskbarLauncherStateController {
+
+    public static final int FLAG_RESUMED = 1 << 0;
+    public static final int FLAG_RECENTS_ANIMATION_RUNNING = 1 << 1;
+    public static final int FLAG_TRANSITION_STATE_START_STASHED = 1 << 2;
+    public static final int FLAG_TRANSITION_STATE_COMMITTED_STASHED = 1 << 3;
+
+    private final AnimatedFloat mIconAlignmentForResumedState =
+            new AnimatedFloat(this::onIconAlignmentRatioChanged);
+    private final AnimatedFloat mIconAlignmentForGestureState =
+            new AnimatedFloat(this::onIconAlignmentRatioChanged);
+    private final AnimatedFloat mIconAlignmentForLauncherState =
+            new AnimatedFloat(this::onIconAlignmentRatioChangedForStateTransition);
+
+    private TaskbarControllers mControllers;
+    private AnimatedFloat mTaskbarBackgroundAlpha;
+    private MultiValueAlpha.AlphaProperty mIconAlphaForHome;
+    private BaseQuickstepLauncher mLauncher;
+
+    private int mPrevState;
+    private int mState;
+
+    private LauncherState mTargetStateOverride = null;
+    private LauncherState mTargetStateOverrideForStateTransition = null;
+
+    private boolean mIsAnimatingToLauncherViaGesture;
+    private boolean mIsAnimatingToLauncherViaResume;
+
+    private final StateManager.StateListener<LauncherState> mStateListener =
+            new StateManager.StateListener<LauncherState>() {
+
+                @Override
+                public void onStateTransitionStart(LauncherState toState) {
+                    mTargetStateOverrideForStateTransition = toState;
+                    updateStateForFlag(FLAG_TRANSITION_STATE_START_STASHED,
+                            toState.isTaskbarStashed());
+                    applyState();
+                }
+
+                @Override
+                public void onStateTransitionComplete(LauncherState finalState) {
+                    updateStateForFlag(FLAG_TRANSITION_STATE_COMMITTED_STASHED,
+                            finalState.isTaskbarStashed());
+                    applyState();
+                }
+            };
+
+    public void init(TaskbarControllers controllers, BaseQuickstepLauncher launcher) {
+        mControllers = controllers;
+        mLauncher = launcher;
+
+        mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController
+                .getTaskbarBackgroundAlpha();
+        MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha();
+        mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
+        mIconAlphaForHome.setConsumer(
+                (Consumer<Float>) alpha -> mLauncher.getHotseat().setIconsAlpha(alpha > 0 ? 0 : 1));
+
+        mIconAlignmentForResumedState.finishAnimation();
+        onIconAlignmentRatioChanged();
+
+        mLauncher.getStateManager().addStateListener(mStateListener);
+    }
+
+    public void onDestroy() {
+        mIconAlignmentForResumedState.finishAnimation();
+        mIconAlignmentForGestureState.finishAnimation();
+        mIconAlignmentForLauncherState.finishAnimation();
+
+        mIconAlphaForHome.setConsumer(null);
+        mLauncher.getHotseat().setIconsAlpha(1f);
+        mLauncher.getStateManager().removeStateListener(mStateListener);
+    }
+
+    public Animator createAnimToLauncher(@NonNull LauncherState toState,
+            @NonNull RecentsAnimationCallbacks callbacks, long duration) {
+        // If going to overview, stash the task bar
+        // If going home, align the icons to hotseat
+        AnimatorSet animatorSet = new AnimatorSet();
+
+        TaskbarStashController stashController = mControllers.taskbarStashController;
+        stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
+                toState.isTaskbarStashed());
+        stashController.updateStateForFlag(FLAG_IN_APP, false);
+
+        updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, true);
+        animatorSet.play(stashController.applyStateWithoutStart(duration));
+        animatorSet.play(applyState(duration, false));
+        animatorSet.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animator) {
+                mTargetStateOverride = null;
+                animator.removeListener(this);
+            }
+
+            @Override
+            public void onAnimationStart(Animator animator) {
+                mTargetStateOverride = toState;
+            }
+        });
+
+        TaskBarRecentsAnimationListener listener = new TaskBarRecentsAnimationListener(callbacks);
+        callbacks.addListener(listener);
+        RecentsView recentsView = mLauncher.getOverviewPanel();
+        recentsView.setTaskLaunchListener(() -> {
+            listener.endGestureStateOverride(true);
+            callbacks.removeListener(listener);
+        });
+        return animatorSet;
+    }
+
+    public boolean isAnimatingToLauncher() {
+        return mIsAnimatingToLauncherViaResume || mIsAnimatingToLauncherViaGesture;
+    }
+
+    /**
+     * Updates the proper flag to change the state of the task bar.
+     *
+     * Note that this only updates the flag. {@link #applyState()} needs to be called separately.
+     *
+     * @param flag The flag to update.
+     * @param enabled Whether to enable the flag
+     */
+    public void updateStateForFlag(int flag, boolean enabled) {
+        if (enabled) {
+            mState |= flag;
+        } else {
+            mState &= ~flag;
+        }
+    }
+
+    private boolean hasAnyFlag(int flagMask) {
+        return hasAnyFlag(mState, flagMask);
+    }
+
+    private boolean hasAnyFlag(int flags, int flagMask) {
+        return (flags & flagMask) != 0;
+    }
+
+    public void applyState() {
+        applyState(TASKBAR_STASH_DURATION);
+    }
+
+    public void applyState(long duration) {
+        applyState(duration, true);
+    }
+
+    public Animator applyState(boolean start) {
+        return applyState(TASKBAR_STASH_DURATION, start);
+    }
+
+    public Animator applyState(long duration, boolean start) {
+        Animator animator = null;
+        if (mPrevState != mState) {
+            int changedFlags = mPrevState ^ mState;
+            animator = onStateChangeApplied(changedFlags, duration, start);
+            mPrevState = mState;
+        }
+        return animator;
+    }
+
+    private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) {
+        AnimatorSet animatorSet = new AnimatorSet();
+        if (hasAnyFlag(changedFlags, FLAG_RESUMED)) {
+            boolean isResumed = isResumed();
+            ObjectAnimator anim = mIconAlignmentForResumedState
+                    .animateToValue(getCurrentIconAlignmentRatio(), isResumed ? 1 : 0)
+                    .setDuration(duration);
+
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mIsAnimatingToLauncherViaResume = false;
+                }
+
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mIsAnimatingToLauncherViaResume = isResumed;
+
+                    TaskbarStashController stashController = mControllers.taskbarStashController;
+                    stashController.updateStateForFlag(FLAG_IN_APP, !isResumed);
+                    stashController.applyState(duration);
+                }
+            });
+            animatorSet.play(anim);
+        }
+
+        if (hasAnyFlag(changedFlags, FLAG_RECENTS_ANIMATION_RUNNING)) {
+            boolean isRecentsAnimationRunning = isRecentsAnimationRunning();
+            Animator animator = mIconAlignmentForGestureState
+                    .animateToValue(isRecentsAnimationRunning ? 1 : 0);
+            if (isRecentsAnimationRunning) {
+                animator.setDuration(duration);
+            }
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mIsAnimatingToLauncherViaGesture = false;
+                }
+
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mIsAnimatingToLauncherViaGesture = isRecentsAnimationRunning();
+                }
+            });
+            animatorSet.play(animator);
+        }
+
+        if (hasAnyFlag(changedFlags, FLAG_TRANSITION_STATE_START_STASHED)) {
+            playStateTransitionAnim(isTransitionStateStartStashed(), animatorSet, duration,
+                    false /* committed */);
+        }
+
+        if (hasAnyFlag(changedFlags, FLAG_TRANSITION_STATE_COMMITTED_STASHED)) {
+            playStateTransitionAnim(isTransitionStateCommittedStashed(), animatorSet, duration,
+                    true /* committed */);
+        }
+
+        if (start) {
+            animatorSet.start();
+        }
+        return animatorSet;
+    }
+
+    private void playStateTransitionAnim(boolean isTransitionStateStashed,
+            AnimatorSet animatorSet, long duration, boolean committed) {
+        TaskbarStashController controller = mControllers.taskbarStashController;
+        controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
+                isTransitionStateStashed);
+        Animator stashAnimator = controller.applyStateWithoutStart(duration);
+        if (stashAnimator != null) {
+            stashAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (isTransitionStateStashed && committed) {
+                        // Reset hotseat alpha to default
+                        mLauncher.getHotseat().setIconsAlpha(1);
+                    }
+                    mTargetStateOverrideForStateTransition = null;
+                }
+
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mIconAlphaForHome.setValue(mLauncher.getHotseat().getIconsAlpha());
+                }
+            });
+            animatorSet.play(stashAnimator);
+            animatorSet.play(mIconAlignmentForLauncherState.animateToValue(
+                    getCurrentIconAlignmentRatioForLauncherState(),
+                    isTransitionStateStashed ? 0 : 1));
+        } else {
+            mTargetStateOverrideForStateTransition = null;
+        }
+    }
+
+    private boolean isResumed() {
+        return (mState & FLAG_RESUMED) != 0;
+    }
+
+    private boolean isRecentsAnimationRunning() {
+        return (mState & FLAG_RECENTS_ANIMATION_RUNNING) != 0;
+    }
+
+    private boolean isTransitionStateStartStashed() {
+        return (mState & FLAG_TRANSITION_STATE_START_STASHED) != 0;
+    }
+
+    private boolean isTransitionStateCommittedStashed() {
+        return (mState & FLAG_TRANSITION_STATE_COMMITTED_STASHED) != 0;
+    }
+
+    private void onIconAlignmentRatioChangedForStateTransition() {
+        onIconAlignmentRatioChanged(
+                mTargetStateOverrideForStateTransition != null
+                        ? mTargetStateOverrideForStateTransition
+                        : mLauncher.getStateManager().getState(),
+                this::getCurrentIconAlignmentRatioForLauncherState);
+    }
+
+    private void onIconAlignmentRatioChanged() {
+        onIconAlignmentRatioChanged(mTargetStateOverride != null ? mTargetStateOverride
+                : mLauncher.getStateManager().getState(), this::getCurrentIconAlignmentRatio);
+    }
+
+    private void onIconAlignmentRatioChanged(LauncherState state,
+            Supplier<Float> alignmentSupplier) {
+        if (mControllers == null) {
+            return;
+        }
+        float alignment = alignmentSupplier.get();
+        mControllers.taskbarViewController.setLauncherIconAlignment(
+                alignment, mLauncher.getDeviceProfile());
+
+        mTaskbarBackgroundAlpha.updateValue(1 - alignment);
+
+        setIconAlpha(state, alignment);
+    }
+
+    private float getCurrentIconAlignmentRatio() {
+        return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
+    }
+
+    private float getCurrentIconAlignmentRatioForLauncherState() {
+        return mIconAlignmentForLauncherState.value;
+    }
+
+    private void setIconAlpha(LauncherState state, float progress) {
+        if ((state.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+            // If the hotseat icons are visible, then switch taskbar in last frame
+            setTaskbarViewVisible(progress < 1);
+        } else {
+            mIconAlphaForHome.setValue(1 - progress);
+        }
+    }
+
+    private void setTaskbarViewVisible(boolean isVisible) {
+        mIconAlphaForHome.setValue(isVisible ? 1 : 0);
+    }
+
+    private final class TaskBarRecentsAnimationListener implements
+            RecentsAnimationCallbacks.RecentsAnimationListener {
+        private final RecentsAnimationCallbacks mCallbacks;
+
+        TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) {
+            mCallbacks = callbacks;
+        }
+
+        @Override
+        public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
+            endGestureStateOverride(true);
+        }
+
+        @Override
+        public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+            endGestureStateOverride(!controller.getFinishTargetIsLauncher());
+        }
+
+        private void endGestureStateOverride(boolean finishedToApp) {
+            mCallbacks.removeListener(this);
+            updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, false);
+            applyState();
+
+            TaskbarStashController controller = mControllers.taskbarStashController;
+            controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
+            controller.applyState();
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index eff59e2..95095fa 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -16,6 +16,7 @@
 package com.android.quickstep.fallback;
 
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
+import static com.android.quickstep.ViewUtils.postFrameDrawn;
 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
 import static com.android.quickstep.fallback.RecentsState.HOME;
 import static com.android.quickstep.fallback.RecentsState.MODAL_TASK;
@@ -218,8 +219,14 @@
 
     @Override
     public void onStateTransitionComplete(RecentsState finalState) {
-        setOverlayEnabled(finalState == DEFAULT || finalState == MODAL_TASK);
+        boolean isOverlayEnabled = finalState == DEFAULT || finalState == MODAL_TASK;
+        setOverlayEnabled(isOverlayEnabled);
         setFreezeViewVisibility(false);
+
+        if (isOverlayEnabled) {
+            postFrameDrawn(this, () -> runActionOnRemoteHandles(remoteTargetHandle ->
+                    remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true)));
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 30b55a8..df99d27 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -74,7 +74,7 @@
     }
 
     public void bind(Task primary, Task secondary, RecentsOrientedState orientedState,
-            StagedSplitBounds splitBoundsConfig) {
+            @Nullable StagedSplitBounds splitBoundsConfig) {
         super.bind(primary, orientedState);
         mSecondaryTask = secondary;
         mTaskIdContainer[1] = secondary.key.id;
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 5d6b656..3cba392 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
+import static com.android.quickstep.ViewUtils.postFrameDrawn;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -104,8 +105,14 @@
 
     @Override
     public void onStateTransitionComplete(LauncherState finalState) {
-        setOverlayEnabled(finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK);
+        boolean isOverlayEnabled = finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK;
+        setOverlayEnabled(isOverlayEnabled);
         setFreezeViewVisibility(false);
+
+        if (isOverlayEnabled) {
+            postFrameDrawn(this, () -> runActionOnRemoteHandles(remoteTargetHandle ->
+                    remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true)));
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 586693c..7b5a6b9 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -383,7 +383,9 @@
 
     protected final RecentsOrientedState mOrientationState;
     protected final BaseActivityInterface<STATE_TYPE, ACTIVITY_TYPE> mSizeStrategy;
+    @Nullable
     protected RecentsAnimationController mRecentsAnimationController;
+    @Nullable
     protected SurfaceTransactionApplier mSyncTransactionApplier;
     protected int mTaskWidth;
     protected int mTaskHeight;
@@ -394,12 +396,15 @@
     // mTaskGridVerticalDiff and mTopBottomRowHeightDiff summed together provides the top
     // position for bottom row of grid tasks.
 
+    @Nullable
     protected RemoteTargetHandle[] mRemoteTargetHandles;
     protected final Rect mLastComputedTaskSize = new Rect();
     protected final Rect mLastComputedGridSize = new Rect();
     protected final Rect mLastComputedGridTaskSize = new Rect();
     // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot.
+    @Nullable
     protected Float mLastComputedTaskStartPushOutDistance = null;
+    @Nullable
     protected Float mLastComputedTaskEndPushOutDistance = null;
     protected boolean mEnableDrawingLiveTile = false;
     protected final Rect mTempRect = new Rect();
@@ -454,11 +459,13 @@
     private final IntSet mTopRowIdSet = new IntSet();
 
     // The GestureEndTarget that is still in progress.
+    @Nullable
     protected GestureState.GestureEndTarget mCurrentGestureEndTarget;
 
     // TODO(b/187528071): Remove these and replace with a real scrim.
     private float mColorTint;
     private final int mTintingColor;
+    @Nullable
     private ObjectAnimator mTintingAnimator;
 
     private int mOverScrollShift = 0;
@@ -542,6 +549,7 @@
     private int mTaskViewIdCount;
     private final int[] INVALID_TASK_IDS = new int[]{-1, -1};
     protected boolean mRunningTaskTileHidden;
+    @Nullable
     private Task[] mTmpRunningTasks;
     protected int mFocusedTaskViewId = -1;
 
@@ -556,7 +564,9 @@
     private int mDownX;
     private int mDownY;
 
+    @Nullable
     private PendingAnimation mPendingAnimation;
+    @Nullable
     private LayoutTransition mLayoutTransition;
 
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -581,7 +591,9 @@
     private final Point mLastMeasureSize = new Point();
     private final int mEmptyMessagePadding;
     private boolean mShowEmptyMessage;
+    @Nullable
     private OnEmptyMessageUpdatedListener mOnEmptyMessageUpdatedListener;
+    @Nullable
     private Layout mEmptyTextLayout;
 
     /**
@@ -596,8 +608,11 @@
      * ensure this View doesn't go back into the {@link #mTaskViewPool},
      * see {@link #onViewRemoved(View)}
      */
+    @Nullable
     private TaskView mSplitHiddenTaskView;
+    @Nullable
     private TaskView mSecondSplitHiddenTaskView;
+    @Nullable
     private StagedSplitBounds mSplitBoundsConfig;
     private final Toast mSplitToast = Toast.makeText(getContext(),
             R.string.toast_split_select_app, Toast.LENGTH_SHORT);
@@ -613,12 +628,15 @@
      * removed from recentsView
      */
     private int mSplitHiddenTaskViewIndex;
+    @Nullable
     private FloatingTaskView mFirstFloatingTaskView;
+    @Nullable
     private FloatingTaskView mSecondFloatingTaskView;
 
     /**
      * The task to be removed and immediately re-added. Should not be added to task pool.
      */
+    @Nullable
     private TaskView mMovingTaskView;
 
     private OverviewActionsView mActionsView;
@@ -638,10 +656,12 @@
                 }
             };
 
+    @Nullable
     private RunnableList mSideTaskLaunchCallback;
+    @Nullable
     private TaskLaunchListener mTaskLaunchListener;
 
-    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr,
+    public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
             BaseActivityInterface sizeStrategy) {
         super(context, attrs, defStyleAttr);
         setEnableFreeScroll(true);
@@ -782,6 +802,7 @@
     }
 
     @Override
+    @Nullable
     public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
         if (mHandleTaskStackChanges) {
             TaskView taskView = getTaskViewByTaskId(taskId);
@@ -817,6 +838,7 @@
      * Update the thumbnail of the task.
      * @param refreshNow Refresh immediately if it's true.
      */
+    @Nullable
     public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow) {
         TaskView taskView = getTaskViewByTaskId(taskId);
         if (taskView != null) {
@@ -1017,10 +1039,12 @@
         }
     }
 
+    @Nullable
     private TaskView getLastGridTaskView() {
         return getLastGridTaskView(getTopRowIdArray(), getBottomRowIdArray());
     }
 
+    @Nullable
     private TaskView getLastGridTaskView(IntArray topRowIdArray, IntArray bottomRowIdArray) {
         if (topRowIdArray.isEmpty() && bottomRowIdArray.isEmpty()) {
             return null;
@@ -1070,6 +1094,10 @@
         return getScrollForPage(taskIndex) == getPagedOrientationHandler().getPrimaryScroll(this);
     }
 
+    /**
+     * Returns a {@link TaskView} that has taskId matching {@code taskId} or null if no match.
+     */
+    @Nullable
     public TaskView getTaskViewByTaskId(int taskId) {
         if (taskId == -1) {
             return null;
@@ -1309,6 +1337,7 @@
             return;
         }
 
+        mLoadPlanEverApplied = true;
         if (taskGroups == null || taskGroups.isEmpty()) {
             removeTasksViewsAndClearAllButton();
             onTaskStackUpdated();
@@ -1411,7 +1440,6 @@
         resetTaskVisuals();
         onTaskStackUpdated();
         updateEnabledOverlays();
-        mLoadPlanEverApplied = true;
     }
 
     private boolean isModal() {
@@ -1480,17 +1508,6 @@
             }
         }
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            // Since we reuse the same mLiveTileTaskViewSimulator in the RecentsView, we need
-            // to reset the params after it settles in Overview from swipe up so that we don't
-            // render with obsolete param values.
-            runActionOnRemoteHandles(remoteTargetHandle -> {
-                TaskViewSimulator simulator = remoteTargetHandle.getTaskViewSimulator();
-                simulator.taskPrimaryTranslation.value = 0;
-                simulator.taskSecondaryTranslation.value = 0;
-                simulator.fullScreenProgress.value = 0;
-                simulator.recentsViewScale.value = 1;
-            });
-
             // Similar to setRunningTaskHidden below, reapply the state before runningTaskView is
             // null.
             if (!mRunningTaskShowScreenshot) {
@@ -1878,7 +1895,7 @@
         setEnableDrawingLiveTile(false);
         runActionOnRemoteHandles(remoteTargetHandle -> {
             remoteTargetHandle.getTransformParams().setTargetSet(null);
-            remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true);
+            remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false);
         });
         mSplitSelectStateController.resetState();
 
@@ -1920,6 +1937,7 @@
         return getTaskViewFromTaskViewId(mFocusedTaskViewId);
     }
 
+    @Nullable
     private TaskView getTaskViewFromTaskViewId(int taskViewId) {
         if (taskViewId == -1) {
             return null;
@@ -2987,6 +3005,10 @@
 
             @SuppressWarnings("WrongCall")
             private void onEnd(boolean success) {
+                // Reset task translations as they may have updated via animations in
+                // createTaskDismissAnimation
+                resetTaskVisuals();
+
                 if (success) {
                     if (shouldRemoveTask) {
                         if (dismissedTaskView.getTask() != null) {
@@ -3003,10 +3025,6 @@
                         }
                     }
 
-                    // Reset task translations as they may have updated via animations in
-                    // createTaskDismissAnimation
-                    resetTaskVisuals();
-
                     int pageToSnapTo = mCurrentPage;
                     mCurrentPageScrollDiff = 0;
                     int taskViewIdToSnapTo = -1;
@@ -3088,12 +3106,17 @@
                     } else if (dismissedIndex < pageToSnapTo || pageToSnapTo == taskCount - 1) {
                         pageToSnapTo--;
                     }
+                    boolean isHomeTaskDismissed = dismissedTaskView == getHomeTaskView();
                     removeViewInLayout(dismissedTaskView);
                     mTopRowIdSet.remove(dismissedTaskViewId);
 
                     if (taskCount == 1) {
                         removeViewInLayout(mClearAllButton);
-                        startHome();
+                        if (isHomeTaskDismissed) {
+                            updateEmptyMessage();
+                        } else {
+                            startHome();
+                        }
                     } else {
                         // Update focus task and its size.
                         if (finalIsFocusedTaskDismissed) {
@@ -4355,7 +4378,6 @@
             TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator();
             tvs.setOrientationState(mOrientationState);
             tvs.setDp(mActivity.getDeviceProfile());
-            tvs.setDrawsBelowRecents(true);
             tvs.recentsViewScale.value = 1;
         }
     }
@@ -4371,12 +4393,15 @@
         }
     }
 
-    public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
+    /**
+     * Finish recents animation.
+     */
+    public void finishRecentsAnimation(boolean toRecents, @Nullable Runnable onFinishComplete) {
         finishRecentsAnimation(toRecents, true /* shouldPip */, onFinishComplete);
     }
 
     public void finishRecentsAnimation(boolean toRecents, boolean shouldPip,
-            Runnable onFinishComplete) {
+            @Nullable Runnable onFinishComplete) {
         // TODO(b/197232424#comment#10) Move this back into onRecentsAnimationComplete(). Maybe?
         cleanupRemoteTargets();
         if (!toRecents && ENABLE_QUICKSTEP_LIVE_TILE.get()) {
@@ -4925,10 +4950,13 @@
 
     private static class PinnedStackAnimationListener<T extends BaseActivity> extends
             IPipAnimationListener.Stub {
+        @Nullable
         private T mActivity;
+        @Nullable
         private RecentsView mRecentsView;
 
-        public void setActivityAndRecentsView(T activity, RecentsView recentsView) {
+        public void setActivityAndRecentsView(@Nullable T activity,
+                @Nullable RecentsView recentsView) {
             mActivity = activity;
             mRecentsView = recentsView;
         }
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index c960f88..f8368ae 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -459,15 +459,36 @@
                 float availableHeight = surfaceHeight
                         - (thumbnailClipHint.top + thumbnailClipHint.bottom);
 
-                if (isRotated) {
-                    float canvasAspect = canvasWidth / (float) canvasHeight;
-                    float availableAspect = availableHeight / availableWidth;
+                float canvasAspect = canvasWidth / (float) canvasHeight;
+                float availableAspect = isRotated
+                        ? availableHeight / availableWidth
+                        : availableWidth / availableHeight;
+                boolean isAspectLargelyDifferent = Utilities.isRelativePercentDifferenceGreaterThan(
+                        canvasAspect, availableAspect, 0.1f);
+                if (isRotated && isAspectLargelyDifferent) {
                     // Do not rotate thumbnail if it would not improve fit
-                    if (Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect,
-                            availableAspect, 0.1f)) {
-                        isRotated = false;
-                        isOrientationDifferent = false;
+                    isRotated = false;
+                    isOrientationDifferent = false;
+                }
+
+                if (isAspectLargelyDifferent) {
+                    // Crop letterbox insets if insets isn't already clipped
+                    if (!TaskView.clipLeft(dp)) {
+                        thumbnailClipHint.left = thumbnailData.letterboxInsets.left;
                     }
+                    if (!TaskView.clipRight(dp)) {
+                        thumbnailClipHint.right = thumbnailData.letterboxInsets.right;
+                    }
+                    if (!TaskView.clipTop(dp)) {
+                        thumbnailClipHint.top = thumbnailData.letterboxInsets.top;
+                    }
+                    if (!TaskView.clipBottom(dp)) {
+                        thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom;
+                    }
+                    availableWidth = surfaceWidth
+                            - (thumbnailClipHint.left + thumbnailClipHint.right);
+                    availableHeight = surfaceHeight
+                            - (thumbnailClipHint.top + thumbnailClipHint.bottom);
                 }
 
                 final float targetW, targetH;
@@ -478,30 +499,25 @@
                     targetW = canvasWidth;
                     targetH = canvasHeight;
                 }
-                float canvasAspect = targetW / targetH;
+                float targetAspect = targetW / targetH;
 
                 // Update the clipHint such that
                 //   > the final clipped position has same aspect ratio as requested by canvas
-                //   > the clipped region is within the task insets if possible
-                //   > the clipped region is not scaled up when drawing. If that is not possible
-                //     while staying within the taskInsets, move outside the insets.
+                //   > first fit the width and crop the extra height
+                //   > if that will leave empty space, fit the height and crop the width instead
                 float croppedWidth = availableWidth;
-                if (croppedWidth < targetW) {
-                    croppedWidth = Math.min(targetW, surfaceWidth);
-                }
-
-                float croppedHeight = croppedWidth / canvasAspect;
+                float croppedHeight = croppedWidth / targetAspect;
                 if (croppedHeight > availableHeight) {
                     croppedHeight = availableHeight;
                     if (croppedHeight < targetH) {
                         croppedHeight = Math.min(targetH, surfaceHeight);
                     }
-                    croppedWidth = croppedHeight * canvasAspect;
+                    croppedWidth = croppedHeight * targetAspect;
 
                     // One last check in case the task aspect radio messed up something
                     if (croppedWidth > surfaceWidth) {
                         croppedWidth = surfaceWidth;
-                        croppedHeight = croppedWidth / canvasAspect;
+                        croppedHeight = croppedWidth / targetAspect;
                     }
                 }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 3da7893..e9a3779 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -648,21 +648,7 @@
                     recentsView.getDepthController());
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
-                public void onAnimationStart(Animator animator) {
-                    recentsView.runActionOnRemoteHandles(
-                            (Consumer<RemoteTargetHandle>) remoteTargetHandle ->
-                                    remoteTargetHandle
-                                            .getTaskViewSimulator()
-                                            .setDrawsBelowRecents(false));
-                }
-
-                @Override
                 public void onAnimationEnd(Animator animator) {
-                    recentsView.runActionOnRemoteHandles(
-                            (Consumer<RemoteTargetHandle>) remoteTargetHandle ->
-                                    remoteTargetHandle
-                                            .getTaskViewSimulator()
-                                            .setDrawsBelowRecents(true));
                     mIsClickableAsLiveTile = true;
                 }
             });
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 5c16b4c..e64ea90 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -251,8 +251,7 @@
         // Tablet UI does not support emulated landscape.
         isTablet = allowRotation && info.isTablet(windowBounds);
         isPhone = !isTablet;
-        isTwoPanels = isTablet && useTwoPanels
-                && (isLandscape || FeatureFlags.ENABLE_TWO_PANEL_HOME_IN_PORTRAIT.get());
+        isTwoPanels = isTablet && useTwoPanels;
 
         aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
         boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 9f3d445..68e19cb 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -383,7 +383,7 @@
             case LauncherSettings.Settings.METHOD_NEW_SCREEN_ID: {
                 Bundle result = new Bundle();
                 result.putInt(LauncherSettings.Settings.EXTRA_VALUE,
-                        mOpenHelper.generateNewScreenId());
+                        mOpenHelper.getNewScreenId());
                 return result;
             }
             case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: {
@@ -628,7 +628,6 @@
         private final Context mContext;
         private final boolean mForMigration;
         private int mMaxItemId = -1;
-        private int mMaxScreenId = -1;
         private boolean mBackupTableExists;
         private boolean mHotseatRestoreTableExists;
 
@@ -672,9 +671,6 @@
             if (mMaxItemId == -1) {
                 mMaxItemId = initializeMaxItemId(getWritableDatabase());
             }
-            if (mMaxScreenId == -1) {
-                mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
-            }
         }
 
         @Override
@@ -682,7 +678,6 @@
             if (LOGD) Log.d(TAG, "creating new launcher database");
 
             mMaxItemId = 1;
-            mMaxScreenId = 0;
 
             addFavoritesTable(db, false);
 
@@ -1043,36 +1038,19 @@
         public void checkId(ContentValues values) {
             int id = values.getAsInteger(Favorites._ID);
             mMaxItemId = Math.max(id, mMaxItemId);
-
-            Integer screen = values.getAsInteger(Favorites.SCREEN);
-            Integer container = values.getAsInteger(Favorites.CONTAINER);
-            if (screen != null && container != null
-                    && container.intValue() == Favorites.CONTAINER_DESKTOP) {
-                mMaxScreenId = Math.max(screen, mMaxScreenId);
-            }
         }
 
         private int initializeMaxItemId(SQLiteDatabase db) {
             return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s", Favorites._ID, Favorites.TABLE_NAME);
         }
 
-        // Generates a new ID to use for an workspace screen in your database. This method
-        // should be only called from the main UI thread. As an exception, we do call it when we
-        // call the constructor from the worker thread; however, this doesn't extend until after the
-        // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
-        // after that point
-        public int generateNewScreenId() {
-            if (mMaxScreenId < 0) {
-                throw new RuntimeException("Error: max screen id was not initialized");
-            }
-            mMaxScreenId += 1;
-            return mMaxScreenId;
-        }
-
-        private int initializeMaxScreenId(SQLiteDatabase db) {
-            return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d AND %1$s >= 0",
+        // Returns a new ID to use for an workspace screen in your database that is greater than all
+        // existing screen IDs.
+        private int getNewScreenId() {
+            return getMaxId(getWritableDatabase(),
+                    "SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d AND %1$s >= 0",
                     Favorites.SCREEN, Favorites.TABLE_NAME, Favorites.CONTAINER,
-                    Favorites.CONTAINER_DESKTOP);
+                    Favorites.CONTAINER_DESKTOP) + 1;
         }
 
         @Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
@@ -1081,7 +1059,6 @@
 
             // Ensure that the max ids are initialized
             mMaxItemId = initializeMaxItemId(db);
-            mMaxScreenId = initializeMaxScreenId(db);
             return count;
         }
     }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 523ac72..cefadf7 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1721,6 +1721,10 @@
 
     @Override
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        if (mScroller.isFinished()) {
+            // This was not caused by the scroller, skip it.
+            return;
+        }
         int newDestinationPage = getDestinationPage();
         if (newDestinationPage >= 0 && newDestinationPage != mCurrentScrollOverPage) {
             mCurrentScrollOverPage = newDestinationPage;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index ce06c6e..131fbfb 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -869,13 +869,13 @@
         mWorkspaceScreens.remove(emptyScreenId);
         mScreenOrder.removeValue(emptyScreenId);
 
-        int newScreenId = -1;
+        int newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(),
+                LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
         // Launcher database isn't aware of empty pages that are already bound, so we need to
         // skip those IDs manually.
-        while (newScreenId == -1 || mWorkspaceScreens.containsKey(newScreenId)) {
-            newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(),
-                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
-                    .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+        while (mWorkspaceScreens.containsKey(newScreenId)) {
+            newScreenId++;
         }
 
         mWorkspaceScreens.put(newScreenId, cl);
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index e253505..c4d8166 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -210,10 +210,6 @@
             "ENABLE_TWO_PANEL_HOME", true,
             "Uses two panel on home screen. Only applicable on large screen devices.");
 
-    public static final BooleanFlag ENABLE_TWO_PANEL_HOME_IN_PORTRAIT = getDebugFlag(
-            "ENABLE_TWO_PANEL_HOME_IN_PORTRAIT", true,
-            "Uses two panel on home screen in portrait if ENABLE_TWO_PANEL_HOME is enabled.");
-
     public static final BooleanFlag ENABLE_SCRIM_FOR_APP_LAUNCH = getDebugFlag(
             "ENABLE_SCRIM_FOR_APP_LAUNCH", false,
             "Enables scrim during app launch animation.");
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index fea15c4..a13fa55 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.model;
 
 import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
-import static com.android.launcher3.WorkspaceLayoutManager.SECOND_SCREEN_ID;
 
 import android.content.Intent;
 import android.content.pm.LauncherActivityInfo;
@@ -300,11 +299,6 @@
         IntSet screensToExclude = new IntSet();
         if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
             screensToExclude.add(FIRST_SCREEN_ID);
-
-            // On split display we don't want to add the new items onto the second screen.
-            if (app.getInvariantDeviceProfile().isSplitDisplay) {
-                screensToExclude.add(SECOND_SCREEN_ID);
-            }
         }
 
         for (int screen = 0; screen < screenCount; screen++) {
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 0439e75..94e06d1 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -292,7 +292,7 @@
         FileLog.d(TAG, "removing items from db " + items.stream().map(
                 (item) -> item.getTargetComponent() == null ? ""
                         : item.getTargetComponent().getPackageName()).collect(
-                Collectors.joining(",")), new Exception());
+                Collectors.joining(",")));
         notifyDelete(items);
         enqueueDeleteRunnable(() -> {
             for (ItemInfo item : items) {
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 8caf886..ba9d09c 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -451,24 +451,19 @@
     public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
             StagedSplitBounds splitInfo, int desiredStagePosition) {
         boolean isLandscape = dp.isLandscape;
-        float verticalDividerDiff = splitInfo.visualDividerBounds.height() / 2f;
-        float horizontalDividerDiff = splitInfo.visualDividerBounds.width() / 2f;
-        float diff;
         if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
             if (isLandscape) {
-                diff = outRect.width() * (1f - splitInfo.leftTaskPercent) + horizontalDividerDiff;
-                outRect.right -= diff;
+                outRect.right = outRect.left + (int) (outRect.width() * splitInfo.leftTaskPercent);
             } else {
-                diff = outRect.height() * (1f - splitInfo.topTaskPercent) + verticalDividerDiff;
-                outRect.bottom -= diff;
+                outRect.bottom = outRect.top + (int) (outRect.height() * splitInfo.topTaskPercent);
             }
         } else {
             if (isLandscape) {
-                diff = outRect.width() * splitInfo.leftTaskPercent + horizontalDividerDiff;
-                outRect.left += diff;
+                outRect.left += (int) (outRect.width() *
+                        (splitInfo.leftTaskPercent + splitInfo.dividerWidthPercent));
             } else {
-                diff = outRect.height() * splitInfo.topTaskPercent + verticalDividerDiff;
-                outRect.top += diff;
+                outRect.top += (int) (outRect.height() *
+                        (splitInfo.topTaskPercent + splitInfo.dividerHeightPercent));
             }
         }
     }
@@ -479,9 +474,9 @@
             StagedSplitBounds splitBoundsConfig, DeviceProfile dp) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
-        int dividerBar = (splitBoundsConfig.appsStackedVertically ?
-                splitBoundsConfig.visualDividerBounds.height() :
-                splitBoundsConfig.visualDividerBounds.width());
+        int dividerBar = splitBoundsConfig.appsStackedVertically
+                ? (int) (splitBoundsConfig.dividerHeightPercent * parentHeight)
+                : (int) (splitBoundsConfig.dividerWidthPercent * parentWidth);
         int primarySnapshotHeight;
         int primarySnapshotWidth;
         int secondarySnapshotHeight;
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index bd39391..326141d 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -24,6 +24,7 @@
 import com.android.launcher3.anim.AlphaUpdateListener;
 
 import java.util.Arrays;
+import java.util.function.Consumer;
 
 /**
  * Utility class to handle separating a single value as a factor of multiple values
@@ -85,6 +86,8 @@
         // Factor of all other alpha channels, only valid if mMyMask is present in mValidMask.
         private float mOthers = 1;
 
+        private Consumer<Float> mConsumer;
+
         AlphaProperty(int myMask) {
             mMyMask = myMask;
         }
@@ -109,16 +112,24 @@
             mValidMask = mMyMask;
             mValue = value;
 
-            mView.setAlpha(mOthers * mValue);
+            final float alpha = mOthers * mValue;
+            mView.setAlpha(alpha);
             if (mUpdateVisibility) {
                 AlphaUpdateListener.updateVisibility(mView);
             }
+            if (mConsumer != null) {
+                mConsumer.accept(mValue);
+            }
         }
 
         public float getValue() {
             return mValue;
         }
 
+        public void setConsumer(Consumer<Float> consumer) {
+            mConsumer = consumer;
+        }
+
         @Override
         public String toString() {
             return Float.toString(mValue);
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 6aef38f..53b1c3e 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -99,6 +99,8 @@
         // This class is orientation-agnostic, so we compute both for later use
         public final float topTaskPercent;
         public final float leftTaskPercent;
+        public final float dividerWidthPercent;
+        public final float dividerHeightPercent;
         /**
          * If {@code true}, that means at the time of creation of this object, the
          * split-screened apps were vertically stacked. This is useful in scenarios like
@@ -130,6 +132,8 @@
 
             leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right;
             topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
+            dividerWidthPercent = visualDividerBounds.width() / (float) rightBottomBounds.right;
+            dividerHeightPercent = visualDividerBounds.height() / (float) rightBottomBounds.bottom;
         }
     }
 
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
index 16f024e..8a4590a 100644
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -86,8 +86,6 @@
 
     @Test
     public void testFindSpaceForItem_prefers_second() throws Exception {
-        mIdp.isSplitDisplay = false;
-
         // First screen has only one hole of size 1
         int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
 
@@ -109,24 +107,6 @@
     }
 
     @Test
-    public void testFindSpaceForItem_prefers_third_on_split_display() throws Exception {
-        mIdp.isSplitDisplay = true;
-        // First screen has only one hole of size 1
-        int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
-
-        // Second screen has 2 holes of sizes 3x2 and 2x3
-        setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
-
-        int[] spaceFound = newTask().findSpaceForItem(
-                mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1);
-        // For split display, it picks the next screen, even if there is enough space
-        // on previous screen
-        assertEquals(2, spaceFound[0]);
-        assertTrue(mScreenOccupancy.get(spaceFound[0])
-                .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1));
-    }
-
-    @Test
     public void testFindSpaceForItem_adds_new_screen() throws Exception {
         // First screen has 2 holes of sizes 3x2 and 2x3
         setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 36af8f0..194ee4f 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -28,7 +28,6 @@
 import com.android.launcher3.tapl.WidgetResizeFrame;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 
@@ -48,7 +47,6 @@
 
     @Test
     @PortraitLandscape
-    @ScreenRecord // b/204807156
     public void testDragIcon() throws Throwable {
         clearHomescreen();
         mDevice.pressHome();
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 758c426..fa39ce0 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -45,7 +45,6 @@
 import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.WidgetManagerHelper;
@@ -139,7 +138,6 @@
     }
 
     @Test
-    @ScreenRecord // b/204807156
     public void testPendingWidget_autoRestored() {
         // A non-restored widget with no config screen gets restored automatically.
         LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);