blob: d25ce578f4a4542f1b69038d5cd2c83628e6650d [file] [log] [blame]
/*
* Copyright (C) 2018 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 androidx.core.animation;
import android.os.Looper;
import android.os.SystemClock;
import android.util.AndroidRuntimeException;
import androidx.annotation.NonNull;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
* JUnit {@link TestRule} that can be used to run {@link Animator}s without actually waiting for the
* duration of the animation. This also helps the test to be written in a deterministic manner.
*
* Create an instance of {@code AnimatorTestRule} and specify it as a {@link org.junit.ClassRule}
* of the test class. Use {@link #advanceTimeBy(long)} to advance animators that have been started.
* Note that {@link #advanceTimeBy(long)} should be called from the same thread you have used to
* start the animator.
*
* <pre>
* {@literal @}SmallTest
* {@literal @}RunWith(AndroidJUnit4.class)
* public class SampleAnimatorTest {
*
* {@literal @}ClassRule
* public static AnimatorTestRule sAnimatorTestRule = new AnimatorTestRule();
*
* {@literal @}UiThreadTest
* {@literal @}Test
* public void sample() {
* final ValueAnimator animator = ValueAnimator.ofInt(0, 1000);
* animator.setDuration(1000L);
* assertThat(animator.getAnimatedValue(), is(0));
* animator.start();
* sAnimatorTestRule.advanceTimeBy(500L);
* assertThat(animator.getAnimatedValue(), is(500));
* }
* }
* </pre>
*/
public final class AnimatorTestRule implements TestRule {
final AnimationHandler mTestHandler;
final long mStartTime;
private long mTotalTimeDelta = 0;
private final Object mLock = new Object();
public AnimatorTestRule() {
mStartTime = SystemClock.uptimeMillis();
mTestHandler = new AnimationHandler(new TestProvider());
}
@NonNull
@Override
public Statement apply(@NonNull final Statement base, @NonNull Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
AnimationHandler.setTestHandler(mTestHandler);
try {
base.evaluate();
} finally {
AnimationHandler.setTestHandler(null);
}
}
};
}
/**
* Advances the animation clock by the given amount of delta in milliseconds. This call will
* produce an animation frame to all the ongoing animations. This method needs to be
* called on the same thread as {@link Animator#start()}.
*
* @param timeDelta the amount of milliseconds to advance
*/
public void advanceTimeBy(long timeDelta) {
if (Looper.myLooper() == null) {
// Throw an exception
throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be"
+ "called on Looper threads");
}
synchronized (mLock) {
// Advance time & pulse a frame
mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta;
}
// produce a frame
mTestHandler.onAnimationFrame(getCurrentTime());
}
/**
* Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a
* different time than the time tracked by {@link SystemClock} This method needs to be called on
* the same thread as {@link Animator#start()}.
*/
public long getCurrentTime() {
if (Looper.myLooper() == null) {
// Throw an exception
throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be"
+ "called on Looper threads");
}
synchronized (mLock) {
return mStartTime + mTotalTimeDelta;
}
}
private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider {
TestProvider() {}
@Override
public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) {
callback.doAnimationFrame(getCurrentTime());
}
@Override
public void postFrameCallback() {
}
@Override
public void setFrameDelay(long delay) {
}
@Override
public long getFrameDelay() {
return 0;
}
}
}