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 import android.view.ViewPropertyAnimator; 24 import android.widget.Button; 25 import com.android.frameworks.coretests.R; 26 27 import java.util.concurrent.TimeUnit; 28 29 /** 30 * Tests for the various lifecycle events of Animators. This abstract class is subclassed by 31 * concrete implementations that provide the actual Animator objects being tested. All of the 32 * testing mechanisms are in this class; the subclasses are only responsible for providing 33 * the mAnimator object. 34 * 35 * This test is more complicated than a typical synchronous test because much of the functionality 36 * must happen on the UI thread. Some tests do this by using the UiThreadTest annotation to 37 * automatically run the whole test on that thread. Other tests must run on the UI thread and also 38 * wait for some later event to occur before ending. These tests use a combination of an 39 * AbstractFuture mechanism and a delayed action to release that Future later. 40 */ 41 public abstract class ViewPropertyAnimatorTest 42 extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> { 43 44 protected static final int ANIM_DURATION = 400; 45 protected static final int ANIM_DELAY = 100; 46 protected static final int ANIM_MID_DURATION = ANIM_DURATION / 2; 47 protected static final int ANIM_MID_DELAY = ANIM_DELAY / 2; 48 protected static final int FUTURE_RELEASE_DELAY = 50; 49 50 private boolean mStarted; // tracks whether we've received the onAnimationStart() callback 51 protected boolean mRunning; // tracks whether we've started the animator 52 private boolean mCanceled; // trackes whether we've canceled the animator 53 protected Animator.AnimatorListener mFutureListener; // mechanism for delaying the end of the test 54 protected FutureWaiter mFuture; // Mechanism for waiting for the UI test to complete 55 private Animator.AnimatorListener mListener; // Listener that handles/tests the events 56 57 protected ViewPropertyAnimator mAnimator; // The animator used in the tests. Must be set in subclass 58 // setup() method prior to calling the superclass setup() 59 60 /** 61 * Cancels the given animator. Used to delay cancellation until some later time (after the 62 * animator has started playing). 63 */ 64 protected static class Canceler implements Runnable { 65 ViewPropertyAnimator mAnim; 66 FutureWaiter mFuture; 67 public Canceler(ViewPropertyAnimator anim, FutureWaiter future) { 68 mAnim = anim; 69 mFuture = future; 70 } 71 @Override 72 public void run() { 73 try { 74 mAnim.cancel(); 75 } catch (junit.framework.AssertionFailedError e) { 76 mFuture.setException(new RuntimeException(e)); 77 } 78 } 79 }; 80 81 /** 82 * Timeout length, based on when the animation should reasonably be complete. 83 */ 84 protected long getTimeout() { 85 return ANIM_DURATION + ANIM_DELAY + FUTURE_RELEASE_DELAY; 86 } 87 88 /** 89 * Releases the given Future object when the listener's end() event is called. Specifically, 90 * it releases it after some further delay, to give the test time to do other things right 91 * after an animation ends. 92 */ 93 protected static class FutureReleaseListener extends AnimatorListenerAdapter { 94 FutureWaiter mFuture; 95 96 public FutureReleaseListener(FutureWaiter future) { 97 mFuture = future; 98 } 99 100 /** 101 * Variant constructor that auto-releases the FutureWaiter after the specified timeout. 102 * @param future 103 * @param timeout 104 */ 105 public FutureReleaseListener(FutureWaiter future, long timeout) { 106 mFuture = future; 107 Handler handler = new Handler(); 108 handler.postDelayed(new Runnable() { 109 @Override 110 public void run() { 111 mFuture.release(); 112 } 113 }, timeout); 114 } 115 116 @Override 117 public void onAnimationEnd(Animator animation) { 118 Handler handler = new Handler(); 119 handler.postDelayed(new Runnable() { 120 @Override 121 public void run() { 122 mFuture.release(); 123 } 124 }, FUTURE_RELEASE_DELAY); 125 } 126 }; 127 128 public ViewPropertyAnimatorTest() { 129 super(BasicAnimatorActivity.class); 130 } 131 132 /** 133 * Sets up the fields used by each test. Subclasses must override this method to create 134 * the protected mAnimator object used in all tests. Overrides must create that animator 135 * and then call super.setup(), where further properties are set on that animator. 136 * @throws Exception 137 */ 138 @Override 139 public void setUp() throws Exception { 140 final BasicAnimatorActivity activity = getActivity(); 141 Button button = (Button) activity.findViewById(R.id.animatingButton); 142 143 mAnimator = button.animate().x(100).y(100); 144 145 super.setUp(); 146 147 // mListener is the main testing mechanism of this file. The asserts of each test 148 // are embedded in the listener callbacks that it implements. 149 mListener = new AnimatorListenerAdapter() { 150 @Override 151 public void onAnimationStart(Animator animation) { 152 // This should only be called on an animation that has not yet been started 153 assertFalse(mStarted); 154 assertTrue(mRunning); 155 mStarted = true; 156 } 157 158 @Override 159 public void onAnimationCancel(Animator animation) { 160 // This should only be called on an animation that has been started and not 161 // yet canceled or ended 162 assertFalse(mCanceled); 163 assertTrue(mRunning); 164 assertTrue(mStarted); 165 mCanceled = true; 166 } 167 168 @Override 169 public void onAnimationEnd(Animator animation) { 170 // This should only be called on an animation that has been started and not 171 // yet ended 172 assertTrue(mRunning); 173 assertTrue(mStarted); 174 mRunning = false; 175 mStarted = false; 176 super.onAnimationEnd(animation); 177 } 178 }; 179 180 mAnimator.setListener(mListener); 181 mAnimator.setDuration(ANIM_DURATION); 182 183 mFuture = new FutureWaiter(); 184 185 mRunning = false; 186 mCanceled = false; 187 mStarted = false; 188 } 189 190 /** 191 * Verify that calling cancel on an unstarted animator does nothing. 192 */ 193 @UiThreadTest 194 @SmallTest 195 public void testCancel() throws Exception { 196 mAnimator.cancel(); 197 } 198 199 /** 200 * Verify that calling cancel on a started animator does the right thing. 201 */ 202 @UiThreadTest 203 @SmallTest 204 public void testStartCancel() throws Exception { 205 mFutureListener = new FutureReleaseListener(mFuture); 206 getActivity().runOnUiThread(new Runnable() { 207 @Override 208 public void run() { 209 try { 210 mRunning = true; 211 mAnimator.start(); 212 mAnimator.cancel(); 213 mFuture.release(); 214 } catch (junit.framework.AssertionFailedError e) { 215 mFuture.setException(new RuntimeException(e)); 216 } 217 } 218 }); 219 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 220 } 221 222 /** 223 * Same as testStartCancel, but with a startDelayed animator 224 */ 225 @SmallTest 226 public void testStartDelayedCancel() throws Exception { 227 mFutureListener = new FutureReleaseListener(mFuture); 228 mAnimator.setStartDelay(ANIM_DELAY); 229 getActivity().runOnUiThread(new Runnable() { 230 @Override 231 public void run() { 232 try { 233 mRunning = true; 234 mAnimator.start(); 235 mAnimator.cancel(); 236 mFuture.release(); 237 } catch (junit.framework.AssertionFailedError e) { 238 mFuture.setException(new RuntimeException(e)); 239 } 240 } 241 }); 242 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 243 } 244 245 /** 246 * Verify that canceling an animator that is playing does the right thing. 247 */ 248 @MediumTest 249 public void testPlayingCancel() throws Exception { 250 mFutureListener = new FutureReleaseListener(mFuture); 251 getActivity().runOnUiThread(new Runnable() { 252 @Override 253 public void run() { 254 try { 255 Handler handler = new Handler(); 256 mAnimator.setListener(mFutureListener); 257 mRunning = true; 258 mAnimator.start(); 259 handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION); 260 } catch (junit.framework.AssertionFailedError e) { 261 mFuture.setException(new RuntimeException(e)); 262 } 263 } 264 }); 265 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 266 } 267 268 /** 269 * Same as testPlayingCancel, but with a startDelayed animator 270 */ 271 @MediumTest 272 public void testPlayingDelayedCancel() throws Exception { 273 mAnimator.setStartDelay(ANIM_DELAY); 274 mFutureListener = new FutureReleaseListener(mFuture); 275 getActivity().runOnUiThread(new Runnable() { 276 @Override 277 public void run() { 278 try { 279 Handler handler = new Handler(); 280 mAnimator.setListener(mFutureListener); 281 mRunning = true; 282 mAnimator.start(); 283 handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DURATION); 284 } catch (junit.framework.AssertionFailedError e) { 285 mFuture.setException(new RuntimeException(e)); 286 } 287 } 288 }); 289 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 290 } 291 292 /** 293 * Same as testPlayingDelayedCancel, but cancel during the startDelay period 294 */ 295 @MediumTest 296 public void testPlayingDelayedCancelMidDelay() throws Exception { 297 mAnimator.setStartDelay(ANIM_DELAY); 298 getActivity().runOnUiThread(new Runnable() { 299 @Override 300 public void run() { 301 try { 302 // Set the listener to automatically timeout after an uncanceled animation 303 // would have finished. This tests to make sure that we're not calling 304 // the listeners with cancel/end callbacks since they won't be called 305 // with the start event. 306 mFutureListener = new FutureReleaseListener(mFuture, getTimeout()); 307 Handler handler = new Handler(); 308 mRunning = true; 309 mAnimator.start(); 310 handler.postDelayed(new Canceler(mAnimator, mFuture), ANIM_MID_DELAY); 311 } catch (junit.framework.AssertionFailedError e) { 312 mFuture.setException(new RuntimeException(e)); 313 } 314 } 315 }); 316 mFuture.get(getTimeout() + 100, TimeUnit.MILLISECONDS); 317 } 318 319 /** 320 * Verifies that canceling a started animation after it has already been canceled 321 * does nothing. 322 */ 323 @MediumTest 324 public void testStartDoubleCancel() throws Exception { 325 mFutureListener = new FutureReleaseListener(mFuture); 326 getActivity().runOnUiThread(new Runnable() { 327 @Override 328 public void run() { 329 try { 330 mRunning = true; 331 mAnimator.start(); 332 mAnimator.cancel(); 333 mAnimator.cancel(); 334 mFuture.release(); 335 } catch (junit.framework.AssertionFailedError e) { 336 mFuture.setException(new RuntimeException(e)); 337 } 338 } 339 }); 340 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 341 } 342 343 /** 344 * Same as testStartDoubleCancel, but with a startDelayed animator 345 */ 346 @MediumTest 347 public void testStartDelayedDoubleCancel() throws Exception { 348 mAnimator.setStartDelay(ANIM_DELAY); 349 mFutureListener = new FutureReleaseListener(mFuture); 350 getActivity().runOnUiThread(new Runnable() { 351 @Override 352 public void run() { 353 try { 354 mRunning = true; 355 mAnimator.start(); 356 mAnimator.cancel(); 357 mAnimator.cancel(); 358 mFuture.release(); 359 } catch (junit.framework.AssertionFailedError e) { 360 mFuture.setException(new RuntimeException(e)); 361 } 362 } 363 }); 364 mFuture.get(getTimeout(), TimeUnit.MILLISECONDS); 365 } 366 367 } 368