1 /*
2  * Copyright (C) 2009 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 
17 package android.view.animation.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNotSame;
23 import static org.junit.Assert.assertNull;
24 import static org.junit.Assert.assertSame;
25 import static org.junit.Assert.assertTrue;
26 import static org.mockito.Mockito.atLeastOnce;
27 import static org.mockito.Mockito.mock;
28 import static org.mockito.Mockito.never;
29 import static org.mockito.Mockito.reset;
30 import static org.mockito.Mockito.times;
31 import static org.mockito.Mockito.verify;
32 import static org.mockito.Mockito.verifyZeroInteractions;
33 
34 import android.app.Activity;
35 import android.app.Instrumentation;
36 import android.content.res.XmlResourceParser;
37 import android.os.SystemClock;
38 import android.support.test.InstrumentationRegistry;
39 import android.support.test.filters.LargeTest;
40 import android.support.test.filters.MediumTest;
41 import android.support.test.rule.ActivityTestRule;
42 import android.support.test.runner.AndroidJUnit4;
43 import android.util.AttributeSet;
44 import android.util.Xml;
45 import android.view.View;
46 import android.view.animation.AccelerateDecelerateInterpolator;
47 import android.view.animation.AccelerateInterpolator;
48 import android.view.animation.Animation;
49 import android.view.animation.Animation.AnimationListener;
50 import android.view.animation.AnimationUtils;
51 import android.view.animation.DecelerateInterpolator;
52 import android.view.animation.Interpolator;
53 import android.view.animation.Transformation;
54 import android.view.cts.R;
55 
56 import com.android.compatibility.common.util.PollingCheck;
57 
58 import org.junit.Before;
59 import org.junit.Rule;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 
63 import java.util.concurrent.CountDownLatch;
64 import java.util.concurrent.TimeUnit;
65 
66 /**
67  * Test {@link Animation}.
68  */
69 @MediumTest
70 @RunWith(AndroidJUnit4.class)
71 public class AnimationTest {
72     private static final float COMPARISON_DELTA = 0.001f;
73 
74     /** It is defined in R.anim.accelerate_alpha */
75     private static final int ACCELERATE_ALPHA_DURATION = 1000;
76 
77     /** It is defined in R.anim.decelerate_alpha */
78     private static final int DECELERATE_ALPHA_DURATION = 2000;
79 
80     private static final int CANCELATION_TIMEOUT = 1000;
81 
82     private Instrumentation mInstrumentation;
83     private Activity mActivity;
84 
85     @Rule
86     public ActivityTestRule<AnimationTestCtsActivity> mActivityRule =
87             new ActivityTestRule<>(AnimationTestCtsActivity.class);
88 
89     @Before
setup()90     public void setup() {
91         mInstrumentation = InstrumentationRegistry.getInstrumentation();
92         mActivity = mActivityRule.getActivity();
93     }
94 
95     @Test
testConstructor()96     public void testConstructor() {
97         XmlResourceParser parser = mActivity.getResources().getAnimation(R.anim.alpha);
98         AttributeSet attrs = Xml.asAttributeSet(parser);
99         new Animation(mActivity, attrs) {
100         };
101 
102         new Animation() {
103         };
104     }
105 
106     @Test
testAccessInterpolator()107     public void testAccessInterpolator() {
108         // check default interpolator
109         MyAnimation myAnimation = new MyAnimation();
110         Interpolator interpolator = myAnimation.getInterpolator();
111         assertTrue(interpolator instanceof AccelerateDecelerateInterpolator); // issue 1561186.
112 
113         myAnimation.ensureInterpolator();
114         assertSame(interpolator, myAnimation.getInterpolator());
115 
116         myAnimation.setInterpolator(null);
117         assertNull(myAnimation.getInterpolator());
118         myAnimation.ensureInterpolator();
119         interpolator = myAnimation.getInterpolator();
120         assertTrue(interpolator instanceof AccelerateDecelerateInterpolator);
121 
122         Animation animation = AnimationUtils.loadAnimation(mActivity, R.anim.decelerate_alpha);
123         interpolator = animation.getInterpolator();
124         assertNotNull(interpolator);
125         assertTrue(interpolator instanceof DecelerateInterpolator);
126 
127         animation = AnimationUtils.loadAnimation(mActivity, R.anim.accelerate_alpha);
128         interpolator = animation.getInterpolator();
129         assertNotNull(interpolator);
130         assertTrue(interpolator instanceof AccelerateInterpolator);
131     }
132 
133     @Test
testDefaultFill()134     public void testDefaultFill() {
135         Animation animation = new Animation() {
136         };
137         assertTrue(animation.getFillBefore());
138         assertFalse(animation.getFillAfter());
139     }
140 
141     @Test
testAccessFill()142     public void testAccessFill() throws Throwable {
143         View animWindow = mActivity.findViewById(R.id.anim_window);
144         // XML file of R.anim.accelerate_alpha
145         // <alpha xmlns:android="http://schemas.android.com/apk/res/android"
146         //     android:interpolator="@android:anim/accelerate_interpolator"
147         //     android:fromAlpha="0.1"
148         //     android:toAlpha="0.9"
149         //     android:duration="1000" />
150         Animation animation = AnimationUtils.loadAnimation(mActivity, R.anim.accelerate_alpha);
151         assertFalse(animation.isFillEnabled());
152         assertTrue(animation.getFillBefore());
153         assertFalse(animation.getFillAfter());
154 
155         AnimationTestUtils.assertRunAnimation(mInstrumentation, mActivityRule, animWindow,
156                 animation);
157 
158         // fillBefore and fillAfter are ignored when fillEnabled is false
159         Transformation transformation = new Transformation();
160         // check alpha before start
161         animation.getTransformation(animation.getStartTime() - 1, transformation);
162         float alpha = transformation.getAlpha();
163         assertEquals(0.1f, alpha, COMPARISON_DELTA);  // issue 1698355
164 
165         transformation = new Transformation();
166         // check alpha after the end
167         animation.getTransformation(animation.getStartTime() + animation.getDuration() + 1,
168                 transformation);
169         alpha = transformation.getAlpha();
170         assertEquals(0.9f, alpha, COMPARISON_DELTA);  // issue 1698355
171 
172         animation.setFillEnabled(true);
173         animation.setFillBefore(false);
174         assertTrue(animation.isFillEnabled());
175         assertFalse(animation.getFillBefore());
176         assertFalse(animation.getFillAfter());
177         AnimationTestUtils.assertRunAnimation(mInstrumentation, mActivityRule, animWindow,
178                 animation);
179 
180         transformation = new Transformation();
181         animation.getTransformation(animation.getStartTime() - 1, transformation);
182         alpha = transformation.getAlpha();
183         assertEquals(1.0f, alpha, COMPARISON_DELTA);
184 
185         transformation = new Transformation();
186         animation.getTransformation(animation.getStartTime() + animation.getDuration() + 1,
187                 transformation);
188         alpha = transformation.getAlpha();
189         assertEquals(1.0f, alpha, COMPARISON_DELTA);
190 
191         animation.setFillBefore(true);
192         animation.setFillAfter(true);
193         assertTrue(animation.isFillEnabled());
194         assertTrue(animation.getFillBefore());
195         assertTrue(animation.getFillAfter());
196         AnimationTestUtils.assertRunAnimation(mInstrumentation, mActivityRule, animWindow,
197                 animation);
198 
199         transformation = new Transformation();
200         animation.getTransformation(animation.getStartTime() - 1, transformation);
201         alpha = transformation.getAlpha();
202         assertEquals(0.1f, alpha, COMPARISON_DELTA);
203 
204         transformation = new Transformation();
205         animation.getTransformation(animation.getStartTime() + animation.getDuration() + 1,
206                 transformation);
207         alpha = transformation.getAlpha();
208         assertEquals(0.9f, alpha, COMPARISON_DELTA);
209     }
210 
211     @Test
testComputeDurationHint()212     public void testComputeDurationHint() {
213         // start offset is 0, duration is 2000, repeat count is 0.
214         Animation animation = AnimationUtils.loadAnimation(mActivity, R.anim.decelerate_alpha);
215         assertEquals(2000, animation.computeDurationHint());
216 
217         // start offset is 0, duration is 2000, repeat count is 2.
218         animation.setRepeatCount(2);
219         assertEquals(6000, animation.computeDurationHint());
220 
221         // start offset is 800, duration is 2000, repeat count is 2.
222         animation.setStartOffset(800);
223         assertEquals(8400, animation.computeDurationHint());
224     }
225 
226     @Test
testRepeatAnimation()227     public void testRepeatAnimation() throws Throwable {
228         // check default repeatMode
229         Animation animation = new Animation() {
230         };
231         assertEquals(Animation.RESTART, animation.getRepeatMode());
232 
233         final View animWindow = mActivity.findViewById(R.id.anim_window);
234 
235         // XML file of R.anim.decelerate_alpha
236         // <alpha xmlns:android="http://schemas.android.com/apk/res/android"
237         //      android:interpolator="@android:anim/decelerate_interpolator"
238         //      android:fromAlpha="0.0"
239         //      android:toAlpha="1.0"
240         //      android:duration="2000" />
241         final Animation anim = AnimationUtils.loadAnimation(mActivity, R.anim.decelerate_alpha);
242         assertEquals(Animation.RESTART, animation.getRepeatMode());
243         long duration = anim.getDuration();
244         assertEquals(DECELERATE_ALPHA_DURATION, duration);
245         // repeat count is 0, repeat mode does not make sense.
246         AnimationTestUtils.assertRunAnimation(mInstrumentation, mActivityRule, animWindow, anim);
247 
248         // test repeat mode REVERSE
249         anim.setRepeatCount(1);
250         anim.setRepeatMode(Animation.REVERSE);
251         // we have to PollingCheck the animation status on test thread,
252         // it cannot be done on UI thread, so we invoke runOnUiThread method here.
253         mActivityRule.runOnUiThread(() -> animWindow.startAnimation(anim));
254 
255         // check whether animation has started
256         PollingCheck.waitFor(anim::hasStarted);
257 
258         Transformation transformation = new Transformation();
259         long startTime = anim.getStartTime();
260         anim.getTransformation(startTime, transformation);
261         float alpha1 = transformation.getAlpha();
262         assertEquals(0.0f, alpha1, COMPARISON_DELTA);
263 
264         anim.getTransformation(startTime + 1000, transformation);
265         float alpha2 = transformation.getAlpha();
266 
267         anim.getTransformation(startTime + 2000, transformation);
268         float alpha3 = transformation.getAlpha();
269         assertEquals(1.0f, alpha3, COMPARISON_DELTA);
270 
271         // wait for animation has ended.
272         // timeout is larger than duration, in case the system is sluggish
273         PollingCheck.waitFor(duration * 2 + 1000, anim::hasEnded);
274 
275         // get start time of reversing.
276         startTime = anim.getStartTime();
277         anim.getTransformation(startTime + 3000, transformation);
278         float alpha4 = transformation.getAlpha();
279 
280         anim.getTransformation(startTime + 4000, transformation);
281         float alpha5 = transformation.getAlpha();
282         assertEquals(0.0f, alpha5, COMPARISON_DELTA);
283 
284         // check decelerating delta alpha when reverse. alpha should change form 0.0f to 1.0f
285         // and then from 1.0f to 0.0f
286         float delta1 = alpha2 - alpha1;
287         float delta2 = alpha3 - alpha2;
288         // the animation plays backward
289         float delta3 = alpha3 - alpha4;
290         float delta4 = alpha4 - alpha5;
291         assertTrue(delta1 > delta2);
292         assertTrue(delta3 > delta4);
293 
294         // test repeat mode RESTART
295         anim.setRepeatMode(Animation.RESTART);
296         // we have to PollingCheck the animation status on test thread,
297         // it cannot be done on UI thread, so we invoke runOnUiThread method here.
298         mActivityRule.runOnUiThread(() -> animWindow.startAnimation(anim));
299 
300         // check whether animation has started
301         PollingCheck.waitFor(anim::hasStarted);
302 
303         transformation = new Transformation();
304         startTime = anim.getStartTime();
305         anim.getTransformation(startTime, transformation);
306         alpha1 = transformation.getAlpha();
307         assertEquals(0.0f, alpha1, COMPARISON_DELTA);
308 
309         anim.getTransformation(startTime + 1000, transformation);
310         alpha2 = transformation.getAlpha();
311 
312         anim.getTransformation(startTime + 2000, transformation);
313         alpha3 = transformation.getAlpha();
314         assertEquals(1.0f, alpha3, COMPARISON_DELTA);
315 
316         // wait for animation has ended.
317         // timeout is larger than duration, in case the system is sluggish
318         PollingCheck.waitFor(duration * 2 + 1000, anim::hasEnded);
319 
320         // get start time of restarting.
321         startTime = anim.getStartTime();
322         anim.getTransformation(startTime + 3000, transformation);
323         alpha4 = transformation.getAlpha();
324 
325         anim.getTransformation(startTime + 4000, transformation);
326         alpha5 = transformation.getAlpha();
327         assertEquals(1.0f, alpha5, COMPARISON_DELTA);
328 
329         // check decelerating delta alpha when restart. alpha should change form 0.0f to 1.0f
330         // and then from 0.0f to 1.0f again
331         delta1 = alpha2 - 0.0f;
332         delta2 = alpha3 - alpha2;
333         // the animation restarts from the beginning
334         delta3 = alpha4 - 0.0f;
335         delta4 = alpha5 - alpha4;
336         assertTrue(delta1 > delta2);
337         assertTrue(delta3 > delta4);
338     }
339 
340     @Test
testAccessStartOffset()341     public void testAccessStartOffset() throws Throwable {
342         final long startOffset = 800;
343         // check default startOffset
344         Animation animation = new Animation() {
345         };
346         assertEquals(0, animation.getStartOffset());
347 
348         View animWindow = mActivity.findViewById(R.id.anim_window);
349         // XML file of R.anim.accelerate_alpha
350         // <alpha xmlns:android="http://schemas.android.com/apk/res/android"
351         //      android:interpolator="@android:anim/accelerate_interpolator"
352         //      android:fromAlpha="0.1"
353         //      android:toAlpha="0.9"
354         //      android:duration="1000" />
355         animation = AnimationUtils.loadAnimation(mActivity, R.anim.accelerate_alpha);
356         animation.setStartOffset(startOffset);
357         assertEquals(startOffset, animation.getStartOffset());
358 
359         AnimationTestUtils.assertRunAnimation(mInstrumentation, mActivityRule, animWindow,
360                 animation, ACCELERATE_ALPHA_DURATION + startOffset);
361 
362         Transformation transformation = new Transformation();
363         long startTime = animation.getStartTime();
364         animation.getTransformation(startTime, transformation);
365         float alpha1 = transformation.getAlpha();
366         assertEquals(0.1f, alpha1, COMPARISON_DELTA);
367 
368         animation.getTransformation(startTime + 400, transformation);
369         float alpha2 = transformation.getAlpha();
370         // alpha is 0.1f during start offset
371         assertEquals(0.1f, alpha2, COMPARISON_DELTA);
372 
373         animation.getTransformation(startTime + startOffset, transformation);
374         float alpha3 = transformation.getAlpha();
375         // alpha is 0.1f during start offset
376         assertEquals(0.1f, alpha3, COMPARISON_DELTA);
377 
378         animation.getTransformation(startTime + startOffset + 1, transformation);
379         float alpha4 = transformation.getAlpha();
380         // alpha is lager than 0.1f after start offset
381         assertTrue(alpha4 > 0.1f);
382     }
383 
384     @Test
testRunAccelerateAlpha()385     public void testRunAccelerateAlpha() throws Throwable {
386         // check default startTime
387         Animation animation = new Animation() {
388         };
389         assertEquals(Animation.START_ON_FIRST_FRAME, animation.getStartTime());
390 
391         long currentTime = AnimationUtils.currentAnimationTimeMillis();
392         animation.setStartTime(currentTime);
393         assertEquals(currentTime, animation.getStartTime());
394 
395         View animWindow = mActivity.findViewById(R.id.anim_window);
396 
397         // XML file of R.anim.accelerate_alpha
398         // <alpha xmlns:android="http://schemas.android.com/apk/res/android"
399         //      android:interpolator="@android:anim/accelerate_interpolator"
400         //      android:fromAlpha="0.1"
401         //      android:toAlpha="0.9"
402         //      android:duration="1000" />
403         Animation anim = AnimationUtils.loadAnimation(mActivity, R.anim.accelerate_alpha);
404         assertEquals(Animation.START_ON_FIRST_FRAME, anim.getStartTime());
405         assertFalse(anim.hasStarted());
406         AnimationTestUtils.assertRunAnimation(mInstrumentation, mActivityRule, animWindow, anim);
407     }
408 
409     @Test
testGetTransformation()410     public void testGetTransformation() throws Throwable {
411         final View animWindow = mActivity.findViewById(R.id.anim_window);
412 
413         // XML file of R.anim.accelerate_alpha
414         // <alpha xmlns:android="http://schemas.android.com/apk/res/android"
415         //      android:interpolator="@android:anim/accelerate_interpolator"
416         //      android:fromAlpha="0.1"
417         //      android:toAlpha="0.9"
418         //      android:duration="1000" />
419         final Animation anim = AnimationUtils.loadAnimation(mActivity, R.anim.accelerate_alpha);
420         assertFalse(anim.hasStarted());
421 
422         // we have to PollingCheck the animation status on test thread,
423         // it cannot be done on UI thread, so we invoke runOnUiThread method here.
424         mActivityRule.runOnUiThread(() -> animWindow.startAnimation(anim));
425 
426         // check whether animation has started
427         PollingCheck.waitFor(anim::hasStarted);
428 
429         // check transformation objects that is provided by the
430         // caller and will be filled in by the animation.
431         Transformation transformation = new Transformation();
432         long startTime = anim.getStartTime();
433         assertTrue(anim.getTransformation(startTime, transformation));
434         float alpha1 = transformation.getAlpha();
435         assertEquals(0.1f, alpha1, COMPARISON_DELTA);
436 
437         assertTrue(anim.getTransformation(startTime + 250, transformation));
438         float alpha2 = transformation.getAlpha();
439 
440         assertTrue(anim.getTransformation(startTime + 500, transformation));
441         float alpha3 = transformation.getAlpha();
442 
443         assertTrue(anim.getTransformation(startTime + 750, transformation));
444         float alpha4 = transformation.getAlpha();
445 
446         // wait for animation has ended.
447         // timeout is larger than duration, in case the system is sluggish
448         PollingCheck.waitFor(2000, anim::hasEnded);
449 
450         assertFalse(anim.getTransformation(startTime + 1000, transformation));
451         float alpha5 = transformation.getAlpha();
452         assertEquals(0.9f, alpha5, COMPARISON_DELTA);
453 
454         // check decelerating delta alpha
455         float delta1 = alpha2 - alpha1;
456         float delta2 = alpha3 - alpha2;
457         float delta3 = alpha4 - alpha3;
458         float delta4 = alpha5 - alpha4;
459         assertTrue(delta1 < delta2);
460         assertTrue(delta2 < delta3);
461         assertTrue(delta3 < delta4);
462     }
463 
464     @Test
465     public void testAccessZAdjustment() {
466         // check default zAdjustment
467         Animation animation = new Animation() {
468         };
469         assertEquals(Animation.ZORDER_NORMAL, animation.getZAdjustment());
470 
471         animation = AnimationUtils.loadAnimation(mActivity, R.anim.accelerate_alpha);
472         assertEquals(Animation.ZORDER_NORMAL, animation.getZAdjustment());
473 
474         animation.setZAdjustment(Animation.ZORDER_TOP);
475         assertEquals(Animation.ZORDER_TOP, animation.getZAdjustment());
476 
477         animation.setZAdjustment(Animation.ZORDER_BOTTOM);
478         assertEquals(Animation.ZORDER_BOTTOM, animation.getZAdjustment());
479     }
480 
481     @Test
482     public void testInitialize() {
483         Animation animation = new Animation() {
484         };
485 
486         assertFalse(animation.isInitialized());
487         animation.initialize(320, 480, 320, 480);
488         assertTrue(animation.isInitialized());
489     }
490 
491     @Test
492     public void testResolveSize() {
493         MyAnimation myAnimation = new MyAnimation();
494 
495         assertEquals(1.0f, myAnimation.resolveSize(Animation.ABSOLUTE, 1.0f, 0, 0),
496                 COMPARISON_DELTA);
497         assertEquals(2.0f, myAnimation.resolveSize(Animation.ABSOLUTE, 2.0f, 0, 0),
498                 COMPARISON_DELTA);
499 
500         assertEquals(6.0f, myAnimation.resolveSize(Animation.RELATIVE_TO_SELF, 3.0f, 2, 0),
501                 COMPARISON_DELTA);
502         assertEquals(9.0f, myAnimation.resolveSize(Animation.RELATIVE_TO_SELF, 3.0f, 3, 0),
503                 COMPARISON_DELTA);
504 
505         assertEquals(18.0f, myAnimation.resolveSize(Animation.RELATIVE_TO_PARENT, 3.0f, 0, 6),
506                 COMPARISON_DELTA);
507         assertEquals(12.0f, myAnimation.resolveSize(Animation.RELATIVE_TO_PARENT, 3.0f, 0, 4),
508                 COMPARISON_DELTA);
509 
510         int unknownType = 7;
511         assertEquals(8.0f, myAnimation.resolveSize(unknownType, 8.0f, 3, 4), COMPARISON_DELTA);
512         assertEquals(10.0f, myAnimation.resolveSize(unknownType, 10.0f, 3, 4), COMPARISON_DELTA);
513     }
514 
515     @Test
516     public void testRestrictDuration() {
517         Animation animation = new Animation() {
518         };
519 
520         animation.setStartOffset(1000);
521         animation.restrictDuration(500);
522         assertEquals(500, animation.getStartOffset());
523         assertEquals(0, animation.getDuration());
524         assertEquals(0, animation.getRepeatCount());
525 
526         animation.setStartOffset(1000);
527         animation.setDuration(1000);
528         animation.restrictDuration(1500);
529         assertEquals(500, animation.getDuration());
530 
531         animation.setStartOffset(1000);
532         animation.setDuration(1000);
533         animation.setRepeatCount(3);
534         animation.restrictDuration(4500);
535         assertEquals(1, animation.getRepeatCount());
536     }
537 
538     @Test
539     public void testScaleCurrentDuration() {
540         Animation animation = new Animation() {
541         };
542 
543         animation.setDuration(10);
544         animation.scaleCurrentDuration(0);
545         assertEquals(0, animation.getDuration());
546 
547         animation.setDuration(10);
548         animation.scaleCurrentDuration(2);
549         assertEquals(20, animation.getDuration());
550 
551         animation.setDuration(10);
552         animation.scaleCurrentDuration(-1);
553         assertEquals(-10, animation.getDuration());
554     }
555 
556     @LargeTest
557     @Test
558     public void testSetAnimationListener() throws Throwable {
559         final View animWindow = mActivity.findViewById(R.id.anim_window);
560 
561         // XML file of R.anim.accelerate_alpha
562         // <alpha xmlns:android="http://schemas.android.com/apk/res/android"
563         //      android:interpolator="@android:anim/accelerate_interpolator"
564         //      android:fromAlpha="0.1"
565         //      android:toAlpha="0.9"
566         //      android:duration="1000" />
567         final Animation anim = AnimationUtils.loadAnimation(mActivity, R.anim.accelerate_alpha);
568         final AnimationListener listener = mock(AnimationListener.class);
569         anim.setAnimationListener(listener);
570         verifyZeroInteractions(listener);
571 
572         AnimationTestUtils.assertRunAnimation(mInstrumentation, mActivityRule, animWindow, anim);
573         verify(listener, times(1)).onAnimationStart(anim);
574         verify(listener, times(1)).onAnimationEnd(anim);
575         verify(listener, never()).onAnimationRepeat(anim);
576 
577         reset(listener);
578         anim.setRepeatCount(2);
579         anim.setRepeatMode(Animation.REVERSE);
580 
581         AnimationTestUtils.assertRunAnimation(mInstrumentation, mActivityRule, animWindow, anim,
582                 3000);
583         verify(listener, times(1)).onAnimationStart(anim);
584         verify(listener, times(2)).onAnimationRepeat(anim);
585         verify(listener, times(1)).onAnimationEnd(anim);
586 
587         reset(listener);
588         // onAnimationEnd will not be invoked and animation should not end
589         anim.setRepeatCount(Animation.INFINITE);
590 
591         mActivityRule.runOnUiThread(() -> animWindow.startAnimation(anim));
592         // Verify that our animation doesn't call listener's onAnimationEnd even after a long
593         // period of time. We just sleep and then verify what's happened with the listener.
594         SystemClock.sleep(4 * ACCELERATE_ALPHA_DURATION);
595 
596         verify(listener, times(1)).onAnimationStart(anim);
597         verify(listener, atLeastOnce()).onAnimationRepeat(anim);
598         verify(listener, never()).onAnimationEnd(anim);
599     }
600 
601     @Test
602     public void testStart() {
603         Animation animation = AnimationUtils.loadAnimation(mActivity, R.anim.accelerate_alpha);
604         animation.setStartTime(0);
605 
606         animation.start();
607         assertEquals(Animation.START_ON_FIRST_FRAME, animation.getStartTime());
608     }
609 
610     @Test
611     public void testStartNow() {
612         Animation animation = AnimationUtils.loadAnimation(mActivity, R.anim.accelerate_alpha);
613         animation.setStartTime(0);
614 
615         long currentTime = AnimationUtils.currentAnimationTimeMillis();
616         animation.startNow();
617         assertEquals(currentTime, animation.getStartTime(), 100);
618     }
619 
620     @Test
621     public void testWillChangeBounds() {
622         Animation animation = new Animation() {
623         };
624 
625         assertTrue(animation.willChangeBounds());
626     }
627 
628     @Test
629     public void testWillChangeTransformationMatrix() {
630         Animation animation = new Animation() {
631         };
632 
633         assertTrue(animation.willChangeTransformationMatrix());
634     }
635 
636     @Test
637     public void testClone() throws CloneNotSupportedException {
638         MyAnimation myAnimation = new MyAnimation();
639         myAnimation.setDuration(3000);
640         myAnimation.setFillAfter(true);
641         myAnimation.setFillBefore(false);
642         myAnimation.setFillEnabled(true);
643         myAnimation.setStartTime(1000);
644         myAnimation.setRepeatCount(10);
645         myAnimation.setRepeatMode(Animation.REVERSE);
646 
647         Animation cloneAnimation = myAnimation.clone();
648         assertNotSame(myAnimation, cloneAnimation);
649         assertEquals(myAnimation.getDuration(), cloneAnimation.getDuration());
650         assertEquals(myAnimation.getFillAfter(), cloneAnimation.getFillAfter());
651         assertEquals(myAnimation.getFillBefore(), cloneAnimation.getFillBefore());
652         assertEquals(myAnimation.isFillEnabled(), cloneAnimation.isFillEnabled());
653         assertEquals(myAnimation.getStartOffset(), cloneAnimation.getStartOffset());
654         assertEquals(myAnimation.getRepeatCount(), cloneAnimation.getRepeatCount());
655         assertEquals(myAnimation.getRepeatMode(), cloneAnimation.getRepeatMode());
656     }
657 
658     @Test
659     public void testCancelImmediately() throws Throwable {
660         MyAnimation anim = new MyAnimation();
661         final CountDownLatch latch1 = new CountDownLatch(1);
662         runCanceledAnimation(anim, latch1, false, false);
663         assertTrue(latch1.await(CANCELATION_TIMEOUT, TimeUnit.MILLISECONDS));
664         assertFalse(anim.isStillAnimating());
665     }
666 
667     @Test
668     public void testRepeatingCancelImmediately() throws Throwable {
669         MyAnimation anim = new MyAnimation();
670         final CountDownLatch latch2 = new CountDownLatch(1);
671         runCanceledAnimation(anim, latch2, true, false);
672         assertTrue(latch2.await(CANCELATION_TIMEOUT, TimeUnit.MILLISECONDS));
673         assertFalse(anim.isStillAnimating());
674     }
675 
676     @Test
677     public void testCancelDelayed() throws Throwable {
678         MyAnimation anim = new MyAnimation();
679         final CountDownLatch latch3 = new CountDownLatch(1);
680         runCanceledAnimation(anim, latch3, false, true);
681         assertTrue(latch3.await(CANCELATION_TIMEOUT, TimeUnit.MILLISECONDS));
682         assertFalse(anim.isStillAnimating());
683     }
684 
685     @Test
686     public void testRepeatingCancelDelayed() throws Throwable {
687         MyAnimation anim = new MyAnimation();
688         final CountDownLatch latch4 = new CountDownLatch(1);
689         runCanceledAnimation(anim, latch4, true, true);
690         assertTrue(latch4.await(CANCELATION_TIMEOUT, TimeUnit.MILLISECONDS));
691         assertFalse(anim.isStillAnimating());
692     }
693 
694     private void runCanceledAnimation(final MyAnimation anim, final CountDownLatch latch,
695             final boolean repeating, final boolean delayed) throws Throwable {
696         // The idea behind this test is that canceling an Animation should result in
697         // it ending, which means not having its getTransformation() method called
698         // anymore. The trick is that cancel() will still allow one more frame to run,
699         // so we have to insert some delay between when we cancel and when we can check
700         // whether it is still animating.
701         final View view = mActivity.findViewById(R.id.anim_window);
702         mActivityRule.runOnUiThread(() -> {
703             anim.setDuration(delayed ? 150 : 100);
704             if (repeating) {
705                 anim.setRepeatCount(Animation.INFINITE);
706             }
707             view.startAnimation(anim);
708             if (!delayed) {
709                 anim.cancel();
710             } else {
711                 view.postDelayed(anim::cancel, 50);
712             }
713             view.postDelayed(() -> {
714                 anim.setStillAnimating(false);
715                 view.postDelayed(latch::countDown, 50);
716             }, delayed ? 100 : 50);
717         });
718     }
719 
720     private class MyAnimation extends Animation {
721         boolean mStillAnimating = false;
722 
723         @Override
ensureInterpolator()724         protected void ensureInterpolator() {
725             super.ensureInterpolator();
726         }
727 
728         @Override
resolveSize(int type, float value, int size, int parentSize)729         protected float resolveSize(int type, float value, int size, int parentSize) {
730             return super.resolveSize(type, value, size, parentSize);
731         }
732 
733         @Override
clone()734         protected Animation clone() throws CloneNotSupportedException {
735             return super.clone();
736         }
737 
setStillAnimating(boolean value)738         public void setStillAnimating(boolean value) {
739             mStillAnimating = value;
740         }
741 
isStillAnimating()742         public boolean isStillAnimating() {
743             return mStillAnimating;
744         }
745 
746         @Override
getTransformation(long currentTime, Transformation outTransformation)747         public boolean getTransformation(long currentTime, Transformation outTransformation) {
748             mStillAnimating = true;
749             return super.getTransformation(currentTime, outTransformation);
750         }
751     }
752 }
753