BoundsAnimationControllerTests.java revision ecc06b32305ac234db24e3f76bcae0199b80c395
1/* 2 * Copyright (C) 2017 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 17package com.android.server.wm; 18 19import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS; 20import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END; 21import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START; 22import static com.android.server.wm.BoundsAnimationController.SchedulePipModeChangedState; 23 24import android.animation.ValueAnimator; 25import android.content.Context; 26import android.graphics.Rect; 27import android.os.Handler; 28import android.os.Looper; 29import android.platform.test.annotations.Presubmit; 30import android.support.test.InstrumentationRegistry; 31import android.support.test.annotation.UiThreadTest; 32import android.support.test.filters.SmallTest; 33import android.support.test.runner.AndroidJUnit4; 34import android.view.WindowManagerInternal.AppTransitionListener; 35 36import org.junit.Test; 37import org.junit.runner.RunWith; 38 39import static org.junit.Assert.assertEquals; 40import static org.junit.Assert.assertFalse; 41import static org.junit.Assert.assertNotEquals; 42import static org.junit.Assert.assertNotNull; 43import static org.junit.Assert.assertNotSame; 44import static org.junit.Assert.assertNull; 45import static org.junit.Assert.assertSame; 46import static org.junit.Assert.assertTrue; 47import static org.junit.Assert.fail; 48 49import com.android.server.wm.BoundsAnimationController.BoundsAnimator; 50 51/** 52 * Test class for {@link BoundsAnimationController} to ensure that it sends the right callbacks 53 * depending on the various interactions. 54 * 55 * We are really concerned about only three of the transition states [F = fullscreen, !F = floating] 56 * F->!F, !F->!F, and !F->F. Each animation can only be cancelled from the target mid-transition, 57 * or if a new animation starts on the same target. The tests below verifies that the target is 58 * notified of all the cases where it is animating and cancelled so that it can respond 59 * appropriately. 60 * 61 * Build/Install/Run: 62 * bit FrameworksServicesTests:com.android.server.wm.BoundsAnimationControllerTests 63 */ 64@SmallTest 65@Presubmit 66@RunWith(AndroidJUnit4.class) 67public class BoundsAnimationControllerTests extends WindowTestsBase { 68 69 /** 70 * Mock value animator to simulate updates with. 71 */ 72 private class MockValueAnimator extends ValueAnimator { 73 74 private float mFraction; 75 76 public MockValueAnimator getWithValue(float fraction) { 77 mFraction = fraction; 78 return this; 79 } 80 81 @Override 82 public Object getAnimatedValue() { 83 return mFraction; 84 } 85 } 86 87 /** 88 * Mock app transition to fire notifications to the bounds animator. 89 */ 90 private class MockAppTransition extends AppTransition { 91 92 private AppTransitionListener mListener; 93 94 MockAppTransition(Context context) { 95 super(context, null); 96 } 97 98 @Override 99 void registerListenerLocked(AppTransitionListener listener) { 100 mListener = listener; 101 } 102 103 public void notifyTransitionPending() { 104 mListener.onAppTransitionPendingLocked(); 105 } 106 107 public void notifyTransitionCancelled(int transit) { 108 mListener.onAppTransitionCancelledLocked(transit); 109 } 110 111 public void notifyTransitionStarting(int transit) { 112 mListener.onAppTransitionStartingLocked(transit, null, null, null, null); 113 } 114 115 public void notifyTransitionFinished() { 116 mListener.onAppTransitionFinishedLocked(null); 117 } 118 } 119 120 /** 121 * A test animate bounds user to track callbacks from the bounds animation. 122 */ 123 private class TestBoundsAnimationTarget implements BoundsAnimationTarget { 124 125 boolean mAwaitingAnimationStart; 126 boolean mMovedToFullscreen; 127 boolean mAnimationStarted; 128 boolean mSchedulePipModeChangedOnStart; 129 boolean mAnimationEnded; 130 Rect mAnimationEndFinalStackBounds; 131 boolean mSchedulePipModeChangedOnEnd; 132 boolean mBoundsUpdated; 133 boolean mCancelRequested; 134 Rect mStackBounds; 135 Rect mTaskBounds; 136 137 void initialize(Rect from) { 138 mAwaitingAnimationStart = true; 139 mMovedToFullscreen = false; 140 mAnimationStarted = false; 141 mAnimationEnded = false; 142 mAnimationEndFinalStackBounds = null; 143 mSchedulePipModeChangedOnStart = false; 144 mSchedulePipModeChangedOnEnd = false; 145 mStackBounds = from; 146 mTaskBounds = null; 147 mBoundsUpdated = false; 148 } 149 150 @Override 151 public void onAnimationStart(boolean schedulePipModeChangedCallback) { 152 mAwaitingAnimationStart = false; 153 mAnimationStarted = true; 154 mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback; 155 156 mController.onAllWindowsDrawn(); 157 } 158 159 @Override 160 public boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds) { 161 // TODO: Once we break the runs apart, we should fail() here if this is called outside 162 // of onAnimationStart() and onAnimationEnd() 163 if (mCancelRequested) { 164 mCancelRequested = false; 165 return false; 166 } else { 167 mBoundsUpdated = true; 168 mStackBounds = stackBounds; 169 mTaskBounds = taskBounds; 170 return true; 171 } 172 } 173 174 @Override 175 public void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackBounds, 176 boolean moveToFullscreen) { 177 mAnimationEnded = true; 178 mAnimationEndFinalStackBounds = finalStackBounds; 179 mSchedulePipModeChangedOnEnd = schedulePipModeChangedCallback; 180 mMovedToFullscreen = moveToFullscreen; 181 mTaskBounds = null; 182 } 183 } 184 185 /** 186 * Drives the animations, makes common assertions along the way. 187 */ 188 private class BoundsAnimationDriver { 189 190 private BoundsAnimationController mController; 191 private TestBoundsAnimationTarget mTarget; 192 private BoundsAnimator mAnimator; 193 194 private Rect mFrom; 195 private Rect mTo; 196 private Rect mLargerBounds; 197 private Rect mExpectedFinalBounds; 198 199 BoundsAnimationDriver(BoundsAnimationController controller, 200 TestBoundsAnimationTarget target) { 201 mController = controller; 202 mTarget = target; 203 } 204 205 BoundsAnimationDriver start(Rect from, Rect to) { 206 if (mAnimator != null) { 207 throw new IllegalArgumentException("Call restart() to restart an animation"); 208 } 209 210 mTarget.initialize(from); 211 212 // Started, not running 213 assertTrue(mTarget.mAwaitingAnimationStart); 214 assertTrue(!mTarget.mAnimationStarted); 215 216 startImpl(from, to); 217 218 // Started and running 219 assertTrue(!mTarget.mAwaitingAnimationStart); 220 assertTrue(mTarget.mAnimationStarted); 221 222 return this; 223 } 224 225 BoundsAnimationDriver restart(Rect to) { 226 if (mAnimator == null) { 227 throw new IllegalArgumentException("Call start() to start a new animation"); 228 } 229 230 BoundsAnimator oldAnimator = mAnimator; 231 boolean toSameBounds = mAnimator.isStarted() && to.equals(mTo); 232 233 // Reset the animation start state 234 mTarget.mAnimationStarted = false; 235 236 // Start animation 237 startImpl(mTarget.mStackBounds, to); 238 239 if (toSameBounds) { 240 // Same animator if same final bounds 241 assertSame(oldAnimator, mAnimator); 242 } 243 244 // No animation start for replacing animation 245 assertTrue(!mTarget.mAnimationStarted); 246 mTarget.mAnimationStarted = true; 247 return this; 248 } 249 250 private BoundsAnimationDriver startImpl(Rect from, Rect to) { 251 boolean fromFullscreen = from.equals(BOUNDS_FULL); 252 boolean toFullscreen = to.equals(BOUNDS_FULL); 253 mFrom = new Rect(from); 254 mTo = new Rect(to); 255 mExpectedFinalBounds = new Rect(to); 256 mLargerBounds = getLargerBounds(mFrom, mTo); 257 258 // Start animation 259 final @SchedulePipModeChangedState int schedulePipModeChangedState = toFullscreen 260 ? SCHEDULE_PIP_MODE_CHANGED_ON_START 261 : fromFullscreen 262 ? SCHEDULE_PIP_MODE_CHANGED_ON_END 263 : NO_PIP_MODE_CHANGED_CALLBACKS; 264 mAnimator = mController.animateBoundsImpl(mTarget, from, to, DURATION, 265 schedulePipModeChangedState, toFullscreen); 266 267 // Original stack bounds, frozen task bounds 268 assertEquals(mFrom, mTarget.mStackBounds); 269 assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds); 270 271 // Animating to larger size 272 if (mFrom.equals(mLargerBounds)) { 273 assertTrue(!mAnimator.animatingToLargerSize()); 274 } else if (mTo.equals(mLargerBounds)) { 275 assertTrue(mAnimator.animatingToLargerSize()); 276 } 277 278 return this; 279 } 280 281 BoundsAnimationDriver expectStarted(boolean schedulePipModeChanged) { 282 // Callback made 283 assertTrue(mTarget.mAnimationStarted); 284 285 assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnStart); 286 return this; 287 } 288 289 BoundsAnimationDriver update(float t) { 290 mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(t)); 291 292 // Temporary stack bounds, frozen task bounds 293 if (t == 0f) { 294 assertEquals(mFrom, mTarget.mStackBounds); 295 } else if (t == 1f) { 296 assertEquals(mTo, mTarget.mStackBounds); 297 } else { 298 assertNotEquals(mFrom, mTarget.mStackBounds); 299 assertNotEquals(mTo, mTarget.mStackBounds); 300 } 301 assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds); 302 return this; 303 } 304 305 BoundsAnimationDriver cancel() { 306 // Cancel 307 mTarget.mCancelRequested = true; 308 mTarget.mBoundsUpdated = false; 309 mExpectedFinalBounds = null; 310 311 // Update 312 mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(0.5f)); 313 314 // Not started, not running, cancel reset 315 assertTrue(!mTarget.mCancelRequested); 316 317 // Stack/task bounds not updated 318 assertTrue(!mTarget.mBoundsUpdated); 319 320 // Callback made 321 assertTrue(mTarget.mAnimationEnded); 322 assertNull(mTarget.mAnimationEndFinalStackBounds); 323 324 return this; 325 } 326 327 BoundsAnimationDriver end() { 328 mAnimator.end(); 329 330 // Final stack bounds 331 assertEquals(mTo, mTarget.mStackBounds); 332 assertEquals(mExpectedFinalBounds, mTarget.mAnimationEndFinalStackBounds); 333 assertNull(mTarget.mTaskBounds); 334 335 return this; 336 } 337 338 BoundsAnimationDriver expectEnded(boolean schedulePipModeChanged, 339 boolean moveToFullscreen) { 340 // Callback made 341 assertTrue(mTarget.mAnimationEnded); 342 343 assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnEnd); 344 assertEquals(moveToFullscreen, mTarget.mMovedToFullscreen); 345 return this; 346 } 347 348 private Rect getLargerBounds(Rect r1, Rect r2) { 349 int r1Area = r1.width() * r1.height(); 350 int r2Area = r2.width() * r2.height(); 351 if (r1Area <= r2Area) { 352 return r2; 353 } else { 354 return r1; 355 } 356 } 357 } 358 359 // Constants 360 private static final boolean SCHEDULE_PIP_MODE_CHANGED = true; 361 private static final boolean MOVE_TO_FULLSCREEN = true; 362 private static final int DURATION = 100; 363 364 // Some dummy bounds to represent fullscreen and floating bounds 365 private static final Rect BOUNDS_FULL = new Rect(0, 0, 100, 100); 366 private static final Rect BOUNDS_FLOATING = new Rect(60, 60, 95, 95); 367 private static final Rect BOUNDS_SMALLER_FLOATING = new Rect(80, 80, 95, 95); 368 369 // Common 370 private MockAppTransition mMockAppTransition; 371 private MockValueAnimator mMockAnimator; 372 private TestBoundsAnimationTarget mTarget; 373 private BoundsAnimationController mController; 374 private BoundsAnimationDriver mDriver; 375 376 // Temp 377 private Rect mTmpRect = new Rect(); 378 379 @Override 380 public void setUp() throws Exception { 381 super.setUp(); 382 383 final Context context = InstrumentationRegistry.getTargetContext(); 384 final Handler handler = new Handler(Looper.getMainLooper()); 385 mMockAppTransition = new MockAppTransition(context); 386 mMockAnimator = new MockValueAnimator(); 387 mTarget = new TestBoundsAnimationTarget(); 388 mController = new BoundsAnimationController(context, mMockAppTransition, handler); 389 mDriver = new BoundsAnimationDriver(mController, mTarget); 390 } 391 392 /** BASE TRANSITIONS **/ 393 394 @UiThreadTest 395 @Test 396 public void testFullscreenToFloatingTransition() throws Exception { 397 mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) 398 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 399 .update(0f) 400 .update(0.5f) 401 .update(1f) 402 .end() 403 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 404 } 405 406 @UiThreadTest 407 @Test 408 public void testFloatingToFullscreenTransition() throws Exception { 409 mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) 410 .expectStarted(SCHEDULE_PIP_MODE_CHANGED) 411 .update(0f) 412 .update(0.5f) 413 .update(1f) 414 .end() 415 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); 416 } 417 418 @UiThreadTest 419 @Test 420 public void testFloatingToSmallerFloatingTransition() throws Exception { 421 mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING) 422 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 423 .update(0f) 424 .update(0.5f) 425 .update(1f) 426 .end() 427 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 428 } 429 430 @UiThreadTest 431 @Test 432 public void testFloatingToLargerFloatingTransition() throws Exception { 433 mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING) 434 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 435 .update(0f) 436 .update(0.5f) 437 .update(1f) 438 .end() 439 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 440 } 441 442 /** F->!F w/ CANCEL **/ 443 444 @UiThreadTest 445 @Test 446 public void testFullscreenToFloatingCancelFromTarget() throws Exception { 447 mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) 448 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 449 .update(0.25f) 450 .cancel() 451 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 452 } 453 454 @UiThreadTest 455 @Test 456 public void testFullscreenToFloatingCancelFromAnimationToSameBounds() throws Exception { 457 mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) 458 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 459 .update(0.25f) 460 .restart(BOUNDS_FLOATING) 461 .end() 462 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 463 } 464 465 @UiThreadTest 466 @Test 467 public void testFullscreenToFloatingCancelFromAnimationToFloatingBounds() throws Exception { 468 mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) 469 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 470 .update(0.25f) 471 .restart(BOUNDS_SMALLER_FLOATING) 472 .end() 473 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 474 } 475 476 @UiThreadTest 477 @Test 478 public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() throws Exception { 479 mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) 480 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 481 .update(0.25f) 482 .restart(BOUNDS_FULL) 483 .end() 484 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); 485 } 486 487 /** !F->F w/ CANCEL **/ 488 489 @UiThreadTest 490 @Test 491 public void testFloatingToFullscreenCancelFromTarget() throws Exception { 492 mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) 493 .expectStarted(SCHEDULE_PIP_MODE_CHANGED) 494 .update(0.25f) 495 .cancel() 496 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 497 } 498 499 @UiThreadTest 500 @Test 501 public void testFloatingToFullscreenCancelFromAnimationToSameBounds() throws Exception { 502 mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) 503 .expectStarted(SCHEDULE_PIP_MODE_CHANGED) 504 .update(0.25f) 505 .restart(BOUNDS_FULL) 506 .end() 507 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); 508 } 509 510 @UiThreadTest 511 @Test 512 public void testFloatingToFullscreenCancelFromAnimationToFloatingBounds() throws Exception { 513 mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) 514 .expectStarted(SCHEDULE_PIP_MODE_CHANGED) 515 .update(0.25f) 516 .restart(BOUNDS_SMALLER_FLOATING) 517 .end() 518 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 519 } 520 521 /** !F->!F w/ CANCEL **/ 522 523 @UiThreadTest 524 @Test 525 public void testFloatingToSmallerFloatingCancelFromTarget() throws Exception { 526 mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING) 527 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 528 .update(0.25f) 529 .cancel() 530 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 531 } 532 533 @UiThreadTest 534 @Test 535 public void testFloatingToLargerFloatingCancelFromTarget() throws Exception { 536 mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING) 537 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 538 .update(0.25f) 539 .cancel() 540 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 541 } 542 543 /** MISC **/ 544 545 @UiThreadTest 546 @Test 547 public void testBoundsAreCopied() throws Exception { 548 Rect from = new Rect(0, 0, 100, 100); 549 Rect to = new Rect(25, 25, 75, 75); 550 mDriver.start(from, to) 551 .update(0.25f) 552 .end(); 553 assertEquals(new Rect(0, 0, 100, 100), from); 554 assertEquals(new Rect(25, 25, 75, 75), to); 555 } 556 557 /** 558 * @return whether the task and stack bounds would be the same if they were at the same offset. 559 */ 560 private boolean assertEqualSizeAtOffset(Rect stackBounds, Rect taskBounds) { 561 mTmpRect.set(taskBounds); 562 mTmpRect.offsetTo(stackBounds.left, stackBounds.top); 563 return stackBounds.equals(mTmpRect); 564 } 565} 566