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;
25import java.util.concurrent.TimeoutException;
26
27/**
28 * Tests for the various lifecycle events of Animators. This abstract class is subclassed by
29 * concrete implementations that provide the actual Animator objects being tested. All of the
30 * testing mechanisms are in this class; the subclasses are only responsible for providing
31 * the mAnimator object.
32 *
33 * This test is more complicated than a typical synchronous test because much of the functionality
34 * must happen on the UI thread. Some tests do this by using the UiThreadTest annotation to
35 * automatically run the whole test on that thread. Other tests must run on the UI thread and also
36 * wait for some later event to occur before ending. These tests use a combination of an
37 * AbstractFuture mechanism and a delayed action to release that Future later.
38 */
39public abstract class EventsTest
40        extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> {
41
42    protected static final int ANIM_DURATION = 400;
43    protected static final int ANIM_DELAY = 100;
44    protected static final int ANIM_MID_DURATION = ANIM_DURATION / 2;
45    protected static final int ANIM_MID_DELAY = ANIM_DELAY / 2;
46    protected static final int ANIM_PAUSE_DURATION = ANIM_DELAY;
47    protected static final int ANIM_PAUSE_DELAY = ANIM_DELAY / 2;
48    protected static final int FUTURE_RELEASE_DELAY = 50;
49    protected static final int ANIM_FULL_DURATION_SLOP = 100;
50
51    private boolean mStarted;  // tracks whether we've received the onAnimationStart() callback
52    protected boolean mRunning;  // tracks whether we've started the animator
53    private boolean mCanceled; // tracks whether we've canceled the animator
54    protected Animator.AnimatorListener mFutureListener; // mechanism for delaying end of the test
55    protected FutureWaiter mFuture; // Mechanism for waiting for the UI test to complete
56    private Animator.AnimatorListener mListener; // Listener that handles/tests the events
57
58    protected Animator mAnimator; // The animator used in the tests. Must be set in subclass
59                                  // setup() method prior to calling the superclass setup()
60
61    /**
62     * Cancels the given animator. Used to delay cancellation until some later time (after the
63     * animator has started playing).
64     */
65    protected static class Canceler implements Runnable {
66        Animator mAnim;
67        FutureWaiter mFuture;
68        public Canceler(Animator anim, FutureWaiter future) {
69            mAnim = anim;
70            mFuture = future;
71        }
72        @Override
73        public void run() {
74            try {
75                mAnim.cancel();
76            } catch (junit.framework.AssertionFailedError e) {
77                mFuture.setException(new RuntimeException(e));
78            }
79        }
80    };
81
82    /**
83     * Timeout length, based on when the animation should reasonably be complete.
84     */
85    protected long getTimeout() {
86        return ANIM_DURATION + ANIM_DELAY + FUTURE_RELEASE_DELAY;
87    }
88
89    /**
90     * Ends the given animator. Used to delay ending until some later time (after the
91     * animator has started playing).
92     */
93    static class Ender implements Runnable {
94        Animator mAnim;
95        FutureWaiter mFuture;
96        public Ender(Animator anim, FutureWaiter future) {
97            mAnim = anim;
98            mFuture = future;
99        }
100        @Override
101        public void run() {
102            try {
103                mAnim.end();
104            } catch (junit.framework.AssertionFailedError e) {
105                mFuture.setException(new RuntimeException(e));
106            }
107        }
108    };
109
110    /**
111     * Pauses the given animator. Used to delay pausing until some later time (after the
112     * animator has started playing).
113     */
114    static class Pauser implements Runnable {
115        Animator mAnim;
116        FutureWaiter mFuture;
117        public Pauser(Animator anim, FutureWaiter future) {
118            mAnim = anim;
119            mFuture = future;
120        }
121        @Override
122        public void run() {
123            try {
124                mAnim.pause();
125            } catch (junit.framework.AssertionFailedError e) {
126                mFuture.setException(new RuntimeException(e));
127            }
128        }
129    };
130
131    /**
132     * Resumes the given animator. Used to delay resuming until some later time (after the
133     * animator has paused for some duration).
134     */
135    static class Resumer implements Runnable {
136        Animator mAnim;
137        FutureWaiter mFuture;
138        public Resumer(Animator anim, FutureWaiter future) {
139            mAnim = anim;
140            mFuture = future;
141        }
142        @Override
143        public void run() {
144            try {
145                mAnim.resume();
146            } catch (junit.framework.AssertionFailedError e) {
147                mFuture.setException(new RuntimeException(e));
148            }
149        }
150    };
151
152    /**
153     * Releases the given Future object when the listener's end() event is called. Specifically,
154     * it releases it after some further delay, to give the test time to do other things right
155     * after an animation ends.
156     */
157    protected static class FutureReleaseListener extends AnimatorListenerAdapter {
158        FutureWaiter mFuture;
159
160        public FutureReleaseListener(FutureWaiter future) {
161            mFuture = future;
162        }
163
164        /**
165         * Variant constructor that auto-releases the FutureWaiter after the specified timeout.
166         * @param future
167         * @param timeout
168         */
169        public FutureReleaseListener(FutureWaiter future, long timeout) {
170            mFuture = future;
171            Handler handler = new Handler();
172            handler.postDelayed(new Runnable() {
173                @Override
174                public void run() {
175                    mFuture.release();
176                }
177            }, timeout);
178        }
179
180        @Override
181        public void onAnimationEnd(Animator animation) {
182            Handler handler = new Handler();
183            handler.postDelayed(new Runnable() {
184                @Override
185                public void run() {
186                    mFuture.release();
187                }
188            }, FUTURE_RELEASE_DELAY);
189        }
190    };
191
192    public EventsTest() {
193        super(BasicAnimatorActivity.class);
194    }
195
196    /**
197     * Sets up the fields used by each test. Subclasses must override this method to create
198     * the protected mAnimator object used in all tests. Overrides must create that animator
199     * and then call super.setup(), where further properties are set on that animator.
200     * @throws Exception
201     */
202    @Override
203    public void setUp() throws Exception {
204        super.setUp();
205
206        // mListener is the main testing mechanism of this file. The asserts of each test
207        // are embedded in the listener callbacks that it implements.
208        mListener = new AnimatorListenerAdapter() {
209            @Override
210            public void onAnimationStart(Animator animation) {
211                // This should only be called on an animation that has not yet been started
212                assertFalse(mStarted);
213                assertTrue(mRunning);
214                mStarted = true;
215            }
216
217            @Override
218            public void onAnimationCancel(Animator animation) {
219                // This should only be called on an animation that has been started and not
220                // yet canceled or ended
221                assertFalse(mCanceled);
222                assertTrue(mRunning || mStarted);
223                mCanceled = true;
224            }
225
226            @Override
227            public void onAnimationEnd(Animator animation) {
228                // This should only be called on an animation that has been started and not
229                // yet ended
230                assertTrue(mRunning || mStarted);
231                mRunning = false;
232                mStarted = false;
233                super.onAnimationEnd(animation);
234            }
235        };
236
237        mAnimator.addListener(mListener);
238        mAnimator.setDuration(ANIM_DURATION);
239
240        mFuture = new FutureWaiter();
241
242        mRunning = false;
243        mCanceled = false;
244        mStarted = false;
245    }
246
247    /**
248     * Verify that calling cancel on an unstarted animator does nothing.
249     */
250    @UiThreadTest
251    @SmallTest
252    public void testCancel() throws Exception {
253        mAnimator.cancel();
254    }
255
256    /**
257     * Verify that calling end on an unstarted animator starts/ends an animator.
258     */
259    @UiThreadTest
260    @SmallTest
261    public void testEnd() throws Exception {
262        mRunning = true; // end() implicitly starts an unstarted animator
263        mAnimator.end();
264    }
265
266    /**
267     * Verify that calling cancel on a started animator does the right thing.
268     */
269    @UiThreadTest
270    @SmallTest
271    public void testStartCancel() throws Exception {
272        mFutureListener = new FutureReleaseListener(mFuture);
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     * Verify that calling end on a started animator does the right thing.
291     */
292    @UiThreadTest
293    @SmallTest
294    public void testStartEnd() throws Exception {
295        mFutureListener = new FutureReleaseListener(mFuture);
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     * Same as testStartCancel, but with a startDelayed animator
314     */
315    @SmallTest
316    public void testStartDelayedCancel() throws Exception {
317        mFutureListener = new FutureReleaseListener(mFuture);
318        mAnimator.setStartDelay(ANIM_DELAY);
319        getActivity().runOnUiThread(new Runnable() {
320            @Override
321            public void run() {
322                try {
323                    mRunning = true;
324                    mAnimator.start();
325                    mAnimator.cancel();
326                    mFuture.release();
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     * Same as testStartEnd, but with a startDelayed animator
337     */
338    @SmallTest
339    public void testStartDelayedEnd() throws Exception {
340        mFutureListener = new FutureReleaseListener(mFuture);
341        mAnimator.setStartDelay(ANIM_DELAY);
342        getActivity().runOnUiThread(new Runnable() {
343            @Override
344            public void run() {
345                try {
346                    mRunning = true;
347                    mAnimator.start();
348                    mAnimator.end();
349                    mFuture.release();
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     * Verify that canceling an animator that is playing does the right thing.
360     */
361    @MediumTest
362    public void testPlayingCancel() throws Exception {
363        mFutureListener = new FutureReleaseListener(mFuture);
364        getActivity().runOnUiThread(new Runnable() {
365            @Override
366            public void run() {
367                try {
368                    Handler handler = new Handler();
369                    mAnimator.addListener(mFutureListener);
370                    mRunning = true;
371                    mAnimator.start();
372                    handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION);
373                } catch (junit.framework.AssertionFailedError e) {
374                    mFuture.setException(new RuntimeException(e));
375                }
376            }
377        });
378        mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
379    }
380
381    /**
382     * Verify that ending an animator that is playing does the right thing.
383     */
384    @MediumTest
385    public void testPlayingEnd() throws Exception {
386        mFutureListener = new FutureReleaseListener(mFuture);
387        getActivity().runOnUiThread(new Runnable() {
388            @Override
389            public void run() {
390                try {
391                    Handler handler = new Handler();
392                    mAnimator.addListener(mFutureListener);
393                    mRunning = true;
394                    mAnimator.start();
395                    handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DURATION);
396                } catch (junit.framework.AssertionFailedError e) {
397                    mFuture.setException(new RuntimeException(e));
398                }
399            }
400        });
401        mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
402    }
403
404    /**
405     * Same as testPlayingCancel, but with a startDelayed animator
406     */
407    @MediumTest
408    public void testPlayingDelayedCancel() throws Exception {
409        mAnimator.setStartDelay(ANIM_DELAY);
410        mFutureListener = new FutureReleaseListener(mFuture);
411        getActivity().runOnUiThread(new Runnable() {
412            @Override
413            public void run() {
414                try {
415                    Handler handler = new Handler();
416                    mAnimator.addListener(mFutureListener);
417                    mRunning = true;
418                    mAnimator.start();
419                    handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION);
420                } catch (junit.framework.AssertionFailedError e) {
421                    mFuture.setException(new RuntimeException(e));
422                }
423            }
424        });
425        mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
426    }
427
428    /**
429     * Same as testPlayingEnd, but with a startDelayed animator
430     */
431    @MediumTest
432    public void testPlayingDelayedEnd() throws Exception {
433        mAnimator.setStartDelay(ANIM_DELAY);
434        mFutureListener = new FutureReleaseListener(mFuture);
435        getActivity().runOnUiThread(new Runnable() {
436            @Override
437            public void run() {
438                try {
439                    Handler handler = new Handler();
440                    mAnimator.addListener(mFutureListener);
441                    mRunning = true;
442                    mAnimator.start();
443                    handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DURATION);
444                } catch (junit.framework.AssertionFailedError e) {
445                    mFuture.setException(new RuntimeException(e));
446                }
447            }
448        });
449        mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
450    }
451
452    /**
453     * Same as testPlayingDelayedCancel, but cancel during the startDelay period
454     */
455    @MediumTest
456    public void testPlayingDelayedCancelMidDelay() throws Exception {
457        mAnimator.setStartDelay(ANIM_DELAY);
458        getActivity().runOnUiThread(new Runnable() {
459            @Override
460            public void run() {
461                try {
462                    // Set the listener to automatically timeout after an uncanceled animation
463                    // would have finished. This tests to make sure that we're not calling
464                    // the listeners with cancel/end callbacks since they won't be called
465                    // with the start event.
466                    mFutureListener = new FutureReleaseListener(mFuture, getTimeout());
467                    Handler handler = new Handler();
468                    mRunning = true;
469                    mAnimator.start();
470                    handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DELAY);
471                } catch (junit.framework.AssertionFailedError e) {
472                    mFuture.setException(new RuntimeException(e));
473                }
474            }
475        });
476        mFuture.get(getTimeout() + 100,  TimeUnit.MILLISECONDS);
477    }
478
479    /**
480     * Same as testPlayingDelayedEnd, but end during the startDelay period
481     */
482    @MediumTest
483    public void testPlayingDelayedEndMidDelay() throws Exception {
484        mAnimator.setStartDelay(ANIM_DELAY);
485        getActivity().runOnUiThread(new Runnable() {
486            @Override
487            public void run() {
488                try {
489                    // Set the listener to automatically timeout after an uncanceled animation
490                    // would have finished. This tests to make sure that we're not calling
491                    // the listeners with cancel/end callbacks since they won't be called
492                    // with the start event.
493                    mFutureListener = new FutureReleaseListener(mFuture, getTimeout());
494                    Handler handler = new Handler();
495                    mRunning = true;
496                    mAnimator.start();
497                    handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DELAY);
498                } catch (junit.framework.AssertionFailedError e) {
499                    mFuture.setException(new RuntimeException(e));
500                }
501            }
502        });
503        mFuture.get(getTimeout() + 100,  TimeUnit.MILLISECONDS);
504    }
505
506    /**
507     * Verifies that canceling a started animation after it has already been canceled
508     * does nothing.
509     */
510    @MediumTest
511    public void testStartDoubleCancel() throws Exception {
512        mFutureListener = new FutureReleaseListener(mFuture);
513        getActivity().runOnUiThread(new Runnable() {
514            @Override
515            public void run() {
516                try {
517                    mRunning = true;
518                    mAnimator.start();
519                    mAnimator.cancel();
520                    mAnimator.cancel();
521                    mFuture.release();
522                } catch (junit.framework.AssertionFailedError e) {
523                    mFuture.setException(new RuntimeException(e));
524                }
525            }
526        });
527        mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
528    }
529
530    /**
531     * Verifies that ending a started animation after it has already been ended
532     * does nothing.
533     */
534    @MediumTest
535    public void testStartDoubleEnd() throws Exception {
536        mFutureListener = new FutureReleaseListener(mFuture);
537        getActivity().runOnUiThread(new Runnable() {
538            @Override
539            public void run() {
540                try {
541                    mRunning = true;
542                    mAnimator.start();
543                    mAnimator.end();
544                    mRunning = true; // end() implicitly starts an unstarted animator
545                    mAnimator.end();
546                    mFuture.release();
547                } catch (junit.framework.AssertionFailedError e) {
548                    mFuture.setException(new RuntimeException(e));
549                }
550            }
551        });
552        mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
553    }
554
555    /**
556     * Same as testStartDoubleCancel, but with a startDelayed animator
557     */
558    @MediumTest
559    public void testStartDelayedDoubleCancel() throws Exception {
560        mAnimator.setStartDelay(ANIM_DELAY);
561        mFutureListener = new FutureReleaseListener(mFuture);
562        getActivity().runOnUiThread(new Runnable() {
563            @Override
564            public void run() {
565                try {
566                    mRunning = true;
567                    mAnimator.start();
568                    mAnimator.cancel();
569                    mAnimator.cancel();
570                    mFuture.release();
571                } catch (junit.framework.AssertionFailedError e) {
572                    mFuture.setException(new RuntimeException(e));
573                }
574            }
575        });
576        mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
577     }
578
579    /**
580     * Same as testStartDoubleEnd, but with a startDelayed animator
581     */
582    @MediumTest
583    public void testStartDelayedDoubleEnd() throws Exception {
584        mAnimator.setStartDelay(ANIM_DELAY);
585        mFutureListener = new FutureReleaseListener(mFuture);
586        getActivity().runOnUiThread(new Runnable() {
587            @Override
588            public void run() {
589                try {
590                    mRunning = true;
591                    mAnimator.start();
592                    mAnimator.end();
593                    mRunning = true; // end() implicitly starts an unstarted animator
594                    mAnimator.end();
595                    mFuture.release();
596                } catch (junit.framework.AssertionFailedError e) {
597                    mFuture.setException(new RuntimeException(e));
598                }
599            }
600        });
601        mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
602     }
603
604    /**
605     * Verify that pausing and resuming an animator ends within
606     * the appropriate timeout duration.
607     */
608    @MediumTest
609    public void testPauseResume() throws Exception {
610        mFutureListener = new FutureReleaseListener(mFuture);
611        getActivity().runOnUiThread(new Runnable() {
612            @Override
613            public void run() {
614                try {
615                    Handler handler = new Handler();
616                    mAnimator.addListener(mFutureListener);
617                    mRunning = true;
618                    mAnimator.start();
619                    handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY);
620                    handler.postDelayed(new Resumer(mAnimator, mFuture),
621                            ANIM_PAUSE_DELAY + ANIM_PAUSE_DURATION);
622                } catch (junit.framework.AssertionFailedError e) {
623                    mFuture.setException(new RuntimeException(e));
624                }
625            }
626        });
627        mFuture.get(getTimeout() + ANIM_PAUSE_DURATION, TimeUnit.MILLISECONDS);
628    }
629
630    /**
631     * Verify that pausing and resuming a startDelayed animator ends within
632     * the appropriate timeout duration.
633     */
634    @MediumTest
635    public void testPauseResumeDelayed() throws Exception {
636        mAnimator.setStartDelay(ANIM_DELAY);
637        mFutureListener = new FutureReleaseListener(mFuture);
638        getActivity().runOnUiThread(new Runnable() {
639            @Override
640            public void run() {
641                try {
642                    Handler handler = new Handler();
643                    mAnimator.addListener(mFutureListener);
644                    mRunning = true;
645                    mAnimator.start();
646                    handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY);
647                    handler.postDelayed(new Resumer(mAnimator, mFuture),
648                            ANIM_PAUSE_DELAY + ANIM_PAUSE_DURATION);
649                } catch (junit.framework.AssertionFailedError e) {
650                    mFuture.setException(new RuntimeException(e));
651                }
652            }
653        });
654        mFuture.get(getTimeout() + ANIM_PAUSE_DURATION + ANIM_FULL_DURATION_SLOP,
655                TimeUnit.MILLISECONDS);
656    }
657
658    /**
659     * Verify that pausing an animator without resuming it causes a timeout.
660     */
661    @MediumTest
662    public void testPauseTimeout() throws Exception {
663        mFutureListener = new FutureReleaseListener(mFuture);
664        getActivity().runOnUiThread(new Runnable() {
665            @Override
666            public void run() {
667                try {
668                    Handler handler = new Handler();
669                    mAnimator.addListener(mFutureListener);
670                    mRunning = true;
671                    mAnimator.start();
672                    handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY);
673                } catch (junit.framework.AssertionFailedError e) {
674                    mFuture.setException(new RuntimeException(e));
675                }
676            }
677        });
678        try {
679            mFuture.get(getTimeout() + ANIM_PAUSE_DURATION + ANIM_FULL_DURATION_SLOP,
680                    TimeUnit.MILLISECONDS);
681        } catch (TimeoutException e) {
682            // Expected behavior, swallow the exception
683        }
684    }
685
686    /**
687     * Verify that pausing a startDelayed animator without resuming it causes a timeout.
688     */
689    @MediumTest
690    public void testPauseTimeoutDelayed() throws Exception {
691        mAnimator.setStartDelay(ANIM_DELAY);
692        mFutureListener = new FutureReleaseListener(mFuture);
693        getActivity().runOnUiThread(new Runnable() {
694            @Override
695            public void run() {
696                try {
697                    Handler handler = new Handler();
698                    mAnimator.addListener(mFutureListener);
699                    mRunning = true;
700                    mAnimator.start();
701                    handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY);
702                } catch (junit.framework.AssertionFailedError e) {
703                    mFuture.setException(new RuntimeException(e));
704                }
705            }
706        });
707        try {
708            mFuture.get(getTimeout() + ANIM_PAUSE_DURATION + ANIM_FULL_DURATION_SLOP,
709                    TimeUnit.MILLISECONDS);
710        } catch (TimeoutException e) {
711            // Expected behavior, swallow the exception
712        }
713    }
714}
715