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