1 /* 2 * Copyright (C) 2019 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 android.server.wm; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 22 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; 23 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 24 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY; 25 import static android.server.wm.ActivityLauncher.KEY_NEW_TASK; 26 import static android.server.wm.ActivityManagerState.STATE_RESUMED; 27 import static android.server.wm.ActivityManagerState.STATE_STOPPED; 28 import static android.server.wm.ComponentNameUtils.getActivityName; 29 import static android.server.wm.app.Components.ALT_LAUNCHING_ACTIVITY; 30 import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY; 31 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY; 32 import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY; 33 import static android.server.wm.app.Components.RESIZEABLE_ACTIVITY; 34 import static android.server.wm.app.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY; 35 import static android.server.wm.app.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2; 36 import static android.server.wm.app.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3; 37 import static android.server.wm.app.Components.TEST_ACTIVITY; 38 import static android.server.wm.app.Components.VIRTUAL_DISPLAY_ACTIVITY; 39 import static android.server.wm.second.Components.SECOND_ACTIVITY; 40 import static android.server.wm.second.Components.SECOND_LAUNCH_BROADCAST_ACTION; 41 import static android.server.wm.second.Components.SECOND_LAUNCH_BROADCAST_RECEIVER; 42 import static android.server.wm.third.Components.THIRD_ACTIVITY; 43 import static android.view.Display.DEFAULT_DISPLAY; 44 45 import static org.junit.Assert.assertEquals; 46 import static org.junit.Assert.assertFalse; 47 import static org.junit.Assert.assertNotEquals; 48 import static org.junit.Assert.assertTrue; 49 import static org.junit.Assume.assumeTrue; 50 51 import android.app.ActivityOptions; 52 import android.content.ComponentName; 53 import android.content.Intent; 54 import android.os.Bundle; 55 import android.platform.test.annotations.Presubmit; 56 import android.server.wm.ActivityManagerState.ActivityDisplay; 57 import android.server.wm.ActivityManagerState.ActivityStack; 58 import android.server.wm.CommandSession.ActivitySession; 59 import android.server.wm.CommandSession.SizeInfo; 60 import android.util.SparseArray; 61 62 import com.android.compatibility.common.util.SystemUtil; 63 64 import org.junit.Before; 65 import org.junit.Test; 66 67 /** 68 * Build/Install/Run: 69 * atest CtsWindowManagerDeviceTestCases:MultiDisplayActivityLaunchTests 70 * 71 * Tests activity launching behavior on multi-display environment. 72 */ 73 @Presubmit 74 public class MultiDisplayActivityLaunchTests extends MultiDisplayTestBase { 75 76 @Before 77 @Override setUp()78 public void setUp() throws Exception { 79 super.setUp(); 80 assumeTrue(supportsMultiDisplay()); 81 } 82 83 /** 84 * Tests launching an activity on virtual display. 85 */ 86 @Test testLaunchActivityOnSecondaryDisplay()87 public void testLaunchActivityOnSecondaryDisplay() throws Exception { 88 validateActivityLaunchOnNewDisplay(ACTIVITY_TYPE_STANDARD); 89 } 90 91 /** 92 * Tests launching a recent activity on virtual display. 93 */ 94 @Test testLaunchRecentActivityOnSecondaryDisplay()95 public void testLaunchRecentActivityOnSecondaryDisplay() throws Exception { 96 validateActivityLaunchOnNewDisplay(ACTIVITY_TYPE_RECENTS); 97 } 98 99 /** 100 * Tests launching an assistant activity on virtual display. 101 */ 102 @Test testLaunchAssistantActivityOnSecondaryDisplay()103 public void testLaunchAssistantActivityOnSecondaryDisplay() throws Exception { 104 validateActivityLaunchOnNewDisplay(ACTIVITY_TYPE_ASSISTANT); 105 } 106 validateActivityLaunchOnNewDisplay(int activityType)107 private void validateActivityLaunchOnNewDisplay(int activityType) throws Exception { 108 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 109 // Create new virtual display. 110 final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); 111 112 // Launch activity on new secondary display. 113 separateTestJournal(); 114 getLaunchActivityBuilder().setUseInstrumentation().setWithShellPermission(true) 115 .setTargetActivity(TEST_ACTIVITY).setNewTask(true) 116 .setMultipleTask(true).setActivityType(activityType) 117 .setDisplayId(newDisplay.mId).execute(); 118 waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, 119 "Activity launched on secondary display must be focused and on top"); 120 121 // Check that activity config corresponds to display config. 122 final SizeInfo reportedSizes = getLastReportedSizesForActivity(TEST_ACTIVITY); 123 assertEquals("Activity launched on secondary display must have proper configuration", 124 CUSTOM_DENSITY_DPI, reportedSizes.densityDpi); 125 126 assertEquals("Top activity must have correct activity type", activityType, 127 mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId)); 128 } 129 } 130 131 /** 132 * Tests launching an activity on primary display explicitly. 133 */ 134 @Test testLaunchActivityOnPrimaryDisplay()135 public void testLaunchActivityOnPrimaryDisplay() throws Exception { 136 // Launch activity on primary display explicitly. 137 launchActivityOnDisplay(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY); 138 139 waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY, 140 "Activity launched on primary display must be focused and on top"); 141 142 // Launch another activity on primary display using the first one 143 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).setNewTask(true) 144 .setMultipleTask(true).setDisplayId(DEFAULT_DISPLAY).execute(); 145 mAmWmState.computeState(TEST_ACTIVITY); 146 147 waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, 148 "Activity launched on primary display must be focused"); 149 } 150 151 /** 152 * Tests launching an existing activity from an activity that resided on secondary display. 153 */ 154 @Test testLaunchActivityFromSecondaryDisplay()155 public void testLaunchActivityFromSecondaryDisplay() throws Exception { 156 getLaunchActivityBuilder().setUseInstrumentation() 157 .setTargetActivity(TEST_ACTIVITY).setNewTask(true) 158 .setDisplayId(DEFAULT_DISPLAY).execute(); 159 160 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 161 final ActivityDisplay newDisplay = 162 virtualDisplaySession.setSimulateDisplay(true).createDisplay(); 163 final int newDisplayId = newDisplay.mId; 164 165 getLaunchActivityBuilder().setUseInstrumentation() 166 .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).setNewTask(true) 167 .setDisplayId(newDisplayId).execute(); 168 waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId, 169 "Activity should be resumed on secondary display"); 170 171 mBroadcastActionTrigger.launchActivityNewTask(getActivityName(TEST_ACTIVITY)); 172 waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, 173 "Activity should be the top resumed on default display"); 174 175 getLaunchActivityBuilder().setUseInstrumentation() 176 .setTargetActivity(TEST_ACTIVITY).setNewTask(true) 177 .setDisplayId(newDisplayId).execute(); 178 waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, 179 "Activity should be resumed on secondary display"); 180 } 181 } 182 183 /** 184 * Tests that an activity can be launched on a secondary display while the primary 185 * display is off. 186 */ 187 @Test testLaunchExternalDisplayActivityWhilePrimaryOff()188 public void testLaunchExternalDisplayActivityWhilePrimaryOff() throws Exception { 189 // Launch something on the primary display so we know there is a resumed activity there 190 launchActivity(RESIZEABLE_ACTIVITY); 191 waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY, 192 "Activity launched on primary display must be resumed"); 193 194 try (final PrimaryDisplayStateSession displayStateSession = 195 new PrimaryDisplayStateSession(); 196 final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) { 197 displayStateSession.turnScreenOff(); 198 199 // Make sure there is no resumed activity when the primary display is off 200 waitAndAssertActivityState(RESIZEABLE_ACTIVITY, STATE_STOPPED, 201 "Activity launched on primary display must be stopped after turning off"); 202 assertEquals("Unexpected resumed activity", 203 0, mAmWmState.getAmState().getResumedActivitiesCount()); 204 205 final ActivityDisplay newDisplay = externalDisplaySession 206 .setCanShowWithInsecureKeyguard(true).createVirtualDisplay(); 207 208 launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); 209 210 // Check that the test activity is resumed on the external display 211 waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, 212 "Activity launched on external display must be resumed"); 213 mAmWmState.assertFocusedAppOnDisplay("App on default display must still be focused", 214 RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY); 215 } 216 } 217 218 /** 219 * Tests launching a non-resizeable activity on virtual display. It should land on the 220 * virtual display. 221 */ 222 @Test testLaunchNonResizeableActivityOnSecondaryDisplay()223 public void testLaunchNonResizeableActivityOnSecondaryDisplay() throws Exception { 224 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 225 // Create new virtual display. 226 final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); 227 228 // Launch activity on new secondary display. 229 launchActivityOnDisplay(NON_RESIZEABLE_ACTIVITY, newDisplay.mId); 230 231 waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId, 232 "Activity requested to launch on secondary display must be focused"); 233 } 234 } 235 236 /** 237 * Tests successfully moving a non-resizeable activity to a virtual display. 238 */ 239 @Test testMoveNonResizeableActivityToSecondaryDisplay()240 public void testMoveNonResizeableActivityToSecondaryDisplay() throws Exception { 241 try (final VirtualDisplayLauncher virtualLauncher = new VirtualDisplayLauncher()) { 242 // Create new virtual display. 243 final ActivityDisplay newDisplay = virtualLauncher.createDisplay(); 244 // Launch a non-resizeable activity on a primary display. 245 final ActivitySession nonResizeableSession = virtualLauncher.launchActivity( 246 builder -> builder.setTargetActivity(NON_RESIZEABLE_ACTIVITY).setNewTask(true)); 247 248 // Launch a resizeable activity on new secondary display to create a new stack there. 249 virtualLauncher.launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay); 250 final int externalFrontStackId = mAmWmState.getAmState() 251 .getFrontStackId(newDisplay.mId); 252 253 // Clear lifecycle callback history before moving the activity so the later verification 254 // can get the callbacks which are related to the reparenting. 255 nonResizeableSession.takeCallbackHistory(); 256 257 // Try to move the non-resizeable activity to the top of stack on secondary display. 258 moveActivityToStack(NON_RESIZEABLE_ACTIVITY, externalFrontStackId); 259 // Wait for a while to check that it will move. 260 mAmWmState.waitForWithAmState(state -> 261 newDisplay.mId == state.getDisplayByActivity(NON_RESIZEABLE_ACTIVITY), 262 "Waiting to see if activity is moved"); 263 assertEquals("Non-resizeable activity should be moved", 264 newDisplay.mId, 265 mAmWmState.getAmState().getDisplayByActivity(NON_RESIZEABLE_ACTIVITY)); 266 267 waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId, 268 "The moved non-resizeable activity must be focused"); 269 assertActivityLifecycle(nonResizeableSession, true /* relaunched */); 270 } 271 } 272 273 /** 274 * Tests launching a non-resizeable activity on virtual display from activity there. It should 275 * land on the secondary display based on the resizeability of the root activity of the task. 276 */ 277 @Test testLaunchNonResizeableActivityFromSecondaryDisplaySameTask()278 public void testLaunchNonResizeableActivityFromSecondaryDisplaySameTask() throws Exception { 279 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 280 // Create new simulated display. 281 final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true) 282 .createDisplay(); 283 284 // Launch activity on new secondary display. 285 launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId); 286 waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId, 287 "Activity launched on secondary display must be focused"); 288 289 // Launch non-resizeable activity from secondary display. 290 mBroadcastActionTrigger.launchActivityNewTask(getActivityName(NON_RESIZEABLE_ACTIVITY)); 291 waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId, 292 "Launched activity must be on the secondary display and resumed"); 293 } 294 } 295 296 /** 297 * Tests launching a non-resizeable activity on virtual display in a new task from activity 298 * there. It must land on the display as its caller. 299 */ 300 @Test testLaunchNonResizeableActivityFromSecondaryDisplayNewTask()301 public void testLaunchNonResizeableActivityFromSecondaryDisplayNewTask() throws Exception { 302 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 303 // Create new virtual display. 304 final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); 305 306 // Launch activity on new secondary display. 307 launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); 308 waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId, 309 "Activity launched on secondary display must be focused"); 310 311 // Launch non-resizeable activity from secondary display in a new task. 312 getLaunchActivityBuilder().setTargetActivity(NON_RESIZEABLE_ACTIVITY) 313 .setNewTask(true).setMultipleTask(true).execute(); 314 315 mAmWmState.waitForActivityState(NON_RESIZEABLE_ACTIVITY, STATE_RESUMED); 316 317 // Check that non-resizeable activity is on the same display. 318 final int newFrontStackId = mAmWmState.getAmState().getFocusedStackId(); 319 final ActivityStack newFrontStack = 320 mAmWmState.getAmState().getStackById(newFrontStackId); 321 assertTrue("Launched activity must be on the same display", 322 newDisplay.mId == newFrontStack.mDisplayId); 323 assertEquals("Launched activity must be resumed", 324 getActivityName(NON_RESIZEABLE_ACTIVITY), 325 newFrontStack.mResumedActivity); 326 mAmWmState.assertFocusedStack( 327 "Top stack must be the one with just launched activity", 328 newFrontStackId); 329 mAmWmState.assertResumedActivities("Both displays must have resumed activities", 330 new SparseArray<ComponentName>(){{ 331 put(newDisplay.mId, LAUNCHING_ACTIVITY); 332 put(newFrontStack.mDisplayId, NON_RESIZEABLE_ACTIVITY); 333 }} 334 ); 335 } 336 } 337 338 /** 339 * Tests launching an activity on virtual display and then launching another activity via shell 340 * command and without specifying the display id - the second activity must appear on the 341 * primary display. 342 */ 343 @Test testConsequentLaunchActivity()344 public void testConsequentLaunchActivity() throws Exception { 345 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 346 // Create new virtual display. 347 final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); 348 349 // Launch activity on new secondary display. 350 launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); 351 352 waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, 353 "Activity launched on secondary display must be on top"); 354 355 // Launch second activity without specifying display. 356 launchActivity(LAUNCHING_ACTIVITY); 357 358 // Check that activity is launched in focused stack on primary display. 359 waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY, 360 "Launched activity must be focused"); 361 mAmWmState.assertResumedActivities("Both displays must have resumed activities", 362 new SparseArray<ComponentName>(){{ 363 put(newDisplay.mId, TEST_ACTIVITY); 364 put(DEFAULT_DISPLAY, LAUNCHING_ACTIVITY); 365 }} 366 ); 367 } 368 } 369 370 /** 371 * Tests launching an activity on simulated display and then launching another activity from the 372 * first one - it must appear on the secondary display, because it was launched from there. 373 */ 374 @Test testConsequentLaunchActivityFromSecondaryDisplay()375 public void testConsequentLaunchActivityFromSecondaryDisplay() throws Exception { 376 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 377 // Create new simulated display. 378 final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true) 379 .createDisplay(); 380 381 // Launch activity on new secondary display. 382 launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); 383 384 waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId, 385 "Activity launched on secondary display must be on top"); 386 387 // Launch second activity from app on secondary display without specifying display id. 388 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).execute(); 389 390 // Check that activity is launched in focused stack on external display. 391 waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, 392 "Launched activity must be on top"); 393 } 394 } 395 396 /** 397 * Tests launching an activity on virtual display and then launching another activity from the 398 * first one - it must appear on the secondary display, because it was launched from there. 399 */ 400 @Test testConsequentLaunchActivityFromVirtualDisplay()401 public void testConsequentLaunchActivityFromVirtualDisplay() throws Exception { 402 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 403 // Create new virtual display. 404 final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); 405 406 // Launch activity on new secondary display. 407 launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); 408 409 waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId, 410 "Activity launched on secondary display must be on top"); 411 412 // Launch second activity from app on secondary display without specifying display id. 413 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).execute(); 414 mAmWmState.computeState(TEST_ACTIVITY); 415 416 // Check that activity is launched in focused stack on external display. 417 waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, 418 "Launched activity must be on top"); 419 } 420 } 421 422 /** 423 * Tests launching an activity on virtual display and then launching another activity from the 424 * first one with specifying the target display - it must appear on the secondary display. 425 */ 426 @Test testConsequentLaunchActivityFromVirtualDisplayToTargetDisplay()427 public void testConsequentLaunchActivityFromVirtualDisplayToTargetDisplay() throws Exception { 428 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 429 // Create new virtual display. 430 final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); 431 432 // Launch activity on new secondary display. 433 launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); 434 435 waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId, 436 "Activity launched on secondary display must be on top"); 437 438 // Launch second activity from app on secondary display specifying same display id. 439 getLaunchActivityBuilder() 440 .setTargetActivity(SECOND_ACTIVITY) 441 .setDisplayId(newDisplay.mId) 442 .execute(); 443 444 // Check that activity is launched in focused stack on external display. 445 waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId, 446 "Launched activity must be on top"); 447 448 // Launch other activity with different uid and check if it has launched successfully. 449 getLaunchActivityBuilder() 450 .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER, 451 SECOND_LAUNCH_BROADCAST_ACTION) 452 .setDisplayId(newDisplay.mId) 453 .setTargetActivity(THIRD_ACTIVITY) 454 .execute(); 455 456 // Check that activity is launched in focused stack on external display. 457 waitAndAssertTopResumedActivity(THIRD_ACTIVITY, newDisplay.mId, 458 "Launched activity must be on top"); 459 } 460 } 461 462 /** 463 * Tests launching an activity to secondary display from activity on primary display. 464 */ 465 @Test testLaunchActivityFromAppToSecondaryDisplay()466 public void testLaunchActivityFromAppToSecondaryDisplay() throws Exception { 467 // Start launching activity. 468 launchActivity(LAUNCHING_ACTIVITY); 469 470 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 471 // Create new simulated display. 472 final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true) 473 .createDisplay(); 474 475 // Launch activity on secondary display from the app on primary display. 476 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY) 477 .setDisplayId(newDisplay.mId).execute(); 478 479 // Check that activity is launched on external display. 480 waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, 481 "Activity launched on secondary display must be focused"); 482 mAmWmState.assertResumedActivities("Both displays must have resumed activities", 483 new SparseArray<ComponentName>(){{ 484 put(DEFAULT_DISPLAY, LAUNCHING_ACTIVITY); 485 put(newDisplay.mId, TEST_ACTIVITY); 486 }} 487 ); 488 } 489 } 490 491 /** Tests that launching app from pending activity queue on external display is allowed. */ 492 @Test testLaunchPendingActivityOnSecondaryDisplay()493 public void testLaunchPendingActivityOnSecondaryDisplay() throws Exception { 494 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 495 // Create new simulated display. 496 final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true) 497 .createDisplay(); 498 final Bundle bundle = ActivityOptions.makeBasic(). 499 setLaunchDisplayId(newDisplay.mId).toBundle(); 500 final Intent intent = new Intent(Intent.ACTION_VIEW) 501 .setComponent(SECOND_ACTIVITY) 502 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) 503 .putExtra(KEY_LAUNCH_ACTIVITY, true) 504 .putExtra(KEY_NEW_TASK, true); 505 mContext.startActivity(intent, bundle); 506 507 // ActivityManagerTestBase.setup would press home key event, which would cause 508 // PhoneWindowManager.startDockOrHome to call AMS.stopAppSwitches. 509 // Since this test case is not start activity from shell, it won't grant 510 // STOP_APP_SWITCHES and this activity should be put into pending activity queue 511 // and this activity should been launched after 512 // ActivityTaskManagerService.APP_SWITCH_DELAY_TIME 513 mAmWmState.waitForPendingActivityContain(SECOND_ACTIVITY); 514 // If the activity is not pending, skip this test. 515 mAmWmState.assumePendingActivityContain(SECOND_ACTIVITY); 516 // In order to speed up test case without waiting for APP_SWITCH_DELAY_TIME, we launch 517 // another activity with LaunchActivityBuilder, in this way the activity can be start 518 // directly and also trigger pending activity to be launched. 519 getLaunchActivityBuilder() 520 .setTargetActivity(THIRD_ACTIVITY) 521 .execute(); 522 mAmWmState.waitForValidState(SECOND_ACTIVITY); 523 waitAndAssertTopResumedActivity(THIRD_ACTIVITY, DEFAULT_DISPLAY, 524 "Top activity must be the newly launched one"); 525 mAmWmState.assertVisibility(SECOND_ACTIVITY, true); 526 assertEquals("Activity launched by app on secondary display must be on that display", 527 newDisplay.mId, mAmWmState.getAmState().getDisplayByActivity(SECOND_ACTIVITY)); 528 } 529 } 530 531 /** 532 * Tests that when an activity is launched with displayId specified and there is an existing 533 * matching task on some other display - that task will moved to the target display. 534 */ 535 @Test testMoveToDisplayOnLaunch()536 public void testMoveToDisplayOnLaunch() throws Exception { 537 // Launch activity with unique affinity, so it will the only one in its task. 538 launchActivity(LAUNCHING_ACTIVITY); 539 540 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 541 // Create new virtual display. 542 final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); 543 mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); 544 // Launch something to that display so that a new stack is created. We need this to be 545 // able to compare task numbers in stacks later. 546 launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId); 547 mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */); 548 549 final int stackNum = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY) 550 .mStacks.size(); 551 final int stackNumOnSecondary = mAmWmState.getAmState() 552 .getDisplay(newDisplay.mId).mStacks.size(); 553 554 // Launch activity on new secondary display. 555 // Using custom command here, because normally we add flags 556 // {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 557 // when launching on some specific display. We don't do it here as we want an existing 558 // task to be used. 559 final String launchCommand = "am start -n " + getActivityName(LAUNCHING_ACTIVITY) 560 + " --display " + newDisplay.mId; 561 executeShellCommand(launchCommand); 562 563 // Check that activity is brought to front. 564 waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId, 565 "Existing task must be brought to front"); 566 567 // Check that task has moved from primary display to secondary. 568 // Since it is 1-to-1 relationship between task and stack for standard type & 569 // fullscreen activity, we check the number of stacks here 570 final int stackNumFinal = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY) 571 .mStacks.size(); 572 assertEquals("Stack number in default stack must be decremented.", stackNum - 1, 573 stackNumFinal); 574 final int stackNumFinalOnSecondary = mAmWmState.getAmState() 575 .getDisplay(newDisplay.mId).mStacks.size(); 576 assertEquals("Stack number on external display must be incremented.", 577 stackNumOnSecondary + 1, stackNumFinalOnSecondary); 578 } 579 } 580 581 /** 582 * Tests that when an activity is launched with displayId specified and there is an existing 583 * matching task on some other display - that task will moved to the target display. 584 */ 585 @Test testMoveToEmptyDisplayOnLaunch()586 public void testMoveToEmptyDisplayOnLaunch() throws Exception { 587 // Launch activity with unique affinity, so it will the only one in its task. And choose 588 // resizeable activity to prevent the test activity be relaunched when launch it to another 589 // display, which may affect on this test case. 590 launchActivity(RESIZEABLE_ACTIVITY); 591 592 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 593 // Create new virtual display. 594 final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); 595 mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); 596 597 final int stackNum = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY).mStacks.size(); 598 599 // Launch activity on new secondary display. 600 // Using custom command here, because normally we add flags 601 // {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 602 // when launching on some specific display. We don't do it here as we want an existing 603 // task to be used. 604 final String launchCommand = "am start -n " + getActivityName(RESIZEABLE_ACTIVITY) 605 + " --display " + newDisplay.mId; 606 executeShellCommand(launchCommand); 607 608 // Check that activity is brought to front. 609 waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId, 610 "Existing task must be brought to front"); 611 612 // Check that task has moved from primary display to secondary. 613 final int stackNumFinal = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY) 614 .mStacks.size(); 615 assertEquals("Stack number in default stack must be decremented.", stackNum - 1, 616 stackNumFinal); 617 } 618 } 619 620 /** 621 * Tests that task affinity does affect what display an activity is launched on but that 622 * matching the task component root does. 623 */ 624 @Test testTaskMatchAcrossDisplays()625 public void testTaskMatchAcrossDisplays() throws Exception { 626 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 627 final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); 628 629 launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); 630 mAmWmState.computeState(LAUNCHING_ACTIVITY); 631 632 // Check that activity is on the secondary display. 633 final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId); 634 final ActivityStack firstFrontStack = 635 mAmWmState.getAmState().getStackById(frontStackId); 636 assertEquals("Activity launched on secondary display must be resumed", 637 getActivityName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity); 638 mAmWmState.assertFocusedStack("Top stack must be on secondary display", 639 frontStackId); 640 641 executeShellCommand("am start -n " + getActivityName(ALT_LAUNCHING_ACTIVITY)); 642 mAmWmState.waitForValidState(ALT_LAUNCHING_ACTIVITY); 643 644 // Check that second activity gets launched on the default display despite 645 // the affinity match on the secondary display. 646 final int defaultDisplayFrontStackId = mAmWmState.getAmState().getFrontStackId( 647 DEFAULT_DISPLAY); 648 final ActivityStack defaultDisplayFrontStack = 649 mAmWmState.getAmState().getStackById(defaultDisplayFrontStackId); 650 assertEquals("Activity launched on default display must be resumed", 651 getActivityName(ALT_LAUNCHING_ACTIVITY), 652 defaultDisplayFrontStack.mResumedActivity); 653 mAmWmState.assertFocusedStack("Top stack must be on primary display", 654 defaultDisplayFrontStackId); 655 656 executeShellCommand("am start -n " + getActivityName(LAUNCHING_ACTIVITY)); 657 mAmWmState.waitForFocusedStack(frontStackId); 658 659 // Check that the third intent is redirected to the first task due to the root 660 // component match on the secondary display. 661 final ActivityStack secondFrontStack = 662 mAmWmState.getAmState().getStackById(frontStackId); 663 assertEquals("Activity launched on secondary display must be resumed", 664 getActivityName(LAUNCHING_ACTIVITY), secondFrontStack.mResumedActivity); 665 mAmWmState.assertFocusedStack("Top stack must be on primary display", frontStackId); 666 assertEquals("Top stack must only contain 1 task", 667 1, secondFrontStack.getTasks().size()); 668 assertEquals("Top task must only contain 1 activity", 669 1, secondFrontStack.getTasks().get(0).mActivities.size()); 670 } 671 } 672 673 /** 674 * Tests that an activity is launched on the preferred display where the caller resided when 675 * both displays have matching tasks. 676 */ 677 @Test testTaskMatchOrderAcrossDisplays()678 public void testTaskMatchOrderAcrossDisplays() throws Exception { 679 getLaunchActivityBuilder().setUseInstrumentation() 680 .setTargetActivity(TEST_ACTIVITY).setNewTask(true) 681 .setDisplayId(DEFAULT_DISPLAY).execute(); 682 final int stackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY); 683 684 getLaunchActivityBuilder().setUseInstrumentation() 685 .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).setNewTask(true) 686 .setDisplayId(DEFAULT_DISPLAY).execute(); 687 688 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 689 final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); 690 getLaunchActivityBuilder().setUseInstrumentation().setWithShellPermission(true) 691 .setTargetActivity(TEST_ACTIVITY).setNewTask(true) 692 .setDisplayId(newDisplay.mId).execute(); 693 assertNotEquals("Top focus stack should not be on default display", 694 stackId, mAmWmState.getAmState().getFocusedStackId()); 695 696 mBroadcastActionTrigger.launchActivityNewTask(getActivityName(TEST_ACTIVITY)); 697 waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, 698 "Activity must be launched on default display"); 699 mAmWmState.assertFocusedStack("Top focus stack must be on the default display", 700 stackId); 701 } 702 } 703 704 /** 705 * Tests that the task affinity search respects the launch display id. 706 */ 707 @Test testLaunchDisplayAffinityMatch()708 public void testLaunchDisplayAffinityMatch() throws Exception { 709 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 710 final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); 711 712 launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); 713 714 // Check that activity is on the secondary display. 715 final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId); 716 final ActivityStack firstFrontStack = 717 mAmWmState.getAmState().getStackById(frontStackId); 718 assertEquals("Activity launched on secondary display must be resumed", 719 getActivityName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity); 720 mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId); 721 722 // We don't want FLAG_ACTIVITY_MULTIPLE_TASK, so we can't use launchActivityOnDisplay 723 executeShellCommand("am start -n " + getActivityName(ALT_LAUNCHING_ACTIVITY) 724 + " -f 0x10000000" // FLAG_ACTIVITY_NEW_TASK 725 + " --display " + newDisplay.mId); 726 mAmWmState.computeState(ALT_LAUNCHING_ACTIVITY); 727 728 // Check that second activity gets launched into the affinity matching 729 // task on the secondary display 730 final int secondFrontStackId = 731 mAmWmState.getAmState().getFrontStackId(newDisplay.mId); 732 final ActivityStack secondFrontStack = 733 mAmWmState.getAmState().getStackById(secondFrontStackId); 734 assertEquals("Activity launched on secondary display must be resumed", 735 getActivityName(ALT_LAUNCHING_ACTIVITY), 736 secondFrontStack.mResumedActivity); 737 mAmWmState.assertFocusedStack("Top stack must be on secondary display", 738 secondFrontStackId); 739 assertEquals("Top stack must only contain 1 task", 740 1, secondFrontStack.getTasks().size()); 741 assertEquals("Top stack task must contain 2 activities", 742 2, secondFrontStack.getTasks().get(0).mActivities.size()); 743 } 744 } 745 746 /** 747 * Tests that a new task launched by an activity will end up on that activity's display 748 * even if the focused stack is not on that activity's display. 749 */ 750 @Test testNewTaskSameDisplay()751 public void testNewTaskSameDisplay() throws Exception { 752 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 753 final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true) 754 .createDisplay(); 755 756 launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId); 757 758 // Check that the first activity is launched onto the secondary display 759 waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId, 760 "Activity launched on secondary display must be resumed"); 761 762 executeShellCommand("am start -n " + getActivityName(TEST_ACTIVITY)); 763 764 // Check that the second activity is launched on the default display 765 waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, 766 "Activity launched on default display must be resumed"); 767 mAmWmState.assertResumedActivities("Both displays should have resumed activities", 768 new SparseArray<ComponentName>(){{ 769 put(DEFAULT_DISPLAY, TEST_ACTIVITY); 770 put(newDisplay.mId, BROADCAST_RECEIVER_ACTIVITY); 771 }} 772 ); 773 774 mBroadcastActionTrigger.launchActivityNewTask(getActivityName(LAUNCHING_ACTIVITY)); 775 776 // Check that the third activity ends up in a new stack in the same display where the 777 // first activity lands 778 waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId, 779 "Activity must be launched on secondary display"); 780 assertEquals("Secondary display must contain 2 stacks", 2, 781 mAmWmState.getAmState().getDisplay(newDisplay.mId).mStacks.size()); 782 mAmWmState.assertResumedActivities("Both displays should have resumed activities", 783 new SparseArray<ComponentName>(){{ 784 put(DEFAULT_DISPLAY, TEST_ACTIVITY); 785 put(newDisplay.mId, LAUNCHING_ACTIVITY); 786 }} 787 ); 788 } 789 } 790 791 /** 792 * Tests than an immediate launch after new display creation is handled correctly. 793 */ 794 @Test testImmediateLaunchOnNewDisplay()795 public void testImmediateLaunchOnNewDisplay() throws Exception { 796 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 797 // Create new virtual display and immediately launch an activity on it. 798 final ActivityDisplay newDisplay = virtualDisplaySession 799 .setLaunchActivity(TEST_ACTIVITY) 800 .createDisplay(); 801 802 // Check that activity is launched and placed correctly. 803 waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, 804 "Test activity must be on top"); 805 final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId); 806 final ActivityStack firstFrontStack = 807 mAmWmState.getAmState().getStackById(frontStackId); 808 assertEquals("Activity launched on secondary display must be resumed", 809 getActivityName(TEST_ACTIVITY), firstFrontStack.mResumedActivity); 810 mAmWmState.assertFocusedStack("Top stack must be on secondary display", 811 frontStackId); 812 } 813 } 814 815 /** Tests launching of activities on a single task instance display. */ 816 @Test testSingleTaskInstanceDisplay()817 public void testSingleTaskInstanceDisplay() throws Exception { 818 try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { 819 ActivityDisplay display = 820 virtualDisplaySession.setSimulateDisplay(true).createDisplay(); 821 final int displayId = display.mId; 822 823 SystemUtil.runWithShellPermissionIdentity( 824 () -> mAtm.setDisplayToSingleTaskInstance(displayId)); 825 display = getDisplayState(displayId); 826 assertTrue("Display must be set to singleTaskInstance", display.mSingleTaskInstance); 827 828 // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY will launch 829 // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2 in the same task and 830 // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3 in different task. 831 launchActivityOnDisplay(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY, displayId); 832 833 waitAndAssertTopResumedActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3, DEFAULT_DISPLAY, 834 "Activity should be resumed on default display"); 835 836 display = getDisplayState(displayId); 837 // Verify that the 2 activities in the same task are on the display and the one in a 838 // different task isn't on the display, but on the default display 839 assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY", 840 display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY)); 841 assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2", 842 display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2)); 843 844 assertFalse("Display shouldn't contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3", 845 display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3)); 846 assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3", 847 getDisplayState(DEFAULT_DISPLAY).containsActivity( 848 SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3)); 849 } 850 } 851 } 852