Merge "Change overview animation to scale instead of translationX" into ub-launcher3-master
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index 2626e7c..0e3d2a4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -88,10 +88,10 @@
     }
 
     @Override
-    public float[] getOverviewTranslationFactor(Launcher launcher) {
-        // Keep the same translation as in overview, so that we don't slide around when
+    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+        // Keep the same transition properties as overview, so that we don't move around when
         // transitioning to All Apps.
-        return LauncherState.OVERVIEW.getOverviewTranslationFactor(launcher);
+        return LauncherState.OVERVIEW.getOverviewScaleAndTranslationYFactor(launcher);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
index 99bf264..f98f7a5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
@@ -50,7 +50,7 @@
     }
 
     @Override
-    public float[] getOverviewTranslationFactor(Launcher launcher) {
-        return new float[] {0f, 0.5f};
+    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+        return new float[] {1f, 0.5f};
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 4b2763b..d97b7b2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -58,8 +58,8 @@
     }
 
     @Override
-    public float[] getOverviewTranslationFactor(Launcher launcher) {
-        return new float[] {0f, 0f};
+    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+        return new float[] {1f, 0f};
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 0c34b45..1b65ca0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -61,9 +61,6 @@
 
     private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
 
-    // If > 0, the animation progress is clamped at that value as long as user is dragging.
-    private float mClampProgressUpdate = -1;
-
     // If true, we will finish the current animation instantly on second touch.
     private boolean mFinishFastOnSecondTouch;
 
@@ -166,15 +163,6 @@
     }
 
     @Override
-    protected void updateProgress(float fraction) {
-        if (mClampProgressUpdate > 0) {
-            mCurrentAnimation.setPlayFraction(Math.min(fraction, mClampProgressUpdate));
-        } else {
-            super.updateProgress(fraction);
-        }
-    }
-
-    @Override
     protected float initCurrentAnimation() {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
@@ -189,10 +177,8 @@
         if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) {
             builder = getNormalToOverviewAnimation();
             totalShift = totalShift * TOTAL_DISTANCE_MULTIPLIER;
-            mClampProgressUpdate = MAXIMUM_DISTANCE_FACTOR;
         } else {
             builder = new AnimatorSetBuilder();
-            mClampProgressUpdate = -1;
         }
 
         if (mPendingAnimation != null) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 9f7fef3..124ec20 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,11 +15,11 @@
  */
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_X_FACTOR;
 import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
+import static com.android.quickstep.views.RecentsView.ADJACENT_SCALE;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 
 import android.animation.ValueAnimator;
@@ -30,7 +30,6 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.PagedView;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.quickstep.views.LauncherRecentsView;
@@ -49,9 +48,9 @@
     @Override
     public void setState(LauncherState state) {
         mRecentsView.setContentAlpha(state.overviewUi ? 1 : 0);
-        float[] translationFactor = state.getOverviewTranslationFactor(mLauncher);
-        mRecentsView.setTranslationXFactor(translationFactor[0]);
-        mRecentsView.setTranslationYFactor(translationFactor[1]);
+        float[] scaleTranslationYFactor = state.getOverviewScaleAndTranslationYFactor(mLauncher);
+        mRecentsView.setAdjacentScale(scaleTranslationYFactor[0]);
+        mRecentsView.setTranslationYFactor(scaleTranslationYFactor[1]);
         if (state.overviewUi) {
             mRecentsView.updateEmptyMessage();
             mRecentsView.resetTaskVisuals();
@@ -61,28 +60,18 @@
     @Override
     public void setStateWithAnimation(final LauncherState toState,
             AnimatorSetBuilder builder, AnimationConfig config) {
-
-        // Scroll to the workspace card before changing to the NORMAL state.
-        LauncherState fromState = mLauncher.getStateManager().getState();
-        int currPage = mRecentsView.getCurrentPage();
-        if (fromState.overviewUi && toState == NORMAL && currPage != 0 && !config.userControlled) {
-            int maxSnapDuration = PagedView.SLOW_PAGE_SNAP_ANIMATION_DURATION;
-            int durationPerPage = maxSnapDuration / 10;
-            int snapDuration = Math.min(maxSnapDuration, durationPerPage * currPage);
-            mRecentsView.snapToPage(0, snapDuration);
-            // Let the snapping animation play for a bit before we translate off screen.
-            builder.setStartDelay(snapDuration / 4);
-        }
-
         PropertySetter setter = config.getProperSetter(builder);
-        float[] translationFactor = toState.getOverviewTranslationFactor(mLauncher);
-        setter.setFloat(mRecentsView, TRANSLATION_X_FACTOR,
-                translationFactor[0],
+        float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
+        setter.setFloat(mRecentsView, ADJACENT_SCALE, scaleTranslationYFactor[0],
                 builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
-        setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR,
-                translationFactor[1],
+        setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1],
                 builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
-        setter.setFloat(mRecentsView, CONTENT_ALPHA, toState.overviewUi ? 1 : 0, LINEAR);
+        setter.setFloat(mRecentsView, CONTENT_ALPHA, toState.overviewUi ? 1 : 0,
+                AGGRESSIVE_EASE_IN_OUT);
+
+        if (!toState.overviewUi) {
+            builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
+        }
 
         if (toState.overviewUi) {
             ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index d428f23..6788827 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -20,7 +20,6 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 
-import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
@@ -45,20 +44,6 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class LauncherRecentsView extends RecentsView<Launcher> implements Insettable {
 
-    public static final FloatProperty<LauncherRecentsView> TRANSLATION_X_FACTOR =
-            new FloatProperty<LauncherRecentsView>("translationXFactor") {
-
-                @Override
-                public void setValue(LauncherRecentsView view, float v) {
-                    view.setTranslationXFactor(v);
-                }
-
-                @Override
-                public Float get(LauncherRecentsView view) {
-                    return view.mTranslationXFactor;
-                }
-            };
-
     public static final FloatProperty<LauncherRecentsView> TRANSLATION_Y_FACTOR =
             new FloatProperty<LauncherRecentsView>("translationYFactor") {
 
@@ -74,8 +59,6 @@
             };
 
     @ViewDebug.ExportedProperty(category = "launcher")
-    private float mTranslationXFactor;
-    @ViewDebug.ExportedProperty(category = "launcher")
     private float mTranslationYFactor;
 
     private Rect mPagePadding = new Rect();
@@ -114,11 +97,6 @@
         setTranslationYFactor(mTranslationYFactor);
     }
 
-    public void setTranslationXFactor(float translationFactor) {
-        mTranslationXFactor = translationFactor;
-        invalidate();
-    }
-
     public void setTranslationYFactor(float translationFactor) {
         mTranslationYFactor = translationFactor;
         setTranslationY(mTranslationYFactor * (mPagePadding.bottom - mPagePadding.top));
@@ -127,10 +105,7 @@
     @Override
     public void draw(Canvas canvas) {
         maybeDrawEmptyMessage(canvas);
-        int count = canvas.save();
-        canvas.translate(mTranslationXFactor * (mIsRtl ? -getWidth() : getWidth()), 0);
         super.draw(canvas);
-        canvas.restoreToCount(count);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index f61a95c..a791861 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -59,8 +59,8 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.PendingAnimation;
+import com.android.launcher3.util.Themes;
 import com.android.quickstep.QuickScrubController;
 import com.android.quickstep.RecentsAnimationInterpolator;
 import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
@@ -85,8 +85,6 @@
 
     public static final FloatProperty<RecentsView> CONTENT_ALPHA =
             new FloatProperty<RecentsView>("contentAlpha") {
-
-
         @Override
         public void setValue(RecentsView recentsView, float v) {
             recentsView.setContentAlpha(v);
@@ -98,6 +96,20 @@
         }
     };
 
+
+
+    public static final FloatProperty<RecentsView> ADJACENT_SCALE =
+            new FloatProperty<RecentsView>("adjacentScale") {
+        @Override
+        public void setValue(RecentsView recentsView, float v) {
+            recentsView.setAdjacentScale(v);
+        }
+
+        @Override
+        public Float get(RecentsView recentsView) {
+            return recentsView.mAdjacentScale;
+        }
+    };
     private static final String PREF_FLIP_RECENTS = "pref_flip_recents";
     private static final int DISMISS_TASK_DURATION = 300;
 
@@ -145,6 +157,8 @@
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private float mContentAlpha = 1;
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private float mAdjacentScale = 1;
 
     // Keeps track of task views whose visual state should not be reset
     private ArraySet<TaskView> mIgnoreResetTaskViews = new ArraySet<>();
@@ -803,10 +817,58 @@
         setVisibility(alpha > 0 ? VISIBLE : GONE);
     }
 
+    public void setAdjacentScale(float adjacentScale) {
+        if (mAdjacentScale == adjacentScale) {
+            return;
+        }
+        mAdjacentScale = adjacentScale;
+        TaskView currTask = getPageAt(mCurrentPage);
+        if (currTask == null) {
+            return;
+        }
+        currTask.setScaleX(mAdjacentScale);
+        currTask.setScaleY(mAdjacentScale);
+
+        if (mCurrentPage - 1 >= 0) {
+            TaskView adjacentTask = getPageAt(mCurrentPage - 1);
+            float[] scaleAndTranslation = getAdjacentScaleAndTranslation(currTask, adjacentTask,
+                    mAdjacentScale, 0);
+            adjacentTask.setScaleX(scaleAndTranslation[0]);
+            adjacentTask.setScaleY(scaleAndTranslation[0]);
+            adjacentTask.setTranslationX(-scaleAndTranslation[1]);
+            adjacentTask.setTranslationY(scaleAndTranslation[2]);
+        }
+        if (mCurrentPage + 1 < getChildCount()) {
+            TaskView adjacentTask = getPageAt(mCurrentPage + 1);
+            float[] scaleAndTranslation = getAdjacentScaleAndTranslation(currTask, adjacentTask,
+                    mAdjacentScale, 0);
+            adjacentTask.setScaleX(scaleAndTranslation[0]);
+            adjacentTask.setScaleY(scaleAndTranslation[0]);
+            adjacentTask.setTranslationX(scaleAndTranslation[1]);
+            adjacentTask.setTranslationY(scaleAndTranslation[2]);
+        }
+    }
+
+    private float[] getAdjacentScaleAndTranslation(TaskView currTask, TaskView adjacentTask,
+            float currTaskToScale, float currTaskToTranslationY) {
+        float displacement = currTask.getWidth() * (currTaskToScale - currTask.getCurveScale());
+        return new float[] {
+                currTaskToScale * adjacentTask.getCurveScale(),
+                mIsRtl ? -displacement : displacement,
+                currTaskToTranslationY
+        };
+    }
+
     @Override
     public void onViewAdded(View child) {
         super.onViewAdded(child);
         child.setAlpha(mContentAlpha);
+        setAdjacentScale(mAdjacentScale);
+    }
+
+    @Override
+    public TaskView getPageAt(int index) {
+        return (TaskView) getChildAt(index);
     }
 
     public void updateEmptyMessage() {
@@ -884,18 +946,24 @@
         float toScale = endInterpolation.taskScale;
         float toTranslationY = endInterpolation.taskY;
 
-        float displacementX = tv.getWidth() * (toScale - tv.getScaleX());
         if (launchingCenterTask) {
+            TaskView centerTask = getPageAt(centerTaskIndex);
             if (taskIndex - 1 >= 0) {
-                anim.play(createAnimForChild(
-                        taskIndex - 1, toScale, displacementX, toTranslationY));
+                TaskView adjacentTask = getPageAt(taskIndex - 1);
+                float[] scaleAndTranslation = getAdjacentScaleAndTranslation(centerTask,
+                        adjacentTask, toScale, toTranslationY);
+                scaleAndTranslation[1] = -scaleAndTranslation[1];
+                anim.play(createAnimForChild(adjacentTask, scaleAndTranslation));
             }
             if (taskIndex + 1 < getPageCount()) {
-                anim.play(createAnimForChild(
-                        taskIndex + 1, toScale, -displacementX, toTranslationY));
+                TaskView adjacentTask = getPageAt(taskIndex + 1);
+                float[] scaleAndTranslation = getAdjacentScaleAndTranslation(centerTask,
+                        adjacentTask, toScale, toTranslationY);
+                anim.play(createAnimForChild(adjacentTask, scaleAndTranslation));
             }
         } else {
             // We are launching an adjacent task, so parallax the center and other adjacent task.
+            float displacementX = tv.getWidth() * (toScale - tv.getCurveScale());
             anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex), TRANSLATION_X,
                     mIsRtl ? -displacementX : displacementX));
 
@@ -911,13 +979,12 @@
         return anim;
     }
 
-    private ObjectAnimator createAnimForChild(int childIndex, float toScale, float tx, float ty) {
-        View child = getChildAt(childIndex);
+    private ObjectAnimator createAnimForChild(View child, float[] toScaleAndTranslation) {
         return ObjectAnimator.ofPropertyValuesHolder(child,
                         new PropertyListBuilder()
-                                .scale(child.getScaleX() * toScale)
-                                .translationY(ty)
-                                .translationX(mIsRtl ? tx : -tx)
+                                .scale(child.getScaleX() * toScaleAndTranslation[0])
+                                .translationX(toScaleAndTranslation[1])
+                                .translationY(toScaleAndTranslation[2])
                                 .build());
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 57516b0..42da472 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -70,6 +70,7 @@
     private Task mTask;
     private TaskThumbnailView mSnapshotView;
     private ImageView mIconView;
+    private float mCurveScale;
 
     public TaskView(Context context) {
         this(context, null);
@@ -178,9 +179,13 @@
 
         mSnapshotView.setDimAlpha(1 - curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
 
-        float scale = 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
-        setScaleX(scale);
-        setScaleY(scale);
+        mCurveScale = 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
+        setScaleX(mCurveScale);
+        setScaleY(mCurveScale);
+    }
+
+    public float getCurveScale() {
+        return mCurveScale;
     }
 
     @Override
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index fdb6f48..4697b82 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -181,12 +181,12 @@
     }
 
     /**
-     * Returns 2 floats designating how much to translate overview:
-     *   X factor is based on width, e.g. 0 is fully onscreen and 1 is fully offscreen
-     *   Y factor is based on padding, e.g. 0 is top aligned and 0.5 is centered vertically
+     * Returns 2 floats designating how to transition overview:
+     *   scale for the current and adjacent pages
+     *   translationY factor where 0 is top aligned and 0.5 is centered vertically
      */
-    public float[] getOverviewTranslationFactor(Launcher launcher) {
-        return new float[] {1f, 0f};
+    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+        return new float[] {1.2f, 0.2f};
     }
 
     public void onStateEnabled(Launcher launcher) {
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index fa86906..420a7c4 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -31,7 +31,6 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.graphics.ViewScrim;
-import com.android.launcher3.uioverrides.UiFactory;
 
 /**
  * Manages the animations between each of the workspace states.
@@ -74,11 +73,11 @@
                     propertySetter);
         }
 
-        propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, Interpolators.ZOOM_IN);
+        propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, Interpolators.ZOOM_OUT);
         propertySetter.setFloat(mWorkspace, View.TRANSLATION_X,
-                scaleAndTranslation[1], Interpolators.ZOOM_IN);
+                scaleAndTranslation[1], Interpolators.ZOOM_OUT);
         propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
-                scaleAndTranslation[2], Interpolators.ZOOM_IN);
+                scaleAndTranslation[2], Interpolators.ZOOM_OUT);
 
         int elements = state.getVisibleElements(mLauncher);
         float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
@@ -113,7 +112,7 @@
         int drawableAlpha = Math.round(pageAlpha * (state.hasWorkspacePageBackground ? 255 : 0));
 
         propertySetter.setInt(cl.getScrimBackground(),
-                DRAWABLE_ALPHA, drawableAlpha, Interpolators.ZOOM_IN);
+                DRAWABLE_ALPHA, drawableAlpha, Interpolators.ZOOM_OUT);
         propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
                 pageAlpha, pageAlphaProvider.interpolator);
     }
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index 9191048..b209a2d 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.anim;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.util.SparseArray;
 import android.view.animation.Interpolator;
@@ -23,6 +24,7 @@
 import com.android.launcher3.LauncherAnimUtils;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Utility class for building animator set
@@ -35,7 +37,7 @@
     protected final ArrayList<Animator> mAnims = new ArrayList<>();
 
     private final SparseArray<Interpolator> mInterpolators = new SparseArray<>();
-    private long mStartDelay = 0;
+    private List<Runnable> mOnFinishRunnables = new ArrayList<>();
 
     /**
      * Associates a tag with all the animations added after this call.
@@ -46,14 +48,24 @@
         mAnims.add(anim);
     }
 
-    public void setStartDelay(long startDelay) {
-        mStartDelay = startDelay;
+    public void addOnFinishRunnable(Runnable onFinishRunnable) {
+        mOnFinishRunnables.add(onFinishRunnable);
     }
 
     public AnimatorSet build() {
         AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
         anim.playTogether(mAnims);
-        anim.setStartDelay(mStartDelay);
+        if (!mOnFinishRunnables.isEmpty()) {
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    for (Runnable onFinishRunnable : mOnFinishRunnables) {
+                        onFinishRunnable.run();
+                    }
+                    mOnFinishRunnables.clear();
+                }
+            });
+        }
         return anim;
     }
 
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 6078776..06ddf22 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -65,15 +65,22 @@
             new PathInterpolator(0.3f, 0f, 0.1f, 1f);
 
     /**
-     * Inversion of zInterpolate, compounded with an ease-out.
+     * Inversion of ZOOM_OUT, compounded with an ease-out.
      */
     public static final Interpolator ZOOM_IN = new Interpolator() {
+        @Override
+        public float getInterpolation(float v) {
+            return DEACCEL_3.getInterpolation(1 - ZOOM_OUT.getInterpolation(1 - v));
+        }
+    };
+
+    public static final Interpolator ZOOM_OUT = new Interpolator() {
 
         private static final float FOCAL_LENGTH = 0.35f;
 
         @Override
         public float getInterpolation(float v) {
-            return DEACCEL_3.getInterpolation(1 - zInterpolate(1 - v));
+            return zInterpolate(v);
         }
 
         /**