Merge changes I37a9dd3e,I6a386626 into ub-launcher3-master

* changes:
  Don't start task from quickswitch until scrolling finishes
  Fix flicker near end of quick switch
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index ea5561b..da73bc0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -166,6 +166,7 @@
         super.onActivityInit(alreadyOnHome);
         mActivity = mActivityInterface.getCreatedActivity();
         mRecentsView = mActivity.getOverviewPanel();
+        mRecentsView.setOnPageTransitionEndCallback(null);
         linkRecentsViewScroll();
         mRecentsView.setDisallowScrollToClearAll(true);
         mRecentsView.getClearAllButton().setVisibilityAlpha(0);
@@ -434,7 +435,12 @@
 
                 @Override
                 public void onAnimationSuccess(Animator animator) {
-                    finishAnimationTargetSetAnimationComplete();
+                    if (mRecentsView != null) {
+                        mRecentsView.setOnPageTransitionEndCallback(FallbackSwipeHandler.this
+                                ::finishAnimationTargetSetAnimationComplete);
+                    } else {
+                        finishAnimationTargetSetAnimationComplete();
+                    }
                     mFinishAnimation = null;
                 }
             };
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index 5bac844..804bc95 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -29,6 +29,7 @@
 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
+import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
@@ -244,7 +245,11 @@
                         | STATE_GESTURE_STARTED,
                 this::setupLauncherUiAfterSwipeUpToRecentsAnimation);
 
-        mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED, this::onEndTargetSet);
+        mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED,
+                this::continueComputingRecentsScrollIfNecessary);
+        mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED
+                        | STATE_RECENTS_SCROLLING_FINISHED,
+                this::onSettledOnEndTarget);
 
         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
@@ -283,6 +288,7 @@
         }
 
         mRecentsView = activity.getOverviewPanel();
+        mRecentsView.setOnPageTransitionEndCallback(null);
         linkRecentsViewScroll();
         addLiveTileOverlay();
 
@@ -505,16 +511,22 @@
     }
 
     private void buildAnimationController() {
-        if (mGestureState.getEndTarget() == HOME || mHasLauncherTransitionControllerStarted) {
-            // We don't want a new mLauncherTransitionController if
-            // mGestureState.getEndTarget() == HOME (it has its own animation) or if we're already
-            // animating the current controller.
+        if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
             return;
         }
         initTransitionEndpoints(mActivity.getDeviceProfile());
         mAnimationFactory.createActivityInterface(mTransitionDragLength);
     }
 
+    /**
+     * We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME
+     * (it has its own animation) or if we're already animating the current controller.
+     * @return Whether we can create the launcher controller or update its progress.
+     */
+    private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
+        return mGestureState.getEndTarget() != HOME && !mHasLauncherTransitionControllerStarted;
+    }
+
     @Override
     public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
         WindowInsets result = view.onApplyWindowInsets(windowInsets);
@@ -558,15 +570,12 @@
             }
         }
 
-        if (mLauncherTransitionController == null || mLauncherTransitionController
-                .getAnimationPlayer().isStarted()) {
-            return;
-        }
         updateLauncherTransitionProgress();
     }
 
     private void updateLauncherTransitionProgress() {
-        if (mGestureState.getEndTarget() == HOME) {
+        if (mLauncherTransitionController == null
+                || !canCreateNewOrUpdateExistingLauncherTransitionController()) {
             return;
         }
         // Normalize the progress to 0 to 1, as the animation controller will clamp it to that
@@ -696,7 +705,7 @@
         }
     }
 
-    private void onEndTargetSet() {
+    private void onSettledOnEndTarget() {
         switch (mGestureState.getEndTarget()) {
             case HOME:
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
@@ -859,13 +868,17 @@
             if (mDeviceState.isFullyGesturalNavMode()) {
                 setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration);
             }
-        } else if (endTarget == NEW_TASK || endTarget == LAST_TASK) {
-            // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
-            // or resumeLastTask().
-            if (mRecentsView != null) {
-                duration = Math.max(duration, mRecentsView.getScroller().getDuration());
-            }
         }
+
+        // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
+        // or resumeLastTask().
+        if (mRecentsView != null) {
+            mRecentsView.setOnPageTransitionEndCallback(
+                    () -> mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED));
+        } else {
+            mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
+        }
+
         animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
     }
 
@@ -949,11 +962,7 @@
             ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
             windowAnim.setDuration(duration).setInterpolator(interpolator);
             windowAnim.addUpdateListener(valueAnimator -> {
-                if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
-                    // Views typically don't compute scroll when invisible as an optimization,
-                    // but in our case we need to since the window offset depends on the scroll.
-                    mRecentsView.computeScroll();
-                }
+                computeRecentsScrollIfInvisible();
             });
             windowAnim.addListener(new AnimationSuccessListener() {
                 @Override
@@ -1005,6 +1014,21 @@
         mHasLauncherTransitionControllerStarted = true;
     }
 
+    private void computeRecentsScrollIfInvisible() {
+        if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
+            // Views typically don't compute scroll when invisible as an optimization,
+            // but in our case we need to since the window offset depends on the scroll.
+            mRecentsView.computeScroll();
+        }
+    }
+
+    private void continueComputingRecentsScrollIfNecessary() {
+        if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)) {
+            computeRecentsScrollIfInvisible();
+            mRecentsView.post(this::continueComputingRecentsScrollIfNecessary);
+        }
+    }
+
     /**
      * Creates an animation that transforms the current app window into the home app.
      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index f7e40ca..631df4c 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -105,6 +105,10 @@
     public static final int STATE_RECENTS_ANIMATION_ENDED =
             getFlagForIndex("STATE_RECENTS_ANIMATION_ENDED");
 
+    // Called when RecentsView stops scrolling and settles on a TaskView.
+    public static final int STATE_RECENTS_SCROLLING_FINISHED =
+            getFlagForIndex("STATE_RECENTS_SCROLLING_FINISHED");
+
 
     // Needed to interact with the current activity
     private final Intent mHomeIntent;
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index e38631d..7d7739e 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -50,6 +50,8 @@
 import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY;
 import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -133,6 +135,7 @@
     protected int mActivePointerId = INVALID_POINTER;
 
     protected boolean mIsPageInTransition = false;
+    private Runnable mOnPageTransitionEndCallback;
 
     protected float mSpringOverScroll;
 
@@ -391,6 +394,22 @@
         AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
         AccessibilityManagerCompat.sendCustomAccessibilityEvent(getPageAt(mCurrentPage),
                 AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
+        if (mOnPageTransitionEndCallback != null) {
+            mOnPageTransitionEndCallback.run();
+            mOnPageTransitionEndCallback = null;
+        }
+    }
+
+    /**
+     * Sets a callback to run once when the scrolling finishes. If there is currently
+     * no page in transition, then the callback is called immediately.
+     */
+    public void setOnPageTransitionEndCallback(@Nullable Runnable callback) {
+        if (mIsPageInTransition || callback == null) {
+            mOnPageTransitionEndCallback = callback;
+        } else {
+            callback.run();
+        }
     }
 
     protected int getUnboundedScroll() {
diff --git a/src/com/android/launcher3/util/OverScroller.java b/src/com/android/launcher3/util/OverScroller.java
index 3c398b8..34efb12 100644
--- a/src/com/android/launcher3/util/OverScroller.java
+++ b/src/com/android/launcher3/util/OverScroller.java
@@ -165,6 +165,9 @@
     /**
      * Returns how long the scroll event will take, in milliseconds.
      *
+     * Note that if mScroller.mState == SPRING, this duration is ignored, so can only
+     * serve as an estimate for how long the spring-controlled scroll will take.
+     *
      * @return The duration of the scroll in milliseconds.
      */
     public final int getDuration() {