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;
23import android.view.ViewPropertyAnimator;
24import android.widget.Button;
25import com.android.frameworks.coretests.R;
26
27import java.util.concurrent.TimeUnit;
28
29/**
30 * Tests for the various lifecycle events of Animators. This abstract class is subclassed by
31 * concrete implementations that provide the actual Animator objects being tested. All of the
32 * testing mechanisms are in this class; the subclasses are only responsible for providing
33 * the mAnimator object.
34 *
35 * This test is more complicated than a typical synchronous test because much of the functionality
36 * must happen on the UI thread. Some tests do this by using the UiThreadTest annotation to
37 * automatically run the whole test on that thread. Other tests must run on the UI thread and also
38 * wait for some later event to occur before ending. These tests use a combination of an
39 * AbstractFuture mechanism and a delayed action to release that Future later.
40 */
41public abstract class ViewPropertyAnimatorTest
42        extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> {
43
44    protected static final int ANIM_DURATION = 400;
45    protected static final int ANIM_DELAY = 100;
46    protected static final int ANIM_MID_DURATION = ANIM_DURATION / 2;
47    protected static final int ANIM_MID_DELAY = ANIM_DELAY / 2;
48    protected static final int FUTURE_RELEASE_DELAY = 50;
49
50    private boolean mStarted;  // tracks whether we've received the onAnimationStart() callback
51    protected boolean mRunning;  // tracks whether we've started the animator
52    private boolean mCanceled; // trackes whether we've canceled the animator
53    protected Animator.AnimatorListener mFutureListener; // mechanism for delaying the end of the test
54    protected FutureWaiter mFuture; // Mechanism for waiting for the UI test to complete
55    private Animator.AnimatorListener mListener; // Listener that handles/tests the events
56
57    protected ViewPropertyAnimator mAnimator; // The animator used in the tests. Must be set in subclass
58                                  // setup() method prior to calling the superclass setup()
59
60    /**
61     * Cancels the given animator. Used to delay cancellation until some later time (after the
62     * animator has started playing).
63     */
64    protected static class Canceler implements Runnable {
65        ViewPropertyAnimator mAnim;
66        FutureWaiter mFuture;
67        public Canceler(ViewPropertyAnimator anim, FutureWaiter future) {
68            mAnim = anim;
69            mFuture = future;
70        }
71        @Override
72        public void run() {
73            try {
74                mAnim.cancel();
75            } catch (junit.framework.AssertionFailedError e) {
76                mFuture.setException(new RuntimeException(e));
77            }
78        }
79    };
80
81    /**
82     * Timeout length, based on when the animation should reasonably be complete.
83     */
84    protected long getTimeout() {
85        return ANIM_DURATION + ANIM_DELAY + FUTURE_RELEASE_DELAY;
86    }
87
88    /**
89     * Releases the given Future object when the listener's end() event is called. Specifically,
90     * it releases it after some further delay, to give the test time to do other things right
91     * after an animation ends.
92     */
93    protected static class FutureReleaseListener extends AnimatorListenerAdapter {
94        FutureWaiter mFuture;
95
96        public FutureReleaseListener(FutureWaiter future) {
97            mFuture = future;
98        }
99
100        /**
101         * Variant constructor that auto-releases the FutureWaiter after the specified timeout.
102         * @param future
103         * @param timeout
104         */
105        public FutureReleaseListener(FutureWaiter future, long timeout) {
106            mFuture = future;
107            Handler handler = new Handler();
108            handler.postDelayed(new Runnable() {
109                @Override
110                public void run() {
111                    mFuture.release();
112                }
113            }, timeout);
114        }
115
116        @Override
117        public void onAnimationEnd(Animator animation) {
118            Handler handler = new Handler();
119            handler.postDelayed(new Runnable() {
120                @Override
121                public void run() {
122                    mFuture.release();
123                }
124            }, FUTURE_RELEASE_DELAY);
125        }
126    };
127
128    public ViewPropertyAnimatorTest() {
129        super(BasicAnimatorActivity.class);
130    }
131
132    /**
133     * Sets up the fields used by each test. Subclasses must override this method to create
134     * the protected mAnimator object used in all tests. Overrides must create that animator
135     * and then call super.setup(), where further properties are set on that animator.
136     * @throws Exception
137     */
138    @Override
139    public void setUp() throws Exception {
140        final BasicAnimatorActivity activity = getActivity();
141        Button button = (Button) activity.findViewById(R.id.animatingButton);
142
143        mAnimator = button.animate().x(100).y(100);
144
145        super.setUp();
146
147        // mListener is the main testing mechanism of this file. The asserts of each test
148        // are embedded in the listener callbacks that it implements.
149        mListener = new AnimatorListenerAdapter() {
150            @Override
151            public void onAnimationStart(Animator animation) {
152                // This should only be called on an animation that has not yet been started
153                assertFalse(mStarted);
154                assertTrue(mRunning);
155                mStarted = true;
156            }
157
158            @Override
159            public void onAnimationCancel(Animator animation) {
160                // This should only be called on an animation that has been started and not
161                // yet canceled or ended
162                assertFalse(mCanceled);
163                assertTrue(mRunning);
164                assertTrue(mStarted);
165                mCanceled = true;
166            }
167
168            @Override
169            public void onAnimationEnd(Animator animation) {
170                // This should only be called on an animation that has been started and not
171                // yet ended
172                assertTrue(mRunning);
173                assertTrue(mStarted);
174                mRunning = false;
175                mStarted = false;
176                super.onAnimationEnd(animation);
177            }
178        };
179
180        mAnimator.setListener(mListener);
181        mAnimator.setDuration(ANIM_DURATION);
182
183        mFuture = new FutureWaiter();
184
185        mRunning = false;
186        mCanceled = false;
187        mStarted = false;
188    }
189
190    /**
191     * Verify that calling cancel on an unstarted animator does nothing.
192     */
193    @UiThreadTest
194    @SmallTest
195    public void testCancel() throws Exception {
196        mAnimator.cancel();
197    }
198
199    /**
200     * Verify that calling cancel on a started animator does the right thing.
201     */
202    @UiThreadTest
203    @SmallTest
204    public void testStartCancel() throws Exception {
205        mFutureListener = new FutureReleaseListener(mFuture);
206        getActivity().runOnUiThread(new Runnable() {
207            @Override
208            public void run() {
209                try {
210                    mRunning = true;
211                    mAnimator.start();
212                    mAnimator.cancel();
213                    mFuture.release();
214                } catch (junit.framework.AssertionFailedError e) {
215                    mFuture.setException(new RuntimeException(e));
216                }
217            }
218        });
219        mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
220    }
221
222    /**
223     * Same as testStartCancel, but with a startDelayed animator
224     */
225    @SmallTest
226    public void testStartDelayedCancel() throws Exception {
227        mFutureListener = new FutureReleaseListener(mFuture);
228        mAnimator.setStartDelay(ANIM_DELAY);
229        getActivity().runOnUiThread(new Runnable() {
230            @Override
231            public void run() {
232                try {
233                    mRunning = true;
234                    mAnimator.start();
235                    mAnimator.cancel();
236                    mFuture.release();
237                } catch (junit.framework.AssertionFailedError e) {
238                    mFuture.setException(new RuntimeException(e));
239                }
240            }
241        });
242        mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
243    }
244
245    /**
246     * Verify that canceling an animator that is playing does the right thing.
247     */
248    @MediumTest
249    public void testPlayingCancel() throws Exception {
250        mFutureListener = new FutureReleaseListener(mFuture);
251        getActivity().runOnUiThread(new Runnable() {
252            @Override
253            public void run() {
254                try {
255                    Handler handler = new Handler();
256                    mAnimator.setListener(mFutureListener);
257                    mRunning = true;
258                    mAnimator.start();
259                    handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION);
260                } catch (junit.framework.AssertionFailedError e) {
261                    mFuture.setException(new RuntimeException(e));
262                }
263            }
264        });
265        mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
266    }
267
268    /**
269     * Same as testPlayingCancel, but with a startDelayed animator
270     */
271    @MediumTest
272    public void testPlayingDelayedCancel() throws Exception {
273        mAnimator.setStartDelay(ANIM_DELAY);
274        mFutureListener = new FutureReleaseListener(mFuture);
275        getActivity().runOnUiThread(new Runnable() {
276            @Override
277            public void run() {
278                try {
279                    Handler handler = new Handler();
280                    mAnimator.setListener(mFutureListener);
281                    mRunning = true;
282                    mAnimator.start();
283                    handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION);
284                } catch (junit.framework.AssertionFailedError e) {
285                    mFuture.setException(new RuntimeException(e));
286                }
287            }
288        });
289        mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
290    }
291
292    /**
293     * Same as testPlayingDelayedCancel, but cancel during the startDelay period
294     */
295    @MediumTest
296    public void testPlayingDelayedCancelMidDelay() throws Exception {
297        mAnimator.setStartDelay(ANIM_DELAY);
298        getActivity().runOnUiThread(new Runnable() {
299            @Override
300            public void run() {
301                try {
302                    // Set the listener to automatically timeout after an uncanceled animation
303                    // would have finished. This tests to make sure that we're not calling
304                    // the listeners with cancel/end callbacks since they won't be called
305                    // with the start event.
306                    mFutureListener = new FutureReleaseListener(mFuture, getTimeout());
307                    Handler handler = new Handler();
308                    mRunning = true;
309                    mAnimator.start();
310                    handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DELAY);
311                } catch (junit.framework.AssertionFailedError e) {
312                    mFuture.setException(new RuntimeException(e));
313                }
314            }
315        });
316        mFuture.get(getTimeout() + 100,  TimeUnit.MILLISECONDS);
317    }
318
319    /**
320     * Verifies that canceling a started animation after it has already been canceled
321     * does nothing.
322     */
323    @MediumTest
324    public void testStartDoubleCancel() throws Exception {
325        mFutureListener = new FutureReleaseListener(mFuture);
326        getActivity().runOnUiThread(new Runnable() {
327            @Override
328            public void run() {
329                try {
330                    mRunning = true;
331                    mAnimator.start();
332                    mAnimator.cancel();
333                    mAnimator.cancel();
334                    mFuture.release();
335                } catch (junit.framework.AssertionFailedError e) {
336                    mFuture.setException(new RuntimeException(e));
337                }
338            }
339        });
340        mFuture.get(getTimeout(), TimeUnit.MILLISECONDS);
341    }
342
343    /**
344     * Same as testStartDoubleCancel, but with a startDelayed animator
345     */
346    @MediumTest
347    public void testStartDelayedDoubleCancel() throws Exception {
348        mAnimator.setStartDelay(ANIM_DELAY);
349        mFutureListener = new FutureReleaseListener(mFuture);
350        getActivity().runOnUiThread(new Runnable() {
351            @Override
352            public void run() {
353                try {
354                    mRunning = true;
355                    mAnimator.start();
356                    mAnimator.cancel();
357                    mAnimator.cancel();
358                    mFuture.release();
359                } catch (junit.framework.AssertionFailedError e) {
360                    mFuture.setException(new RuntimeException(e));
361                }
362            }
363        });
364        mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
365     }
366
367}
368