/* * Copyright (C) 2015 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.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.SmallTest; import android.view.Choreographer; import android.view.animation.LinearInterpolator; import java.util.ArrayList; import static android.test.MoreAsserts.assertNotEqual; public class ValueAnimatorTests extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> { private static final long WAIT_TIME_OUT = 5000; private ValueAnimator a1; private ValueAnimator a2; // Tolerance of error in calculations related to duration, frame time, etc. due to frame delay. private final static long TOLERANCE = 100; // ms private final static long POLL_INTERVAL = 100; // ms private final static float A1_START_VALUE = 0f; private final static float A1_END_VALUE = 1f; private final static int A2_START_VALUE = 100; private final static int A2_END_VALUE = 200; private final static long DEFAULT_FRAME_INTERVAL = 5; //ms private final static long COMMIT_DELAY = 3; //ms public ValueAnimatorTests() { super(BasicAnimatorActivity.class); } @Override public void setUp() throws Exception { super.setUp(); a1 = ValueAnimator.ofFloat(A1_START_VALUE, A1_END_VALUE).setDuration(300); a2 = ValueAnimator.ofInt(A2_START_VALUE, A2_END_VALUE).setDuration(500); } @Override public void tearDown() throws Exception { a1 = null; a2 = null; super.tearDown(); } @SmallTest public void testStartDelay() throws Throwable { final ValueAnimator a = ValueAnimator.ofFloat(5f, 20f); assertEquals(a.getStartDelay(), 0); final long delay = 200; a.setStartDelay(delay); assertEquals(a.getStartDelay(), delay); final MyUpdateListener listener = new MyUpdateListener(); a.addUpdateListener(listener); final long[] startTime = new long[1]; runTestOnUiThread(new Runnable() { @Override public void run() { // Test the time between isRunning() and isStarted() assertFalse(a.isStarted()); assertFalse(a.isRunning()); a.start(); startTime[0] = SystemClock.uptimeMillis(); assertTrue(a.isStarted()); assertFalse(a.isRunning()); } }); Thread.sleep(a.getTotalDuration()); runTestOnUiThread(new Runnable() { @Override public void run() { assertTrue(listener.wasRunning); assertTrue(listener.firstRunningFrameTime - startTime[0] >= delay); } }); Thread.sleep(a.getTotalDuration()); runTestOnUiThread(new Runnable() { @Override public void run() { assertFalse(a.isStarted()); } }); } @SmallTest public void testListenerCallbacks() throws Throwable { final MyListener l1 = new MyListener(); final MyListener l2 = new MyListener(); a1.addListener(l1); a2.addListener(l2); a2.setStartDelay(400); assertFalse(l1.startCalled); assertFalse(l1.cancelCalled); assertFalse(l1.endCalled); assertFalse(l2.startCalled); assertFalse(l2.cancelCalled); assertFalse(l2.endCalled); runTestOnUiThread(new Runnable() { @Override public void run() { a1.start(); a2.start(); } }); long wait = 0; Thread.sleep(POLL_INTERVAL); wait += POLL_INTERVAL; runTestOnUiThread(new Runnable() { @Override public void run() { assertFalse(l1.cancelCalled); a1.cancel(); assertTrue(l1.cancelCalled); assertTrue(l1.endCalled); } }); while (wait < a2.getStartDelay()) { runTestOnUiThread(new Runnable() { @Override public void run() { // Make sure a2's start listener isn't called during start delay. assertTrue(l1.startCalled); assertFalse(l2.startCalled); } }); Thread.sleep(POLL_INTERVAL); wait += POLL_INTERVAL; } long delay = Math.max(a1.getTotalDuration(), a2.getTotalDuration()) + TOLERANCE; Thread.sleep(delay); runTestOnUiThread(new Runnable() { @Override public void run() { // a1 is canceled. assertTrue(l1.startCalled); assertTrue(l1.cancelCalled); assertTrue(l1.endCalled); // a2 is supposed to finish normally assertTrue(l2.startCalled); assertFalse(l2.cancelCalled); assertTrue(l2.endCalled); } }); } @SmallTest public void testIsStarted() throws Throwable { assertFalse(a1.isStarted()); assertFalse(a2.isStarted()); assertFalse(a1.isRunning()); assertFalse(a2.isRunning()); final long startDelay = 150; a1.setStartDelay(startDelay); final long[] startTime = new long[1]; runTestOnUiThread(new Runnable() { @Override public void run() { a1.start(); a2.start(); startTime[0] = SystemClock.uptimeMillis(); assertTrue(a1.isStarted()); assertTrue(a2.isStarted()); } }); long delayMs = 0; while (delayMs < startDelay) { Thread.sleep(POLL_INTERVAL); delayMs += POLL_INTERVAL; runTestOnUiThread(new Runnable() { @Override public void run() { if (SystemClock.uptimeMillis() - startTime[0] < startDelay) { assertFalse(a1.isRunning()); } } }); } Thread.sleep(startDelay); runTestOnUiThread(new Runnable() { @Override public void run() { assertTrue(a1.isRunning()); assertTrue(a2.isRunning()); } }); long delay = Math.max(a1.getTotalDuration(), a2.getTotalDuration()) * 2; Thread.sleep(delay); runTestOnUiThread(new Runnable() { @Override public void run() { assertFalse(a1.isStarted()); assertFalse(a1.isRunning()); assertFalse(a2.isStarted()); assertFalse(a2.isRunning()); } }); } @SmallTest public void testPause() throws Throwable { runTestOnUiThread(new Runnable() { @Override public void run() { assertFalse(a1.isPaused()); assertFalse(a2.isPaused()); a1.start(); a2.start(); assertFalse(a1.isPaused()); assertFalse(a2.isPaused()); assertTrue(a1.isStarted()); assertTrue(a2.isStarted()); } }); Thread.sleep(POLL_INTERVAL); runTestOnUiThread(new Runnable() { @Override public void run() { assertTrue(a1.isRunning()); assertTrue(a2.isRunning()); a1.pause(); assertTrue(a1.isPaused()); assertFalse(a2.isPaused()); assertTrue(a1.isRunning()); } }); Thread.sleep(a2.getTotalDuration()); runTestOnUiThread(new Runnable() { @Override public void run() { // By this time, a2 should have finished, and a1 is still paused assertFalse(a2.isStarted()); assertFalse(a2.isRunning()); assertTrue(a1.isStarted()); assertTrue(a1.isRunning()); assertTrue(a1.isPaused()); a1.resume(); } }); Thread.sleep(POLL_INTERVAL); runTestOnUiThread(new Runnable() { @Override public void run() { assertTrue(a1.isRunning()); assertTrue(a1.isStarted()); assertFalse(a1.isPaused()); } }); Thread.sleep(a1.getTotalDuration()); runTestOnUiThread(new Runnable() { @Override public void run() { // a1 should finish by now. assertFalse(a1.isRunning()); assertFalse(a1.isStarted()); assertFalse(a1.isPaused()); } }); } @SmallTest public void testPauseListener() throws Throwable { MyPauseListener l1 = new MyPauseListener(); MyPauseListener l2 = new MyPauseListener(); a1.addPauseListener(l1); a2.addPauseListener(l2); assertFalse(l1.pauseCalled); assertFalse(l1.resumeCalled); assertFalse(l2.pauseCalled); assertFalse(l2.resumeCalled); runTestOnUiThread(new Runnable() { @Override public void run() { a1.start(); a2.start(); } }); Thread.sleep(a1.getTotalDuration() / 2); a1.pause(); Thread.sleep(a2.getTotalDuration()); // Only a1's pause listener should be called. assertTrue(l1.pauseCalled); assertFalse(l1.resumeCalled); runTestOnUiThread(new Runnable() { @Override public void run() { a1.resume(); } }); Thread.sleep(a1.getTotalDuration()); assertTrue(l1.pauseCalled); assertTrue(l1.resumeCalled); assertFalse(l2.pauseCalled); assertFalse(l2.resumeCalled); } @SmallTest public void testResume() throws Throwable { final MyUpdateListener l1 = new MyUpdateListener(); final long totalDuration = a1.getTotalDuration(); a1.addUpdateListener(l1); // Set a longer duration on a1 for this test a1.setDuration(1000); assertTrue(l1.firstRunningFrameTime < 0); assertTrue(l1.lastUpdateTime < 0); final long[] lastUpdate = new long[1]; runTestOnUiThread(new Runnable() { @Override public void run() { a1.start(); } }); Thread.sleep(totalDuration / 2); runTestOnUiThread(new Runnable() { @Override public void run() { assertTrue(l1.firstRunningFrameTime > 0); assertTrue(l1.lastUpdateTime > l1.firstRunningFrameTime); lastUpdate[0] = l1.lastUpdateTime; a1.pause(); } }); Thread.sleep(totalDuration); runTestOnUiThread(new Runnable() { @Override public void run() { // There should be no update after pause() assertEquals(lastUpdate[0], l1.lastUpdateTime); a1.resume(); } }); do { Thread.sleep(POLL_INTERVAL); runTestOnUiThread(new Runnable() { @Override public void run() { assertTrue(l1.lastUpdateTime > lastUpdate[0]); lastUpdate[0] = l1.lastUpdateTime; } }); } while (!a1.isStarted()); // Time between pause and resume: totalDuration long entireSpan = totalDuration * 2; long frameDelta = l1.lastUpdateTime - l1.firstRunningFrameTime; assertTrue(Math.abs(entireSpan - frameDelta) < TOLERANCE); } @SmallTest public void testEnd() throws Throwable { final MyListener l1 = new MyListener(); final MyListener l2 = new MyListener(); a1.addListener(l1); a2.addListener(l2); a1.addListener(new MyListener() { @Override public void onAnimationEnd(Animator anim) { anim.cancel(); } }); a2.addListener(new MyListener() { @Override public void onAnimationCancel(Animator anim) { anim.end(); } }); runTestOnUiThread(new Runnable() { @Override public void run() { assertFalse(l1.cancelCalled); assertFalse(l1.endCalled); assertFalse(l2.cancelCalled); assertFalse(l2.endCalled); a1.start(); a2.start(); } }); Thread.sleep(POLL_INTERVAL); runTestOnUiThread(new Runnable() { @Override public void run() { a1.end(); a2.cancel(); } }); Thread.sleep(POLL_INTERVAL); runTestOnUiThread(new Runnable() { @Override public void run() { // Calling cancel from onAnimationEnd will be ignored. assertFalse(l1.cancelCalled); assertTrue(l1.endCalled); assertTrue(l2.cancelCalled); assertTrue(l2.endCalled); float value1 = (Float) a1.getAnimatedValue(); int value2 = (Integer) a2.getAnimatedValue(); assertEquals(A1_END_VALUE, value1); assertEquals(A2_END_VALUE, value2); } }); } @SmallTest public void testEndValue() throws Throwable { final MyListener l1 = new MyListener(); a1.addListener(l1); final MyListener l2 = new MyListener(); a2.addListener(l2); runTestOnUiThread(new Runnable() { @Override public void run() { a1.start(); a2.start(); } }); Thread.sleep(POLL_INTERVAL); runTestOnUiThread(new Runnable() { @Override public void run() { // Animation has started but not finished, check animated values against end values assertFalse(l1.endCalled); assertFalse(l2.endCalled); assertNotEqual(A1_END_VALUE, a1.getAnimatedValue()); assertNotEqual(A1_END_VALUE, a2.getAnimatedValue()); // Force a2 to end. a2.end(); } }); Thread.sleep(a1.getTotalDuration()); runTestOnUiThread(new Runnable() { @Override public void run() { assertFalse(l1.cancelCalled); assertTrue(l1.endCalled); assertFalse(l2.cancelCalled); assertTrue(l2.endCalled); // By now a1 should have finished normally and a2 has skipped to the end, check // their end values. assertEquals(A1_END_VALUE, ((Float) (a1.getAnimatedValue())).floatValue()); assertEquals(A2_END_VALUE, ((Integer) (a2.getAnimatedValue())).intValue()); } }); } @SmallTest public void testUpdateListener() throws InterruptedException { final MyFrameCallbackProvider provider = new MyFrameCallbackProvider(); long sleep = 0; while (provider.mHandler == null) { Thread.sleep(POLL_INTERVAL); sleep += POLL_INTERVAL; if (sleep > WAIT_TIME_OUT) { break; } } // Either the looper has started, or timed out assertNotNull(provider.mHandler); final MyListener listener = new MyListener(); final MyUpdateListener l1 = new MyUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { long currentTime = SystemClock.uptimeMillis(); long frameDelay = provider.getFrameDelay(); if (lastUpdateTime > 0) { // Error tolerance here is one frame. assertTrue((currentTime - lastUpdateTime) < frameDelay * 2); } else { // First frame: assertTrue(listener.startCalled); assertTrue(listener.startTime > 0); assertTrue(currentTime - listener.startTime < frameDelay * 2); } super.onAnimationUpdate(animation); } }; a1.addUpdateListener(l1); a1.addListener(listener); a1.setStartDelay(100); provider.mHandler.post(new Runnable() { @Override public void run() { AnimationHandler.getInstance().setProvider(provider); a1.start(); } }); Thread.sleep(POLL_INTERVAL); assertTrue(a1.isStarted()); Thread.sleep(a1.getTotalDuration() + TOLERANCE); // Finished by now. assertFalse(a1.isStarted()); assertTrue(listener.endTime > 0); // Check the time difference between last frame and end time. assertTrue(listener.endTime >= l1.lastUpdateTime); assertTrue(listener.endTime - l1.lastUpdateTime < 2 * provider.getFrameDelay()); } @SmallTest public void testConcurrentModification() throws Throwable { // Attempt to modify list of animations as the list is being iterated final ValueAnimator a0 = ValueAnimator.ofInt(100, 200).setDuration(500); final ValueAnimator a3 = ValueAnimator.ofFloat(0, 1).setDuration(500); final ValueAnimator a4 = ValueAnimator.ofInt(200, 300).setDuration(500); final MyListener listener = new MyListener() { @Override public void onAnimationEnd(Animator anim) { super.onAnimationEnd(anim); // AnimationHandler should be iterating the list at the moment, end/cancel all // the other animations. No ConcurrentModificationException should happen. a0.cancel(); a1.end(); a3.end(); a4.cancel(); } }; a2.addListener(listener); runTestOnUiThread(new Runnable() { @Override public void run() { a0.start(); a1.start(); a2.start(); a3.start(); a4.start(); } }); runTestOnUiThread(new Runnable() { @Override public void run() { assertTrue(a0.isStarted()); assertTrue(a1.isStarted()); assertTrue(a2.isStarted()); assertTrue(a3.isStarted()); assertTrue(a4.isStarted()); } }); Thread.sleep(POLL_INTERVAL); runTestOnUiThread(new Runnable() { @Override public void run() { // End the animator that should be in the middle of the list. a2.end(); } }); Thread.sleep(POLL_INTERVAL); assertTrue(listener.endCalled); assertFalse(a0.isStarted()); assertFalse(a1.isStarted()); assertFalse(a2.isStarted()); assertFalse(a3.isStarted()); assertFalse(a4.isStarted()); } @SmallTest public void testSeek() throws Throwable { final MyListener l1 = new MyListener(); final MyListener l2 = new MyListener(); final MyUpdateListener updateListener1 = new MyUpdateListener(); final MyUpdateListener updateListener2 = new MyUpdateListener(); final float a1StartFraction = 0.2f; final float a2StartFraction = 0.3f; // Extend duration so we have plenty of latitude to manipulate the animations when they // are running. a1.setDuration(1000); a2.setDuration(1000); a1.addListener(l1); a2.addListener(l2); a1.addUpdateListener(updateListener1); a2.addUpdateListener(updateListener2); TimeInterpolator interpolator = new LinearInterpolator(); a1.setInterpolator(interpolator); a2.setInterpolator(interpolator); runTestOnUiThread(new Runnable() { @Override public void run() { assertFalse(a1.isStarted()); assertFalse(a1.isRunning()); assertFalse(a2.isStarted()); assertFalse(a2.isRunning()); // Test isRunning() and isStarted() before and after seek a1.setCurrentFraction(a1StartFraction); a2.setCurrentFraction(a2StartFraction); assertFalse(a1.isStarted()); assertFalse(a1.isRunning()); assertFalse(a2.isStarted()); assertFalse(a2.isRunning()); } }); Thread.sleep(POLL_INTERVAL); // Start animation and seek during the animation. runTestOnUiThread(new Runnable() { @Override public void run() { assertFalse(a1.isStarted()); assertFalse(a1.isRunning()); assertFalse(a2.isStarted()); assertFalse(a2.isRunning()); assertEquals(a1StartFraction, a1.getAnimatedFraction()); assertEquals(a2StartFraction, a2.getAnimatedFraction()); a1.start(); a2.start(); } }); Thread.sleep(POLL_INTERVAL); final float halfwayFraction = 0.5f; runTestOnUiThread(new Runnable() { @Override public void run() { assertTrue(l1.startCalled); assertTrue(l2.startCalled); assertFalse(l1.endCalled); assertFalse(l2.endCalled); // Check whether the animations start from the seeking fraction assertTrue(updateListener1.startFraction >= a1StartFraction); assertTrue(updateListener2.startFraction >= a2StartFraction); assertTrue(a1.isStarted()); assertTrue(a1.isRunning()); assertTrue(a2.isStarted()); assertTrue(a2.isRunning()); a1.setCurrentFraction(halfwayFraction); a2.setCurrentFraction(halfwayFraction); } }); Thread.sleep(POLL_INTERVAL); // Check that seeking during running doesn't change animation's internal state runTestOnUiThread(new Runnable() { @Override public void run() { assertTrue(l1.startCalled); assertTrue(l2.startCalled); assertFalse(l1.endCalled); assertFalse(l2.endCalled); assertTrue(a1.isStarted()); assertTrue(a1.isRunning()); assertTrue(a2.isStarted()); assertTrue(a2.isRunning()); } }); // Wait until the animators finish successfully. long wait = Math.max(a1.getTotalDuration(), a2.getTotalDuration()); Thread.sleep(wait); runTestOnUiThread(new Runnable() { @Override public void run() { // Verify that the animators have finished. assertTrue(l1.endCalled); assertTrue(l2.endCalled); assertFalse(a1.isStarted()); assertFalse(a2.isStarted()); assertFalse(a1.isRunning()); assertFalse(a2.isRunning()); } }); // Re-start animator a1 after it ends normally, and check that seek value from last run // does not affect the new run. updateListener1.reset(); runTestOnUiThread(new Runnable() { @Override public void run() { a1.start(); } }); Thread.sleep(POLL_INTERVAL); runTestOnUiThread(new Runnable() { @Override public void run() { assertTrue(updateListener1.wasRunning); assertTrue(updateListener1.startFraction >= 0); assertTrue(updateListener1.startFraction < halfwayFraction); a1.end(); } }); } @SmallTest public void testSeekWhileRunning() throws Throwable { // Seek one animator to the beginning and the other one to the end when they are running. final MyListener l1 = new MyListener(); final MyListener l2 = new MyListener(); a1.addListener(l1); a2.addListener(l2); runTestOnUiThread(new Runnable() { @Override public void run() { assertFalse(l1.startCalled); assertFalse(l2.startCalled); assertEquals(0f, a1.getAnimatedFraction()); assertEquals(0f, a2.getAnimatedFraction()); a1.start(); a2.start(); } }); Thread.sleep(POLL_INTERVAL); runTestOnUiThread(new Runnable() { @Override public void run() { assertFalse(l1.endCalled); assertFalse(l2.endCalled); assertTrue(a1.isRunning()); assertTrue(a2.isRunning()); // During the run, seek one to the beginning, the other to the end a1.setCurrentFraction(0f); a2.setCurrentFraction(1f); } }); Thread.sleep(POLL_INTERVAL); runTestOnUiThread(new Runnable() { @Override public void run() { // Check that a2 has finished due to the seeking, but a1 hasn't finished. assertFalse(l1.endCalled); assertTrue(l2.endCalled); assertEquals(1f, a2.getAnimatedFraction()); } }); Thread.sleep(a1.getTotalDuration()); runTestOnUiThread(new Runnable() { @Override public void run() { // By now a1 should finish also. assertTrue(l1.endCalled); assertEquals(1f, a1.getAnimatedFraction()); } }); } @SmallTest public void testEndBeforeStart() throws Throwable { // This test calls two animators that are not yet started. One animator has completed a // previous run but hasn't started since then, the other one has never run. When end() is // called on these two animators, we expected their animation listeners to receive both // onAnimationStarted(Animator) and onAnimationEnded(Animator) callbacks, in that sequence. a1.setStartDelay(20); // First start a1's first run. final MyListener normalEndingListener = new MyListener(); a1.addListener(normalEndingListener); runTestOnUiThread(new Runnable() { @Override public void run() { assertFalse(a1.isStarted()); assertFalse(normalEndingListener.startCalled); assertFalse(normalEndingListener.endCalled); // Start normally a1.start(); } }); Thread.sleep(a1.getTotalDuration() + POLL_INTERVAL); // a1 should have finished by now. runTestOnUiThread(new Runnable() { @Override public void run() { // Call end() on both a1 and a2 without calling start() final MyListener l1 = new MyListener(); a1.addListener(l1); final MyListener l2 = new MyListener(); a2.addListener(l2); assertFalse(a1.isStarted()); assertFalse(l1.startCalled); assertFalse(l1.endCalled); assertFalse(a2.isStarted()); assertFalse(l2.startCalled); assertFalse(l1.endCalled); a1.end(); a2.end(); // Check that both animators' listeners have received the animation callbacks. assertTrue(l1.startCalled); assertTrue(l1.endCalled); assertFalse(a1.isStarted()); assertTrue(l1.endTime >= l1.startTime); assertTrue(l2.startCalled); assertTrue(l2.endCalled); assertFalse(a2.isStarted()); assertTrue(l2.endTime >= l1.startTime); } }); } @SmallTest public void testZeroDuration() throws Throwable { // Run two animators with zero duration, with one running forward and the other one // backward. Check that the animations start and finish with the correct end fractions. a1.setDuration(0); a2.setDuration(0); // Set a fraction on an animation with 0-duration final ValueAnimator a3 = ValueAnimator.ofInt(0, 100); a3.setDuration(0); a3.setCurrentFraction(1.0f); assertEquals(1.0f, a3.getAnimatedFraction()); final MyListener l1 = new MyListener(); final MyListener l2 = new MyListener(); final MyListener l3 = new MyListener(); a1.addListener(l1); a2.addListener(l2); a3.addListener(l3); runTestOnUiThread(new Runnable() { @Override public void run() { assertFalse(l1.startCalled); assertFalse(l2.startCalled); assertFalse(l3.startCalled); assertFalse(l1.endCalled); assertFalse(l2.endCalled); assertFalse(l3.endCalled); a1.start(); a2.reverse(); a3.start(); // Check that the animators' values are immediately set to end value in the case of // 0-duration. assertEquals(A1_END_VALUE, a1.getAnimatedValue()); assertEquals(A2_START_VALUE, a2.getAnimatedValue()); } }); Thread.sleep(POLL_INTERVAL); runTestOnUiThread(new Runnable() { @Override public void run() { // Check that the animators have started and finished with the right values. assertTrue(l1.startCalled); assertTrue(l2.startCalled); assertTrue(l3.startCalled); assertTrue(l1.endCalled); assertTrue(l2.endCalled); assertTrue(l3.endCalled); assertEquals(1.0f, a1.getAnimatedFraction()); assertEquals(0f, a2.getAnimatedFraction()); assertEquals(1f, a3.getAnimatedFraction()); assertEquals(A1_END_VALUE, a1.getAnimatedValue()); assertEquals(A2_START_VALUE, a2.getAnimatedValue()); assertEquals(100, a3.getAnimatedValue()); } }); } @SmallTest public void testZeroScale() throws Throwable { // Test whether animations would end properly when the scale is forced to be zero float scale = ValueAnimator.getDurationScale(); ValueAnimator.setDurationScale(0f); // Run two animators, one of which has a start delay, after setting the duration scale to 0 a1.setStartDelay(200); final MyListener l1 = new MyListener(); final MyListener l2 = new MyListener(); a1.addListener(l1); a2.addListener(l2); runTestOnUiThread(new Runnable() { @Override public void run() { assertFalse(l1.startCalled); assertFalse(l2.startCalled); assertFalse(l1.endCalled); assertFalse(l2.endCalled); a1.start(); a2.start(); // In the case of 0 duration scale applied to a non-0 duration, check that the // value is immediately set to the start value. assertEquals(A2_START_VALUE, a2.getAnimatedValue()); } }); Thread.sleep(POLL_INTERVAL); runTestOnUiThread(new Runnable() { @Override public void run() { assertTrue(l1.startCalled); assertTrue(l2.startCalled); assertTrue(l1.endCalled); assertTrue(l2.endCalled); assertEquals(A1_END_VALUE, a1.getAnimatedValue()); assertEquals(A2_END_VALUE, a2.getAnimatedValue()); } }); // Restore duration scale ValueAnimator.setDurationScale(scale); } @SmallTest public void testReverse() throws Throwable { // Prolong animators duration so that we can do multiple checks during their run final ValueAnimator a3 = ValueAnimator.ofInt(0, 100); a1.setDuration(400); a2.setDuration(600); a3.setDuration(400); final MyListener l1 = new MyListener(); final MyListener l2 = new MyListener(); final MyListener l3 = new MyListener(); a1.addListener(l1); a2.addListener(l2); a3.addListener(l3); // Reverse three animators, seek one to the beginning and another to the end, and force // to end the third one during reversing. runTestOnUiThread(new Runnable() { @Override public void run() { assertFalse(l1.startCalled); assertFalse(l2.startCalled); assertFalse(l3.startCalled); assertFalse(l1.endCalled); assertFalse(l2.endCalled); assertFalse(l3.endCalled); a1.reverse(); a2.reverse(); a3.reverse(); } }); Thread.sleep(POLL_INTERVAL); runTestOnUiThread(new Runnable() { @Override public void run() { assertTrue(l1.startCalled); assertTrue(l2.startCalled); assertTrue(l3.startCalled); a1.setCurrentFraction(0f); a2.setCurrentFraction(1f); a3.end(); // Check that the fraction has been set, and the getter returns the correct values. assertEquals(1f, a1.getAnimatedFraction()); assertEquals(0f, a2.getAnimatedFraction()); } }); Thread.sleep(POLL_INTERVAL); // By now, a2 should have finished due to the seeking. It wouldn't have finished otherwise. runTestOnUiThread(new Runnable() { @Override public void run() { // Check that both animations have started, and a2 has finished. assertFalse(l1.endCalled); assertTrue(l2.endCalled); assertTrue(l3.endCalled); } }); Thread.sleep(a1.getTotalDuration()); runTestOnUiThread(new Runnable() { @Override public void run() { // Verify that a1 has finished as well. assertTrue(l1.endCalled); assertEquals(0f, a1.getAnimatedFraction()); assertEquals(0f, a2.getAnimatedFraction()); assertEquals(0f, a3.getAnimatedFraction()); } }); } class MyUpdateListener implements ValueAnimator.AnimatorUpdateListener { boolean wasRunning = false; long firstRunningFrameTime = -1; long lastUpdateTime = -1; float startFraction = 0; @Override public void onAnimationUpdate(ValueAnimator animation) { lastUpdateTime = SystemClock.uptimeMillis(); if (animation.isRunning() && !wasRunning) { // Delay has passed firstRunningFrameTime = lastUpdateTime; startFraction = animation.getAnimatedFraction(); wasRunning = animation.isRunning(); } } void reset() { wasRunning = false; firstRunningFrameTime = -1; lastUpdateTime = -1; startFraction = 0; } } class MyListener implements Animator.AnimatorListener { boolean startCalled = false; boolean cancelCalled = false; boolean endCalled = false; long startTime = -1; long endTime = -1; @Override public void onAnimationStart(Animator animation) { startCalled = true; startTime = SystemClock.uptimeMillis(); } @Override public void onAnimationEnd(Animator animation) { endCalled = true; endTime = SystemClock.uptimeMillis(); } @Override public void onAnimationCancel(Animator animation) { cancelCalled = true; } @Override public void onAnimationRepeat(Animator animation) { } } class MyPauseListener implements Animator.AnimatorPauseListener { boolean pauseCalled = false; boolean resumeCalled = false; @Override public void onAnimationPause(Animator animation) { pauseCalled = true; } @Override public void onAnimationResume(Animator animation) { resumeCalled = true; } } class MyFrameCallbackProvider implements AnimationHandler.AnimationFrameCallbackProvider { Handler mHandler = null; private final static int MSG_FRAME = 0; private long mFrameDelay = DEFAULT_FRAME_INTERVAL; private ArrayList<Choreographer.FrameCallback> mFrameCallbacks = new ArrayList<>(); final LooperThread mThread = new LooperThread(); public MyFrameCallbackProvider() { mThread.start(); } @Override public void postFrameCallback(Choreographer.FrameCallback callback) { mHandler.sendEmptyMessageDelayed(MSG_FRAME, mFrameDelay); if (!mFrameCallbacks.contains(callback)) { mFrameCallbacks.add(callback); } } @Override public void postCommitCallback(Runnable runnable) { // Run the runnable after a commit delay mHandler.postDelayed(runnable, COMMIT_DELAY); } @Override public long getFrameTime() { return SystemClock.uptimeMillis(); } @Override public long getFrameDelay() { return mFrameDelay; } @Override public void setFrameDelay(long delay) { mFrameDelay = delay; if (mFrameCallbacks.size() != 0) { mHandler.removeMessages(MSG_FRAME); mHandler.sendEmptyMessageDelayed(MSG_FRAME, mFrameDelay); } } class LooperThread extends Thread { public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // Handle message here. switch (msg.what) { case MSG_FRAME: for (int i = 0; i < mFrameCallbacks.size(); i++) { mFrameCallbacks.get(i).doFrame(SystemClock.uptimeMillis()); } break; default: break; } } }; Looper.loop(); } } } }