Fix: switching states during animations was broken

- cancelling state animations on home screens before starting new ones
- temporary workaround for the fact that onAnimationEnd is not called immediately when an animation is cancelled
- forcing all apps zoom-in animation to complete if it's cancelled

Change-Id: I3eb011f689050692e8d95f2736e01ab5420f722e
diff --git a/src/com/android/launcher2/AllAppsTabbed.java b/src/com/android/launcher2/AllAppsTabbed.java
index eaeb80f..e0ff1a8 100644
--- a/src/com/android/launcher2/AllAppsTabbed.java
+++ b/src/com/android/launcher2/AllAppsTabbed.java
@@ -19,7 +19,6 @@
 import com.android.launcher.R;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -81,9 +80,9 @@
                 final float alpha = mAllApps.getAlpha();
                 ValueAnimator alphaAnim = ObjectAnimator.ofFloat(mAllApps, "alpha", alpha, 0.0f).
                         setDuration(duration);
-                alphaAnim.addListener(new AnimatorListenerAdapter() {
+                alphaAnim.addListener(new LauncherAnimatorListenerAdapter() {
                     @Override
-                    public void onAnimationEnd(Animator animation) {
+                    public void onAnimationEndOrCancel(Animator animation) {
                         String tag = getCurrentTabTag();
                         if (tag == TAG_ALL) {
                             mAllApps.setAppFilter(AllAppsPagedView.ALL_APPS_FLAG);
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index 95c976b..66d5cb5 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher2;
 
-import java.util.Arrays;
+import com.android.launcher.R;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -26,7 +26,6 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.WallpaperManager;
-import android.content.ClipDescription;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -42,7 +41,6 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.ContextMenu;
-import android.view.DragEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
@@ -51,7 +49,7 @@
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.LayoutAnimationController;
 
-import com.android.launcher.R;
+import java.util.Arrays;
 
 public class CellLayout extends ViewGroup implements Dimmable {
     static final String TAG = "CellLayout";
@@ -245,6 +243,7 @@
             // The animation holds a reference to the drag outline bitmap as long is it's
             // running. This way the bitmap can be GCed when the animations are complete.
             anim.getAnimator().addListener(new AnimatorListenerAdapter() {
+                @Override
                 public void onAnimationEnd(Animator animation) {
                     if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
                         anim.setTag(null);
@@ -305,11 +304,13 @@
             AnimatorSet bouncer = new AnimatorSet();
             bouncer.play(scaleUp).before(scaleDown);
             bouncer.play(scaleUp).with(alphaFadeOut);
-            bouncer.addListener(new AnimatorListenerAdapter() {
+            bouncer.addListener(new LauncherAnimatorListenerAdapter() {
+                @Override
                 public void onAnimationStart(Animator animation) {
                     setHover(true);
                 }
-                public void onAnimationEnd(Animator animation) {
+                @Override
+                public void onAnimationEndOrCancel(Animator animation) {
                     setHover(false);
                     setHoverScale(1.0f);
                     setHoverAlpha(1.0f);
diff --git a/src/com/android/launcher2/InterruptibleInOutAnimator.java b/src/com/android/launcher2/InterruptibleInOutAnimator.java
index 5ebe605..570b9e7 100644
--- a/src/com/android/launcher2/InterruptibleInOutAnimator.java
+++ b/src/com/android/launcher2/InterruptibleInOutAnimator.java
@@ -52,8 +52,9 @@
         mOriginalFromValue = fromValue;
         mOriginalToValue = toValue;
 
-        mAnimator.addListener(new AnimatorListenerAdapter() {
-            public void onAnimationEnd(Animator animation) {
+        mAnimator.addListener(new LauncherAnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEndOrCancel(Animator animation) {
                 mDirection = STOPPED;
             }
         });
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 71978fa..c14c22c 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -21,7 +21,6 @@
 import com.android.launcher.R;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
@@ -41,12 +40,12 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.Intent.ShortcutIconResource;
 import android.content.IntentFilter;
+import android.content.Intent.ShortcutIconResource;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -79,9 +78,9 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 import android.view.WindowManager;
+import android.view.View.OnLongClickListener;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.view.inputmethod.InputMethodManager;
@@ -90,10 +89,10 @@
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
 import android.widget.TabHost;
-import android.widget.TabHost.OnTabChangeListener;
-import android.widget.TabHost.TabContentFactory;
 import android.widget.TextView;
 import android.widget.Toast;
+import android.widget.TabHost.OnTabChangeListener;
+import android.widget.TabHost.TabContentFactory;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -303,9 +302,9 @@
                     ValueAnimator alphaAnim = ObjectAnimator.ofFloat(mCustomizePagedView,
                             "alpha", alpha, 0.0f);
                     alphaAnim.setDuration(duration);
-                    alphaAnim.addListener(new AnimatorListenerAdapter() {
+                    alphaAnim.addListener(new LauncherAnimatorListenerAdapter() {
                         @Override
-                        public void onAnimationEnd(Animator animation) {
+                        public void onAnimationEndOrCancel(Animator animation) {
                             String tag = mHomeCustomizationDrawer.getCurrentTabTag();
                             if (tag == WIDGETS_TAG) {
                                 mCustomizePagedView.setCustomizationFilter(
@@ -2295,13 +2294,13 @@
         if (seq != null) {
             Animator anim = ObjectAnimator.ofFloat(view, "alpha", show ? 1.0f : 0.0f);
             anim.setDuration(duration);
-            anim.addListener(new AnimatorListenerAdapter() {
+            anim.addListener(new LauncherAnimatorListenerAdapter() {
                 @Override
                 public void onAnimationStart(Animator animation) {
                     if (showing) showToolbarButton(view);
                 }
                 @Override
-                public void onAnimationEnd(Animator animation) {
+                public void onAnimationEndOrCancel(Animator animation) {
                     if (hiding) hideToolbarButton(view);
                 }
             });
@@ -2388,7 +2387,7 @@
 
         setPivotsForZoom(toView, toState, scale);
 
-        if (toState == State.ALL_APPS) {
+        if (toAllApps) {
             mWorkspace.shrinkToBottom(animated);
         } else {
             mWorkspace.shrinkToTop(animated);
@@ -2400,7 +2399,7 @@
                     PropertyValuesHolder.ofFloat("scaleY", scale, 1.0f));
             scaleAnim.setDuration(duration);
             scaleAnim.setInterpolator(new DecelerateInterpolator());
-            scaleAnim.addListener(new AnimatorListenerAdapter() {
+            scaleAnim.addListener(new LauncherAnimatorListenerAdapter() {
                 @Override
                 public void onAnimationStart(Animator animation) {
                     // Prepare the position
@@ -2408,6 +2407,14 @@
                     toView.setTranslationY(0.0f);
                     toView.setVisibility(View.VISIBLE);
                 }
+                @Override
+                public void onAnimationEndOrCancel(Animator animation) {
+                    // If we don't set the final scale values here, if this animation is cancelled
+                    // it will have the wrong scale value and subsequent cameraPan animations will
+                    // not fix that
+                    toView.setScaleX(1.0f);
+                    toView.setScaleY(1.0f);
+                }
             });
 
             AnimatorSet toolbarHideAnim = new AnimatorSet();
@@ -2464,12 +2471,10 @@
                     PropertyValuesHolder.ofFloat("scaleY", scaleFactor));
             scaleAnim.setDuration(duration);
             scaleAnim.setInterpolator(new AccelerateInterpolator());
-            mStateAnimation.addListener(new AnimatorListenerAdapter() {
+            mStateAnimation.addListener(new LauncherAnimatorListenerAdapter() {
                 @Override
-                public void onAnimationEnd(Animator animation) {
+                public void onAnimationEndOrCancel(Animator animation) {
                     fromView.setVisibility(View.GONE);
-                    fromView.setScaleX(1.0f);
-                    fromView.setScaleY(1.0f);
                 }
             });
 
@@ -2524,14 +2529,14 @@
         if (animated) {
             if (mStateAnimation != null) mStateAnimation.cancel();
             mStateAnimation = new AnimatorSet();
-            mStateAnimation.addListener(new AnimatorListenerAdapter() {
+            mStateAnimation.addListener(new LauncherAnimatorListenerAdapter() {
                 @Override
                 public void onAnimationStart(Animator animation) {
                     toView.setVisibility(View.VISIBLE);
                     toView.setY(toViewStartY);
                 }
                 @Override
-                public void onAnimationEnd(Animator animation) {
+                public void onAnimationEndOrCancel(Animator animation) {
                     fromView.setVisibility(View.GONE);
                 }
             });
@@ -2543,8 +2548,11 @@
             ObjectAnimator fromAnim = ObjectAnimator.ofFloat(fromView, "y",
                     fromViewStartY, fromViewEndY);
             fromAnim.setDuration(duration);
-            ObjectAnimator toAnim = ObjectAnimator.ofFloat(toView, "y",
-                    toViewStartY, toViewEndY);
+            ObjectAnimator toAnim = ObjectAnimator.ofPropertyValuesHolder(toView,
+                    PropertyValuesHolder.ofFloat("y", toViewStartY, toViewEndY),
+                    PropertyValuesHolder.ofFloat("scaleX", toView.getScaleX(), 1.0f),
+                    PropertyValuesHolder.ofFloat("scaleY", toView.getScaleY(), 1.0f)
+                    );
             fromAnim.setDuration(duration);
             mStateAnimation.playTogether(toolbarHideAnim, fromAnim, toAnim);
 
@@ -2556,6 +2564,8 @@
             fromView.setY(fromViewEndY);
             fromView.setVisibility(View.GONE);
             toView.setY(toViewEndY);
+            toView.setScaleX(1.0f);
+            toView.setScaleY(1.0f);
             toView.setVisibility(View.VISIBLE);
             hideAndShowToolbarButtons(toState, null, null);
         }
diff --git a/src/com/android/launcher2/LauncherAnimatorListenerAdapter.java b/src/com/android/launcher2/LauncherAnimatorListenerAdapter.java
new file mode 100644
index 0000000..3ab4868
--- /dev/null
+++ b/src/com/android/launcher2/LauncherAnimatorListenerAdapter.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 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.launcher2;
+
+import android.animation.Animator;
+
+import java.util.HashSet;
+
+/**
+ * This adapter class provides empty implementations of the methods from {@link android.animation.Animator.AnimatorListener}.
+ * Any custom listener that cares only about a subset of the methods of this listener can
+ * simply subclass this adapter class instead of implementing the interface directly.
+ */
+public abstract class LauncherAnimatorListenerAdapter implements Animator.AnimatorListener {
+    HashSet<Animator> cancelled = new HashSet<Animator>();
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public final void onAnimationCancel(Animator animation) {
+        onAnimationEndOrCancel(animation);
+        cancelled.add(animation);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public final void onAnimationEnd(Animator animation) {
+        if (!cancelled.contains(animation)) onAnimationEndOrCancel(animation);
+        cancelled.remove(animation);
+    }
+
+    /**
+     * Like onAnimationEnd, except it's called immediately in the case on onAnimationCancel, and
+     * it's only called once in that case
+     */
+    public void onAnimationEndOrCancel(Animator animation) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationRepeat(Animator animation) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationStart(Animator animation) {
+    }
+
+}
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index dfc664d..e385f8f 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -16,17 +16,16 @@
 
 package com.android.launcher2;
 
-import java.util.ArrayList;
-import java.util.HashSet;
+import com.android.launcher.R;
 
 import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
+import android.animation.Animator.AnimatorListener;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.WallpaperManager;
 import android.appwidget.AppWidgetManager;
@@ -60,7 +59,8 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
-import com.android.launcher.R;
+import java.util.ArrayList;
+import java.util.HashSet;
 
 /**
  * The workspace is a wide area with a wallpaper and a finite number of pages.
@@ -146,6 +146,7 @@
     private ShrinkPosition mShrunkenState;
     private boolean mWaitingToShrink = false;
     private ShrinkPosition mWaitingToShrinkPosition;
+    private AnimatorSet mAnimator;
 
     private boolean mInScrollArea = false;
     private boolean mInDragMode = false;
@@ -215,11 +216,13 @@
         mExternalDragOutlinePaint.setAntiAlias(true);
         setWillNotDraw(false);
 
-        mUnshrinkAnimationListener = new AnimatorListenerAdapter() {
+        mUnshrinkAnimationListener = new LauncherAnimatorListenerAdapter() {
+            @Override
             public void onAnimationStart(Animator animation) {
                 mIsInUnshrinkAnimation = true;
             }
-            public void onAnimationEnd(Animator animation) {
+            @Override
+            public void onAnimationEndOrCancel(Animator animation) {
                 mIsInUnshrinkAnimation = false;
             }
         };
@@ -785,6 +788,11 @@
         // of the views accordingly
         newX -= (pageWidth - scaledPageWidth) / 2.0f;
         newY -= (pageHeight - scaledPageHeight) / 2.0f;
+
+        if (mAnimator != null) {
+            mAnimator.cancel();
+        }
+        mAnimator = new AnimatorSet();
         for (int i = 0; i < screenCount; i++) {
             CellLayout cl = (CellLayout) getChildAt(i);
 
@@ -805,7 +813,7 @@
                         PropertyValuesHolder.ofFloat("alpha", finalAlpha),
                         PropertyValuesHolder.ofFloat("rotationY", rotation));
                 anim.setDuration(duration);
-                anim.start();
+                mAnimator.playTogether(anim);
             } else {
                 cl.setX((int)newX);
                 cl.setY((int)newY);
@@ -818,6 +826,9 @@
             // increment newX for the next screen
             newX += scaledPageWidth + extraScaledSpacing;
         }
+        if (animated) {
+            mAnimator.start();
+        }
         setChildrenDrawnWithCacheEnabled(true);
     }
 
@@ -929,7 +940,10 @@
     void unshrink(boolean animated) {
         if (mIsSmall) {
             mIsSmall = false;
-            AnimatorSet s = new AnimatorSet();
+            if (mAnimator != null) {
+                mAnimator.cancel();
+            }
+            mAnimator = new AnimatorSet();
             final int screenCount = getChildCount();
 
             final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime);
@@ -945,8 +959,7 @@
                 }
 
                 if (animated) {
-
-                    s.playTogether(
+                    mAnimator.playTogether(
                             ObjectAnimator.ofFloat(cl, "translationX", 0.0f).setDuration(duration),
                             ObjectAnimator.ofFloat(cl, "translationY", 0.0f).setDuration(duration),
                             ObjectAnimator.ofFloat(cl, "scaleX", 1.0f).setDuration(duration),
@@ -967,8 +980,8 @@
             if (animated) {
                 // If we call this when we're not animated, onAnimationEnd is never called on
                 // the listener; make sure we only use the listener when we're actually animating
-                s.addListener(mUnshrinkAnimationListener);
-                s.start();
+                mAnimator.addListener(mUnshrinkAnimationListener);
+                mAnimator.start();
             }
         }
     }