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