All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowRenderNodeAnimatorR Maven / Gradle / Ivy

package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.R;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.graphics.animation.RenderNodeAnimator;
import android.view.Choreographer;
import android.view.Choreographer.FrameCallback;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;

/**
 * Copy of ShadowRenderNodeAnimator that reflects move of RenderNodeAnimator to android.graphics in
 * R
 */
@Implements(value = RenderNodeAnimator.class, minSdk = R, isInAndroidSdk = false)
public class ShadowRenderNodeAnimatorR {
  private static final int STATE_FINISHED = 3;

  @RealObject RenderNodeAnimator realObject;
  private boolean scheduled = false;
  private long startTime = -1;
  private boolean isEnding = false;

  @Resetter
  public static void reset() {
    // sAnimationHelper is a static field used for processing delayed animations. Since it registers
    // callbacks on the Choreographer, this is a problem if not reset between tests (as once the
    // test is complete, its scheduled callbacks would be removed, but the static object would still
    // believe it was registered and not re-register for the next test).
    reflector(RenderNodeAnimatorReflector.class).setAnimationHelper(new ThreadLocal<>());
  }

  @Implementation
  public void moveToRunningState() {
    reflector(RenderNodeAnimatorReflector.class, realObject).moveToRunningState();
    if (!isEnding) {
      // Only schedule if this wasn't called during an end() call, as Robolectric will run any
      // Choreographer callbacks synchronously when unpaused (and thus end up running the full
      // animation even though RenderNodeAnimator just wanted to kick it into STATE_STARTED).
      schedule();
    }
  }

  @Implementation
  public void doStart() {
    reflector(RenderNodeAnimatorReflector.class, realObject).doStart();
    schedule();
  }

  @Implementation
  public void cancel() {
    RenderNodeAnimatorReflector renderNodeReflector =
        reflector(RenderNodeAnimatorReflector.class, realObject);
    renderNodeReflector.cancel();

    int state = renderNodeReflector.getState();

    if (state != STATE_FINISHED) {
      // In 21, RenderNodeAnimator only calls nEnd, it doesn't call the Java end method. Thus, it
      // expects the native code will end up calling onFinished, so we do that here.
      renderNodeReflector.onFinished();
    }
  }

  @Implementation
  public void end() {
    RenderNodeAnimatorReflector renderNodeReflector =
        reflector(RenderNodeAnimatorReflector.class, realObject);

    // Set this to true to prevent us from scheduling and running the full animation on the end()
    // call. This can happen if the animation had not been started yet.
    isEnding = true;
    renderNodeReflector.end();
    isEnding = false;
    unschedule();

    int state = renderNodeReflector.getState();
    if (state != STATE_FINISHED) {
      // This means that the RenderNodeAnimator called out to native code to finish the animation,
      // expecting that it would end up calling onFinished. Since that won't happen in Robolectric,
      // we call onFinished ourselves.
      renderNodeReflector.onFinished();
    }
  }

  private void schedule() {
    if (!scheduled) {
      scheduled = true;
      Choreographer.getInstance().postFrameCallback(frameCallback);
    }
  }

  private void unschedule() {
    if (scheduled) {
      Choreographer.getInstance().removeFrameCallback(frameCallback);
      scheduled = false;
    }
  }

  private final FrameCallback frameCallback =
      new FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
          scheduled = false;
          if (startTime == -1) {
            startTime = frameTimeNanos;
          }

          long duration = realObject.getDuration();
          long curTime = frameTimeNanos - startTime;
          if (curTime >= duration) {
            reflector(RenderNodeAnimatorReflector.class, realObject).onFinished();
          } else {
            schedule();
          }
        }
      };

  @ForType(value = RenderNodeAnimator.class)
  interface RenderNodeAnimatorReflector {

    @Accessor("mState")
    int getState();

    @Static
    @Accessor("sAnimationHelper")
    void setAnimationHelper(ThreadLocal threadLocal);

    void onFinished();

    @Direct
    void doStart();

    @Direct
    void cancel();

    @Direct
    void moveToRunningState();

    @Direct
    void end();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy