content: /*
   * 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.graphics.Path;
  import android.graphics.PointF;
  import android.util.FloatProperty;
  import android.util.IntProperty;
  import android.util.Log;
  import android.util.PathParser;
  import android.util.Property;
  
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.util.HashMap;
  import java.util.List;
  
  /**
   * This class holds information about a property and the values that that property
   * should take on during an animation. PropertyValuesHolder objects can be used to create
   * animations with ValueAnimator or ObjectAnimator that operate on several different properties
   * in parallel.
   */
  public class PropertyValuesHolder implements Cloneable {
  
      /**
       * The name of the property associated with the values. This need not be a real property,
       * unless this object is being used with ObjectAnimator. But this is the name by which
       * aniamted values are looked up with getAnimatedValue(String) in ValueAnimator.
       */
      String mPropertyName;
  
      /**
       * @hide
       */
      protected Property mProperty;
  
      /**
       * The setter function, if needed. ObjectAnimator hands off this functionality to
       * PropertyValuesHolder, since it holds all of the per-property information. This
       * property is automatically
       * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
       */
      Method mSetter = null;
  
      /**
       * The getter function, if needed. ObjectAnimator hands off this functionality to
       * PropertyValuesHolder, since it holds all of the per-property information. This
       * property is automatically
       * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
       * The getter is only derived and used if one of the values is null.
       */
      private Method mGetter = null;
  
      /**
       * The type of values supplied. This information is used both in deriving the setter/getter
       * functions and in deriving the type of TypeEvaluator.
       */
      Class mValueType;
  
      /**
       * The set of keyframes (time/value pairs) that define this animation.
       */
      Keyframes mKeyframes = null;
  
  
      // type evaluators for the primitive types handled by this implementation
      private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
      private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
  
      // We try several different types when searching for appropriate setter/getter functions.
      // The caller may have supplied values in a type that does not match the setter/getter
      // functions (such as the integers 0 and 1 to represent floating point values for alpha).
      // Also, the use of generics in constructors means that we end up with the Object versions
      // of primitive types (Float vs. float). But most likely, the setter/getter functions
      // will take primitive types instead.
      // So we supply an ordered array of other types to try before giving up.
      private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class,
              Double.class, Integer.class};
      private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class,
              Float.class, Double.class};
      private static Class[] DOUBLE_VARIANTS = {double.class, Double.class, float.class, int.class,
              Float.class, Integer.class};
  
      // These maps hold all property entries for a particular class. This map
      // is used to speed up property/setter/getter lookups for a given class/property
      // combination. No need to use reflection on the combination more than once.
      private static final HashMap<Class, HashMap<String, Method>> sSetterPropertyMap =
              new HashMap<Class, HashMap<String, Method>>();
      private static final HashMap<Class, HashMap<String, Method>> sGetterPropertyMap =
              new HashMap<Class, HashMap<String, Method>>();
  
      // Used to pass single value to varargs parameter in setter invocation
      final Object[] mTmpValueArray = new Object[1];
  
      /**
       * The type evaluator used to calculate the animated values. This evaluator is determined
       * automatically based on the type of the start/end objects passed into the constructor,
       * but the system only knows about the primitive types int and float. Any other
       * type will need to set the evaluator to a custom evaluator for that type.
       */
      private TypeEvaluator mEvaluator;
  
      /**
       * The value most recently calculated by calculateValue(). This is set during
       * that function and might be retrieved later either by ValueAnimator.animatedValue() or
       * by the property-setting logic in ObjectAnimator.animatedValue().
       */
      private Object mAnimatedValue;
  
      /**
       * Converts from the source Object type to the setter Object type.
       */
      private TypeConverter mConverter;
  
      /**
       * Internal utility constructor, used by the factory methods to set the property name.
       * @param propertyName The name of the property for this holder.
       */
      private PropertyValuesHolder(String propertyName) {
          mPropertyName = propertyName;
      }
  
      /**
       * Internal utility constructor, used by the factory methods to set the property.
       * @param property The property for this holder.
       */
      private PropertyValuesHolder(Property property) {
          mProperty = property;
          if (property != null) {
              mPropertyName = property.getName();
          }
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property name and
       * set of int values.
       * @param propertyName The name of the property being animated.
       * @param values The values that the named property will animate between.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       */
      public static PropertyValuesHolder ofInt(String propertyName, int... values) {
          return new IntPropertyValuesHolder(propertyName, values);
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property and
       * set of int values.
       * @param property The property being animated. Should not be null.
       * @param values The values that the property will animate between.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       */
      public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) {
          return new IntPropertyValuesHolder(property, values);
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property name and
       * set of <code>int[]</code> values. At least two <code>int[]</code> values must be supplied,
       * a start and end value. If more values are supplied, the values will be animated from the
       * start, through all intermediate values to the end value. When used with ObjectAnimator,
       * the elements of the array represent the parameters of the setter function.
       *
       * @param propertyName The name of the property being animated. Can also be the
       *                     case-sensitive name of the entire setter method. Should not be null.
       * @param values The values that the property will animate between.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       * @see IntArrayEvaluator#IntArrayEvaluator(int[])
       * @see ObjectAnimator#ofMultiInt(Object, String, TypeConverter, TypeEvaluator, Object[])
       */
      public static PropertyValuesHolder ofMultiInt(String propertyName, int[][] values) {
          if (values.length < 2) {
              throw new IllegalArgumentException("At least 2 values must be supplied");
          }
          int numParameters = 0;
          for (int i = 0; i < values.length; i++) {
              if (values[i] == null) {
                  throw new IllegalArgumentException("values must not be null");
              }
              int length = values[i].length;
              if (i == 0) {
                  numParameters = length;
              } else if (length != numParameters) {
                  throw new IllegalArgumentException("Values must all have the same length");
              }
          }
          IntArrayEvaluator evaluator = new IntArrayEvaluator(new int[numParameters]);
          return new MultiIntValuesHolder(propertyName, null, evaluator, (Object[]) values);
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property name to use
       * as a multi-int setter. The values are animated along the path, with the first
       * parameter of the setter set to the x coordinate and the second set to the y coordinate.
       *
       * @param propertyName The name of the property being animated. Can also be the
       *                     case-sensitive name of the entire setter method. Should not be null.
       *                     The setter must take exactly two <code>int</code> parameters.
       * @param path The Path along which the values should be animated.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
       */
      public static PropertyValuesHolder ofMultiInt(String propertyName, Path path) {
          Keyframes keyframes = KeyframeSet.ofPath(path);
          PointFToIntArray converter = new PointFToIntArray();
          return new MultiIntValuesHolder(propertyName, converter, null, keyframes);
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property and
       * set of Object values for use with ObjectAnimator multi-value setters. The Object
       * values are converted to <code>int[]</code> using the converter.
       *
       * @param propertyName The property being animated or complete name of the setter.
       *                     Should not be null.
       * @param converter Used to convert the animated value to setter parameters.
       * @param evaluator A TypeEvaluator that will be called on each animation frame to
       * provide the necessary interpolation between the Object values to derive the animated
       * value.
       * @param values The values that the property will animate between.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       * @see ObjectAnimator#ofMultiInt(Object, String, TypeConverter, TypeEvaluator, Object[])
       * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
       */
      @SafeVarargs
      public static <V> PropertyValuesHolder ofMultiInt(String propertyName,
              TypeConverter<V, int[]> converter, TypeEvaluator<V> evaluator, V... values) {
          return new MultiIntValuesHolder(propertyName, converter, evaluator, values);
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder object with the specified property name or
       * setter name for use in a multi-int setter function using ObjectAnimator. The values can be
       * of any type, but the type should be consistent so that the supplied
       * {@link android.animation.TypeEvaluator} can be used to to evaluate the animated value. The
       * <code>converter</code> converts the values to parameters in the setter function.
       *
       * <p>At least two values must be supplied, a start and an end value.</p>
       *
       * @param propertyName The name of the property to associate with the set of values. This
       *                     may also be the complete name of a setter function.
       * @param converter    Converts <code>values</code> into int parameters for the setter.
       *                     Can be null if the Keyframes have int[] values.
       * @param evaluator    Used to interpolate between values.
       * @param values       The values at specific fractional times to evaluate between
       * @return A PropertyValuesHolder for a multi-int parameter setter.
       */
      public static <T> PropertyValuesHolder ofMultiInt(String propertyName,
              TypeConverter<T, int[]> converter, TypeEvaluator<T> evaluator, Keyframe... values) {
          KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
          return new MultiIntValuesHolder(propertyName, converter, evaluator, keyframeSet);
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property name and
       * set of float values.
       * @param propertyName The name of the property being animated.
       * @param values The values that the named property will animate between.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       */
      public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
          return new FloatPropertyValuesHolder(propertyName, values);
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property and
       * set of float values.
       * @param property The property being animated. Should not be null.
       * @param values The values that the property will animate between.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       */
      public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) {
          return new FloatPropertyValuesHolder(property, values);
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property name and
       * set of <code>float[]</code> values. At least two <code>float[]</code> values must be supplied,
       * a start and end value. If more values are supplied, the values will be animated from the
       * start, through all intermediate values to the end value. When used with ObjectAnimator,
       * the elements of the array represent the parameters of the setter function.
       *
       * @param propertyName The name of the property being animated. Can also be the
       *                     case-sensitive name of the entire setter method. Should not be null.
       * @param values The values that the property will animate between.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       * @see FloatArrayEvaluator#FloatArrayEvaluator(float[])
       * @see ObjectAnimator#ofMultiFloat(Object, String, TypeConverter, TypeEvaluator, Object[])
       */
      public static PropertyValuesHolder ofMultiFloat(String propertyName, float[][] values) {
          if (values.length < 2) {
              throw new IllegalArgumentException("At least 2 values must be supplied");
          }
          int numParameters = 0;
          for (int i = 0; i < values.length; i++) {
              if (values[i] == null) {
                  throw new IllegalArgumentException("values must not be null");
              }
              int length = values[i].length;
              if (i == 0) {
                  numParameters = length;
              } else if (length != numParameters) {
                  throw new IllegalArgumentException("Values must all have the same length");
              }
          }
          FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[numParameters]);
          return new MultiFloatValuesHolder(propertyName, null, evaluator, (Object[]) values);
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property name to use
       * as a multi-float setter. The values are animated along the path, with the first
       * parameter of the setter set to the x coordinate and the second set to the y coordinate.
       *
       * @param propertyName The name of the property being animated. Can also be the
       *                     case-sensitive name of the entire setter method. Should not be null.
       *                     The setter must take exactly two <code>float</code> parameters.
       * @param path The Path along which the values should be animated.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
       */
      public static PropertyValuesHolder ofMultiFloat(String propertyName, Path path) {
          Keyframes keyframes = KeyframeSet.ofPath(path);
          PointFToFloatArray converter = new PointFToFloatArray();
          return new MultiFloatValuesHolder(propertyName, converter, null, keyframes);
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property and
       * set of Object values for use with ObjectAnimator multi-value setters. The Object
       * values are converted to <code>float[]</code> using the converter.
       *
       * @param propertyName The property being animated or complete name of the setter.
       *                     Should not be null.
       * @param converter Used to convert the animated value to setter parameters.
       * @param evaluator A TypeEvaluator that will be called on each animation frame to
       * provide the necessary interpolation between the Object values to derive the animated
       * value.
       * @param values The values that the property will animate between.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       * @see ObjectAnimator#ofMultiFloat(Object, String, TypeConverter, TypeEvaluator, Object[])
       */
      @SafeVarargs
      public static <V> PropertyValuesHolder ofMultiFloat(String propertyName,
              TypeConverter<V, float[]> converter, TypeEvaluator<V> evaluator, V... values) {
          return new MultiFloatValuesHolder(propertyName, converter, evaluator, values);
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder object with the specified property name or
       * setter name for use in a multi-float setter function using ObjectAnimator. The values can be
       * of any type, but the type should be consistent so that the supplied
       * {@link android.animation.TypeEvaluator} can be used to to evaluate the animated value. The
       * <code>converter</code> converts the values to parameters in the setter function.
       *
       * <p>At least two values must be supplied, a start and an end value.</p>
       *
       * @param propertyName The name of the property to associate with the set of values. This
       *                     may also be the complete name of a setter function.
       * @param converter    Converts <code>values</code> into float parameters for the setter.
       *                     Can be null if the Keyframes have float[] values.
       * @param evaluator    Used to interpolate between values.
       * @param values       The values at specific fractional times to evaluate between
       * @return A PropertyValuesHolder for a multi-float parameter setter.
       */
      public static <T> PropertyValuesHolder ofMultiFloat(String propertyName,
              TypeConverter<T, float[]> converter, TypeEvaluator<T> evaluator, Keyframe... values) {
          KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
          return new MultiFloatValuesHolder(propertyName, converter, evaluator, keyframeSet);
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property name and
       * set of Object values. This variant also takes a TypeEvaluator because the system
       * cannot automatically interpolate between objects of unknown type.
       *
       * <p><strong>Note:</strong> The Object values are stored as references to the original
       * objects, which means that changes to those objects after this method is called will
       * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
       * after this method is called, callers should pass a copy of those objects instead.
       *
       * @param propertyName The name of the property being animated.
       * @param evaluator A TypeEvaluator that will be called on each animation frame to
       * provide the necessary interpolation between the Object values to derive the animated
       * value.
       * @param values The values that the named property will animate between.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       */
      public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,
              Object... values) {
          PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
          pvh.setObjectValues(values);
          pvh.setEvaluator(evaluator);
          return pvh;
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property name and
       * a Path along which the values should be animated. This variant supports a
       * <code>TypeConverter</code> to convert from <code>PointF</code> to the target
       * type.
       *
       * <p>The PointF passed to <code>converter</code> or <code>property</code>, if
       * <code>converter</code> is <code>null</code>, is reused on each animation frame and should
       * not be stored by the setter or TypeConverter.</p>
       *
       * @param propertyName The name of the property being animated.
       * @param converter Converts a PointF to the type associated with the setter. May be
       *                  null if conversion is unnecessary.
       * @param path The Path along which the values should be animated.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       */
      public static PropertyValuesHolder ofObject(String propertyName,
              TypeConverter<PointF, ?> converter, Path path) {
          PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
          pvh.mKeyframes = KeyframeSet.ofPath(path);
          pvh.mValueType = PointF.class;
          pvh.setConverter(converter);
          return pvh;
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property and
       * set of Object values. This variant also takes a TypeEvaluator because the system
       * cannot automatically interpolate between objects of unknown type.
       *
       * <p><strong>Note:</strong> The Object values are stored as references to the original
       * objects, which means that changes to those objects after this method is called will
       * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
       * after this method is called, callers should pass a copy of those objects instead.
       *
       * @param property The property being animated. Should not be null.
       * @param evaluator A TypeEvaluator that will be called on each animation frame to
       * provide the necessary interpolation between the Object values to derive the animated
       * value.
       * @param values The values that the property will animate between.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       */
      @SafeVarargs
      public static <V> PropertyValuesHolder ofObject(Property property,
              TypeEvaluator<V> evaluator, V... values) {
          PropertyValuesHolder pvh = new PropertyValuesHolder(property);
          pvh.setObjectValues(values);
          pvh.setEvaluator(evaluator);
          return pvh;
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property and
       * set of Object values. This variant also takes a TypeEvaluator because the system
       * cannot automatically interpolate between objects of unknown type. This variant also
       * takes a <code>TypeConverter</code> to convert from animated values to the type
       * of the property. If only one value is supplied, the <code>TypeConverter</code>
       * must be a {@link android.animation.BidirectionalTypeConverter} to retrieve the current
       * value.
       *
       * <p><strong>Note:</strong> The Object values are stored as references to the original
       * objects, which means that changes to those objects after this method is called will
       * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
       * after this method is called, callers should pass a copy of those objects instead.
       *
       * @param property The property being animated. Should not be null.
       * @param converter Converts the animated object to the Property type.
       * @param evaluator A TypeEvaluator that will be called on each animation frame to
       * provide the necessary interpolation between the Object values to derive the animated
       * value.
       * @param values The values that the property will animate between.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       * @see #setConverter(TypeConverter)
       * @see TypeConverter
       */
      @SafeVarargs
      public static <T, V> PropertyValuesHolder ofObject(Property<?, V> property,
              TypeConverter<T, V> converter, TypeEvaluator<T> evaluator, T... values) {
          PropertyValuesHolder pvh = new PropertyValuesHolder(property);
          pvh.setConverter(converter);
          pvh.setObjectValues(values);
          pvh.setEvaluator(evaluator);
          return pvh;
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder with a given property and
       * a Path along which the values should be animated. This variant supports a
       * <code>TypeConverter</code> to convert from <code>PointF</code> to the target
       * type.
       *
       * <p>The PointF passed to <code>converter</code> or <code>property</code>, if
       * <code>converter</code> is <code>null</code>, is reused on each animation frame and should
       * not be stored by the setter or TypeConverter.</p>
       *
       * @param property The property being animated. Should not be null.
       * @param converter Converts a PointF to the type associated with the setter. May be
       *                  null if conversion is unnecessary.
       * @param path The Path along which the values should be animated.
       * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
       */
      public static <V> PropertyValuesHolder ofObject(Property<?, V> property,
              TypeConverter<PointF, V> converter, Path path) {
          PropertyValuesHolder pvh = new PropertyValuesHolder(property);
          pvh.mKeyframes = KeyframeSet.ofPath(path);
          pvh.mValueType = PointF.class;
          pvh.setConverter(converter);
          return pvh;
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder object with the specified property name and set
       * of values. These values can be of any type, but the type should be consistent so that
       * an appropriate {@link android.animation.TypeEvaluator} can be found that matches
       * the common type.
       * <p>If there is only one value, it is assumed to be the end value of an animation,
       * and an initial value will be derived, if possible, by calling a getter function
       * on the object. Also, if any value is null, the value will be filled in when the animation
       * starts in the same way. This mechanism of automatically getting null values only works
       * if the PropertyValuesHolder object is used in conjunction
       * {@link ObjectAnimator}, and with a getter function
       * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
       * no way of determining what the value should be.
       * @param propertyName The name of the property associated with this set of values. This
       * can be the actual property name to be used when using a ObjectAnimator object, or
       * just a name used to get animated values, such as if this object is used with an
       * ValueAnimator object.
       * @param values The set of values to animate between.
       */
      public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) {
          KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
          return ofKeyframes(propertyName, keyframeSet);
      }
  
      /**
       * Constructs and returns a PropertyValuesHolder object with the specified property and set
       * of values. These values can be of any type, but the type should be consistent so that
       * an appropriate {@link android.animation.TypeEvaluator} can be found that matches
       * the common type.
       * <p>If there is only one value, it is assumed to be the end value of an animation,
       * and an initial value will be derived, if possible, by calling the property's
       * {@link android.util.Property#get(Object)} function.
       * Also, if any value is null, the value will be filled in when the animation
       * starts in the same way. This mechanism of automatically getting null values only works
       * if the PropertyValuesHolder object is used in conjunction with
       * {@link ObjectAnimator}, since otherwise PropertyValuesHolder has
       * no way of determining what the value should be.
       * @param property The property associated with this set of values. Should not be null.
       * @param values The set of values to animate between.
       */
      public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) {
          KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
          return ofKeyframes(property, keyframeSet);
      }
  
      static PropertyValuesHolder ofKeyframes(String propertyName, Keyframes keyframes) {
          if (keyframes instanceof Keyframes.IntKeyframes) {
              return new IntPropertyValuesHolder(propertyName, (Keyframes.IntKeyframes) keyframes);
          } else if (keyframes instanceof Keyframes.FloatKeyframes) {
              return new FloatPropertyValuesHolder(propertyName,
                      (Keyframes.FloatKeyframes) keyframes);
          } else {
              PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
              pvh.mKeyframes = keyframes;
              pvh.mValueType = keyframes.getType();
              return pvh;
          }
      }
  
      static PropertyValuesHolder ofKeyframes(Property property, Keyframes keyframes) {
          if (keyframes instanceof Keyframes.IntKeyframes) {
              return new IntPropertyValuesHolder(property, (Keyframes.IntKeyframes) keyframes);
          } else if (keyframes instanceof Keyframes.FloatKeyframes) {
              return new FloatPropertyValuesHolder(property, (Keyframes.FloatKeyframes) keyframes);
          } else {
              PropertyValuesHolder pvh = new PropertyValuesHolder(property);
              pvh.mKeyframes = keyframes;
              pvh.mValueType = keyframes.getType();
              return pvh;
          }
      }
  
      /**
       * Set the animated values for this object to this set of ints.
       * If there is only one value, it is assumed to be the end value of an animation,
       * and an initial value will be derived, if possible, by calling a getter function
       * on the object. Also, if any value is null, the value will be filled in when the animation
       * starts in the same way. This mechanism of automatically getting null values only works
       * if the PropertyValuesHolder object is used in conjunction
       * {@link ObjectAnimator}, and with a getter function
       * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
       * no way of determining what the value should be.
       *
       * @param values One or more values that the animation will animate between.
       */
      public void setIntValues(int... values) {
          mValueType = int.class;
          mKeyframes = KeyframeSet.ofInt(values);
      }
  
      /**
       * Set the animated values for this object to this set of floats.
       * If there is only one value, it is assumed to be the end value of an animation,
       * and an initial value will be derived, if possible, by calling a getter function
       * on the object. Also, if any value is null, the value will be filled in when the animation
       * starts in the same way. This mechanism of automatically getting null values only works
       * if the PropertyValuesHolder object is used in conjunction
       * {@link ObjectAnimator}, and with a getter function
       * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
       * no way of determining what the value should be.
       *
       * @param values One or more values that the animation will animate between.
       */
      public void setFloatValues(float... values) {
          mValueType = float.class;
          mKeyframes = KeyframeSet.ofFloat(values);
      }
  
      /**
       * Set the animated values for this object to this set of Keyframes.
       *
       * @param values One or more values that the animation will animate between.
       */
      public void setKeyframes(Keyframe... values) {
          int numKeyframes = values.length;
          Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes,2)];
          mValueType = ((Keyframe)values[0]).getType();
          for (int i = 0; i < numKeyframes; ++i) {
              keyframes[i] = (Keyframe)values[i];
          }
          mKeyframes = new KeyframeSet(keyframes);
      }
  
      /**
       * Set the animated values for this object to this set of Objects.
       * If there is only one value, it is assumed to be the end value of an animation,
       * and an initial value will be derived, if possible, by calling a getter function
       * on the object. Also, if any value is null, the value will be filled in when the animation
       * starts in the same way. This mechanism of automatically getting null values only works
       * if the PropertyValuesHolder object is used in conjunction
       * {@link ObjectAnimator}, and with a getter function
       * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
       * no way of determining what the value should be.
       *
       * <p><strong>Note:</strong> The Object values are stored as references to the original
       * objects, which means that changes to those objects after this method is called will
       * affect the values on the PropertyValuesHolder. If the objects will be mutated externally
       * after this method is called, callers should pass a copy of those objects instead.
       *
       * @param values One or more values that the animation will animate between.
       */
      public void setObjectValues(Object... values) {
          mValueType = values[0].getClass();
          mKeyframes = KeyframeSet.ofObject(values);
          if (mEvaluator != null) {
              mKeyframes.setEvaluator(mEvaluator);
          }
      }
  
      /**
       * Sets the converter to convert from the values type to the setter's parameter type.
       * If only one value is supplied, <var>converter</var> must be a
       * {@link android.animation.BidirectionalTypeConverter}.
       * @param converter The converter to use to convert values.
       */
      public void setConverter(TypeConverter converter) {
          mConverter = converter;
      }
  
      /**
       * Determine the setter or getter function using the JavaBeans convention of setFoo or
       * getFoo for a property named 'foo'. This function figures out what the name of the
       * function should be and uses reflection to find the Method with that name on the
       * target object.
       *
       * @param targetClass The class to search for the method
       * @param prefix "set" or "get", depending on whether we need a setter or getter.
       * @param valueType The type of the parameter (in the case of a setter). This type
       * is derived from the values set on this PropertyValuesHolder. This type is used as
       * a first guess at the parameter type, but we check for methods with several different
       * types to avoid problems with slight mis-matches between supplied values and actual
       * value types used on the setter.
       * @return Method the method associated with mPropertyName.
       */
      private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
          // TODO: faster implementation...
          Method returnVal = null;
          String methodName = getMethodName(prefix, mPropertyName);
          Class args[] = null;
          if (valueType == null) {
              try {
                  returnVal = targetClass.getMethod(methodName, args);
              } catch (NoSuchMethodException e) {
                  // Swallow the error, log it later
              }
          } else {
              args = new Class[1];
              Class typeVariants[];
              if (valueType.equals(Float.class)) {
                  typeVariants = FLOAT_VARIANTS;
              } else if (valueType.equals(Integer.class)) {
                  typeVariants = INTEGER_VARIANTS;
              } else if (valueType.equals(Double.class)) {
                  typeVariants = DOUBLE_VARIANTS;
              } else {
                  typeVariants = new Class[1];
                  typeVariants[0] = valueType;
              }
              for (Class typeVariant : typeVariants) {
                  args[0] = typeVariant;
                  try {
                      returnVal = targetClass.getMethod(methodName, args);
                      if (mConverter == null) {
                          // change the value type to suit
                          mValueType = typeVariant;
                      }
                      return returnVal;
                  } catch (NoSuchMethodException e) {
                      // Swallow the error and keep trying other variants
                  }
              }
              // If we got here, then no appropriate function was found
          }
  
          if (returnVal == null) {
              Log.w("PropertyValuesHolder", "Method " +
                      getMethodName(prefix, mPropertyName) + "() with type " + valueType +
                      " not found on target class " + targetClass);
          }
  
          return returnVal;
      }
  
  
      /**
       * Returns the setter or getter requested. This utility function checks whether the
       * requested method exists in the propertyMapMap cache. If not, it calls another
       * utility function to request the Method from the targetClass directly.
       * @param targetClass The Class on which the requested method should exist.
       * @param propertyMapMap The cache of setters/getters derived so far.
       * @param prefix "set" or "get", for the setter or getter.
       * @param valueType The type of parameter passed into the method (null for getter).
       * @return Method the method associated with mPropertyName.
       */
      private Method setupSetterOrGetter(Class targetClass,
              HashMap<Class, HashMap<String, Method>> propertyMapMap,
              String prefix, Class valueType) {
          Method setterOrGetter = null;
          synchronized(propertyMapMap) {
              // Have to lock property map prior to reading it, to guard against
              // another thread putting something in there after we've checked it
              // but before we've added an entry to it
              HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
              boolean wasInMap = false;
              if (propertyMap != null) {
                  wasInMap = propertyMap.containsKey(mPropertyName);
                  if (wasInMap) {
                      setterOrGetter = propertyMap.get(mPropertyName);
                  }
              }
              if (!wasInMap) {
                  setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
                  if (propertyMap == null) {
                      propertyMap = new HashMap<String, Method>();
                      propertyMapMap.put(targetClass, propertyMap);
                  }
                  propertyMap.put(mPropertyName, setterOrGetter);
              }
          }
          return setterOrGetter;
      }
  
      /**
       * Utility function to get the setter from targetClass
       * @param targetClass The Class on which the requested method should exist.
       */
      void setupSetter(Class targetClass) {
          Class<?> propertyType = mConverter == null ? mValueType : mConverter.getTargetType();
          mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType);
      }
  
      /**
       * Utility function to get the getter from targetClass
       */
      private void setupGetter(Class targetClass) {
          mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null);
      }
  
      /**
       * Internal function (called from ObjectAnimator) to set up the setter and getter
       * prior to running the animation. If the setter has not been manually set for this
       * object, it will be derived automatically given the property name, target object, and
       * types of values supplied. If no getter has been set, it will be supplied iff any of the
       * supplied values was null. If there is a null value, then the getter (supplied or derived)
       * will be called to set those null values to the current value of the property
       * on the target object.
       * @param target The object on which the setter (and possibly getter) exist.
       */
      void setupSetterAndGetter(Object target) {
          if (mProperty != null) {
              // check to make sure that mProperty is on the class of target
              try {
                  Object testValue = null;
                  List<Keyframe> keyframes = mKeyframes.getKeyframes();
                  int keyframeCount = keyframes == null ? 0 : keyframes.size();
                  for (int i = 0; i < keyframeCount; i++) {
                      Keyframe kf = keyframes.get(i);
                      if (!kf.hasValue() || kf.valueWasSetOnStart()) {
                          if (testValue == null) {
                              testValue = convertBack(mProperty.get(target));
                          }
                          kf.setValue(testValue);
                          kf.setValueWasSetOnStart(true);
                      }
                  }
                  return;
              } catch (ClassCastException e) {
                  Log.w("PropertyValuesHolder","No such property (" + mProperty.getName() +
                          ") on target object " + target + ". Trying reflection instead");
                  mProperty = null;
              }
          }
          // We can't just say 'else' here because the catch statement sets mProperty to null.
          if (mProperty == null) {
              Class targetClass = target.getClass();
              if (mSetter == null) {
                  setupSetter(targetClass);
              }
              List<Keyframe> keyframes = mKeyframes.getKeyframes();
              int keyframeCount = keyframes == null ? 0 : keyframes.size();
              for (int i = 0; i < keyframeCount; i++) {
                  Keyframe kf = keyframes.get(i);
                  if (!kf.hasValue() || kf.valueWasSetOnStart()) {
                      if (mGetter == null) {
                          setupGetter(targetClass);
                          if (mGetter == null) {
                              // Already logged the error - just return to avoid NPE
                              return;
                          }
                      }
                      try {
                          Object value = convertBack(mGetter.invoke(target));
                          kf.setValue(value);
                          kf.setValueWasSetOnStart(true);
                      } catch (InvocationTargetException e) {
                          Log.e("PropertyValuesHolder", e.toString());
                      } catch (IllegalAccessException e) {
                          Log.e("PropertyValuesHolder", e.toString());
                      }
                  }
              }
          }
      }
  
      private Object convertBack(Object value) {
          if (mConverter != null) {
              if (!(mConverter instanceof BidirectionalTypeConverter)) {
                  throw new IllegalArgumentException("Converter "
                          + mConverter.getClass().getName()
                          + " must be a BidirectionalTypeConverter");
              }
              value = ((BidirectionalTypeConverter) mConverter).convertBack(value);
          }
          return value;
      }
  
      /**
       * Utility function to set the value stored in a particular Keyframe. The value used is
       * whatever the value is for the property name specified in the keyframe on the target object.
       *
       * @param target The target object from which the current value should be extracted.
       * @param kf The keyframe which holds the property name and value.
       */
      private void setupValue(Object target, Keyframe kf) {
          if (mProperty != null) {
              Object value = convertBack(mProperty.get(target));
              kf.setValue(value);
          } else {
              try {
                  if (mGetter == null) {
                      Class targetClass = target.getClass();
                      setupGetter(targetClass);
                      if (mGetter == null) {
                          // Already logged the error - just return to avoid NPE
                          return;
                      }
                  }
                  Object value = convertBack(mGetter.invoke(target));
                  kf.setValue(value);
              } catch (InvocationTargetException e) {
                  Log.e("PropertyValuesHolder", e.toString());
              } catch (IllegalAccessException e) {
                  Log.e("PropertyValuesHolder", e.toString());
              }
          }
      }
  
      /**
       * This function is called by ObjectAnimator when setting the start values for an animation.
       * The start values are set according to the current values in the target object. The
       * property whose value is extracted is whatever is specified by the propertyName of this
       * PropertyValuesHolder object.
       *
       * @param target The object which holds the start values that should be set.
       */
      void setupStartValue(Object target) {
          List<Keyframe> keyframes = mKeyframes.getKeyframes();
          if (!keyframes.isEmpty()) {
              setupValue(target, keyframes.get(0));
          }
      }
  
      /**
       * This function is called by ObjectAnimator when setting the end values for an animation.
       * The end values are set according to the current values in the target object. The
       * property whose value is extracted is whatever is specified by the propertyName of this
       * PropertyValuesHolder object.
       *
       * @param target The object which holds the start values that should be set.
       */
      void setupEndValue(Object target) {
          List<Keyframe> keyframes = mKeyframes.getKeyframes();
          if (!keyframes.isEmpty()) {
              setupValue(target, keyframes.get(keyframes.size() - 1));
          }
      }
  
      @Override
      public PropertyValuesHolder clone() {
          try {
              PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone();
              newPVH.mPropertyName = mPropertyName;
              newPVH.mProperty = mProperty;
              newPVH.mKeyframes = mKeyframes.clone();
              newPVH.mEvaluator = mEvaluator;
              return newPVH;
          } catch (CloneNotSupportedException e) {
              // won't reach here
              return null;
          }
      }
  
      /**
       * Internal function to set the value on the target object, using the setter set up
       * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
       * to handle turning the value calculated by ValueAnimator into a value set on the object
       * according to the name of the property.
       * @param target The target object on which the value is set
       */
      void setAnimatedValue(Object target) {
          if (mProperty != null) {
              mProperty.set(target, getAnimatedValue());
          }
          if (mSetter != null) {
              try {
                  mTmpValueArray[0] = getAnimatedValue();
                  mSetter.invoke(target, mTmpValueArray);
              } catch (InvocationTargetException e) {
                  Log.e("PropertyValuesHolder", e.toString());
              } catch (IllegalAccessException e) {
                  Log.e("PropertyValuesHolder", e.toString());
              }
          }
      }
  
      /**
       * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used
       * to calculate animated values.
       */
      void init() {
          if (mEvaluator == null) {
              // We already handle int and float automatically, but not their Object
              // equivalents
              mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                      (mValueType == Float.class) ? sFloatEvaluator :
                      null;
          }
          if (mEvaluator != null) {
              // KeyframeSet knows how to evaluate the common types - only give it a custom
              // evaluator if one has been set on this class
              mKeyframes.setEvaluator(mEvaluator);
          }
      }
  
      /**
       * The TypeEvaluator will be automatically determined based on the type of values
       * supplied to PropertyValuesHolder. The evaluator can be manually set, however, if so
       * desired. This may be important in cases where either the type of the values supplied
       * do not match the way that they should be interpolated between, or if the values
       * are of a custom type or one not currently understood by the animation system. Currently,
       * only values of type float and int (and their Object equivalents: Float
       * and Integer) are  correctly interpolated; all other types require setting a TypeEvaluator.
       * @param evaluator
       */
      public void setEvaluator(TypeEvaluator evaluator) {
          mEvaluator = evaluator;
          mKeyframes.setEvaluator(evaluator);
      }
  
      /**
       * Function used to calculate the value according to the evaluator set up for
       * this PropertyValuesHolder object. This function is called by ValueAnimator.animateValue().
       *
       * @param fraction The elapsed, interpolated fraction of the animation.
       */
      void calculateValue(float fraction) {
          Object value = mKeyframes.getValue(fraction);
          mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
      }
  
      /**
       * Sets the name of the property that will be animated. This name is used to derive
       * a setter function that will be called to set animated values.
       * For example, a property name of <code>foo</code> will result
       * in a call to the function <code>setFoo()</code> on the target object. If either
       * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
       * also be derived and called.
       *
       * <p>Note that the setter function derived from this property name
       * must take the same parameter type as the
       * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
       * the setter function will fail.</p>
       *
       * @param propertyName The name of the property being animated.
       */
      public void setPropertyName(String propertyName) {
          mPropertyName = propertyName;
      }
  
      /**
       * Sets the property that will be animated.
       *
       * <p>Note that if this PropertyValuesHolder object is used with ObjectAnimator, the property
       * must exist on the target object specified in that ObjectAnimator.</p>
       *
       * @param property The property being animated.
       */
      public void setProperty(Property property) {
          mProperty = property;
      }
  
      /**
       * Gets the name of the property that will be animated. This name will be used to derive
       * a setter function that will be called to set animated values.
       * For example, a property name of <code>foo</code> will result
       * in a call to the function <code>setFoo()</code> on the target object. If either
       * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
       * also be derived and called.
       */
      public String getPropertyName() {
          return mPropertyName;
      }
  
      /**
       * Internal function, called by ValueAnimator and ObjectAnimator, to retrieve the value
       * most recently calculated in calculateValue().
       * @return
       */
      Object getAnimatedValue() {
          return mAnimatedValue;
      }
  
      /**
       * PropertyValuesHolder is Animators use to hold internal animation related data.
       * Therefore, in order to replicate the animation behavior, we need to get data out of
       * PropertyValuesHolder.
       * @hide
       */
      public void getPropertyValues(PropertyValues values) {
          init();
          values.propertyName = mPropertyName;
          values.type = mValueType;
          values.startValue = mKeyframes.getValue(0);
          if (values.startValue instanceof PathParser.PathData) {
              // PathData evaluator returns the same mutable PathData object when query fraction,
              // so we have to make a copy here.
              values.startValue = new PathParser.PathData((PathParser.PathData) values.startValue);
          }
          values.endValue = mKeyframes.getValue(1);
          if (values.endValue instanceof PathParser.PathData) {
              // PathData evaluator returns the same mutable PathData object when query fraction,
              // so we have to make a copy here.
              values.endValue = new PathParser.PathData((PathParser.PathData) values.endValue);
          }
          // TODO: We need a better way to get data out of keyframes.
          if (mKeyframes instanceof PathKeyframes.FloatKeyframesBase
                  || mKeyframes instanceof PathKeyframes.IntKeyframesBase
                  || (mKeyframes.getKeyframes() != null && mKeyframes.getKeyframes().size() > 2)) {
              // When a pvh has more than 2 keyframes, that means there are intermediate values in
              // addition to start/end values defined for animators. Another case where such
              // intermediate values are defined is when animator has a path to animate along. In
              // these cases, a data source is needed to capture these intermediate values.
              values.dataSource = new PropertyValues.DataSource() {
                  @Override
                  public Object getValueAtFraction(float fraction) {
                      return mKeyframes.getValue(fraction);
                  }
              };
          } else {
              values.dataSource = null;
          }
      }
  
      /**
       * @hide
       */
      public Class getValueType() {
          return mValueType;
      }
  
      @Override
      public String toString() {
          return mPropertyName + ": " + mKeyframes.toString();
      }
  
      /**
       * Utility method to derive a setter/getter method name from a property name, where the
       * prefix is typically "set" or "get" and the first letter of the property name is
       * capitalized.
       *
       * @param prefix The precursor to the method name, before the property name begins, typically
       * "set" or "get".
       * @param propertyName The name of the property that represents the bulk of the method name
       * after the prefix. The first letter of this word will be capitalized in the resulting
       * method name.
       * @return String the property name converted to a method name according to the conventions
       * specified above.
       */
      static String getMethodName(String prefix, String propertyName) {
          if (propertyName == null || propertyName.length() == 0) {
              // shouldn't get here
              return prefix;
          }
          char firstLetter = Character.toUpperCase(propertyName.charAt(0));
          String theRest = propertyName.substring(1);
          return prefix + firstLetter + theRest;
      }
  
      static class IntPropertyValuesHolder extends PropertyValuesHolder {
  
          // Cache JNI functions to avoid looking them up twice
          private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
                  new HashMap<Class, HashMap<String, Long>>();
          long mJniSetter;
          private IntProperty mIntProperty;
  
          Keyframes.IntKeyframes mIntKeyframes;
          int mIntAnimatedValue;
  
          public IntPropertyValuesHolder(String propertyName, Keyframes.IntKeyframes keyframes) {
              super(propertyName);
              mValueType = int.class;
              mKeyframes = keyframes;
              mIntKeyframes = keyframes;
          }
  
          public IntPropertyValuesHolder(Property property, Keyframes.IntKeyframes keyframes) {
              super(property);
              mValueType = int.class;
              mKeyframes = keyframes;
              mIntKeyframes = keyframes;
              if (property instanceof  IntProperty) {
                  mIntProperty = (IntProperty) mProperty;
              }
          }
  
          public IntPropertyValuesHolder(String propertyName, int... values) {
              super(propertyName);
              setIntValues(values);
          }
  
          public IntPropertyValuesHolder(Property property, int... values) {
              super(property);
              setIntValues(values);
              if (property instanceof  IntProperty) {
                  mIntProperty = (IntProperty) mProperty;
              }
          }
  
          @Override
          public void setProperty(Property property) {
              if (property instanceof IntProperty) {
                  mIntProperty = (IntProperty) property;
              } else {
                  super.setProperty(property);
              }
          }
  
          @Override
          public void setIntValues(int... values) {
              super.setIntValues(values);
              mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
          }
  
          @Override
          void calculateValue(float fraction) {
              mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);
          }
  
          @Override
          Object getAnimatedValue() {
              return mIntAnimatedValue;
          }
  
          @Override
          public IntPropertyValuesHolder clone() {
              IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone();
              newPVH.mIntKeyframes = (Keyframes.IntKeyframes) newPVH.mKeyframes;
              return newPVH;
          }
  
          /**
           * Internal function to set the value on the target object, using the setter set up
           * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
           * to handle turning the value calculated by ValueAnimator into a value set on the object
           * according to the name of the property.
           * @param target The target object on which the value is set
           */
          @Override
          void setAnimatedValue(Object target) {
              if (mIntProperty != null) {
                  mIntProperty.setValue(target, mIntAnimatedValue);
                  return;
              }
              if (mProperty != null) {
                  mProperty.set(target, mIntAnimatedValue);
                  return;
              }
              if (mJniSetter != 0) {
                  nCallIntMethod(target, mJniSetter, mIntAnimatedValue);
                  return;
              }
              if (mSetter != null) {
                  try {
                      mTmpValueArray[0] = mIntAnimatedValue;
                      mSetter.invoke(target, mTmpValueArray);
                  } catch (InvocationTargetException e) {
                      Log.e("PropertyValuesHolder", e.toString());
                  } catch (IllegalAccessException e) {
                      Log.e("PropertyValuesHolder", e.toString());
                  }
              }
          }
  
          @Override
          void setupSetter(Class targetClass) {
              if (mProperty != null) {
                  return;
              }
              // Check new static hashmap<propName, int> for setter method
              synchronized(sJNISetterPropertyMap) {
                  HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
                  boolean wasInMap = false;
                  if (propertyMap != null) {
                      wasInMap = propertyMap.containsKey(mPropertyName);
                      if (wasInMap) {
                          Long jniSetter = propertyMap.get(mPropertyName);
                          if (jniSetter != null) {
                              mJniSetter = jniSetter;
                          }
                      }
                  }
                  if (!wasInMap) {
                      String methodName = getMethodName("set", mPropertyName);
                      try {
                          mJniSetter = nGetIntMethod(targetClass, methodName);
                      } catch (NoSuchMethodError e) {
                          // Couldn't find it via JNI - try reflection next. Probably means the method
                          // doesn't exist, or the type is wrong. An error will be logged later if
                          // reflection fails as well.
                      }
                      if (propertyMap == null) {
                          propertyMap = new HashMap<String, Long>();
                          sJNISetterPropertyMap.put(targetClass, propertyMap);
                      }
                      propertyMap.put(mPropertyName, mJniSetter);
                  }
              }
              if (mJniSetter == 0) {
                  // Couldn't find method through fast JNI approach - just use reflection
                  super.setupSetter(targetClass);
              }
          }
      }
  
      static class FloatPropertyValuesHolder extends PropertyValuesHolder {
  
          // Cache JNI functions to avoid looking them up twice
          private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
                  new HashMap<Class, HashMap<String, Long>>();
          long mJniSetter;
          private FloatProperty mFloatProperty;
  
          Keyframes.FloatKeyframes mFloatKeyframes;
          float mFloatAnimatedValue;
  
          public FloatPropertyValuesHolder(String propertyName, Keyframes.FloatKeyframes keyframes) {
              super(propertyName);
              mValueType = float.class;
              mKeyframes = keyframes;
              mFloatKeyframes = keyframes;
          }
  
          public FloatPropertyValuesHolder(Property property, Keyframes.FloatKeyframes keyframes) {
              super(property);
              mValueType = float.class;
              mKeyframes = keyframes;
              mFloatKeyframes = keyframes;
              if (property instanceof FloatProperty) {
                  mFloatProperty = (FloatProperty) mProperty;
              }
          }
  
          public FloatPropertyValuesHolder(String propertyName, float... values) {
              super(propertyName);
              setFloatValues(values);
          }
  
          public FloatPropertyValuesHolder(Property property, float... values) {
              super(property);
              setFloatValues(values);
              if (property instanceof  FloatProperty) {
                  mFloatProperty = (FloatProperty) mProperty;
              }
          }
  
          @Override
          public void setProperty(Property property) {
              if (property instanceof FloatProperty) {
                  mFloatProperty = (FloatProperty) property;
              } else {
                  super.setProperty(property);
              }
          }
  
          @Override
          public void setFloatValues(float... values) {
              super.setFloatValues(values);
              mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
          }
  
          @Override
          void calculateValue(float fraction) {
              mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
          }
  
          @Override
          Object getAnimatedValue() {
              return mFloatAnimatedValue;
          }
  
          @Override
          public FloatPropertyValuesHolder clone() {
              FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone();
              newPVH.mFloatKeyframes = (Keyframes.FloatKeyframes) newPVH.mKeyframes;
              return newPVH;
          }
  
          /**
           * Internal function to set the value on the target object, using the setter set up
           * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
           * to handle turning the value calculated by ValueAnimator into a value set on the object
           * according to the name of the property.
           * @param target The target object on which the value is set
           */
          @Override
          void setAnimatedValue(Object target) {
              if (mFloatProperty != null) {
                  mFloatProperty.setValue(target, mFloatAnimatedValue);
                  return;
              }
              if (mProperty != null) {
                  mProperty.set(target, mFloatAnimatedValue);
                  return;
              }
              if (mJniSetter != 0) {
                  nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue);
                  return;
              }
              if (mSetter != null) {
                  try {
                      mTmpValueArray[0] = mFloatAnimatedValue;
                      mSetter.invoke(target, mTmpValueArray);
                  } catch (InvocationTargetException e) {
                      Log.e("PropertyValuesHolder", e.toString());
                  } catch (IllegalAccessException e) {
                      Log.e("PropertyValuesHolder", e.toString());
                  }
              }
          }
  
          @Override
          void setupSetter(Class targetClass) {
              if (mProperty != null) {
                  return;
              }
              // Check new static hashmap<propName, int> for setter method
              synchronized (sJNISetterPropertyMap) {
                  HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
                  boolean wasInMap = false;
                  if (propertyMap != null) {
                      wasInMap = propertyMap.containsKey(mPropertyName);
                      if (wasInMap) {
                          Long jniSetter = propertyMap.get(mPropertyName);
                          if (jniSetter != null) {
                              mJniSetter = jniSetter;
                          }
                      }
                  }
                  if (!wasInMap) {
                      String methodName = getMethodName("set", mPropertyName);
                      try {
                          mJniSetter = nGetFloatMethod(targetClass, methodName);
                      } catch (NoSuchMethodError e) {
                          // Couldn't find it via JNI - try reflection next. Probably means the method
                          // doesn't exist, or the type is wrong. An error will be logged later if
                          // reflection fails as well.
                      }
                      if (propertyMap == null) {
                          propertyMap = new HashMap<String, Long>();
                          sJNISetterPropertyMap.put(targetClass, propertyMap);
                      }
                      propertyMap.put(mPropertyName, mJniSetter);
                  }
              }
              if (mJniSetter == 0) {
                  // Couldn't find method through fast JNI approach - just use reflection
                  super.setupSetter(targetClass);
              }
          }
  
      }
  
      static class MultiFloatValuesHolder extends PropertyValuesHolder {
          private long mJniSetter;
          private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
                  new HashMap<Class, HashMap<String, Long>>();
  
          public MultiFloatValuesHolder(String propertyName, TypeConverter converter,
                  TypeEvaluator evaluator, Object... values) {
              super(propertyName);
              setConverter(converter);
              setObjectValues(values);
              setEvaluator(evaluator);
          }
  
          public MultiFloatValuesHolder(String propertyName, TypeConverter converter,
                  TypeEvaluator evaluator, Keyframes keyframes) {
              super(propertyName);
              setConverter(converter);
              mKeyframes = keyframes;
              setEvaluator(evaluator);
          }
  
          /**
           * Internal function to set the value on the target object, using the setter set up
           * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
           * to handle turning the value calculated by ValueAnimator into a value set on the object
           * according to the name of the property.
           *
           * @param target The target object on which the value is set
           */
          @Override
          void setAnimatedValue(Object target) {
              float[] values = (float[]) getAnimatedValue();
              int numParameters = values.length;
              if (mJniSetter != 0) {
                  switch (numParameters) {
                      case 1:
                          nCallFloatMethod(target, mJniSetter, values[0]);
                          break;
                      case 2:
                          nCallTwoFloatMethod(target, mJniSetter, values[0], values[1]);
                          break;
                      case 4:
                          nCallFourFloatMethod(target, mJniSetter, values[0], values[1],
                                  values[2], values[3]);
                          break;
                      default: {
                          nCallMultipleFloatMethod(target, mJniSetter, values);
                          break;
                      }
                  }
              }
          }
  
          /**
           * Internal function (called from ObjectAnimator) to set up the setter and getter
           * prior to running the animation. No getter can be used for multiple parameters.
           *
           * @param target The object on which the setter exists.
           */
          @Override
          void setupSetterAndGetter(Object target) {
              setupSetter(target.getClass());
          }
  
          @Override
          void setupSetter(Class targetClass) {
              if (mJniSetter != 0) {
                  return;
              }
              synchronized(sJNISetterPropertyMap) {
                  HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
                  boolean wasInMap = false;
                  if (propertyMap != null) {
                      wasInMap = propertyMap.containsKey(mPropertyName);
                      if (wasInMap) {
                          Long jniSetter = propertyMap.get(mPropertyName);
                          if (jniSetter != null) {
                              mJniSetter = jniSetter;
                          }
                      }
                  }
                  if (!wasInMap) {
                      String methodName = getMethodName("set", mPropertyName);
                      calculateValue(0f);
                      float[] values = (float[]) getAnimatedValue();
                      int numParams = values.length;
                      try {
                          mJniSetter = nGetMultipleFloatMethod(targetClass, methodName, numParams);
                      } catch (NoSuchMethodError e) {
                          // try without the 'set' prefix
                          try {
                              mJniSetter = nGetMultipleFloatMethod(targetClass, mPropertyName,
                                      numParams);
                          } catch (NoSuchMethodError e2) {
                              // just try reflection next
                          }
                      }
                      if (propertyMap == null) {
                          propertyMap = new HashMap<String, Long>();
                          sJNISetterPropertyMap.put(targetClass, propertyMap);
                      }
                      propertyMap.put(mPropertyName, mJniSetter);
                  }
              }
          }
      }
  
      static class MultiIntValuesHolder extends PropertyValuesHolder {
          private long mJniSetter;
          private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
                  new HashMap<Class, HashMap<String, Long>>();
  
          public MultiIntValuesHolder(String propertyName, TypeConverter converter,
                  TypeEvaluator evaluator, Object... values) {
              super(propertyName);
              setConverter(converter);
              setObjectValues(values);
              setEvaluator(evaluator);
          }
  
          public MultiIntValuesHolder(String propertyName, TypeConverter converter,
                  TypeEvaluator evaluator, Keyframes keyframes) {
              super(propertyName);
              setConverter(converter);
              mKeyframes = keyframes;
              setEvaluator(evaluator);
          }
  
          /**
           * Internal function to set the value on the target object, using the setter set up
           * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
           * to handle turning the value calculated by ValueAnimator into a value set on the object
           * according to the name of the property.
           *
           * @param target The target object on which the value is set
           */
          @Override
          void setAnimatedValue(Object target) {
              int[] values = (int[]) getAnimatedValue();
              int numParameters = values.length;
              if (mJniSetter != 0) {
                  switch (numParameters) {
                      case 1:
                          nCallIntMethod(target, mJniSetter, values[0]);
                          break;
                      case 2:
                          nCallTwoIntMethod(target, mJniSetter, values[0], values[1]);
                          break;
                      case 4:
                          nCallFourIntMethod(target, mJniSetter, values[0], values[1],
                                  values[2], values[3]);
                          break;
                      default: {
                          nCallMultipleIntMethod(target, mJniSetter, values);
                          break;
                      }
                  }
              }
          }
  
          /**
           * Internal function (called from ObjectAnimator) to set up the setter and getter
           * prior to running the animation. No getter can be used for multiple parameters.
           *
           * @param target The object on which the setter exists.
           */
          @Override
          void setupSetterAndGetter(Object target) {
              setupSetter(target.getClass());
          }
  
          @Override
          void setupSetter(Class targetClass) {
              if (mJniSetter != 0) {
                  return;
              }
              synchronized(sJNISetterPropertyMap) {
                  HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
                  boolean wasInMap = false;
                  if (propertyMap != null) {
                      wasInMap = propertyMap.containsKey(mPropertyName);
                      if (wasInMap) {
                          Long jniSetter = propertyMap.get(mPropertyName);
                          if (jniSetter != null) {
                              mJniSetter = jniSetter;
                          }
                      }
                  }
                  if (!wasInMap) {
                      String methodName = getMethodName("set", mPropertyName);
                      calculateValue(0f);
                      int[] values = (int[]) getAnimatedValue();
                      int numParams = values.length;
                      try {
                          mJniSetter = nGetMultipleIntMethod(targetClass, methodName, numParams);
                      } catch (NoSuchMethodError e) {
                          // try without the 'set' prefix
                          try {
                              mJniSetter = nGetMultipleIntMethod(targetClass, mPropertyName,
                                      numParams);
                          } catch (NoSuchMethodError e2) {
                              // couldn't find it.
                          }
                      }
                      if (propertyMap == null) {
                          propertyMap = new HashMap<String, Long>();
                          sJNISetterPropertyMap.put(targetClass, propertyMap);
                      }
                      propertyMap.put(mPropertyName, mJniSetter);
                  }
              }
          }
      }
  
      /**
       * Convert from PointF to float[] for multi-float setters along a Path.
       */
      private static class PointFToFloatArray extends TypeConverter<PointF, float[]> {
          private float[] mCoordinates = new float[2];
  
          public PointFToFloatArray() {
              super(PointF.class, float[].class);
          }
  
          @Override
          public float[] convert(PointF value) {
              mCoordinates[0] = value.x;
              mCoordinates[1] = value.y;
              return mCoordinates;
          }
      };
  
      /**
       * Convert from PointF to int[] for multi-int setters along a Path.
       */
      private static class PointFToIntArray extends TypeConverter<PointF, int[]> {
          private int[] mCoordinates = new int[2];
  
          public PointFToIntArray() {
              super(PointF.class, int[].class);
          }
  
          @Override
          public int[] convert(PointF value) {
              mCoordinates[0] = Math.round(value.x);
              mCoordinates[1] = Math.round(value.y);
              return mCoordinates;
          }
      };
  
      /**
       * @hide
       */
      public static class PropertyValues {
          public String propertyName;
          public Class type;
          public Object startValue;
          public Object endValue;
          public DataSource dataSource = null;
          public interface DataSource {
              Object getValueAtFraction(float fraction);
          }
          public String toString() {
              return ("property name: " + propertyName + ", type: " + type + ", startValue: "
                      + startValue.toString() + ", endValue: " + endValue.toString());
          }
      }
  
      native static private long nGetIntMethod(Class targetClass, String methodName);
      native static private long nGetFloatMethod(Class targetClass, String methodName);
      native static private long nGetMultipleIntMethod(Class targetClass, String methodName,
              int numParams);
      native static private long nGetMultipleFloatMethod(Class targetClass, String methodName,
              int numParams);
      native static private void nCallIntMethod(Object target, long methodID, int arg);
      native static private void nCallFloatMethod(Object target, long methodID, float arg);
      native static private void nCallTwoIntMethod(Object target, long methodID, int arg1, int arg2);
      native static private void nCallFourIntMethod(Object target, long methodID, int arg1, int arg2,
              int arg3, int arg4);
      native static private void nCallMultipleIntMethod(Object target, long methodID, int[] args);
      native static private void nCallTwoFloatMethod(Object target, long methodID, float arg1,
              float arg2);
      native static private void nCallFourFloatMethod(Object target, long methodID, float arg1,
              float arg2, float arg3, float arg4);
      native static private void nCallMultipleFloatMethod(Object target, long methodID, float[] args);
  }
  