content: /*
   * Copyright (C) 2014 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.view;
  
  import android.animation.Animator;
  import android.animation.TimeInterpolator;
  import android.animation.ValueAnimator;
  import android.annotation.UnsupportedAppUsage;
  import android.graphics.CanvasProperty;
  import android.graphics.Paint;
  import android.graphics.RecordingCanvas;
  import android.graphics.RenderNode;
  import android.os.Handler;
  import android.os.Looper;
  import android.util.SparseIntArray;
  
  import com.android.internal.util.VirtualRefBasePtr;
  import com.android.internal.view.animation.FallbackLUTInterpolator;
  import com.android.internal.view.animation.HasNativeInterpolator;
  import com.android.internal.view.animation.NativeInterpolatorFactory;
  
  import java.util.ArrayList;
  
  /**
   * @hide
   */
  public class RenderNodeAnimator extends Animator {
      // Keep in sync with enum RenderProperty in Animator.h
      public static final int TRANSLATION_X = 0;
      public static final int TRANSLATION_Y = 1;
      public static final int TRANSLATION_Z = 2;
      public static final int SCALE_X = 3;
      public static final int SCALE_Y = 4;
      public static final int ROTATION = 5;
      public static final int ROTATION_X = 6;
      public static final int ROTATION_Y = 7;
      public static final int X = 8;
      public static final int Y = 9;
      public static final int Z = 10;
      public static final int ALPHA = 11;
      // The last value in the enum, used for array size initialization
      public static final int LAST_VALUE = ALPHA;
  
      // Keep in sync with enum PaintFields in Animator.h
      public static final int PAINT_STROKE_WIDTH = 0;
  
      /**
       * Field for the Paint alpha channel, which should be specified as a value
       * between 0 and 255.
       */
      public static final int PAINT_ALPHA = 1;
  
      // ViewPropertyAnimator uses a mask for its values, we need to remap them
      // to the enum values here. RenderPropertyAnimator can't use the mask values
      // directly as internally it uses a lookup table so it needs the values to
      // be sequential starting from 0
      private static final SparseIntArray sViewPropertyAnimatorMap = new SparseIntArray(15) {{
          put(ViewPropertyAnimator.TRANSLATION_X, TRANSLATION_X);
          put(ViewPropertyAnimator.TRANSLATION_Y, TRANSLATION_Y);
          put(ViewPropertyAnimator.TRANSLATION_Z, TRANSLATION_Z);
          put(ViewPropertyAnimator.SCALE_X, SCALE_X);
          put(ViewPropertyAnimator.SCALE_Y, SCALE_Y);
          put(ViewPropertyAnimator.ROTATION, ROTATION);
          put(ViewPropertyAnimator.ROTATION_X, ROTATION_X);
          put(ViewPropertyAnimator.ROTATION_Y, ROTATION_Y);
          put(ViewPropertyAnimator.X, X);
          put(ViewPropertyAnimator.Y, Y);
          put(ViewPropertyAnimator.Z, Z);
          put(ViewPropertyAnimator.ALPHA, ALPHA);
      }};
  
      private VirtualRefBasePtr mNativePtr;
  
      private Handler mHandler;
      private RenderNode mTarget;
      private View mViewTarget;
      private int mRenderProperty = -1;
      private float mFinalValue;
      private TimeInterpolator mInterpolator;
  
      private static final int STATE_PREPARE = 0;
      private static final int STATE_DELAYED = 1;
      private static final int STATE_RUNNING = 2;
      private static final int STATE_FINISHED = 3;
      private int mState = STATE_PREPARE;
  
      private long mUnscaledDuration = 300;
      private long mUnscaledStartDelay = 0;
      // If this is true, we will run any start delays on the UI thread. This is
      // the safe default, and is necessary to ensure start listeners fire at
      // the correct time. Animators created by RippleDrawable (the
      // CanvasProperty<> ones) do not have this expectation, and as such will
      // set this to false so that the renderthread handles the startdelay instead
      private final boolean mUiThreadHandlesDelay;
      private long mStartDelay = 0;
      private long mStartTime;
  
      @UnsupportedAppUsage
      public static int mapViewPropertyToRenderProperty(int viewProperty) {
          return sViewPropertyAnimatorMap.get(viewProperty);
      }
  
      @UnsupportedAppUsage
      public RenderNodeAnimator(int property, float finalValue) {
          mRenderProperty = property;
          mFinalValue = finalValue;
          mUiThreadHandlesDelay = true;
          init(nCreateAnimator(property, finalValue));
      }
  
      @UnsupportedAppUsage
      public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) {
          init(nCreateCanvasPropertyFloatAnimator(
                  property.getNativeContainer(), finalValue));
          mUiThreadHandlesDelay = false;
      }
  
      /**
       * Creates a new render node animator for a field on a Paint property.
       *
       * @param property The paint property to target
       * @param paintField Paint field to animate, one of {@link #PAINT_ALPHA} or
       *            {@link #PAINT_STROKE_WIDTH}
       * @param finalValue The target value for the property
       */
      @UnsupportedAppUsage
      public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) {
          init(nCreateCanvasPropertyPaintAnimator(
                  property.getNativeContainer(), paintField, finalValue));
          mUiThreadHandlesDelay = false;
      }
  
      public RenderNodeAnimator(int x, int y, float startRadius, float endRadius) {
          init(nCreateRevealAnimator(x, y, startRadius, endRadius));
          mUiThreadHandlesDelay = true;
      }
  
      private void init(long ptr) {
          mNativePtr = new VirtualRefBasePtr(ptr);
      }
  
      private void checkMutable() {
          if (mState != STATE_PREPARE) {
              throw new IllegalStateException("Animator has already started, cannot change it now!");
          }
          if (mNativePtr == null) {
              throw new IllegalStateException("Animator's target has been destroyed "
                      + "(trying to modify an animation after activity destroy?)");
          }
      }
  
      static boolean isNativeInterpolator(TimeInterpolator interpolator) {
          return interpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class);
      }
  
      private void applyInterpolator() {
          if (mInterpolator == null || mNativePtr == null) return;
  
          long ni;
          if (isNativeInterpolator(mInterpolator)) {
              ni = ((NativeInterpolatorFactory)mInterpolator).createNativeInterpolator();
          } else {
              long duration = nGetDuration(mNativePtr.get());
              ni = FallbackLUTInterpolator.createNativeInterpolator(mInterpolator, duration);
          }
          nSetInterpolator(mNativePtr.get(), ni);
      }
  
      @Override
      public void start() {
          if (mTarget == null) {
              throw new IllegalStateException("Missing target!");
          }
  
          if (mState != STATE_PREPARE) {
              throw new IllegalStateException("Already started!");
          }
  
          mState = STATE_DELAYED;
          if (mHandler == null) {
              mHandler = new Handler(true);
          }
          applyInterpolator();
  
          if (mNativePtr == null) {
              // It's dead, immediately cancel
              cancel();
          } else if (mStartDelay <= 0 || !mUiThreadHandlesDelay) {
              nSetStartDelay(mNativePtr.get(), mStartDelay);
              doStart();
          } else {
              getHelper().addDelayedAnimation(this);
          }
      }
  
      private void doStart() {
          // Alpha is a special snowflake that has the canonical value stored
          // in mTransformationInfo instead of in RenderNode, so we need to update
          // it with the final value here.
          if (mRenderProperty == RenderNodeAnimator.ALPHA) {
              mViewTarget.ensureTransformationInfo();
              mViewTarget.setAlphaInternal(mFinalValue);
          }
  
          moveToRunningState();
  
          if (mViewTarget != null) {
              // Kick off a frame to start the process
              mViewTarget.invalidateViewProperty(true, false);
          }
      }
  
      private void moveToRunningState() {
          mState = STATE_RUNNING;
          if (mNativePtr != null) {
              nStart(mNativePtr.get());
          }
          notifyStartListeners();
      }
  
      private void notifyStartListeners() {
          final ArrayList<AnimatorListener> listeners = cloneListeners();
          final int numListeners = listeners == null ? 0 : listeners.size();
          for (int i = 0; i < numListeners; i++) {
              listeners.get(i).onAnimationStart(this);
          }
      }
  
      @Override
      public void cancel() {
          if (mState != STATE_PREPARE && mState != STATE_FINISHED) {
              if (mState == STATE_DELAYED) {
                  getHelper().removeDelayedAnimation(this);
                  moveToRunningState();
              }
  
              final ArrayList<AnimatorListener> listeners = cloneListeners();
              final int numListeners = listeners == null ? 0 : listeners.size();
              for (int i = 0; i < numListeners; i++) {
                  listeners.get(i).onAnimationCancel(this);
              }
  
              end();
          }
      }
  
      @Override
      public void end() {
          if (mState != STATE_FINISHED) {
              if (mState < STATE_RUNNING) {
                  getHelper().removeDelayedAnimation(this);
                  doStart();
              }
              if (mNativePtr != null) {
                  nEnd(mNativePtr.get());
                  if (mViewTarget != null) {
                      // Kick off a frame to flush the state change
                      mViewTarget.invalidateViewProperty(true, false);
                  }
              } else {
                  // It's already dead, jump to onFinish
                  onFinished();
              }
          }
      }
  
      @Override
      public void pause() {
          throw new UnsupportedOperationException();
      }
  
      @Override
      public void resume() {
          throw new UnsupportedOperationException();
      }
  
      /** @hide */
      @UnsupportedAppUsage
      public void setTarget(View view) {
          mViewTarget = view;
          setTarget(mViewTarget.mRenderNode);
      }
  
      /** Sets the animation target to the owning view of the RecordingCanvas */
      public void setTarget(RecordingCanvas canvas) {
          setTarget(canvas.mNode);
      }
  
      /** @hide */
      @UnsupportedAppUsage
      public void setTarget(DisplayListCanvas canvas) {
          setTarget((RecordingCanvas) canvas);
      }
  
      private void setTarget(RenderNode node) {
          checkMutable();
          if (mTarget != null) {
              throw new IllegalStateException("Target already set!");
          }
          nSetListener(mNativePtr.get(), this);
          mTarget = node;
          mTarget.addAnimator(this);
      }
  
      @UnsupportedAppUsage
      public void setStartValue(float startValue) {
          checkMutable();
          nSetStartValue(mNativePtr.get(), startValue);
      }
  
      @Override
      public void setStartDelay(long startDelay) {
          checkMutable();
          if (startDelay < 0) {
              throw new IllegalArgumentException("startDelay must be positive; " + startDelay);
          }
          mUnscaledStartDelay = startDelay;
          mStartDelay = (long) (ValueAnimator.getDurationScale() * startDelay);
      }
  
      @Override
      public long getStartDelay() {
          return mUnscaledStartDelay;
      }
  
      @UnsupportedAppUsage
      @Override
      public RenderNodeAnimator setDuration(long duration) {
          checkMutable();
          if (duration < 0) {
              throw new IllegalArgumentException("duration must be positive; " + duration);
          }
          mUnscaledDuration = duration;
          nSetDuration(mNativePtr.get(), (long) (duration * ValueAnimator.getDurationScale()));
          return this;
      }
  
      @Override
      public long getDuration() {
          return mUnscaledDuration;
      }
  
      @Override
      public long getTotalDuration() {
          return mUnscaledDuration + mUnscaledStartDelay;
      }
  
      @Override
      public boolean isRunning() {
          return mState == STATE_DELAYED || mState == STATE_RUNNING;
      }
  
      @Override
      public boolean isStarted() {
          return mState != STATE_PREPARE;
      }
  
      @Override
      public void setInterpolator(TimeInterpolator interpolator) {
          checkMutable();
          mInterpolator = interpolator;
      }
  
      @Override
      public TimeInterpolator getInterpolator() {
          return mInterpolator;
      }
  
      protected void onFinished() {
          if (mState == STATE_PREPARE) {
              // Unlikely but possible, the native side has been destroyed
              // before we have started.
              releaseNativePtr();
              return;
          }
          if (mState == STATE_DELAYED) {
              getHelper().removeDelayedAnimation(this);
              notifyStartListeners();
          }
          mState = STATE_FINISHED;
  
          final ArrayList<AnimatorListener> listeners = cloneListeners();
          final int numListeners = listeners == null ? 0 : listeners.size();
          for (int i = 0; i < numListeners; i++) {
              listeners.get(i).onAnimationEnd(this);
          }
  
          // Release the native object, as it has a global reference to us. This
          // breaks the cyclic reference chain, and allows this object to be
          // GC'd
          releaseNativePtr();
      }
  
      private void releaseNativePtr() {
          if (mNativePtr != null) {
              mNativePtr.release();
              mNativePtr = null;
          }
      }
  
      @SuppressWarnings("unchecked")
      private ArrayList<AnimatorListener> cloneListeners() {
          ArrayList<AnimatorListener> listeners = getListeners();
          if (listeners != null) {
              listeners = (ArrayList<AnimatorListener>) listeners.clone();
          }
          return listeners;
      }
  
      public long getNativeAnimator() {
          return mNativePtr.get();
      }
  
      /**
       * @return true if the animator was started, false if still delayed
       */
      private boolean processDelayed(long frameTimeMs) {
          if (mStartTime == 0) {
              mStartTime = frameTimeMs;
          } else if ((frameTimeMs - mStartTime) >= mStartDelay) {
              doStart();
              return true;
          }
          return false;
      }
  
      private static DelayedAnimationHelper getHelper() {
          DelayedAnimationHelper helper = sAnimationHelper.get();
          if (helper == null) {
              helper = new DelayedAnimationHelper();
              sAnimationHelper.set(helper);
          }
          return helper;
      }
  
      private static ThreadLocal<DelayedAnimationHelper> sAnimationHelper =
              new ThreadLocal<DelayedAnimationHelper>();
  
      private static class DelayedAnimationHelper implements Runnable {
  
          private ArrayList<RenderNodeAnimator> mDelayedAnims = new ArrayList<RenderNodeAnimator>();
          private final Choreographer mChoreographer;
          private boolean mCallbackScheduled;
  
          public DelayedAnimationHelper() {
              mChoreographer = Choreographer.getInstance();
          }
  
          public void addDelayedAnimation(RenderNodeAnimator animator) {
              mDelayedAnims.add(animator);
              scheduleCallback();
          }
  
          public void removeDelayedAnimation(RenderNodeAnimator animator) {
              mDelayedAnims.remove(animator);
          }
  
          private void scheduleCallback() {
              if (!mCallbackScheduled) {
                  mCallbackScheduled = true;
                  mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
              }
          }
  
          @Override
          public void run() {
              long frameTimeMs = mChoreographer.getFrameTime();
              mCallbackScheduled = false;
  
              int end = 0;
              for (int i = 0; i < mDelayedAnims.size(); i++) {
                  RenderNodeAnimator animator = mDelayedAnims.get(i);
                  if (!animator.processDelayed(frameTimeMs)) {
                      if (end != i) {
                          mDelayedAnims.set(end, animator);
                      }
                      end++;
                  }
              }
              while (mDelayedAnims.size() > end) {
                  mDelayedAnims.remove(mDelayedAnims.size() - 1);
              }
  
              if (mDelayedAnims.size() > 0) {
                  scheduleCallback();
              }
          }
      }
  
      // Called by native
      @UnsupportedAppUsage
      private static void callOnFinished(RenderNodeAnimator animator) {
          if (animator.mHandler != null) {
              animator.mHandler.post(animator::onFinished);
          } else {
              new Handler(Looper.getMainLooper(), null, true).post(animator::onFinished);
          }
      }
  
      @Override
      public Animator clone() {
          throw new IllegalStateException("Cannot clone this animator");
      }
  
      @Override
      public void setAllowRunningAsynchronously(boolean mayRunAsync) {
          checkMutable();
          nSetAllowRunningAsync(mNativePtr.get(), mayRunAsync);
      }
  
      private static native long nCreateAnimator(int property, float finalValue);
      private static native long nCreateCanvasPropertyFloatAnimator(
              long canvasProperty, float finalValue);
      private static native long nCreateCanvasPropertyPaintAnimator(
              long canvasProperty, int paintField, float finalValue);
      private static native long nCreateRevealAnimator(
              int x, int y, float startRadius, float endRadius);
  
      private static native void nSetStartValue(long nativePtr, float startValue);
      private static native void nSetDuration(long nativePtr, long duration);
      private static native long nGetDuration(long nativePtr);
      private static native void nSetStartDelay(long nativePtr, long startDelay);
      private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
      private static native void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync);
      private static native void nSetListener(long animPtr, RenderNodeAnimator listener);
  
      private static native void nStart(long animPtr);
      private static native void nEnd(long animPtr);
  }
  