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 */
16package android.animation;
17
18import android.os.Handler;
19import android.test.ActivityInstrumentationTestCase2;
20import android.test.UiThreadTest;
21import android.test.suitebuilder.annotation.MediumTest;
22import android.test.suitebuilder.annotation.SmallTest;
23
24import 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 */
38public 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