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