Add keyframes to animation framework.

Change-Id: I5c8c8037aeeedae1ce7a18200986caf57264772f
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index db8b9eb..24c3c88 100755
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -190,6 +190,11 @@
     private Object mAnimatedValue = null;
 
     /**
+     * The set of keyframes (time/value pairs) that define this animation.
+     */
+    private KeyframeSet mKeyframeSet = null;
+
+    /**
      * The type of the values, as determined by the valueFrom/valueTo properties.
      */
     Class mValueType;
@@ -222,6 +227,20 @@
     }
 
     /**
+     * This constructor takes a set of {@link Keyframe} objects that define the values
+     * for the animation, along with the times at which those values will hold true during
+     * the animation.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param keyframes The set of keyframes that define the time/value pairs for the animation.
+     */
+    public Animator(long duration, Keyframe...keyframes) {
+        mDuration = duration;
+        mKeyframeSet = new KeyframeSet(keyframes);
+        mValueType = keyframes[0].getType();
+    }
+
+    /**
      * This function is called immediately before processing the first animation
      * frame of an animation. If there is a nonzero <code>startDelay</code>, the
      * function is called after that delay ends.
@@ -421,6 +440,15 @@
     }
 
     /**
+     * Gets the set of keyframes that define this animation.
+     *
+     * @return KeyframeSet The set of keyframes for this animation.
+     */
+    KeyframeSet getKeyframes() {
+        return mKeyframeSet;
+    }
+
+    /**
      * Gets the value that this animation will start from.
      *
      * @return Object The starting value for the animation.
@@ -751,7 +779,11 @@
      */
     void animateValue(float fraction) {
         fraction = mInterpolator.getInterpolation(fraction);
-        mAnimatedValue = mEvaluator.evaluate(fraction, mValueFrom, mValueTo);
+        if (mKeyframeSet != null) {
+            mAnimatedValue = mKeyframeSet.getValue(fraction, mEvaluator);
+        } else {
+            mAnimatedValue = mEvaluator.evaluate(fraction, mValueFrom, mValueTo);
+        }
         if (mUpdateListeners != null) {
             int numListeners = mUpdateListeners.size();
             for (int i = 0; i < numListeners; ++i) {
diff --git a/core/java/android/animation/Keyframe.java b/core/java/android/animation/Keyframe.java
new file mode 100644
index 0000000..b98994a
--- /dev/null
+++ b/core/java/android/animation/Keyframe.java
@@ -0,0 +1,185 @@
+/*
+ * 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 android.animation;
+
+import android.view.animation.Interpolator;
+
+/**
+ * This class represents timea time/value pair for an animation. The Keyframe class is used
+ * by {@link Animator} to define the values that the animation target will have over the course
+ * of the animation. As the time proceeds from one keyframe to the other, the value of the
+ * target object will animate between the value at the previous keyframe and the value at the
+ * next keyframe. Each keyframe also holds an option {@link android.view.animation.Interpolator}
+ * object, which defines the time interpolation over the intervalue preceding the keyframe.
+ */
+public class Keyframe {
+    /**
+     * The time at which mValue will hold true.
+     */
+    private float mFraction;
+
+    /**
+     * The value of the animation at the time mFraction.
+     */
+    private Object mValue;
+
+    /**
+     * The type of the value in this Keyframe. This type is determined at construction time,
+     * based on the type of the <code>value</code> object passed into the constructor.
+     */
+    private Class mValueType;
+
+    /**
+     * The optional time interpolator for the interval preceding this keyframe. A null interpolator
+     * (the default) results in linear interpolation over the interval.
+     */
+    private Interpolator mInterpolator = null;
+
+    /**
+     * Private constructor, called from the public constructors with the additional
+     * <code>valueType</code> parameter.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     * @param value The value that the object will animate to as the animation time approaches
+     * the time in this keyframe, and the the value animated from as the time passes the time in
+     * this keyframe.
+     * @param valueType The type of the <code>value</code> object. This is used by the
+     * {@link #getValue()} functionm, which is queried by {@link Animator} to determine
+     * the type of {@link TypeEvaluator} to use to interpolate between values.
+     */
+    private Keyframe(float fraction, Object value, Class valueType) {
+        mFraction = fraction;
+        mValue = value;
+        mValueType = valueType;
+    }
+
+    /**
+     * Constructs a Keyframe object with the given time and value. The time defines the
+     * time, as a proportion of an overall animation's duration, at which the value will hold true
+     * for the animation. The value for the animation between keyframes will be calculated as
+     * an interpolation between the values at those keyframes.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     * @param value The value that the object will animate to as the animation time approaches
+     * the time in this keyframe, and the the value animated from as the time passes the time in
+     * this keyframe.
+     */
+    public Keyframe(float fraction, Object value) {
+        this(fraction, value, Object.class);
+    }
+
+    /**
+     * Constructs a Keyframe object with the given time and integer value. The time defines the
+     * time, as a proportion of an overall animation's duration, at which the value will hold true
+     * for the animation. The value for the animation between keyframes will be calculated as
+     * an interpolation between the values at those keyframes.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     * @param value The value that the object will animate to as the animation time approaches
+     * the time in this keyframe, and the the value animated from as the time passes the time in
+     * this keyframe.
+     */
+    public Keyframe(float fraction, int value) {
+        this(fraction, value, int.class);
+    }
+
+    /**
+     * Constructs a Keyframe object with the given time and float value. The time defines the
+     * time, as a proportion of an overall animation's duration, at which the value will hold true
+     * for the animation. The value for the animation between keyframes will be calculated as
+     * an interpolation between the values at those keyframes.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     * @param value The value that the object will animate to as the animation time approaches
+     * the time in this keyframe, and the the value animated from as the time passes the time in
+     * this keyframe.
+     */
+    public Keyframe(float fraction, float value) {
+        this(fraction, value, float.class);
+    }
+
+    /**
+     * Constructs a Keyframe object with the given time and double value. The time defines the
+     * time, as a proportion of an overall animation's duration, at which the value will hold true
+     * for the animation. The value for the animation between keyframes will be calculated as
+     * an interpolation between the values at those keyframes.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     * @param value The value that the object will animate to as the animation time approaches
+     * the time in this keyframe, and the the value animated from as the time passes the time in
+     * this keyframe.
+     */
+    public Keyframe(float fraction, double value) {
+        this(fraction, value, double.class);
+    }
+
+    /**
+     * Gets the value for this Keyframe.
+     *
+     * @return The value for this Keyframe.
+     */
+    public Object getValue() {
+        return mValue;
+    }
+
+    /**
+     * Gets the time for this keyframe, as a fraction of the overall animation duration.
+     *
+     * @return The time associated with this keyframe, as a fraction of the overall animation
+     * duration. This should be a value between 0 and 1.
+     */
+    public float getFraction() {
+        return mFraction;
+    }
+
+    /**
+     * Gets the optional interpolator for this Keyframe. A value of <code>null</code> indicates
+     * that there is no interpolation, which is the same as linear interpolation.
+     *
+     * @return The optional interpolator for this Keyframe.
+     */
+    public Interpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    /**
+     * Sets the optional interpolator for this Keyframe. A value of <code>null</code> indicates
+     * that there is no interpolation, which is the same as linear interpolation.
+     *
+     * @return The optional interpolator for this Keyframe.
+     */
+    public void setInterpolator(Interpolator interpolator) {
+        mInterpolator = interpolator;
+    }
+
+    /**
+     * Gets the type of keyframe. This information is used by Animator to determine the type of
+     * {@linke TypeEvaluator} to use when calculating values between keyframes. The type is based
+     * on the type of Keyframe created. For example, {@link IntKeyframe} returns a value of
+     * <code>int.class</code>. This superclass returns a value of <code>Object.class</code>.
+     *
+     * @return The type of the value stored in the Keyframe.
+     */
+    public Class getType() {
+        return mValueType;
+    }
+}
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
new file mode 100644
index 0000000..59e9708
--- /dev/null
+++ b/core/java/android/animation/KeyframeSet.java
@@ -0,0 +1,99 @@
+/*
+ * 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 android.animation;
+
+import java.util.ArrayList;
+
+import android.view.animation.Interpolator;
+
+/**
+ * This class holds a collection of Keyframe objects and is called by Animator to calculate
+ * values between those keyframes for a given animation. The class internal to the animation
+ * package because it is an implementation detail of how Keyframes are stored and used.
+ */
+class KeyframeSet {
+
+    private int mNumKeyframes;
+
+    private ArrayList<Keyframe> mKeyframes;
+
+    public KeyframeSet(Keyframe... keyframes) {
+        mKeyframes = new ArrayList<Keyframe>();
+        for (Keyframe keyframe : keyframes) {
+            mKeyframes.add(keyframe);
+        }
+        mNumKeyframes = mKeyframes.size();
+    }
+
+    /**
+     * Gets the animated value, given the elapsed fraction of the animation (interpolated by the
+     * animation's interpolator) and the evaluator used to calculate in-between values. This
+     * function maps the input fraction to the appropriate keyframe interval and a fraction
+     * between them and returns the interpolated value. Note that the input fraction may fall
+     * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a
+     * spring interpolation that might send the fraction past 1.0). We handle this situation by
+     * just using the two keyframes at the appropriate end when the value is outside those bounds.
+     *
+     * @param fraction The elapsed fraction of the animation
+     * @param evaluator The type evaluator to use when calculating the interpolated values.
+     * @return The animated value.
+     */
+    public Object getValue(float fraction, TypeEvaluator evaluator) {
+        // TODO: special-case 2-keyframe common case
+
+        if (fraction <= 0f) {
+            final Keyframe prevKeyframe = mKeyframes.get(0);
+            final Keyframe nextKeyframe = mKeyframes.get(1);
+            final Interpolator interpolator = nextKeyframe.getInterpolator();
+            if (interpolator != null) {
+                fraction = interpolator.getInterpolation(fraction);
+            }
+            float intervalFraction = (fraction - prevKeyframe.getFraction()) /
+                (nextKeyframe.getFraction() - prevKeyframe.getFraction());
+            return evaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
+                    nextKeyframe.getValue());
+        } else if (fraction >= 1f) {
+            final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
+            final Keyframe nextKeyframe = mKeyframes.get(mNumKeyframes - 1);
+            final Interpolator interpolator = nextKeyframe.getInterpolator();
+            if (interpolator != null) {
+                fraction = interpolator.getInterpolation(fraction);
+            }
+            float intervalFraction = (fraction - prevKeyframe.getFraction()) /
+                (nextKeyframe.getFraction() - prevKeyframe.getFraction());
+            return evaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
+                    nextKeyframe.getValue());
+        }
+        Keyframe prevKeyframe = mKeyframes.get(0);
+        for (int i = 1; i < mNumKeyframes; ++i) {
+            Keyframe nextKeyframe = mKeyframes.get(i);
+            if (fraction < nextKeyframe.getFraction()) {
+                final Interpolator interpolator = nextKeyframe.getInterpolator();
+                if (interpolator != null) {
+                    fraction = interpolator.getInterpolation(fraction);
+                }
+                float intervalFraction = (fraction - prevKeyframe.getFraction()) /
+                    (nextKeyframe.getFraction() - prevKeyframe.getFraction());
+                return evaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
+                        nextKeyframe.getValue());
+            }
+            prevKeyframe = nextKeyframe;
+        }
+        // shouldn't get here
+        return mKeyframes.get(mNumKeyframes - 1).getValue();
+    }
+}
diff --git a/core/java/android/animation/PropertyAnimator.java b/core/java/android/animation/PropertyAnimator.java
index 99799f0..937dd58 100644
--- a/core/java/android/animation/PropertyAnimator.java
+++ b/core/java/android/animation/PropertyAnimator.java
@@ -55,6 +55,9 @@
     // at a time.
     private ReentrantReadWriteLock propertyMapLock = new ReentrantReadWriteLock();
 
+    // Used to pass single value to varargs parameter in setter invocation
+    private Object[] mTmpValueArray = new Object[1];
+
 
     /**
      * Sets the name of the property that will be animated. This name is used to derive
@@ -272,6 +275,33 @@
     }
 
     /**
+     * A constructor that takes <code>Keyframe</code>s. When this constructor
+     * is called, the system expects to find a setter for <code>propertyName</code> on
+     * the target object that takes a value of the same type as that returned from
+     * {@link Keyframe#getType()}.
+     * .
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have a public function on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property on the <code>target</code> object
+     * that will be animated. Given this name, the constructor will search for a
+     * setter on the target object with the name <code>setPropertyName</code>. For example,
+     * if the constructor is called with <code>propertyName = "foo"</code>, then the
+     * target object should have a setter function with the name <code>setFoo()</code>.
+     * @param keyframes The set of keyframes that define the times and values for the animation.
+     * These keyframes should be ordered in increasing time value, should have a starting
+     * keyframe with a fraction of 0 and and ending keyframe with a fraction of 1.
+     */
+    public PropertyAnimator(int duration, Object target, String propertyName,
+            Keyframe...keyframes) {
+        super(duration, keyframes);
+        mTarget = target;
+        mPropertyName = propertyName;
+    }
+
+    /**
      * This function is called immediately before processing the first animation
      * frame of an animation. If there is a nonzero <code>startDelay</code>, the
      * function is called after that delay ends.
@@ -309,7 +339,7 @@
                 propertyMapLock.writeLock().unlock();
             }
         }
-        if (getValueFrom() == null || getValueTo() == null) {
+        if (getKeyframes() == null && (getValueFrom() == null || getValueTo() == null)) {
             // Need to set up the getter if not set by the user, then call it
             // to get the initial values
             if (mGetter == null) {
@@ -376,7 +406,8 @@
         super.animateValue(fraction);
         if (mSetter != null) {
             try {
-                mSetter.invoke(mTarget, getAnimatedValue());
+                mTmpValueArray[0] = getAnimatedValue();
+                mSetter.invoke(mTarget, mTmpValueArray);
             } catch (InvocationTargetException e) {
                 Log.e("PropertyAnimator", e.toString());
             } catch (IllegalAccessException e) {