Add additional fake task views on overview gesture completion.

Added motion pause listening to animate an additional fake task view on overview gestures in the sandbox.

Test: manual

Demo: https://drive.google.com/file/d/1EpewbIAxiMUEz0Fqdfbok5q9xpWS6NWz/view?usp=sharing
Change-Id: Ifd0aed0a2bbb3204ae32c833b8466952679700e3
diff --git a/quickstep/res/drawable/default_sandbox_app_previous_task_thumbnail.xml b/quickstep/res/drawable/default_sandbox_app_previous_task_thumbnail.xml
new file mode 100644
index 0000000..9c95497
--- /dev/null
+++ b/quickstep/res/drawable/default_sandbox_app_previous_task_thumbnail.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2020 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+  <solid android:color="@color/gesture_tutorial_fake_previous_task_view_color" />
+</shape>
diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml
index 2ff3a5e..9d06dfb 100644
--- a/quickstep/res/layout/gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/gesture_tutorial_fragment.xml
@@ -31,6 +31,14 @@
         android:visibility="invisible" />
 
     <View
+        android:id="@+id/gesture_tutorial_fake_previous_task_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleX="0.98"
+        android:scaleY="0.98"
+        android:visibility="invisible" />
+
+    <View
         android:id="@+id/gesture_tutorial_fake_task_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index d1b0a70..a9a9e2a 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -56,7 +56,7 @@
 
 /** Utility class to handle Home and Assistant gestures. */
 public class NavBarGestureHandler implements OnTouchListener,
-        TriggerSwipeUpTouchTracker.OnSwipeUpListener {
+        TriggerSwipeUpTouchTracker.OnSwipeUpListener, MotionPauseDetector.OnMotionPauseListener {
 
     private static final String LOG_TAG = "NavBarGestureHandler";
     private static final long RETRACT_GESTURE_ANIMATION_DURATION_MS = 300;
@@ -181,7 +181,7 @@
                 mLaunchedAssistant = false;
                 mSwipeUpTouchTracker.init();
                 mMotionPauseDetector.clear();
-                mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseDetected);
+                mMotionPauseDetector.setOnMotionPauseListener(this);
                 break;
             case MotionEvent.ACTION_MOVE:
                 mLastPos.set(event.getX(), event.getY());
@@ -256,7 +256,13 @@
                 || event.getY() >= mDisplaySize.y - mBottomGestureHeight;
     }
 
-    protected void onMotionPauseDetected() {
+    @Override
+    public void onMotionPauseChanged(boolean isPaused) {
+        mGestureCallback.onMotionPaused(isPaused);
+    }
+
+    @Override
+    public void onMotionPauseDetected() {
         VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
     }
 
@@ -311,6 +317,9 @@
         /** Called whenever any touch is completed. */
         void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity);
 
+        /** Called when a motion stops or resumes */
+        default void onMotionPaused(boolean isPaused) {}
+
         /** Indicates how far a touch originating in the nav bar has moved from the nav bar. */
         default void setNavBarGestureProgress(@Nullable Float displacement) {}
 
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 865b66e..68c63bf 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -61,16 +61,21 @@
 
 @TargetApi(Build.VERSION_CODES.R)
 abstract class SwipeUpGestureTutorialController extends TutorialController {
-    private final ViewSwipeUpAnimation mViewSwipeUpAnimation;
+
+    private static final int FAKE_PREVIOUS_TASK_MARGIN = Utilities.dpToPx(12);
+
+    private final ViewSwipeUpAnimation mTaskViewSwipeUpAnimation;
     private float mFakeTaskViewRadius;
     private Rect mFakeTaskViewRect = new Rect();
     private RunningWindowAnim mRunningWindowAnim;
+    private boolean mShowTasks = false;
+    private boolean mShowPreviousTasks = false;
 
     SwipeUpGestureTutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
         super(tutorialFragment, tutorialType);
         RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext);
         OverviewComponentObserver observer = new OverviewComponentObserver(mContext, deviceState);
-        mViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState,
+        mTaskViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState,
                 new GestureState(observer, -1));
         observer.onDestroy();
         deviceState.destroy();
@@ -83,16 +88,22 @@
                 .getWindowInsets()
                 .getInsets(WindowInsets.Type.systemBars());
         dp.updateInsets(new Rect(insets.left, insets.top, insets.right, insets.bottom));
-        mViewSwipeUpAnimation.initDp(dp);
+        mTaskViewSwipeUpAnimation.initDp(dp);
 
         mFakeTaskViewRadius = QuickStepContract.getWindowCornerRadius(mContext.getResources());
-        mFakeTaskView.setClipToOutline(true);
-        mFakeTaskView.setOutlineProvider(new ViewOutlineProvider() {
+
+        ViewOutlineProvider outlineProvider = new ViewOutlineProvider() {
             @Override
             public void getOutline(View view, Outline outline) {
                 outline.setRoundRect(mFakeTaskViewRect, mFakeTaskViewRadius);
             }
-        });
+        };
+
+        mFakeTaskView.setClipToOutline(true);
+        mFakeTaskView.setOutlineProvider(outlineProvider);
+
+        mFakePreviousTaskView.setClipToOutline(true);
+        mFakePreviousTaskView.setOutlineProvider(outlineProvider);
     }
 
     private void cancelRunningAnimation() {
@@ -114,16 +125,22 @@
                 mFakeIconView.setVisibility(View.INVISIBLE);
                 mFakeTaskView.setVisibility(View.INVISIBLE);
                 mFakeTaskView.setAlpha(1);
+                mFakePreviousTaskView.setVisibility(View.INVISIBLE);
+                mFakePreviousTaskView.setAlpha(1);
+                mShowTasks = false;
+                mShowPreviousTasks = false;
                 mRunningWindowAnim = null;
             }
         };
         if (toOverviewFirst) {
-            anim.setFloat(mViewSwipeUpAnimation.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
+            anim.setFloat(mTaskViewSwipeUpAnimation
+                    .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation, boolean isReverse) {
                     PendingAnimation fadeAnim = new PendingAnimation(300);
                     fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL);
+                    fadeAnim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL);
                     fadeAnim.addListener(resetTaskView);
                     AnimatorSet animset = fadeAnim.buildAnim();
                     animset.setStartDelay(100);
@@ -133,6 +150,7 @@
             });
         } else {
             anim.setViewAlpha(mFakeTaskView, 0, ACCEL);
+            anim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL);
             anim.setViewAlpha(mFakeIconView, 0, ACCEL);
             anim.addListener(resetTaskView);
         }
@@ -148,8 +166,10 @@
         hideFeedback();
         hideHandCoachingAnimation();
         cancelRunningAnimation();
+        mFakePreviousTaskView.setVisibility(View.INVISIBLE);
+        mShowPreviousTasks = false;
         RectFSpringAnim rectAnim =
-                mViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity);
+                mTaskViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity);
         // After home animation finishes, fade out and run onEndRunnable.
         rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable(
                 () -> fadeOutFakeTaskView(false, onEndRunnable)));
@@ -161,11 +181,31 @@
         if (displacement == null || mTutorialType == HOME_NAVIGATION_COMPLETE
                 || mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) {
             mFakeTaskView.setVisibility(View.INVISIBLE);
+            mFakePreviousTaskView.setVisibility(View.INVISIBLE);
         } else {
+            mShowTasks = true;
             mFakeTaskView.setVisibility(View.VISIBLE);
-            if (mRunningWindowAnim == null) {
-                mViewSwipeUpAnimation.updateDisplacement(displacement);
+            if (mShowPreviousTasks) {
+                mFakePreviousTaskView.setVisibility(View.VISIBLE);
             }
+            if (mRunningWindowAnim == null) {
+                mTaskViewSwipeUpAnimation.updateDisplacement(displacement);
+            }
+        }
+    }
+
+    @Override
+    public void onMotionPaused(boolean unused) {
+        if (mShowTasks) {
+            if (!mShowPreviousTasks) {
+                mFakePreviousTaskView.setTranslationX(
+                        -(2 * mFakePreviousTaskView.getWidth() + FAKE_PREVIOUS_TASK_MARGIN));
+                mFakePreviousTaskView.animate()
+                    .setDuration(300)
+                    .translationX(-(mFakePreviousTaskView.getWidth() + FAKE_PREVIOUS_TASK_MARGIN))
+                    .start();
+            }
+            mShowPreviousTasks = true;
         }
     }
 
@@ -232,6 +272,7 @@
                             false /* isVerticalBarLayout */);
                     mFakeIconView.setAlpha(1);
                     mFakeTaskView.setAlpha(getWindowAlpha(progress));
+                    mFakePreviousTaskView.setAlpha(getWindowAlpha(progress));
                 }
 
                 @Override
@@ -258,9 +299,11 @@
         public void applySurfaceParams(SurfaceParams[] params) {
             SurfaceParams p = params[0];
             mFakeTaskView.setAnimationMatrix(p.matrix);
+            mFakePreviousTaskView.setAnimationMatrix(p.matrix);
             mFakeTaskViewRect.set(p.windowCrop);
             mFakeTaskViewRadius = p.cornerRadius;
             mFakeTaskView.invalidateOutline();
+            mFakePreviousTaskView.invalidateOutline();
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 0d5a110..12d2efc 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -52,6 +52,7 @@
     final View mLauncherView;
     final ClipIconView mFakeIconView;
     final View mFakeTaskView;
+    final View mFakePreviousTaskView;
     final View mRippleView;
     final RippleDrawable mRippleDrawable;
     @Nullable final TutorialHandAnimation mHandCoachingAnimation;
@@ -74,6 +75,8 @@
         mLauncherView = getMockLauncherView();
         mFakeIconView = rootView.findViewById(R.id.gesture_tutorial_fake_icon_view);
         mFakeTaskView = rootView.findViewById(R.id.gesture_tutorial_fake_task_view);
+        mFakePreviousTaskView =
+                rootView.findViewById(R.id.gesture_tutorial_fake_previous_task_view);
         mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view);
         mRippleDrawable = (RippleDrawable) mRippleView.getBackground();
         mHandCoachingAnimation = tutorialFragment.getHandAnimation();
@@ -93,6 +96,8 @@
         if (mContext != null) {
             rootView.setBackground(mContext.getDrawable(getMockWallpaperResId()));
             mFakeTaskView.setBackground(mContext.getDrawable(getMockAppTaskThumbnailResId()));
+            mFakePreviousTaskView.setBackground(
+                    mContext.getDrawable(getMockPreviousAppTaskThumbnailResId()));
             mFakeIconView.setBackground(mContext.getDrawable(getMockAppIconResId()));
         }
     }
@@ -126,6 +131,11 @@
         return R.drawable.default_sandbox_app_task_thumbnail;
     }
 
+    @DrawableRes
+    protected int getMockPreviousAppTaskThumbnailResId() {
+        return R.drawable.default_sandbox_app_previous_task_thumbnail;
+    }
+
     @Nullable
     public View getMockLauncherView() {
         InvariantDeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext);
diff --git a/res/values/colors.xml b/res/values/colors.xml
index f56fbaa..78c2df6 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -37,6 +37,7 @@
 
     <color name="gesture_tutorial_ripple_color">#A0C2F9</color> <!-- Light Blue -->
     <color name="gesture_tutorial_fake_task_view_color">#6DA1FF</color> <!-- Light Blue -->
+    <color name="gesture_tutorial_fake_previous_task_view_color">#9CCC65</color> <!-- Light Green -->
     <color name="gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
     <color name="gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->
 </resources>
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 1e023df..5c2f35b 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -404,6 +404,11 @@
         return (size / densityRatio);
     }
 
+    /** Converts a dp value to pixels for the current device. */
+    public static int dpToPx(float dp) {
+        return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
+    }
+
     public static int pxFromSp(float size, DisplayMetrics metrics) {
         return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                 size, metrics));