| /* |
| * Copyright (C) 2011 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.test.ActivityInstrumentationTestCase2; |
| import android.test.UiThreadTest; |
| import android.test.suitebuilder.annotation.MediumTest; |
| import android.test.suitebuilder.annotation.SmallTest; |
| |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| |
| /** |
| * Tests for the various lifecycle events of Animators. This abstract class is subclassed by |
| * concrete implementations that provide the actual Animator objects being tested. All of the |
| * testing mechanisms are in this class; the subclasses are only responsible for providing |
| * the mAnimator object. |
| * |
| * This test is more complicated than a typical synchronous test because much of the functionality |
| * must happen on the UI thread. Some tests do this by using the UiThreadTest annotation to |
| * automatically run the whole test on that thread. Other tests must run on the UI thread and also |
| * wait for some later event to occur before ending. These tests use a combination of an |
| * AbstractFuture mechanism and a delayed action to release that Future later. |
| */ |
| public abstract class EventsTest |
| extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> { |
| |
| protected static final int ANIM_DURATION = 400; |
| protected static final int ANIM_DELAY = 100; |
| protected static final int ANIM_MID_DURATION = ANIM_DURATION / 2; |
| protected static final int ANIM_MID_DELAY = ANIM_DELAY / 2; |
| protected static final int ANIM_PAUSE_DURATION = ANIM_DELAY; |
| protected static final int ANIM_PAUSE_DELAY = ANIM_DELAY / 2; |
| protected static final int FUTURE_RELEASE_DELAY = 50; |
| protected static final int ANIM_FULL_DURATION_SLOP = 100; |
| |
| private boolean mStarted; // tracks whether we've received the onAnimationStart() callback |
| protected boolean mRunning; // tracks whether we've started the animator |
| private boolean mCanceled; // tracks whether we've canceled the animator |
| protected Animator.AnimatorListener mFutureListener; // mechanism for delaying end of the test |
| protected FutureWaiter mFuture; // Mechanism for waiting for the UI test to complete |
| private Animator.AnimatorListener mListener; // Listener that handles/tests the events |
| |
| protected Animator mAnimator; // The animator used in the tests. Must be set in subclass |
| // setup() method prior to calling the superclass setup() |
| |
| /** |
| * Cancels the given animator. Used to delay cancellation until some later time (after the |
| * animator has started playing). |
| */ |
| protected static class Canceler implements Runnable { |
| Animator mAnim; |
| FutureWaiter mFuture; |
| public Canceler(Animator anim, FutureWaiter future) { |
| mAnim = anim; |
| mFuture = future; |
| } |
| @Override |
| public void run() { |
| try { |
| mAnim.cancel(); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }; |
| |
| /** |
| * Timeout length, based on when the animation should reasonably be complete. |
| */ |
| protected long getTimeout() { |
| return ANIM_DURATION + ANIM_DELAY + FUTURE_RELEASE_DELAY; |
| } |
| |
| /** |
| * Ends the given animator. Used to delay ending until some later time (after the |
| * animator has started playing). |
| */ |
| static class Ender implements Runnable { |
| Animator mAnim; |
| FutureWaiter mFuture; |
| public Ender(Animator anim, FutureWaiter future) { |
| mAnim = anim; |
| mFuture = future; |
| } |
| @Override |
| public void run() { |
| try { |
| mAnim.end(); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }; |
| |
| /** |
| * Pauses the given animator. Used to delay pausing until some later time (after the |
| * animator has started playing). |
| */ |
| static class Pauser implements Runnable { |
| Animator mAnim; |
| FutureWaiter mFuture; |
| public Pauser(Animator anim, FutureWaiter future) { |
| mAnim = anim; |
| mFuture = future; |
| } |
| @Override |
| public void run() { |
| try { |
| mAnim.pause(); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }; |
| |
| /** |
| * Resumes the given animator. Used to delay resuming until some later time (after the |
| * animator has paused for some duration). |
| */ |
| static class Resumer implements Runnable { |
| Animator mAnim; |
| FutureWaiter mFuture; |
| public Resumer(Animator anim, FutureWaiter future) { |
| mAnim = anim; |
| mFuture = future; |
| } |
| @Override |
| public void run() { |
| try { |
| mAnim.resume(); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }; |
| |
| /** |
| * Releases the given Future object when the listener's end() event is called. Specifically, |
| * it releases it after some further delay, to give the test time to do other things right |
| * after an animation ends. |
| */ |
| protected static class FutureReleaseListener extends AnimatorListenerAdapter { |
| FutureWaiter mFuture; |
| |
| public FutureReleaseListener(FutureWaiter future) { |
| mFuture = future; |
| } |
| |
| /** |
| * Variant constructor that auto-releases the FutureWaiter after the specified timeout. |
| * @param future |
| * @param timeout |
| */ |
| public FutureReleaseListener(FutureWaiter future, long timeout) { |
| mFuture = future; |
| Handler handler = new Handler(); |
| handler.postDelayed(new Runnable() { |
| @Override |
| public void run() { |
| mFuture.release(); |
| } |
| }, timeout); |
| } |
| |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| Handler handler = new Handler(); |
| handler.postDelayed(new Runnable() { |
| @Override |
| public void run() { |
| mFuture.release(); |
| } |
| }, FUTURE_RELEASE_DELAY); |
| } |
| }; |
| |
| public EventsTest() { |
| super(BasicAnimatorActivity.class); |
| } |
| |
| /** |
| * Sets up the fields used by each test. Subclasses must override this method to create |
| * the protected mAnimator object used in all tests. Overrides must create that animator |
| * and then call super.setup(), where further properties are set on that animator. |
| * @throws Exception |
| */ |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| |
| // mListener is the main testing mechanism of this file. The asserts of each test |
| // are embedded in the listener callbacks that it implements. |
| mListener = new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationStart(Animator animation) { |
| // This should only be called on an animation that has not yet been started |
| assertFalse(mStarted); |
| assertTrue(mRunning); |
| mStarted = true; |
| } |
| |
| @Override |
| public void onAnimationCancel(Animator animation) { |
| // This should only be called on an animation that has been started and not |
| // yet canceled or ended |
| assertFalse(mCanceled); |
| assertTrue(mRunning || mStarted); |
| mCanceled = true; |
| } |
| |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| // This should only be called on an animation that has been started and not |
| // yet ended |
| assertTrue(mRunning || mStarted); |
| mRunning = false; |
| mStarted = false; |
| super.onAnimationEnd(animation); |
| } |
| }; |
| |
| mAnimator.addListener(mListener); |
| mAnimator.setDuration(ANIM_DURATION); |
| |
| mFuture = new FutureWaiter(); |
| |
| mRunning = false; |
| mCanceled = false; |
| mStarted = false; |
| } |
| |
| /** |
| * Verify that calling cancel on an unstarted animator does nothing. |
| */ |
| @UiThreadTest |
| @SmallTest |
| public void testCancel() throws Exception { |
| mAnimator.cancel(); |
| } |
| |
| /** |
| * Verify that calling end on an unstarted animator starts/ends an animator. |
| */ |
| @UiThreadTest |
| @SmallTest |
| public void testEnd() throws Exception { |
| mRunning = true; // end() implicitly starts an unstarted animator |
| mAnimator.end(); |
| } |
| |
| /** |
| * Verify that calling cancel on a started animator does the right thing. |
| */ |
| @UiThreadTest |
| @SmallTest |
| public void testStartCancel() throws Exception { |
| mFutureListener = new FutureReleaseListener(mFuture); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| mRunning = true; |
| mAnimator.start(); |
| mAnimator.cancel(); |
| mFuture.release(); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Verify that calling end on a started animator does the right thing. |
| */ |
| @UiThreadTest |
| @SmallTest |
| public void testStartEnd() throws Exception { |
| mFutureListener = new FutureReleaseListener(mFuture); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| mRunning = true; |
| mAnimator.start(); |
| mAnimator.end(); |
| mFuture.release(); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Same as testStartCancel, but with a startDelayed animator |
| */ |
| @SmallTest |
| public void testStartDelayedCancel() throws Exception { |
| mFutureListener = new FutureReleaseListener(mFuture); |
| mAnimator.setStartDelay(ANIM_DELAY); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| mRunning = true; |
| mAnimator.start(); |
| mAnimator.cancel(); |
| mFuture.release(); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Same as testStartEnd, but with a startDelayed animator |
| */ |
| @SmallTest |
| public void testStartDelayedEnd() throws Exception { |
| mFutureListener = new FutureReleaseListener(mFuture); |
| mAnimator.setStartDelay(ANIM_DELAY); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| mRunning = true; |
| mAnimator.start(); |
| mAnimator.end(); |
| mFuture.release(); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Verify that canceling an animator that is playing does the right thing. |
| */ |
| @MediumTest |
| public void testPlayingCancel() throws Exception { |
| mFutureListener = new FutureReleaseListener(mFuture); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| Handler handler = new Handler(); |
| mAnimator.addListener(mFutureListener); |
| mRunning = true; |
| mAnimator.start(); |
| handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Verify that ending an animator that is playing does the right thing. |
| */ |
| @MediumTest |
| public void testPlayingEnd() throws Exception { |
| mFutureListener = new FutureReleaseListener(mFuture); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| Handler handler = new Handler(); |
| mAnimator.addListener(mFutureListener); |
| mRunning = true; |
| mAnimator.start(); |
| handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DURATION); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Same as testPlayingCancel, but with a startDelayed animator |
| */ |
| @MediumTest |
| public void testPlayingDelayedCancel() throws Exception { |
| mAnimator.setStartDelay(ANIM_DELAY); |
| mFutureListener = new FutureReleaseListener(mFuture); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| Handler handler = new Handler(); |
| mAnimator.addListener(mFutureListener); |
| mRunning = true; |
| mAnimator.start(); |
| handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Same as testPlayingEnd, but with a startDelayed animator |
| */ |
| @MediumTest |
| public void testPlayingDelayedEnd() throws Exception { |
| mAnimator.setStartDelay(ANIM_DELAY); |
| mFutureListener = new FutureReleaseListener(mFuture); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| Handler handler = new Handler(); |
| mAnimator.addListener(mFutureListener); |
| mRunning = true; |
| mAnimator.start(); |
| handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DURATION); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Same as testPlayingDelayedCancel, but cancel during the startDelay period |
| */ |
| @MediumTest |
| public void testPlayingDelayedCancelMidDelay() throws Exception { |
| mAnimator.setStartDelay(ANIM_DELAY); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| // Set the listener to automatically timeout after an uncanceled animation |
| // would have finished. This tests to make sure that we're not calling |
| // the listeners with cancel/end callbacks since they won't be called |
| // with the start event. |
| mFutureListener = new FutureReleaseListener(mFuture, getTimeout()); |
| Handler handler = new Handler(); |
| mRunning = true; |
| mAnimator.start(); |
| handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DELAY); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout() + 100, TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Same as testPlayingDelayedEnd, but end during the startDelay period |
| */ |
| @MediumTest |
| public void testPlayingDelayedEndMidDelay() throws Exception { |
| mAnimator.setStartDelay(ANIM_DELAY); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| // Set the listener to automatically timeout after an uncanceled animation |
| // would have finished. This tests to make sure that we're not calling |
| // the listeners with cancel/end callbacks since they won't be called |
| // with the start event. |
| mFutureListener = new FutureReleaseListener(mFuture, getTimeout()); |
| Handler handler = new Handler(); |
| mRunning = true; |
| mAnimator.start(); |
| handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DELAY); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout() + 100, TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Verifies that canceling a started animation after it has already been canceled |
| * does nothing. |
| */ |
| @MediumTest |
| public void testStartDoubleCancel() throws Exception { |
| mFutureListener = new FutureReleaseListener(mFuture); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| mRunning = true; |
| mAnimator.start(); |
| mAnimator.cancel(); |
| mAnimator.cancel(); |
| mFuture.release(); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Verifies that ending a started animation after it has already been ended |
| * does nothing. |
| */ |
| @MediumTest |
| public void testStartDoubleEnd() throws Exception { |
| mFutureListener = new FutureReleaseListener(mFuture); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| mRunning = true; |
| mAnimator.start(); |
| mAnimator.end(); |
| mRunning = true; // end() implicitly starts an unstarted animator |
| mAnimator.end(); |
| mFuture.release(); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Same as testStartDoubleCancel, but with a startDelayed animator |
| */ |
| @MediumTest |
| public void testStartDelayedDoubleCancel() throws Exception { |
| mAnimator.setStartDelay(ANIM_DELAY); |
| mFutureListener = new FutureReleaseListener(mFuture); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| mRunning = true; |
| mAnimator.start(); |
| mAnimator.cancel(); |
| mAnimator.cancel(); |
| mFuture.release(); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Same as testStartDoubleEnd, but with a startDelayed animator |
| */ |
| @MediumTest |
| public void testStartDelayedDoubleEnd() throws Exception { |
| mAnimator.setStartDelay(ANIM_DELAY); |
| mFutureListener = new FutureReleaseListener(mFuture); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| mRunning = true; |
| mAnimator.start(); |
| mAnimator.end(); |
| mRunning = true; // end() implicitly starts an unstarted animator |
| mAnimator.end(); |
| mFuture.release(); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Verify that pausing and resuming an animator ends within |
| * the appropriate timeout duration. |
| */ |
| @MediumTest |
| public void testPauseResume() throws Exception { |
| mFutureListener = new FutureReleaseListener(mFuture); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| Handler handler = new Handler(); |
| mAnimator.addListener(mFutureListener); |
| mRunning = true; |
| mAnimator.start(); |
| handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY); |
| handler.postDelayed(new Resumer(mAnimator, mFuture), |
| ANIM_PAUSE_DELAY + ANIM_PAUSE_DURATION); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout() + ANIM_PAUSE_DURATION, TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Verify that pausing and resuming a startDelayed animator ends within |
| * the appropriate timeout duration. |
| */ |
| @MediumTest |
| public void testPauseResumeDelayed() throws Exception { |
| mAnimator.setStartDelay(ANIM_DELAY); |
| mFutureListener = new FutureReleaseListener(mFuture); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| Handler handler = new Handler(); |
| mAnimator.addListener(mFutureListener); |
| mRunning = true; |
| mAnimator.start(); |
| handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY); |
| handler.postDelayed(new Resumer(mAnimator, mFuture), |
| ANIM_PAUSE_DELAY + ANIM_PAUSE_DURATION); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| mFuture.get(getTimeout() + ANIM_PAUSE_DURATION + ANIM_FULL_DURATION_SLOP, |
| TimeUnit.MILLISECONDS); |
| } |
| |
| /** |
| * Verify that pausing an animator without resuming it causes a timeout. |
| */ |
| @MediumTest |
| public void testPauseTimeout() throws Exception { |
| mFutureListener = new FutureReleaseListener(mFuture); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| Handler handler = new Handler(); |
| mAnimator.addListener(mFutureListener); |
| mRunning = true; |
| mAnimator.start(); |
| handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| try { |
| mFuture.get(getTimeout() + ANIM_PAUSE_DURATION + ANIM_FULL_DURATION_SLOP, |
| TimeUnit.MILLISECONDS); |
| } catch (TimeoutException e) { |
| // Expected behavior, swallow the exception |
| } |
| } |
| |
| /** |
| * Verify that pausing a startDelayed animator without resuming it causes a timeout. |
| */ |
| @MediumTest |
| public void testPauseTimeoutDelayed() throws Exception { |
| mAnimator.setStartDelay(ANIM_DELAY); |
| mFutureListener = new FutureReleaseListener(mFuture); |
| getActivity().runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| Handler handler = new Handler(); |
| mAnimator.addListener(mFutureListener); |
| mRunning = true; |
| mAnimator.start(); |
| handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY); |
| } catch (junit.framework.AssertionFailedError e) { |
| mFuture.setException(new RuntimeException(e)); |
| } |
| } |
| }); |
| try { |
| mFuture.get(getTimeout() + ANIM_PAUSE_DURATION + ANIM_FULL_DURATION_SLOP, |
| TimeUnit.MILLISECONDS); |
| } catch (TimeoutException e) { |
| // Expected behavior, swallow the exception |
| } |
| } |
| } |