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 */ 16package android.animation; 17 18import android.os.Handler; 19import android.test.ActivityInstrumentationTestCase2; 20import android.test.UiThreadTest; 21import android.test.suitebuilder.annotation.MediumTest; 22import android.test.suitebuilder.annotation.SmallTest; 23 24import 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 */ 38public 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 || mStarted); 177 mCanceled = true; 178 } 179 180 @Override 181 public void onAnimationEnd(Animator animation) { 182 // This should only be called on an animation that has been started and not 183 // yet ended 184 assertTrue(mRunning || mStarted); 185 mRunning = false; 186 mStarted = false; 187 super.onAnimationEnd(animation); 188 } 189 }; 190 191 mAnimator.addListener(mListener); 192 mAnimator.setDuration(ANIM_DURATION); 193 194 mFuture = new FutureWaiter(); 195 196 mRunning = false; 197 mCanceled = false; 198 mStarted = false; 199 } 200 201 /** 202 * Verify that calling cancel on an unstarted animator does nothing. 203 */ 204 @UiThreadTest 205 @SmallTest 206 public void testCancel() throws Exception { 207 mAnimator.cancel(); 208 } 209 210 /** 211 * Verify that calling end on an unstarted animator starts/ends an animator. 212 */ 213 @UiThreadTest 214 @SmallTest 215 public void testEnd() throws Exception { 216 mRunning = true; // end() implicitly starts an unstarted animator 217 mAnimator.end(); 218 } 219 220 /** 221 * Verify that calling cancel on a started animator does the right thing. 222 */ 223 @UiThreadTest 224 @SmallTest 225 public void testStartCancel() throws Exception { 226 mFutureListener = new FutureReleaseListener(mFuture); 227 getActivity().runOnUiThread(new Runnable() { 228 @Override 229 public void run() { 230 try { 231 mRunning = true; 232 mAnimator.start(); 233 mAnimator.cancel(); 234 mFuture.release(); 235 } catch (junit.framework.AssertionFailedError e) { 236 mFuture.setException(new RuntimeException(e)); 237 } 238 } 239 }); 240 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 241 } 242 243 /** 244 * Verify that calling end on a started animator does the right thing. 245 */ 246 @UiThreadTest 247 @SmallTest 248 public void testStartEnd() throws Exception { 249 mFutureListener = new FutureReleaseListener(mFuture); 250 getActivity().runOnUiThread(new Runnable() { 251 @Override 252 public void run() { 253 try { 254 mRunning = true; 255 mAnimator.start(); 256 mAnimator.end(); 257 mFuture.release(); 258 } catch (junit.framework.AssertionFailedError e) { 259 mFuture.setException(new RuntimeException(e)); 260 } 261 } 262 }); 263 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 264 } 265 266 /** 267 * Same as testStartCancel, but with a startDelayed animator 268 */ 269 @SmallTest 270 public void testStartDelayedCancel() throws Exception { 271 mFutureListener = new FutureReleaseListener(mFuture); 272 mAnimator.setStartDelay(ANIM_DELAY); 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 * Same as testStartEnd, but with a startDelayed animator 291 */ 292 @SmallTest 293 public void testStartDelayedEnd() throws Exception { 294 mFutureListener = new FutureReleaseListener(mFuture); 295 mAnimator.setStartDelay(ANIM_DELAY); 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 * Verify that canceling an animator that is playing does the right thing. 314 */ 315 @MediumTest 316 public void testPlayingCancel() throws Exception { 317 mFutureListener = new FutureReleaseListener(mFuture); 318 getActivity().runOnUiThread(new Runnable() { 319 @Override 320 public void run() { 321 try { 322 Handler handler = new Handler(); 323 mAnimator.addListener(mFutureListener); 324 mRunning = true; 325 mAnimator.start(); 326 handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION); 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 * Verify that ending an animator that is playing does the right thing. 337 */ 338 @MediumTest 339 public void testPlayingEnd() throws Exception { 340 mFutureListener = new FutureReleaseListener(mFuture); 341 getActivity().runOnUiThread(new Runnable() { 342 @Override 343 public void run() { 344 try { 345 Handler handler = new Handler(); 346 mAnimator.addListener(mFutureListener); 347 mRunning = true; 348 mAnimator.start(); 349 handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DURATION); 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 * Same as testPlayingCancel, but with a startDelayed animator 360 */ 361 @MediumTest 362 public void testPlayingDelayedCancel() throws Exception { 363 mAnimator.setStartDelay(ANIM_DELAY); 364 mFutureListener = new FutureReleaseListener(mFuture); 365 getActivity().runOnUiThread(new Runnable() { 366 @Override 367 public void run() { 368 try { 369 Handler handler = new Handler(); 370 mAnimator.addListener(mFutureListener); 371 mRunning = true; 372 mAnimator.start(); 373 handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION); 374 } catch (junit.framework.AssertionFailedError e) { 375 mFuture.setException(new RuntimeException(e)); 376 } 377 } 378 }); 379 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 380 } 381 382 /** 383 * Same as testPlayingEnd, but with a startDelayed animator 384 */ 385 @MediumTest 386 public void testPlayingDelayedEnd() throws Exception { 387 mAnimator.setStartDelay(ANIM_DELAY); 388 mFutureListener = new FutureReleaseListener(mFuture); 389 getActivity().runOnUiThread(new Runnable() { 390 @Override 391 public void run() { 392 try { 393 Handler handler = new Handler(); 394 mAnimator.addListener(mFutureListener); 395 mRunning = true; 396 mAnimator.start(); 397 handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DURATION); 398 } catch (junit.framework.AssertionFailedError e) { 399 mFuture.setException(new RuntimeException(e)); 400 } 401 } 402 }); 403 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 404 } 405 406 /** 407 * Same as testPlayingDelayedCancel, but cancel during the startDelay period 408 */ 409 @MediumTest 410 public void testPlayingDelayedCancelMidDelay() throws Exception { 411 mAnimator.setStartDelay(ANIM_DELAY); 412 getActivity().runOnUiThread(new Runnable() { 413 @Override 414 public void run() { 415 try { 416 // Set the listener to automatically timeout after an uncanceled animation 417 // would have finished. This tests to make sure that we're not calling 418 // the listeners with cancel/end callbacks since they won't be called 419 // with the start event. 420 mFutureListener = new FutureReleaseListener(mFuture, getTimeout()); 421 Handler handler = new Handler(); 422 mRunning = true; 423 mAnimator.start(); 424 handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DELAY); 425 } catch (junit.framework.AssertionFailedError e) { 426 mFuture.setException(new RuntimeException(e)); 427 } 428 } 429 }); 430 mFuture.get(getTimeout() + 100, TimeUnit.MILLISECONDS); 431 } 432 433 /** 434 * Same as testPlayingDelayedEnd, but end during the startDelay period 435 */ 436 @MediumTest 437 public void testPlayingDelayedEndMidDelay() throws Exception { 438 mAnimator.setStartDelay(ANIM_DELAY); 439 getActivity().runOnUiThread(new Runnable() { 440 @Override 441 public void run() { 442 try { 443 // Set the listener to automatically timeout after an uncanceled animation 444 // would have finished. This tests to make sure that we're not calling 445 // the listeners with cancel/end callbacks since they won't be called 446 // with the start event. 447 mFutureListener = new FutureReleaseListener(mFuture, getTimeout()); 448 Handler handler = new Handler(); 449 mRunning = true; 450 mAnimator.start(); 451 handler.postDelayed(new Ender(mAnimator, mFuture), ANIM_MID_DELAY); 452 } catch (junit.framework.AssertionFailedError e) { 453 mFuture.setException(new RuntimeException(e)); 454 } 455 } 456 }); 457 mFuture.get(getTimeout() + 100, TimeUnit.MILLISECONDS); 458 } 459 460 /** 461 * Verifies that canceling a started animation after it has already been canceled 462 * does nothing. 463 */ 464 @MediumTest 465 public void testStartDoubleCancel() throws Exception { 466 mFutureListener = new FutureReleaseListener(mFuture); 467 getActivity().runOnUiThread(new Runnable() { 468 @Override 469 public void run() { 470 try { 471 mRunning = true; 472 mAnimator.start(); 473 mAnimator.cancel(); 474 mAnimator.cancel(); 475 mFuture.release(); 476 } catch (junit.framework.AssertionFailedError e) { 477 mFuture.setException(new RuntimeException(e)); 478 } 479 } 480 }); 481 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 482 } 483 484 /** 485 * Verifies that ending a started animation after it has already been ended 486 * does nothing. 487 */ 488 @MediumTest 489 public void testStartDoubleEnd() throws Exception { 490 mFutureListener = new FutureReleaseListener(mFuture); 491 getActivity().runOnUiThread(new Runnable() { 492 @Override 493 public void run() { 494 try { 495 mRunning = true; 496 mAnimator.start(); 497 mAnimator.end(); 498 mRunning = true; // end() implicitly starts an unstarted animator 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 mRunning = true; // end() implicitly starts an unstarted animator 548 mAnimator.end(); 549 mFuture.release(); 550 } catch (junit.framework.AssertionFailedError e) { 551 mFuture.setException(new RuntimeException(e)); 552 } 553 } 554 }); 555 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 556 } 557 558} 559