[Live Tile] Support launching another task (other than the current running task) in Overview

- Get rid of the defer cancelation logic
- Render animation on the task view of the task being launched upon task view appeared callback
- Finish the recents animation upon the end of the recents window animation

Fixes: 164926736
Test: manual
Change-Id: Ibffb6a9c74c235efc8615a22b0306551532c7b61
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 38adf39..ae4bd96 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -16,14 +16,10 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
 import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
@@ -32,11 +28,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.PendingAnimation;
+import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 /**
@@ -53,60 +46,16 @@
     protected boolean isLaunchingFromRecents(@NonNull View v,
             @Nullable RemoteAnimationTargetCompat[] targets) {
         return mLauncher.getStateManager().getState().overviewUi
-                && findTaskViewToLaunch(mLauncher, v, targets) != null;
+                && findTaskViewToLaunch(mLauncher.getOverviewPanel(), v, targets) != null;
     }
 
     @Override
     protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
             @NonNull RemoteAnimationTargetCompat[] appTargets,
             @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing) {
-        RecentsView recentsView = mLauncher.getOverviewPanel();
-        boolean skipLauncherChanges = !launcherClosing;
-
-        TaskView taskView = findTaskViewToLaunch(mLauncher, v, appTargets);
-        PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
-        createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
-                mLauncher.getDepthController(), pa);
-        anim.play(pa.buildAnim());
-
-        Animator childStateAnimation = null;
-        // Found a visible recents task that matches the opening app, lets launch the app from there
-        Animator launcherAnim;
-        final AnimatorListenerAdapter windowAnimEndListener;
-        if (launcherClosing) {
-            launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
-            launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
-            launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
-
-            // Make sure recents gets fixed up by resetting task alphas and scales, etc.
-            windowAnimEndListener = new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mLauncher.getStateManager().moveToRestState();
-                    mLauncher.getStateManager().reapplyState();
-                }
-            };
-        } else {
-            AnimatorPlaybackController controller =
-                    mLauncher.getStateManager().createAnimationToNewWorkspace(NORMAL,
-                            RECENTS_LAUNCH_DURATION);
-            controller.dispatchOnStart();
-            childStateAnimation = controller.getTarget();
-            launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
-            windowAnimEndListener = new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mLauncher.getStateManager().goToState(NORMAL, false);
-                }
-            };
-        }
-        anim.play(launcherAnim);
-
-        // Set the current animation first, before adding windowAnimEndListener. Setting current
-        // animation adds some listeners which need to be called before windowAnimEndListener
-        // (the ordering of listeners matter in this case).
-        mLauncher.getStateManager().setCurrentAnimation(anim, childStateAnimation);
-        anim.addListener(windowAnimEndListener);
+        TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
+                launcherClosing, mLauncher.getStateManager(), mLauncher.getOverviewPanel(),
+                mLauncher.getDepthController());
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 5e05a7d..941a10b 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -41,10 +41,14 @@
 import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
 import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
@@ -104,6 +108,7 @@
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.function.Consumer;
 
 /**
@@ -1370,17 +1375,64 @@
     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
         endLauncherTransitionController();
         mActivityInterface.onSwipeUpToRecentsComplete();
-        if (mRecentsAnimationController != null) {
-            mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
-                    true /* screenshot */);
-        }
         mRecentsView.onSwipeUpAnimationSuccess();
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            mTaskAnimationManager.setLaunchOtherTaskInLiveTileModeHandler(
+                    this::launchOtherTaskInLiveTileMode);
+        }
 
         SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
         doLogGesture(RECENTS, mRecentsView.getCurrentPageTaskView());
         reset();
     }
 
+    private void launchOtherTaskInLiveTileMode(RemoteAnimationTargetCompat appearedTaskTarget) {
+        TaskView taskView = mRecentsView.getTaskView(appearedTaskTarget.taskId);
+        if (taskView == null) {
+            return;
+        }
+
+        RemoteAnimationTargetCompat[] apps = Arrays.copyOf(
+                mRecentsAnimationTargets.apps,
+                mRecentsAnimationTargets.apps.length + 1);
+        apps[apps.length - 1] = appearedTaskTarget;
+        boolean launcherClosing =
+                taskIsATargetWithMode(apps, mActivity.getTaskId(), MODE_CLOSING);
+
+        AnimatorSet anim = new AnimatorSet();
+        TaskViewUtils.composeRecentsLaunchAnimator(
+                anim, taskView, apps,
+                mRecentsAnimationTargets.wallpapers, launcherClosing,
+                mActivity.getStateManager(), mRecentsView,
+                mActivityInterface.getDepthController());
+        anim.addListener(new AnimatorListenerAdapter(){
+
+            @Override
+            public void onAnimationEnd(Animator animator) {
+                cleanUp(false);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animator) {
+                cleanUp(true);
+            }
+
+            private void cleanUp(boolean canceled) {
+                if (mRecentsAnimationController != null) {
+                    mRecentsAnimationController.finish(false /* toRecents */,
+                            null /* onFinishComplete */);
+                    if (canceled) {
+                        mRecentsAnimationController = null;
+                    } else {
+                        mActivityInterface.onLaunchTaskSuccess();
+                    }
+                    ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+                }
+            }
+        });
+        anim.start();
+    }
+
     private void addLiveTileOverlay() {
         if (LiveTileOverlay.INSTANCE.attach(mActivity.getRootView().getOverlay())) {
             mRecentsView.setLiveTileOverlayAttached(true);
@@ -1438,39 +1490,33 @@
 
     protected void startNewTask(Consumer<Boolean> resultCallback) {
         // Launch the task user scrolled to (mRecentsView.getNextPage()).
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            // We finish recents animation inside launchTask() when live tile is enabled.
-            mRecentsView.getNextPageTaskView().launchTask(false /* animate */,
-                    true /* freezeTaskList */);
-        } else {
-            if (!mCanceled) {
-                TaskView nextTask = mRecentsView.getNextPageTaskView();
-                if (nextTask != null) {
-                    int taskId = nextTask.getTask().key.id;
-                    mGestureState.updateLastStartedTaskId(taskId);
-                    boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
-                            .contains(taskId);
-                    nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
-                            success -> {
-                                resultCallback.accept(success);
-                                if (success) {
-                                    if (hasTaskPreviouslyAppeared) {
-                                        onRestartPreviouslyAppearedTask();
-                                    }
-                                } else {
-                                    mActivityInterface.onLaunchTaskFailed();
-                                    nextTask.notifyTaskLaunchFailed(TAG);
-                                    mRecentsAnimationController.finish(true /* toRecents */, null);
+        if (!mCanceled) {
+            TaskView nextTask = mRecentsView.getNextPageTaskView();
+            if (nextTask != null) {
+                int taskId = nextTask.getTask().key.id;
+                mGestureState.updateLastStartedTaskId(taskId);
+                boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
+                        .contains(taskId);
+                nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
+                        success -> {
+                            resultCallback.accept(success);
+                            if (success) {
+                                if (hasTaskPreviouslyAppeared) {
+                                    onRestartPreviouslyAppearedTask();
                                 }
-                            }, MAIN_EXECUTOR.getHandler());
-                } else {
-                    mActivityInterface.onLaunchTaskFailed();
-                    Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show();
-                    mRecentsAnimationController.finish(true /* toRecents */, null);
-                }
+                            } else {
+                                mActivityInterface.onLaunchTaskFailed();
+                                nextTask.notifyTaskLaunchFailed(TAG);
+                                mRecentsAnimationController.finish(true /* toRecents */, null);
+                            }
+                        }, MAIN_EXECUTOR.getHandler());
+            } else {
+                mActivityInterface.onLaunchTaskFailed();
+                Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show();
+                mRecentsAnimationController.finish(true /* toRecents */, null);
             }
-            mCanceled = false;
         }
+        mCanceled = false;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 51f5e5d..21e6689 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -89,24 +89,6 @@
     }
 
     /**
-     * Notifies the controller that we want to defer cancel until the next app transition starts.
-     * If {@param screenshot} is set, then we will receive a screenshot on the next
-     * {@link RecentsAnimationCallbacks#onAnimationCanceled(ThumbnailData)} and we must also call
-     * {@link #cleanupScreenshot()} when that screenshot is no longer used.
-     */
-    public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
-        mController.setDeferCancelUntilNextTransition(defer, screenshot);
-    }
-
-    /**
-     * Cleans up the screenshot previously returned from
-     * {@link RecentsAnimationCallbacks#onAnimationCanceled(ThumbnailData)}.
-     */
-    public void cleanupScreenshot() {
-        UI_HELPER_EXECUTOR.execute(() -> mController.cleanupScreenshot());
-    }
-
-    /**
      * Remove task remote animation target from
      * {@link RecentsAnimationCallbacks#onTaskAppeared(RemoteAnimationTargetCompat)}}.
      */
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index cad51f4..f38c1ea 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
 
@@ -31,6 +32,8 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
+import java.util.function.Consumer;
+
 public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
 
     private RecentsAnimationController mController;
@@ -39,6 +42,7 @@
     // Temporary until we can hook into gesture state events
     private GestureState mLastGestureState;
     private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
+    private Consumer<RemoteAnimationTargetCompat> mLaunchOtherTaskHandler;
 
     /**
      * Preloads the recents animation.
@@ -88,22 +92,21 @@
 
             @Override
             public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
-                if (thumbnailData != null) {
-                    // If a screenshot is provided, switch to the screenshot before cleaning up
-                    activityInterface.switchRunningTaskViewToScreenshot(thumbnailData,
-                            () -> cleanUpRecentsAnimation(thumbnailData));
-                } else {
-                    cleanUpRecentsAnimation(null /* canceledThumbnail */);
-                }
+                cleanUpRecentsAnimation();
             }
 
             @Override
             public void onRecentsAnimationFinished(RecentsAnimationController controller) {
-                cleanUpRecentsAnimation(null /* canceledThumbnail */);
+                cleanUpRecentsAnimation();
             }
 
             @Override
             public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+                if (mLaunchOtherTaskHandler != null
+                        && mLastGestureState.getEndTarget() == RECENTS) {
+                    mLaunchOtherTaskHandler.accept(appearedTaskTarget);
+                    return;
+                }
                 if (mController != null) {
                     if (mLastAppearedTaskTarget == null
                             || appearedTaskTarget.taskId != mLastAppearedTaskTarget.taskId) {
@@ -138,6 +141,15 @@
     }
 
     /**
+     * The passed-in handler is used to render side task launch animation in recents in live tile
+     * mode.
+     */
+    public void setLaunchOtherTaskInLiveTileModeHandler(
+            Consumer<RemoteAnimationTargetCompat> handler) {
+        mLaunchOtherTaskHandler = handler;
+    }
+
+    /**
      * Finishes the running recents animation.
      */
     public void finishRunningRecentsAnimation(boolean toHome) {
@@ -146,7 +158,7 @@
             Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
                     ? mController::finishAnimationToHome
                     : mController::finishAnimationToApp);
-            cleanUpRecentsAnimation(null /* canceledThumbnail */);
+            cleanUpRecentsAnimation();
         }
     }
 
@@ -173,12 +185,7 @@
     /**
      * Cleans up the recents animation entirely.
      */
-    private void cleanUpRecentsAnimation(ThumbnailData canceledThumbnail) {
-        // Clean up the screenshot if necessary
-        if (mController != null && canceledThumbnail != null) {
-            mController.cleanupScreenshot();
-        }
-
+    private void cleanUpRecentsAnimation() {
         // Release all the target leashes
         if (mTargets != null) {
             mTargets.release();
@@ -194,6 +201,7 @@
         mTargets = null;
         mLastGestureState = null;
         mLastAppearedTaskTarget = null;
+        mLaunchOtherTaskHandler = null;
     }
 
     public void dump() {
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index a5af181..7299c38 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -17,15 +17,20 @@
 
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
 import android.content.ComponentName;
 import android.content.Context;
@@ -35,12 +40,16 @@
 import android.os.Build;
 import android.view.View;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.BaseActivity;
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TaskViewSimulator;
@@ -67,8 +76,7 @@
      * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
      */
     public static TaskView findTaskViewToLaunch(
-            BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets) {
-        RecentsView recentsView = activity.getOverviewPanel();
+            RecentsView recentsView, View v, RemoteAnimationTargetCompat[] targets) {
         if (v instanceof TaskView) {
             TaskView taskView = (TaskView) v;
             return recentsView.isTaskViewVisible(taskView) ? taskView : null;
@@ -130,7 +138,8 @@
 
         SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
         final RemoteAnimationTargets targets =
-                new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_OPENING);
+                new RemoteAnimationTargets(appTargets, wallpaperTargets,
+                        ENABLE_QUICKSTEP_LIVE_TILE.get() ? MODE_CLOSING : MODE_OPENING);
         targets.addReleaseCheck(applier);
 
         TransformParams params = new TransformParams()
@@ -235,4 +244,57 @@
                     TOUCH_RESPONSE_INTERPOLATOR);
         }
     }
+
+    public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
+            @NonNull RemoteAnimationTargetCompat[] appTargets,
+            @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing,
+            @NonNull StateManager stateManager, @NonNull RecentsView recentsView,
+            @NonNull DepthController depthController) {
+        boolean skipLauncherChanges = !launcherClosing;
+
+        TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets);
+        PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
+        createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
+                depthController, pa);
+        anim.play(pa.buildAnim());
+
+        Animator childStateAnimation = null;
+        // Found a visible recents task that matches the opening app, lets launch the app from there
+        Animator launcherAnim;
+        final AnimatorListenerAdapter windowAnimEndListener;
+        if (launcherClosing) {
+            launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
+            launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
+            launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
+
+            // Make sure recents gets fixed up by resetting task alphas and scales, etc.
+            windowAnimEndListener = new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    stateManager.moveToRestState();
+                    stateManager.reapplyState();
+                }
+            };
+        } else {
+            AnimatorPlaybackController controller =
+                    stateManager.createAnimationToNewWorkspace(NORMAL,
+                            RECENTS_LAUNCH_DURATION);
+            controller.dispatchOnStart();
+            childStateAnimation = controller.getTarget();
+            launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
+            windowAnimEndListener = new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    stateManager.goToState(NORMAL, false);
+                }
+            };
+        }
+        anim.play(launcherAnim);
+
+        // Set the current animation first, before adding windowAnimEndListener. Setting current
+        // animation adds some listeners which need to be called before windowAnimEndListener
+        // (the ordering of listeners matter in this case).
+        stateManager.setCurrentAnimation(anim, childStateAnimation);
+        anim.addListener(windowAnimEndListener);
+    }
 }