/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.server.wm.ComponentNameUtils.getWindowName; import static android.server.wm.StateLogger.logE; import static android.server.wm.WindowManagerState.STATE_RESUMED; import static android.server.wm.WindowManagerState.STATE_STOPPED; import static android.server.wm.WindowManagerState.TRANSIT_TASK_CLOSE; import static android.server.wm.WindowManagerState.TRANSIT_TASK_OPEN; import static android.server.wm.app.Components.BOTTOM_ACTIVITY; import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY; import static android.server.wm.app.Components.LAUNCHING_ACTIVITY; import static android.server.wm.app.Components.LAUNCH_TEST_ON_DESTROY_ACTIVITY; import static android.server.wm.app.Components.RESIZEABLE_ACTIVITY; import static android.server.wm.app.Components.SHOW_WHEN_LOCKED_ATTR_ACTIVITY; import static android.server.wm.app.Components.TEST_ACTIVITY; import static android.server.wm.app.Components.TOAST_ACTIVITY; import static android.server.wm.app.Components.VIRTUAL_DISPLAY_ACTIVITY; import static android.server.wm.app27.Components.SDK_27_LAUNCHING_ACTIVITY; import static android.server.wm.app27.Components.SDK_27_SEPARATE_PROCESS_ACTIVITY; import static android.server.wm.app27.Components.SDK_27_TEST_ACTIVITY; import static android.server.wm.lifecycle.ActivityStarterTests.StandardActivity; import static android.view.Display.DEFAULT_DISPLAY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; import android.platform.test.annotations.Presubmit; import android.server.wm.CommandSession.ActivityCallback; import android.server.wm.CommandSession.ActivitySession; import android.server.wm.CommandSession.SizeInfo; import android.server.wm.WindowManagerState.ActivityTask; import android.server.wm.WindowManagerState.DisplayContent; import org.junit.Before; import org.junit.Test; /** * Build/Install/Run: * atest CtsWindowManagerDeviceTestCases:MultiDisplayPolicyTests * * Tests each expected policy on multi-display environment. */ @Presubmit @android.server.wm.annotation.Group3 public class MultiDisplayPolicyTests extends MultiDisplayTestBase { @Before @Override public void setUp() throws Exception { super.setUp(); assumeTrue(supportsMultiDisplay()); } /** * Tests that all activities that were on the private display are destroyed on display removal. */ @Test public void testContentDestroyOnDisplayRemoved() { try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { // Create new private virtual display. final DisplayContent newDisplay = virtualDisplaySession.createDisplay(); mWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); // Launch activities on new secondary display. launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Launched activity must be resumed"); launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId); waitAndAssertActivityStateOnDisplay(RESIZEABLE_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Launched activity must be resumed"); separateTestJournal(); // Destroy the display and check if activities are removed from system. } mWmState.waitForActivityRemoved(TEST_ACTIVITY); mWmState.waitForActivityRemoved(RESIZEABLE_ACTIVITY); // Check AM state. assertFalse("Activity from removed display must be destroyed", mWmState.containsActivity(TEST_ACTIVITY)); assertFalse("Activity from removed display must be destroyed", mWmState.containsActivity(RESIZEABLE_ACTIVITY)); // Check WM state. assertFalse("Activity windows from removed display must be destroyed", mWmState.containsWindow(getWindowName(TEST_ACTIVITY))); assertFalse("Activity windows from removed display must be destroyed", mWmState.containsWindow(getWindowName(RESIZEABLE_ACTIVITY))); // Check activity logs. assertActivityDestroyed(TEST_ACTIVITY); assertActivityDestroyed(RESIZEABLE_ACTIVITY); } /** * Tests that newly launched activity will be landing on default display on display removal. */ @Test public void testActivityLaunchOnContentDestroyDisplayRemoved() { try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { // Create new private virtual display. final DisplayContent newDisplay = virtualDisplaySession.createDisplay(); mWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); // Launch activities on new secondary display. launchActivityOnDisplay(LAUNCH_TEST_ON_DESTROY_ACTIVITY, newDisplay.mId); waitAndAssertActivityStateOnDisplay(LAUNCH_TEST_ON_DESTROY_ACTIVITY, STATE_RESUMED, newDisplay.mId,"Launched activity must be resumed on secondary display"); // Destroy the display } waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, "Newly launches activity should be landing on default display"); } /** * Tests that the update of display metrics updates all its content. */ @Test public void testDisplayResize() { final VirtualDisplaySession virtualDisplaySession = createManagedVirtualDisplaySession(); // Create new virtual display. final DisplayContent newDisplay = virtualDisplaySession.createDisplay(); mWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); // Launch a resizeable activity on new secondary display. separateTestJournal(); launchActivityOnDisplay(RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN, newDisplay.mId); waitAndAssertActivityStateOnDisplay(RESIZEABLE_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Launched activity must be resumed"); // Grab reported sizes and compute new with slight size change. final SizeInfo initialSize = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY); // Resize the display separateTestJournal(); virtualDisplaySession.resizeDisplay(); mWmState.waitForWithAmState(amState -> { try { return amState.hasActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED) && new ActivityLifecycleCounts(RESIZEABLE_ACTIVITY) .getCount(ActivityCallback.ON_CONFIGURATION_CHANGED) == 1; } catch (Exception e) { logE("Error waiting for valid state: " + e.getMessage()); return false; } }, "the configuration change to happen and activity to be resumed"); mWmState.computeState( new WaitForValidActivityState(RESIZEABLE_ACTIVITY), new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY)); mWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true); mWmState.assertVisibility(RESIZEABLE_ACTIVITY, true); // Check if activity in virtual display was resized properly. assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */, 1 /* numConfigChange */); final SizeInfo updatedSize = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY); assertTrue(updatedSize.widthDp <= initialSize.widthDp); assertTrue(updatedSize.heightDp <= initialSize.heightDp); assertTrue(updatedSize.displayWidth == initialSize.displayWidth / 2); assertTrue(updatedSize.displayHeight == initialSize.displayHeight / 2); } /** * Tests that when primary display is rotated secondary displays are not affected. */ @Test public void testRotationNotAffectingSecondaryScreen() { final VirtualDisplayLauncher virtualLauncher = mObjectTracker.manage(new VirtualDisplayLauncher()); // Create new virtual display. final DisplayContent newDisplay = virtualLauncher.setResizeDisplay(false).createDisplay(); mWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); // Launch activity on new secondary display. final ActivitySession resizeableActivitySession = virtualLauncher.launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay); waitAndAssertActivityStateOnDisplay(RESIZEABLE_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Top activity must be on secondary display"); final SizeInfo initialSize = resizeableActivitySession.getConfigInfo().sizeInfo; assertNotNull("Test activity must have reported initial size on launch", initialSize); final RotationSession rotationSession = createManagedRotationSession(); // Rotate primary display and check that activity on secondary display is not affected. rotateAndCheckSameSizes(rotationSession, resizeableActivitySession, initialSize); // Launch activity to secondary display when primary one is rotated. final int initialRotation = mWmState.getRotation(); rotationSession.set((initialRotation + 1) % 4); final ActivitySession testActivitySession = virtualLauncher.launchActivityOnDisplay(TEST_ACTIVITY, newDisplay); waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Top activity must be on secondary display"); final SizeInfo testActivitySize = testActivitySession.getConfigInfo().sizeInfo; assertEquals("Sizes of secondary display must not change after rotation of primary" + " display", initialSize, testActivitySize); } private void rotateAndCheckSameSizes(RotationSession rotationSession, ActivitySession activitySession, SizeInfo initialSize) { for (int rotation = 3; rotation >= 0; --rotation) { rotationSession.set(rotation); final SizeInfo rotatedSize = activitySession.getConfigInfo().sizeInfo; assertEquals("Sizes must not change after rotation", initialSize, rotatedSize); } } /** * Tests that turning the primary display off does not affect the activity running * on an external secondary display. */ @Test public void testExternalDisplayActivityTurnPrimaryOff() { // Launch something on the primary display so we know there is a resumed activity there launchActivity(RESIZEABLE_ACTIVITY); waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY, "Activity launched on primary display must be resumed"); final DisplayContent newDisplay = createManagedExternalDisplaySession() .setCanShowWithInsecureKeyguard(true).createVirtualDisplay(); launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); // Check that the activity is launched onto the external display waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Activity launched on external display must be resumed"); mWmState.assertFocusedAppOnDisplay("App on default display must still be focused", RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY); separateTestJournal(); mObjectTracker.manage(new PrimaryDisplayStateSession()).turnScreenOff(); // Wait for the fullscreen stack to start sleeping, and then make sure the // test activity is still resumed. final ActivityLifecycleCounts counts = new ActivityLifecycleCounts(RESIZEABLE_ACTIVITY); if (!Condition.waitFor(counts.countWithRetry(RESIZEABLE_ACTIVITY + " to be stopped", countSpec(ActivityCallback.ON_STOP, CountSpec.EQUALS, 1)))) { fail(RESIZEABLE_ACTIVITY + " has received " + counts.getCount(ActivityCallback.ON_STOP) + " onStop() calls, expecting 1"); } // For this test we create this virtual display with flag showContentWhenLocked, so it // cannot be effected when default display screen off. waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Activity launched on external display must be resumed"); } /** * Tests that turning the secondary display off stops activities running and makes invisible * on that display. */ @Test public void testExternalDisplayToggleState() { final ExternalDisplaySession externalDisplaySession = createManagedExternalDisplaySession(); final DisplayContent newDisplay = externalDisplaySession.createVirtualDisplay(); launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); // Check that the test activity is resumed on the external display waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Activity launched on external display must be resumed"); externalDisplaySession.turnDisplayOff(); // Check that turning off the external display stops the activity, and makes it // invisible. waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED, "Activity launched on external display must be stopped after turning off"); mWmState.assertVisibility(TEST_ACTIVITY, false /* visible */); externalDisplaySession.turnDisplayOn(); // Check that turning on the external display resumes the activity waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Activity launched on external display must be resumed"); } /** * Tests no leaking after external display removed. */ @Test public void testNoLeakOnExternalDisplay() throws Exception { // How this test works: // When receiving the request to remove a display and some activities still exist on that // display, it will finish those activities first, so the display won't be removed // immediately. Then, when all activities were destroyed, the display removes itself. // Get display count before testing, as some devices may have more than one built-in // display. mWmState.computeState(); final int displayCount = mWmState.getDisplayCount(); try (final VirtualDisplaySession externalDisplaySession = new VirtualDisplaySession()) { final DisplayContent newDisplay = externalDisplaySession .setSimulateDisplay(true).createDisplay(); launchActivityOnDisplay(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId); waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId, "Virtual activity should be Top Resumed Activity."); mWmState.assertFocusedAppOnDisplay("Activity on second display must be focused.", VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId); } mWmState.waitFor((amState) -> amState.getDisplayCount() == displayCount, "external displays to be removed"); assertEquals(displayCount, mWmState.getDisplayCount()); assertEquals(displayCount, mWmState.getKeyguardControllerState(). mKeyguardOccludedStates.size()); } /** * Tests launching activities on secondary and then on primary display to see if the stack * visibility is not affected. */ @Test public void testLaunchActivitiesAffectsVisibility() { // Start launching activity. launchActivity(LAUNCHING_ACTIVITY); // Create new virtual display. final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay(); mWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); // Launch activity on new secondary display. launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); mWmState.assertVisibility(TEST_ACTIVITY, true /* visible */); mWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); // Launch activity on primary display and check if it doesn't affect activity on // secondary display. getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY).execute(); mWmState.waitForValidState(RESIZEABLE_ACTIVITY); mWmState.assertVisibility(TEST_ACTIVITY, true /* visible */); mWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */); assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY), pair(newDisplay.mId, TEST_ACTIVITY)); } /** * Test that move-task works when moving between displays. */ @Test public void testMoveTaskBetweenDisplays() { // Create new virtual display. final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay(); mWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); mWmState.assertFocusedActivity("Virtual display activity must be on top", VIRTUAL_DISPLAY_ACTIVITY); final int defaultDisplayStackId = mWmState.getFocusedStackId(); ActivityTask frontStack = mWmState.getRootTask( defaultDisplayStackId); assertEquals("Top stack must remain on primary display", DEFAULT_DISPLAY, frontStack.mDisplayId); // Launch activity on new secondary display. launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Top activity must be on secondary display"); assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY), pair(newDisplay.mId, TEST_ACTIVITY)); // Move activity from secondary display to primary. moveActivityToRootTaskOrOnTop(TEST_ACTIVITY, defaultDisplayStackId); waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, "Moved activity must be on top"); } /** * Tests launching activities on secondary display and then removing it to see if stack focus * is moved correctly. * This version launches virtual display creator to fullscreen stack in split-screen. */ @Test public void testStackFocusSwitchOnDisplayRemoved() { assumeTrue(supportsSplitScreenMultiWindow()); // Start launching activity into docked stack. launchActivitiesInSplitScreen( getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY), getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)); mWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */); tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */, WINDOWING_MODE_MULTI_WINDOW); } /** * Tests launching activities on secondary display and then removing it to see if stack focus * is moved correctly. * This version launches virtual display creator to docked stack in split-screen. */ @Test public void testStackFocusSwitchOnDisplayRemoved2() { assumeTrue(supportsSplitScreenMultiWindow()); // Setup split-screen. launchActivitiesInSplitScreen( getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY), getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY)); mWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */); tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */, WINDOWING_MODE_MULTI_WINDOW); } /** * Tests launching activities on secondary display and then removing it to see if stack focus * is moved correctly. * This version works without split-screen. */ @Test public void testStackFocusSwitchOnDisplayRemoved3() { // Start an activity on default display to determine default stack. launchActivity(BROADCAST_RECEIVER_ACTIVITY); final int focusedStackWindowingMode = mWmState.getFrontStackWindowingMode( DEFAULT_DISPLAY); // Finish probing activity. mBroadcastActionTrigger.finishBroadcastReceiverActivity(); tryCreatingAndRemovingDisplayWithActivity(false /* splitScreen */, focusedStackWindowingMode); } /** * Create a virtual display, launch a test activity there, destroy the display and check if test * activity is moved to a stack on the default display. */ private void tryCreatingAndRemovingDisplayWithActivity(boolean splitScreen, int windowingMode) { try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { // Create new virtual display. final DisplayContent newDisplay = virtualDisplaySession .setPublicDisplay(true) .setLaunchInSplitScreen(splitScreen) .createDisplay(); if (splitScreen) { // Set the secondary split root task as launch root to verify remaining tasks will // be reparented to matching launch root after removed the virtual display. mTaskOrganizer.setLaunchRoot(mTaskOrganizer.getSecondarySplitTaskId()); } // Launch activity on new secondary display. launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId); waitAndAssertActivityStateOnDisplay(RESIZEABLE_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Test activity must be on secondary display"); separateTestJournal(); // Destroy virtual display. } mWmState.computeState(); assertActivityLifecycle(RESIZEABLE_ACTIVITY, false /* relaunched */); mWmState.waitForValidState(new WaitForValidActivityState.Builder(RESIZEABLE_ACTIVITY) .setWindowingMode(windowingMode) .setActivityType(ACTIVITY_TYPE_STANDARD) .build()); mWmState.assertValidity(); // Check if the top activity is now back on primary display. mWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */); mWmState.assertFocusedStack( "Default stack on primary display must be focused after display removed", windowingMode, ACTIVITY_TYPE_STANDARD); mWmState.assertFocusedActivity( "Focus must be switched back to activity on primary display", RESIZEABLE_ACTIVITY); } /** * Tests launching activities on secondary display and then removing it to see if stack focus * is moved correctly. */ @Test public void testStackFocusSwitchOnStackEmptiedInSleeping() { assumeTrue(supportsLockScreen()); validateStackFocusSwitchOnStackEmptied(createManagedVirtualDisplaySession(), createManagedLockScreenSession()); } /** * Tests launching activities on secondary display and then finishing it to see if stack focus * is moved correctly. */ @Test public void testStackFocusSwitchOnStackEmptied() { validateStackFocusSwitchOnStackEmptied(createManagedVirtualDisplaySession(), null /* lockScreenSession */); } private void validateStackFocusSwitchOnStackEmptied(VirtualDisplaySession virtualDisplaySession, LockScreenSession lockScreenSession) { if (lockScreenSession != null) { lockScreenSession.setLockCredential(); } // Create new virtual display. final DisplayContent newDisplay = virtualDisplaySession.createDisplay(); mWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); // Launch activity on new secondary display. launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId); waitAndAssertActivityStateOnDisplay(BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED, newDisplay.mId,"Top activity must be on secondary display"); if (lockScreenSession != null) { // Lock the device, so that activity containers will be detached. lockScreenSession.sleepDevice(); } // Finish activity on secondary display. mBroadcastActionTrigger.finishBroadcastReceiverActivity(); if (lockScreenSession != null) { // Unlock and check if the focus is switched back to primary display. lockScreenSession.wakeUpDevice().enterAndConfirmLockCredential(); } waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, DEFAULT_DISPLAY, "Top activity must be switched back to primary display"); } /** * Tests that input events on the primary display take focus from the virtual display. */ @Test public void testStackFocusSwitchOnTouchEvent() { // If config_perDisplayFocusEnabled, the focus will not move even if touching on // the Activity in the different display. assumeFalse(perDisplayFocusEnabled()); // Create new virtual display. final DisplayContent newDisplay = createManagedVirtualDisplaySession().createDisplay(); mWmState.computeState(VIRTUAL_DISPLAY_ACTIVITY); mWmState.assertFocusedActivity("Top activity must be the latest launched one", VIRTUAL_DISPLAY_ACTIVITY); launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Activity launched on secondary display must be resumed"); tapOnDisplayCenter(DEFAULT_DISPLAY); waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, DEFAULT_DISPLAY, "Top activity must be on the primary display"); assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY), pair(newDisplay.mId, TEST_ACTIVITY)); tapOnDisplayCenter(newDisplay.mId); mWmState.waitForValidState(TEST_ACTIVITY); mWmState.assertFocusedAppOnDisplay("App on secondary display must be focused", TEST_ACTIVITY, newDisplay.mId); } /** * Tests that tapping on the primary display after showing the keyguard resumes the * activity on the primary display. */ @Test public void testStackFocusSwitchOnTouchEventAfterKeyguard() { assumeFalse(perDisplayFocusEnabled()); assumeTrue(supportsLockScreen()); // Launch something on the primary display so we know there is a resumed activity there launchActivity(RESIZEABLE_ACTIVITY); waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY, "Activity launched on primary display must be resumed"); final LockScreenSession lockScreenSession = createManagedLockScreenSession(); lockScreenSession.sleepDevice(); // Make sure there is no resumed activity when the primary display is off waitAndAssertActivityState(RESIZEABLE_ACTIVITY, STATE_STOPPED, "Activity launched on primary display must be stopped after turning off"); assertEquals("Unexpected resumed activity", 0, mWmState.getResumedActivitiesCount()); final DisplayContent newDisplay = createManagedExternalDisplaySession() .setCanShowWithInsecureKeyguard(true).createVirtualDisplay(); launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); // Unlock the device and tap on the middle of the primary display lockScreenSession.wakeUpDevice(); executeShellCommand("wm dismiss-keyguard"); mWmState.waitForKeyguardGone(); mWmState.waitForValidState(RESIZEABLE_ACTIVITY, TEST_ACTIVITY); // Check that the test activity is resumed on the external display and is on top waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Activity on external display must be resumed"); assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY), pair(newDisplay.mId, TEST_ACTIVITY)); tapOnDisplayCenter(DEFAULT_DISPLAY); // Check that the activity on the primary display is the topmost resumed waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY, "Activity on primary display must be resumed and on top"); assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY), pair(newDisplay.mId, TEST_ACTIVITY)); } /** * Tests that showWhenLocked works on a secondary display. */ @Test public void testSecondaryDisplayShowWhenLocked() { assumeTrue(supportsSecureLock()); final LockScreenSession lockScreenSession = createManagedLockScreenSession(); lockScreenSession.setLockCredential(); launchActivity(TEST_ACTIVITY); final DisplayContent newDisplay = createManagedExternalDisplaySession() .createVirtualDisplay(); launchActivityOnDisplay(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, newDisplay.mId); lockScreenSession.gotoKeyguard(); waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED, "Expected stopped activity on default display"); waitAndAssertActivityStateOnDisplay(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Expected resumed activity on secondary display"); } /** * Tests tap and set focus between displays. */ @Test public void testSecondaryDisplayFocus() { assumeFalse(perDisplayFocusEnabled()); launchActivity(TEST_ACTIVITY); mWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED); final DisplayContent newDisplay = createManagedVirtualDisplaySession() .setSimulateDisplay(true).createDisplay(); launchActivityOnDisplay(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId); waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId, "Virtual activity should be Top Resumed Activity."); mWmState.assertFocusedAppOnDisplay("Activity on second display must be focused.", VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId); tapOnDisplayCenter(DEFAULT_DISPLAY); waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, "Activity should be top resumed when tapped."); mWmState.assertFocusedActivity("Activity on default display must be top focused.", TEST_ACTIVITY); tapOnDisplayCenter(newDisplay.mId); waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId, "Virtual display activity should be top resumed when tapped."); mWmState.assertFocusedActivity("Activity on second display must be top focused.", VIRTUAL_DISPLAY_ACTIVITY); mWmState.assertFocusedAppOnDisplay( "Activity on default display must be still focused.", TEST_ACTIVITY, DEFAULT_DISPLAY); } /** * Tests that toast works on a secondary display. */ @Test public void testSecondaryDisplayShowToast() { final DisplayContent newDisplay = createManagedVirtualDisplaySession() .setPublicDisplay(true) .createDisplay(); final String TOAST_NAME = "Toast"; launchActivityOnDisplay(TOAST_ACTIVITY, newDisplay.mId); waitAndAssertActivityStateOnDisplay(TOAST_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Activity launched on external display must be resumed"); assertTrue("Toast window must be shown", mWmState.waitForWithAmState( state -> state.containsWindow(TOAST_NAME), "toast window to show")); assertTrue("Toast window must be visible", mWmState.isWindowSurfaceShown(TOAST_NAME)); } /** * Tests that the surface size of a fullscreen task is same as its display's surface size. * Also check that the surface size has updated after reparenting to other display. */ @Test public void testTaskSurfaceSizeAfterReparentDisplay() { try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { // Create new simulated display and launch an activity on it. final DisplayContent newDisplay = virtualDisplaySession.setSimulateDisplay(true) .createDisplay(); launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId, "Top activity must be the newly launched one"); assertTopTaskSameSurfaceSizeWithDisplay(newDisplay.mId); separateTestJournal(); // Destroy the display. } // Activity must be reparented to default display and relaunched. assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */); waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, "Top activity must be reparented to default display"); // Check the surface size after task was reparented to default display. assertTopTaskSameSurfaceSizeWithDisplay(DEFAULT_DISPLAY); } private void assertTopTaskSameSurfaceSizeWithDisplay(int displayId) { final DisplayContent display = mWmState.getDisplay(displayId); final int stackId = mWmState.getFrontRootTaskId(displayId); final ActivityTask task = mWmState.getRootTask(stackId).getTopTask(); assertEquals("Task must have same surface width with its display", display.getSurfaceSize(), task.getSurfaceWidth()); assertEquals("Task must have same surface height with its display", display.getSurfaceSize(), task.getSurfaceHeight()); } @Test public void testAppTransitionForActivityOnDifferentDisplay() { final TestActivitySession transitionActivitySession = createManagedTestActivitySession(); // Create new simulated display. final DisplayContent newDisplay = createManagedVirtualDisplaySession() .setSimulateDisplay(true).createDisplay(); // Launch BottomActivity on top of launcher activity to prevent transition state // affected by wallpaper theme. launchActivityOnDisplay(BOTTOM_ACTIVITY, DEFAULT_DISPLAY); waitAndAssertTopResumedActivity(BOTTOM_ACTIVITY, DEFAULT_DISPLAY, "Activity must be resumed"); // Launch StandardActivity on default display, verify last transition if is correct. transitionActivitySession.launchTestActivityOnDisplaySync(StandardActivity.class, DEFAULT_DISPLAY); mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); mWmState.assertValidity(); assertEquals(TRANSIT_TASK_OPEN, mWmState.getDisplay(DEFAULT_DISPLAY).getLastTransition()); // Finish current activity & launch another TestActivity in virtual display in parallel. transitionActivitySession.finishCurrentActivityNoWait(); launchActivityOnDisplayNoWait(TEST_ACTIVITY, newDisplay.mId); mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); mWmState.waitForAppTransitionIdleOnDisplay(newDisplay.mId); mWmState.assertValidity(); // Verify each display's last transition if is correct as expected. assertEquals(TRANSIT_TASK_CLOSE, mWmState.getDisplay(DEFAULT_DISPLAY).getLastTransition()); assertEquals(TRANSIT_TASK_OPEN, mWmState.getDisplay(newDisplay.mId).getLastTransition()); } @Test public void testNoTransitionWhenMovingActivityToDisplay() throws Exception { // Create new simulated display & capture new display's transition state. final DisplayContent newDisplay = createManagedVirtualDisplaySession() .setSimulateDisplay(true).createDisplay(); // Launch TestActivity in virtual display & capture its transition state. launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); mWmState.waitForAppTransitionIdleOnDisplay(newDisplay.mId); mWmState.assertValidity(); final String lastTranstionOnVirtualDisplay = mWmState .getDisplay(newDisplay.mId).getLastTransition(); // Move TestActivity from virtual display to default display. getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY) .allowMultipleInstances(false).setNewTask(true) .setDisplayId(DEFAULT_DISPLAY).execute(); // Verify TestActivity moved to virtual display. waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, "Existing task must be brought to front"); // Make sure last transition will not change when task move to another display. assertEquals(lastTranstionOnVirtualDisplay, mWmState.getDisplay(newDisplay.mId).getLastTransition()); } @Test public void testPreQTopProcessResumedActivity() { final DisplayContent newDisplay = createManagedVirtualDisplaySession() .setSimulateDisplay(true).createDisplay(); getLaunchActivityBuilder().setUseInstrumentation() .setTargetActivity(SDK_27_TEST_ACTIVITY).setNewTask(true) .setDisplayId(newDisplay.mId).execute(); waitAndAssertTopResumedActivity(SDK_27_TEST_ACTIVITY, newDisplay.mId, "Activity launched on secondary display must be resumed and focused"); getLaunchActivityBuilder().setUseInstrumentation() .setTargetActivity(SDK_27_LAUNCHING_ACTIVITY).setNewTask(true) .setDisplayId(DEFAULT_DISPLAY).setWindowingMode(WINDOWING_MODE_FULLSCREEN) .execute(); waitAndAssertTopResumedActivity(SDK_27_LAUNCHING_ACTIVITY, DEFAULT_DISPLAY, "Activity launched on default display must be resumed and focused"); assertEquals("There must be only one resumed activity in the package.", 1, mWmState.getResumedActivitiesCountInPackage( SDK_27_LAUNCHING_ACTIVITY.getPackageName())); getLaunchActivityBuilder().setUseInstrumentation() .setTargetActivity(SDK_27_SEPARATE_PROCESS_ACTIVITY).setNewTask(true) .setDisplayId(DEFAULT_DISPLAY).setWindowingMode(WINDOWING_MODE_FULLSCREEN) .execute(); waitAndAssertTopResumedActivity(SDK_27_SEPARATE_PROCESS_ACTIVITY, DEFAULT_DISPLAY, "Activity launched on default display must be resumed and focused"); assertTrue("Activity that was on secondary display must be resumed", mWmState.hasActivityState(SDK_27_TEST_ACTIVITY, STATE_RESUMED)); assertEquals("There must be only two resumed activities in the package.", 2, mWmState.getResumedActivitiesCountInPackage( SDK_27_TEST_ACTIVITY.getPackageName())); } @Test public void testPreQTopProcessResumedDisplayMoved() throws Exception { final DisplayContent newDisplay = createManagedVirtualDisplaySession() .setSimulateDisplay(true).createDisplay(); getLaunchActivityBuilder().setUseInstrumentation() .setTargetActivity(SDK_27_LAUNCHING_ACTIVITY).setNewTask(true) .setDisplayId(DEFAULT_DISPLAY).execute(); waitAndAssertTopResumedActivity(SDK_27_LAUNCHING_ACTIVITY, DEFAULT_DISPLAY, "Activity launched on default display must be resumed and focused"); getLaunchActivityBuilder().setUseInstrumentation() .setTargetActivity(SDK_27_TEST_ACTIVITY).setNewTask(true) .setDisplayId(newDisplay.mId).execute(); waitAndAssertTopResumedActivity(SDK_27_TEST_ACTIVITY, newDisplay.mId, "Activity launched on secondary display must be resumed and focused"); tapOnDisplayCenter(DEFAULT_DISPLAY); waitAndAssertTopResumedActivity(SDK_27_LAUNCHING_ACTIVITY, DEFAULT_DISPLAY, "Activity launched on default display must be resumed and focused"); assertEquals("There must be only one resumed activity in the package.", 1, mWmState.getResumedActivitiesCountInPackage( SDK_27_LAUNCHING_ACTIVITY.getPackageName())); } }