Start shortcuts close animation where open left off.

- Before we always started the close animation at 0 instead of
  the previous open progress, which looked janky.
- Shortened the animations' durations and start delays to
  account for the fact that the open animation was only
  partially finished when the close animation started.

Bug: 30465231
Change-Id: I958ee5f4543dbf1185f3d0229c55fc1b51929655
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index b651f25..37b6d04 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.shortcuts;
 
 import android.animation.Animator;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -26,6 +27,7 @@
 
 import com.android.launcher3.IconCache;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LogAccelerateInterpolator;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
@@ -36,7 +38,7 @@
  * A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
  * This lets us animate the DeepShortcutView (icon and text) separately from the background.
  */
-public class DeepShortcutView extends FrameLayout {
+public class DeepShortcutView extends FrameLayout implements ValueAnimator.AnimatorUpdateListener {
 
     private static final Point sTempPoint = new Point();
 
@@ -44,6 +46,7 @@
 
     private DeepShortcutTextView mBubbleText;
     private View mIconView;
+    private float mOpenAnimationProgress;
 
     public DeepShortcutView(Context context) {
         this(context, null, 0);
@@ -95,14 +98,41 @@
     }
 
     /**
-     * Creates an animator to play when the shortcut container is being opened or closed.
+     * Creates an animator to play when the shortcut container is being opened.
      */
-    public Animator createOpenCloseAnimation(
-            boolean isContainerAboveIcon, boolean pivotLeft, boolean isReverse) {
+    public Animator createOpenAnimation(boolean isContainerAboveIcon, boolean pivotLeft) {
         Point center = getIconCenter();
-        return new ZoomRevealOutlineProvider(center.x, center.y, mPillRect,
-                this, mIconView, isContainerAboveIcon, pivotLeft)
-                .createRevealAnimator(this, isReverse);
+        ValueAnimator openAnimator =  new ZoomRevealOutlineProvider(center.x, center.y,
+                mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft)
+                        .createRevealAnimator(this, false);
+        mOpenAnimationProgress = 0f;
+        openAnimator.addUpdateListener(this);
+        return openAnimator;
+    }
+
+    @Override
+    public void onAnimationUpdate(ValueAnimator valueAnimator) {
+        mOpenAnimationProgress = valueAnimator.getAnimatedFraction();
+    }
+
+    public boolean isOpenOrOpening() {
+        return mOpenAnimationProgress > 0;
+    }
+
+    /**
+     * Creates an animator to play when the shortcut container is being closed.
+     */
+    public Animator createCloseAnimation(boolean isContainerAboveIcon, boolean pivotLeft,
+            long duration) {
+        Point center = getIconCenter();
+        ValueAnimator closeAnimator =  new ZoomRevealOutlineProvider(center.x, center.y,
+                mPillRect, this, mIconView, isContainerAboveIcon, pivotLeft)
+                        .createRevealAnimator(this, true);
+        // Scale down the duration and interpolator according to the progress
+        // that the open animation was at when the close started.
+        closeAnimator.setDuration((long) (duration * mOpenAnimationProgress));
+        closeAnimator.setInterpolator(new CloseInterpolator(mOpenAnimationProgress));
+        return closeAnimator;
     }
 
     /**
@@ -113,7 +143,7 @@
         int iconCenterX = getIconCenter().x;
         return new PillWidthRevealOutlineProvider(mPillRect,
                 iconCenterX - halfHeight, iconCenterX + halfHeight)
-                .createRevealAnimator(this, true);
+                        .createRevealAnimator(this, true);
     }
 
     /**
@@ -168,4 +198,26 @@
             mTranslateView.setTranslationX(mTranslateX - pivotX);
         }
     }
+
+    /**
+     * An interpolator that reverses the current open animation progress.
+     */
+    private static class CloseInterpolator extends LogAccelerateInterpolator {
+        private float mStartProgress;
+        private float mRemainingProgress;
+
+        /**
+         * @param openAnimationProgress The progress that the open interpolator ended at.
+         */
+        public CloseInterpolator(float openAnimationProgress) {
+            super(100, 0);
+            mStartProgress = 1f - openAnimationProgress;
+            mRemainingProgress = openAnimationProgress;
+        }
+
+        @Override
+        public float getInterpolation(float v) {
+            return mStartProgress + super.getInterpolation(v) * mRemainingProgress;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
index 819a653..8865190 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -27,7 +27,6 @@
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.graphics.drawable.ShapeDrawable;
 import android.os.Build;
 import android.os.Handler;
@@ -54,7 +53,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherViewPropertyAnimator;
-import com.android.launcher3.LogAccelerateInterpolator;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
@@ -255,8 +253,7 @@
             final DeepShortcutView deepShortcutView = getShortcutAt(i);
             deepShortcutView.setVisibility(INVISIBLE);
 
-            Animator anim = deepShortcutView.createOpenCloseAnimation(
-                    mIsAboveIcon, mIsLeftAligned, false);
+            Animator anim = deepShortcutView.createOpenAnimation(mIsAboveIcon, mIsLeftAligned);
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationStart(Animator animation) {
@@ -623,24 +620,29 @@
         mLauncher.getDragController().removeDragListener(this);
 
         final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
-        final int numShortcuts = getShortcutCount();
+        final int shortcutCount = getShortcutCount();
+        int numOpenShortcuts = 0;
+        for (int i = 0; i < shortcutCount; i++) {
+            if (getShortcutAt(i).isOpenOrOpening()) {
+                numOpenShortcuts++;
+            }
+        }
         final long duration = getResources().getInteger(
                 R.integer.config_deepShortcutCloseDuration);
         final long stagger = getResources().getInteger(
                 R.integer.config_deepShortcutCloseStagger);
 
-        long arrowDelay = (numShortcuts - 1) * stagger + (duration * 4 / 6);
-        int firstShortcutIndex = mIsAboveIcon ? (numShortcuts - 1) : 0;
-        LogAccelerateInterpolator interpolator = new LogAccelerateInterpolator(100, 0);
-        for (int i = 0; i < numShortcuts; i++) {
+        long arrowDelay = (numOpenShortcuts - 1) * stagger + (duration * 4 / 6);
+        int firstOpenShortcutIndex = mIsAboveIcon ? shortcutCount - numOpenShortcuts : 0;
+        int shortcutWithArrowIndex = mIsAboveIcon ? (numOpenShortcuts - 1) : 0;
+        for (int i = firstOpenShortcutIndex; i < firstOpenShortcutIndex + numOpenShortcuts; i++) {
             final DeepShortcutView view = getShortcutAt(i);
             Animator anim;
             if (view.willDrawIcon()) {
-                anim = view.createOpenCloseAnimation(mIsAboveIcon, mIsLeftAligned, true);
-                int animationIndex = mIsAboveIcon ? i : numShortcuts - i - 1;
+                anim = view.createCloseAnimation(mIsAboveIcon, mIsLeftAligned, duration);
+                int animationIndex = mIsAboveIcon ? i - firstOpenShortcutIndex
+                        : numOpenShortcuts - i - 1;
                 anim.setStartDelay(stagger * animationIndex);
-                anim.setDuration(duration);
-                anim.setInterpolator(interpolator);
             } else {
                 // The view is being dragged. Animate it such that it collapses with the drag view
                 anim = view.collapseToIcon();
@@ -660,7 +662,7 @@
                 anim2.setDuration(DragView.VIEW_ZOOM_DURATION);
                 shortcutAnims.play(anim2);
 
-                if (i == firstShortcutIndex) {
+                if (i == shortcutWithArrowIndex) {
                     arrowDelay = 0;
                 }
             }
diff --git a/src/com/android/launcher3/util/RevealOutlineAnimation.java b/src/com/android/launcher3/util/RevealOutlineAnimation.java
index cd98882..4560477 100644
--- a/src/com/android/launcher3/util/RevealOutlineAnimation.java
+++ b/src/com/android/launcher3/util/RevealOutlineAnimation.java
@@ -38,6 +38,8 @@
         final float elevation = revealView.getElevation();
 
         va.addListener(new AnimatorListenerAdapter() {
+            private boolean mWasCanceled = false;
+
             public void onAnimationStart(Animator animation) {
                 revealView.setOutlineProvider(RevealOutlineAnimation.this);
                 revealView.setClipToOutline(true);
@@ -46,11 +48,18 @@
                 }
             }
 
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mWasCanceled = true;
+            }
+
             public void onAnimationEnd(Animator animation) {
-                revealView.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
-                revealView.setClipToOutline(false);
-                if (shouldRemoveElevationDuringAnimation()) {
-                    revealView.setTranslationZ(0);
+                if (!mWasCanceled) {
+                    revealView.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
+                    revealView.setClipToOutline(false);
+                    if (shouldRemoveElevationDuringAnimation()) {
+                        revealView.setTranslationZ(0);
+                    }
                 }
             }