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);
177                assertTrue(mStarted);
178                mCanceled = true;
179            }
180
181            @Override
182            public void onAnimationEnd(Animator animation) {
183                // This should only be called on an animation that has been started and not
184                // yet ended
185                assertTrue(mRunning);
186                assertTrue(mStarted);
187                mRunning = false;
188                mStarted = false;
189                super.onAnimationEnd(animation);
190            }
191        };
192
193        mAnimator.addListener(mListener);
194        mAnimator.setDuration(ANIM_DURATION);
195
196        mFuture = new FutureWaiter();
197
198        mRunning = false;
199        mCanceled = false;
200        mStarted = false;
201    }
202
203    /**
204     * Verify that calling cancel on an unstarted animator does nothing.
205     */
206    @UiThreadTest
207    @SmallTest
208    public void testCancel() throws Exception {
209        mAnimator.cancel();
210    }
211
212    /**
213     * Verify that calling end on an unstarted animator does nothing.
214     */
215    @UiThreadTest
216    @SmallTest
217    public void testEnd() throws Exception {
218        mAnimator.end();
219    }
220
221    /**
222     * Verify that calling cancel on a started animator does the right thing.
223     */
224    @UiThreadTest
225    @SmallTest
226    public void testStartCancel() throws Exception {
227        mFutureListener = new FutureReleaseListener(mFuture);
228        getActivity().runOnUiThread(new Runnable() {
229            @Override
230            public void run() {
231                try {
232                    mRunning = true;
233                    mAnimator.start();
234                    mAnimator.cancel();
235                    mFuture.release();
236                } catch (junit.framework.AssertionFailedError e) {
237                    mFuture.setException(new RuntimeException(e));
238                }
239            }
240        });
241        mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
242    }
243
244    /**
245     * Verify that calling end on a started animator does the right thing.
246     */
247    @UiThreadTest
248    @SmallTest
249    public void testStartEnd() throws Exception {
250        mFutureListener = new FutureReleaseListener(mFuture);
251        getActivity().runOnUiThread(new Runnable() {
252            @Override
253            public void run() {
254                try {
255                    mRunning = true;
256                    mAnimator.start();
257                    mAnimator.end();
258                    mFuture.release();
259                } catch (junit.framework.AssertionFailedError e) {
260                    mFuture.setException(new RuntimeException(e));
261                }
262            }
263        });
264        mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
265    }
266
267    /**
268     * Same as testStartCancel, but with a startDelayed animator
269     */
270    @SmallTest
271    public void testStartDelayedCancel() throws Exception {
272        mFutureListener = new FutureReleaseListener(mFuture);
273        mAnimator.setStartDelay(ANIM_DELAY);
274        getActivity().runOnUiThread(new Runnable() {
275            @Override
276            public void run() {
277                try {
278                    mRunning = true;
279                    mAnimator.start();
280                    mAnimator.cancel();
281                    mFuture.release();
282                } catch (junit.framework.AssertionFailedError e) {
283                    mFuture.setException(new RuntimeException(e));
284                }
285            }
286        });
287        mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
288    }
289
290    /**
291     * Same as testStartEnd, but with a startDelayed animator
292     */
293    @SmallTest
294    public void testStartDelayedEnd() throws Exception {
295        mFutureListener = new FutureReleaseListener(mFuture);
296        mAnimator.setStartDelay(ANIM_DELAY);
297        getActivity().runOnUiThread(new Runnable() {
298            @Override
299            public void run() {
300                try {
301                    mRunning = true;
302                    mAnimator.start();
303                    mAnimator.end();
304                    mFuture.release();
305                } catch (junit.framework.AssertionFailedError e) {
306                    mFuture.setException(new RuntimeException(e));
307                }
308            }
309        });
310        mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
311    }
312
313    /**
314     * Verify that canceling an animator that is playing does the right thing.
315     */
316    @MediumTest
317    public void testPlayingCancel() throws Exception {
318        mFutureListener = new FutureReleaseListener(mFuture);
319        getActivity().runOnUiThread(new Runnable() {
320            @Override
321            public void run() {
322                try {
323                    Handler handler = new Handler();
324                    mAnimator.addListener(mFutureListener);
325                    mRunning = true;
326                    mAnimator.start();
327                    handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION);
328                } catch (junit.framework.AssertionFailedError e) {
329                    mFuture.setException(new RuntimeException(e));
330                }
331            }
332        });
333        mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
334    }
335
336    /**
337     * Verify that ending an animator that is playing does the right thing.
338     */
339    @MediumTest
340    public void testPlayingEnd() throws Exception {
341        mFutureListener = new FutureReleaseListener(mFuture);
342        getActivity().runOnUiThread(new Runnable() {
343            @Override
344            public void run() {
345                try {
346                    Handler handler = new Handler();
347                    mAnimator.addListener(mFutureListener);
348                    mRunning = true;
349                    mAnimator.start();
350                    handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DURATION);
351                } catch (junit.framework.AssertionFailedError e) {
352                    mFuture.setException(new RuntimeException(e));
353                }
354            }
355        });
356        mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
357    }
358
359    /**
360     * Same as testPlayingCancel, but with a startDelayed animator
361     */
362    @MediumTest
363    public void testPlayingDelayedCancel() throws Exception {
364        mAnimator.setStartDelay(ANIM_DELAY);
365        mFutureListener = new FutureReleaseListener(mFuture);
366        getActivity().runOnUiThread(new Runnable() {
367            @Override
368            public void run() {
369                try {
370                    Handler handler = new Handler();
371                    mAnimator.addListener(mFutureListener);
372                    mRunning = true;
373                    mAnimator.start();
374                    handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION);
375                } catch (junit.framework.AssertionFailedError e) {
376                    mFuture.setException(new RuntimeException(e));
377                }
378            }
379        });
380        mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
381    }
382
383    /**
384     * Same as testPlayingEnd, but with a startDelayed animator
385     */
386    @MediumTest
387    public void testPlayingDelayedEnd() throws Exception {
388        mAnimator.setStartDelay(ANIM_DELAY);
389        mFutureListener = new FutureReleaseListener(mFuture);
390        getActivity().runOnUiThread(new Runnable() {
391            @Override
392            public void run() {
393                try {
394                    Handler handler = new Handler();
395                    mAnimator.addListener(mFutureListener);
396                    mRunning = true;
397                    mAnimator.start();
398                    handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DURATION);
399                } catch (junit.framework.AssertionFailedError e) {
400                    mFuture.setException(new RuntimeException(e));
401                }
402            }
403        });
404        mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
405    }
406
407    /**
408     * Same as testPlayingDelayedCancel, but cancel during the startDelay period
409     */
410    @MediumTest
411    public void testPlayingDelayedCancelMidDelay() throws Exception {
412        mAnimator.setStartDelay(ANIM_DELAY);
413        getActivity().runOnUiThread(new Runnable() {
414            @Override
415            public void run() {
416                try {
417                    // Set the listener to automatically timeout after an uncanceled animation
418                    // would have finished. This tests to make sure that we're not calling
419                    // the listeners with cancel/end callbacks since they won't be called
420                    // with the start event.
421                    mFutureListener = new FutureReleaseListener(mFuture, getTimeout());
422                    Handler handler = new Handler();
423                    mRunning = true;
424                    mAnimator.start();
425                    handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DELAY);
426                } catch (junit.framework.AssertionFailedError e) {
427                    mFuture.setException(new RuntimeException(e));
428                }
429            }
430        });
431        mFuture.get(getTimeout() + 100,  TimeUnit.MILLISECONDS);
432    }
433
434    /**
435     * Same as testPlayingDelayedEnd, but end during the startDelay period
436     */
437    @MediumTest
438    public void testPlayingDelayedEndMidDelay() throws Exception {
439        mAnimator.setStartDelay(ANIM_DELAY);
440        getActivity().runOnUiThread(new Runnable() {
441            @Override
442            public void run() {
443                try {
444                    // Set the listener to automatically timeout after an uncanceled animation
445                    // would have finished. This tests to make sure that we're not calling
446                    // the listeners with cancel/end callbacks since they won't be called
447                    // with the start event.
448                    mFutureListener = new FutureReleaseListener(mFuture, getTimeout());
449                    Handler handler = new Handler();
450                    mRunning = true;
451                    mAnimator.start();
452                    handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DELAY);
453                } catch (junit.framework.AssertionFailedError e) {
454                    mFuture.setException(new RuntimeException(e));
455                }
456            }
457        });
458        mFuture.get(getTimeout() + 100,  TimeUnit.MILLISECONDS);
459    }
460
461    /**
462     * Verifies that canceling a started animation after it has already been canceled
463     * does nothing.
464     */
465    @MediumTest
466    public void testStartDoubleCancel() throws Exception {
467        mFutureListener = new FutureReleaseListener(mFuture);
468        getActivity().runOnUiThread(new Runnable() {
469            @Override
470            public void run() {
471                try {
472                    mRunning = true;
473                    mAnimator.start();
474                    mAnimator.cancel();
475                    mAnimator.cancel();
476                    mFuture.release();
477                } catch (junit.framework.AssertionFailedError e) {
478                    mFuture.setException(new RuntimeException(e));
479                }
480            }
481        });
482        mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
483    }
484
485    /**
486     * Verifies that ending a started animation after it has already been ended
487     * does nothing.
488     */
489    @MediumTest
490    public void testStartDoubleEnd() throws Exception {
491        mFutureListener = new FutureReleaseListener(mFuture);
492        getActivity().runOnUiThread(new Runnable() {
493            @Override
494            public void run() {
495                try {
496                    mRunning = true;
497                    mAnimator.start();
498                    mAnimator.end();
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                    mAnimator.end();
548                    mFuture.release();
549                } catch (junit.framework.AssertionFailedError e) {
550                    mFuture.setException(new RuntimeException(e));
551                }
552            }
553        });
554        mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
555     }
556
557}
558