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.app.ActivityManager.PROCESS_STATE_HOME; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 23 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 24 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 25 26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; 27 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; 28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; 29 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 30 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; 31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; 32 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 33 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; 34 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 35 import static com.android.server.wm.ActivityRecord.State.PAUSED; 36 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; 37 import static com.android.server.wm.WindowContainer.POSITION_TOP; 38 39 import static com.google.common.truth.Truth.assertThat; 40 41 import static org.junit.Assert.assertEquals; 42 import static org.junit.Assert.assertFalse; 43 import static org.junit.Assert.assertTrue; 44 import static org.mockito.ArgumentMatchers.any; 45 import static org.mockito.ArgumentMatchers.anyBoolean; 46 import static org.mockito.ArgumentMatchers.anyInt; 47 48 import android.content.ComponentName; 49 import android.content.Intent; 50 import android.content.pm.ActivityInfo; 51 import android.content.pm.ApplicationInfo; 52 import android.platform.test.annotations.Presubmit; 53 import android.view.IRecentsAnimationRunner; 54 55 import androidx.test.filters.MediumTest; 56 57 import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks; 58 59 import org.junit.Before; 60 import org.junit.Test; 61 import org.junit.runner.RunWith; 62 63 /** 64 * Build/Install/Run: 65 * atest WmTests:RecentsAnimationTest 66 */ 67 @MediumTest 68 @Presubmit 69 @RunWith(WindowTestRunner.class) 70 public class RecentsAnimationTest extends WindowTestsBase { 71 72 private static final int TEST_USER_ID = 100; 73 74 private final ComponentName mRecentsComponent = 75 new ComponentName(mContext.getPackageName(), "RecentsActivity"); 76 private RecentsAnimationController mRecentsAnimationController; 77 78 @Before setUp()79 public void setUp() throws Exception { 80 mRecentsAnimationController = mock(RecentsAnimationController.class); 81 mAtm.mWindowManager.setRecentsAnimationController(mRecentsAnimationController); 82 doNothing().when(mAtm.mWindowManager).initializeRecentsAnimation( 83 anyInt(), any(), any(), anyInt(), any(), any()); 84 85 final RecentTasks recentTasks = mAtm.getRecentTasks(); 86 spyOn(recentTasks); 87 doReturn(mRecentsComponent).when(recentTasks).getRecentsComponent(); 88 } 89 90 @Test testRecentsActivityVisiblility()91 public void testRecentsActivityVisiblility() { 92 TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); 93 Task recentsStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, 94 ACTIVITY_TYPE_RECENTS, true /* onTop */); 95 final WindowProcessController wpc = mSystemServicesTestRule.addProcess( 96 mRecentsComponent.getPackageName(), mRecentsComponent.getPackageName(), 97 // Use real pid/uid of the test so the corresponding process can be mapped by 98 // Binder.getCallingPid/Uid. 99 WindowManagerService.MY_PID, WindowManagerService.MY_UID); 100 ActivityRecord recentActivity = new ActivityBuilder(mAtm) 101 .setComponent(mRecentsComponent) 102 .setTask(recentsStack) 103 .setUseProcess(wpc) 104 .build(); 105 ActivityRecord topActivity = new ActivityBuilder(mAtm).setCreateTask(true).build(); 106 topActivity.getRootTask().moveToFront("testRecentsActivityVisiblility"); 107 108 doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible( 109 any() /* starting */, anyInt() /* configChanges */, 110 anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */); 111 112 RecentsAnimationCallbacks recentsAnimation = startRecentsActivity( 113 mRecentsComponent, true /* getRecentsAnimation */); 114 // The launch-behind state should make the recents activity visible. 115 assertTrue(recentActivity.isVisibleRequested()); 116 assertEquals(ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS, 117 mAtm.mDemoteTopAppReasons); 118 assertFalse(mAtm.mInternal.useTopSchedGroupForTopProcess()); 119 120 // Simulate the animation is cancelled without changing the stack order. 121 recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */); 122 // The non-top recents activity should be invisible by the restored launch-behind state. 123 assertFalse(recentActivity.isVisibleRequested()); 124 assertEquals(0, mAtm.mDemoteTopAppReasons); 125 } 126 127 @Test testPreloadRecentsActivity()128 public void testPreloadRecentsActivity() { 129 TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); 130 final Task homeStack = 131 defaultTaskDisplayArea.getRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); 132 defaultTaskDisplayArea.positionChildAt(POSITION_TOP, homeStack, 133 false /* includingParents */); 134 ActivityRecord topRunningHomeActivity = homeStack.topRunningActivity(); 135 if (topRunningHomeActivity == null) { 136 topRunningHomeActivity = new ActivityBuilder(mAtm) 137 .setParentTask(homeStack) 138 .setCreateTask(true) 139 .build(); 140 } 141 142 ActivityInfo aInfo = new ActivityInfo(); 143 aInfo.applicationInfo = new ApplicationInfo(); 144 aInfo.applicationInfo.uid = 10001; 145 aInfo.applicationInfo.targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; 146 aInfo.packageName = aInfo.applicationInfo.packageName = mRecentsComponent.getPackageName(); 147 aInfo.processName = "recents"; 148 doReturn(aInfo).when(mSupervisor).resolveActivity(any() /* intent */, any() /* rInfo */, 149 anyInt() /* startFlags */, any() /* profilerInfo */); 150 151 // Assume its process is alive because the caller should be the recents service. 152 final WindowProcessController proc = mSystemServicesTestRule.addProcess(aInfo.packageName, 153 aInfo.processName, 12345 /* pid */, aInfo.applicationInfo.uid); 154 proc.setCurrentProcState(PROCESS_STATE_HOME); 155 156 Intent recentsIntent = new Intent().setComponent(mRecentsComponent); 157 // Null animation indicates to preload. 158 mAtm.startRecentsActivity(recentsIntent, 0 /* eventTime */, 159 null /* recentsAnimationRunner */); 160 161 Task recentsStack = defaultTaskDisplayArea.getRootTask(WINDOWING_MODE_FULLSCREEN, 162 ACTIVITY_TYPE_RECENTS); 163 assertThat(recentsStack).isNotNull(); 164 165 ActivityRecord recentsActivity = recentsStack.getTopNonFinishingActivity(); 166 // The activity is started in background so it should be invisible and will be stopped. 167 assertThat(recentsActivity).isNotNull(); 168 assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity); 169 assertFalse(recentsActivity.isVisibleRequested()); 170 171 // Assume it is stopped to test next use case. 172 recentsActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */, 173 null /* description */); 174 mSupervisor.mStoppingActivities.remove(recentsActivity); 175 176 spyOn(recentsActivity); 177 // Start when the recents activity exists. It should ensure the configuration. 178 mAtm.startRecentsActivity(recentsIntent, 0 /* eventTime */, 179 null /* recentsAnimationRunner */); 180 181 verify(recentsActivity).ensureActivityConfiguration(anyInt() /* globalChanges */, 182 anyBoolean() /* preserveWindow */, eq(true) /* ignoreVisibility */); 183 assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity); 184 } 185 186 @Test testRestartRecentsActivity()187 public void testRestartRecentsActivity() throws Exception { 188 // Have a recents activity that is not attached to its process (ActivityRecord.app = null). 189 TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); 190 Task recentsStack = defaultTaskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, 191 ACTIVITY_TYPE_RECENTS, true /* onTop */); 192 ActivityRecord recentActivity = new ActivityBuilder(mAtm).setComponent( 193 mRecentsComponent).setCreateTask(true).setParentTask(recentsStack).build(); 194 WindowProcessController app = recentActivity.app; 195 recentActivity.app = null; 196 197 // Start an activity on top. 198 new ActivityBuilder(mAtm).setCreateTask(true).build().getRootTask().moveToFront( 199 "testRestartRecentsActivity"); 200 201 doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible( 202 any() /* starting */, anyInt() /* configChanges */, 203 anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */); 204 doReturn(app).when(mAtm).getProcessController(eq(recentActivity.processName), anyInt()); 205 ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager(); 206 doNothing().when(lifecycleManager).scheduleTransaction(any()); 207 208 startRecentsActivity(); 209 210 // Recents activity must be restarted, but not be resumed while running recents animation. 211 verify(mRootWindowContainer.mTaskSupervisor).startSpecificActivity( 212 eq(recentActivity), eq(false), anyBoolean()); 213 assertThat(recentActivity.getState()).isEqualTo(PAUSED); 214 } 215 216 @Test testSetLaunchTaskBehindOfTargetActivity()217 public void testSetLaunchTaskBehindOfTargetActivity() { 218 TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); 219 Task homeStack = taskDisplayArea.getRootHomeTask(); 220 // Assume the home activity support recents. 221 ActivityRecord targetActivity = homeStack.getTopNonFinishingActivity(); 222 if (targetActivity == null) { 223 targetActivity = new ActivityBuilder(mAtm) 224 .setCreateTask(true) 225 .setParentTask(homeStack) 226 .build(); 227 } 228 229 // Put another home activity in home stack. 230 ActivityRecord anotherHomeActivity = new ActivityBuilder(mAtm) 231 .setComponent(new ComponentName(mContext.getPackageName(), "Home2")) 232 .setCreateTask(true) 233 .setParentTask(homeStack) 234 .build(); 235 // Start an activity on top so the recents activity can be started. 236 new ActivityBuilder(mAtm) 237 .setCreateTask(true) 238 .build() 239 .getRootTask() 240 .moveToFront("Activity start"); 241 242 // Start the recents animation. 243 RecentsAnimationCallbacks recentsAnimation = startRecentsActivity( 244 targetActivity.getTask().getBaseIntent().getComponent(), 245 true /* getRecentsAnimation */); 246 // Ensure launch-behind is set for being visible. 247 assertTrue(targetActivity.mLaunchTaskBehind); 248 249 anotherHomeActivity.moveFocusableActivityToTop("launchAnotherHome"); 250 251 // The test uses mocked RecentsAnimationController so we have to invoke the callback 252 // manually to simulate the flow. 253 recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */); 254 // We should restore the launch-behind of the original target activity. 255 assertFalse(targetActivity.mLaunchTaskBehind); 256 } 257 258 @Test testCancelAnimationOnVisibleStackOrderChange()259 public void testCancelAnimationOnVisibleStackOrderChange() { 260 TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); 261 Task fullscreenStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, 262 ACTIVITY_TYPE_STANDARD, true /* onTop */); 263 new ActivityBuilder(mAtm) 264 .setComponent(new ComponentName(mContext.getPackageName(), "App1")) 265 .setCreateTask(true) 266 .setParentTask(fullscreenStack) 267 .build(); 268 Task recentsStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, 269 ACTIVITY_TYPE_RECENTS, true /* onTop */); 270 new ActivityBuilder(mAtm) 271 .setComponent(mRecentsComponent) 272 .setCreateTask(true) 273 .setParentTask(recentsStack) 274 .build(); 275 Task fullscreenStack2 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, 276 ACTIVITY_TYPE_STANDARD, true /* onTop */); 277 new ActivityBuilder(mAtm) 278 .setComponent(new ComponentName(mContext.getPackageName(), "App2")) 279 .setCreateTask(true) 280 .setParentTask(fullscreenStack2) 281 .build(); 282 283 // Start the recents animation 284 startRecentsActivity(); 285 286 fullscreenStack.moveToFront("Activity start"); 287 288 // Assume recents animation already started, set a state that cancel recents animation 289 // with screenshot. 290 doReturn(true).when(mRecentsAnimationController).shouldDeferCancelUntilNextTransition(); 291 doReturn(true).when(mRecentsAnimationController).shouldDeferCancelWithScreenshot(); 292 // Start another fullscreen activity. 293 fullscreenStack2.moveToFront("Activity start"); 294 295 // Ensure that the recents animation was canceled by setCancelOnNextTransitionStart(). 296 verify(mRecentsAnimationController, times(1)).setCancelOnNextTransitionStart(); 297 } 298 299 @Test testKeepAnimationOnHiddenStackOrderChange()300 public void testKeepAnimationOnHiddenStackOrderChange() { 301 TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); 302 Task fullscreenStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, 303 ACTIVITY_TYPE_STANDARD, true /* onTop */); 304 new ActivityBuilder(mAtm) 305 .setComponent(new ComponentName(mContext.getPackageName(), "App1")) 306 .setCreateTask(true) 307 .setParentTask(fullscreenStack) 308 .build(); 309 Task recentsStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, 310 ACTIVITY_TYPE_RECENTS, true /* onTop */); 311 new ActivityBuilder(mAtm) 312 .setComponent(mRecentsComponent) 313 .setCreateTask(true) 314 .setParentTask(recentsStack) 315 .build(); 316 Task fullscreenStack2 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, 317 ACTIVITY_TYPE_STANDARD, true /* onTop */); 318 new ActivityBuilder(mAtm) 319 .setComponent(new ComponentName(mContext.getPackageName(), "App2")) 320 .setCreateTask(true) 321 .setParentTask(fullscreenStack2) 322 .build(); 323 324 // Start the recents animation 325 startRecentsActivity(); 326 327 fullscreenStack.removeIfPossible(); 328 329 // Ensure that the recents animation was NOT canceled 330 verify(mAtm.mWindowManager, times(0)).cancelRecentsAnimation( 331 eq(REORDER_KEEP_IN_PLACE), any()); 332 verify(mRecentsAnimationController, times(0)).setCancelOnNextTransitionStart(); 333 } 334 335 @Test testMultipleUserHomeActivity_findUserHomeTask()336 public void testMultipleUserHomeActivity_findUserHomeTask() { 337 TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay() 338 .getDefaultTaskDisplayArea(); 339 Task homeStack = taskDisplayArea.getRootTask(WINDOWING_MODE_UNDEFINED, 340 ACTIVITY_TYPE_HOME); 341 ActivityRecord otherUserHomeActivity = new ActivityBuilder(mAtm) 342 .setParentTask(homeStack) 343 .setCreateTask(true) 344 .setComponent(new ComponentName(mContext.getPackageName(), "Home2")) 345 .build(); 346 otherUserHomeActivity.getTask().mUserId = TEST_USER_ID; 347 348 Task fullscreenStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, 349 ACTIVITY_TYPE_STANDARD, true /* onTop */); 350 new ActivityBuilder(mAtm) 351 .setComponent(new ComponentName(mContext.getPackageName(), "App1")) 352 .setCreateTask(true) 353 .setParentTask(fullscreenStack) 354 .build(); 355 356 doReturn(TEST_USER_ID).when(mAtm).getCurrentUserId(); 357 doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible( 358 any() /* starting */, anyInt() /* configChanges */, 359 anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */); 360 361 startRecentsActivity(otherUserHomeActivity.getTask().getBaseIntent().getComponent(), 362 true); 363 364 // Ensure we find the task for the right user and it is made visible 365 assertTrue(otherUserHomeActivity.isVisibleRequested()); 366 } 367 startRecentsActivity()368 private void startRecentsActivity() { 369 startRecentsActivity(mRecentsComponent, false /* getRecentsAnimation */); 370 } 371 372 /** 373 * @return non-null {@link RecentsAnimationCallbacks} if the given {@code getRecentsAnimation} 374 * is {@code true}. 375 */ startRecentsActivity(ComponentName recentsComponent, boolean getRecentsAnimation)376 private RecentsAnimationCallbacks startRecentsActivity(ComponentName recentsComponent, 377 boolean getRecentsAnimation) { 378 RecentsAnimationCallbacks[] recentsAnimation = { null }; 379 if (getRecentsAnimation) { 380 doAnswer(invocation -> { 381 // The callback is actually RecentsAnimation. 382 recentsAnimation[0] = invocation.getArgument(2); 383 return null; 384 }).when(mAtm.mWindowManager).initializeRecentsAnimation( 385 anyInt() /* targetActivityType */, any() /* recentsAnimationRunner */, 386 any() /* callbacks */, anyInt() /* displayId */, any() /* recentTaskIds */, 387 any() /* targetActivity */); 388 } 389 390 Intent recentsIntent = new Intent(); 391 recentsIntent.setComponent(recentsComponent); 392 mAtm.startRecentsActivity(recentsIntent, 0 /* eventTime */, 393 mock(IRecentsAnimationRunner.class)); 394 return recentsAnimation[0]; 395 } 396 } 397