1 /* 2 * Copyright (C) 2018 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 17 package com.android.server.wm; 18 19 import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 20 import static android.view.Display.DEFAULT_DISPLAY; 21 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 22 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; 23 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; 24 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; 25 26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast; 27 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; 28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 29 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; 30 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; 32 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 33 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions; 34 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; 35 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; 36 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION; 37 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP; 38 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; 39 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM; 40 41 import static org.junit.Assert.assertEquals; 42 import static org.junit.Assert.assertFalse; 43 import static org.junit.Assert.assertNotEquals; 44 import static org.junit.Assert.assertNotNull; 45 import static org.junit.Assert.assertTrue; 46 import static org.junit.Assert.fail; 47 import static org.mockito.ArgumentMatchers.any; 48 import static org.mockito.ArgumentMatchers.anyBoolean; 49 import static org.mockito.ArgumentMatchers.anyInt; 50 import static org.mockito.ArgumentMatchers.eq; 51 import static org.mockito.Mockito.clearInvocations; 52 import static org.mockito.Mockito.mock; 53 import static org.mockito.Mockito.never; 54 import static org.mockito.Mockito.reset; 55 56 import android.content.pm.ActivityInfo; 57 import android.content.res.Configuration; 58 import android.os.Binder; 59 import android.os.IBinder; 60 import android.os.IInterface; 61 import android.platform.test.annotations.Presubmit; 62 import android.util.SparseBooleanArray; 63 import android.view.IRecentsAnimationRunner; 64 import android.view.SurfaceControl; 65 import android.view.WindowManager.LayoutParams; 66 import android.window.TaskSnapshot; 67 68 import androidx.test.filters.SmallTest; 69 70 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; 71 72 import com.google.common.truth.Truth; 73 74 import org.junit.Before; 75 import org.junit.Test; 76 import org.junit.runner.RunWith; 77 import org.mockito.Mock; 78 import org.mockito.MockitoAnnotations; 79 80 import java.util.ArrayList; 81 82 /** 83 * Build/Install/Run: 84 * atest WmTests:RecentsAnimationControllerTest 85 */ 86 @SmallTest 87 @Presubmit 88 @RunWith(WindowTestRunner.class) 89 public class RecentsAnimationControllerTest extends WindowTestsBase { 90 91 @Mock SurfaceControl mMockLeash; 92 @Mock SurfaceControl.Transaction mMockTransaction; 93 @Mock OnAnimationFinishedCallback mFinishedCallback; 94 @Mock IRecentsAnimationRunner mMockRunner; 95 @Mock RecentsAnimationController.RecentsAnimationCallbacks mAnimationCallbacks; 96 @Mock TaskSnapshot mMockTaskSnapshot; 97 private RecentsAnimationController mController; 98 private Task mRootHomeTask; 99 100 @Before setUp()101 public void setUp() throws Exception { 102 MockitoAnnotations.initMocks(this); 103 doNothing().when(mWm.mRoot).performSurfacePlacement(); 104 when(mMockRunner.asBinder()).thenReturn(new Binder()); 105 mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks, 106 DEFAULT_DISPLAY)); 107 mRootHomeTask = mDefaultDisplay.getDefaultTaskDisplayArea().getRootHomeTask(); 108 assertNotNull(mRootHomeTask); 109 } 110 111 @Test testRemovedBeforeStarted_expectCanceled()112 public void testRemovedBeforeStarted_expectCanceled() throws Exception { 113 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 114 AnimationAdapter adapter = mController.addAnimation(activity.getTask(), 115 false /* isRecentTaskInvisible */); 116 adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS, 117 mFinishedCallback); 118 119 // The activity doesn't contain window so the animation target cannot be created. 120 mController.startAnimation(); 121 122 // Verify that the finish callback to reparent the leash is called 123 verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), eq(adapter)); 124 // Verify the animation canceled callback to the app was made 125 verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */); 126 verifyNoMoreInteractionsExceptAsBinder(mMockRunner); 127 } 128 129 @Test testCancelAfterRemove_expectIgnored()130 public void testCancelAfterRemove_expectIgnored() { 131 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 132 AnimationAdapter adapter = mController.addAnimation(activity.getTask(), 133 false /* isRecentTaskInvisible */); 134 adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS, 135 mFinishedCallback); 136 137 // Remove the app window so that the animation target can not be created 138 activity.removeImmediately(); 139 mController.startAnimation(); 140 mController.cleanupAnimation(REORDER_KEEP_IN_PLACE); 141 try { 142 mController.cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "test"); 143 } catch (Exception e) { 144 fail("Unexpected failure when canceling animation after finishing it"); 145 } 146 } 147 148 @Test testIncludedApps_expectTargetAndVisible()149 public void testIncludedApps_expectTargetAndVisible() { 150 mWm.setRecentsAnimationController(mController); 151 final ActivityRecord homeActivity = createHomeActivity(); 152 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 153 final ActivityRecord hiddenActivity = createActivityRecord(mDefaultDisplay); 154 hiddenActivity.setVisible(false); 155 mDefaultDisplay.getConfiguration().windowConfiguration.setRotation( 156 mDefaultDisplay.getRotation()); 157 initializeRecentsAnimationController(mController, homeActivity); 158 159 // Ensure that we are animating the target activity as well 160 assertTrue(mController.isAnimatingTask(homeActivity.getTask())); 161 assertTrue(mController.isAnimatingTask(activity.getTask())); 162 assertFalse(mController.isAnimatingTask(hiddenActivity.getTask())); 163 } 164 165 @Test testLaunchAndStartRecents_expectTargetAndVisible()166 public void testLaunchAndStartRecents_expectTargetAndVisible() throws Exception { 167 mWm.setRecentsAnimationController(mController); 168 final ActivityRecord homeActivity = createHomeActivity(); 169 final Task task = createTask(mDefaultDisplay); 170 // Emulate that activity1 has just launched activity2, but app transition has not yet been 171 // executed. 172 final ActivityRecord activity1 = createActivityRecord(task); 173 activity1.setVisible(true); 174 activity1.setVisibleRequested(false); 175 activity1.addWindow(createWindowState(new LayoutParams(TYPE_BASE_APPLICATION), activity1)); 176 177 final ActivityRecord activity2 = createActivityRecord(task); 178 activity2.setVisible(false); 179 activity2.setVisibleRequested(true); 180 181 mDefaultDisplay.getConfiguration().windowConfiguration.setRotation( 182 mDefaultDisplay.getRotation()); 183 initializeRecentsAnimationController(mController, homeActivity); 184 mController.startAnimation(); 185 verify(mMockRunner, never()).onAnimationCanceled(null /* taskIds */, 186 null /* taskSnapshots */); 187 } 188 189 @Test testWallpaperIncluded_expectTarget()190 public void testWallpaperIncluded_expectTarget() throws Exception { 191 mWm.setRecentsAnimationController(mController); 192 final ActivityRecord homeActivity = createHomeActivity(); 193 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 194 final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); 195 activity.addWindow(win1); 196 final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, 197 mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */); 198 spyOn(mDefaultDisplay.mWallpaperController); 199 doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible(); 200 201 mDefaultDisplay.getConfiguration().windowConfiguration.setRotation( 202 mDefaultDisplay.getRotation()); 203 initializeRecentsAnimationController(mController, homeActivity); 204 mController.startAnimation(); 205 206 // Ensure that we are animating the app and wallpaper target 207 assertTrue(mController.isAnimatingTask(activity.getTask())); 208 assertTrue(mController.isAnimatingWallpaper(wallpaperWindowToken)); 209 } 210 211 @Test testWallpaperAnimatorCanceled_expectAnimationKeepsRunning()212 public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception { 213 mWm.setRecentsAnimationController(mController); 214 final ActivityRecord homeActivity = createHomeActivity(); 215 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 216 final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); 217 activity.addWindow(win1); 218 final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, 219 mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */); 220 spyOn(mDefaultDisplay.mWallpaperController); 221 doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible(); 222 223 mDefaultDisplay.getConfiguration().windowConfiguration.setRotation( 224 mDefaultDisplay.getRotation()); 225 initializeRecentsAnimationController(mController, homeActivity); 226 mController.startAnimation(); 227 228 // Cancel the animation and ensure the controller is still running 229 wallpaperWindowToken.cancelAnimation(); 230 assertTrue(mController.isAnimatingTask(activity.getTask())); 231 assertFalse(mController.isAnimatingWallpaper(wallpaperWindowToken)); 232 verify(mMockRunner, never()).onAnimationCanceled(null /* taskIds */, 233 null /* taskSnapshots */); 234 } 235 236 @Test testFinish_expectTargetAndWallpaperAdaptersRemoved()237 public void testFinish_expectTargetAndWallpaperAdaptersRemoved() { 238 mWm.setRecentsAnimationController(mController); 239 final ActivityRecord homeActivity = createHomeActivity(); 240 final WindowState hwin1 = createWindow(null, TYPE_BASE_APPLICATION, homeActivity, "hwin1"); 241 homeActivity.addWindow(hwin1); 242 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 243 final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); 244 activity.addWindow(win1); 245 final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, 246 mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */); 247 spyOn(mDefaultDisplay.mWallpaperController); 248 doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible(); 249 250 // Start and finish the animation 251 initializeRecentsAnimationController(mController, homeActivity); 252 mController.startAnimation(); 253 254 assertTrue(mController.isAnimatingTask(homeActivity.getTask())); 255 assertTrue(mController.isAnimatingTask(activity.getTask())); 256 257 // Reset at this point since we may remove adapters that couldn't be created 258 clearInvocations(mController); 259 mController.cleanupAnimation(REORDER_MOVE_TO_TOP); 260 261 // Ensure that we remove the task (home & app) and wallpaper adapters 262 verify(mController, times(2)).removeAnimation(any()); 263 verify(mController, times(1)).removeWallpaperAnimation(any()); 264 } 265 266 @Test testDeferCancelAnimation()267 public void testDeferCancelAnimation() throws Exception { 268 mWm.setRecentsAnimationController(mController); 269 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 270 final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); 271 activity.addWindow(win1); 272 assertEquals(activity.getTask().getTopVisibleActivity(), activity); 273 assertEquals(activity.findMainWindow(), win1); 274 275 mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */); 276 assertTrue(mController.isAnimatingTask(activity.getTask())); 277 278 mController.setDeferredCancel(true /* deferred */, false /* screenshot */); 279 mController.cancelAnimationWithScreenshot(false /* screenshot */); 280 verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */); 281 282 // Simulate the app transition finishing 283 mController.mAppTransitionListener.onAppTransitionStartingLocked(0, 0); 284 verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false); 285 } 286 287 @Test testDeferCancelAnimationWithScreenShot()288 public void testDeferCancelAnimationWithScreenShot() throws Exception { 289 mWm.setRecentsAnimationController(mController); 290 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 291 final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); 292 activity.addWindow(win1); 293 assertEquals(activity.getTask().getTopVisibleActivity(), activity); 294 assertEquals(activity.findMainWindow(), win1); 295 296 RecentsAnimationController.TaskAnimationAdapter adapter = mController.addAnimation( 297 activity.getTask(), false /* isRecentTaskInvisible */); 298 assertTrue(mController.isAnimatingTask(activity.getTask())); 299 300 spyOn(mWm.mTaskSnapshotController); 301 doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(), 302 anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */); 303 mController.setDeferredCancel(true /* deferred */, true /* screenshot */); 304 mController.cancelAnimationWithScreenshot(true /* screenshot */); 305 verify(mMockRunner).onAnimationCanceled(any(int[].class) /* taskIds */, 306 any(TaskSnapshot[].class) /* taskSnapshots */); 307 308 // Continue the animation (simulating a call to cleanupScreenshot()) 309 mController.continueDeferredCancelAnimation(); 310 verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false); 311 } 312 313 @Test testShouldAnimateWhenNoCancelWithDeferredScreenshot()314 public void testShouldAnimateWhenNoCancelWithDeferredScreenshot() { 315 mWm.setRecentsAnimationController(mController); 316 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 317 final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); 318 activity.addWindow(win1); 319 assertEquals(activity.getTask().getTopVisibleActivity(), activity); 320 assertEquals(activity.findMainWindow(), win1); 321 322 mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */); 323 assertTrue(mController.isAnimatingTask(activity.getTask())); 324 325 // Assume activity transition should animate when no 326 // IRecentsAnimationController#setDeferCancelUntilNextTransition called. 327 assertFalse(mController.shouldDeferCancelWithScreenshot()); 328 assertTrue(activity.shouldAnimate()); 329 } 330 331 @Test testBinderDiedAfterCancelWithDeferredScreenshot()332 public void testBinderDiedAfterCancelWithDeferredScreenshot() throws Exception { 333 mWm.setRecentsAnimationController(mController); 334 final ActivityRecord homeActivity = createHomeActivity(); 335 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 336 final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); 337 activity.addWindow(win1); 338 339 initializeRecentsAnimationController(mController, homeActivity); 340 mController.setWillFinishToHome(true); 341 342 // Verify cancel is called with a snapshot and that we've created an overlay 343 spyOn(mWm.mTaskSnapshotController); 344 doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(), 345 anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */); 346 mController.cancelAnimationWithScreenshot(true /* screenshot */); 347 verify(mMockRunner).onAnimationCanceled(any(), any()); 348 349 // Simulate process crashing and ensure the animation is still canceled 350 mController.binderDied(); 351 verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false); 352 } 353 354 @Test testRecentViewInFixedPortraitWhenTopAppInLandscape()355 public void testRecentViewInFixedPortraitWhenTopAppInLandscape() { 356 unblockDisplayRotation(mDefaultDisplay); 357 mWm.setRecentsAnimationController(mController); 358 359 final ActivityRecord homeActivity = createHomeActivity(); 360 homeActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 361 362 final ActivityRecord landActivity = createActivityRecord(mDefaultDisplay); 363 landActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 364 final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, landActivity, "win1"); 365 landActivity.addWindow(win1); 366 367 assertEquals(landActivity.getTask().getTopVisibleActivity(), landActivity); 368 assertEquals(landActivity.findMainWindow(), win1); 369 370 // Ensure that the display is in Landscape 371 landActivity.onDescendantOrientationChanged(landActivity); 372 assertEquals(Configuration.ORIENTATION_LANDSCAPE, 373 mDefaultDisplay.getConfiguration().orientation); 374 375 initializeRecentsAnimationController(mController, homeActivity); 376 377 assertTrue(mDefaultDisplay.isFixedRotationLaunchingApp(homeActivity)); 378 379 // Check that the home app is in portrait 380 assertEquals(Configuration.ORIENTATION_PORTRAIT, 381 homeActivity.getConfiguration().orientation); 382 383 // Home activity won't become top (return to landActivity), so the top rotated record should 384 // be cleared. 385 mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); 386 assertFalse(mDefaultDisplay.isFixedRotationLaunchingApp(homeActivity)); 387 assertFalse(mDefaultDisplay.hasTopFixedRotationLaunchingApp()); 388 // The transform should keep until the transition is done, so the restored configuration 389 // won't be sent to activity and cause unnecessary configuration change. 390 assertTrue(homeActivity.hasFixedRotationTransform()); 391 392 // In real case the transition will be executed from RecentsAnimation#finishAnimation. 393 mDefaultDisplay.mFixedRotationTransitionListener.onAppTransitionFinishedLocked( 394 homeActivity.token); 395 assertFalse(homeActivity.hasFixedRotationTransform()); 396 } 397 prepareFixedRotationLaunchingAppWithRecentsAnim()398 private ActivityRecord prepareFixedRotationLaunchingAppWithRecentsAnim() { 399 final ActivityRecord homeActivity = createHomeActivity(); 400 homeActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 401 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 402 // Add a window so it can be animated by the recents. 403 final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); 404 activity.addWindow(win); 405 // Assume an activity is launching to different rotation. 406 mDefaultDisplay.setFixedRotationLaunchingApp(activity, 407 (mDefaultDisplay.getRotation() + 1) % 4); 408 409 assertTrue(activity.hasFixedRotationTransform()); 410 assertTrue(mDefaultDisplay.isFixedRotationLaunchingApp(activity)); 411 412 // Before the transition is done, the recents animation is triggered. 413 initializeRecentsAnimationController(mController, homeActivity); 414 assertFalse(homeActivity.hasFixedRotationTransform()); 415 assertTrue(mController.isAnimatingTask(activity.getTask())); 416 417 return activity; 418 } 419 420 @Test testClearFixedRotationLaunchingAppAfterCleanupAnimation()421 public void testClearFixedRotationLaunchingAppAfterCleanupAnimation() { 422 final ActivityRecord activity = prepareFixedRotationLaunchingAppWithRecentsAnim(); 423 424 // Simulate giving up the swipe up gesture to keep the original activity as top. 425 mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); 426 // The rotation transform should be cleared after updating orientation with display. 427 assertTopFixedRotationLaunchingAppCleared(activity); 428 429 // Simulate swiping up recents (home) in different rotation. 430 final ActivityRecord home = mDefaultDisplay.getDefaultTaskDisplayArea().getHomeActivity(); 431 startRecentsInDifferentRotation(home); 432 433 // If the recents activity becomes the top running activity (e.g. the original top activity 434 // is either finishing or moved to back during recents animation), the display orientation 435 // will be determined by it so the fixed rotation must be cleared. 436 activity.finishing = true; 437 mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); 438 assertTopFixedRotationLaunchingAppCleared(home); 439 440 startRecentsInDifferentRotation(home); 441 // Assume recents activity becomes invisible for some reason (e.g. screen off). 442 home.setVisible(false); 443 mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); 444 // Although there won't be a transition finish callback, the fixed rotation must be cleared. 445 assertTopFixedRotationLaunchingAppCleared(home); 446 } 447 448 @Test testKeepFixedRotationWhenMovingRecentsToTop()449 public void testKeepFixedRotationWhenMovingRecentsToTop() { 450 final ActivityRecord activity = prepareFixedRotationLaunchingAppWithRecentsAnim(); 451 // Assume a transition animation has started running before recents animation. Then the 452 // activity will receive onAnimationFinished that notifies app transition finished when 453 // removing the recents animation of task. 454 activity.getTask().getAnimationSources().add(activity); 455 456 // Simulate swiping to home/recents before the transition is done. 457 mController.cleanupAnimation(REORDER_MOVE_TO_TOP); 458 // The rotation transform should be preserved. In real case, it will be cleared by the next 459 // move-to-top transition. 460 assertTrue(activity.hasFixedRotationTransform()); 461 } 462 463 @Test testCheckRotationAfterCleanup()464 public void testCheckRotationAfterCleanup() { 465 mWm.setRecentsAnimationController(mController); 466 spyOn(mDisplayContent.mFixedRotationTransitionListener); 467 final ActivityRecord recents = mock(ActivityRecord.class); 468 recents.setOverrideOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); 469 doReturn(ORIENTATION_PORTRAIT).when(recents) 470 .getRequestedConfigurationOrientation(anyBoolean()); 471 mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recents); 472 473 // Rotation update is skipped while the recents animation is running. 474 final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation(); 475 final int topOrientation = DisplayContentTests.getRotatedOrientation(mDefaultDisplay); 476 assertFalse(displayRotation.updateOrientation(topOrientation, false /* forceUpdate */)); 477 assertEquals(ActivityInfo.SCREEN_ORIENTATION_UNSET, displayRotation.getLastOrientation()); 478 final int prevRotation = mDisplayContent.getRotation(); 479 mWm.cleanupRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); 480 481 // In real case, it is called from RecentsAnimation#finishAnimation -> continueWindowLayout 482 // -> handleAppTransitionReady -> add FINISH_LAYOUT_REDO_CONFIG, and DisplayContent# 483 // applySurfaceChangesTransaction will call updateOrientation for FINISH_LAYOUT_REDO_CONFIG. 484 assertTrue(displayRotation.updateOrientation(topOrientation, false /* forceUpdate */)); 485 // The display should be updated to the changed orientation after the animation is finished. 486 assertNotEquals(displayRotation.getRotation(), prevRotation); 487 } 488 489 @Test testWallpaperHasFixedRotationApplied()490 public void testWallpaperHasFixedRotationApplied() { 491 unblockDisplayRotation(mDefaultDisplay); 492 mWm.setRecentsAnimationController(mController); 493 494 // Create a portrait home activity, a wallpaper and a landscape activity displayed on top. 495 final ActivityRecord homeActivity = createHomeActivity(); 496 homeActivity.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 497 498 final WindowState homeWindow = createWindow(null, TYPE_BASE_APPLICATION, homeActivity, 499 "homeWindow"); 500 makeWindowVisible(homeWindow); 501 homeActivity.addWindow(homeWindow); 502 homeWindow.getAttrs().flags |= FLAG_SHOW_WALLPAPER; 503 504 // Landscape application 505 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 506 final WindowState applicationWindow = createWindow(null, TYPE_BASE_APPLICATION, activity, 507 "applicationWindow"); 508 activity.addWindow(applicationWindow); 509 activity.setOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 510 511 // Wallpaper 512 final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, 513 mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */); 514 final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken, 515 "wallpaperWindow"); 516 517 // Make sure the landscape activity is on top and the display is in landscape 518 activity.moveFocusableActivityToTop("test"); 519 mDefaultDisplay.getConfiguration().windowConfiguration.setRotation( 520 mDefaultDisplay.getRotation()); 521 522 spyOn(mDefaultDisplay.mWallpaperController); 523 doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible(); 524 525 // Start the recents animation 526 initializeRecentsAnimationController(mController, homeActivity); 527 528 mDefaultDisplay.mWallpaperController.adjustWallpaperWindows(); 529 530 // Check preconditions 531 ArrayList<WallpaperWindowToken> wallpapers = new ArrayList<>(1); 532 mDefaultDisplay.forAllWallpaperWindows(wallpapers::add); 533 534 Truth.assertThat(wallpapers).hasSize(1); 535 Truth.assertThat(wallpapers.get(0).getTopChild()).isEqualTo(wallpaperWindow); 536 537 // Actual check 538 assertEquals(Configuration.ORIENTATION_PORTRAIT, 539 wallpapers.get(0).getConfiguration().orientation); 540 541 mController.cleanupAnimation(REORDER_MOVE_TO_TOP); 542 // The transform state should keep because we expect to listen the signal from the 543 // transition executed by moving the task to front. 544 assertTrue(homeActivity.hasFixedRotationTransform()); 545 assertTrue(mDefaultDisplay.isFixedRotationLaunchingApp(homeActivity)); 546 547 mDefaultDisplay.mFixedRotationTransitionListener.onAppTransitionFinishedLocked( 548 homeActivity.token); 549 // Wallpaper's transform state should be cleared with home. 550 assertFalse(homeActivity.hasFixedRotationTransform()); 551 assertFalse(wallpaperWindowToken.hasFixedRotationTransform()); 552 } 553 554 @Test testIsAnimatingByRecents()555 public void testIsAnimatingByRecents() { 556 final ActivityRecord homeActivity = createHomeActivity(); 557 final Task rootTask = createTask(mDefaultDisplay); 558 final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */); 559 final Task leafTask = createTaskInRootTask(childTask, 0 /* userId */); 560 spyOn(leafTask); 561 doReturn(true).when(leafTask).isVisible(); 562 563 initializeRecentsAnimationController(mController, homeActivity); 564 565 // Verify RecentsAnimationController will animate visible leaf task by default. 566 verify(mController).addAnimation(eq(leafTask), anyBoolean(), anyBoolean(), any()); 567 assertTrue(leafTask.isAnimatingByRecents()); 568 569 // Make sure isAnimatingByRecents will also return true when it called by the parent task. 570 assertTrue(rootTask.isAnimatingByRecents()); 571 assertTrue(childTask.isAnimatingByRecents()); 572 } 573 574 @Test testRestoreNavBarWhenEnteringRecents_expectAnimation()575 public void testRestoreNavBarWhenEnteringRecents_expectAnimation() { 576 setupForShouldAttachNavBarDuringTransition(); 577 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 578 final ActivityRecord homeActivity = createHomeActivity(); 579 initializeRecentsAnimationController(mController, homeActivity); 580 581 final WindowToken navToken = mDefaultDisplay.getDisplayPolicy().getNavigationBar().mToken; 582 final SurfaceControl.Transaction transaction = navToken.getPendingTransaction(); 583 584 verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled( 585 eq(mDefaultDisplay.mDisplayId), eq(false)); 586 verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl()); 587 verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE); 588 assertTrue(mController.isNavigationBarAttachedToApp()); 589 590 mController.cleanupAnimation(REORDER_MOVE_TO_TOP); 591 verify(mController).restoreNavigationBarFromApp(eq(true)); 592 verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled( 593 eq(mDefaultDisplay.mDisplayId), eq(true)); 594 verify(transaction).setLayer(navToken.getSurfaceControl(), 0); 595 assertFalse(mController.isNavigationBarAttachedToApp()); 596 assertTrue(navToken.isAnimating(ANIMATION_TYPE_TOKEN_TRANSFORM)); 597 } 598 599 @Test testRestoreNavBarWhenBackToApp_expectNoAnimation()600 public void testRestoreNavBarWhenBackToApp_expectNoAnimation() { 601 setupForShouldAttachNavBarDuringTransition(); 602 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 603 final ActivityRecord homeActivity = createHomeActivity(); 604 initializeRecentsAnimationController(mController, homeActivity); 605 606 final WindowToken navToken = mDefaultDisplay.getDisplayPolicy().getNavigationBar().mToken; 607 final SurfaceControl.Transaction transaction = navToken.getPendingTransaction(); 608 609 verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled( 610 eq(mDefaultDisplay.mDisplayId), eq(false)); 611 verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl()); 612 verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE); 613 assertTrue(mController.isNavigationBarAttachedToApp()); 614 615 final WindowContainer parent = navToken.getParent(); 616 617 mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); 618 verify(mController).restoreNavigationBarFromApp(eq(false)); 619 verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled( 620 eq(mDefaultDisplay.mDisplayId), eq(true)); 621 verify(transaction).setLayer(navToken.getSurfaceControl(), 0); 622 verify(transaction).reparent(navToken.getSurfaceControl(), parent.getSurfaceControl()); 623 assertFalse(mController.isNavigationBarAttachedToApp()); 624 assertFalse(navToken.isAnimating(ANIMATION_TYPE_TOKEN_TRANSFORM)); 625 } 626 627 @Test testAddTaskToTargets_expectAnimation()628 public void testAddTaskToTargets_expectAnimation() { 629 setupForShouldAttachNavBarDuringTransition(); 630 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 631 final ActivityRecord homeActivity = createHomeActivity(); 632 initializeRecentsAnimationController(mController, homeActivity); 633 634 final WindowToken navToken = mDefaultDisplay.getDisplayPolicy().getNavigationBar().mToken; 635 final SurfaceControl.Transaction transaction = navToken.getPendingTransaction(); 636 637 verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled( 638 eq(mDefaultDisplay.mDisplayId), eq(false)); 639 verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl()); 640 verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE); 641 assertTrue(mController.isNavigationBarAttachedToApp()); 642 643 final WindowContainer parent = navToken.getParent(); 644 645 mController.addTaskToTargets(createTask(mDefaultDisplay), (type, anim) -> {}); 646 mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); 647 verify(mController).restoreNavigationBarFromApp(eq(true)); 648 verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled( 649 eq(mDefaultDisplay.mDisplayId), eq(true)); 650 verify(transaction).setLayer(navToken.getSurfaceControl(), 0); 651 assertFalse(mController.isNavigationBarAttachedToApp()); 652 assertTrue(navToken.isAnimating(ANIMATION_TYPE_TOKEN_TRANSFORM)); 653 } 654 655 @Test testNotAttachNavigationBar_controlledByFadeRotationAnimation()656 public void testNotAttachNavigationBar_controlledByFadeRotationAnimation() { 657 setupForShouldAttachNavBarDuringTransition(); 658 AsyncRotationController mockController = 659 mock(AsyncRotationController.class); 660 doReturn(mockController).when(mDefaultDisplay).getAsyncRotationController(); 661 final ActivityRecord homeActivity = createHomeActivity(); 662 initializeRecentsAnimationController(mController, homeActivity); 663 assertFalse(mController.isNavigationBarAttachedToApp()); 664 } 665 666 @Test testAttachNavBarInSplitScreenMode()667 public void testAttachNavBarInSplitScreenMode() { 668 setupForShouldAttachNavBarDuringTransition(); 669 TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm); 670 final ActivityRecord primary = createActivityRecordWithParentTask( 671 organizer.createTaskToPrimary(true)); 672 final ActivityRecord secondary = createActivityRecordWithParentTask( 673 organizer.createTaskToSecondary(true)); 674 final ActivityRecord homeActivity = createHomeActivity(); 675 homeActivity.setVisibility(true); 676 initializeRecentsAnimationController(mController, homeActivity); 677 678 WindowState navWindow = mController.getNavigationBarWindow(); 679 final WindowToken navToken = navWindow.mToken; 680 final SurfaceControl.Transaction transaction = navToken.getPendingTransaction(); 681 682 verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled( 683 eq(mDefaultDisplay.mDisplayId), eq(false)); 684 verify(navWindow).setSurfaceTranslationY(-secondary.getBounds().top); 685 verify(transaction).reparent(navToken.getSurfaceControl(), secondary.getSurfaceControl()); 686 assertTrue(mController.isNavigationBarAttachedToApp()); 687 reset(navWindow); 688 689 mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); 690 final WindowContainer parent = navToken.getParent(); 691 verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled( 692 eq(mDefaultDisplay.mDisplayId), eq(true)); 693 verify(navWindow).setSurfaceTranslationY(0); 694 verify(transaction).reparent(navToken.getSurfaceControl(), parent.getSurfaceControl()); 695 verify(mController).restoreNavigationBarFromApp(eq(false)); 696 assertFalse(mController.isNavigationBarAttachedToApp()); 697 } 698 699 @Test testCleanupAnimation_expectExitAnimationDone()700 public void testCleanupAnimation_expectExitAnimationDone() { 701 mWm.setRecentsAnimationController(mController); 702 final ActivityRecord homeActivity = createHomeActivity(); 703 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 704 final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); 705 activity.addWindow(win1); 706 707 initializeRecentsAnimationController(mController, homeActivity); 708 mController.startAnimation(); 709 710 spyOn(win1); 711 spyOn(win1.mWinAnimator); 712 // Simulate when the window is exiting and cleanupAnimation invoked 713 // (e.g. screen off during RecentsAnimation animating), will expect the window receives 714 // onExitAnimationDone to destroy the surface when the removal is allowed. 715 win1.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class); 716 win1.mHasSurface = true; 717 win1.mAnimatingExit = true; 718 win1.mRemoveOnExit = true; 719 win1.mWindowRemovalAllowed = true; 720 mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); 721 verify(win1).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), any()); 722 verify(win1).onExitAnimationDone(); 723 verify(win1).destroySurface(eq(false), eq(false)); 724 assertFalse(win1.mAnimatingExit); 725 assertFalse(win1.mHasSurface); 726 } 727 728 @Test testCancelForRotation_ReorderToTop()729 public void testCancelForRotation_ReorderToTop() throws Exception { 730 mWm.setRecentsAnimationController(mController); 731 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 732 final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); 733 activity.addWindow(win1); 734 735 mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */); 736 mController.setWillFinishToHome(true); 737 mController.cancelAnimationForDisplayChange(); 738 739 verify(mMockRunner).onAnimationCanceled(any(), any()); 740 verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_TOP, false); 741 } 742 743 @Test testCancelForRotation_ReorderToOriginalPosition()744 public void testCancelForRotation_ReorderToOriginalPosition() throws Exception { 745 mWm.setRecentsAnimationController(mController); 746 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 747 final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); 748 activity.addWindow(win1); 749 750 mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */); 751 mController.setWillFinishToHome(false); 752 mController.cancelAnimationForDisplayChange(); 753 754 verify(mMockRunner).onAnimationCanceled(any(), any()); 755 verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_ORIGINAL_POSITION, false); 756 } 757 758 @Test testCancelForStartHome()759 public void testCancelForStartHome() throws Exception { 760 mWm.setRecentsAnimationController(mController); 761 final ActivityRecord homeActivity = createHomeActivity(); 762 final ActivityRecord activity = createActivityRecord(mDefaultDisplay); 763 final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); 764 activity.addWindow(win1); 765 766 initializeRecentsAnimationController(mController, homeActivity); 767 mController.setWillFinishToHome(true); 768 769 // Verify cancel is called with a snapshot and that we've created an overlay 770 spyOn(mWm.mTaskSnapshotController); 771 doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(), 772 anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */); 773 mController.cancelAnimationForHomeStart(); 774 verify(mMockRunner).onAnimationCanceled(any(), any()); 775 776 // Continue the animation (simulating a call to cleanupScreenshot()) 777 mController.continueDeferredCancelAnimation(); 778 verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_TOP, false); 779 780 // Assume home was moved to front so will-be-top callback should not be called. 781 homeActivity.moveFocusableActivityToTop("test"); 782 spyOn(mDefaultDisplay.mFixedRotationTransitionListener); 783 mController.cleanupAnimation(REORDER_MOVE_TO_TOP); 784 verify(mDefaultDisplay.mFixedRotationTransitionListener, never()).notifyRecentsWillBeTop(); 785 } 786 createHomeActivity()787 private ActivityRecord createHomeActivity() { 788 final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService) 789 .setParentTask(mRootHomeTask) 790 .setCreateTask(true) 791 .build(); 792 // Avoid {@link RecentsAnimationController.TaskAnimationAdapter#createRemoteAnimationTarget} 793 // returning null when calling {@link RecentsAnimationController#createAppAnimations}. 794 homeActivity.setVisibility(true); 795 return homeActivity; 796 } 797 startRecentsInDifferentRotation(ActivityRecord recentsActivity)798 private void startRecentsInDifferentRotation(ActivityRecord recentsActivity) { 799 final DisplayContent displayContent = recentsActivity.mDisplayContent; 800 displayContent.setFixedRotationLaunchingApp(recentsActivity, 801 (displayContent.getRotation() + 1) % 4); 802 mController = new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks, 803 displayContent.getDisplayId()); 804 initializeRecentsAnimationController(mController, recentsActivity); 805 assertTrue(recentsActivity.hasFixedRotationTransform()); 806 } 807 assertTopFixedRotationLaunchingAppCleared(ActivityRecord activity)808 private static void assertTopFixedRotationLaunchingAppCleared(ActivityRecord activity) { 809 assertFalse(activity.hasFixedRotationTransform()); 810 assertFalse(activity.mDisplayContent.hasTopFixedRotationLaunchingApp()); 811 } 812 setupForShouldAttachNavBarDuringTransition()813 private void setupForShouldAttachNavBarDuringTransition() { 814 final WindowState navBar = spy(createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar")); 815 mDefaultDisplay.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs); 816 mWm.setRecentsAnimationController(mController); 817 doReturn(navBar).when(mController).getNavigationBarWindow(); 818 final DisplayPolicy displayPolicy = spy(mDefaultDisplay.getDisplayPolicy()); 819 doReturn(displayPolicy).when(mDefaultDisplay).getDisplayPolicy(); 820 doReturn(true).when(displayPolicy).shouldAttachNavBarToAppDuringTransition(); 821 } 822 initializeRecentsAnimationController(RecentsAnimationController controller, ActivityRecord activity)823 private static void initializeRecentsAnimationController(RecentsAnimationController controller, 824 ActivityRecord activity) { 825 controller.initialize(activity.getActivityType(), new SparseBooleanArray(), activity); 826 } 827 verifyNoMoreInteractionsExceptAsBinder(IInterface binder)828 private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) { 829 verify(binder, atLeast(0)).asBinder(); 830 verifyNoMoreInteractions(binder); 831 } 832 } 833