Allow for continuous scrolling to previous tasks

Starting a new touch interaction on the nav bar while going to a new task
cancels that animation to allow for continuous scrolling.

Specifically, the new interaction still creates a new touch consumer and
WindowTransformSwipeHandler, but reuses the previous RecentsAnimationState
in order to defer finishing it.

Bug: 111926330
Change-Id: Ia4f5f8dbb2b3ae70791676f1e3e5ce84deb22f74
diff --git a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
index 5996df7..1633485 100644
--- a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
@@ -96,6 +96,11 @@
     }
 
     @Override
+    public OtherActivityTouchConsumer.RecentsAnimationState getRecentsAnimationStateToReuse() {
+        return mTarget.getRecentsAnimationStateToReuse();
+    }
+
+    @Override
     public boolean deferNextEventToMainThread() {
         // If our target is still null, defer the next target as well
         TouchConsumer target = mTarget;
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index a7f5f0b..13a9341 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -21,7 +21,6 @@
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.INVALID_POINTER_ID;
-
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -102,18 +101,21 @@
     private WindowTransformSwipeHandler mInteractionHandler;
     private int mDisplayRotation;
     private Rect mStableInsets = new Rect();
+    private boolean mCanGestureBeContinued;
 
     private VelocityTracker mVelocityTracker;
     private MotionPauseDetector mMotionPauseDetector;
     private MotionEventQueue mEventQueue;
     private boolean mIsGoingToLauncher;
+    private RecentsAnimationState mRecentsAnimationState;
 
     public OtherActivityTouchConsumer(Context base, RunningTaskInfo runningTaskInfo,
             RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
             MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
             @HitTarget int downHitTarget, OverviewCallbacks overviewCallbacks,
             TaskOverlayFactory taskOverlayFactory, InputConsumerController inputConsumer,
-            VelocityTracker velocityTracker, TouchInteractionLog touchInteractionLog) {
+            VelocityTracker velocityTracker, TouchInteractionLog touchInteractionLog,
+            @Nullable RecentsAnimationState recentsAnimationStateToReuse) {
         super(base);
 
         mRunningTask = runningTaskInfo;
@@ -130,6 +132,7 @@
         mTouchInteractionLog = touchInteractionLog;
         mTouchInteractionLog.setTouchConsumer(this);
         mInputConsumer = inputConsumer;
+        mRecentsAnimationState = recentsAnimationStateToReuse;
     }
 
     @Override
@@ -150,7 +153,8 @@
                 mActivePointerId = ev.getPointerId(0);
                 mDownPos.set(ev.getX(), ev.getY());
                 mLastPos.set(mDownPos);
-                mPassedInitialSlop = false;
+                // If mRecentsAnimationState != null, we are continuing the previous gesture.
+                mPassedInitialSlop = mRecentsAnimationState != null;
                 mQuickStepDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
 
                 // Start the window animation on down to give more time for launcher to draw if the
@@ -256,10 +260,15 @@
         mTouchInteractionLog.startRecentsAnimation();
 
         // Create the shared handler
-        RecentsAnimationState animationState = new RecentsAnimationState();
+        boolean reuseOldAnimState = mRecentsAnimationState != null;
+        if (reuseOldAnimState) {
+            mRecentsAnimationState.changeParent(this);
+        } else {
+            mRecentsAnimationState = new RecentsAnimationState(this);
+        }
         final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
-                animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper,
-                mInputConsumer, mTouchInteractionLog);
+                mRecentsAnimationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper,
+                reuseOldAnimState, mInputConsumer, mTouchInteractionLog);
 
         // Preload the plan
         mRecentsModel.getTasks(null);
@@ -291,8 +300,18 @@
                     }
                 };
 
-        Runnable startActivity = () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
-                mHomeIntent, assistDataReceiver, animationState, null, null);
+        Runnable startActivity;
+        if (reuseOldAnimState) {
+            startActivity = () -> {
+                handler.onRecentsAnimationStart(mRecentsAnimationState.mController,
+                        mRecentsAnimationState.mTargets, mRecentsAnimationState.mHomeContentInsets,
+                        mRecentsAnimationState.mMinimizedHomeBounds);
+            };
+        } else {
+            startActivity = () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
+                    mHomeIntent, assistDataReceiver, mRecentsAnimationState, null, null);
+        }
+
 
         if (Looper.myLooper() != Looper.getMainLooper()) {
             startActivity.run();
@@ -353,8 +372,10 @@
         if (mInteractionHandler != null) {
             final WindowTransformSwipeHandler handler = mInteractionHandler;
             mInteractionHandler = null;
-            mIsGoingToLauncher = handler.mIsGoingToRecents || handler.mIsGoingToHome;
-            mMainThreadExecutor.execute(handler::reset);
+            WindowTransformSwipeHandler.GestureEndTarget endTarget = handler.mGestureEndTarget;
+            mIsGoingToLauncher = endTarget != null && endTarget.isLauncher;
+            mCanGestureBeContinued = endTarget != null && endTarget.canBeContinued;
+            mMainThreadExecutor.execute(mCanGestureBeContinued ? handler::cancel : handler::reset);
         }
     }
 
@@ -444,25 +465,33 @@
     }
 
     @Override
+    public @Nullable RecentsAnimationState getRecentsAnimationStateToReuse() {
+        return mCanGestureBeContinued ? mRecentsAnimationState : null;
+    }
+
+    @Override
     public boolean deferNextEventToMainThread() {
         // TODO: Consider also check if the eventQueue is using mainThread of not.
         return mInteractionHandler != null;
     }
 
-    private class RecentsAnimationState implements RecentsAnimationListener {
+    public static class RecentsAnimationState implements RecentsAnimationListener {
 
         private static final String ANIMATION_START_EVT = "RecentsAnimationState.onAnimationStart";
         private final int id;
 
+        private OtherActivityTouchConsumer mParent;
+
         private RecentsAnimationControllerCompat mController;
         private RemoteAnimationTargetSet mTargets;
         private Rect mHomeContentInsets;
         private Rect mMinimizedHomeBounds;
         private boolean mCancelled;
 
-        public RecentsAnimationState() {
-            id = mAnimationStates.size();
-            mAnimationStates.put(id, this);
+        public RecentsAnimationState(OtherActivityTouchConsumer parent) {
+            mParent = parent;
+            id = mParent.mAnimationStates.size();
+            mParent.mAnimationStates.put(id, this);
         }
 
         @Override
@@ -475,31 +504,37 @@
             mTargets = new RemoteAnimationTargetSet(apps, MODE_CLOSING);
             mHomeContentInsets = homeContentInsets;
             mMinimizedHomeBounds = minimizedHomeBounds;
-            mEventQueue.onCommand(id);
+            mParent.mEventQueue.onCommand(id);
             RaceConditionTracker.onEvent(ANIMATION_START_EVT, EXIT);
         }
 
         @Override
         public void onAnimationCanceled() {
             mCancelled = true;
-            mEventQueue.onCommand(id);
+            mParent.mEventQueue.onCommand(id);
         }
 
         public void execute() {
-            if (mInteractionHandler == null || mInteractionHandler.id != id) {
+            WindowTransformSwipeHandler handler = mParent.mInteractionHandler;
+            if (handler == null || handler.id != id) {
                 if (!mCancelled && mController != null) {
                     TraceHelper.endSection("RecentsController", "Finishing no handler");
                     mController.finish(false /* toHome */);
                 }
             } else if (mCancelled) {
                 TraceHelper.endSection("RecentsController",
-                        "Cancelled: " + mInteractionHandler);
-                mInteractionHandler.onRecentsAnimationCanceled();
+                        "Cancelled: " + handler);
+                handler.onRecentsAnimationCanceled();
             } else {
                 TraceHelper.partitionSection("RecentsController", "Received");
-                mInteractionHandler.onRecentsAnimationStart(mController, mTargets,
+                handler.onRecentsAnimationStart(mController, mTargets,
                         mHomeContentInsets, mMinimizedHomeBounds);
             }
         }
+
+        public void changeParent(OtherActivityTouchConsumer newParent) {
+            mParent = newParent;
+            mParent.mAnimationStates.put(id, this);
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index 057a2ee..1d5ffe7 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -21,6 +21,9 @@
 import android.view.MotionEvent;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+
+import com.android.quickstep.OtherActivityTouchConsumer.RecentsAnimationState;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -73,5 +76,12 @@
         return false;
     }
 
+    /**
+     * When continuing a gesture, return the current non-null animation state that hasn't finished.
+     */
+    default @Nullable RecentsAnimationState getRecentsAnimationStateToReuse() {
+        return null;
+    }
+
     default void onShowOverviewFromAltTab() {}
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index d5de3ff..7da7bcd 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -47,6 +47,7 @@
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.OtherActivityTouchConsumer.RecentsAnimationState;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -237,16 +238,18 @@
         if (oldConsumer.deferNextEventToMainThread()) {
             mEventQueue = new MotionEventQueue(mMainThreadChoreographer,
                     new DeferredTouchConsumer((v) -> getCurrentTouchConsumer(downHitTarget,
-                            oldConsumer.forceToLauncherConsumer(), v)));
+                            oldConsumer.forceToLauncherConsumer(),
+                            oldConsumer.getRecentsAnimationStateToReuse(), v)));
             mEventQueue.deferInit();
         } else {
-            mEventQueue = new MotionEventQueue(
-                    mMainThreadChoreographer, getCurrentTouchConsumer(downHitTarget, false, null));
+            mEventQueue = new MotionEventQueue(mMainThreadChoreographer,
+                    getCurrentTouchConsumer(downHitTarget, false, null, null));
         }
     }
 
-    private TouchConsumer getCurrentTouchConsumer(
-            @HitTarget int downHitTarget, boolean forceToLauncher, VelocityTracker tracker) {
+    private TouchConsumer getCurrentTouchConsumer(@HitTarget int downHitTarget,
+            boolean forceToLauncher, RecentsAnimationState recentsAnimationStateToReuse,
+            VelocityTracker tracker) {
         RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
 
         if (runningTaskInfo == null && !forceToLauncher) {
@@ -269,7 +272,8 @@
                     mOverviewComponentObserver.getOverviewIntent(),
                     mOverviewComponentObserver.getActivityControlHelper(), mMainThreadExecutor,
                     mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks,
-                    mTaskOverlayFactory, mInputConsumer, tracker, mTouchInteractionLog);
+                    mTaskOverlayFactory, mInputConsumer, tracker, mTouchInteractionLog,
+                    recentsAnimationStateToReuse);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index c60752a..f9495a4 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -197,19 +197,21 @@
     };
 
     enum GestureEndTarget {
-        HOME(1, STATE_SCALED_CONTROLLER_HOME, true, ContainerType.WORKSPACE),
+        HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE),
 
         RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
-                | STATE_SCREENSHOT_VIEW_SHOWN, true, ContainerType.TASKSWITCHER),
+                | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER),
 
-        NEW_TASK(0, STATE_START_NEW_TASK, false, ContainerType.APP),
+        NEW_TASK(0, STATE_START_NEW_TASK, false, true, ContainerType.APP),
 
-        LAST_TASK(0, STATE_SCALED_CONTROLLER_LAST_TASK, false, ContainerType.APP);
+        LAST_TASK(0, STATE_SCALED_CONTROLLER_LAST_TASK, false, false, ContainerType.APP);
 
-        GestureEndTarget(float endShift, int endState, boolean isLauncher, int containerType) {
+        GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued,
+                int containerType) {
             this.endShift = endShift;
             this.endState = endState;
             this.isLauncher = isLauncher;
+            this.canBeContinued = canBeContinued;
             this.containerType = containerType;
         }
 
@@ -217,6 +219,7 @@
         public final float endShift;
         public final int endState;
         public final boolean isLauncher;
+        public final boolean canBeContinued;
         public final int containerType;
     }
 
@@ -235,8 +238,7 @@
     private final ClipAnimationHelper.TransformParams mTransformParams;
 
     protected Runnable mGestureEndCallback;
-    protected boolean mIsGoingToRecents;
-    protected boolean mIsGoingToHome;
+    protected GestureEndTarget mGestureEndTarget;
     private boolean mIsShelfPeeking;
     private DeviceProfile mDp;
     private int mTransitionDragLength;
@@ -247,6 +249,7 @@
     // visible.
     private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
     private boolean mDispatchedDownEvent;
+    private boolean mContinuingLastGesture;
     // To avoid UI jump when gesture is started, we offset the animation by the threshold.
     private float mShiftAtGestureStart = 0;
 
@@ -302,7 +305,7 @@
     private Bundle mAssistData;
 
     WindowTransformSwipeHandler(int id, RunningTaskInfo runningTaskInfo, Context context,
-            long touchTimeMs, ActivityControlHelper<T> controller,
+            long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
             InputConsumerController inputConsumer, TouchInteractionLog touchInteractionLog) {
         this.id = id;
         mContext = context;
@@ -312,6 +315,7 @@
         mActivityControlHelper = controller;
         mActivityInitListener = mActivityControlHelper
                 .createActivityInitListener(this::onActivityInit);
+        mContinuingLastGesture = continuingLastGesture;
         mTouchInteractionLog = touchInteractionLog;
         mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
                 this::createNewTouchProxyHandler);
@@ -480,7 +484,7 @@
         });
         mRecentsView.setEnableFreeScroll(false);
         mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
-            if (!mBgLongSwipeMode && !mIsGoingToHome) {
+            if (!mBgLongSwipeMode && mGestureEndTarget != HOME) {
                 updateFinalShift();
             }
         });
@@ -538,6 +542,9 @@
     }
 
     private void setupRecentsViewUi() {
+        if (mContinuingLastGesture) {
+            return;
+        }
         mRecentsView.setEnableDrawingLiveTile(false);
         mRecentsView.showTask(mRunningTaskId);
         mRecentsView.setRunningTaskHidden(true);
@@ -767,16 +774,13 @@
                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
             }
         }
-        // Update insets of the adjacent tasks, as we might switch to them.
+        // Update insets of the non-running tasks, as we might switch to them.
         int runningTaskIndex = mRecentsView == null ? -1 : mRecentsView.getRunningTaskIndex();
         if (mInteractionType == INTERACTION_NORMAL && runningTaskIndex >= 0) {
-            TaskView nextTaskView = mRecentsView.getTaskViewAt(runningTaskIndex + 1);
-            TaskView prevTaskView = mRecentsView.getTaskViewAt(runningTaskIndex - 1);
-            if (nextTaskView != null) {
-                nextTaskView.setFullscreenProgress(1 - mCurrentShift.value);
-            }
-            if (prevTaskView != null) {
-                prevTaskView.setFullscreenProgress(1 - mCurrentShift.value);
+            for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
+                if (i != runningTaskIndex) {
+                    mRecentsView.getTaskViewAt(i).setFullscreenProgress(1 - mCurrentShift.value);
+                }
             }
         }
 
@@ -788,7 +792,7 @@
     }
 
     private void updateLauncherTransitionProgress() {
-        if (mIsGoingToHome) {
+        if (mGestureEndTarget == HOME) {
             return;
         }
         float progress = mCurrentShift.value;
@@ -909,10 +913,18 @@
         float endShift;
         final float startShift;
         Interpolator interpolator = DEACCEL;
-        final int nextPage = mRecentsView != null ? mRecentsView.getNextPage() : -1;
-        final int runningTaskIndex = mRecentsView != null ? mRecentsView.getRunningTaskIndex() : -1;
-        boolean goingToNewTask = mRecentsView != null && nextPage != runningTaskIndex
-                && mRecentsView.getTaskViewAt(nextPage) != null;
+        int nextPage = 0;
+        int taskToLaunch = 0;
+        final boolean goingToNewTask;
+        if (mRecentsView != null) {
+            nextPage = mRecentsView.getNextPage();
+            final int lastTaskIndex = mRecentsView.getTaskViewCount() - 1;
+            final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
+            taskToLaunch = nextPage <= lastTaskIndex ? nextPage : lastTaskIndex;
+            goingToNewTask = mRecentsView != null && taskToLaunch != runningTaskIndex;
+        } else {
+            goingToNewTask = false;
+        }
         final boolean reachedOverviewThreshold = currentShift >= MIN_PROGRESS_FOR_OVERVIEW;
         if (!isFling) {
             if (SWIPE_HOME.get()) {
@@ -973,6 +985,11 @@
             }
         }
 
+        if (mRecentsView != null && !endTarget.isLauncher && taskToLaunch != nextPage) {
+            // Scrolled to Clear all button, snap back to last task and launch it.
+            mRecentsView.snapToPage(taskToLaunch, Math.toIntExact(duration), interpolator);
+        }
+
         if (endTarget == HOME) {
             setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
             duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
@@ -989,11 +1006,6 @@
             if (mRecentsView != null) {
                 duration = Math.max(duration, mRecentsView.getScroller().getDuration());
             }
-        } else if (endTarget == LAST_TASK) {
-            if (mRecentsView != null && nextPage != runningTaskIndex) {
-                // Scrolled to Clear all button, snap back to current task and resume it.
-                mRecentsView.snapToPage(runningTaskIndex, Math.toIntExact(duration));
-            }
         }
         animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
     }
@@ -1028,11 +1040,10 @@
 
     private void animateToProgressInternal(float start, float end, long duration,
             Interpolator interpolator, GestureEndTarget target, float velocityPxPerMs) {
-        mIsGoingToHome = target == HOME;
-        mIsGoingToRecents = target == RECENTS;
+        mGestureEndTarget = target;
         ActivityControlHelper.HomeAnimationFactory homeAnimFactory;
         Animator windowAnim;
-        if (mIsGoingToHome) {
+        if (mGestureEndTarget == HOME) {
             if (mActivity != null) {
                 homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity);
             } else {
@@ -1071,7 +1082,7 @@
         long startMillis = SystemClock.uptimeMillis();
         // Always play the entire launcher animation when going home, since it is separate from
         // the animation that has been controlled thus far.
-        final float finalStart = mIsGoingToHome ? 0 : start;
+        final float finalStart = mGestureEndTarget == HOME ? 0 : start;
         executeOnUiThread(() -> {
             // Animate the launcher components at the same time as the window, always on UI thread.
             // Adjust start progress and duration in case we are on a different thread.
@@ -1144,6 +1155,14 @@
                     .setSyncTransactionApplier(syncTransactionApplier);
             mClipAnimationHelper.applyTransform(targetSet, mTransformParams);
         });
+        anim.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                if (mRecentsView != null) {
+                    mRecentsView.post(mRecentsView::resetTaskVisuals);
+                }
+            }
+        });
         return anim;
     }
 
@@ -1187,6 +1206,14 @@
         }
     }
 
+    public void cancel() {
+        mCurrentShift.cancelAnimation();
+        if (mLauncherTransitionController != null && mLauncherTransitionController
+                .getAnimationPlayer().isStarted()) {
+            mLauncherTransitionController.getAnimationPlayer().cancel();
+        }
+    }
+
     private void invalidateHandler() {
         mCurrentShift.finishAnimation();