Home | History | Annotate | Download | only in animation
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package android.animation;
     17 
     18 import android.os.Handler;
     19 import android.test.ActivityInstrumentationTestCase2;
     20 import android.test.UiThreadTest;
     21 import android.test.suitebuilder.annotation.MediumTest;
     22 import android.test.suitebuilder.annotation.SmallTest;
     23 
     24 import java.util.concurrent.TimeUnit;
     25 
     26 /**
     27  * Tests for the various lifecycle events of Animators. This abstract class is subclassed by
     28  * concrete implementations that provide the actual Animator objects being tested. All of the
     29  * testing mechanisms are in this class; the subclasses are only responsible for providing
     30  * the mAnimator object.
     31  *
     32  * This test is more complicated than a typical synchronous test because much of the functionality
     33  * must happen on the UI thread. Some tests do this by using the UiThreadTest annotation to
     34  * automatically run the whole test on that thread. Other tests must run on the UI thread and also
     35  * wait for some later event to occur before ending. These tests use a combination of an
     36  * AbstractFuture mechanism and a delayed action to release that Future later.
     37  */
     38 public abstract class EventsTest
     39         extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> {
     40 
     41     protected static final int ANIM_DURATION = 400;
     42     protected static final int ANIM_DELAY = 100;
     43     protected static final int ANIM_MID_DURATION = ANIM_DURATION / 2;
     44     protected static final int ANIM_MID_DELAY = ANIM_DELAY / 2;
     45     protected static final int FUTURE_RELEASE_DELAY = 50;
     46 
     47     private boolean mStarted;  // tracks whether we've received the onAnimationStart() callback
     48     protected boolean mRunning;  // tracks whether we've started the animator
     49     private boolean mCanceled; // trackes whether we've canceled the animator
     50     protected Animator.AnimatorListener mFutureListener; // mechanism for delaying the end of the test
     51     protected FutureWaiter mFuture; // Mechanism for waiting for the UI test to complete
     52     private Animator.AnimatorListener mListener; // Listener that handles/tests the events
     53 
     54     protected Animator mAnimator; // The animator used in the tests. Must be set in subclass
     55                                   // setup() method prior to calling the superclass setup()
     56 
     57     /**
     58      * Cancels the given animator. Used to delay cancellation until some later time (after the
     59      * animator has started playing).
     60      */
     61     protected static class Canceler implements Runnable {
     62         Animator mAnim;
     63         FutureWaiter mFuture;
     64         public Canceler(Animator anim, FutureWaiter future) {
     65             mAnim = anim;
     66             mFuture = future;
     67         }
     68         @Override
     69         public void run() {
     70             try {
     71                 mAnim.cancel();
     72             } catch (junit.framework.AssertionFailedError e) {
     73                 mFuture.setException(new RuntimeException(e));
     74             }
     75         }
     76     };
     77 
     78     /**
     79      * Timeout length, based on when the animation should reasonably be complete.
     80      */
     81     protected long getTimeout() {
     82         return ANIM_DURATION + ANIM_DELAY + FUTURE_RELEASE_DELAY;
     83     }
     84 
     85     /**
     86      * Ends the given animator. Used to delay ending until some later time (after the
     87      * animator has started playing).
     88      */
     89     static class Ender implements Runnable {
     90         Animator mAnim;
     91         FutureWaiter mFuture;
     92         public Ender(Animator anim, FutureWaiter future) {
     93             mAnim = anim;
     94             mFuture = future;
     95         }
     96         @Override
     97         public void run() {
     98             try {
     99                 mAnim.end();
    100             } catch (junit.framework.AssertionFailedError e) {
    101                 mFuture.setException(new RuntimeException(e));
    102             }
    103         }
    104     };
    105 
    106     /**
    107      * Releases the given Future object when the listener's end() event is called. Specifically,
    108      * it releases it after some further delay, to give the test time to do other things right
    109      * after an animation ends.
    110      */
    111     protected static class FutureReleaseListener extends AnimatorListenerAdapter {
    112         FutureWaiter mFuture;
    113 
    114         public FutureReleaseListener(FutureWaiter future) {
    115             mFuture = future;
    116         }
    117 
    118         /**
    119          * Variant constructor that auto-releases the FutureWaiter after the specified timeout.
    120          * @param future
    121          * @param timeout
    122          */
    123         public FutureReleaseListener(FutureWaiter future, long timeout) {
    124             mFuture = future;
    125             Handler handler = new Handler();
    126             handler.postDelayed(new Runnable() {
    127                 @Override
    128                 public void run() {
    129                     mFuture.release();
    130                 }
    131             }, timeout);
    132         }
    133 
    134         @Override
    135         public void onAnimationEnd(Animator animation) {
    136             Handler handler = new Handler();
    137             handler.postDelayed(new Runnable() {
    138                 @Override
    139                 public void run() {
    140                     mFuture.release();
    141                 }
    142             }, FUTURE_RELEASE_DELAY);
    143         }
    144     };
    145 
    146     public EventsTest() {
    147         super(BasicAnimatorActivity.class);
    148     }
    149 
    150     /**
    151      * Sets up the fields used by each test. Subclasses must override this method to create
    152      * the protected mAnimator object used in all tests. Overrides must create that animator
    153      * and then call super.setup(), where further properties are set on that animator.
    154      * @throws Exception
    155      */
    156     @Override
    157     public void setUp() throws Exception {
    158         super.setUp();
    159 
    160         // mListener is the main testing mechanism of this file. The asserts of each test
    161         // are embedded in the listener callbacks that it implements.
    162         mListener = new AnimatorListenerAdapter() {
    163             @Override
    164             public void onAnimationStart(Animator animation) {
    165                 // This should only be called on an animation that has not yet been started
    166                 assertFalse(mStarted);
    167                 assertTrue(mRunning);
    168                 mStarted = true;
    169             }
    170 
    171             @Override
    172             public void onAnimationCancel(Animator animation) {
    173                 // This should only be called on an animation that has been started and not
    174                 // yet canceled or ended
    175                 assertFalse(mCanceled);
    176                 assertTrue(mRunning || mStarted);
    177                 mCanceled = true;
    178             }
    179 
    180             @Override
    181             public void onAnimationEnd(Animator animation) {
    182                 // This should only be called on an animation that has been started and not
    183                 // yet ended
    184                 assertTrue(mRunning || mStarted);
    185                 mRunning = false;
    186                 mStarted = false;
    187                 super.onAnimationEnd(animation);
    188             }
    189         };
    190 
    191         mAnimator.addListener(mListener);
    192         mAnimator.setDuration(ANIM_DURATION);
    193 
    194         mFuture = new FutureWaiter();
    195 
    196         mRunning = false;
    197         mCanceled = false;
    198         mStarted = false;
    199     }
    200 
    201     /**
    202      * Verify that calling cancel on an unstarted animator does nothing.
    203      */
    204     @UiThreadTest
    205     @SmallTest
    206     public void testCancel() throws Exception {
    207         mAnimator.cancel();
    208     }
    209 
    210     /**
    211      * Verify that calling end on an unstarted animator starts/ends an animator.
    212      */
    213     @UiThreadTest
    214     @SmallTest
    215     public void testEnd() throws Exception {
    216         mRunning = true; // end() implicitly starts an unstarted animator
    217         mAnimator.end();
    218     }
    219 
    220     /**
    221      * Verify that calling cancel on a started animator does the right thing.
    222      */
    223     @UiThreadTest
    224     @SmallTest
    225     public void testStartCancel() throws Exception {
    226         mFutureListener = new FutureReleaseListener(mFuture);
    227         getActivity().runOnUiThread(new Runnable() {
    228             @Override
    229             public void run() {
    230                 try {
    231                     mRunning = true;
    232                     mAnimator.start();
    233                     mAnimator.cancel();
    234                     mFuture.release();
    235                 } catch (junit.framework.AssertionFailedError e) {
    236                     mFuture.setException(new RuntimeException(e));
    237                 }
    238             }
    239         });
    240         mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
    241     }
    242 
    243     /**
    244      * Verify that calling end on a started animator does the right thing.
    245      */
    246     @UiThreadTest
    247     @SmallTest
    248     public void testStartEnd() throws Exception {
    249         mFutureListener = new FutureReleaseListener(mFuture);
    250         getActivity().runOnUiThread(new Runnable() {
    251             @Override
    252             public void run() {
    253                 try {
    254                     mRunning = true;
    255                     mAnimator.start();
    256                     mAnimator.end();
    257                     mFuture.release();
    258                 } catch (junit.framework.AssertionFailedError e) {
    259                     mFuture.setException(new RuntimeException(e));
    260                 }
    261             }
    262         });
    263         mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
    264     }
    265 
    266     /**
    267      * Same as testStartCancel, but with a startDelayed animator
    268      */
    269     @SmallTest
    270     public void testStartDelayedCancel() throws Exception {
    271         mFutureListener = new FutureReleaseListener(mFuture);
    272         mAnimator.setStartDelay(ANIM_DELAY);
    273         getActivity().runOnUiThread(new Runnable() {
    274             @Override
    275             public void run() {
    276                 try {
    277                     mRunning = true;
    278                     mAnimator.start();
    279                     mAnimator.cancel();
    280                     mFuture.release();
    281                 } catch (junit.framework.AssertionFailedError e) {
    282                     mFuture.setException(new RuntimeException(e));
    283                 }
    284             }
    285         });
    286         mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
    287     }
    288 
    289     /**
    290      * Same as testStartEnd, but with a startDelayed animator
    291      */
    292     @SmallTest
    293     public void testStartDelayedEnd() throws Exception {
    294         mFutureListener = new FutureReleaseListener(mFuture);
    295         mAnimator.setStartDelay(ANIM_DELAY);
    296         getActivity().runOnUiThread(new Runnable() {
    297             @Override
    298             public void run() {
    299                 try {
    300                     mRunning = true;
    301                     mAnimator.start();
    302                     mAnimator.end();
    303                     mFuture.release();
    304                 } catch (junit.framework.AssertionFailedError e) {
    305                     mFuture.setException(new RuntimeException(e));
    306                 }
    307             }
    308         });
    309         mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
    310     }
    311 
    312     /**
    313      * Verify that canceling an animator that is playing does the right thing.
    314      */
    315     @MediumTest
    316     public void testPlayingCancel() throws Exception {
    317         mFutureListener = new FutureReleaseListener(mFuture);
    318         getActivity().runOnUiThread(new Runnable() {
    319             @Override
    320             public void run() {
    321                 try {
    322                     Handler handler = new Handler();
    323                     mAnimator.addListener(mFutureListener);
    324                     mRunning = true;
    325                     mAnimator.start();
    326                     handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION);
    327                 } catch (junit.framework.AssertionFailedError e) {
    328                     mFuture.setException(new RuntimeException(e));
    329                 }
    330             }
    331         });
    332         mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
    333     }
    334 
    335     /**
    336      * Verify that ending an animator that is playing does the right thing.
    337      */
    338     @MediumTest
    339     public void testPlayingEnd() throws Exception {
    340         mFutureListener = new FutureReleaseListener(mFuture);
    341         getActivity().runOnUiThread(new Runnable() {
    342             @Override
    343             public void run() {
    344                 try {
    345                     Handler handler = new Handler();
    346                     mAnimator.addListener(mFutureListener);
    347                     mRunning = true;
    348                     mAnimator.start();
    349                     handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DURATION);
    350                 } catch (junit.framework.AssertionFailedError e) {
    351                     mFuture.setException(new RuntimeException(e));
    352                 }
    353             }
    354         });
    355         mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
    356     }
    357 
    358     /**
    359      * Same as testPlayingCancel, but with a startDelayed animator
    360      */
    361     @MediumTest
    362     public void testPlayingDelayedCancel() throws Exception {
    363         mAnimator.setStartDelay(ANIM_DELAY);
    364         mFutureListener = new FutureReleaseListener(mFuture);
    365         getActivity().runOnUiThread(new Runnable() {
    366             @Override
    367             public void run() {
    368                 try {
    369                     Handler handler = new Handler();
    370                     mAnimator.addListener(mFutureListener);
    371                     mRunning = true;
    372                     mAnimator.start();
    373                     handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION);
    374                 } catch (junit.framework.AssertionFailedError e) {
    375                     mFuture.setException(new RuntimeException(e));
    376                 }
    377             }
    378         });
    379         mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
    380     }
    381 
    382     /**
    383      * Same as testPlayingEnd, but with a startDelayed animator
    384      */
    385     @MediumTest
    386     public void testPlayingDelayedEnd() throws Exception {
    387         mAnimator.setStartDelay(ANIM_DELAY);
    388         mFutureListener = new FutureReleaseListener(mFuture);
    389         getActivity().runOnUiThread(new Runnable() {
    390             @Override
    391             public void run() {
    392                 try {
    393                     Handler handler = new Handler();
    394                     mAnimator.addListener(mFutureListener);
    395                     mRunning = true;
    396                     mAnimator.start();
    397                     handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DURATION);
    398                 } catch (junit.framework.AssertionFailedError e) {
    399                     mFuture.setException(new RuntimeException(e));
    400                 }
    401             }
    402         });
    403         mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
    404     }
    405 
    406     /**
    407      * Same as testPlayingDelayedCancel, but cancel during the startDelay period
    408      */
    409     @MediumTest
    410     public void testPlayingDelayedCancelMidDelay() throws Exception {
    411         mAnimator.setStartDelay(ANIM_DELAY);
    412         getActivity().runOnUiThread(new Runnable() {
    413             @Override
    414             public void run() {
    415                 try {
    416                     // Set the listener to automatically timeout after an uncanceled animation
    417                     // would have finished. This tests to make sure that we're not calling
    418                     // the listeners with cancel/end callbacks since they won't be called
    419                     // with the start event.
    420                     mFutureListener = new FutureReleaseListener(mFuture, getTimeout());
    421                     Handler handler = new Handler();
    422                     mRunning = true;
    423                     mAnimator.start();
    424                     handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DELAY);
    425                 } catch (junit.framework.AssertionFailedError e) {
    426                     mFuture.setException(new RuntimeException(e));
    427                 }
    428             }
    429         });
    430         mFuture.get(getTimeout() + 100,  TimeUnit.MILLISECONDS);
    431     }
    432 
    433     /**
    434      * Same as testPlayingDelayedEnd, but end during the startDelay period
    435      */
    436     @MediumTest
    437     public void testPlayingDelayedEndMidDelay() throws Exception {
    438         mAnimator.setStartDelay(ANIM_DELAY);
    439         getActivity().runOnUiThread(new Runnable() {
    440             @Override
    441             public void run() {
    442                 try {
    443                     // Set the listener to automatically timeout after an uncanceled animation
    444                     // would have finished. This tests to make sure that we're not calling
    445                     // the listeners with cancel/end callbacks since they won't be called
    446                     // with the start event.
    447                     mFutureListener = new FutureReleaseListener(mFuture, getTimeout());
    448                     Handler handler = new Handler();
    449                     mRunning = true;
    450                     mAnimator.start();
    451                     handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DELAY);
    452                 } catch (junit.framework.AssertionFailedError e) {
    453                     mFuture.setException(new RuntimeException(e));
    454                 }
    455             }
    456         });
    457         mFuture.get(getTimeout() + 100,  TimeUnit.MILLISECONDS);
    458     }
    459 
    460     /**
    461      * Verifies that canceling a started animation after it has already been canceled
    462      * does nothing.
    463      */
    464     @MediumTest
    465     public void testStartDoubleCancel() throws Exception {
    466         mFutureListener = new FutureReleaseListener(mFuture);
    467         getActivity().runOnUiThread(new Runnable() {
    468             @Override
    469             public void run() {
    470                 try {
    471                     mRunning = true;
    472                     mAnimator.start();
    473                     mAnimator.cancel();
    474                     mAnimator.cancel();
    475                     mFuture.release();
    476                 } catch (junit.framework.AssertionFailedError e) {
    477                     mFuture.setException(new RuntimeException(e));
    478                 }
    479             }
    480         });
    481         mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
    482     }
    483 
    484     /**
    485      * Verifies that ending a started animation after it has already been ended
    486      * does nothing.
    487      */
    488     @MediumTest
    489     public void testStartDoubleEnd() throws Exception {
    490         mFutureListener = new FutureReleaseListener(mFuture);
    491         getActivity().runOnUiThread(new Runnable() {
    492             @Override
    493             public void run() {
    494                 try {
    495                     mRunning = true;
    496                     mAnimator.start();
    497                     mAnimator.end();
    498                     mRunning = true; // end() implicitly starts an unstarted animator
    499                     mAnimator.end();
    500                     mFuture.release();
    501                 } catch (junit.framework.AssertionFailedError e) {
    502                     mFuture.setException(new RuntimeException(e));
    503                 }
    504             }
    505         });
    506         mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
    507     }
    508 
    509     /**
    510      * Same as testStartDoubleCancel, but with a startDelayed animator
    511      */
    512     @MediumTest
    513     public void testStartDelayedDoubleCancel() throws Exception {
    514         mAnimator.setStartDelay(ANIM_DELAY);
    515         mFutureListener = new FutureReleaseListener(mFuture);
    516         getActivity().runOnUiThread(new Runnable() {
    517             @Override
    518             public void run() {
    519                 try {
    520                     mRunning = true;
    521                     mAnimator.start();
    522                     mAnimator.cancel();
    523                     mAnimator.cancel();
    524                     mFuture.release();
    525                 } catch (junit.framework.AssertionFailedError e) {
    526                     mFuture.setException(new RuntimeException(e));
    527                 }
    528             }
    529         });
    530         mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
    531      }
    532 
    533     /**
    534      * Same as testStartDoubleEnd, but with a startDelayed animator
    535      */
    536     @MediumTest
    537     public void testStartDelayedDoubleEnd() throws Exception {
    538         mAnimator.setStartDelay(ANIM_DELAY);
    539         mFutureListener = new FutureReleaseListener(mFuture);
    540         getActivity().runOnUiThread(new Runnable() {
    541             @Override
    542             public void run() {
    543                 try {
    544                     mRunning = true;
    545                     mAnimator.start();
    546                     mAnimator.end();
    547                     mRunning = true; // end() implicitly starts an unstarted animator
    548                     mAnimator.end();
    549                     mFuture.release();
    550                 } catch (junit.framework.AssertionFailedError e) {
    551                     mFuture.setException(new RuntimeException(e));
    552                 }
    553             }
    554         });
    555         mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
    556      }
    557 
    558 }
    559