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 com.hx.curtain.drawer.nineoldandroids.animation;
  
  import android.os.Handler;
  import android.os.Looper;
  import android.os.Message;
  import android.util.AndroidRuntimeException;
  import android.view.animation.AccelerateDecelerateInterpolator;
  import android.view.animation.AnimationUtils;
  import android.view.animation.Interpolator;
  import android.view.animation.LinearInterpolator;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  
  /**
   * This class provides a simple timing engine for running animations
   * which calculate animated values and set them on target objects.
   *
   * <p>There is a single timing pulse that all animations use. It runs in a
   * custom handler to ensure that property changes happen on the UI thread.</p>
   *
   * <p>By default, ValueAnimator uses non-linear time interpolation, via the
   * {@link AccelerateDecelerateInterpolator} class, which accelerates into and decelerates
   * out of an animation. This behavior can be changed by calling
   */
  public class ValueAnimator extends Animator {
  
      /**
       * Internal constants
       */
  
      /*
       * The default amount of time in ms between animation frames
       */
      private static final long DEFAULT_FRAME_DELAY = 10;
  
      /**
       * Messages sent to timing handler: START is sent when an animation first begins, FRAME is sent
       * by the handler to itself to process the next animation frame
       */
      static final int ANIMATION_START = 0;
      static final int ANIMATION_FRAME = 1;
  
      /**
       * Values used with internal variable mPlayingState to indicate the current state of an
       * animation.
       */
      static final int STOPPED    = 0; // Not yet playing
      static final int RUNNING    = 1; // Playing normally
      static final int SEEKED     = 2; // Seeked to some time value
  
      /**
       * Internal variables
       * NOTE: This object implements the clone() method, making a deep copy of any referenced
       * objects. As other non-trivial fields are added to this class, make sure to add logic
       * to clone() to make deep copies of them.
       */
  
      // The first time that the animation's animateFrame() method is called. This time is used to
      // determine elapsed time (and therefore the elapsed fraction) in subsequent calls
      // to animateFrame()
      long mStartTime;
  
      /**
       * Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked
       * to a value.
       */
      long mSeekTime = -1;
  
      // TODO: We access the following ThreadLocal variables often, some of them on every update.
      // If ThreadLocal access is significantly expensive, we may want to put all of these
      // fields into a structure sot hat we just access ThreadLocal once to get the reference
      // to that structure, then access the structure directly for each field.
  
      // The static sAnimationHandler processes the internal timing loop on which all animations
      // are based
      private static ThreadLocal<AnimationHandler> sAnimationHandler =
              new ThreadLocal<AnimationHandler>();
  
      // The per-thread list of all active animations
      private static final ThreadLocal<ArrayList<ValueAnimator>> sAnimations =
              new ThreadLocal<ArrayList<ValueAnimator>>() {
                  @Override
                  protected ArrayList<ValueAnimator> initialValue() {
                      return new ArrayList<ValueAnimator>();
                  }
              };
  
      // The per-thread set of animations to be started on the next animation frame
      private static final ThreadLocal<ArrayList<ValueAnimator>> sPendingAnimations =
              new ThreadLocal<ArrayList<ValueAnimator>>() {
                  @Override
                  protected ArrayList<ValueAnimator> initialValue() {
                      return new ArrayList<ValueAnimator>();
                  }
              };
  
      /**
       * Internal per-thread collections used to avoid set collisions as animations start and end
       * while being processed.
       */
      private static final ThreadLocal<ArrayList<ValueAnimator>> sDelayedAnims =
              new ThreadLocal<ArrayList<ValueAnimator>>() {
                  @Override
                  protected ArrayList<ValueAnimator> initialValue() {
                      return new ArrayList<ValueAnimator>();
                  }
              };
  
      private static final ThreadLocal<ArrayList<ValueAnimator>> sEndingAnims =
              new ThreadLocal<ArrayList<ValueAnimator>>() {
                  @Override
                  protected ArrayList<ValueAnimator> initialValue() {
                      return new ArrayList<ValueAnimator>();
                  }
              };
  
      private static final ThreadLocal<ArrayList<ValueAnimator>> sReadyAnims =
              new ThreadLocal<ArrayList<ValueAnimator>>() {
                  @Override
                  protected ArrayList<ValueAnimator> initialValue() {
                      return new ArrayList<ValueAnimator>();
                  }
              };
  
      // The time interpolator to be used if none is set on the animation
      private static final /*Time*/Interpolator sDefaultInterpolator =
              new AccelerateDecelerateInterpolator();
  
      // type evaluators for the primitive types handled by this implementation
      private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
      private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
  
      /**
       * Used to indicate whether the animation is currently playing in reverse. This causes the
       * elapsed fraction to be inverted to calculate the appropriate values.
       */
      private boolean mPlayingBackwards = false;
  
      /**
       * This variable tracks the current iteration that is playing. When mCurrentIteration exceeds the
       * repeatCount (if repeatCount!=INFINITE), the animation ends
       */
      private int mCurrentIteration = 0;
  
      /**
       * Tracks current elapsed/eased fraction, for querying in getAnimatedFraction().
       */
      private float mCurrentFraction = 0f;
  
      /**
       * Tracks whether a startDelay'd animation has begun playing through the startDelay.
       */
      private boolean mStartedDelay = false;
  
      /**
       * Tracks the time at which the animation began playing through its startDelay. This is
       * different from the mStartTime variable, which is used to track when the animation became
       * active (which is when the startDelay expired and the animation was added to the active
       * animations list).
       */
      private long mDelayStartTime;
  
      /**
       * Flag that represents the current state of the animation. Used to figure out when to start
       * an animation (if state == STOPPED). Also used to end an animation that
       * has been cancel()'d or end()'d since the last animation frame. Possible values are
       * STOPPED, RUNNING, SEEKED.
       */
      int mPlayingState = STOPPED;
  
      /**
       * Additional playing state to indicate whether an animator has been start()'d. There is
       * some lag between a call to start() and the first animation frame. We should still note
       * that the animation has been started, even if it's first animation frame has not yet
       * happened, and reflect that state in isRunning().
       * Note that delayed animations are different: they are not started until their first
       * animation frame, which occurs after their delay elapses.
       */
      private boolean mRunning = false;
  
      /**
       * Additional playing state to indicate whether an animator has been start()'d, whether or
       * not there is a nonzero startDelay.
       */
      private boolean mStarted = false;
  
      /**
       * Flag that denotes whether the animation is set up and ready to go. Used to
       * set up animation that has not yet been started.
       */
      boolean mInitialized = false;
  
      //
      // Backing variables
      //
  
      // How long the animation should last in ms
      private long mDuration = 300;
  
      // The amount of time in ms to delay starting the animation after start() is called
      private long mStartDelay = 0;
  
      // The number of milliseconds between animation frames
      private static long sFrameDelay = DEFAULT_FRAME_DELAY;
  
      // The number of times the animation will repeat. The default is 0, which means the animation
      // will play only once
      private int mRepeatCount = 0;
  
      /**
       * The type of repetition that will occur when repeatMode is nonzero. RESTART means the
       * animation will start from the beginning on every new cycle. REVERSE means the animation
       * will reverse directions on each iteration.
       */
      private int mRepeatMode = RESTART;
  
      /**
       * The time interpolator to be used. The elapsed fraction of the animation will be passed
       * through this interpolator to calculate the interpolated fraction, which is then used to
       * calculate the animated values.
       */
      private /*Time*/Interpolator mInterpolator = sDefaultInterpolator;
  
      /**
       * The set of listeners to be sent events through the life of an animation.
       */
      private ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
  
      /**
       * The property/value sets being animated.
       */
      PropertyValuesHolder[] mValues;
  
      /**
       * A hashmap of the PropertyValuesHolder objects. This map is used to lookup animated values
       * by property name during calls to getAnimatedValue(String).
       */
      HashMap<String, PropertyValuesHolder> mValuesMap;
  
      /**
       * Public constants
       */
  
      /**
       * When the animation reaches the end and <code>repeatCount</code> is INFINITE
       * or a positive value, the animation restarts from the beginning.
       */
      public static final int RESTART = 1;
      /**
       * When the animation reaches the end and <code>repeatCount</code> is INFINITE
       * or a positive value, the animation reverses direction on every iteration.
       */
      public static final int REVERSE = 2;
      /**
       * This value used used with the {@link #setRepeatCount(int)} property to repeat
       * the animation indefinitely.
       */
      public static final int INFINITE = -1;
  
      /**
       * Creates a new ValueAnimator object. This default constructor is primarily for
       * use internally; the factory methods which take parameters are more generally
       * useful.
       */
      public ValueAnimator() {
      }
  
      /**
       * Constructs and returns a ValueAnimator that animates between int values. A single
       * value implies that that value is the one being animated to. However, this is not typically
       * useful in a ValueAnimator object because there is no way for the object to determine the
       * starting value for the animation (unlike ObjectAnimator, which can derive that value
       * from the target object and property being animated). Therefore, there should typically
       * be two or more values.
       *
       * @param values A set of values that the animation will animate between over time.
       * @return A ValueAnimator object that is set up to animate between the given values.
       */
      public static ValueAnimator ofInt(int... values) {
          ValueAnimator anim = new ValueAnimator();
          anim.setIntValues(values);
          return anim;
      }
  
      /**
       * Constructs and returns a ValueAnimator that animates between float values. A single
       * value implies that that value is the one being animated to. However, this is not typically
       * useful in a ValueAnimator object because there is no way for the object to determine the
       * starting value for the animation (unlike ObjectAnimator, which can derive that value
       * from the target object and property being animated). Therefore, there should typically
       * be two or more values.
       *
       * @param values A set of values that the animation will animate between over time.
       * @return A ValueAnimator object that is set up to animate between the given values.
       */
      public static ValueAnimator ofFloat(float... values) {
          ValueAnimator anim = new ValueAnimator();
          anim.setFloatValues(values);
          return anim;
      }
  
      /**
       * Constructs and returns a ValueAnimator that animates between the values
       * specified in the PropertyValuesHolder objects.
       *
       * @param values A set of PropertyValuesHolder objects whose values will be animated
       * between over time.
       * @return A ValueAnimator object that is set up to animate between the given values.
       */
      public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) {
          ValueAnimator anim = new ValueAnimator();
          anim.setValues(values);
          return anim;
      }
      /**
       * Constructs and returns a ValueAnimator that animates between Object values. A single
       * value implies that that value is the one being animated to. However, this is not typically
       * useful in a ValueAnimator object because there is no way for the object to determine the
       * starting value for the animation (unlike ObjectAnimator, which can derive that value
       * from the target object and property being animated). Therefore, there should typically
       * be two or more values.
       *
       * <p>Since ValueAnimator does not know how to animate between arbitrary Objects, this
       * factory method also takes a TypeEvaluator object that the ValueAnimator will use
       * to perform that interpolation.
       *
       * @param evaluator A TypeEvaluator that will be called on each animation frame to
       * provide the ncessry interpolation between the Object values to derive the animated
       * value.
       * @param values A set of values that the animation will animate between over time.
       * @return A ValueAnimator object that is set up to animate between the given values.
       */
      public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
          ValueAnimator anim = new ValueAnimator();
          anim.setObjectValues(values);
          anim.setEvaluator(evaluator);
          return anim;
      }
  
      /**
       * Sets int values that will be animated between. A single
       * value implies that that value is the one being animated to. However, this is not typically
       * useful in a ValueAnimator object because there is no way for the object to determine the
       * starting value for the animation (unlike ObjectAnimator, which can derive that value
       * from the target object and property being animated). Therefore, there should typically
       * be two or more values.
       *
       * <p>If there are already multiple sets of values defined for this ValueAnimator via more
       * than one PropertyValuesHolder object, this method will set the values for the first
       * of those objects.</p>
       *
       * @param values A set of values that the animation will animate between over time.
       */
      public void setIntValues(int... values) {
          if (values == null || values.length == 0) {
              return;
          }
          if (mValues == null || mValues.length == 0) {
              setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofInt("", values)});
          } else {
              PropertyValuesHolder valuesHolder = mValues[0];
              valuesHolder.setIntValues(values);
          }
          // New property/values/target should cause re-initialization prior to starting
          mInitialized = false;
      }
  
      /**
       * Sets float values that will be animated between. A single
       * value implies that that value is the one being animated to. However, this is not typically
       * useful in a ValueAnimator object because there is no way for the object to determine the
       * starting value for the animation (unlike ObjectAnimator, which can derive that value
       * from the target object and property being animated). Therefore, there should typically
       * be two or more values.
       *
       * <p>If there are already multiple sets of values defined for this ValueAnimator via more
       * than one PropertyValuesHolder object, this method will set the values for the first
       * of those objects.</p>
       *
       * @param values A set of values that the animation will animate between over time.
       */
      public void setFloatValues(float... values) {
          if (values == null || values.length == 0) {
              return;
          }
          if (mValues == null || mValues.length == 0) {
              setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofFloat("", values)});
          } else {
              PropertyValuesHolder valuesHolder = mValues[0];
              valuesHolder.setFloatValues(values);
          }
          // New property/values/target should cause re-initialization prior to starting
          mInitialized = false;
      }
  
      /**
       * Sets the values to animate between for this animation. A single
       * value implies that that value is the one being animated to. However, this is not typically
       * useful in a ValueAnimator object because there is no way for the object to determine the
       * starting value for the animation (unlike ObjectAnimator, which can derive that value
       * from the target object and property being animated). Therefore, there should typically
       * be two or more values.
       *
       * <p>If there are already multiple sets of values defined for this ValueAnimator via more
       * than one PropertyValuesHolder object, this method will set the values for the first
       * of those objects.</p>
       *
       * <p>There should be a TypeEvaluator set on the ValueAnimator that knows how to interpolate
       * between these value objects. ValueAnimator only knows how to interpolate between the
       * primitive types specified in the other setValues() methods.</p>
       *
       * @param values The set of values to animate between.
       */
      public void setObjectValues(Object... values) {
          if (values == null || values.length == 0) {
              return;
          }
          if (mValues == null || mValues.length == 0) {
              setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofObject("",
                      (TypeEvaluator)null, values)});
          } else {
              PropertyValuesHolder valuesHolder = mValues[0];
              valuesHolder.setObjectValues(values);
          }
          // New property/values/target should cause re-initialization prior to starting
          mInitialized = false;
      }
  
      /**
       * Sets the values, per property, being animated between. This function is called internally
       * by the constructors of ValueAnimator that take a list of values. But an ValueAnimator can
       * be constructed without values and this method can be called to set the values manually
       * instead.
       *
       * @param values The set of values, per property, being animated between.
       */
      public void setValues(PropertyValuesHolder... values) {
          int numValues = values.length;
          mValues = values;
          mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
          for (int i = 0; i < numValues; ++i) {
              PropertyValuesHolder valuesHolder = (PropertyValuesHolder) values[i];
              mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
          }
          // New property/values/target should cause re-initialization prior to starting
          mInitialized = false;
      }
  
      /**
       * Returns the values that this ValueAnimator animates between. These values are stored in
       * PropertyValuesHolder objects, even if the ValueAnimator was created with a simple list
       * of value objects instead.
       *
       * @return PropertyValuesHolder[] An array of PropertyValuesHolder objects which hold the
       * values, per property, that define the animation.
       */
      public PropertyValuesHolder[] getValues() {
          return mValues;
      }
  
      /**
       * 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.
       * It takes care of the final initialization steps for the
       * animation.
       *
       *  <p>Overrides of this method should call the superclass method to ensure
       *  that internal mechanisms for the animation are set up correctly.</p>
       */
      void initAnimation() {
          if (!mInitialized) {
              int numValues = mValues.length;
              for (int i = 0; i < numValues; ++i) {
                  mValues[i].init();
              }
              mInitialized = true;
          }
      }
  
  
      /**
       * Sets the length of the animation. The default duration is 300 milliseconds.
       *
       * @param duration The length of the animation, in milliseconds. This value cannot
       * be negative.
       * @return ValueAnimator The object called with setDuration(). This return
       * value makes it easier to compose statements together that construct and then set the
       * duration, as in <code>ValueAnimator.ofInt(0, 10).setDuration(500).start()</code>.
       */
      public ValueAnimator setDuration(long duration) {
          if (duration < 0) {
              throw new IllegalArgumentException("Animators cannot have negative duration: " +
                      duration);
          }
          mDuration = duration;
          return this;
      }
  
      /**
       * Gets the length of the animation. The default duration is 300 milliseconds.
       *
       * @return The length of the animation, in milliseconds.
       */
      public long getDuration() {
          return mDuration;
      }
  
      /**
       * Sets the position of the animation to the specified point in time. This time should
       * be between 0 and the total duration of the animation, including any repetition. If
       * the animation has not yet been started, then it will not advance forward after it is
       * set to this time; it will simply set the time to this value and perform any appropriate
       * actions based on that time. If the animation is already running, then setCurrentPlayTime()
       * will set the current playing time to this value and continue playing from that point.
       *
       * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
       */
      public void setCurrentPlayTime(long playTime) {
          initAnimation();
          long currentTime = AnimationUtils.currentAnimationTimeMillis();
          if (mPlayingState != RUNNING) {
              mSeekTime = playTime;
              mPlayingState = SEEKED;
          }
          mStartTime = currentTime - playTime;
          animationFrame(currentTime);
      }
  
      /**
       * Gets the current position of the animation in time, which is equal to the current
       * time minus the time that the animation started. An animation that is not yet started will
       * return a value of zero.
       *
       * @return The current position in time of the animation.
       */
      public long getCurrentPlayTime() {
          if (!mInitialized || mPlayingState == STOPPED) {
              return 0;
          }
          return AnimationUtils.currentAnimationTimeMillis() - mStartTime;
      }
  
      /**
       * This custom, static handler handles the timing pulse that is shared by
       * all active animations. This approach ensures that the setting of animation
       * values will happen on the UI thread and that all animations will share
       * the same times for calculating their values, which makes synchronizing
       * animations possible.
       *
       */
      private static class AnimationHandler extends Handler {
          /**
           * There are only two messages that we care about: ANIMATION_START and
           * ANIMATION_FRAME. The START message is sent when an animation's start()
           * method is called. It cannot start synchronously when start() is called
           * because the call may be on the wrong thread, and it would also not be
           * synchronized with other animations because it would not start on a common
           * timing pulse. So each animation sends a START message to the handler, which
           * causes the handler to place the animation on the active animations queue and
           * start processing frames for that animation.
           * The FRAME message is the one that is sent over and over while there are any
           * active animations to process.
           */
          @Override
          public void handleMessage(Message msg) {
              boolean callAgain = true;
              ArrayList<ValueAnimator> animations = sAnimations.get();
              ArrayList<ValueAnimator> delayedAnims = sDelayedAnims.get();
              switch (msg.what) {
                  // TODO: should we avoid sending frame message when starting if we
                  // were already running?
                  case ANIMATION_START:
                      ArrayList<ValueAnimator> pendingAnimations = sPendingAnimations.get();
                      if (animations.size() > 0 || delayedAnims.size() > 0) {
                          callAgain = false;
                      }
                      // pendingAnims holds any animations that have requested to be started
                      // We're going to clear sPendingAnimations, but starting animation may
                      // cause more to be added to the pending list (for example, if one animation
                      // starting triggers another starting). So we loop until sPendingAnimations
                      // is empty.
                      while (pendingAnimations.size() > 0) {
                          ArrayList<ValueAnimator> pendingCopy =
                                  (ArrayList<ValueAnimator>) pendingAnimations.clone();
                          pendingAnimations.clear();
                          int count = pendingCopy.size();
                          for (int i = 0; i < count; ++i) {
                              ValueAnimator anim = pendingCopy.get(i);
                              // If the animation has a startDelay, place it on the delayed list
                              if (anim.mStartDelay == 0) {
                                  anim.startAnimation();
                              } else {
                                  delayedAnims.add(anim);
                              }
                          }
                      }
                      // fall through to process first frame of new animations
                  case ANIMATION_FRAME:
                      // currentTime holds the common time for all animations processed
                      // during this frame
                      long currentTime = AnimationUtils.currentAnimationTimeMillis();
                      ArrayList<ValueAnimator> readyAnims = sReadyAnims.get();
                      ArrayList<ValueAnimator> endingAnims = sEndingAnims.get();
  
                      // First, process animations currently sitting on the delayed queue, adding
                      // them to the active animations if they are ready
                      int numDelayedAnims = delayedAnims.size();
                      for (int i = 0; i < numDelayedAnims; ++i) {
                          ValueAnimator anim = delayedAnims.get(i);
                          if (anim.delayedAnimationFrame(currentTime)) {
                              readyAnims.add(anim);
                          }
                      }
                      int numReadyAnims = readyAnims.size();
                      if (numReadyAnims > 0) {
                          for (int i = 0; i < numReadyAnims; ++i) {
                              ValueAnimator anim = readyAnims.get(i);
                              anim.startAnimation();
                              anim.mRunning = true;
                              delayedAnims.remove(anim);
                          }
                          readyAnims.clear();
                      }
  
                      // Now process all active animations. The return value from animationFrame()
                      // tells the handler whether it should now be ended
                      int numAnims = animations.size();
                      int i = 0;
                      while (i < numAnims) {
                          ValueAnimator anim = animations.get(i);
                          if (anim.animationFrame(currentTime)) {
                              endingAnims.add(anim);
                          }
                          if (animations.size() == numAnims) {
                              ++i;
                          } else {
                              // An animation might be canceled or ended by client code
                              // during the animation frame. Check to see if this happened by
                              // seeing whether the current index is the same as it was before
                              // calling animationFrame(). Another approach would be to copy
                              // animations to a temporary list and process that list instead,
                              // but that entails garbage and processing overhead that would
                              // be nice to avoid.
                              --numAnims;
                              endingAnims.remove(anim);
                          }
                      }
                      if (endingAnims.size() > 0) {
                          for (i = 0; i < endingAnims.size(); ++i) {
                              endingAnims.get(i).endAnimation();
                          }
                          endingAnims.clear();
                      }
  
                      // If there are still active or delayed animations, call the handler again
                      // after the frameDelay
                      if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) {
                          sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay -
                              (AnimationUtils.currentAnimationTimeMillis() - currentTime)));
                      }
                      break;
              }
          }
      }
  
      /**
       * The amount of time, in milliseconds, to delay starting the animation after
       * {@link #start()} is called.
       *
       * @return the number of milliseconds to delay running the animation
       */
      public long getStartDelay() {
          return mStartDelay;
      }
  
      /**
       * The amount of time, in milliseconds, to delay starting the animation after
       * {@link #start()} is called.
  
       * @param startDelay The amount of the delay, in milliseconds
       */
      public void setStartDelay(long startDelay) {
          this.mStartDelay = startDelay;
      }
  
      /**
       * The amount of time, in milliseconds, between each frame of the animation. This is a
       * requested time that the animation will attempt to honor, but the actual delay between
       * frames may be different, depending on system load and capabilities. This is a static
       * function because the same delay will be applied to all animations, since they are all
       * run off of a single timing loop.
       *
       * @return the requested time between frames, in milliseconds
       */
      public static long getFrameDelay() {
          return sFrameDelay;
      }
  
      /**
       * The amount of time, in milliseconds, between each frame of the animation. This is a
       * requested time that the animation will attempt to honor, but the actual delay between
       * frames may be different, depending on system load and capabilities. This is a static
       * function because the same delay will be applied to all animations, since they are all
       * run off of a single timing loop.
       *
       * @param frameDelay the requested time between frames, in milliseconds
       */
      public static void setFrameDelay(long frameDelay) {
          sFrameDelay = frameDelay;
      }
  
      /**
       * The most recent value calculated by this <code>ValueAnimator</code> when there is just one
       * property being animated. This value is only sensible while the animation is running. The main
       * purpose for this read-only property is to retrieve the value from the <code>ValueAnimator</code>
       * during a call to {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which
       * is called during each animation frame, immediately after the value is calculated.
       *
       * @return animatedValue The value most recently calculated by this <code>ValueAnimator</code> for
       * the single property being animated. If there are several properties being animated
       * (specified by several PropertyValuesHolder objects in the constructor), this function
       * returns the animated value for the first of those objects.
       */
      public Object getAnimatedValue() {
          if (mValues != null && mValues.length > 0) {
              return mValues[0].getAnimatedValue();
          }
          // Shouldn't get here; should always have values unless ValueAnimator was set up wrong
          return null;
      }
  
      /**
       * The most recent value calculated by this <code>ValueAnimator</code> for <code>propertyName</code>.
       * The main purpose for this read-only property is to retrieve the value from the
       * <code>ValueAnimator</code> during a call to
       * {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which
       * is called during each animation frame, immediately after the value is calculated.
       *
       * @return animatedValue The value most recently calculated for the named property
       * by this <code>ValueAnimator</code>.
       */
      public Object getAnimatedValue(String propertyName) {
          PropertyValuesHolder valuesHolder = mValuesMap.get(propertyName);
          if (valuesHolder != null) {
              return valuesHolder.getAnimatedValue();
          } else {
              // At least avoid crashing if called with bogus propertyName
              return null;
          }
      }
  
      /**
       * Sets how many times the animation should be repeated. If the repeat
       * count is 0, the animation is never repeated. If the repeat count is
       * greater than 0 or {@link #INFINITE}, the repeat mode will be taken
       * into account. The repeat count is 0 by default.
       *
       * @param value the number of times the animation should be repeated
       */
      public void setRepeatCount(int value) {
          mRepeatCount = value;
      }
      /**
       * Defines how many times the animation should repeat. The default value
       * is 0.
       *
       * @return the number of times the animation should repeat, or {@link #INFINITE}
       */
      public int getRepeatCount() {
          return mRepeatCount;
      }
  
      /**
       * Defines what this animation should do when it reaches the end. This
       * setting is applied only when the repeat count is either greater than
       * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}.
       *
       * @param value {@link #RESTART} or {@link #REVERSE}
       */
      public void setRepeatMode(int value) {
          mRepeatMode = value;
      }
  
      /**
       * Defines what this animation should do when it reaches the end.
       *
       * @return either one of {@link #REVERSE} or {@link #RESTART}
       */
      public int getRepeatMode() {
          return mRepeatMode;
      }
  
      /**
       * Adds a listener to the set of listeners that are sent update events through the life of
       * an animation. This method is called on all listeners for every frame of the animation,
       * after the values for the animation have been calculated.
       *
       * @param listener the listener to be added to the current set of listeners for this animation.
       */
      public void addUpdateListener(AnimatorUpdateListener listener) {
          if (mUpdateListeners == null) {
              mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
          }
          mUpdateListeners.add(listener);
      }
  
      /**
       * Removes all listeners from the set listening to frame updates for this animation.
       */
      public void removeAllUpdateListeners() {
          if (mUpdateListeners == null) {
              return;
          }
          mUpdateListeners.clear();
          mUpdateListeners = null;
      }
  
      /**
       * Removes a listener from the set listening to frame updates for this animation.
       *
       * @param listener the listener to be removed from the current set of update listeners
       * for this animation.
       */
      public void removeUpdateListener(AnimatorUpdateListener listener) {
          if (mUpdateListeners == null) {
              return;
          }
          mUpdateListeners.remove(listener);
          if (mUpdateListeners.size() == 0) {
              mUpdateListeners = null;
          }
      }
  
  
      /**
       * The time interpolator used in calculating the elapsed fraction of this animation. The
       * interpolator determines whether the animation runs with linear or non-linear motion,
       * such as acceleration and deceleration. The default value is
       * {@link AccelerateDecelerateInterpolator}
       *
       * @param value the interpolator to be used by this animation. A value of <code>null</code>
       * will result in linear interpolation.
       */
      @Override
      public void setInterpolator(/*Time*/Interpolator value) {
          if (value != null) {
              mInterpolator = value;
          } else {
              mInterpolator = new LinearInterpolator();
          }
      }
  
      /**
       * Returns the timing interpolator that this ValueAnimator uses.
       *
       * @return The timing interpolator for this ValueAnimator.
       */
      public /*Time*/Interpolator getInterpolator() {
          return mInterpolator;
      }
  
      /**
       * The type evaluator to be used when calculating the animated values of this animation.
       * The system will automatically assign a float or int evaluator based on the type
       * of <code>startValue</code> and <code>endValue</code> in the constructor. But if these values
       * are not one of these primitive types, or if different evaluation is desired (such as is
       * necessary with int values that represent colors), a custom evaluator needs to be assigned.
       * For example, when running an animation on color values, the {@link ArgbEvaluator}
       * should be used to get correct RGB color interpolation.
       *
       * <p>If this ValueAnimator has only one set of values being animated between, this evaluator
       * will be used for that set. If there are several sets of values being animated, which is
       * the case if PropertyValuesHOlder objects were set on the ValueAnimator, then the evaluator
       * is assigned just to the first PropertyValuesHolder object.</p>
       *
       * @param value the evaluator to be used this animation
       */
      public void setEvaluator(TypeEvaluator value) {
          if (value != null && mValues != null && mValues.length > 0) {
              mValues[0].setEvaluator(value);
          }
      }
  
      /**
       * Start the animation playing. This version of start() takes a boolean flag that indicates
       * whether the animation should play in reverse. The flag is usually false, but may be set
       * to true if called from the reverse() method.
       *
       * <p>The animation started by calling this method will be run on the thread that called
       * this method. This thread should have a Looper on it (a runtime exception will be thrown if
       * this is not the case). Also, if the animation will animate
       * properties of objects in the view hierarchy, then the calling thread should be the UI
       * thread for that view hierarchy.</p>
       *
       * @param playBackwards Whether the ValueAnimator should start playing in reverse.
       */
      private void start(boolean playBackwards) {
          if (Looper.myLooper() == null) {
              throw new AndroidRuntimeException("Animators may only be run on Looper threads");
          }
          mPlayingBackwards = playBackwards;
          mCurrentIteration = 0;
          mPlayingState = STOPPED;
          mStarted = true;
          mStartedDelay = false;
          sPendingAnimations.get().add(this);
          if (mStartDelay == 0) {
              // This sets the initial value of the animation, prior to actually starting it running
              setCurrentPlayTime(getCurrentPlayTime());
              mPlayingState = STOPPED;
              mRunning = true;
  
              if (mListeners != null) {
                  ArrayList<AnimatorListener> tmpListeners =
                          (ArrayList<AnimatorListener>) mListeners.clone();
                  int numListeners = tmpListeners.size();
                  for (int i = 0; i < numListeners; ++i) {
                      tmpListeners.get(i).onAnimationStart(this);
                  }
              }
          }
          AnimationHandler animationHandler = sAnimationHandler.get();
          if (animationHandler == null) {
              animationHandler = new AnimationHandler();
              sAnimationHandler.set(animationHandler);
          }
          animationHandler.sendEmptyMessage(ANIMATION_START);
      }
  
      @Override
      public void start() {
          start(false);
      }
  
      @Override
      public void cancel() {
          // Only cancel if the animation is actually running or has been started and is about
          // to run
          if (mPlayingState != STOPPED || sPendingAnimations.get().contains(this) ||
                  sDelayedAnims.get().contains(this)) {
              // Only notify listeners if the animator has actually started
              if (mRunning && mListeners != null) {
                  ArrayList<AnimatorListener> tmpListeners =
                          (ArrayList<AnimatorListener>) mListeners.clone();
                  for (AnimatorListener listener : tmpListeners) {
                      listener.onAnimationCancel(this);
                  }
              }
              endAnimation();
          }
      }
  
      @Override
      public void end() {
          if (!sAnimations.get().contains(this) && !sPendingAnimations.get().contains(this)) {
              // Special case if the animation has not yet started; get it ready for ending
              mStartedDelay = false;
              startAnimation();
          } else if (!mInitialized) {
              initAnimation();
          }
          // The final value set on the target varies, depending on whether the animation
          // was supposed to repeat an odd number of times
          if (mRepeatCount > 0 && (mRepeatCount & 0x01) == 1) {
              animateValue(0f);
          } else {
              animateValue(1f);
          }
          endAnimation();
      }
  
      @Override
      public boolean isRunning() {
          return (mPlayingState == RUNNING || mRunning);
      }
  
      @Override
      public boolean isStarted() {
          return mStarted;
      }
  
      /**
       * Plays the ValueAnimator in reverse. If the animation is already running,
       * it will stop itself and play backwards from the point reached when reverse was called.
       * If the animation is not currently running, then it will start from the end and
       * play backwards. This behavior is only set for the current animation; future playing
       * of the animation will use the default behavior of playing forward.
       */
      public void reverse() {
          mPlayingBackwards = !mPlayingBackwards;
          if (mPlayingState == RUNNING) {
              long currentTime = AnimationUtils.currentAnimationTimeMillis();
              long currentPlayTime = currentTime - mStartTime;
              long timeLeft = mDuration - currentPlayTime;
              mStartTime = currentTime - timeLeft;
          } else {
              start(true);
          }
      }
  
      /**
       * Called internally to end an animation by removing it from the animations list. Must be
       * called on the UI thread.
       */
      private void endAnimation() {
          sAnimations.get().remove(this);
          sPendingAnimations.get().remove(this);
          sDelayedAnims.get().remove(this);
          mPlayingState = STOPPED;
          if (mRunning && mListeners != null) {
              ArrayList<AnimatorListener> tmpListeners =
                      (ArrayList<AnimatorListener>) mListeners.clone();
              int numListeners = tmpListeners.size();
              for (int i = 0; i < numListeners; ++i) {
                  tmpListeners.get(i).onAnimationEnd(this);
              }
          }
          mRunning = false;
          mStarted = false;
      }
  
      /**
       * Called internally to start an animation by adding it to the active animations list. Must be
       * called on the UI thread.
       */
      private void startAnimation() {
          initAnimation();
          sAnimations.get().add(this);
          if (mStartDelay > 0 && mListeners != null) {
              // Listeners were already notified in start() if startDelay is 0; this is
              // just for delayed animations
              ArrayList<AnimatorListener> tmpListeners =
                      (ArrayList<AnimatorListener>) mListeners.clone();
              int numListeners = tmpListeners.size();
              for (int i = 0; i < numListeners; ++i) {
                  tmpListeners.get(i).onAnimationStart(this);
              }
          }
      }
  
      /**
       * Internal function called to process an animation frame on an animation that is currently
       * sleeping through its <code>startDelay</code> phase. The return value indicates whether it
       * should be woken up and put on the active animations queue.
       *
       * @param currentTime The current animation time, used to calculate whether the animation
       * has exceeded its <code>startDelay</code> and should be started.
       * @return True if the animation's <code>startDelay</code> has been exceeded and the animation
       * should be added to the set of active animations.
       */
      private boolean delayedAnimationFrame(long currentTime) {
          if (!mStartedDelay) {
              mStartedDelay = true;
              mDelayStartTime = currentTime;
          } else {
              long deltaTime = currentTime - mDelayStartTime;
              if (deltaTime > mStartDelay) {
                  // startDelay ended - start the anim and record the
                  // mStartTime appropriately
                  mStartTime = currentTime - (deltaTime - mStartDelay);
                  mPlayingState = RUNNING;
                  return true;
              }
          }
          return false;
      }
  
      /**
       * This internal function processes a single animation frame for a given animation. The
       * currentTime parameter is the timing pulse sent by the handler, used to calculate the
       * elapsed duration, and therefore
       * the elapsed fraction, of the animation. The return value indicates whether the animation
       * should be ended (which happens when the elapsed time of the animation exceeds the
       * animation's duration, including the repeatCount).
       *
       * @param currentTime The current time, as tracked by the static timing handler
       * @return true if the animation's duration, including any repetitions due to
       * <code>repeatCount</code> has been exceeded and the animation should be ended.
       */
      boolean animationFrame(long currentTime) {
          boolean done = false;
  
          if (mPlayingState == STOPPED) {
              mPlayingState = RUNNING;
              if (mSeekTime < 0) {
                  mStartTime = currentTime;
              } else {
                  mStartTime = currentTime - mSeekTime;
                  // Now that we're playing, reset the seek time
                  mSeekTime = -1;
              }
          }
          switch (mPlayingState) {
          case RUNNING:
          case SEEKED:
              float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
              if (fraction >= 1f) {
                  if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
                      // Time to repeat
                      if (mListeners != null) {
                          int numListeners = mListeners.size();
                          for (int i = 0; i < numListeners; ++i) {
                              mListeners.get(i).onAnimationRepeat(this);
                          }
                      }
                      if (mRepeatMode == REVERSE) {
                          mPlayingBackwards = mPlayingBackwards ? false : true;
                      }
                      mCurrentIteration += (int)fraction;
                      fraction = fraction % 1f;
                      mStartTime += mDuration;
                  } else {
                      done = true;
                      fraction = Math.min(fraction, 1.0f);
                  }
              }
              if (mPlayingBackwards) {
                  fraction = 1f - fraction;
              }
              animateValue(fraction);
              break;
          }
  
          return done;
      }
  
      /**
       * Returns the current animation fraction, which is the elapsed/interpolated fraction used in
       * the most recent frame update on the animation.
       *
       * @return Elapsed/interpolated fraction of the animation.
       */
      public float getAnimatedFraction() {
          return mCurrentFraction;
      }
  
      /**
       * This method is called with the elapsed fraction of the animation during every
       * animation frame. This function turns the elapsed fraction into an interpolated fraction
       * and then into an animated value (from the evaluator. The function is called mostly during
       * animation updates, but it is also called when the <code>end()</code>
       * function is called, to set the final value on the property.
       *
       * <p>Overrides of this method must call the superclass to perform the calculation
       * of the animated value.</p>
       *
       * @param fraction The elapsed fraction of the animation.
       */
      void animateValue(float fraction) {
          fraction = mInterpolator.getInterpolation(fraction);
          mCurrentFraction = fraction;
          int numValues = mValues.length;
          for (int i = 0; i < numValues; ++i) {
              mValues[i].calculateValue(fraction);
          }
          if (mUpdateListeners != null) {
              int numListeners = mUpdateListeners.size();
              for (int i = 0; i < numListeners; ++i) {
                  mUpdateListeners.get(i).onAnimationUpdate(this);
              }
          }
      }
  
      @Override
      public ValueAnimator clone() {
          final ValueAnimator anim = (ValueAnimator) super.clone();
          if (mUpdateListeners != null) {
              ArrayList<AnimatorUpdateListener> oldListeners = mUpdateListeners;
              anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
              int numListeners = oldListeners.size();
              for (int i = 0; i < numListeners; ++i) {
                  anim.mUpdateListeners.add(oldListeners.get(i));
              }
          }
          anim.mSeekTime = -1;
          anim.mPlayingBackwards = false;
          anim.mCurrentIteration = 0;
          anim.mInitialized = false;
          anim.mPlayingState = STOPPED;
          anim.mStartedDelay = false;
          PropertyValuesHolder[] oldValues = mValues;
          if (oldValues != null) {
              int numValues = oldValues.length;
              anim.mValues = new PropertyValuesHolder[numValues];
              anim.mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
              for (int i = 0; i < numValues; ++i) {
                  PropertyValuesHolder newValuesHolder = oldValues[i].clone();
                  anim.mValues[i] = newValuesHolder;
                  anim.mValuesMap.put(newValuesHolder.getPropertyName(), newValuesHolder);
              }
          }
          return anim;
      }
  
      /**
       * Implementors of this interface can add themselves as update listeners
       * to an <code>ValueAnimator</code> instance to receive callbacks on every animation
       * frame, after the current frame's values have been calculated for that
       * <code>ValueAnimator</code>.
       */
      public static interface AnimatorUpdateListener {
          /**
           * <p>Notifies the occurrence of another frame of the animation.</p>
           *
           * @param animation The animation which was repeated.
           */
          void onAnimationUpdate(ValueAnimator animation);
  
      }
  
      /**
       * Return the number of animations currently running.
       *
       * Used by StrictMode internally to annotate violations.  Only
       * called on the main thread.
       *
       * @hide
       */
      public static int getCurrentAnimationsCount() {
          return sAnimations.get().size();
      }
  
      /**
       * Clear all animations on this thread, without canceling or ending them.
       * This should be used with caution.
       *
       * @hide
       */
      public static void clearAllAnimations() {
          sAnimations.get().clear();
          sPendingAnimations.get().clear();
          sDelayedAnims.get().clear();
      }
  
      @Override
      public String toString() {
          String returnVal = "ValueAnimator@" + Integer.toHexString(hashCode());
          if (mValues != null) {
              for (int i = 0; i < mValues.length; ++i) {
                  returnVal += "\n    " + mValues[i].toString();
              }
          }
          return returnVal;
      }
  }
  