Make recents animation work with fallback activity.

Bug: 77157702
Bug: 77152886
Change-Id: Ide312b750efb8214a7c262f7380b5dbd2ef4647f
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index e4a8f36..307345a 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -106,6 +106,8 @@
     private DeviceProfile mDeviceProfile;
     private View mFloatingView;
 
+    private RemoteAnimationRunnerCompat mRemoteAnimationOverride;
+
     private final AnimatorListenerAdapter mReapplyStateListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
@@ -175,6 +177,10 @@
         return getDefaultActivityLaunchOptions(launcher, v);
     }
 
+    public void setRemoteAnimationOverride(RemoteAnimationRunnerCompat remoteAnimationOverride) {
+        mRemoteAnimationOverride = remoteAnimationOverride;
+    }
+
     /**
      * Try to find a TaskView that corresponds with the component of the launched view.
      *
@@ -635,6 +641,7 @@
      * Registers remote animations used when closing apps to home screen.
      */
     private void registerRemoteAnimations() {
+        // Unregister this
         if (hasControlRemoteAppTransitionPermission()) {
             try {
                 RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
@@ -669,12 +676,36 @@
     private RemoteAnimationRunnerCompat getWallpaperOpenRunner() {
         return new LauncherAnimationRunner(mHandler) {
             @Override
-            public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
-                if (mLauncher.getStateManager().getState().overviewUi) {
-                    // We use a separate transition for Overview mode.
-                    return null;
+            public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats,
+                    Runnable runnable) {
+                if (mLauncher.getStateManager().getState().overviewUi
+                        && mRemoteAnimationOverride != null) {
+                    // This transition is only used for the fallback activity and should not be
+                    // managed here (but necessary to implement here since the defined remote
+                    // animation currently takes precendence over the one defined in the activity
+                    // options).
+                    mRemoteAnimationOverride.onAnimationStart(targetCompats, runnable);
+                    return;
                 }
+                super.onAnimationStart(targetCompats, runnable);
+            }
 
+            @Override
+            public void onAnimationCancelled() {
+                if (mLauncher.getStateManager().getState().overviewUi
+                        && mRemoteAnimationOverride != null) {
+                    // This transition is only used for the fallback activity and should not be
+                    // managed here (but necessary to implement here since the defined remote
+                    // animation currently takes precendence over the one defined in the activity
+                    // options).
+                    mRemoteAnimationOverride.onAnimationCancelled();
+                    return;
+                }
+                super.onAnimationCancelled();
+            }
+
+            @Override
+            public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
                 AnimatorSet anim = new AnimatorSet();
                 anim.play(getClosingWindowAnimators(targetCompats));
 
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 4e3528c..3e96c44 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -36,8 +36,10 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppTransitionManagerImpl;
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.util.ViewOnDrawExecutor;
@@ -80,7 +82,10 @@
 
     ActivityInitListener createActivityInitListener(BiPredicate<T, Boolean> onInitListener);
 
-    void startRecents(Context context, Intent intent, AssistDataReceiver assistDataReceiver,
+    void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver,
+            final RecentsAnimationListener remoteAnimationListener);
+
+    void startRecentsFromButton(Context context, Intent intent,
             RecentsAnimationListener remoteAnimationListener);
 
     @UiThread
@@ -203,21 +208,44 @@
         }
 
         @Override
-        public void startRecents(Context context, Intent intent,
-                AssistDataReceiver assistDataReceiver,
-                RecentsAnimationListener remoteAnimationListener) {
+        public void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver,
+                final RecentsAnimationListener remoteAnimationListener) {
             ActivityManagerWrapper.getInstance().startRecentsActivity(
                     intent, assistDataReceiver, remoteAnimationListener, null, null);
         }
 
+        @Override
+        public void startRecentsFromButton(Context context, Intent intent,
+                RecentsAnimationListener remoteAnimationListener) {
+            // We should use the remove animation for the fallback activity recents button case,
+            // it works better with PiP.  In Launcher, we have already registered the remote
+            // animation definition, which takes priority over explicitly defined remote
+            // animations in the provided activity options when starting the activity, so we
+            // just register a remote animation factory to get a callback to handle this.
+            LauncherAppTransitionManagerImpl appTransitionManager =
+                    (LauncherAppTransitionManagerImpl) getLauncher().getAppTransitionManager();
+            appTransitionManager.setRemoteAnimationOverride(new RecentsAnimationActivityOptions(
+                    remoteAnimationListener, () -> {
+                        // Once the controller is finished, also reset the remote animation override
+                        appTransitionManager.setRemoteAnimationOverride(null);
+                    }));
+            context.startActivity(intent);
+        }
+
+        @Nullable
+        @UiThread
+        private Launcher getLauncher() {
+            LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+            if (app == null) {
+                return null;
+            }
+            return (Launcher) app.getModel().getCallback();
+        }
+
         @Nullable
         @UiThread
         private Launcher getVisibleLaucher() {
-            LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-            if (app == null) {
-                return null;
-            }
-            Launcher launcher = (Launcher) app.getModel().getCallback();
+            Launcher launcher = getLauncher();
             return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
                     launcher : null;
         }
@@ -325,12 +353,23 @@
         }
 
         @Override
-        public void startRecents(Context context, Intent intent,
-                AssistDataReceiver assistDataReceiver,
+        public void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver,
                 final RecentsAnimationListener remoteAnimationListener) {
-            ActivityOptions options =
-                    ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
-                            new FallbackActivityOptions(remoteAnimationListener), 10000, 10000));
+            // We can use the normal recents animation for swipe up
+            ActivityManagerWrapper.getInstance().startRecentsActivity(
+                    intent, assistDataReceiver, remoteAnimationListener, null, null);
+        }
+
+        @Override
+        public void startRecentsFromButton(Context context, Intent intent,
+                RecentsAnimationListener remoteAnimationListener) {
+            // We should use the remove animation for the fallback activity recents button case,
+            // it works better with PiP. For the fallback activity, we should not have registered
+            // the launcher app transition manager, so we should just start the remote animation here.
+            ActivityOptions options = ActivityOptionsCompat.makeRemoteAnimation(
+                    new RemoteAnimationAdapterCompat(
+                            new RecentsAnimationActivityOptions(remoteAnimationListener, null),
+                            10000, 10000));
             context.startActivity(intent, options.toBundle());
         }
 
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 0ab2df7..4d695de 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -218,7 +218,7 @@
         handler.initWhenReady();
 
         TraceHelper.beginSection("RecentsController");
-        Runnable startActivity = () -> mActivityControlHelper.startRecents(this, mHomeIntent,
+        Runnable startActivity = () -> mActivityControlHelper.startRecentsFromSwipe(mHomeIntent,
                 new AssistDataReceiver() {
                     @Override
                     public void onHandleAssistData(Bundle bundle) {
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 985a364..8e59578 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -53,6 +53,7 @@
 import com.android.quickstep.util.SysuiEventLogger;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.shared.recents.view.RecentsTransition;
@@ -77,7 +78,7 @@
     private static final int RID_CANCEL_CONTROLLER = 1;
     private static final int RID_CANCEL_ZOOM_OUT_ANIMATION = 2;
 
-    private static final long RECENTS_LAUNCH_DURATION = 150;
+    private static final long RECENTS_LAUNCH_DURATION = 200;
 
     private static final String TAG = "OverviewCommandHelper";
     private static final boolean DEBUG_START_FALLBACK_ACTIVITY = false;
@@ -151,12 +152,13 @@
     private void initSwipeHandler(ActivityControlHelper helper, long time,
             Consumer<WindowTransformSwipeHandler> onAnimationInitCallback) {
         final int commandId = mCurrentCommandId;
-        RunningTaskInfo taskInfo = ActivityManagerWrapper.getInstance().getRunningTask();
+        final RunningTaskInfo runningTask = ActivityManagerWrapper.getInstance().getRunningTask();
+        final int runningTaskId = runningTask.id;
         final WindowTransformSwipeHandler handler =
-                new WindowTransformSwipeHandler(taskInfo, mContext, time, helper);
+                new WindowTransformSwipeHandler(runningTask, mContext, time, helper);
 
         // Preload the plan
-        mRecentsModel.loadTasks(taskInfo.id, null);
+        mRecentsModel.loadTasks(runningTaskId, null);
         mWindowTransformSwipeHandler = handler;
 
         mTempTaskTargetRect.setEmpty();
@@ -172,13 +174,8 @@
         addFinishCommand(commandId, RID_RESET_SWIPE_HANDLER, handler::reset);
 
         TraceHelper.beginSection(TAG);
-        Runnable startActivity = () -> helper.startRecents(mContext, homeIntent,
-                new AssistDataReceiver() {
-                    @Override
-                    public void onHandleAssistData(Bundle bundle) {
-                        mRecentsModel.preloadAssistData(taskInfo.id, bundle);
-                    }
-                },
+        Runnable startActivity = () -> helper.startRecentsFromButton(mContext,
+                addToIntent(homeIntent),
                 new RecentsAnimationListener() {
                     public void onAnimationStart(
                             RecentsAnimationControllerCompat controller,
@@ -190,11 +187,17 @@
                                     minimizedHomeBounds);
                             mTempTaskTargetRect.set(handler.getTargetRect(mWindowSize));
 
+                            ThumbnailData thumbnail = mAM.getTaskThumbnail(runningTaskId,
+                                    true /* reducedResolution */);
                             mMainThreadExecutor.execute(() -> {
                                 addFinishCommand(commandId,
                                         RID_CANCEL_CONTROLLER, () -> controller.finish(true));
                                 if (commandId == mCurrentCommandId) {
                                     onAnimationInitCallback.accept(handler);
+
+                                    // The animation has started, which means the other activity
+                                    // should be paused, lets update the thumbnail
+                                    handler.switchToScreenshotImmediate(thumbnail);
                                 }
                             });
                         } else {
@@ -230,7 +233,7 @@
         });
         handler.onGestureStarted();
         anim.setDuration(RECENTS_LAUNCH_DURATION);
-        anim.setInterpolator(Interpolators.AGGRESSIVE_EASE);
+        anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
         anim.start();
         addFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION, anim::cancel);
     }
@@ -241,6 +244,7 @@
             return;
         }
 
+        ActivityManagerWrapper.getInstance().closeSystemWindows("recentapps");
         long time = SystemClock.elapsedRealtime();
         mMainThreadExecutor.execute(() -> {
             long elapsedTime = time - mLastToggleTime;
@@ -248,40 +252,37 @@
 
             mCurrentCommandId++;
             mTempTaskTargetRect.round(mTaskTargetRect);
-            boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
             int runnableCount = mCurrentCommandFinishRunnables.size();
             if (runnableCount > 0) {
                 for (int i = 0; i < runnableCount; i++) {
                     mCurrentCommandFinishRunnables.valueAt(i).run();
                 }
                 mCurrentCommandFinishRunnables.clear();
-                isQuickTap = true;
             }
 
+            // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows
+            //       the menu activity which takes window focus, prevening the right condition from
+            //       being run below
             ActivityControlHelper helper = getActivityControlHelper();
             RecentsView recents = helper.getVisibleRecentsView();
             if (recents != null) {
-                int childCount = recents.getChildCount();
-                if (childCount != 0) {
-                    ((TaskView) recents.getChildAt(childCount >= 2 ? 1 : 0)).launchTask(true);
+                // Launch the next task
+                recents.showNextTask();
+            } 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;
                 }
 
-                // There are not enough tasks. Skip
-                return;
+                // Start overview
+                if (helper.switchToRecentsIfVisible()) {
+                    SysuiEventLogger.writeDummyRecentsTransition(0);
+                    // Do nothing
+                } else {
+                    initSwipeHandler(helper, time, this::startZoomOutAnim);
+                }
             }
-
-            if (isQuickTap) {
-                // Focus last task. Start is on background thread so that all ActivityManager calls
-                // are serialized
-                BackgroundExecutor.get().submit(this::startLastTask);
-                return;
-            }
-            if (helper.switchToRecentsIfVisible()) {
-                SysuiEventLogger.writeDummyRecentsTransition(0);
-                return;
-            }
-
-            initSwipeHandler(helper, time, this::startZoomOutAnim);
         });
     }
 
@@ -362,10 +363,6 @@
         return false;
     }
 
-    public boolean isUsingFallbackActivity() {
-        return DEBUG_START_FALLBACK_ACTIVITY;
-    }
-
     public ActivityControlHelper getActivityControlHelper() {
         if (DEBUG_START_FALLBACK_ACTIVITY) {
             return new FallbackActivityControllerHelper();
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityOptions.java b/quickstep/src/com/android/quickstep/RecentsAnimationActivityOptions.java
similarity index 71%
rename from quickstep/src/com/android/quickstep/FallbackActivityOptions.java
rename to quickstep/src/com/android/quickstep/RecentsAnimationActivityOptions.java
index 04352c3..a25e192 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityOptions.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationActivityOptions.java
@@ -24,24 +24,33 @@
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.TransactionCompat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
+import java.util.function.Consumer;
 
 /**
- * Temporary class to create activity options to emulate recents transition for fallback activtiy.
+ * Class to create activity options to emulate recents transition.
  */
-public class FallbackActivityOptions implements RemoteAnimationRunnerCompat {
+public class RecentsAnimationActivityOptions implements RemoteAnimationRunnerCompat {
 
     private final RecentsAnimationListener mListener;
+    private final Runnable mFinishCallback;
 
-    public FallbackActivityOptions(RecentsAnimationListener listener) {
+    public RecentsAnimationActivityOptions(RecentsAnimationListener listener,
+            Runnable finishCallback) {
         mListener = listener;
+        mFinishCallback = finishCallback;
     }
 
     @Override
     public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats,
             Runnable runnable) {
         showOpeningTarget(targetCompats);
-        DummyRecentsAnimationControllerCompat dummyRecentsAnim =
-                new DummyRecentsAnimationControllerCompat(runnable);
+        RemoteRecentsAnimationControllerCompat dummyRecentsAnim =
+                new RemoteRecentsAnimationControllerCompat(() -> {
+                    runnable.run();
+                    if (mFinishCallback != null) {
+                        mFinishCallback.run();
+                    }
+                });
 
         Rect insets = new Rect();
         WindowManagerWrapper.getInstance().getStableInsets(insets);
@@ -54,23 +63,22 @@
     }
 
     private void showOpeningTarget(RemoteAnimationTargetCompat[] targetCompats) {
+        TransactionCompat t = new TransactionCompat();
         for (RemoteAnimationTargetCompat target : targetCompats) {
-            TransactionCompat t = new TransactionCompat();
             int layer = target.mode == RemoteAnimationTargetCompat.MODE_CLOSING
                     ? Integer.MAX_VALUE
                     : target.prefixOrderIndex;
             t.setLayer(target.leash, layer);
             t.show(target.leash);
-            t.apply();
         }
+        t.apply();
     }
 
-    private static class DummyRecentsAnimationControllerCompat
-            extends RecentsAnimationControllerCompat {
+    private class RemoteRecentsAnimationControllerCompat extends RecentsAnimationControllerCompat {
 
         final Runnable mFinishCallback;
 
-        public DummyRecentsAnimationControllerCompat(Runnable finishCallback) {
+        public RemoteRecentsAnimationControllerCompat(Runnable finishCallback) {
             mFinishCallback = finishCallback;
         }
 
@@ -87,7 +95,8 @@
 
         @Override
         public void finish(boolean toHome) {
-            if (toHome) {
+            // This should never be called with toHome == false
+            if (mFinishCallback != null) {
                 mFinishCallback.run();
             }
         }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 1e43202..4652f2d 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -159,6 +159,16 @@
     }
 
     @Override
+    public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
+        mTaskChangeId++;
+    }
+
+    @Override
+    public void onActivityUnpinned() {
+        mTaskChangeId++;
+    }
+
+    @Override
     public void onTaskStackChanged() {
         mTaskChangeId++;
 
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 36a9d56..975c62b 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -196,6 +196,10 @@
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
 
+    // Only used with the recents activity, when the screenshot should be fetched at the beginning
+    // of the animation and not at the end when the activity is already paused
+    private boolean mSkipScreenshotAtEndOfTransition;
+
     WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs,
             ActivityControlHelper<T> controller) {
         mContext = context;
@@ -582,7 +586,6 @@
                     mSourceStackBounds.set(target.sourceContainerBounds);
 
                     initTransitionEndpoints(dp);
-                    break;
                 }
             }
         }
@@ -725,8 +728,9 @@
                         () -> setStateOnUiThread(STATE_SWITCH_TO_SCREENSHOT_COMPLETE));
             }
         };
+
         synchronized (mRecentsAnimationWrapper) {
-            if (mRecentsAnimationWrapper.controller != null) {
+            if (mRecentsAnimationWrapper.controller != null && !mSkipScreenshotAtEndOfTransition) {
                 TransactionCompat transaction = new TransactionCompat();
                 for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
                     if (app.mode == MODE_CLOSING) {
@@ -753,6 +757,12 @@
         doLogGesture(true /* toLauncher */);
     }
 
+    @UiThread
+    public void switchToScreenshotImmediate(ThumbnailData thumbnail) {
+        mRecentsView.updateThumbnail(mRunningTaskId, thumbnail);
+        mSkipScreenshotAtEndOfTransition = true;
+    }
+
     private void setupLauncherUiAfterSwipeUpAnimation() {
         if (mLauncherTransitionController != null) {
             mLauncherTransitionController.getAnimationPlayer().end();
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index e7f69b7..592dc2d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -126,6 +126,26 @@
                 }
             }
         }
+
+        @Override
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
+            // Check this is for the right user
+            if (!checkCurrentUserId(userId, false /* debug */)) {
+                return;
+            }
+
+            // Remove the task immediately from the task list
+            TaskView taskView = getTaskView(taskId);
+            if (taskView != null) {
+                removeView(taskView);
+            }
+        }
+
+        @Override
+        public void onActivityUnpinned() {
+            // TODO: Re-enable layout transitions for addition of the unpinned task
+            reloadIfNeeded();
+        }
     };
 
     private int mLoadPlanId = -1;
@@ -581,6 +601,23 @@
         }
     }
 
+    public void showNextTask() {
+        TaskView runningTaskView = getTaskView(mRunningTaskId);
+        if (runningTaskView == null) {
+            // Launch the first task
+            if (getChildCount() > 0) {
+                ((TaskView) getChildAt(0)).launchTask(true /* animate */);
+            }
+        } else {
+            // Get the next launch task
+            int runningTaskIndex = indexOfChild(runningTaskView);
+            int nextTaskIndex = Math.max(0, Math.min(getChildCount() - 1, runningTaskIndex + 1));
+            if (nextTaskIndex < getChildCount()) {
+                ((TaskView) getChildAt(nextTaskIndex)).launchTask(true /* animate */);
+            }
+        }
+    }
+
     public QuickScrubController getQuickScrubController() {
         return mQuickScrubController;
     }