Merge "Change interpolation logic to all apps transitioning on fling b/30486958" into ub-launcher3-calgary
diff --git a/res/interpolator/caret_animation_interpolator.xml b/res/interpolator/caret_animation_interpolator.xml
deleted file mode 100644
index 25af4bc..0000000
--- a/res/interpolator/caret_animation_interpolator.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<pathInterpolator
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:pathData="M 0.0,0.0 c 0.0001,0.0 0.0,1.0 1.0,1.0" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2bc0cae..d11d5a5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -254,4 +254,7 @@
     <!-- Accessibility action to show quick actions menu for an icon. [CHAR_LIMIT=30] -->
     <string name="action_deep_shortcut">Shortcuts</string>
 
+    <!-- Accessibility description for the shortcuts menu shown for an app. -->
+    <string name="shortcuts_menu_description"><xliff:g id="number_of_shortcuts" example="3">%1$d</xliff:g> shortcuts for <xliff:g id="app_name" example="Messenger">%2$s</xliff:g></string>
+
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 4c0230a..f029765 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -79,12 +79,15 @@
 
     <style name="Icon.DeepShortcut">
         <item name="android:gravity">start|center_vertical</item>
+        <item name="android:textAlignment">viewStart</item>
         <item name="android:elevation">@dimen/deep_shortcuts_elevation</item>
         <item name="android:paddingStart">@dimen/bg_pill_height</item>
         <item name="android:paddingEnd">@dimen/deep_shortcut_padding_end</item>
         <item name="android:drawableEnd">@drawable/deep_shortcuts_drag_handle</item>
         <item name="android:drawablePadding">@dimen/deep_shortcut_drawable_padding</item>
-        <item name="android:textColor">@color/quantum_panel_text_color</item>
+        <item name="android:textColor">#FF212121</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:fontFamily">sans-serif</item>
         <item name="android:shadowRadius">0</item>
         <item name="customShadows">false</item>
         <item name="layoutHorizontal">true</item>
diff --git a/src/com/android/launcher3/allapps/AllAppsCaretController.java b/src/com/android/launcher3/allapps/AllAppsCaretController.java
new file mode 100644
index 0000000..622322b
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsCaretController.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 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.launcher3.allapps;
+
+import android.animation.ObjectAnimator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.pageindicators.CaretDrawable;
+
+public class AllAppsCaretController {
+    // Determines when the caret should flip. Should be accessed via getThreshold()
+    private static final float CARET_THRESHOLD = 0.015f;
+    private static final float CARET_THRESHOLD_LAND = 0.5f;
+    // The velocity at which the caret will peak (i.e. exhibit a 90 degree bend)
+    private static final float PEAK_VELOCITY = VerticalPullDetector.RELEASE_VELOCITY_PX_MS * .7f;
+
+    private Launcher mLauncher;
+
+    private ObjectAnimator mCaretAnimator;
+    private CaretDrawable mCaretDrawable;
+    private float mLastCaretProgress;
+    private boolean mThresholdCrossed;
+
+    public AllAppsCaretController(CaretDrawable caret, Launcher launcher) {
+        mLauncher = launcher;
+        mCaretDrawable = caret;
+
+        final long caretAnimationDuration = launcher.getResources().getInteger(
+                R.integer.config_caretAnimationDuration);
+        final Interpolator caretInterpolator = AnimationUtils.loadInterpolator(launcher,
+                android.R.interpolator.fast_out_slow_in);
+
+        // We will set values later
+        mCaretAnimator = ObjectAnimator.ofFloat(mCaretDrawable, "caretProgress", 0);
+        mCaretAnimator.setDuration(caretAnimationDuration);
+        mCaretAnimator.setInterpolator(caretInterpolator);
+    }
+
+    /**
+     * Updates the state of the caret based on the progress of the {@link AllAppsContainerView}, as
+     * defined by the {@link AllAppsTransitionController}. Uses the container's velocity to adjust
+     * angle of caret.
+     *
+     * @param containerProgress The progress of the container in the range [0..1]
+     * @param velocity The velocity of the container
+     * @param dragging {@code true} if the container is being dragged
+     */
+    public void updateCaret(float containerProgress, float velocity, boolean dragging) {
+        // If we're in portrait and the shift is not 0 or 1, adjust the caret based on velocity
+        if (getThreshold() < containerProgress && containerProgress < 1 - getThreshold() &&
+                !mLauncher.useVerticalBarLayout()) {
+            mThresholdCrossed = true;
+
+            // How fast are we moving as a percentage of the peak velocity?
+            final float pctOfFlingVelocity = Math.max(-1, Math.min(velocity / PEAK_VELOCITY, 1));
+
+            mCaretDrawable.setCaretProgress(pctOfFlingVelocity);
+
+            // Set the last caret progress to this progress to prevent animator cancellation
+            mLastCaretProgress = pctOfFlingVelocity;
+            // Animate to neutral. This is necessary so the caret doesn't "freeze" when the
+            // container stops moving (e.g., during a drag or when the threshold is reached).
+            animateCaretToProgress(CaretDrawable.PROGRESS_CARET_NEUTRAL);
+        } else if (!dragging) {
+            // Otherwise, if we're not dragging, match the caret to the appropriate state
+            if (containerProgress <= getThreshold()) { // All Apps is up
+                animateCaretToProgress(CaretDrawable.PROGRESS_CARET_POINTING_DOWN);
+            } else if (containerProgress >= 1 - getThreshold()) { // All Apps is down
+                animateCaretToProgress(CaretDrawable.PROGRESS_CARET_POINTING_UP);
+            }
+        }
+    }
+
+    private void animateCaretToProgress(float progress) {
+        // If the new progress is the same as the last progress we animated to, terminate early
+        if (Float.compare(mLastCaretProgress, progress) == 0) {
+            return;
+        }
+
+        if (mCaretAnimator.isRunning()) {
+            mCaretAnimator.cancel(); // Stop the animator in its tracks
+        }
+
+        // Update the progress and start the animation
+        mLastCaretProgress = progress;
+        mCaretAnimator.setFloatValues(progress);
+        mCaretAnimator.start();
+    }
+
+    private float getThreshold() {
+        // In landscape, just return the landscape threshold.
+        if (mLauncher.useVerticalBarLayout()) {
+            return CARET_THRESHOLD_LAND;
+        }
+
+        // Before the threshold is crossed, it is reported as zero. This makes the caret immediately
+        // responsive when a drag begins. After the threshold is crossed, we return the constant
+        // value. This means the caret will start its state-based adjustment sooner. That is, we
+        // won't have to wait until the panel is completely settled to begin animation.
+        return mThresholdCrossed ? CARET_THRESHOLD : 0f;
+    }
+
+    public void onDragStart() {
+        mThresholdCrossed = false;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index e29e4d7..b0a62e1 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -11,7 +11,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.AccelerateInterpolator;
-import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
@@ -21,7 +20,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.pageindicators.CaretDrawable;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.TouchController;
 
@@ -56,11 +54,7 @@
     private Hotseat mHotseat;
     private int mHotseatBackgroundColor;
 
-    private ObjectAnimator mCaretAnimator;
-    private final long mCaretAnimationDuration;
-    private final Interpolator mCaretInterpolator;
-    private CaretDrawable mCaretDrawable;
-    private float mLastCaretProgress;
+    private AllAppsCaretController mCaretController;
 
     private float mStatusBarHeight;
 
@@ -106,11 +100,6 @@
         mBezelSwipeUpHeight = l.getResources().getDimensionPixelSize(
                 R.dimen.all_apps_bezel_swipe_height);
 
-        mCaretAnimationDuration = l.getResources().getInteger(
-                R.integer.config_caretAnimationDuration);
-        mCaretInterpolator = AnimationUtils.loadInterpolator(l,
-                R.interpolator.caret_animation_interpolator);
-
         mEvaluator = new ArgbEvaluator();
         mAllAppsBackgroundColor = l.getColor(R.color.all_apps_container_color);
     }
@@ -197,6 +186,7 @@
 
     @Override
     public void onDragStart(boolean start) {
+        mCaretController.onDragStart();
         cancelAnimation();
         mCurrentAnimation = LauncherAnimUtils.createAnimatorSet();
         mShiftStart = mAppsView.getTranslationY();
@@ -337,19 +327,15 @@
             return;
         }
         mWorkspace.setWorkspaceYTranslationAndAlpha(
-                PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent),
-                interpolation);
-        updateCaret(progress);
-        updateLightStatusBar(shiftCurrent);
+                PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent), interpolation);
 
         if (!mDetector.isDraggingState()) {
             mContainerVelocity = mDetector.computeVelocity(shiftCurrent - shiftPrevious,
                     System.currentTimeMillis());
         }
-    }
 
-    public float getContainerVelocity() {
-        return mContainerVelocity;
+        mCaretController.updateCaret(progress, mContainerVelocity, mDetector.isDraggingState());
+        updateLightStatusBar(shiftCurrent);
     }
 
     public float getProgress() {
@@ -441,6 +427,9 @@
         mAppsView.post(new Runnable() {
             @Override
             public void run() {
+                if (mDiscoBounceAnimation == null) {
+                    return;
+                }
                 mDiscoBounceAnimation.start();
             }
         });
@@ -529,56 +518,14 @@
         mCurrentAnimation = null;
     }
 
-    private void updateCaret(float shift) {
-        // Animate to a neutral state by default
-        float newCaretProgress = CaretDrawable.PROGRESS_CARET_NEUTRAL;
-
-        // If we're in portrait and the shift is not 0 or 1, adjust the caret based on velocity
-        if (0f < shift && shift < 1f && !mLauncher.useVerticalBarLayout()) {
-            // How fast are we moving as a percentage of the minimum fling velocity?
-            final float pctOfFlingVelocity = Math.max(-1, Math.min(
-                    mContainerVelocity / VerticalPullDetector.RELEASE_VELOCITY_PX_MS, 1));
-
-            mCaretDrawable.setCaretProgress(pctOfFlingVelocity);
-
-            // Set the last caret progress to this progress to prevent animator cancellation
-            mLastCaretProgress = pctOfFlingVelocity;
-        } else if (!mDetector.isDraggingState()) {
-            // Otherwise, if we're not dragging, match the caret to the appropriate state
-            if (Float.compare(shift, 0f) == 0) { // All Apps is up
-                newCaretProgress = CaretDrawable.PROGRESS_CARET_POINTING_DOWN;
-            } else if (Float.compare(shift, 1f) == 0) { // All Apps is down
-                newCaretProgress = CaretDrawable.PROGRESS_CARET_POINTING_UP;
-            }
-        }
-
-        // If the new progress is the same as the last progress we animated to, terminate early
-        if (Float.compare(mLastCaretProgress, newCaretProgress) == 0) {
-            return;
-        }
-
-        if (mCaretAnimator.isRunning()) {
-            mCaretAnimator.cancel(); // Stop the animator in its tracks
-        }
-
-        // Update the progress and start the animation
-        mLastCaretProgress = newCaretProgress;
-        mCaretAnimator.setFloatValues(newCaretProgress);
-        mCaretAnimator.start();
-    }
-
     public void setupViews(AllAppsContainerView appsView, Hotseat hotseat, Workspace workspace) {
         mAppsView = appsView;
         mHotseat = hotseat;
         mWorkspace = workspace;
-        mCaretDrawable = mWorkspace.getPageIndicator().getCaretDrawable();
         mHotseat.addOnLayoutChangeListener(this);
         mHotseat.bringToFront();
-
-        // we will set values later
-        mCaretAnimator = ObjectAnimator.ofFloat(mCaretDrawable, "caretProgress", 0);
-        mCaretAnimator.setDuration(mCaretAnimationDuration);
-        mCaretAnimator.setInterpolator(mCaretInterpolator);
+        mCaretController = new AllAppsCaretController(
+                mWorkspace.getPageIndicator().getCaretDrawable(), mLauncher);
     }
 
     @Override
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 28b6f3e..3300b76 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -374,22 +374,32 @@
 
     @Override
     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
-        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
-        if (currentFolder != null) {
-            if (child == currentFolder) {
-                return super.onRequestSendAccessibilityEvent(child, event);
-            }
+        // Shortcuts can appear above folder
+        View topView = mLauncher.getOpenShortcutsContainer();
+        if (topView != null) {
+            return handleTopViewSendAccessibilityEvent(topView, child, event);
+        }
 
-            if (isInAccessibleDrag() && child instanceof DropTargetBar) {
-                return super.onRequestSendAccessibilityEvent(child, event);
-            }
-            // Skip propagating onRequestSendAccessibilityEvent all for other children
-            // when a folder is open
-            return false;
+        topView = mLauncher.getWorkspace().getOpenFolder();
+        if (topView != null) {
+            return handleTopViewSendAccessibilityEvent(topView, child, event);
         }
         return super.onRequestSendAccessibilityEvent(child, event);
     }
 
+    private boolean handleTopViewSendAccessibilityEvent(
+            View topView, View child, AccessibilityEvent event) {
+        if (child == topView) {
+            return super.onRequestSendAccessibilityEvent(child, event);
+        }
+        if (isInAccessibleDrag() && child instanceof DropTargetBar) {
+            return super.onRequestSendAccessibilityEvent(child, event);
+        }
+        // Skip propagating onRequestSendAccessibilityEvent for all other children
+        // which are not topView
+        return false;
+    }
+
     @Override
     public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
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 53a28de..a341b97 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -40,6 +40,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.LinearLayout;
 
@@ -55,7 +56,6 @@
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 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;
@@ -147,6 +147,8 @@
             shortcut.getBubbleText().setAccessibilityDelegate(mAccessibilityDelegate);
             addView(shortcut);
         }
+        setContentDescription(getContext().getString(R.string.shortcuts_menu_description,
+                numShortcuts, originalIcon.getContentDescription().toString()));
 
         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
         orientAboutIcon(originalIcon);
@@ -258,8 +260,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) {
@@ -276,6 +277,8 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 mOpenCloseAnimator = null;
+
+                sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
             }
         });
 
@@ -626,24 +629,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();
@@ -663,7 +671,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);
+                    }
                 }
             }