| /* |
| * 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.Task; |
| 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.getFocusedTaskId(); |
| Task 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.getFrontRootTaskWindowingMode( |
| 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.assertFocusedRootTask( |
| "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 Task 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<StandardActivity> 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())); |
| |
| // Start SeparateProcessActivity in the same task as LaunchingActivity by setting |
| // allowMultipleInstances to false, and the TestActivity should be resumed. |
| getLaunchActivityBuilder().setUseInstrumentation() |
| .setTargetActivity(SDK_27_SEPARATE_PROCESS_ACTIVITY).setNewTask(true) |
| .setDisplayId(DEFAULT_DISPLAY).setWindowingMode(WINDOWING_MODE_FULLSCREEN) |
| .allowMultipleInstances(false).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())); |
| } |
| } |