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 */ 16 package android.animation; 17 18 import android.os.Handler; 19 import android.test.ActivityInstrumentationTestCase2; 20 import android.test.UiThreadTest; 21 import android.test.suitebuilder.annotation.MediumTest; 22 import android.test.suitebuilder.annotation.SmallTest; 23 24 import java.util.concurrent.TimeUnit; 25 import java.util.concurrent.TimeoutException; 26 27 /** 28 * Tests for the various lifecycle events of Animators. This abstract class is subclassed by 29 * concrete implementations that provide the actual Animator objects being tested. All of the 30 * testing mechanisms are in this class; the subclasses are only responsible for providing 31 * the mAnimator object. 32 * 33 * This test is more complicated than a typical synchronous test because much of the functionality 34 * must happen on the UI thread. Some tests do this by using the UiThreadTest annotation to 35 * automatically run the whole test on that thread. Other tests must run on the UI thread and also 36 * wait for some later event to occur before ending. These tests use a combination of an 37 * AbstractFuture mechanism and a delayed action to release that Future later. 38 */ 39 public abstract class EventsTest 40 extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> { 41 42 protected static final int ANIM_DURATION = 400; 43 protected static final int ANIM_DELAY = 100; 44 protected static final int ANIM_MID_DURATION = ANIM_DURATION / 2; 45 protected static final int ANIM_MID_DELAY = ANIM_DELAY / 2; 46 protected static final int ANIM_PAUSE_DURATION = ANIM_DELAY; 47 protected static final int ANIM_PAUSE_DELAY = ANIM_DELAY / 2; 48 protected static final int FUTURE_RELEASE_DELAY = 50; 49 protected static final int ANIM_FULL_DURATION_SLOP = 100; 50 51 private boolean mStarted; // tracks whether we've received the onAnimationStart() callback 52 protected boolean mRunning; // tracks whether we've started the animator 53 private boolean mCanceled; // tracks whether we've canceled the animator 54 protected Animator.AnimatorListener mFutureListener; // mechanism for delaying end of the test 55 protected FutureWaiter mFuture; // Mechanism for waiting for the UI test to complete 56 private Animator.AnimatorListener mListener; // Listener that handles/tests the events 57 58 protected Animator mAnimator; // The animator used in the tests. Must be set in subclass 59 // setup() method prior to calling the superclass setup() 60 61 /** 62 * Cancels the given animator. Used to delay cancellation until some later time (after the 63 * animator has started playing). 64 */ 65 protected static class Canceler implements Runnable { 66 Animator mAnim; 67 FutureWaiter mFuture; 68 public Canceler(Animator anim, FutureWaiter future) { 69 mAnim = anim; 70 mFuture = future; 71 } 72 @Override 73 public void run() { 74 try { 75 mAnim.cancel(); 76 } catch (junit.framework.AssertionFailedError e) { 77 mFuture.setException(new RuntimeException(e)); 78 } 79 } 80 }; 81 82 /** 83 * Timeout length, based on when the animation should reasonably be complete. 84 */ 85 protected long getTimeout() { 86 return ANIM_DURATION + ANIM_DELAY + FUTURE_RELEASE_DELAY; 87 } 88 89 /** 90 * Ends the given animator. Used to delay ending until some later time (after the 91 * animator has started playing). 92 */ 93 static class Ender implements Runnable { 94 Animator mAnim; 95 FutureWaiter mFuture; 96 public Ender(Animator anim, FutureWaiter future) { 97 mAnim = anim; 98 mFuture = future; 99 } 100 @Override 101 public void run() { 102 try { 103 mAnim.end(); 104 } catch (junit.framework.AssertionFailedError e) { 105 mFuture.setException(new RuntimeException(e)); 106 } 107 } 108 }; 109 110 /** 111 * Pauses the given animator. Used to delay pausing until some later time (after the 112 * animator has started playing). 113 */ 114 static class Pauser implements Runnable { 115 Animator mAnim; 116 FutureWaiter mFuture; 117 public Pauser(Animator anim, FutureWaiter future) { 118 mAnim = anim; 119 mFuture = future; 120 } 121 @Override 122 public void run() { 123 try { 124 mAnim.pause(); 125 } catch (junit.framework.AssertionFailedError e) { 126 mFuture.setException(new RuntimeException(e)); 127 } 128 } 129 }; 130 131 /** 132 * Resumes the given animator. Used to delay resuming until some later time (after the 133 * animator has paused for some duration). 134 */ 135 static class Resumer implements Runnable { 136 Animator mAnim; 137 FutureWaiter mFuture; 138 public Resumer(Animator anim, FutureWaiter future) { 139 mAnim = anim; 140 mFuture = future; 141 } 142 @Override 143 public void run() { 144 try { 145 mAnim.resume(); 146 } catch (junit.framework.AssertionFailedError e) { 147 mFuture.setException(new RuntimeException(e)); 148 } 149 } 150 }; 151 152 /** 153 * Releases the given Future object when the listener's end() event is called. Specifically, 154 * it releases it after some further delay, to give the test time to do other things right 155 * after an animation ends. 156 */ 157 protected static class FutureReleaseListener extends AnimatorListenerAdapter { 158 FutureWaiter mFuture; 159 160 public FutureReleaseListener(FutureWaiter future) { 161 mFuture = future; 162 } 163 164 /** 165 * Variant constructor that auto-releases the FutureWaiter after the specified timeout. 166 * @param future 167 * @param timeout 168 */ 169 public FutureReleaseListener(FutureWaiter future, long timeout) { 170 mFuture = future; 171 Handler handler = new Handler(); 172 handler.postDelayed(new Runnable() { 173 @Override 174 public void run() { 175 mFuture.release(); 176 } 177 }, timeout); 178 } 179 180 @Override 181 public void onAnimationEnd(Animator animation) { 182 Handler handler = new Handler(); 183 handler.postDelayed(new Runnable() { 184 @Override 185 public void run() { 186 mFuture.release(); 187 } 188 }, FUTURE_RELEASE_DELAY); 189 } 190 }; 191 192 public EventsTest() { 193 super(BasicAnimatorActivity.class); 194 } 195 196 /** 197 * Sets up the fields used by each test. Subclasses must override this method to create 198 * the protected mAnimator object used in all tests. Overrides must create that animator 199 * and then call super.setup(), where further properties are set on that animator. 200 * @throws Exception 201 */ 202 @Override 203 public void setUp() throws Exception { 204 super.setUp(); 205 206 // mListener is the main testing mechanism of this file. The asserts of each test 207 // are embedded in the listener callbacks that it implements. 208 mListener = new AnimatorListenerAdapter() { 209 @Override 210 public void onAnimationStart(Animator animation) { 211 // This should only be called on an animation that has not yet been started 212 assertFalse(mStarted); 213 assertTrue(mRunning); 214 mStarted = true; 215 } 216 217 @Override 218 public void onAnimationCancel(Animator animation) { 219 // This should only be called on an animation that has been started and not 220 // yet canceled or ended 221 assertFalse(mCanceled); 222 assertTrue(mRunning || mStarted); 223 mCanceled = true; 224 } 225 226 @Override 227 public void onAnimationEnd(Animator animation) { 228 // This should only be called on an animation that has been started and not 229 // yet ended 230 assertTrue(mRunning || mStarted); 231 mRunning = false; 232 mStarted = false; 233 super.onAnimationEnd(animation); 234 } 235 }; 236 237 mAnimator.addListener(mListener); 238 mAnimator.setDuration(ANIM_DURATION); 239 240 mFuture = new FutureWaiter(); 241 242 mRunning = false; 243 mCanceled = false; 244 mStarted = false; 245 } 246 247 /** 248 * Verify that calling cancel on an unstarted animator does nothing. 249 */ 250 @UiThreadTest 251 @SmallTest 252 public void testCancel() throws Exception { 253 mAnimator.cancel(); 254 } 255 256 /** 257 * Verify that calling end on an unstarted animator starts/ends an animator. 258 */ 259 @UiThreadTest 260 @SmallTest 261 public void testEnd() throws Exception { 262 mRunning = true; // end() implicitly starts an unstarted animator 263 mAnimator.end(); 264 } 265 266 /** 267 * Verify that calling cancel on a started animator does the right thing. 268 */ 269 @UiThreadTest 270 @SmallTest 271 public void testStartCancel() throws Exception { 272 mFutureListener = new FutureReleaseListener(mFuture); 273 getActivity().runOnUiThread(new Runnable() { 274 @Override 275 public void run() { 276 try { 277 mRunning = true; 278 mAnimator.start(); 279 mAnimator.cancel(); 280 mFuture.release(); 281 } catch (junit.framework.AssertionFailedError e) { 282 mFuture.setException(new RuntimeException(e)); 283 } 284 } 285 }); 286 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 287 } 288 289 /** 290 * Verify that calling end on a started animator does the right thing. 291 */ 292 @UiThreadTest 293 @SmallTest 294 public void testStartEnd() throws Exception { 295 mFutureListener = new FutureReleaseListener(mFuture); 296 getActivity().runOnUiThread(new Runnable() { 297 @Override 298 public void run() { 299 try { 300 mRunning = true; 301 mAnimator.start(); 302 mAnimator.end(); 303 mFuture.release(); 304 } catch (junit.framework.AssertionFailedError e) { 305 mFuture.setException(new RuntimeException(e)); 306 } 307 } 308 }); 309 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 310 } 311 312 /** 313 * Same as testStartCancel, but with a startDelayed animator 314 */ 315 @SmallTest 316 public void testStartDelayedCancel() throws Exception { 317 mFutureListener = new FutureReleaseListener(mFuture); 318 mAnimator.setStartDelay(ANIM_DELAY); 319 getActivity().runOnUiThread(new Runnable() { 320 @Override 321 public void run() { 322 try { 323 mRunning = true; 324 mAnimator.start(); 325 mAnimator.cancel(); 326 mFuture.release(); 327 } catch (junit.framework.AssertionFailedError e) { 328 mFuture.setException(new RuntimeException(e)); 329 } 330 } 331 }); 332 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 333 } 334 335 /** 336 * Same as testStartEnd, but with a startDelayed animator 337 */ 338 @SmallTest 339 public void testStartDelayedEnd() throws Exception { 340 mFutureListener = new FutureReleaseListener(mFuture); 341 mAnimator.setStartDelay(ANIM_DELAY); 342 getActivity().runOnUiThread(new Runnable() { 343 @Override 344 public void run() { 345 try { 346 mRunning = true; 347 mAnimator.start(); 348 mAnimator.end(); 349 mFuture.release(); 350 } catch (junit.framework.AssertionFailedError e) { 351 mFuture.setException(new RuntimeException(e)); 352 } 353 } 354 }); 355 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 356 } 357 358 /** 359 * Verify that canceling an animator that is playing does the right thing. 360 */ 361 @MediumTest 362 public void testPlayingCancel() throws Exception { 363 mFutureListener = new FutureReleaseListener(mFuture); 364 getActivity().runOnUiThread(new Runnable() { 365 @Override 366 public void run() { 367 try { 368 Handler handler = new Handler(); 369 mAnimator.addListener(mFutureListener); 370 mRunning = true; 371 mAnimator.start(); 372 handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION); 373 } catch (junit.framework.AssertionFailedError e) { 374 mFuture.setException(new RuntimeException(e)); 375 } 376 } 377 }); 378 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 379 } 380 381 /** 382 * Verify that ending an animator that is playing does the right thing. 383 */ 384 @MediumTest 385 public void testPlayingEnd() throws Exception { 386 mFutureListener = new FutureReleaseListener(mFuture); 387 getActivity().runOnUiThread(new Runnable() { 388 @Override 389 public void run() { 390 try { 391 Handler handler = new Handler(); 392 mAnimator.addListener(mFutureListener); 393 mRunning = true; 394 mAnimator.start(); 395 handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DURATION); 396 } catch (junit.framework.AssertionFailedError e) { 397 mFuture.setException(new RuntimeException(e)); 398 } 399 } 400 }); 401 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 402 } 403 404 /** 405 * Same as testPlayingCancel, but with a startDelayed animator 406 */ 407 @MediumTest 408 public void testPlayingDelayedCancel() throws Exception { 409 mAnimator.setStartDelay(ANIM_DELAY); 410 mFutureListener = new FutureReleaseListener(mFuture); 411 getActivity().runOnUiThread(new Runnable() { 412 @Override 413 public void run() { 414 try { 415 Handler handler = new Handler(); 416 mAnimator.addListener(mFutureListener); 417 mRunning = true; 418 mAnimator.start(); 419 handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION); 420 } catch (junit.framework.AssertionFailedError e) { 421 mFuture.setException(new RuntimeException(e)); 422 } 423 } 424 }); 425 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 426 } 427 428 /** 429 * Same as testPlayingEnd, but with a startDelayed animator 430 */ 431 @MediumTest 432 public void testPlayingDelayedEnd() throws Exception { 433 mAnimator.setStartDelay(ANIM_DELAY); 434 mFutureListener = new FutureReleaseListener(mFuture); 435 getActivity().runOnUiThread(new Runnable() { 436 @Override 437 public void run() { 438 try { 439 Handler handler = new Handler(); 440 mAnimator.addListener(mFutureListener); 441 mRunning = true; 442 mAnimator.start(); 443 handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DURATION); 444 } catch (junit.framework.AssertionFailedError e) { 445 mFuture.setException(new RuntimeException(e)); 446 } 447 } 448 }); 449 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 450 } 451 452 /** 453 * Same as testPlayingDelayedCancel, but cancel during the startDelay period 454 */ 455 @MediumTest 456 public void testPlayingDelayedCancelMidDelay() throws Exception { 457 mAnimator.setStartDelay(ANIM_DELAY); 458 getActivity().runOnUiThread(new Runnable() { 459 @Override 460 public void run() { 461 try { 462 // Set the listener to automatically timeout after an uncanceled animation 463 // would have finished. This tests to make sure that we're not calling 464 // the listeners with cancel/end callbacks since they won't be called 465 // with the start event. 466 mFutureListener = new FutureReleaseListener(mFuture, getTimeout()); 467 Handler handler = new Handler(); 468 mRunning = true; 469 mAnimator.start(); 470 handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DELAY); 471 } catch (junit.framework.AssertionFailedError e) { 472 mFuture.setException(new RuntimeException(e)); 473 } 474 } 475 }); 476 mFuture.get(getTimeout() + 100, TimeUnit.MILLISECONDS); 477 } 478 479 /** 480 * Same as testPlayingDelayedEnd, but end during the startDelay period 481 */ 482 @MediumTest 483 public void testPlayingDelayedEndMidDelay() throws Exception { 484 mAnimator.setStartDelay(ANIM_DELAY); 485 getActivity().runOnUiThread(new Runnable() { 486 @Override 487 public void run() { 488 try { 489 // Set the listener to automatically timeout after an uncanceled animation 490 // would have finished. This tests to make sure that we're not calling 491 // the listeners with cancel/end callbacks since they won't be called 492 // with the start event. 493 mFutureListener = new FutureReleaseListener(mFuture, getTimeout()); 494 Handler handler = new Handler(); 495 mRunning = true; 496 mAnimator.start(); 497 handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DELAY); 498 } catch (junit.framework.AssertionFailedError e) { 499 mFuture.setException(new RuntimeException(e)); 500 } 501 } 502 }); 503 mFuture.get(getTimeout() + 100, TimeUnit.MILLISECONDS); 504 } 505 506 /** 507 * Verifies that canceling a started animation after it has already been canceled 508 * does nothing. 509 */ 510 @MediumTest 511 public void testStartDoubleCancel() throws Exception { 512 mFutureListener = new FutureReleaseListener(mFuture); 513 getActivity().runOnUiThread(new Runnable() { 514 @Override 515 public void run() { 516 try { 517 mRunning = true; 518 mAnimator.start(); 519 mAnimator.cancel(); 520 mAnimator.cancel(); 521 mFuture.release(); 522 } catch (junit.framework.AssertionFailedError e) { 523 mFuture.setException(new RuntimeException(e)); 524 } 525 } 526 }); 527 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 528 } 529 530 /** 531 * Verifies that ending a started animation after it has already been ended 532 * does nothing. 533 */ 534 @MediumTest 535 public void testStartDoubleEnd() throws Exception { 536 mFutureListener = new FutureReleaseListener(mFuture); 537 getActivity().runOnUiThread(new Runnable() { 538 @Override 539 public void run() { 540 try { 541 mRunning = true; 542 mAnimator.start(); 543 mAnimator.end(); 544 mRunning = true; // end() implicitly starts an unstarted animator 545 mAnimator.end(); 546 mFuture.release(); 547 } catch (junit.framework.AssertionFailedError e) { 548 mFuture.setException(new RuntimeException(e)); 549 } 550 } 551 }); 552 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 553 } 554 555 /** 556 * Same as testStartDoubleCancel, but with a startDelayed animator 557 */ 558 @MediumTest 559 public void testStartDelayedDoubleCancel() throws Exception { 560 mAnimator.setStartDelay(ANIM_DELAY); 561 mFutureListener = new FutureReleaseListener(mFuture); 562 getActivity().runOnUiThread(new Runnable() { 563 @Override 564 public void run() { 565 try { 566 mRunning = true; 567 mAnimator.start(); 568 mAnimator.cancel(); 569 mAnimator.cancel(); 570 mFuture.release(); 571 } catch (junit.framework.AssertionFailedError e) { 572 mFuture.setException(new RuntimeException(e)); 573 } 574 } 575 }); 576 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 577 } 578 579 /** 580 * Same as testStartDoubleEnd, but with a startDelayed animator 581 */ 582 @MediumTest 583 public void testStartDelayedDoubleEnd() throws Exception { 584 mAnimator.setStartDelay(ANIM_DELAY); 585 mFutureListener = new FutureReleaseListener(mFuture); 586 getActivity().runOnUiThread(new Runnable() { 587 @Override 588 public void run() { 589 try { 590 mRunning = true; 591 mAnimator.start(); 592 mAnimator.end(); 593 mRunning = true; // end() implicitly starts an unstarted animator 594 mAnimator.end(); 595 mFuture.release(); 596 } catch (junit.framework.AssertionFailedError e) { 597 mFuture.setException(new RuntimeException(e)); 598 } 599 } 600 }); 601 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 602 } 603 604 /** 605 * Verify that pausing and resuming an animator ends within 606 * the appropriate timeout duration. 607 */ 608 @MediumTest 609 public void testPauseResume() throws Exception { 610 mFutureListener = new FutureReleaseListener(mFuture); 611 getActivity().runOnUiThread(new Runnable() { 612 @Override 613 public void run() { 614 try { 615 Handler handler = new Handler(); 616 mAnimator.addListener(mFutureListener); 617 mRunning = true; 618 mAnimator.start(); 619 handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY); 620 handler.postDelayed(new Resumer(mAnimator, mFuture), 621 ANIM_PAUSE_DELAY + ANIM_PAUSE_DURATION); 622 } catch (junit.framework.AssertionFailedError e) { 623 mFuture.setException(new RuntimeException(e)); 624 } 625 } 626 }); 627 mFuture.get(getTimeout() + ANIM_PAUSE_DURATION, TimeUnit.MILLISECONDS); 628 } 629 630 /** 631 * Verify that pausing and resuming a startDelayed animator ends within 632 * the appropriate timeout duration. 633 */ 634 @MediumTest 635 public void testPauseResumeDelayed() throws Exception { 636 mAnimator.setStartDelay(ANIM_DELAY); 637 mFutureListener = new FutureReleaseListener(mFuture); 638 getActivity().runOnUiThread(new Runnable() { 639 @Override 640 public void run() { 641 try { 642 Handler handler = new Handler(); 643 mAnimator.addListener(mFutureListener); 644 mRunning = true; 645 mAnimator.start(); 646 handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY); 647 handler.postDelayed(new Resumer(mAnimator, mFuture), 648 ANIM_PAUSE_DELAY + ANIM_PAUSE_DURATION); 649 } catch (junit.framework.AssertionFailedError e) { 650 mFuture.setException(new RuntimeException(e)); 651 } 652 } 653 }); 654 mFuture.get(getTimeout() + ANIM_PAUSE_DURATION + ANIM_FULL_DURATION_SLOP, 655 TimeUnit.MILLISECONDS); 656 } 657 658 /** 659 * Verify that pausing an animator without resuming it causes a timeout. 660 */ 661 @MediumTest 662 public void testPauseTimeout() throws Exception { 663 mFutureListener = new FutureReleaseListener(mFuture); 664 getActivity().runOnUiThread(new Runnable() { 665 @Override 666 public void run() { 667 try { 668 Handler handler = new Handler(); 669 mAnimator.addListener(mFutureListener); 670 mRunning = true; 671 mAnimator.start(); 672 handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY); 673 } catch (junit.framework.AssertionFailedError e) { 674 mFuture.setException(new RuntimeException(e)); 675 } 676 } 677 }); 678 try { 679 mFuture.get(getTimeout() + ANIM_PAUSE_DURATION + ANIM_FULL_DURATION_SLOP, 680 TimeUnit.MILLISECONDS); 681 } catch (TimeoutException e) { 682 // Expected behavior, swallow the exception 683 } 684 } 685 686 /** 687 * Verify that pausing a startDelayed animator without resuming it causes a timeout. 688 */ 689 @MediumTest 690 public void testPauseTimeoutDelayed() throws Exception { 691 mAnimator.setStartDelay(ANIM_DELAY); 692 mFutureListener = new FutureReleaseListener(mFuture); 693 getActivity().runOnUiThread(new Runnable() { 694 @Override 695 public void run() { 696 try { 697 Handler handler = new Handler(); 698 mAnimator.addListener(mFutureListener); 699 mRunning = true; 700 mAnimator.start(); 701 handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY); 702 } catch (junit.framework.AssertionFailedError e) { 703 mFuture.setException(new RuntimeException(e)); 704 } 705 } 706 }); 707 try { 708 mFuture.get(getTimeout() + ANIM_PAUSE_DURATION + ANIM_FULL_DURATION_SLOP, 709 TimeUnit.MILLISECONDS); 710 } catch (TimeoutException e) { 711 // Expected behavior, swallow the exception 712 } 713 } 714 } 715