Merge "Using recents animation and swipe handler for 3-button mode" into sc-dev
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index a2f15f5..6da2201 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -48,6 +48,7 @@
 import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
 
 import android.animation.Animator;
@@ -616,7 +617,7 @@
         final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
         if (passed != mPassedOverviewThreshold) {
             mPassedOverviewThreshold = passed;
-            if (!mDeviceState.isFullyGesturalNavMode()) {
+            if (!mDeviceState.isTwoButtonNavMode()) {
                 performHapticFeedback();
             }
         }
@@ -722,6 +723,9 @@
 
     @UiThread
     public void onGestureStarted(boolean isLikelyToStartNewTask) {
+        mActivityInterface.closeOverlay();
+        TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+
         if (mRecentsView != null) {
             InteractionJankMonitorWrapper.begin(mRecentsView,
                     InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH, 2000 /* ms timeout */);
@@ -849,6 +853,10 @@
 
     private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
             boolean isCancel) {
+        if (mDeviceState.isButtonNavMode()) {
+            // Button mode, this is only used to go to recents
+            return RECENTS;
+        }
         final GestureEndTarget endTarget;
         final boolean goingToNewTask;
         if (mRecentsView != null) {
@@ -953,15 +961,20 @@
         } else if (endTarget == RECENTS) {
             if (mRecentsView != null) {
                 int nearestPage = mRecentsView.getDestinationPage();
+                boolean isScrolling = false;
                 if (mRecentsView.getNextPage() != nearestPage) {
                     // We shouldn't really scroll to the next page when swiping up to recents.
                     // Only allow settling on the next page if it's nearest to the center.
                     mRecentsView.snapToPage(nearestPage, Math.toIntExact(duration));
+                    isScrolling = true;
                 }
                 if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) {
                     mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION);
+                    isScrolling = true;
                 }
-                duration = Math.max(duration, mRecentsView.getScroller().getDuration());
+                if (!mDeviceState.isButtonNavMode() || isScrolling) {
+                    duration = Math.max(duration, mRecentsView.getScroller().getDuration());
+                }
             }
         }
 
@@ -1764,7 +1777,6 @@
 
     public interface Factory {
 
-        AbsSwipeUpHandler newHandler(
-                GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
+        AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
deleted file mode 100644
index d159fa0..0000000
--- a/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.statehandlers.DepthController.DEPTH;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.util.Log;
-import android.view.animation.Interpolator;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.taskbar.TaskbarController;
-import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.SurfaceTransactionApplier;
-import com.android.quickstep.util.TaskViewSimulator;
-import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-/**
- * Provider for the atomic (for 3-button mode) remote window animation from the app to the overview.
- *
- * @param <T> activity that contains the overview
- */
-final class AppToOverviewAnimationProvider<T extends StatefulActivity<?>> extends
-        RemoteAnimationProvider {
-
-    private static final long RECENTS_LAUNCH_DURATION = 250;
-    private static final String TAG = "AppToOverviewAnimationProvider";
-
-    private final BaseActivityInterface<?, T> mActivityInterface;
-    // The id of the currently running task that is transitioning to overview.
-    private final RunningTaskInfo mTargetTask;
-    private final RecentsAnimationDeviceState mDeviceState;
-
-    private T mActivity;
-    private RecentsView mRecentsView;
-
-    AppToOverviewAnimationProvider(
-            BaseActivityInterface<?, T> activityInterface, RunningTaskInfo targetTask,
-            RecentsAnimationDeviceState deviceState) {
-        mActivityInterface = activityInterface;
-        mTargetTask = targetTask;
-        mDeviceState = deviceState;
-    }
-
-    /**
-     * Callback for when the activity is ready/initialized.
-     *
-     * @param activity the activity that is ready
-     * @param wasVisible true if it was visible before
-     */
-    boolean onActivityReady(T activity, Boolean wasVisible) {
-        activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTask);
-        AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
-        BaseActivityInterface.AnimationFactory factory = mActivityInterface.prepareRecentsUI(
-                mDeviceState,
-                wasVisible, (controller) -> {
-                    controller.getNormalController().dispatchOnStart();
-                    controller.getNormalController().getAnimationPlayer().end();
-                });
-        factory.createActivityInterface(RECENTS_LAUNCH_DURATION);
-        factory.setRecentsAttachedToAppWindow(true, false);
-        mActivity = activity;
-        mRecentsView = mActivity.getOverviewPanel();
-        return false;
-    }
-
-    /**
-     * Create remote window animation from the currently running app to the overview panel.
-     *
-     * @param appTargets the target apps
-     * @return animation from app to overview
-     */
-    @Override
-    public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
-            RemoteAnimationTargetCompat[] wallpaperTargets) {
-        PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
-        if (mActivity == null) {
-            Log.e(TAG, "Animation created, before activity");
-            return pa.buildAnim();
-        }
-
-        mRecentsView.setRunningTaskIconScaledDown(true);
-        pa.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                mActivityInterface.onSwipeUpToRecentsComplete();
-                mRecentsView.animateUpRunningTaskIconScale();
-            }
-        });
-
-        DepthController depthController = mActivityInterface.getDepthController();
-        if (depthController != null) {
-            pa.addFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(mActivity),
-                    OVERVIEW.getDepth(mActivity), TOUCH_RESPONSE_INTERPOLATOR);
-        }
-
-        TaskbarController taskbarController = mActivityInterface.getTaskbarController();
-        if (taskbarController != null) {
-            pa.add(taskbarController.createAnimToLauncher(OVERVIEW, getRecentsLaunchDuration()));
-        }
-
-        RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets,
-                wallpaperTargets, MODE_CLOSING);
-
-        // Use the top closing app to determine the insets for the animation
-        RemoteAnimationTargetCompat runningTaskTarget = mTargetTask == null ? null
-                : targets.findTask(mTargetTask.taskId);
-        if (runningTaskTarget == null) {
-            Log.e(TAG, "No closing app");
-            return pa.buildAnim();
-        }
-
-        TaskViewSimulator tsv = new TaskViewSimulator(mActivity, mRecentsView.getSizeStrategy());
-        tsv.setDp(mActivity.getDeviceProfile());
-        tsv.setOrientationState(mRecentsView.getPagedViewOrientedState());
-        tsv.setPreview(runningTaskTarget);
-
-        TransformParams params = new TransformParams()
-                .setTargetSet(targets)
-                .setSyncTransactionApplier(new SurfaceTransactionApplier(mActivity.getRootView()));
-
-        AnimatedFloat recentsAlpha = new AnimatedFloat(() -> { });
-        params.setBaseBuilderProxy((builder, app, p)
-                -> builder.withAlpha(recentsAlpha.value));
-
-        Interpolator taskInterpolator;
-        if (targets.isAnimatingHome()) {
-            params.setHomeBuilderProxy((builder, app, p) -> builder.withAlpha(1 - p.getProgress()));
-
-            taskInterpolator = TOUCH_RESPONSE_INTERPOLATOR;
-            pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1, TOUCH_RESPONSE_INTERPOLATOR);
-        } else {
-            // When animation from app to recents, the recents layer is drawn on top of the app. To
-            // prevent the overlap, we animate the task first and then quickly fade in the recents.
-            taskInterpolator = clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0, 0.8f);
-            pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1,
-                    clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0.8f, 1));
-        }
-
-        pa.addFloat(params, TransformParams.PROGRESS, 0, 1, taskInterpolator);
-        tsv.addAppToOverviewAnim(pa, taskInterpolator);
-        pa.addOnFrameCallback(() -> tsv.apply(params));
-        return pa.buildAnim();
-    }
-
-    /**
-     * Get duration of animation from app to overview.
-     *
-     * @return duration of animation
-     */
-    long getRecentsLaunchDuration() {
-        return RECENTS_LAUNCH_DURATION;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index bffe3a1..e13d1a4 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -105,7 +105,7 @@
     @Override
     public RecentsView getVisibleRecentsView() {
         RecentsActivity activity = getCreatedActivity();
-        if (activity != null && activity.hasWindowFocus()) {
+        if (activity != null && activity.hasBeenResumed()) {
             return activity.getOverviewPanel();
         }
         return null;
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index ca5765e..98b96b2 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -177,7 +177,7 @@
     @UiThread
     private Launcher getVisibleLauncher() {
         Launcher launcher = getCreatedActivity();
-        return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus()
+        return (launcher != null) && launcher.isStarted() && launcher.hasBeenResumed()
                 ? launcher : null;
     }
 
@@ -188,6 +188,7 @@
             return false;
         }
 
+        closeOverlay();
         launcher.getStateManager().goToState(OVERVIEW,
                 launcher.getStateManager().shouldAnimateStateChange(), onCompleteCallback);
         return true;
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 63fdd0b..923d4f1 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -16,29 +16,28 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
-import android.content.Context;
+import android.content.Intent;
+import android.graphics.PointF;
 import android.os.Build;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.view.ViewConfiguration;
 
 import androidx.annotation.BinderThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
 
 import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.launcher3.util.RunnableList;
+import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.systemui.shared.system.LatencyTrackerCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.util.ArrayList;
 
 /**
  * Helper class to handle various atomic commands for switching between Overview.
@@ -46,66 +45,191 @@
 @TargetApi(Build.VERSION_CODES.P)
 public class OverviewCommandHelper {
 
-    private final Context mContext;
-    private final RecentsAnimationDeviceState mDeviceState;
+    public static final int TYPE_SHOW = 1;
+    public static final int TYPE_SHOW_NEXT_FOCUS = 2;
+    public static final int TYPE_HIDE = 3;
+    public static final int TYPE_TOGGLE = 4;
+
+    private static final String TRANSITION_NAME = "Transition:toOverview";
+
+    private final TouchInteractionService mService;
     private final OverviewComponentObserver mOverviewComponentObserver;
+    private final TaskAnimationManager mTaskAnimationManager;
+    private final ArrayList<CommandInfo> mPendingCommands = new ArrayList<>();
 
-    private long mLastToggleTime;
-
-    public OverviewCommandHelper(Context context, RecentsAnimationDeviceState deviceState,
-            OverviewComponentObserver observer) {
-        mContext = context;
-        mDeviceState = deviceState;
+    public OverviewCommandHelper(TouchInteractionService service,
+            OverviewComponentObserver observer,
+            TaskAnimationManager taskAnimationManager) {
+        mService = service;
         mOverviewComponentObserver = observer;
+        mTaskAnimationManager = taskAnimationManager;
     }
 
-    @BinderThread
-    public void onOverviewToggle() {
-        // If currently screen pinning, do not enter overview
-        if (mDeviceState.isScreenPinningActive()) {
+    /**
+     * Called when the command finishes execution.
+     */
+    private void scheduleNextTask(CommandInfo command) {
+        if (!mPendingCommands.isEmpty() && mPendingCommands.get(0) == command) {
+            mPendingCommands.remove(0);
+            executeNext();
+        }
+    }
+
+    /**
+     * Executes the next command from the queue. If the command finishes immediately (returns true),
+     * it continues to execute the next command, until the queue is empty of a command defer's its
+     * completion (returns false).
+     */
+    @UiThread
+    private void executeNext() {
+        if (mPendingCommands.isEmpty()) {
             return;
         }
-
-        TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-        MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
+        CommandInfo cmd = mPendingCommands.get(0);
+        if (executeCommand(cmd)) {
+            scheduleNextTask(cmd);
+        }
     }
 
+    @UiThread
+    private void addCommand(CommandInfo cmd) {
+        boolean wasEmpty = mPendingCommands.isEmpty();
+        mPendingCommands.add(cmd);
+        if (wasEmpty) {
+            executeNext();
+        }
+    }
+
+    /**
+     * Adds a command to be executed next, after all pending tasks are completed
+     */
     @BinderThread
-    public void onOverviewShown(boolean triggeredFromAltTab) {
-        if (triggeredFromAltTab) {
-            TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-        }
-        MAIN_EXECUTOR.execute(new ShowRecentsCommand(triggeredFromAltTab));
+    public void addCommand(int type) {
+        CommandInfo cmd = new CommandInfo(type);
+        MAIN_EXECUTOR.execute(() -> addCommand(cmd));
     }
 
-    @BinderThread
-    public void onOverviewHidden() {
-        MAIN_EXECUTOR.execute(new HideRecentsCommand());
+    private TaskView getNextTask(RecentsView view) {
+        final TaskView runningTaskView = view.getRunningTaskView();
+
+        if (runningTaskView == null) {
+            return view.getTaskViewCount() > 0 ? view.getTaskViewAt(0) : null;
+        } else {
+            final TaskView nextTask = view.getNextTaskView();
+            return nextTask != null ? nextTask : runningTaskView;
+        }
     }
 
-    private class ShowRecentsCommand extends RecentsActivityCommand {
-
-        private final boolean mTriggeredFromAltTab;
-
-        ShowRecentsCommand(boolean triggeredFromAltTab) {
-            mTriggeredFromAltTab = triggeredFromAltTab;
+    private boolean launchTask(RecentsView recents, @Nullable TaskView taskView, CommandInfo cmd) {
+        RunnableList callbackList = null;
+        if (taskView != null) {
+            taskView.setEndQuickswitchCuj(true);
+            callbackList = taskView.launchTaskAnimated();
         }
 
-        @Override
-        protected boolean handleCommand(long elapsedTime) {
-            // TODO: Go to the next page if started from alt-tab.
-            return mActivityInterface.getVisibleRecentsView() != null;
+        if (callbackList != null) {
+            callbackList.add(() -> scheduleNextTask(cmd));
+            return false;
+        } else {
+            recents.startHome();
+            return true;
         }
+    }
 
-        @Override
-        protected void onTransitionComplete() {
-            // TODO(b/138729100) This doesn't execute first time launcher is run
-            if (mTriggeredFromAltTab) {
-                RecentsView rv =  mActivityInterface.getVisibleRecentsView();
-                if (rv == null) {
-                    return;
+    /**
+     * Executes the task and returns true if next task can be executed. If false, then the next
+     * task is deferred until {@link #scheduleNextTask} is called
+     */
+    private <T extends StatefulActivity<?>> boolean executeCommand(CommandInfo cmd) {
+        BaseActivityInterface<?, T> activityInterface =
+                mOverviewComponentObserver.getActivityInterface();
+        RecentsView recents = activityInterface.getVisibleRecentsView();
+        if (recents == null) {
+            if (cmd.type == TYPE_HIDE) {
+                // already hidden
+                return true;
+            }
+        } else {
+            switch (cmd.type) {
+                case TYPE_SHOW:
+                    // already visible
+                    return true;
+                case TYPE_HIDE: {
+                    int currentPage = recents.getNextPage();
+                    TaskView tv = (currentPage >= 0 && currentPage < recents.getTaskViewCount())
+                            ? (TaskView) recents.getPageAt(currentPage)
+                            : null;
+                    return launchTask(recents, tv, cmd);
                 }
+                case TYPE_TOGGLE:
+                    return launchTask(recents, getNextTask(recents), cmd);
+            }
+        }
 
+        if (activityInterface.switchToRecentsIfVisible(() -> scheduleNextTask(cmd))) {
+            // If successfully switched, wait until animation finishes
+            return false;
+        }
+
+        final T activity = activityInterface.getCreatedActivity();
+        if (activity != null) {
+            InteractionJankMonitorWrapper.begin(
+                    activity.getRootView(),
+                    InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+        }
+
+        GestureState gestureState = mService.createGestureState(GestureState.DEFAULT_STATE);
+        AbsSwipeUpHandler interactionHandler = mService.getSwipeUpHandlerFactory()
+                .newHandler(gestureState, cmd.createTime);
+        interactionHandler.setGestureEndCallback(
+                () -> onTransitionComplete(cmd, interactionHandler));
+
+        Intent intent = new Intent(interactionHandler.getLaunchIntent());
+        interactionHandler.initWhenReady(intent);
+
+        RecentsAnimationListener recentAnimListener = new RecentsAnimationListener() {
+            @Override
+            public void onRecentsAnimationStart(RecentsAnimationController controller,
+                    RecentsAnimationTargets targets) {
+                interactionHandler.onGestureEnded(0, new PointF(), new PointF());
+                cmd.removeListener(this);
+            }
+
+            @Override
+            public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+                interactionHandler.onGestureCancelled();
+                cmd.removeListener(this);
+            }
+        };
+
+        if (mTaskAnimationManager.isRecentsAnimationRunning()) {
+            cmd.mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(gestureState);
+            cmd.mActiveCallbacks.addListener(interactionHandler);
+            mTaskAnimationManager.notifyRecentsAnimationState(interactionHandler);
+            interactionHandler.onGestureStarted(true /*isLikelyToStartNewTask*/);
+
+            cmd.mActiveCallbacks.addListener(recentAnimListener);
+            mTaskAnimationManager.notifyRecentsAnimationState(recentAnimListener);
+        } else {
+            intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, gestureState.getGestureId());
+            cmd.mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(
+                    gestureState, intent, interactionHandler);
+            interactionHandler.onGestureStarted(false /*isLikelyToStartNewTask*/);
+            cmd.mActiveCallbacks.addListener(recentAnimListener);
+        }
+
+        Trace.beginAsyncSection(TRANSITION_NAME, 0);
+        return false;
+    }
+
+    private void onTransitionComplete(CommandInfo cmd, AbsSwipeUpHandler handler) {
+        cmd.removeListener(handler);
+        Trace.endAsyncSection(TRANSITION_NAME, 0);
+
+        if (cmd.type == TYPE_SHOW_NEXT_FOCUS) {
+            RecentsView rv =
+                    mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
+            if (rv != null) {
                 // Ensure that recents view has focus so that it receives the followup key inputs
                 TaskView taskView = rv.getNextTaskView();
                 if (taskView == null) {
@@ -120,130 +244,22 @@
                 }
             }
         }
+        scheduleNextTask(cmd);
     }
 
-    private class HideRecentsCommand extends RecentsActivityCommand {
+    private static class CommandInfo {
+        public final long createTime = SystemClock.elapsedRealtime();
+        public final int type;
+        RecentsAnimationCallbacks mActiveCallbacks;
 
-        @Override
-        protected boolean handleCommand(long elapsedTime) {
-            RecentsView recents = mActivityInterface.getVisibleRecentsView();
-            if (recents == null) {
-                return false;
-            }
-            int currentPage = recents.getNextPage();
-            if (currentPage >= 0 && currentPage < recents.getTaskViewCount()) {
-                ((TaskView) recents.getPageAt(currentPage)).launchTaskAnimated();
-            } else {
-                recents.startHome();
-            }
-            return true;
-        }
-    }
-
-    private class RecentsActivityCommand<T extends StatefulActivity<?>> implements Runnable {
-
-        private static final String TRANSITION_NAME = "Transition:toOverview";
-        protected final BaseActivityInterface<?, T> mActivityInterface;
-        private final long mCreateTime;
-        private final AppToOverviewAnimationProvider<T> mAnimationProvider;
-
-        private final long mToggleClickedTime = SystemClock.uptimeMillis();
-        private ActivityInitListener mListener;
-
-        public RecentsActivityCommand() {
-            mActivityInterface = mOverviewComponentObserver.getActivityInterface();
-            mCreateTime = SystemClock.elapsedRealtime();
-            mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface,
-                    ActivityManagerWrapper.getInstance().getRunningTask(), mDeviceState);
-
-            // Preload the plan
-            RecentsModel.INSTANCE.get(mContext).getTasks(null);
+        CommandInfo(int type) {
+            this.type = type;
         }
 
-        @Override
-        public void run() {
-            long elapsedTime = mCreateTime - mLastToggleTime;
-            mLastToggleTime = mCreateTime;
-
-            if (handleCommand(elapsedTime)) {
-                // Command already handled.
-                return;
+        void removeListener(RecentsAnimationListener listener) {
+            if (mActiveCallbacks != null) {
+                mActiveCallbacks.removeListener(listener);
             }
-
-            if (mActivityInterface.switchToRecentsIfVisible(this::onTransitionComplete)) {
-                // If successfully switched, then return
-                return;
-            }
-
-            final T activity = mActivityInterface.getCreatedActivity();
-            if (activity != null) {
-                InteractionJankMonitorWrapper.begin(
-                        activity.getRootView(),
-                        InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
-            }
-
-            // Otherwise, start overview.
-            mListener = mActivityInterface.createActivityInitListener(this::onActivityReady);
-            mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
-                    new RemoteAnimationProvider() {
-                        @Override
-                        public AnimatorSet createWindowAnimation(
-                                RemoteAnimationTargetCompat[] appTargets,
-                                RemoteAnimationTargetCompat[] wallpaperTargets) {
-                            return RecentsActivityCommand.this.createWindowAnimation(appTargets,
-                                    wallpaperTargets);
-                        }
-                    }, mContext, MAIN_EXECUTOR.getHandler(),
-                    mAnimationProvider.getRecentsLaunchDuration());
         }
-
-        protected boolean handleCommand(long elapsedTime) {
-            // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows
-            //       the menu activity which takes window focus, preventing the right condition from
-            //       being run below
-            RecentsView recents = mActivityInterface.getVisibleRecentsView();
-            if (recents != null) {
-                // Launch the next task
-                recents.showNextTask();
-                return true;
-            } else if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
-                // The user tried to launch back into overview too quickly, either after
-                // launching an app, or before overview has actually shown, just ignore for now
-                return true;
-            }
-            return false;
-        }
-
-        private boolean onActivityReady(Boolean wasVisible) {
-            final T activity = mActivityInterface.getCreatedActivity();
-            return mAnimationProvider.onActivityReady(activity, wasVisible);
-        }
-
-        private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
-                RemoteAnimationTargetCompat[] wallpaperTargets) {
-            LatencyTrackerCompat.logToggleRecents(
-                    mContext, (int) (SystemClock.uptimeMillis() - mToggleClickedTime));
-
-            mListener.unregister();
-
-            AnimatorSet animatorSet = mAnimationProvider.createWindowAnimation(appTargets,
-                    wallpaperTargets);
-            animatorSet.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    Trace.beginAsyncSection(TRANSITION_NAME, 0);
-                    super.onAnimationStart(animation);
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    onTransitionComplete();
-                    Trace.endAsyncSection(TRANSITION_NAME, 0);
-                }
-            });
-            return animatorSet;
-        }
-
-        protected void onTransitionComplete() { }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index f99b7e6..458f45a 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,12 +17,13 @@
 
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 
-import static com.android.launcher3.util.SettingsCache.ONE_HANDED_ENABLED;
-import static com.android.launcher3.util.SettingsCache.ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED;
 import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ALL;
 import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_FRAME_DELAY;
+import static com.android.launcher3.util.SettingsCache.ONE_HANDED_ENABLED;
+import static com.android.launcher3.util.SettingsCache.ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
@@ -60,11 +61,11 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayHolder;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.SettingsCache;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
 import com.android.quickstep.util.NavBarPosition;
@@ -304,6 +305,13 @@
     }
 
     /**
+     * @return whether the current nav mode is 2-button-based.
+     */
+    public boolean isTwoButtonNavMode() {
+        return mMode == TWO_BUTTONS;
+    }
+
+    /**
      * @return whether the current nav mode is button-based.
      */
     public boolean isButtonNavMode() {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index fc805d0..8e6f663 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -24,6 +24,7 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
 import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
 
@@ -157,13 +158,23 @@
         @BinderThread
         public void onOverviewToggle() {
             TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
-            mOverviewCommandHelper.onOverviewToggle();
+            // If currently screen pinning, do not enter overview
+            if (mDeviceState.isScreenPinningActive()) {
+                return;
+            }
+            TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+            mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
         }
 
         @BinderThread
         @Override
         public void onOverviewShown(boolean triggeredFromAltTab) {
-            mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
+            if (triggeredFromAltTab) {
+                TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+                mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW_NEXT_FOCUS);
+            } else {
+                mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW);
+            }
         }
 
         @BinderThread
@@ -171,7 +182,7 @@
         public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
             if (triggeredFromAltTab && !triggeredFromHomeKey) {
                 // onOverviewShownFromAltTab hides the overview and ends at the target app
-                mOverviewCommandHelper.onOverviewHidden();
+                mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HIDE);
             }
         }
 
@@ -326,8 +337,8 @@
     public void onUserUnlocked() {
         mTaskAnimationManager = new TaskAnimationManager(this);
         mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
-        mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState,
-                mOverviewComponentObserver);
+        mOverviewCommandHelper = new OverviewCommandHelper(this,
+                mOverviewComponentObserver, mTaskAnimationManager);
         mResetGestureInputConsumer = new ResetGestureInputConsumer(mTaskAnimationManager);
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
         mInputConsumer.registerInputConsumer();
@@ -555,7 +566,7 @@
         ProtoTracer.INSTANCE.get(this).scheduleFrameUpdate();
     }
 
-    private GestureState createGestureState(GestureState previousGestureState) {
+    public GestureState createGestureState(GestureState previousGestureState) {
         GestureState gestureState = new GestureState(mOverviewComponentObserver,
                 ActiveGestureLog.INSTANCE.generateAndSetLogId());
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
@@ -704,16 +715,15 @@
         }
     }
 
+    public AbsSwipeUpHandler.Factory getSwipeUpHandlerFactory() {
+        return !mOverviewComponentObserver.isHomeAndOverviewSame()
+                ? mFallbackSwipeHandlerFactory : mLauncherSwipeHandlerFactory;
+    }
+
     private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
             MotionEvent event) {
 
-        final AbsSwipeUpHandler.Factory factory;
-        if (!mOverviewComponentObserver.isHomeAndOverviewSame()) {
-            factory = mFallbackSwipeHandlerFactory;
-        } else {
-            factory = mLauncherSwipeHandlerFactory;
-        }
-
+        final AbsSwipeUpHandler.Factory factory = getSwipeUpHandlerFactory();
         final boolean shouldDefer = !mOverviewComponentObserver.isHomeAndOverviewSame()
                 || gestureState.getActivityInterface().deferStartingActivity(mDeviceState, event);
         final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
@@ -886,15 +896,17 @@
     }
 
     private AbsSwipeUpHandler createLauncherSwipeHandler(
-            GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
+            GestureState gestureState, long touchTimeMs) {
         return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
-                gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
+                gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
+                mInputConsumer);
     }
 
     private AbsSwipeUpHandler createFallbackSwipeHandler(
-            GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
+            GestureState gestureState, long touchTimeMs) {
         return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
-                gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
+                gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
+                mInputConsumer);
     }
 
     protected boolean shouldNotifyBackGesture() {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 5baf518..9878d45 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -31,7 +31,6 @@
 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
 import static com.android.quickstep.GestureState.STATE_OVERSCROLL_WINDOW_CREATED;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -65,7 +64,6 @@
 import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.RotationTouchHelper;
 import com.android.quickstep.TaskAnimationManager;
-import com.android.quickstep.TaskUtils;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.CachedEventDispatcher;
@@ -381,9 +379,6 @@
         // Once we detect the gesture, we can enable batching to reduce further updates
         mInputEventReceiver.setBatchingEnabled(true);
 
-        mActivityInterface.closeOverlay();
-        TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-
         // Notify the handler that the gesture has actually started
         mInteractionHandler.onGestureStarted(isLikelyToStartNewTask);
     }
@@ -391,8 +386,7 @@
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
         ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation");
 
-        mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs,
-                mTaskAnimationManager.isRecentsAnimationRunning());
+        mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs);
         mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
         mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler.getMotionPauseListener());
         Intent intent = new Intent(mInteractionHandler.getLaunchIntent());
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index bca9687..882241e 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1535,29 +1535,6 @@
         }
     }
 
-    public void showNextTask() {
-        final TaskView runningTaskView = getRunningTaskView();
-        final TaskView targetTask;
-
-        if (runningTaskView == null) {
-            // Launch the first task
-            if (getTaskViewCount() > 0) {
-                targetTask = getTaskViewAt(0);
-            } else {
-                return;
-            }
-        } else {
-            final TaskView nextTask = getNextTaskView();
-            if (nextTask != null) {
-                targetTask = nextTask;
-            } else {
-                targetTask = runningTaskView;
-            }
-        }
-        targetTask.setEndQuickswitchCuj(true);
-        targetTask.launchTaskAnimated();
-    }
-
     public void setRunningTaskIconScaledDown(boolean isScaledDown) {
         if (mRunningTaskIconScaledDown != isScaledDown) {
             mRunningTaskIconScaledDown = isScaledDown;