Defer launching the quickscrub task until after the page settles

- Tweak the durations to be less laggy
- Add some state logging

Bug: 67957962
Bug: 70180755
Change-Id: Ia0caa5178b3ed976705eb2d973bc00d8f1b9e3ca
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index 61684e0..7f9d3a1 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -32,6 +32,7 @@
 
     private static final int NUM_QUICK_SCRUB_SECTIONS = 5;
     private static final long AUTO_ADVANCE_DELAY = 500;
+    private static final int QUICKSCRUB_SNAP_DURATION_PER_PAGE = 325;
     private static final int QUICKSCRUB_END_SNAP_DURATION_PER_PAGE = 60;
 
     private Launcher mLauncher;
@@ -58,18 +59,22 @@
         if (mRecentsView == null) {
         } else {
             int page = mRecentsView.getNextPage();
-            // Settle on the page then launch it.
-            int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen())
-                    * QUICKSCRUB_END_SNAP_DURATION_PER_PAGE;
-            mRecentsView.snapToPage(page, snapDuration);
-            // TODO: Fix this to actually wait until page-settle
-            mRecentsView.postDelayed(() -> {
+            Runnable launchTaskRunnable = () -> {
                 if (page < mRecentsView.getFirstTaskIndex()) {
                     mRecentsView.getPageAt(page).performClick();
                 } else {
                     ((TaskView) mRecentsView.getPageAt(page)).launchTask(true);
                 }
-            }, snapDuration);
+            };
+            int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen())
+                    * QUICKSCRUB_END_SNAP_DURATION_PER_PAGE;
+            if (mRecentsView.snapToPage(page, snapDuration)) {
+                // Settle on the page then launch it
+                mRecentsView.setNextPageSwitchRunnable(launchTaskRunnable);
+            } else {
+                // No page move needed, just launch it
+                launchTaskRunnable.run();
+            }
         }
     }
 
@@ -93,7 +98,9 @@
 
     private void goToPageWithHaptic(int pageToGoTo) {
         if (pageToGoTo != mRecentsView.getNextPage()) {
-            mRecentsView.snapToPage(pageToGoTo);
+            int duration = Math.abs(pageToGoTo - mRecentsView.getNextPage())
+                    * QUICKSCRUB_SNAP_DURATION_PER_PAGE;
+            mRecentsView.snapToPage(pageToGoTo, duration);
             mRecentsView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP,
                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
         }
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index 26fe54e..6b1f3d3 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -75,6 +75,7 @@
     private boolean mOverviewStateEnabled;
     private boolean mTaskStackListenerRegistered;
     private LayoutTransition mLayoutTransition;
+    private Runnable mNextPageSwitchRunnable;
 
     /**
      * TODO: Call reloadIdNeeded in onTaskStackChanged.
@@ -243,6 +244,19 @@
         updateTaskStackListenerState();
     }
 
+    public void setNextPageSwitchRunnable(Runnable r) {
+        mNextPageSwitchRunnable = r;
+    }
+
+    @Override
+    protected void onPageEndTransition() {
+        super.onPageEndTransition();
+        if (mNextPageSwitchRunnable != null) {
+            mNextPageSwitchRunnable.run();
+            mNextPageSwitchRunnable = null;
+        }
+    }
+
     private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
         final RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
         TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 17f0482..dd0892b 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -41,6 +41,7 @@
 import android.os.Looper;
 import android.support.annotation.UiThread;
 import android.support.annotation.WorkerThread;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewTreeObserver.OnDrawListener;
 
@@ -65,8 +66,12 @@
 import com.android.systemui.shared.system.TransactionCompat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
+import java.util.StringJoiner;
+
 @TargetApi(Build.VERSION_CODES.O)
 public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
+    private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
+    private static final boolean DEBUG_STATES = false;
 
     // Launcher UI related states
     private static final int STATE_LAUNCHER_PRESENT = 1 << 0;
@@ -86,8 +91,21 @@
     private static final int LAUNCHER_UI_STATES =
             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_ACTIVITY_MULTIPLIER_COMPLETE;
 
+    // For debugging, keep in sync with above states
+    private static final String[] STATES = new String[] {
+            "STATE_LAUNCHER_PRESENT",
+            "STATE_LAUNCHER_DRAWN",
+            "STATE_ACTIVITY_MULTIPLIER_COMPLETE",
+            "STATE_APP_CONTROLLER_RECEIVED",
+            "STATE_SCALED_CONTROLLER_RECENTS",
+            "STATE_SCALED_CONTROLLER_APP",
+            "STATE_HANDLER_INVALIDATED",
+            "STATE_GESTURE_STARTED"
+    };
+
     private static final long MAX_SWIPE_DURATION = 200;
     private static final long MIN_SWIPE_DURATION = 80;
+    private static final int QUICK_SWITCH_START_DURATION = 133;
     private static final int QUICK_SWITCH_SNAP_DURATION = 120;
 
     private static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
@@ -154,7 +172,13 @@
     }
 
     private void initStateCallbacks() {
-        mStateCallback = new MultiStateCallback();
+        mStateCallback = new MultiStateCallback() {
+            @Override
+            public void setState(int stateFlag) {
+                debugNewState(stateFlag);
+                super.setState(stateFlag);
+            }
+        };
         mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
                 this::initializeLauncherAnimationController);
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
@@ -356,7 +380,7 @@
         mDeferredQuickScrubEnd = false;
         mQuickScrubController = mRecentsView.getQuickScrubController();
         mQuickScrubController.onQuickScrubStart(mStartedQuickScrubFromHome);
-        animateToProgress(1f, MAX_SWIPE_DURATION);
+        animateToProgress(1f, QUICK_SWITCH_START_DURATION);
         if (mStartedQuickScrubFromHome) {
             mLauncherLayoutListener.setVisibility(View.INVISIBLE);
         }
@@ -599,9 +623,14 @@
             for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
                 TaskView taskView = (TaskView) mRecentsView.getPageAt(i);
                 if (taskView.getTask().key.id != mRunningTaskId) {
-                    mRecentsView.snapToPage(i, QUICK_SWITCH_SNAP_DURATION);
-                    taskView.postDelayed(() -> {taskView.launchTask(true);},
-                            QUICK_SWITCH_SNAP_DURATION);
+                    Runnable launchTaskRunnable = () -> taskView.launchTask(true);
+                    if (mRecentsView.snapToPage(i, QUICK_SWITCH_SNAP_DURATION)) {
+                        // Snap to the new page then launch it
+                        mRecentsView.setNextPageSwitchRunnable(launchTaskRunnable);
+                    } else {
+                        // No need to move page, just launch task directly
+                        launchTaskRunnable.run();
+                    }
                     break;
                 }
             }
@@ -655,4 +684,24 @@
             // TODO:
         }
     }
+
+    private synchronized void debugNewState(int stateFlag) {
+        if (!DEBUG_STATES) {
+            return;
+        }
+
+        int state = mStateCallback.getState();
+        StringJoiner currentStateStr = new StringJoiner(", ", "[", "]");
+        String stateFlagStr = "Unknown-" + stateFlag;
+        for (int i = 0; i < STATES.length; i++) {
+            if ((state & (i << i)) != 0) {
+                currentStateStr.add(STATES[i]);
+            }
+            if (stateFlag == (1 << i)) {
+                stateFlagStr = STATES[i] + " (" + stateFlag + ")";
+            }
+        }
+        Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding " + stateFlagStr + " to "
+                + currentStateStr);
+    }
 }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 0ebae81..bb137b0 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1622,7 +1622,7 @@
         return (float) Math.sin(f);
     }
 
-    protected void snapToPageWithVelocity(int whichPage, int velocity) {
+    protected boolean snapToPageWithVelocity(int whichPage, int velocity) {
         whichPage = validateNewPage(whichPage);
         int halfScreenSize = getMeasuredWidth() / 2;
 
@@ -1633,8 +1633,7 @@
         if (Math.abs(velocity) < mMinFlingVelocity) {
             // If the velocity is low enough, then treat this more as an automatic page advance
             // as opposed to an apparent physical response to flinging
-            snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
-            return;
+            return snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
         }
 
         // Here we compute a "distance" that will be used in the computation of the overall
@@ -1653,39 +1652,39 @@
         // interpolator at zero, ie. 5. We use 4 to make it a little slower.
         duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
 
-        snapToPage(whichPage, delta, duration);
+        return snapToPage(whichPage, delta, duration);
     }
 
-    public void snapToPage(int whichPage) {
-        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+    public boolean snapToPage(int whichPage) {
+        return snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
     }
 
-    public void snapToPageImmediately(int whichPage) {
-        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true, null);
+    public boolean snapToPageImmediately(int whichPage) {
+        return snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true, null);
     }
 
-    public void snapToPage(int whichPage, int duration) {
-        snapToPage(whichPage, duration, false, null);
+    public boolean snapToPage(int whichPage, int duration) {
+        return snapToPage(whichPage, duration, false, null);
     }
 
-    protected void snapToPage(int whichPage, int duration, TimeInterpolator interpolator) {
-        snapToPage(whichPage, duration, false, interpolator);
+    protected boolean snapToPage(int whichPage, int duration, TimeInterpolator interpolator) {
+        return snapToPage(whichPage, duration, false, interpolator);
     }
 
-    protected void snapToPage(int whichPage, int duration, boolean immediate,
+    protected boolean snapToPage(int whichPage, int duration, boolean immediate,
             TimeInterpolator interpolator) {
         whichPage = validateNewPage(whichPage);
 
         int newX = getScrollForPage(whichPage);
         final int delta = newX - getUnboundedScrollX();
-        snapToPage(whichPage, delta, duration, immediate, interpolator);
+        return snapToPage(whichPage, delta, duration, immediate, interpolator);
     }
 
-    protected void snapToPage(int whichPage, int delta, int duration) {
-        snapToPage(whichPage, delta, duration, false, null);
+    protected boolean snapToPage(int whichPage, int delta, int duration) {
+        return snapToPage(whichPage, delta, duration, false, null);
     }
 
-    protected void snapToPage(int whichPage, int delta, int duration, boolean immediate,
+    protected boolean snapToPage(int whichPage, int delta, int duration, boolean immediate,
             TimeInterpolator interpolator) {
         whichPage = validateNewPage(whichPage);
 
@@ -1723,6 +1722,7 @@
         }
 
         invalidate();
+        return Math.abs(delta) > 0;
     }
 
     public void scrollLeft() {