Live tile should switch to screenshot before finishing recents animation

Fixes: 139439373
Test: Launch a new app from shortcuts in Overview mode. Make sure the
live tile stays put (no hole). Same with touching empty space in
Overview to go home.

Change-Id: I6cacf2842e21f9856d0021cea9fddf4f870f09f0
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index ae8fd82..f2aa842 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -89,5 +89,5 @@
         return RotationMode.NORMAL;
     }
 
-    public static void clearSwipeSharedState(boolean finishAnimation) {}
+    public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) { }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index 46e883a..2f8af44 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 
 import android.content.Context;
@@ -196,11 +197,15 @@
         return new RecentsViewStateController(launcher);
     }
 
-    /**
-     * Clears the swipe shared state for the current swipe gesture.
-     */
-    public static void clearSwipeSharedState(boolean finishAnimation) {
-        TouchInteractionService.getSwipeSharedState().clearAllState(finishAnimation);
+    /** Clears the swipe shared state for the current swipe gesture. */
+    public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) {
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            launcher.<RecentsView>getOverviewPanel().switchToScreenshot(
+                    () -> TouchInteractionService.getSwipeSharedState().clearAllState(
+                            finishAnimation));
+        } else {
+            TouchInteractionService.getSwipeSharedState().clearAllState(finishAnimation);
+        }
     }
 
     /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/ViewUtils.java
new file mode 100644
index 0000000..cbb6ad4
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/ViewUtils.java
@@ -0,0 +1,74 @@
+/*
+ * 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 android.graphics.Canvas;
+import android.view.View;
+
+import com.android.systemui.shared.system.WindowCallbacksCompat;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Utility class for helpful methods related to {@link View} objects.
+ */
+public class ViewUtils {
+
+    /** See {@link #postDraw(View, Runnable, BooleanSupplier)}} */
+    public static boolean postDraw(View view, Runnable onFinishRunnable) {
+        return postDraw(view, onFinishRunnable, () -> false);
+    }
+
+    /**
+     * Inject some addition logic in order to make sure that the view is updated smoothly post
+     * draw, and allow addition task to be run after view update.
+     *
+     * @param onFinishRunnable runnable to be run right after the view finishes drawing.
+     */
+    public static boolean postDraw(View view, Runnable onFinishRunnable, BooleanSupplier canceled) {
+        // Defer finishing the animation until the next launcher frame with the
+        // new thumbnail
+        return new WindowCallbacksCompat(view) {
+            // The number of frames to defer until we actually finish the animation
+            private int mDeferFrameCount = 2;
+
+            @Override
+            public void onPostDraw(Canvas canvas) {
+                // If we were cancelled after this was attached, do not update
+                // the state.
+                if (canceled.getAsBoolean()) {
+                    detach();
+                    return;
+                }
+
+                if (mDeferFrameCount > 0) {
+                    mDeferFrameCount--;
+                    // Workaround, detach and reattach to invalidate the root node for
+                    // another draw
+                    detach();
+                    attach();
+                    view.invalidate();
+                    return;
+                }
+
+                if (onFinishRunnable != null) {
+                    onFinishRunnable.run();
+                }
+                detach();
+            }
+        }.attach();
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index db8eb27..c034579 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -45,7 +45,6 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Canvas;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.os.Build;
@@ -90,12 +89,10 @@
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.WindowCallbacksCompat;
 
 @TargetApi(Build.VERSION_CODES.O)
 public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
-        extends BaseSwipeUpHandler<T, RecentsView>
-        implements OnApplyWindowInsetsListener {
+        extends BaseSwipeUpHandler<T, RecentsView> implements OnApplyWindowInsetsListener {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
 
     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
@@ -1007,6 +1004,10 @@
         }
     }
 
+    public boolean isCanceled() {
+        return mCanceled;
+    }
+
     @UiThread
     private void resumeLastTask() {
         mRecentsAnimationWrapper.finish(false /* toRecents */, null);
@@ -1099,14 +1100,21 @@
     }
 
     private void switchToScreenshot() {
+        SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (controller != null) {
+                // Update the screenshot of the task
+                if (mTaskSnapshot == null) {
+                    mTaskSnapshot = controller.screenshotTask(mRunningTaskId);
+                }
+                mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot, false /* refreshNow */);
+            }
             setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
         } else if (!mRecentsAnimationWrapper.hasTargets()) {
             // If there are no targets, then we don't need to capture anything
             setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
         } else {
             boolean finishTransitionPosted = false;
-            SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
             if (controller != null) {
                 // Update the screenshot of the task
                 if (mTaskSnapshot == null) {
@@ -1123,34 +1131,8 @@
                 if (taskView != null && !mCanceled) {
                     // Defer finishing the animation until the next launcher frame with the
                     // new thumbnail
-                    finishTransitionPosted = new WindowCallbacksCompat(taskView) {
-
-                        // The number of frames to defer until we actually finish the animation
-                        private int mDeferFrameCount = 2;
-
-                        @Override
-                        public void onPostDraw(Canvas canvas) {
-                            // If we were cancelled after this was attached, do not update
-                            // the state.
-                            if (mCanceled) {
-                                detach();
-                                return;
-                            }
-
-                            if (mDeferFrameCount > 0) {
-                                mDeferFrameCount--;
-                                // Workaround, detach and reattach to invalidate the root node for
-                                // another draw
-                                detach();
-                                attach();
-                                taskView.invalidate();
-                                return;
-                            }
-
-                            setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
-                            detach();
-                        }
-                    }.attach();
+                    finishTransitionPosted = ViewUtils.postDraw(taskView,
+                            () -> setStateOnUiThread(STATE_SCREENSHOT_CAPTURED), this::isCanceled);
                 }
             }
             if (!finishTransitionPosted) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 0f9fc17..4c308fb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -78,7 +78,12 @@
 
     @Override
     public void startHome() {
-        mActivity.getStateManager().goToState(NORMAL);
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            switchToScreenshot(() -> finishRecentsAnimation(true /* toRecents */,
+                    () -> mActivity.getStateManager().goToState(NORMAL)));
+        } else {
+            mActivity.getStateManager().goToState(NORMAL);
+        }
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 554f437..29e93aa 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -107,6 +107,7 @@
 import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
+import com.android.quickstep.ViewUtils;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -380,14 +381,23 @@
         return null;
     }
 
-    public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
+    /**
+     * Update the thumbnail of the task.
+     * @param refreshNow Refresh immediately if it's true.
+     */
+    public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow) {
         TaskView taskView = getTaskView(taskId);
         if (taskView != null) {
-            taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData);
+            taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData, refreshNow);
         }
         return taskView;
     }
 
+    /** See {@link #updateThumbnail(int, ThumbnailData, boolean)} */
+    public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
+        return updateThumbnail(taskId, thumbnailData, true /* refreshNow */);
+    }
+
     @Override
     protected void onWindowVisibilityChanged(int visibility) {
         super.onWindowVisibilityChanged(visibility);
@@ -1819,4 +1829,19 @@
         final WindowInsets insets = getRootWindowInsets();
         return Math.max(insets.getSystemGestureInsets().right, insets.getSystemWindowInsetRight());
     }
+
+    /** If it's in the live tile mode, switch the running task into screenshot mode. */
+    public void switchToScreenshot(Runnable onFinishRunnable) {
+        TaskView taskView = getRunningTaskView();
+        if (taskView == null) {
+            if (onFinishRunnable != null) {
+                onFinishRunnable.run();
+            }
+            return;
+        }
+
+        taskView.setShowScreenshot(true);
+        taskView.getThumbnail().refresh();
+        ViewUtils.postDraw(taskView, onFinishRunnable);
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 5799c01..adeb974 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -134,16 +134,35 @@
     }
 
     /**
-     * Updates this thumbnail.
+     * Updates the thumbnail.
+     * @param refreshNow whether the {@code thumbnailData} will be used to redraw immediately.
+     *                   In most cases, we use the {@link #setThumbnail(Task, ThumbnailData)}
+     *                   version with {@code refreshNow} is true. The only exception is
+     *                   in the live tile case that we grab a screenshot when user enters Overview
+     *                   upon swipe up so that a usable screenshot is accessible immediately when
+     *                   recents animation needs to be finished / cancelled.
      */
-    public void setThumbnail(Task task, ThumbnailData thumbnailData) {
+    public void setThumbnail(Task task, ThumbnailData thumbnailData, boolean refreshNow) {
         mTask = task;
-        if (thumbnailData != null && thumbnailData.thumbnail != null) {
-            Bitmap bm = thumbnailData.thumbnail;
+        mThumbnailData =
+                (thumbnailData != null && thumbnailData.thumbnail != null) ? thumbnailData : null;
+        if (refreshNow) {
+            refresh();
+        }
+    }
+
+    /** See {@link #setThumbnail(Task, ThumbnailData, boolean)} */
+    public void setThumbnail(Task task, ThumbnailData thumbnailData) {
+        setThumbnail(task, thumbnailData, true /* refreshNow */);
+    }
+
+    /** Updates the shader, paint, matrix to redraw. */
+    public void refresh() {
+        if (mThumbnailData != null && mThumbnailData.thumbnail != null) {
+            Bitmap bm = mThumbnailData.thumbnail;
             bm.prepareToDraw();
             mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
             mPaint.setShader(mBitmapShader);
-            mThumbnailData = thumbnailData;
             updateThumbnailMatrix();
         } else {
             mBitmapShader = null;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 51802df..bfb9613 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -53,7 +53,6 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4f8b20e..b95f88f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1893,7 +1893,7 @@
             // recents animation into launcher. Defer launching the activity until Launcher is
             // next resumed.
             addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer));
-            UiFactory.clearSwipeSharedState(true /* finishAnimation */);
+            UiFactory.clearSwipeSharedState(this, true /* finishAnimation */);
             return true;
         }
 
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index 467ae02..6d9ed88 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -96,7 +96,8 @@
 
     public static void resetPendingActivityResults(Launcher launcher, int requestCode) { }
 
-    public static void clearSwipeSharedState(boolean finishAnimation) {}
+    /** No-op. */
+    public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) { }
 
     public static Person[] getPersons(ShortcutInfo si) {
         return Utilities.EMPTY_PERSON_ARRAY;