Merge "Add add/remove for AnimationListener's"
diff --git a/api/current.txt b/api/current.txt
index cd33830..cec80c7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -51901,6 +51901,7 @@
   public abstract class Animation implements java.lang.Cloneable {
     ctor public Animation();
     ctor public Animation(android.content.Context, android.util.AttributeSet);
+    method public void addAnimationListener(android.view.animation.Animation.AnimationListener);
     method protected void applyTransformation(float, android.view.animation.Transformation);
     method public void cancel();
     method protected android.view.animation.Animation clone() throws java.lang.CloneNotSupportedException;
@@ -51925,6 +51926,7 @@
     method public void initialize(int, int, int, int);
     method public boolean isFillEnabled();
     method public boolean isInitialized();
+    method public void removeAnimationListener(android.view.animation.Animation.AnimationListener);
     method public void reset();
     method protected float resolveSize(int, float, int, int);
     method public void restrictDuration(long);
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 0e1f767..95a346f 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -30,6 +30,9 @@
 
 import dalvik.system.CloseGuard;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Abstraction for an Animation that can be applied to Views, Surfaces, or
  * other objects. See the {@link android.view.animation animation package
@@ -182,10 +185,14 @@
     Interpolator mInterpolator;
 
     /**
-     * The animation listener to be notified when the animation starts, ends or repeats.
+     * An animation listener to be notified when the animation starts, ends or repeats.
      */
-    @UnsupportedAppUsage
-    AnimationListener mListener;
+    private AnimationListener mListener;
+
+    /**
+     * A list of animation listeners to be notified when the animation starts, ends or repeats.
+     */
+    private List<AnimationListener> mListeners;
 
     /**
      * Desired Z order mode during animation.
@@ -371,23 +378,17 @@
         if (mListenerHandler == null) {
             mOnStart = new Runnable() {
                 public void run() {
-                    if (mListener != null) {
-                        mListener.onAnimationStart(Animation.this);
-                    }
+                    dispatchAnimationStart();
                 }
             };
             mOnRepeat = new Runnable() {
                 public void run() {
-                    if (mListener != null) {
-                        mListener.onAnimationRepeat(Animation.this);
-                    }
+                    dispatchAnimationRepeat();
                 }
             };
             mOnEnd = new Runnable() {
                 public void run() {
-                    if (mListener != null) {
-                        mListener.onAnimationEnd(Animation.this);
-                    }
+                    dispatchAnimationEnd();
                 }
             };
         }
@@ -830,6 +831,10 @@
         return true;
     }
 
+    private boolean hasAnimationListener() {
+        return mListener != null || (mListeners != null && !mListeners.isEmpty());
+    }
+
     /**
      * <p>Binds an animation listener to this animation. The animation listener
      * is notified of animation events such as the end of the animation or the
@@ -842,6 +847,32 @@
     }
 
     /**
+     * <p>Adds an animation listener to this animation. The animation listener
+     * is notified of animation events such as the end of the animation or the
+     * repetition of the animation.</p>
+     *
+     * @param listener the animation listener to be notified
+     */
+    public void addAnimationListener(AnimationListener listener) {
+        if (mListeners == null) {
+            mListeners = new ArrayList<>(1);
+        }
+        mListeners.add(listener);
+    }
+
+    /**
+     * <p>Removes an animation listener that has been added with
+     * {@link #addAnimationListener(AnimationListener)}.</p>
+     *
+     * @param listener the animation listener to be removed
+     */
+    public void removeAnimationListener(AnimationListener listener) {
+        if (mListeners != null) {
+            mListeners.remove(listener);
+        }
+    }
+
+    /**
      * Gurantees that this animation has an interpolator. Will use
      * a AccelerateDecelerateInterpolator is nothing else was specified.
      */
@@ -947,26 +978,59 @@
     }
 
     private void fireAnimationStart() {
-        if (mListener != null) {
-            if (mListenerHandler == null) mListener.onAnimationStart(this);
+        if (hasAnimationListener()) {
+            if (mListenerHandler == null) dispatchAnimationStart();
             else mListenerHandler.postAtFrontOfQueue(mOnStart);
         }
     }
 
     private void fireAnimationRepeat() {
-        if (mListener != null) {
-            if (mListenerHandler == null) mListener.onAnimationRepeat(this);
+        if (hasAnimationListener()) {
+            if (mListenerHandler == null) dispatchAnimationRepeat();
             else mListenerHandler.postAtFrontOfQueue(mOnRepeat);
         }
     }
 
     private void fireAnimationEnd() {
-        if (mListener != null) {
-            if (mListenerHandler == null) mListener.onAnimationEnd(this);
+        if (hasAnimationListener()) {
+            if (mListenerHandler == null) dispatchAnimationEnd();
             else mListenerHandler.postAtFrontOfQueue(mOnEnd);
         }
     }
 
+    void dispatchAnimationStart() {
+        if (mListener != null) {
+            mListener.onAnimationStart(this);
+        }
+        if (mListeners != null && !mListeners.isEmpty()) {
+            for (AnimationListener listener : mListeners) {
+                listener.onAnimationStart(this);
+            }
+        }
+    }
+
+    void dispatchAnimationRepeat() {
+        if (mListener != null) {
+            mListener.onAnimationRepeat(this);
+        }
+        if (mListeners != null && !mListeners.isEmpty()) {
+            for (AnimationListener listener : mListeners) {
+                listener.onAnimationRepeat(this);
+            }
+        }
+    }
+
+    void dispatchAnimationEnd() {
+        if (mListener != null) {
+            mListener.onAnimationEnd(this);
+        }
+        if (mListeners != null && !mListeners.isEmpty()) {
+            for (AnimationListener listener : mListeners) {
+                listener.onAnimationEnd(this);
+            }
+        }
+    }
+
     /**
      * Gets the transformation to apply at a specified point in time. Implementations of this
      * method should always replace the specified Transformation or document they are doing
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 767024e..03c6ca6 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -389,16 +389,12 @@
         }
 
         if (started && !mStarted) {
-            if (mListener != null) {
-                mListener.onAnimationStart(this);
-            }
+            dispatchAnimationStart();
             mStarted = true;
         }
 
         if (ended != mEnded) {
-            if (mListener != null) {
-                mListener.onAnimationEnd(this);
-            }
+            dispatchAnimationEnd();
             mEnded = ended;
         }