1 /*
2  * Copyright (C) 2023 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.animation;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertTrue;
21 
22 import android.util.PollingCheck;
23 import android.view.View;
24 
25 import androidx.test.ext.junit.rules.ActivityScenarioRule;
26 import androidx.test.filters.MediumTest;
27 
28 import com.android.frameworks.coretests.R;
29 
30 import org.junit.Before;
31 import org.junit.Rule;
32 import org.junit.Test;
33 
34 import java.util.ArrayList;
35 import java.util.concurrent.CountDownLatch;
36 import java.util.concurrent.TimeUnit;
37 
38 @MediumTest
39 public class AnimatorSetCallsTest {
40     @Rule
41     public final ActivityScenarioRule<AnimatorSetActivity> mRule =
42             new ActivityScenarioRule<>(AnimatorSetActivity.class);
43 
44     private AnimatorSetActivity mActivity;
45     private AnimatorSet mSet1;
46     private AnimatorSet mSet2;
47     private ObjectAnimator mAnimator;
48     private CountListener mListener1;
49     private CountListener mListener2;
50     private CountListener mListener3;
51 
52     @Before
setUp()53     public void setUp() throws Exception {
54         mRule.getScenario().onActivity((activity) -> {
55             mActivity = activity;
56             View square = mActivity.findViewById(R.id.square1);
57 
58             mSet1 = new AnimatorSet();
59             mListener1 = new CountListener();
60             mSet1.addListener(mListener1);
61             mSet1.addPauseListener(mListener1);
62 
63             mSet2 = new AnimatorSet();
64             mListener2 = new CountListener();
65             mSet2.addListener(mListener2);
66             mSet2.addPauseListener(mListener2);
67 
68             mAnimator = ObjectAnimator.ofFloat(square, "translationX", 0f, 100f);
69             mListener3 = new CountListener();
70             mAnimator.addListener(mListener3);
71             mAnimator.addPauseListener(mListener3);
72             mAnimator.setDuration(1);
73 
74             mSet2.play(mAnimator);
75             mSet1.play(mSet2);
76         });
77     }
78 
79     @Test
startEndCalledOnChildren()80     public void startEndCalledOnChildren() {
81         mRule.getScenario().onActivity((a) -> mSet1.start());
82         waitForOnUiThread(() -> mListener1.endForward > 0);
83 
84         // only startForward and endForward should have been called once
85         mListener1.assertValues(
86                 1, 0, 1, 0, 0, 0, 0, 0
87         );
88         mListener2.assertValues(
89                 1, 0, 1, 0, 0, 0, 0, 0
90         );
91         mListener3.assertValues(
92                 1, 0, 1, 0, 0, 0, 0, 0
93         );
94     }
95 
96     @Test
cancelCalledOnChildren()97     public void cancelCalledOnChildren() {
98         mRule.getScenario().onActivity((a) -> {
99             mSet1.start();
100             mSet1.cancel();
101         });
102         waitForOnUiThread(() -> mListener1.endForward > 0);
103 
104         // only startForward and endForward should have been called once
105         mListener1.assertValues(
106                 1, 0, 1, 0, 1, 0, 0, 0
107         );
108         mListener2.assertValues(
109                 1, 0, 1, 0, 1, 0, 0, 0
110         );
111         mListener3.assertValues(
112                 1, 0, 1, 0, 1, 0, 0, 0
113         );
114     }
115 
116     @Test
startEndReversedCalledOnChildren()117     public void startEndReversedCalledOnChildren() {
118         mRule.getScenario().onActivity((a) -> mSet1.reverse());
119         waitForOnUiThread(() -> mListener1.endReverse > 0);
120 
121         // only startForward and endForward should have been called once
122         mListener1.assertValues(
123                 0, 1, 0, 1, 0, 0, 0, 0
124         );
125         mListener2.assertValues(
126                 0, 1, 0, 1, 0, 0, 0, 0
127         );
128         mListener3.assertValues(
129                 0, 1, 0, 1, 0, 0, 0, 0
130         );
131     }
132 
133     @Test
pauseResumeCalledOnChildren()134     public void pauseResumeCalledOnChildren() {
135         mRule.getScenario().onActivity((a) -> {
136             mSet1.start();
137             mSet1.pause();
138         });
139         waitForOnUiThread(() -> mListener1.pause > 0);
140 
141         // only startForward and pause should have been called once
142         mListener1.assertValues(
143                 1, 0, 0, 0, 0, 0, 1, 0
144         );
145         mListener2.assertValues(
146                 1, 0, 0, 0, 0, 0, 1, 0
147         );
148         mListener3.assertValues(
149                 1, 0, 0, 0, 0, 0, 1, 0
150         );
151 
152         mRule.getScenario().onActivity((a) -> mSet1.resume());
153         waitForOnUiThread(() -> mListener1.endForward > 0);
154 
155         // resume and endForward should have been called once
156         mListener1.assertValues(
157                 1, 0, 1, 0, 0, 0, 1, 1
158         );
159         mListener2.assertValues(
160                 1, 0, 1, 0, 0, 0, 1, 1
161         );
162         mListener3.assertValues(
163                 1, 0, 1, 0, 0, 0, 1, 1
164         );
165     }
166 
167     @Test
updateOnlyWhileChangingValues()168     public void updateOnlyWhileChangingValues() {
169         ArrayList<Float> updateValues = new ArrayList<>();
170         mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
171             @Override
172             public void onAnimationUpdate(ValueAnimator animation) {
173                 updateValues.add((Float) animation.getAnimatedValue());
174             }
175         });
176 
177         mSet1.setCurrentPlayTime(0);
178 
179         assertEquals(1, updateValues.size());
180         assertEquals(0f, updateValues.get(0), 0f);
181     }
182 
183     @Test
updateOnlyWhileRunning()184     public void updateOnlyWhileRunning() {
185         ArrayList<Float> updateValues = new ArrayList<>();
186         mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
187             @Override
188             public void onAnimationUpdate(ValueAnimator animation) {
189                 updateValues.add((Float) animation.getAnimatedValue());
190             }
191         });
192 
193         mRule.getScenario().onActivity((a) -> {
194             mSet1.start();
195         });
196 
197         waitForOnUiThread(() -> mListener1.endForward > 0);
198 
199         // the duration is only 1ms, so there should only be two values, 0 and 100.
200         assertEquals(0f, updateValues.get(0), 0f);
201         assertEquals(100f, updateValues.get(updateValues.size() - 1), 0f);
202 
203         // now check all the values in the middle, which can never go from 100->0.
204         boolean isAtEnd = false;
205         for (int i = 1; i < updateValues.size() - 1; i++) {
206             float actual = updateValues.get(i);
207             if (actual == 100f) {
208                 isAtEnd = true;
209             }
210             float expected = isAtEnd ? 100f : 0f;
211             assertEquals(expected, actual, 0f);
212         }
213     }
214 
215     @Test
pauseResumeSeekingAnimators()216     public void pauseResumeSeekingAnimators() {
217         ValueAnimator animator2 = ValueAnimator.ofFloat(0f, 1f);
218         mSet2.play(animator2).after(mAnimator);
219         mSet2.setStartDelay(100);
220         mSet1.setStartDelay(100);
221         mAnimator.setDuration(100);
222 
223         mActivity.runOnUiThread(() -> {
224             mSet1.setCurrentPlayTime(0);
225             mSet1.pause();
226 
227             // only startForward and pause should have been called once
228             mListener1.assertValues(
229                     1, 0, 0, 0, 0, 0, 1, 0
230             );
231             mListener2.assertValues(
232                     0, 0, 0, 0, 0, 0, 0, 0
233             );
234             mListener3.assertValues(
235                     0, 0, 0, 0, 0, 0, 0, 0
236             );
237 
238             mSet1.resume();
239             mListener1.assertValues(
240                     1, 0, 0, 0, 0, 0, 1, 1
241             );
242             mListener2.assertValues(
243                     0, 0, 0, 0, 0, 0, 0, 0
244             );
245             mListener3.assertValues(
246                     0, 0, 0, 0, 0, 0, 0, 0
247             );
248 
249             mSet1.setCurrentPlayTime(200);
250 
251             // resume and endForward should have been called once
252             mListener1.assertValues(
253                     1, 0, 0, 0, 0, 0, 1, 1
254             );
255             mListener2.assertValues(
256                     1, 0, 0, 0, 0, 0, 0, 0
257             );
258             mListener3.assertValues(
259                     1, 0, 0, 0, 0, 0, 0, 0
260             );
261 
262             mSet1.pause();
263             mListener1.assertValues(
264                     1, 0, 0, 0, 0, 0, 2, 1
265             );
266             mListener2.assertValues(
267                     1, 0, 0, 0, 0, 0, 1, 0
268             );
269             mListener3.assertValues(
270                     1, 0, 0, 0, 0, 0, 1, 0
271             );
272             mSet1.resume();
273             mListener1.assertValues(
274                     1, 0, 0, 0, 0, 0, 2, 2
275             );
276             mListener2.assertValues(
277                     1, 0, 0, 0, 0, 0, 1, 1
278             );
279             mListener3.assertValues(
280                     1, 0, 0, 0, 0, 0, 1, 1
281             );
282 
283             // now go to animator2
284             mSet1.setCurrentPlayTime(400);
285             mSet1.pause();
286             mSet1.resume();
287             mListener1.assertValues(
288                     1, 0, 0, 0, 0, 0, 3, 3
289             );
290             mListener2.assertValues(
291                     1, 0, 0, 0, 0, 0, 2, 2
292             );
293             mListener3.assertValues(
294                     1, 0, 1, 0, 0, 0, 1, 1
295             );
296 
297             // now go back to mAnimator
298             mSet1.setCurrentPlayTime(250);
299             mSet1.pause();
300             mSet1.resume();
301             mListener1.assertValues(
302                     1, 0, 0, 0, 0, 0, 4, 4
303             );
304             mListener2.assertValues(
305                     1, 0, 0, 0, 0, 0, 3, 3
306             );
307             mListener3.assertValues(
308                     1, 1, 1, 0, 0, 0, 2, 2
309             );
310 
311             // now go back to before mSet2 was being run
312             mSet1.setCurrentPlayTime(1);
313             mSet1.pause();
314             mSet1.resume();
315             mListener1.assertValues(
316                     1, 0, 0, 0, 0, 0, 5, 5
317             );
318             mListener2.assertValues(
319                     1, 0, 0, 1, 0, 0, 3, 3
320             );
321             mListener3.assertValues(
322                     1, 1, 1, 1, 0, 0, 2, 2
323             );
324         });
325     }
326 
327     @Test
endInCancel()328     public void endInCancel() throws Throwable {
329         AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
330             @Override
331             public void onAnimationCancel(Animator animation) {
332                 mSet1.end();
333             }
334         };
335         mSet1.addListener(listener);
336         mActivity.runOnUiThread(() -> {
337             mSet1.start();
338             mSet1.cancel();
339             // Should go to the end value
340             View square = mActivity.findViewById(R.id.square1);
341             assertEquals(100f, square.getTranslationX(), 0.001f);
342         });
343     }
344 
345     @Test
reentrantStart()346     public void reentrantStart() throws Throwable {
347         CountDownLatch latch = new CountDownLatch(3);
348         AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
349             @Override
350             public void onAnimationStart(Animator animation, boolean isReverse) {
351                 mSet1.start();
352                 latch.countDown();
353             }
354         };
355         mSet1.addListener(listener);
356         mSet2.addListener(listener);
357         mAnimator.addListener(listener);
358         mActivity.runOnUiThread(() -> mSet1.start());
359         assertTrue(latch.await(1, TimeUnit.SECONDS));
360 
361         // Make sure that the UI thread hasn't been destroyed by a stack overflow...
362         mActivity.runOnUiThread(() -> {});
363     }
364 
365     @Test
reentrantEnd()366     public void reentrantEnd() throws Throwable {
367         CountDownLatch latch = new CountDownLatch(3);
368         AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
369             @Override
370             public void onAnimationEnd(Animator animation, boolean isReverse) {
371                 mSet1.end();
372                 latch.countDown();
373             }
374         };
375         mSet1.addListener(listener);
376         mSet2.addListener(listener);
377         mAnimator.addListener(listener);
378         mActivity.runOnUiThread(() -> {
379             mSet1.start();
380             mSet1.end();
381         });
382         assertTrue(latch.await(1, TimeUnit.SECONDS));
383 
384         // Make sure that the UI thread hasn't been destroyed by a stack overflow...
385         mActivity.runOnUiThread(() -> {});
386     }
387 
388     @Test
reentrantPause()389     public void reentrantPause() throws Throwable {
390         CountDownLatch latch = new CountDownLatch(3);
391         AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
392             @Override
393             public void onAnimationPause(Animator animation) {
394                 mSet1.pause();
395                 latch.countDown();
396             }
397         };
398         mSet1.addPauseListener(listener);
399         mSet2.addPauseListener(listener);
400         mAnimator.addPauseListener(listener);
401         mActivity.runOnUiThread(() -> {
402             mSet1.start();
403             mSet1.pause();
404         });
405         assertTrue(latch.await(1, TimeUnit.SECONDS));
406 
407         // Make sure that the UI thread hasn't been destroyed by a stack overflow...
408         mActivity.runOnUiThread(() -> {});
409     }
410 
411     @Test
reentrantResume()412     public void reentrantResume() throws Throwable {
413         CountDownLatch latch = new CountDownLatch(3);
414         AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
415             @Override
416             public void onAnimationResume(Animator animation) {
417                 mSet1.resume();
418                 latch.countDown();
419             }
420         };
421         mSet1.addPauseListener(listener);
422         mSet2.addPauseListener(listener);
423         mAnimator.addPauseListener(listener);
424         mActivity.runOnUiThread(() -> {
425             mSet1.start();
426             mSet1.pause();
427             mSet1.resume();
428         });
429         assertTrue(latch.await(1, TimeUnit.SECONDS));
430 
431         // Make sure that the UI thread hasn't been destroyed by a stack overflow...
432         mActivity.runOnUiThread(() -> {});
433     }
434 
waitForOnUiThread(PollingCheck.PollingCheckCondition condition)435     private void waitForOnUiThread(PollingCheck.PollingCheckCondition condition) {
436         final boolean[] value = new boolean[1];
437         PollingCheck.waitFor(() -> {
438             mActivity.runOnUiThread(() -> value[0] = condition.canProceed());
439             return value[0];
440         });
441     }
442 
443     private static class CountListener implements Animator.AnimatorListener,
444             Animator.AnimatorPauseListener {
445         public int startNoParam;
446         public int endNoParam;
447         public int startReverse;
448         public int startForward;
449         public int endForward;
450         public int endReverse;
451         public int cancel;
452         public int repeat;
453         public int pause;
454         public int resume;
455 
assertValues( int startForward, int startReverse, int endForward, int endReverse, int cancel, int repeat, int pause, int resume )456         public void assertValues(
457                 int startForward,
458                 int startReverse,
459                 int endForward,
460                 int endReverse,
461                 int cancel,
462                 int repeat,
463                 int pause,
464                 int resume
465         ) {
466             assertEquals("onAnimationStart() without direction", 0, startNoParam);
467             assertEquals("onAnimationEnd() without direction", 0, endNoParam);
468             assertEquals("onAnimationStart(forward)", startForward, this.startForward);
469             assertEquals("onAnimationStart(reverse)", startReverse, this.startReverse);
470             assertEquals("onAnimationEnd(forward)", endForward, this.endForward);
471             assertEquals("onAnimationEnd(reverse)", endReverse, this.endReverse);
472             assertEquals("onAnimationCancel()", cancel, this.cancel);
473             assertEquals("onAnimationRepeat()", repeat, this.repeat);
474             assertEquals("onAnimationPause()", pause, this.pause);
475             assertEquals("onAnimationResume()", resume, this.resume);
476         }
477 
478         @Override
onAnimationStart(Animator animation, boolean isReverse)479         public void onAnimationStart(Animator animation, boolean isReverse) {
480             if (isReverse) {
481                 startReverse++;
482             } else {
483                 startForward++;
484             }
485         }
486 
487         @Override
onAnimationEnd(Animator animation, boolean isReverse)488         public void onAnimationEnd(Animator animation, boolean isReverse) {
489             if (isReverse) {
490                 endReverse++;
491             } else {
492                 endForward++;
493             }
494         }
495 
496         @Override
onAnimationStart(Animator animation)497         public void onAnimationStart(Animator animation) {
498             startNoParam++;
499         }
500 
501         @Override
onAnimationEnd(Animator animation)502         public void onAnimationEnd(Animator animation) {
503             endNoParam++;
504         }
505 
506         @Override
onAnimationCancel(Animator animation)507         public void onAnimationCancel(Animator animation) {
508             cancel++;
509         }
510 
511         @Override
onAnimationRepeat(Animator animation)512         public void onAnimationRepeat(Animator animation) {
513             repeat++;
514         }
515 
516         @Override
onAnimationPause(Animator animation)517         public void onAnimationPause(Animator animation) {
518             pause++;
519         }
520 
521         @Override
onAnimationResume(Animator animation)522         public void onAnimationResume(Animator animation) {
523             resume++;
524         }
525     }
526 }
527