BoundsAnimationControllerTests.java revision 4a526c124554e75dc4bc11a682645a73bd47d501
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 157 @Override 158 public boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds) { 159 // TODO: Once we break the runs apart, we should fail() here if this is called outside 160 // of onAnimationStart() and onAnimationEnd() 161 if (mCancelRequested) { 162 mCancelRequested = false; 163 return false; 164 } else { 165 mBoundsUpdated = true; 166 mStackBounds = stackBounds; 167 mTaskBounds = taskBounds; 168 return true; 169 } 170 } 171 172 @Override 173 public void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackBounds, 174 boolean moveToFullscreen) { 175 mAnimationEnded = true; 176 mAnimationEndFinalStackBounds = finalStackBounds; 177 mSchedulePipModeChangedOnEnd = schedulePipModeChangedCallback; 178 mMovedToFullscreen = moveToFullscreen; 179 mTaskBounds = null; 180 } 181 } 182 183 /** 184 * Drives the animations, makes common assertions along the way. 185 */ 186 private class BoundsAnimationDriver { 187 188 private BoundsAnimationController mController; 189 private TestBoundsAnimationTarget mTarget; 190 private BoundsAnimator mAnimator; 191 192 private Rect mFrom; 193 private Rect mTo; 194 private Rect mLargerBounds; 195 private Rect mExpectedFinalBounds; 196 197 BoundsAnimationDriver(BoundsAnimationController controller, 198 TestBoundsAnimationTarget target) { 199 mController = controller; 200 mTarget = target; 201 } 202 203 BoundsAnimationDriver start(Rect from, Rect to) { 204 if (mAnimator != null) { 205 throw new IllegalArgumentException("Call restart() to restart an animation"); 206 } 207 208 boolean fromFullscreen = from.equals(BOUNDS_FULL); 209 boolean toFullscreen = to.equals(BOUNDS_FULL); 210 211 mTarget.initialize(from); 212 213 // Started, not running 214 assertTrue(mTarget.mAwaitingAnimationStart); 215 assertTrue(!mTarget.mAnimationStarted); 216 217 startImpl(from, to); 218 219 // Ensure that the animator is paused for the all windows drawn signal when animating 220 // to/from fullscreen 221 if (fromFullscreen || toFullscreen) { 222 assertTrue(mAnimator.isPaused()); 223 mController.onAllWindowsDrawn(); 224 } else { 225 assertTrue(!mAnimator.isPaused()); 226 } 227 228 // Started and running 229 assertTrue(!mTarget.mAwaitingAnimationStart); 230 assertTrue(mTarget.mAnimationStarted); 231 232 return this; 233 } 234 235 BoundsAnimationDriver restart(Rect to) { 236 if (mAnimator == null) { 237 throw new IllegalArgumentException("Call start() to start a new animation"); 238 } 239 240 BoundsAnimator oldAnimator = mAnimator; 241 boolean toSameBounds = mAnimator.isStarted() && to.equals(mTo); 242 243 // Reset the animation start state 244 mTarget.mAnimationStarted = false; 245 246 // Start animation 247 startImpl(mTarget.mStackBounds, to); 248 249 if (toSameBounds) { 250 // Same animator if same final bounds 251 assertSame(oldAnimator, mAnimator); 252 } 253 254 // No animation start for replacing animation 255 assertTrue(!mTarget.mAnimationStarted); 256 mTarget.mAnimationStarted = true; 257 return this; 258 } 259 260 private BoundsAnimationDriver startImpl(Rect from, Rect to) { 261 boolean fromFullscreen = from.equals(BOUNDS_FULL); 262 boolean toFullscreen = to.equals(BOUNDS_FULL); 263 mFrom = new Rect(from); 264 mTo = new Rect(to); 265 mExpectedFinalBounds = new Rect(to); 266 mLargerBounds = getLargerBounds(mFrom, mTo); 267 268 // Start animation 269 final @SchedulePipModeChangedState int schedulePipModeChangedState = toFullscreen 270 ? SCHEDULE_PIP_MODE_CHANGED_ON_START 271 : fromFullscreen 272 ? SCHEDULE_PIP_MODE_CHANGED_ON_END 273 : NO_PIP_MODE_CHANGED_CALLBACKS; 274 mAnimator = mController.animateBoundsImpl(mTarget, from, to, DURATION, 275 schedulePipModeChangedState, fromFullscreen, toFullscreen); 276 277 // Original stack bounds, frozen task bounds 278 assertEquals(mFrom, mTarget.mStackBounds); 279 assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds); 280 281 // Animating to larger size 282 if (mFrom.equals(mLargerBounds)) { 283 assertTrue(!mAnimator.animatingToLargerSize()); 284 } else if (mTo.equals(mLargerBounds)) { 285 assertTrue(mAnimator.animatingToLargerSize()); 286 } 287 288 return this; 289 } 290 291 BoundsAnimationDriver expectStarted(boolean schedulePipModeChanged) { 292 // Callback made 293 assertTrue(mTarget.mAnimationStarted); 294 295 assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnStart); 296 return this; 297 } 298 299 BoundsAnimationDriver update(float t) { 300 mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(t)); 301 302 // Temporary stack bounds, frozen task bounds 303 if (t == 0f) { 304 assertEquals(mFrom, mTarget.mStackBounds); 305 } else if (t == 1f) { 306 assertEquals(mTo, mTarget.mStackBounds); 307 } else { 308 assertNotEquals(mFrom, mTarget.mStackBounds); 309 assertNotEquals(mTo, mTarget.mStackBounds); 310 } 311 assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds); 312 return this; 313 } 314 315 BoundsAnimationDriver cancel() { 316 // Cancel 317 mTarget.mCancelRequested = true; 318 mTarget.mBoundsUpdated = false; 319 mExpectedFinalBounds = null; 320 321 // Update 322 mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(0.5f)); 323 324 // Not started, not running, cancel reset 325 assertTrue(!mTarget.mCancelRequested); 326 327 // Stack/task bounds not updated 328 assertTrue(!mTarget.mBoundsUpdated); 329 330 // Callback made 331 assertTrue(mTarget.mAnimationEnded); 332 assertNull(mTarget.mAnimationEndFinalStackBounds); 333 334 return this; 335 } 336 337 BoundsAnimationDriver end() { 338 mAnimator.end(); 339 340 // Final stack bounds 341 assertEquals(mTo, mTarget.mStackBounds); 342 assertEquals(mExpectedFinalBounds, mTarget.mAnimationEndFinalStackBounds); 343 assertNull(mTarget.mTaskBounds); 344 345 return this; 346 } 347 348 BoundsAnimationDriver expectEnded(boolean schedulePipModeChanged, 349 boolean moveToFullscreen) { 350 // Callback made 351 assertTrue(mTarget.mAnimationEnded); 352 353 assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnEnd); 354 assertEquals(moveToFullscreen, mTarget.mMovedToFullscreen); 355 return this; 356 } 357 358 private Rect getLargerBounds(Rect r1, Rect r2) { 359 int r1Area = r1.width() * r1.height(); 360 int r2Area = r2.width() * r2.height(); 361 if (r1Area <= r2Area) { 362 return r2; 363 } else { 364 return r1; 365 } 366 } 367 } 368 369 // Constants 370 private static final boolean SCHEDULE_PIP_MODE_CHANGED = true; 371 private static final boolean MOVE_TO_FULLSCREEN = true; 372 private static final int DURATION = 100; 373 374 // Some dummy bounds to represent fullscreen and floating bounds 375 private static final Rect BOUNDS_FULL = new Rect(0, 0, 100, 100); 376 private static final Rect BOUNDS_FLOATING = new Rect(60, 60, 95, 95); 377 private static final Rect BOUNDS_SMALLER_FLOATING = new Rect(80, 80, 95, 95); 378 379 // Common 380 private MockAppTransition mMockAppTransition; 381 private MockValueAnimator mMockAnimator; 382 private TestBoundsAnimationTarget mTarget; 383 private BoundsAnimationController mController; 384 private BoundsAnimationDriver mDriver; 385 386 // Temp 387 private Rect mTmpRect = new Rect(); 388 389 @Override 390 public void setUp() throws Exception { 391 super.setUp(); 392 393 final Context context = InstrumentationRegistry.getTargetContext(); 394 final Handler handler = new Handler(Looper.getMainLooper()); 395 mMockAppTransition = new MockAppTransition(context); 396 mMockAnimator = new MockValueAnimator(); 397 mTarget = new TestBoundsAnimationTarget(); 398 mController = new BoundsAnimationController(context, mMockAppTransition, handler, null); 399 mDriver = new BoundsAnimationDriver(mController, mTarget); 400 } 401 402 /** BASE TRANSITIONS **/ 403 404 @UiThreadTest 405 @Test 406 public void testFullscreenToFloatingTransition() throws Exception { 407 mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) 408 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 409 .update(0f) 410 .update(0.5f) 411 .update(1f) 412 .end() 413 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 414 } 415 416 @UiThreadTest 417 @Test 418 public void testFloatingToFullscreenTransition() throws Exception { 419 mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) 420 .expectStarted(SCHEDULE_PIP_MODE_CHANGED) 421 .update(0f) 422 .update(0.5f) 423 .update(1f) 424 .end() 425 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); 426 } 427 428 @UiThreadTest 429 @Test 430 public void testFloatingToSmallerFloatingTransition() throws Exception { 431 mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING) 432 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 433 .update(0f) 434 .update(0.5f) 435 .update(1f) 436 .end() 437 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 438 } 439 440 @UiThreadTest 441 @Test 442 public void testFloatingToLargerFloatingTransition() throws Exception { 443 mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING) 444 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 445 .update(0f) 446 .update(0.5f) 447 .update(1f) 448 .end() 449 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 450 } 451 452 /** F->!F w/ CANCEL **/ 453 454 @UiThreadTest 455 @Test 456 public void testFullscreenToFloatingCancelFromTarget() throws Exception { 457 mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) 458 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 459 .update(0.25f) 460 .cancel() 461 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 462 } 463 464 @UiThreadTest 465 @Test 466 public void testFullscreenToFloatingCancelFromAnimationToSameBounds() throws Exception { 467 mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) 468 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 469 .update(0.25f) 470 .restart(BOUNDS_FLOATING) 471 .end() 472 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 473 } 474 475 @UiThreadTest 476 @Test 477 public void testFullscreenToFloatingCancelFromAnimationToFloatingBounds() throws Exception { 478 mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) 479 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 480 .update(0.25f) 481 .restart(BOUNDS_SMALLER_FLOATING) 482 .end() 483 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 484 } 485 486 @UiThreadTest 487 @Test 488 public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() throws Exception { 489 mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) 490 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 491 .update(0.25f) 492 .restart(BOUNDS_FULL) 493 .end() 494 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); 495 } 496 497 /** !F->F w/ CANCEL **/ 498 499 @UiThreadTest 500 @Test 501 public void testFloatingToFullscreenCancelFromTarget() throws Exception { 502 mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) 503 .expectStarted(SCHEDULE_PIP_MODE_CHANGED) 504 .update(0.25f) 505 .cancel() 506 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 507 } 508 509 @UiThreadTest 510 @Test 511 public void testFloatingToFullscreenCancelFromAnimationToSameBounds() throws Exception { 512 mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) 513 .expectStarted(SCHEDULE_PIP_MODE_CHANGED) 514 .update(0.25f) 515 .restart(BOUNDS_FULL) 516 .end() 517 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); 518 } 519 520 @UiThreadTest 521 @Test 522 public void testFloatingToFullscreenCancelFromAnimationToFloatingBounds() throws Exception { 523 mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) 524 .expectStarted(SCHEDULE_PIP_MODE_CHANGED) 525 .update(0.25f) 526 .restart(BOUNDS_SMALLER_FLOATING) 527 .end() 528 .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 529 } 530 531 /** !F->!F w/ CANCEL **/ 532 533 @UiThreadTest 534 @Test 535 public void testFloatingToSmallerFloatingCancelFromTarget() throws Exception { 536 mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_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 @UiThreadTest 544 @Test 545 public void testFloatingToLargerFloatingCancelFromTarget() throws Exception { 546 mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING) 547 .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) 548 .update(0.25f) 549 .cancel() 550 .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); 551 } 552 553 /** MISC **/ 554 555 @UiThreadTest 556 @Test 557 public void testBoundsAreCopied() throws Exception { 558 Rect from = new Rect(0, 0, 100, 100); 559 Rect to = new Rect(25, 25, 75, 75); 560 mDriver.start(from, to) 561 .update(0.25f) 562 .end(); 563 assertEquals(new Rect(0, 0, 100, 100), from); 564 assertEquals(new Rect(25, 25, 75, 75), to); 565 } 566 567 /** 568 * @return whether the task and stack bounds would be the same if they were at the same offset. 569 */ 570 private boolean assertEqualSizeAtOffset(Rect stackBounds, Rect taskBounds) { 571 mTmpRect.set(taskBounds); 572 mTmpRect.offsetTo(stackBounds.left, stackBounds.top); 573 return stackBounds.equals(mTmpRect); 574 } 575} 576