| /* |
| * Copyright (C) 2016 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.am; |
| |
| import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; |
| import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; |
| import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; |
| import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; |
| import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; |
| import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; |
| import static android.server.am.ActivityLauncher.KEY_LAUNCH_ACTIVITY; |
| import static android.server.am.ActivityLauncher.KEY_NEW_TASK; |
| import static android.server.am.ActivityManagerDisplayTestBase.ReportedDisplayMetrics.getDisplayMetrics; |
| import static android.server.am.ActivityManagerState.STATE_RESUMED; |
| import static android.server.am.ActivityManagerState.STATE_STOPPED; |
| import static android.server.am.ComponentNameUtils.getActivityName; |
| import static android.server.am.ComponentNameUtils.getWindowName; |
| import static android.server.am.Components.ALT_LAUNCHING_ACTIVITY; |
| import static android.server.am.Components.BOTTOM_ACTIVITY; |
| import static android.server.am.Components.BROADCAST_RECEIVER_ACTIVITY; |
| import static android.server.am.Components.HOME_ACTIVITY; |
| import static android.server.am.Components.LAUNCHING_ACTIVITY; |
| import static android.server.am.Components.LAUNCH_BROADCAST_RECEIVER; |
| import static android.server.am.Components.LAUNCH_TEST_ON_DESTROY_ACTIVITY; |
| import static android.server.am.Components.LaunchBroadcastReceiver.ACTION_TEST_ACTIVITY_START; |
| import static android.server.am.Components.LaunchBroadcastReceiver.EXTRA_COMPONENT_NAME; |
| import static android.server.am.Components.LaunchBroadcastReceiver.EXTRA_TARGET_DISPLAY; |
| import static android.server.am.Components.LaunchBroadcastReceiver.LAUNCH_BROADCAST_ACTION; |
| import static android.server.am.Components.NON_RESIZEABLE_ACTIVITY; |
| import static android.server.am.Components.RESIZEABLE_ACTIVITY; |
| import static android.server.am.Components.SHOW_WHEN_LOCKED_ATTR_ACTIVITY; |
| import static android.server.am.Components.SINGLE_HOME_ACTIVITY; |
| import static android.server.am.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY; |
| import static android.server.am.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2; |
| import static android.server.am.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3; |
| import static android.server.am.Components.TEST_ACTIVITY; |
| import static android.server.am.Components.TOAST_ACTIVITY; |
| import static android.server.am.Components.VIRTUAL_DISPLAY_ACTIVITY; |
| import static android.server.am.StateLogger.logAlways; |
| import static android.server.am.StateLogger.logE; |
| import static android.server.am.UiDeviceUtils.pressSleepButton; |
| import static android.server.am.UiDeviceUtils.pressWakeupButton; |
| import static android.server.am.WindowManagerState.TRANSIT_TASK_CLOSE; |
| import static android.server.am.WindowManagerState.TRANSIT_TASK_OPEN; |
| import static android.server.am.app27.Components.SDK_27_HOME_ACTIVITY; |
| import static android.server.am.lifecycle.ActivityStarterTests.StandardActivity; |
| import static android.server.am.second.Components.EMBEDDING_ACTIVITY; |
| import static android.server.am.second.Components.EmbeddingActivity.ACTION_EMBEDDING_TEST_ACTIVITY_START; |
| import static android.server.am.second.Components.EmbeddingActivity.EXTRA_EMBEDDING_COMPONENT_NAME; |
| import static android.server.am.second.Components.EmbeddingActivity.EXTRA_EMBEDDING_TARGET_DISPLAY; |
| import static android.server.am.second.Components.SECOND_ACTIVITY; |
| import static android.server.am.second.Components.SECOND_LAUNCH_BROADCAST_ACTION; |
| import static android.server.am.second.Components.SECOND_LAUNCH_BROADCAST_RECEIVER; |
| import static android.server.am.second.Components.SECOND_NO_EMBEDDING_ACTIVITY; |
| import static android.server.am.third.Components.THIRD_ACTIVITY; |
| import static android.view.Display.DEFAULT_DISPLAY; |
| import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; |
| |
| import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand; |
| import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent; |
| import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.junit.Assume.assumeNotNull; |
| import static org.junit.Assume.assumeTrue; |
| |
| import android.app.Activity; |
| import android.app.ActivityManager; |
| import android.app.ActivityOptions; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.ContextWrapper; |
| import android.content.Intent; |
| import android.content.res.Configuration; |
| import android.graphics.Rect; |
| import android.hardware.display.DisplayManager; |
| import android.os.Bundle; |
| import android.os.SystemClock; |
| import android.platform.test.annotations.Presubmit; |
| import android.server.am.ActivityManagerState.ActivityDisplay; |
| import android.server.am.ActivityManagerState.ActivityStack; |
| import android.server.am.CommandSession.ActivityCallback; |
| import android.server.am.CommandSession.ActivitySession; |
| import android.server.am.CommandSession.SizeInfo; |
| import android.server.am.TestJournalProvider.TestJournalContainer; |
| import android.server.am.WindowManagerState.WindowState; |
| import android.support.test.InstrumentationRegistry; |
| import android.support.test.filters.FlakyTest; |
| import android.text.TextUtils; |
| import android.util.SparseArray; |
| import android.view.Display; |
| import android.view.inputmethod.EditorInfo; |
| import android.view.inputmethod.InputMethodManager; |
| import android.widget.EditText; |
| import android.widget.LinearLayout; |
| |
| import androidx.annotation.Nullable; |
| |
| import com.android.compatibility.common.util.SystemUtil; |
| import com.android.cts.mockime.ImeEvent; |
| import com.android.cts.mockime.ImeEventStream; |
| import com.android.cts.mockime.ImeSettings; |
| import com.android.cts.mockime.MockImeSession; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| import java.util.function.Predicate; |
| |
| /** |
| * Build/Install/Run: |
| * atest CtsActivityManagerDeviceTestCases:ActivityManagerMultiDisplayTests |
| */ |
| @Presubmit |
| public class ActivityManagerMultiDisplayTests extends ActivityManagerDisplayTestBase { |
| @Before |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| |
| assumeTrue(supportsMultiDisplay()); |
| } |
| |
| /** |
| * Tests launching an activity on virtual display. |
| */ |
| @Test |
| public void testLaunchActivityOnSecondaryDisplay() throws Exception { |
| validateActivityLaunchOnNewDisplay(ACTIVITY_TYPE_STANDARD); |
| } |
| |
| /** |
| * Tests launching a home activity on virtual display. |
| */ |
| @Test |
| public void testLaunchHomeActivityOnSecondaryDisplay() throws Exception { |
| try (final HomeActivitySession homeSession = new HomeActivitySession(HOME_ACTIVITY)) { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| |
| assertEquals("No stacks on newly launched virtual display", 0, |
| newDisplay.mStacks.size()); |
| } |
| } |
| } |
| |
| /** |
| * Tests launching a single instance home activity on virtual display that supports system |
| * decorations. |
| */ |
| // TODO (b/118206886): Will add it back once launcher's patch is merged into master. |
| private void testLaunchSingleHomeActivityOnDisplayWithDecorations() throws Exception { |
| try (final HomeActivitySession session = new HomeActivitySession(SINGLE_HOME_ACTIVITY)) { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display with system decoration support. |
| final ActivityDisplay newDisplay |
| = virtualDisplaySession.setShowSystemDecorations(true).createDisplay(); |
| |
| assertEquals("No stacks on newly launched virtual display", 0, |
| newDisplay.mStacks.size()); |
| } |
| } |
| } |
| |
| /** |
| * Tests launching a home activity on virtual display that supports system decorations. |
| */ |
| // TODO (b/118206886): Will add it back once launcher's patch is merged into master. |
| private void testLaunchHomeActivityOnDisplayWithDecorations() throws Exception { |
| try (final HomeActivitySession homeSession = new HomeActivitySession(HOME_ACTIVITY)) { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display with system decoration support. |
| final ActivityDisplay newDisplay |
| = virtualDisplaySession.setShowSystemDecorations(true).createDisplay(); |
| |
| // Home activity should be automatically launched on the new display. |
| waitAndAssertTopResumedActivity(HOME_ACTIVITY, newDisplay.mId, |
| "Activity launched on secondary display must be focused and on top"); |
| assertEquals("Top activity must be home type", ACTIVITY_TYPE_HOME, |
| mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId)); |
| } |
| } |
| } |
| |
| /** |
| * Tests home activity that target before Q won't be started on virtual display that supports |
| * system decorations. |
| */ |
| // TODO (b/118206886): Will add it back once launcher's patch is merged into master. |
| private void testLaunchSdk27HomeActivityOnDisplayWithDecorations() throws Exception { |
| try (final HomeActivitySession homeSession |
| = new HomeActivitySession(SDK_27_HOME_ACTIVITY)) { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display with system decoration support. |
| final ActivityDisplay newDisplay |
| = virtualDisplaySession.setShowSystemDecorations(true).createDisplay(); |
| |
| // Launching an activity on virtual display. There should only have one stack on the |
| // display, i.e. home stack should not be created. |
| getLaunchActivityBuilder() |
| .setUseInstrumentation() |
| .setWithShellPermission(true) |
| .setNewTask(true) |
| .setTargetActivity(TEST_ACTIVITY) |
| .setDisplayId(newDisplay.mId) |
| .execute(); |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Activity launched on secondary display must be focused and on top"); |
| assertEquals("There must have exactly one stack on virtual display.", 1, |
| mAmWmState.getAmState().getDisplay(newDisplay.mId).mStacks.size()); |
| } |
| } |
| } |
| |
| /** |
| * Tests launching a recent activity on virtual display. |
| */ |
| @Test |
| public void testLaunchRecentActivityOnSecondaryDisplay() throws Exception { |
| validateActivityLaunchOnNewDisplay(ACTIVITY_TYPE_RECENTS); |
| } |
| |
| /** |
| * Tests launching an assistant activity on virtual display. |
| */ |
| @Test |
| public void testLaunchAssistantActivityOnSecondaryDisplay() throws Exception { |
| validateActivityLaunchOnNewDisplay(ACTIVITY_TYPE_ASSISTANT); |
| } |
| |
| private void validateActivityLaunchOnNewDisplay(int activityType) throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| |
| // Launch activity on new secondary display. |
| separateTestJournal(); |
| getLaunchActivityBuilder().setUseInstrumentation().setWithShellPermission(true) |
| .setTargetActivity(TEST_ACTIVITY).setNewTask(true) |
| .setMultipleTask(true).setActivityType(activityType) |
| .setDisplayId(newDisplay.mId).execute(); |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Activity launched on secondary display must be focused and on top"); |
| |
| // Check that activity config corresponds to display config. |
| final SizeInfo reportedSizes = getLastReportedSizesForActivity(TEST_ACTIVITY); |
| assertEquals("Activity launched on secondary display must have proper configuration", |
| CUSTOM_DENSITY_DPI, reportedSizes.densityDpi); |
| |
| assertEquals("Top activity must have correct activity type", activityType, |
| mAmWmState.getAmState().getFrontStackActivityType(newDisplay.mId)); |
| } |
| } |
| |
| /** |
| * Tests launching an activity on primary display explicitly. |
| */ |
| @Test |
| public void testLaunchActivityOnPrimaryDisplay() throws Exception { |
| // Launch activity on primary display explicitly. |
| launchActivityOnDisplay(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY); |
| |
| waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY, |
| "Activity launched on primary display must be focused and on top"); |
| |
| // Launch another activity on primary display using the first one |
| getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).setNewTask(true) |
| .setMultipleTask(true).setDisplayId(DEFAULT_DISPLAY).execute(); |
| mAmWmState.computeState(TEST_ACTIVITY); |
| |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, |
| "Activity launched on primary display must be focused"); |
| } |
| |
| /** |
| * Tests launching an existing activity from an activity that resided on secondary display. |
| */ |
| @Test |
| public void testLaunchActivityFromSecondaryDisplay() throws Exception { |
| getLaunchActivityBuilder().setUseInstrumentation() |
| .setTargetActivity(TEST_ACTIVITY).setNewTask(true) |
| .setDisplayId(DEFAULT_DISPLAY).execute(); |
| |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = |
| virtualDisplaySession.setSimulateDisplay(true).createDisplay(); |
| final int newDisplayId = newDisplay.mId; |
| |
| getLaunchActivityBuilder().setUseInstrumentation() |
| .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).setNewTask(true) |
| .setDisplayId(newDisplayId).execute(); |
| waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId, |
| "Activity should be resumed on secondary display"); |
| |
| mBroadcastActionTrigger.launchActivityNewTask(getActivityName(TEST_ACTIVITY)); |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, |
| "Activity should be the top resumed on default display"); |
| |
| getLaunchActivityBuilder().setUseInstrumentation() |
| .setTargetActivity(TEST_ACTIVITY).setNewTask(true) |
| .setDisplayId(newDisplayId).execute(); |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Activity should be resumed on secondary display"); |
| } |
| } |
| |
| /** |
| * Tests launching a non-resizeable activity on virtual display. It should land on the |
| * virtual display. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testLaunchNonResizeableActivityOnSecondaryDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(NON_RESIZEABLE_ACTIVITY, newDisplay.mId); |
| |
| waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId, |
| "Activity requested to launch on secondary display must be focused"); |
| } |
| } |
| |
| /** |
| * Tests successfully moving a non-resizeable activity to a virtual display. |
| */ |
| @Test |
| public void testMoveNonResizeableActivityToSecondaryDisplay() throws Exception { |
| try (final VirtualDisplayLauncher virtualLauncher = new VirtualDisplayLauncher()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualLauncher.createDisplay(); |
| // Launch a non-resizeable activity on a primary display. |
| final ActivitySession nonResizeableSession = virtualLauncher.launchActivity( |
| builder -> builder.setTargetActivity(NON_RESIZEABLE_ACTIVITY).setNewTask(true)); |
| |
| // Launch a resizeable activity on new secondary display to create a new stack there. |
| virtualLauncher.launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay); |
| final int externalFrontStackId = mAmWmState.getAmState() |
| .getFrontStackId(newDisplay.mId); |
| |
| // Clear lifecycle callback history before moving the activity so the later verification |
| // can get the callbacks which are related to the reparenting. |
| nonResizeableSession.takeCallbackHistory(); |
| |
| // Try to move the non-resizeable activity to the top of stack on secondary display. |
| moveActivityToStack(NON_RESIZEABLE_ACTIVITY, externalFrontStackId); |
| // Wait for a while to check that it will move. |
| mAmWmState.waitForWithAmState(state -> |
| newDisplay.mId == state.getDisplayByActivity(NON_RESIZEABLE_ACTIVITY), |
| "Waiting to see if activity is moved"); |
| assertEquals("Non-resizeable activity should be moved", |
| newDisplay.mId, |
| mAmWmState.getAmState().getDisplayByActivity(NON_RESIZEABLE_ACTIVITY)); |
| |
| waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId, |
| "The moved non-resizeable activity must be focused"); |
| assertActivityLifecycle(nonResizeableSession, true /* relaunched */); |
| } |
| } |
| |
| /** |
| * Tests launching a non-resizeable activity on virtual display from activity there. It should |
| * land on the secondary display based on the resizeability of the root activity of the task. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testLaunchNonResizeableActivityFromSecondaryDisplaySameTask() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new simulated display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true) |
| .createDisplay(); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId); |
| waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId, |
| "Activity launched on secondary display must be focused"); |
| |
| // Launch non-resizeable activity from secondary display. |
| mBroadcastActionTrigger.launchActivityNewTask(getActivityName(NON_RESIZEABLE_ACTIVITY)); |
| waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId, |
| "Launched activity must be on the secondary display and resumed"); |
| } |
| } |
| |
| /** |
| * Tests launching a non-resizeable activity on virtual display in a new task from activity |
| * there. It must land on the display as its caller. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testLaunchNonResizeableActivityFromSecondaryDisplayNewTask() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); |
| waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId, |
| "Activity launched on secondary display must be focused"); |
| |
| // Launch non-resizeable activity from secondary display in a new task. |
| getLaunchActivityBuilder().setTargetActivity(NON_RESIZEABLE_ACTIVITY) |
| .setNewTask(true).setMultipleTask(true).execute(); |
| |
| mAmWmState.waitForActivityState(NON_RESIZEABLE_ACTIVITY, STATE_RESUMED); |
| |
| // Check that non-resizeable activity is on the same display. |
| final int newFrontStackId = mAmWmState.getAmState().getFocusedStackId(); |
| final ActivityStack newFrontStack = |
| mAmWmState.getAmState().getStackById(newFrontStackId); |
| assertTrue("Launched activity must be on the same display", |
| newDisplay.mId == newFrontStack.mDisplayId); |
| assertEquals("Launched activity must be resumed", |
| getActivityName(NON_RESIZEABLE_ACTIVITY), |
| newFrontStack.mResumedActivity); |
| mAmWmState.assertFocusedStack( |
| "Top stack must be the one with just launched activity", |
| newFrontStackId); |
| mAmWmState.assertResumedActivities("Both displays must have resumed activities", |
| new SparseArray<ComponentName>(){{ |
| put(newDisplay.mId, LAUNCHING_ACTIVITY); |
| put(newFrontStack.mDisplayId, NON_RESIZEABLE_ACTIVITY); |
| }} |
| ); |
| } |
| } |
| |
| /** |
| * Tests launching an activity on a virtual display without special permission must not be |
| * allowed. |
| */ |
| @Test |
| public void testLaunchWithoutPermissionOnVirtualDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| |
| separateTestJournal(); |
| |
| // Try to launch an activity and check it security exception was triggered. |
| getLaunchActivityBuilder() |
| .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER, |
| SECOND_LAUNCH_BROADCAST_ACTION) |
| .setDisplayId(newDisplay.mId) |
| .setTargetActivity(TEST_ACTIVITY) |
| .execute(); |
| |
| assertSecurityExceptionFromActivityLauncher(); |
| |
| mAmWmState.computeState(TEST_ACTIVITY); |
| assertFalse("Restricted activity must not be launched", |
| mAmWmState.getAmState().containsActivity(TEST_ACTIVITY)); |
| } |
| } |
| |
| /** |
| * Tests launching an activity on a virtual display without special permission must be allowed |
| * for activities with same UID. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testLaunchWithoutPermissionOnVirtualDisplayByOwner() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| |
| // Try to launch an activity and check it security exception was triggered. |
| getLaunchActivityBuilder() |
| .setUseBroadcastReceiver(LAUNCH_BROADCAST_RECEIVER, LAUNCH_BROADCAST_ACTION) |
| .setDisplayId(newDisplay.mId) |
| .setTargetActivity(TEST_ACTIVITY) |
| .execute(); |
| |
| mAmWmState.waitForValidState(TEST_ACTIVITY); |
| |
| final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId(); |
| final ActivityStack focusedStack = |
| mAmWmState.getAmState().getStackById(externalFocusedStackId); |
| assertEquals("Focused stack must be on secondary display", newDisplay.mId, |
| focusedStack.mDisplayId); |
| |
| mAmWmState.assertFocusedActivity("Focus must be on newly launched app", |
| TEST_ACTIVITY); |
| assertEquals("Activity launched by owner must be on external display", |
| externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId()); |
| } |
| } |
| |
| /** |
| * Tests launching an activity on virtual display and then launching another activity via shell |
| * command and without specifying the display id - the second activity must appear on the |
| * primary display. |
| */ |
| @Test |
| public void testConsequentLaunchActivity() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Activity launched on secondary display must be on top"); |
| |
| // Launch second activity without specifying display. |
| launchActivity(LAUNCHING_ACTIVITY); |
| |
| // Check that activity is launched in focused stack on primary display. |
| waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY, |
| "Launched activity must be focused"); |
| mAmWmState.assertResumedActivities("Both displays must have resumed activities", |
| new SparseArray<ComponentName>(){{ |
| put(newDisplay.mId, TEST_ACTIVITY); |
| put(DEFAULT_DISPLAY, LAUNCHING_ACTIVITY); |
| }} |
| ); |
| } |
| } |
| |
| /** |
| * Tests launching an activity on simulated display and then launching another activity from the |
| * first one - it must appear on the secondary display, because it was launched from there. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testConsequentLaunchActivityFromSecondaryDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new simulated display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true) |
| .createDisplay(); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); |
| |
| waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId, |
| "Activity launched on secondary display must be on top"); |
| |
| // Launch second activity from app on secondary display without specifying display id. |
| getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).execute(); |
| |
| // Check that activity is launched in focused stack on external display. |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Launched activity must be on top"); |
| } |
| } |
| |
| /** |
| * Tests launching an activity on virtual display and then launching another activity from the |
| * first one - it must appear on the secondary display, because it was launched from there. |
| */ |
| @Test |
| public void testConsequentLaunchActivityFromVirtualDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); |
| |
| waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId, |
| "Activity launched on secondary display must be on top"); |
| |
| // Launch second activity from app on secondary display without specifying display id. |
| getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).execute(); |
| mAmWmState.computeState(TEST_ACTIVITY); |
| |
| // Check that activity is launched in focused stack on external display. |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Launched activity must be on top"); |
| } |
| } |
| |
| /** |
| * Tests launching an activity on virtual display and then launching another activity from the |
| * first one with specifying the target display - it must appear on the secondary display. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testConsequentLaunchActivityFromVirtualDisplayToTargetDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); |
| |
| waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId, |
| "Activity launched on secondary display must be on top"); |
| |
| // Launch second activity from app on secondary display specifying same display id. |
| getLaunchActivityBuilder() |
| .setTargetActivity(SECOND_ACTIVITY) |
| .setDisplayId(newDisplay.mId) |
| .execute(); |
| |
| // Check that activity is launched in focused stack on external display. |
| waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId, |
| "Launched activity must be on top"); |
| |
| // Launch other activity with different uid and check if it has launched successfully. |
| getLaunchActivityBuilder() |
| .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER, |
| SECOND_LAUNCH_BROADCAST_ACTION) |
| .setDisplayId(newDisplay.mId) |
| .setTargetActivity(THIRD_ACTIVITY) |
| .execute(); |
| |
| // Check that activity is launched in focused stack on external display. |
| waitAndAssertTopResumedActivity(THIRD_ACTIVITY, newDisplay.mId, |
| "Launched activity must be on top"); |
| } |
| } |
| |
| /** |
| * Tests launching an activity on virtual display and then launching another activity that |
| * doesn't allow embedding - it should fail with security exception. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testConsequentLaunchActivityFromVirtualDisplayNoEmbedding() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); |
| |
| waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId, |
| "Activity launched on secondary display must be resumed"); |
| |
| separateTestJournal(); |
| |
| // Launch second activity from app on secondary display specifying same display id. |
| getLaunchActivityBuilder() |
| .setTargetActivity(SECOND_NO_EMBEDDING_ACTIVITY) |
| .setDisplayId(newDisplay.mId) |
| .execute(); |
| |
| assertSecurityExceptionFromActivityLauncher(); |
| } |
| } |
| |
| /** |
| * Tests launching an activity to secondary display from activity on primary display. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testLaunchActivityFromAppToSecondaryDisplay() throws Exception { |
| // Start launching activity. |
| launchActivity(LAUNCHING_ACTIVITY); |
| |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new simulated display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true) |
| .createDisplay(); |
| |
| // Launch activity on secondary display from the app on primary display. |
| getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY) |
| .setDisplayId(newDisplay.mId).execute(); |
| |
| // Check that activity is launched on external display. |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Activity launched on secondary display must be focused"); |
| mAmWmState.assertResumedActivities("Both displays must have resumed activities", |
| new SparseArray<ComponentName>(){{ |
| put(DEFAULT_DISPLAY, LAUNCHING_ACTIVITY); |
| put(newDisplay.mId, TEST_ACTIVITY); |
| }} |
| ); |
| } |
| } |
| |
| /** |
| * Tests |
| * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)} |
| * for simulated display. It is owned by system and is public, so should be accessible. |
| */ |
| @Test |
| public void testCanAccessSystemOwnedDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true) |
| .createDisplay(); |
| |
| final Context targetContext = InstrumentationRegistry.getTargetContext(); |
| final ActivityManager activityManager = |
| (ActivityManager) targetContext.getSystemService(Context.ACTIVITY_SERVICE); |
| final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(TEST_ACTIVITY); |
| |
| assertTrue(activityManager.isActivityStartAllowedOnDisplay(targetContext, |
| newDisplay.mId, intent)); |
| } |
| } |
| |
| /** |
| * Tests |
| * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)} |
| * for a public virtual display and an activity that doesn't support embedding from shell. |
| */ |
| @Test |
| public void testCanAccessPublicVirtualDisplayWithInternalPermission() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true) |
| .createDisplay(); |
| |
| final Context targetContext = InstrumentationRegistry.getTargetContext(); |
| final ActivityManager activityManager = |
| (ActivityManager) targetContext.getSystemService(Context.ACTIVITY_SERVICE); |
| final Intent intent = new Intent(Intent.ACTION_VIEW) |
| .setComponent(SECOND_NO_EMBEDDING_ACTIVITY); |
| |
| SystemUtil.runWithShellPermissionIdentity(() -> |
| assertTrue(activityManager.isActivityStartAllowedOnDisplay(targetContext, |
| newDisplay.mId, intent)), "android.permission.INTERNAL_SYSTEM_WINDOW"); |
| } |
| } |
| |
| /** |
| * Tests |
| * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)} |
| * for a private virtual display and an activity that doesn't support embedding from shell. |
| */ |
| @Test |
| public void testCanAccessPrivateVirtualDisplayWithInternalPermission() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false) |
| .createDisplay(); |
| |
| final Context targetContext = InstrumentationRegistry.getTargetContext(); |
| final ActivityManager activityManager = |
| (ActivityManager) targetContext.getSystemService(Context.ACTIVITY_SERVICE); |
| final Intent intent = new Intent(Intent.ACTION_VIEW) |
| .setComponent(SECOND_NO_EMBEDDING_ACTIVITY); |
| |
| SystemUtil.runWithShellPermissionIdentity(() -> |
| assertTrue(activityManager.isActivityStartAllowedOnDisplay(targetContext, |
| newDisplay.mId, intent)), "android.permission.INTERNAL_SYSTEM_WINDOW"); |
| } |
| } |
| |
| /** |
| * Tests |
| * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)} |
| * for a public virtual display, an activity that supports embedding but the launching entity |
| * does not have required permission to embed an activity from other app. |
| */ |
| @Test |
| public void testCantAccessPublicVirtualDisplayNoEmbeddingPermission() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true) |
| .createDisplay(); |
| |
| final Context targetContext = InstrumentationRegistry.getTargetContext(); |
| final ActivityManager activityManager = |
| (ActivityManager) targetContext.getSystemService(Context.ACTIVITY_SERVICE); |
| final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(SECOND_ACTIVITY); |
| |
| assertFalse(activityManager.isActivityStartAllowedOnDisplay(targetContext, |
| newDisplay.mId, intent)); |
| } |
| } |
| |
| /** |
| * Tests |
| * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)} |
| * for a public virtual display and an activity that does not support embedding. |
| */ |
| @Test |
| public void testCantAccessPublicVirtualDisplayActivityEmbeddingNotAllowed() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true) |
| .createDisplay(); |
| |
| final Context targetContext = InstrumentationRegistry.getTargetContext(); |
| final ActivityManager activityManager = |
| (ActivityManager) targetContext.getSystemService(Context.ACTIVITY_SERVICE); |
| final Intent intent = new Intent(Intent.ACTION_VIEW) |
| .setComponent(SECOND_NO_EMBEDDING_ACTIVITY); |
| |
| SystemUtil.runWithShellPermissionIdentity(() -> |
| assertFalse(activityManager.isActivityStartAllowedOnDisplay(targetContext, |
| newDisplay.mId, intent)), "android.permission.ACTIVITY_EMBEDDING"); |
| } |
| } |
| |
| /** |
| * Tests |
| * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)} |
| * for a public virtual display and an activity that supports embedding. |
| */ |
| @Test |
| public void testCanAccessPublicVirtualDisplayActivityEmbeddingAllowed() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true) |
| .createDisplay(); |
| |
| final Context targetContext = InstrumentationRegistry.getTargetContext(); |
| final ActivityManager activityManager = |
| (ActivityManager) targetContext.getSystemService(Context.ACTIVITY_SERVICE); |
| final Intent intent = new Intent(Intent.ACTION_VIEW) |
| .setComponent(SECOND_ACTIVITY); |
| |
| SystemUtil.runWithShellPermissionIdentity(() -> |
| assertTrue(activityManager.isActivityStartAllowedOnDisplay(targetContext, |
| newDisplay.mId, intent)), "android.permission.ACTIVITY_EMBEDDING"); |
| } |
| } |
| |
| /** |
| * Tests |
| * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)} |
| * for a private virtual display. |
| */ |
| @Test |
| public void testCantAccessPrivateVirtualDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false) |
| .createDisplay(); |
| |
| final Context targetContext = InstrumentationRegistry.getTargetContext(); |
| final ActivityManager activityManager = |
| (ActivityManager) targetContext.getSystemService(Context.ACTIVITY_SERVICE); |
| final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(SECOND_ACTIVITY); |
| |
| assertFalse(activityManager.isActivityStartAllowedOnDisplay(targetContext, |
| newDisplay.mId, intent)); |
| } |
| } |
| |
| /** |
| * Tests |
| * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)} |
| * for a private virtual display to check the start of its own activity. |
| */ |
| @Test |
| public void testCanAccessPrivateVirtualDisplayByOwner() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false) |
| .createDisplay(); |
| |
| // Check the embedding call |
| separateTestJournal(); |
| mContext.sendBroadcast(new Intent(ACTION_TEST_ACTIVITY_START) |
| .setPackage(LAUNCH_BROADCAST_RECEIVER.getPackageName()) |
| .setFlags(Intent.FLAG_RECEIVER_FOREGROUND) |
| .putExtra(EXTRA_COMPONENT_NAME, TEST_ACTIVITY) |
| .putExtra(EXTRA_TARGET_DISPLAY, newDisplay.mId)); |
| |
| assertActivityStartCheckResult(true); |
| } |
| } |
| |
| /** |
| * Tests |
| * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)} |
| * for a private virtual display by UID present on that display and target activity that allows |
| * embedding. |
| */ |
| @Test |
| public void testCanAccessPrivateVirtualDisplayByUidPresentOnDisplayActivityEmbeddingAllowed() |
| throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false) |
| .createDisplay(); |
| // Launch a test activity into the target display |
| launchActivityOnDisplay(EMBEDDING_ACTIVITY, newDisplay.mId); |
| |
| // Check the embedding call |
| separateTestJournal(); |
| mContext.sendBroadcast(new Intent(ACTION_EMBEDDING_TEST_ACTIVITY_START) |
| .setFlags(Intent.FLAG_RECEIVER_FOREGROUND) |
| .putExtra(EXTRA_EMBEDDING_COMPONENT_NAME, SECOND_ACTIVITY) |
| .putExtra(EXTRA_EMBEDDING_TARGET_DISPLAY, newDisplay.mId)); |
| |
| assertActivityStartCheckResult(true); |
| } |
| } |
| |
| /** |
| * Tests |
| * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)} |
| * for a private virtual display by UID present on that display and target activity that does |
| * not allow embedding. |
| */ |
| @Test |
| public void testCanAccessPrivateVirtualDisplayByUidPresentOnDisplayActivityEmbeddingNotAllowed() |
| throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(false) |
| .createDisplay(); |
| // Launch a test activity into the target display |
| launchActivityOnDisplay(EMBEDDING_ACTIVITY, newDisplay.mId); |
| |
| // Check the embedding call |
| separateTestJournal(); |
| mContext.sendBroadcast(new Intent(ACTION_EMBEDDING_TEST_ACTIVITY_START) |
| .setFlags(Intent.FLAG_RECEIVER_FOREGROUND) |
| .putExtra(EXTRA_EMBEDDING_COMPONENT_NAME, SECOND_NO_EMBEDDING_ACTIVITY) |
| .putExtra(EXTRA_EMBEDDING_TARGET_DISPLAY, newDisplay.mId)); |
| |
| assertActivityStartCheckResult(false); |
| } |
| } |
| |
| private void assertActivityStartCheckResult(boolean expected) { |
| final String component = ActivityLauncher.TAG; |
| for (int retry = 1; retry <= 5; retry++) { |
| final Bundle extras = TestJournalContainer.get(component).extras; |
| if (extras.containsKey(ActivityLauncher.KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY)) { |
| assertEquals("Activity start check must match", expected, extras |
| .getBoolean(ActivityLauncher.KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY)); |
| return; |
| } |
| |
| logAlways("***Waiting for activity start check for " + component |
| + " ... retry=" + retry); |
| SystemClock.sleep(500); |
| } |
| fail("Expected activity start check from " + component + " not found"); |
| } |
| |
| /** |
| * Tests launching activities on secondary and then on primary display to see if the stack |
| * visibility is not affected. |
| */ |
| @Test |
| public void testLaunchActivitiesAffectsVisibility() throws Exception { |
| // Start launching activity. |
| launchActivity(LAUNCHING_ACTIVITY); |
| |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */); |
| mAmWmState.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(); |
| mAmWmState.waitForValidState(RESIZEABLE_ACTIVITY); |
| mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */); |
| mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */); |
| mAmWmState.assertResumedActivities("Both displays must have resumed activities", |
| new SparseArray<ComponentName>(){{ |
| put(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY); |
| put(newDisplay.mId, TEST_ACTIVITY); |
| }} |
| ); |
| } |
| } |
| |
| /** |
| * Test that move-task works when moving between displays. |
| */ |
| @Test |
| public void testMoveTaskBetweenDisplays() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| mAmWmState.assertFocusedActivity("Virtual display activity must be on top", |
| VIRTUAL_DISPLAY_ACTIVITY); |
| final int defaultDisplayStackId = mAmWmState.getAmState().getFocusedStackId(); |
| ActivityStack frontStack = mAmWmState.getAmState().getStackById( |
| defaultDisplayStackId); |
| assertEquals("Top stack must remain on primary display", |
| DEFAULT_DISPLAY, frontStack.mDisplayId); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Top activity must be on secondary display"); |
| mAmWmState.assertResumedActivities("Both displays must have resumed activities", |
| new SparseArray<ComponentName>(){{ |
| put(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY); |
| put(newDisplay.mId, TEST_ACTIVITY); |
| }} |
| ); |
| |
| // Move activity from secondary display to primary. |
| moveActivityToStack(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 |
| @FlakyTest(bugId = 77207453) |
| public void testStackFocusSwitchOnDisplayRemoved() throws Exception { |
| assumeTrue(supportsSplitScreenMultiWindow()); |
| |
| // Start launching activity into docked stack. |
| launchActivitiesInSplitScreen( |
| getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY), |
| getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)); |
| mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */); |
| |
| tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */, |
| WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); |
| } |
| |
| /** |
| * 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 |
| @FlakyTest(bugId = 77207453) |
| public void testStackFocusSwitchOnDisplayRemoved2() throws Exception { |
| assumeTrue(supportsSplitScreenMultiWindow()); |
| |
| // Setup split-screen. |
| launchActivitiesInSplitScreen( |
| getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY), |
| getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY)); |
| mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */); |
| |
| tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */, |
| WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); |
| } |
| |
| /** |
| * 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() throws Exception { |
| // Start an activity on default display to determine default stack. |
| launchActivity(BROADCAST_RECEIVER_ACTIVITY); |
| final int focusedStackWindowingMode = mAmWmState.getAmState().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) |
| throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession |
| .setPublicDisplay(true) |
| .setLaunchInSplitScreen(splitScreen) |
| .createDisplay(); |
| mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| if (splitScreen) { |
| mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */); |
| } |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId); |
| waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId, |
| "Top activity must be on secondary display"); |
| final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId); |
| mAmWmState.assertFocusedStack("Top stack must be on secondary display", frontStackId); |
| |
| separateTestJournal(); |
| // Destroy virtual display. |
| } |
| |
| mAmWmState.computeState(true); |
| assertActivityLifecycle(RESIZEABLE_ACTIVITY, false /* relaunched */); |
| mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(RESIZEABLE_ACTIVITY) |
| .setWindowingMode(windowingMode) |
| .setActivityType(ACTIVITY_TYPE_STANDARD) |
| .build()); |
| mAmWmState.assertSanity(); |
| mAmWmState.assertValidBounds(true /* compareTaskAndStackBounds */); |
| |
| // Check if the top activity is now back on primary display. |
| mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */); |
| mAmWmState.assertFocusedStack( |
| "Default stack on primary display must be focused after display removed", |
| windowingMode, ACTIVITY_TYPE_STANDARD); |
| mAmWmState.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 |
| @FlakyTest(bugId = 112055644) |
| public void testStackFocusSwitchOnStackEmptiedInSleeping() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession(); |
| final LockScreenSession lockScreenSession = new LockScreenSession()) { |
| validateStackFocusSwitchOnStackEmptied(virtualDisplaySession, lockScreenSession); |
| } |
| } |
| |
| /** |
| * Tests launching activities on secondary display and then finishing it to see if stack focus |
| * is moved correctly. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testStackFocusSwitchOnStackEmptied() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| validateStackFocusSwitchOnStackEmptied(virtualDisplaySession, |
| null /* lockScreenSession */); |
| } |
| } |
| |
| private void validateStackFocusSwitchOnStackEmptied(VirtualDisplaySession virtualDisplaySession, |
| LockScreenSession lockScreenSession) throws Exception { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| final int focusedStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId); |
| waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, 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().unlockDevice(); |
| } |
| |
| 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 |
| @FlakyTest(bugId = 112055644) |
| public void testStackFocusSwitchOnTouchEvent() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| |
| mAmWmState.computeState(VIRTUAL_DISPLAY_ACTIVITY); |
| mAmWmState.assertFocusedActivity("Top activity must be the latest launched one", |
| VIRTUAL_DISPLAY_ACTIVITY); |
| |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Activity launched on secondary display must be on top"); |
| |
| final ReportedDisplayMetrics displayMetrics = getDisplayMetrics(); |
| final int width = displayMetrics.getSize().getWidth(); |
| final int height = displayMetrics.getSize().getHeight(); |
| tapOnDisplay(width / 2, height / 2, DEFAULT_DISPLAY); |
| |
| waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, DEFAULT_DISPLAY, |
| "Top activity must be on the primary display"); |
| mAmWmState.assertResumedActivities("Both displays must have resumed activities", |
| new SparseArray<ComponentName>(){{ |
| put(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY); |
| put(newDisplay.mId, TEST_ACTIVITY); |
| }} |
| ); |
| mAmWmState.assertFocusedAppOnDisplay("App on secondary display must still be focused", |
| TEST_ACTIVITY, newDisplay.mId); |
| } |
| } |
| |
| /** Test that shell is allowed to launch on secondary displays. */ |
| @Test |
| public void testPermissionLaunchFromShell() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| mAmWmState.assertFocusedActivity("Virtual display activity must be on top", |
| VIRTUAL_DISPLAY_ACTIVITY); |
| final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId(); |
| ActivityStack frontStack = mAmWmState.getAmState().getStackById( |
| defaultDisplayFocusedStackId); |
| assertEquals("Top stack must remain on primary display", |
| DEFAULT_DISPLAY, frontStack.mDisplayId); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Front activity must be on secondary display"); |
| mAmWmState.assertResumedActivities("Both displays must have resumed activities", |
| new SparseArray<ComponentName>(){{ |
| put(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY); |
| put(newDisplay.mId, TEST_ACTIVITY); |
| }} |
| ); |
| |
| // Launch other activity with different uid and check it is launched on dynamic stack on |
| // secondary display. |
| final String startCmd = "am start -n " + getActivityName(SECOND_ACTIVITY) |
| + " --display " + newDisplay.mId; |
| executeShellCommand(startCmd); |
| |
| waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId, |
| "Focus must be on newly launched app"); |
| mAmWmState.assertResumedActivities("Both displays must have resumed activities", |
| new SparseArray<ComponentName>(){{ |
| put(DEFAULT_DISPLAY, VIRTUAL_DISPLAY_ACTIVITY); |
| put(newDisplay.mId, SECOND_ACTIVITY); |
| }} |
| ); |
| } |
| } |
| |
| /** Test that launching app from pending activity queue on external display is allowed. */ |
| @Test |
| @FlakyTest(bugId = 118708868) |
| public void testLaunchPendingActivityOnSecondaryDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new simulated display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true) |
| .createDisplay(); |
| final Bundle bundle = ActivityOptions.makeBasic(). |
| setLaunchDisplayId(newDisplay.mId).toBundle(); |
| final Intent intent = new Intent(Intent.ACTION_VIEW) |
| .setComponent(SECOND_ACTIVITY) |
| .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) |
| .putExtra(KEY_LAUNCH_ACTIVITY, true) |
| .putExtra(KEY_NEW_TASK, true); |
| mContext.startActivity(intent, bundle); |
| |
| // ActivityManagerTestBase.setup would press home key event, which would cause |
| // PhoneWindowManager.startDockOrHome to call AMS.stopAppSwitches. |
| // Since this test case is not start activity from shell, it won't grant |
| // STOP_APP_SWITCHES and this activity should be put into pending activity queue |
| // and this activity should been launched after |
| // ActivityTaskManagerService.APP_SWITCH_DELAY_TIME |
| mAmWmState.waitForPendingActivityContain(SECOND_ACTIVITY); |
| // If the activity is not pending, skip this test. |
| mAmWmState.assumePendingActivityContain(SECOND_ACTIVITY); |
| // In order to speed up test case without waiting for APP_SWITCH_DELAY_TIME, we launch |
| // another activity with LaunchActivityBuilder, in this way the activity can be start |
| // directly and also trigger pending activity to be launched. |
| getLaunchActivityBuilder() |
| .setTargetActivity(THIRD_ACTIVITY) |
| .execute(); |
| mAmWmState.waitForValidState(SECOND_ACTIVITY); |
| waitAndAssertTopResumedActivity(THIRD_ACTIVITY, DEFAULT_DISPLAY, |
| "Top activity must be the newly launched one"); |
| mAmWmState.assertVisibility(SECOND_ACTIVITY, true); |
| assertEquals("Activity launched by app on secondary display must be on that display", |
| newDisplay.mId, mAmWmState.getAmState().getDisplayByActivity(SECOND_ACTIVITY)); |
| } |
| } |
| |
| /** Test that launching from app that is on external display is allowed. */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testPermissionLaunchFromAppOnSecondary() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new simulated display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true) |
| .createDisplay(); |
| |
| // Launch activity with different uid on secondary display. |
| final String startCmd = "am start -n " + getActivityName(SECOND_ACTIVITY) |
| + " --display " + newDisplay.mId; |
| executeShellCommand(startCmd); |
| |
| waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId, |
| "Top activity must be the newly launched one"); |
| |
| // Launch another activity with third different uid from app on secondary display and |
| // check it is launched on secondary display. |
| getLaunchActivityBuilder() |
| .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER, |
| SECOND_LAUNCH_BROADCAST_ACTION) |
| .setDisplayId(newDisplay.mId) |
| .setTargetActivity(THIRD_ACTIVITY) |
| .execute(); |
| |
| waitAndAssertTopResumedActivity(THIRD_ACTIVITY, newDisplay.mId, |
| "Top activity must be the newly launched one"); |
| } |
| } |
| |
| /** Tests that an activity can launch an activity from a different UID into its own task. */ |
| @Test |
| public void testPermissionLaunchMultiUidTask() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true) |
| .createDisplay(); |
| |
| launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); |
| mAmWmState.computeState(LAUNCHING_ACTIVITY); |
| |
| // Check that the first activity is launched onto the secondary display |
| final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId); |
| ActivityStack frontStack = mAmWmState.getAmState().getStackById( |
| frontStackId); |
| assertEquals("Activity launched on secondary display must be resumed", |
| getActivityName(LAUNCHING_ACTIVITY), |
| frontStack.mResumedActivity); |
| mAmWmState.assertFocusedStack("Top stack must be on secondary display", |
| frontStackId); |
| |
| // Launch an activity from a different UID into the first activity's task |
| getLaunchActivityBuilder().setTargetActivity(SECOND_ACTIVITY).execute(); |
| |
| waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId, |
| "Top activity must be the newly launched one"); |
| frontStack = mAmWmState.getAmState().getStackById(frontStackId); |
| assertEquals("Secondary display must contain 1 task", 1, frontStack.getTasks().size()); |
| } |
| } |
| |
| /** |
| * Test that launching from display owner is allowed even when the the display owner |
| * doesn't have anything on the display. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testPermissionLaunchFromOwner() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| mAmWmState.assertFocusedActivity("Virtual display activity must be focused", |
| VIRTUAL_DISPLAY_ACTIVITY); |
| final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId(); |
| ActivityStack frontStack = |
| mAmWmState.getAmState().getStackById(defaultDisplayFocusedStackId); |
| assertEquals("Top stack must remain on primary display", |
| DEFAULT_DISPLAY, frontStack.mDisplayId); |
| |
| // Launch other activity with different uid on secondary display. |
| final String startCmd = "am start -n " + getActivityName(SECOND_ACTIVITY) |
| + " --display " + newDisplay.mId; |
| executeShellCommand(startCmd); |
| |
| waitAndAssertTopResumedActivity(SECOND_ACTIVITY, newDisplay.mId, |
| "Top activity must be the newly launched one"); |
| |
| // Check that owner uid can launch its own activity on secondary display. |
| getLaunchActivityBuilder() |
| .setUseBroadcastReceiver(LAUNCH_BROADCAST_RECEIVER, LAUNCH_BROADCAST_ACTION) |
| .setNewTask(true) |
| .setMultipleTask(true) |
| .setDisplayId(newDisplay.mId) |
| .execute(); |
| |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Top activity must be the newly launched one"); |
| } |
| } |
| |
| /** |
| * Test that launching from app that is not present on external display and doesn't own it to |
| * that external display is not allowed. |
| */ |
| @Test |
| public void testPermissionLaunchFromDifferentApp() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| mAmWmState.assertFocusedActivity("Virtual display activity must be focused", |
| VIRTUAL_DISPLAY_ACTIVITY); |
| final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId(); |
| ActivityStack frontStack = mAmWmState.getAmState().getStackById( |
| defaultDisplayFocusedStackId); |
| assertEquals("Top stack must remain on primary display", |
| DEFAULT_DISPLAY, frontStack.mDisplayId); |
| |
| // Launch activity on new secondary display. |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Top activity must be the newly launched one"); |
| |
| separateTestJournal(); |
| |
| // Launch other activity with different uid and check security exception is triggered. |
| getLaunchActivityBuilder() |
| .setUseBroadcastReceiver(SECOND_LAUNCH_BROADCAST_RECEIVER, |
| SECOND_LAUNCH_BROADCAST_ACTION) |
| .setDisplayId(newDisplay.mId) |
| .setTargetActivity(THIRD_ACTIVITY) |
| .execute(); |
| |
| assertSecurityExceptionFromActivityLauncher(); |
| |
| mAmWmState.waitForValidState(TEST_ACTIVITY); |
| mAmWmState.assertFocusedActivity("Top activity must be the first one launched", |
| TEST_ACTIVITY); |
| } |
| } |
| |
| private void assertSecurityExceptionFromActivityLauncher() { |
| final String component = ActivityLauncher.TAG; |
| for (int retry = 1; retry <= 5; retry++) { |
| if (ActivityLauncher.hasCaughtSecurityException()) { |
| return; |
| } |
| |
| logAlways("***Waiting for SecurityException from " + component + " ... retry=" + retry); |
| SystemClock.sleep(500); |
| } |
| fail("Expected exception from " + component + " not found"); |
| } |
| |
| /** |
| * Test that only private virtual display can show content with insecure keyguard. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testFlagShowWithInsecureKeyguardOnPublicVirtualDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Try to create new show-with-insecure-keyguard public virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession |
| .setPublicDisplay(true) |
| .setCanShowWithInsecureKeyguard(true) |
| .setMustBeCreated(false) |
| .createDisplay(); |
| |
| // Check that the display is not created. |
| assertNull(newDisplay); |
| } |
| } |
| |
| /** |
| * Test that all activities that were on the private display are destroyed on display removal. |
| */ |
| @Test |
| public void testContentDestroyOnDisplayRemoved() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new private virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| |
| // Launch activities on new secondary display. |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Launched activity must be on top"); |
| |
| launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId); |
| waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId, |
| "Launched activity must be on top"); |
| |
| separateTestJournal(); |
| // Destroy the display and check if activities are removed from system. |
| } |
| |
| mAmWmState.waitForActivityRemoved(TEST_ACTIVITY); |
| mAmWmState.waitForActivityRemoved(RESIZEABLE_ACTIVITY); |
| |
| // Check AM state. |
| assertFalse("Activity from removed display must be destroyed", |
| mAmWmState.getAmState().containsActivity(TEST_ACTIVITY)); |
| assertFalse("Activity from removed display must be destroyed", |
| mAmWmState.getAmState().containsActivity(RESIZEABLE_ACTIVITY)); |
| // Check WM state. |
| assertFalse("Activity windows from removed display must be destroyed", |
| mAmWmState.getWmState().containsWindow(getWindowName(TEST_ACTIVITY))); |
| assertFalse("Activity windows from removed display must be destroyed", |
| mAmWmState.getWmState().containsWindow(getWindowName(RESIZEABLE_ACTIVITY))); |
| // Check activity logs. |
| assertActivityDestroyed(TEST_ACTIVITY); |
| assertActivityDestroyed(RESIZEABLE_ACTIVITY); |
| } |
| |
| /** |
| * Test that newly launched activity will be landing on default display on display removal. |
| */ |
| @Test |
| public void testActivityLaunchOnContentDestroyDisplayRemoved() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new private virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| |
| // Launch activities on new secondary display. |
| getLaunchActivityBuilder().setUseInstrumentation().setWithShellPermission(true) |
| .setTargetActivity(LAUNCH_TEST_ON_DESTROY_ACTIVITY).setNewTask(true) |
| .setMultipleTask(true).setDisplayId(newDisplay.mId).execute(); |
| |
| waitAndAssertTopResumedActivity(LAUNCH_TEST_ON_DESTROY_ACTIVITY, newDisplay.mId, |
| "Launched activity must be on top"); |
| |
| // Destroy the display |
| } |
| |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, |
| "Newly launches activity should be landing on default display"); |
| } |
| |
| /** |
| * Test that the update of display metrics updates all its content. |
| */ |
| @Test |
| @FlakyTest(bugId = 121105810) |
| public void testDisplayResize() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| |
| // Launch a resizeable activity on new secondary display. |
| separateTestJournal(); |
| launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId); |
| waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId, |
| "Launched activity must be on top"); |
| |
| // Grab reported sizes and compute new with slight size change. |
| final SizeInfo initialSize = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY); |
| |
| // Resize the display |
| separateTestJournal(); |
| virtualDisplaySession.resizeDisplay(); |
| |
| mAmWmState.waitForWithAmState(amState -> { |
| try { |
| return readConfigChangeNumber(RESIZEABLE_ACTIVITY) == 1 |
| && amState.hasActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED); |
| } catch (Exception e) { |
| logE("Error waiting for valid state: " + e.getMessage()); |
| return false; |
| } |
| }, "Wait for the configuration change to happen and for activity to be resumed."); |
| |
| mAmWmState.computeState(false /* compareTaskAndStackBounds */, |
| new WaitForValidActivityState(RESIZEABLE_ACTIVITY), |
| new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY)); |
| mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true); |
| mAmWmState.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); |
| } |
| } |
| |
| /** Read the number of configuration changes sent to activity from logs. */ |
| private int readConfigChangeNumber(ComponentName activityName) |
| throws Exception { |
| return (new ActivityLifecycleCounts(activityName)) |
| .getCount(ActivityCallback.ON_CONFIGURATION_CHANGED); |
| } |
| |
| /** |
| * Tests that when an activity is launched with displayId specified and there is an existing |
| * matching task on some other display - that task will moved to the target display. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testMoveToDisplayOnLaunch() throws Exception { |
| // Launch activity with unique affinity, so it will the only one in its task. |
| launchActivity(LAUNCHING_ACTIVITY); |
| |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| // Launch something to that display so that a new stack is created. We need this to be |
| // able to compare task numbers in stacks later. |
| launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay.mId); |
| mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */); |
| |
| final int stackNum = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY) |
| .mStacks.size(); |
| final int stackNumOnSecondary = mAmWmState.getAmState() |
| .getDisplay(newDisplay.mId).mStacks.size(); |
| |
| // Launch activity on new secondary display. |
| // Using custom command here, because normally we add flags |
| // {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} |
| // when launching on some specific display. We don't do it here as we want an existing |
| // task to be used. |
| final String launchCommand = "am start -n " + getActivityName(LAUNCHING_ACTIVITY) |
| + " --display " + newDisplay.mId; |
| executeShellCommand(launchCommand); |
| |
| // Check that activity is brought to front. |
| waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId, |
| "Existing task must be brought to front"); |
| |
| // Check that task has moved from primary display to secondary. |
| // Since it is 1-to-1 relationship between task and stack for standard type & |
| // fullscreen activity, we check the number of stacks here |
| final int stackNumFinal = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY) |
| .mStacks.size(); |
| assertEquals("Stack number in default stack must be decremented.", stackNum - 1, |
| stackNumFinal); |
| final int stackNumFinalOnSecondary = mAmWmState.getAmState() |
| .getDisplay(newDisplay.mId).mStacks.size(); |
| assertEquals("Stack number on external display must be incremented.", |
| stackNumOnSecondary + 1, stackNumFinalOnSecondary); |
| } |
| } |
| |
| /** |
| * Tests that when an activity is launched with displayId specified and there is an existing |
| * matching task on some other display - that task will moved to the target display. |
| */ |
| @Test |
| public void testMoveToEmptyDisplayOnLaunch() throws Exception { |
| // Launch activity with unique affinity, so it will the only one in its task. And choose |
| // resizeable activity to prevent the test activity be relaunched when launch it to another |
| // display, which may affect on this test case. |
| launchActivity(RESIZEABLE_ACTIVITY); |
| |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| |
| final int stackNum = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY).mStacks.size(); |
| |
| // Launch activity on new secondary display. |
| // Using custom command here, because normally we add flags |
| // {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} |
| // when launching on some specific display. We don't do it here as we want an existing |
| // task to be used. |
| final String launchCommand = "am start -n " + getActivityName(RESIZEABLE_ACTIVITY) |
| + " --display " + newDisplay.mId; |
| executeShellCommand(launchCommand); |
| |
| // Check that activity is brought to front. |
| waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, newDisplay.mId, |
| "Existing task must be brought to front"); |
| |
| // Check that task has moved from primary display to secondary. |
| final int stackNumFinal = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY) |
| .mStacks.size(); |
| assertEquals("Stack number in default stack must be decremented.", stackNum - 1, |
| stackNumFinal); |
| } |
| } |
| |
| /** |
| * Tests that when primary display is rotated secondary displays are not affected. |
| */ |
| @Test |
| public void testRotationNotAffectingSecondaryScreen() throws Exception { |
| try (final VirtualDisplayLauncher virtualLauncher = new VirtualDisplayLauncher()) { |
| // Create new virtual display. |
| final ActivityDisplay newDisplay = virtualLauncher.setResizeDisplay(false) |
| .createDisplay(); |
| mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */); |
| |
| // Launch activity on new secondary display. |
| final ActivitySession resizeableActivitySession = |
| virtualLauncher.launchActivityOnDisplay(RESIZEABLE_ACTIVITY, newDisplay); |
| waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, 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); |
| |
| try (final RotationSession rotationSession = new RotationSession()) { |
| // 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 = mAmWmState.getWmState().getRotation(); |
| rotationSession.set((initialRotation + 1) % 4); |
| |
| final ActivitySession testActivitySession = |
| virtualLauncher.launchActivityOnDisplay(TEST_ACTIVITY, newDisplay); |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, 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) throws Exception { |
| 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 task affinity does affect what display an activity is launched on but that |
| * matching the task component root does. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testTaskMatchAcrossDisplays() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| |
| launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); |
| mAmWmState.computeState(LAUNCHING_ACTIVITY); |
| |
| // Check that activity is on the secondary display. |
| final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId); |
| final ActivityStack firstFrontStack = |
| mAmWmState.getAmState().getStackById(frontStackId); |
| assertEquals("Activity launched on secondary display must be resumed", |
| getActivityName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity); |
| mAmWmState.assertFocusedStack("Top stack must be on secondary display", |
| frontStackId); |
| |
| executeShellCommand("am start -n " + getActivityName(ALT_LAUNCHING_ACTIVITY)); |
| mAmWmState.waitForValidState(ALT_LAUNCHING_ACTIVITY); |
| |
| // Check that second activity gets launched on the default display despite |
| // the affinity match on the secondary display. |
| final int defaultDisplayFrontStackId = mAmWmState.getAmState().getFrontStackId( |
| DEFAULT_DISPLAY); |
| final ActivityStack defaultDisplayFrontStack = |
| mAmWmState.getAmState().getStackById(defaultDisplayFrontStackId); |
| assertEquals("Activity launched on default display must be resumed", |
| getActivityName(ALT_LAUNCHING_ACTIVITY), |
| defaultDisplayFrontStack.mResumedActivity); |
| mAmWmState.assertFocusedStack("Top stack must be on primary display", |
| defaultDisplayFrontStackId); |
| |
| executeShellCommand("am start -n " + getActivityName(LAUNCHING_ACTIVITY)); |
| mAmWmState.waitForFocusedStack(frontStackId); |
| |
| // Check that the third intent is redirected to the first task due to the root |
| // component match on the secondary display. |
| final ActivityStack secondFrontStack = |
| mAmWmState.getAmState().getStackById(frontStackId); |
| assertEquals("Activity launched on secondary display must be resumed", |
| getActivityName(LAUNCHING_ACTIVITY), secondFrontStack.mResumedActivity); |
| mAmWmState.assertFocusedStack("Top stack must be on primary display", frontStackId); |
| assertEquals("Top stack must only contain 1 task", |
| 1, secondFrontStack.getTasks().size()); |
| assertEquals("Top task must only contain 1 activity", |
| 1, secondFrontStack.getTasks().get(0).mActivities.size()); |
| } |
| } |
| |
| /** |
| * Tests that an activity is launched on the preferred display where the caller resided when |
| * both displays have matching tasks. |
| */ |
| @Test |
| public void testTaskMatchOrderAcrossDisplays() throws Exception { |
| getLaunchActivityBuilder().setUseInstrumentation() |
| .setTargetActivity(TEST_ACTIVITY).setNewTask(true) |
| .setDisplayId(DEFAULT_DISPLAY).execute(); |
| final int stackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY); |
| |
| getLaunchActivityBuilder().setUseInstrumentation() |
| .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).setNewTask(true) |
| .setDisplayId(DEFAULT_DISPLAY).execute(); |
| |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| getLaunchActivityBuilder().setUseInstrumentation().setWithShellPermission(true) |
| .setTargetActivity(TEST_ACTIVITY).setNewTask(true) |
| .setDisplayId(newDisplay.mId).execute(); |
| assertNotEquals("Top focus stack should not be on default display", |
| stackId, mAmWmState.getAmState().getFocusedStackId()); |
| |
| mBroadcastActionTrigger.launchActivityNewTask(getActivityName(TEST_ACTIVITY)); |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, |
| "Activity must be launched on default display"); |
| mAmWmState.assertFocusedStack("Top focus stack must be on the default display", |
| stackId); |
| } |
| } |
| |
| /** |
| * Tests that the task affinity search respects the launch display id. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testLaunchDisplayAffinityMatch() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay(); |
| |
| launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId); |
| |
| // Check that activity is on the secondary display. |
| final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId); |
| final ActivityStack firstFrontStack = |
| mAmWmState.getAmState().getStackById(frontStackId); |
| assertEquals("Activity launched on secondary display must be resumed", |
| getActivityName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity); |
| mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId); |
| |
| // We don't want FLAG_ACTIVITY_MULTIPLE_TASK, so we can't use launchActivityOnDisplay |
| executeShellCommand("am start -n " + getActivityName(ALT_LAUNCHING_ACTIVITY) |
| + " -f 0x10000000" // FLAG_ACTIVITY_NEW_TASK |
| + " --display " + newDisplay.mId); |
| mAmWmState.computeState(ALT_LAUNCHING_ACTIVITY); |
| |
| // Check that second activity gets launched into the affinity matching |
| // task on the secondary display |
| final int secondFrontStackId = |
| mAmWmState.getAmState().getFrontStackId(newDisplay.mId); |
| final ActivityStack secondFrontStack = |
| mAmWmState.getAmState().getStackById(secondFrontStackId); |
| assertEquals("Activity launched on secondary display must be resumed", |
| getActivityName(ALT_LAUNCHING_ACTIVITY), |
| secondFrontStack.mResumedActivity); |
| mAmWmState.assertFocusedStack("Top stack must be on secondary display", |
| secondFrontStackId); |
| assertEquals("Top stack must only contain 1 task", |
| 1, secondFrontStack.getTasks().size()); |
| assertEquals("Top stack task must contain 2 activities", |
| 2, secondFrontStack.getTasks().get(0).mActivities.size()); |
| } |
| } |
| |
| /** |
| * Tests that a new task launched by an activity will end up on that activity's display |
| * even if the focused stack is not on that activity's display. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testNewTaskSameDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true) |
| .createDisplay(); |
| |
| launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId); |
| |
| // Check that the first activity is launched onto the secondary display |
| waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId, |
| "Activity launched on secondary display must be resumed"); |
| |
| executeShellCommand("am start -n " + getActivityName(TEST_ACTIVITY)); |
| |
| // Check that the second activity is launched on the default display |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, |
| "Activity launched on default display must be resumed"); |
| mAmWmState.assertResumedActivities("Both displays should have resumed activities", |
| new SparseArray<ComponentName>(){{ |
| put(DEFAULT_DISPLAY, TEST_ACTIVITY); |
| put(newDisplay.mId, BROADCAST_RECEIVER_ACTIVITY); |
| }} |
| ); |
| |
| mBroadcastActionTrigger.launchActivityNewTask(getActivityName(LAUNCHING_ACTIVITY)); |
| |
| // Check that the third activity ends up in a new stack in the same display where the |
| // first activity lands |
| waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId, |
| "Activity must be launched on secondary display"); |
| assertEquals("Secondary display must contain 2 stacks", 2, |
| mAmWmState.getAmState().getDisplay(newDisplay.mId).mStacks.size()); |
| mAmWmState.assertResumedActivities("Both displays should have resumed activities", |
| new SparseArray<ComponentName>(){{ |
| put(DEFAULT_DISPLAY, TEST_ACTIVITY); |
| put(newDisplay.mId, LAUNCHING_ACTIVITY); |
| }} |
| ); |
| } |
| } |
| |
| /** |
| * Tests than an immediate launch after new display creation is handled correctly. |
| */ |
| @Test |
| public void testImmediateLaunchOnNewDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new virtual display and immediately launch an activity on it. |
| final ActivityDisplay newDisplay = virtualDisplaySession |
| .setLaunchActivity(TEST_ACTIVITY) |
| .createDisplay(); |
| |
| // Check that activity is launched and placed correctly. |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Test activity must be on top"); |
| final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId); |
| final ActivityStack firstFrontStack = |
| mAmWmState.getAmState().getStackById(frontStackId); |
| assertEquals("Activity launched on secondary display must be resumed", |
| getActivityName(TEST_ACTIVITY), firstFrontStack.mResumedActivity); |
| mAmWmState.assertFocusedStack("Top stack must be on secondary display", |
| frontStackId); |
| } |
| } |
| |
| /** |
| * Tests that turning the primary display off does not affect the activity running |
| * on an external secondary display. |
| */ |
| @Test |
| public void testExternalDisplayActivityTurnPrimaryOff() throws Exception { |
| // 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"); |
| |
| try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession(); |
| final PrimaryDisplayStateSession displayStateSession = |
| new PrimaryDisplayStateSession()) { |
| final ActivityDisplay newDisplay = |
| externalDisplaySession.createVirtualDisplay(true /* showContentWhenLocked */); |
| |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| |
| // Check that the activity is launched onto the external display |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Activity launched on external display must be resumed"); |
| mAmWmState.assertFocusedAppOnDisplay("App on default display must still be focused", |
| RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY); |
| |
| separateTestJournal(); |
| displayStateSession.turnScreenOff(); |
| |
| // Wait for the fullscreen stack to start sleeping, and then make sure the |
| // test activity is still resumed. |
| int retry = 0; |
| int stopCount = 0; |
| do { |
| stopCount = (new ActivityLifecycleCounts(RESIZEABLE_ACTIVITY)) |
| .getCount(ActivityCallback.ON_STOP); |
| if (stopCount == 1) { |
| break; |
| } |
| logAlways("***testExternalDisplayActivityTurnPrimaryOff... retry=" + retry); |
| SystemClock.sleep(TimeUnit.SECONDS.toMillis(1)); |
| } while (retry++ < 5); |
| |
| if (stopCount != 1) { |
| fail(RESIZEABLE_ACTIVITY + " has received " + stopCount |
| + " 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. |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Activity launched on external display must be resumed"); |
| } |
| } |
| |
| /** |
| * Tests that an activity can be launched on a secondary display while the primary |
| * display is off. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testLaunchExternalDisplayActivityWhilePrimaryOff() throws Exception { |
| // 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"); |
| |
| try (final PrimaryDisplayStateSession displayStateSession = |
| new PrimaryDisplayStateSession(); |
| final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) { |
| displayStateSession.turnScreenOff(); |
| |
| // 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, mAmWmState.getAmState().getResumedActivitiesCount()); |
| |
| final ActivityDisplay newDisplay = |
| externalDisplaySession.createVirtualDisplay(true /* showContentWhenLocked */); |
| |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| |
| // Check that the test activity is resumed on the external display |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Activity launched on external display must be resumed"); |
| mAmWmState.assertFocusedAppOnDisplay("App on default display must still be focused", |
| RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY); |
| } |
| } |
| |
| /** |
| * Tests that turning the secondary display off stops activities running on that display. |
| */ |
| @Test |
| public void testExternalDisplayToggleState() throws Exception { |
| try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) { |
| final ActivityDisplay newDisplay = |
| externalDisplaySession.createVirtualDisplay(false /* showContentWhenLocked */); |
| |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| |
| // Check that the test activity is resumed on the external display |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Activity launched on external display must be resumed"); |
| |
| externalDisplaySession.turnDisplayOff(); |
| |
| // Check that turning off the external display stops the activity |
| waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED, |
| "Activity launched on external display must be stopped after turning off"); |
| |
| externalDisplaySession.turnDisplayOn(); |
| |
| // Check that turning on the external display resumes the activity |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Activity launched on external display must be resumed"); |
| } |
| } |
| |
| /** |
| * Tests that tapping on the primary display after showing the keyguard resumes the |
| * activity on the primary display. |
| */ |
| @Test |
| @FlakyTest(bugId = 112055644) |
| public void testStackFocusSwitchOnTouchEventAfterKeyguard() throws Exception { |
| // 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"); |
| |
| try (final LockScreenSession lockScreenSession = new LockScreenSession(); |
| final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) { |
| 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, mAmWmState.getAmState().getResumedActivitiesCount()); |
| |
| final ActivityDisplay newDisplay = |
| externalDisplaySession.createVirtualDisplay(true /* showContentWhenLocked */); |
| |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| |
| // Unlock the device and tap on the middle of the primary display |
| lockScreenSession.wakeUpDevice(); |
| executeShellCommand("wm dismiss-keyguard"); |
| mAmWmState.waitForKeyguardGone(); |
| mAmWmState.waitForValidState(RESIZEABLE_ACTIVITY, TEST_ACTIVITY); |
| |
| // Check that the test activity is resumed on the external display and is on top |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId, |
| "Activity on external display must be resumed and on top"); |
| mAmWmState.assertResumedActivities("Both displays should have resumed activities", |
| new SparseArray<ComponentName>(){{ |
| put(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY); |
| put(newDisplay.mId, TEST_ACTIVITY); |
| }} |
| ); |
| |
| final ReportedDisplayMetrics displayMetrics = getDisplayMetrics(); |
| final int width = displayMetrics.getSize().getWidth(); |
| final int height = displayMetrics.getSize().getHeight(); |
| tapOnDisplay(width / 2, height / 2, 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"); |
| mAmWmState.assertResumedActivities("Both displays should have resumed activities", |
| new SparseArray<ComponentName>(){{ |
| put(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY); |
| put(newDisplay.mId, TEST_ACTIVITY); |
| }} |
| ); |
| mAmWmState.assertFocusedAppOnDisplay("App on external display must still be focused", |
| TEST_ACTIVITY, newDisplay.mId); |
| } |
| } |
| |
| /** |
| * Tests that showWhenLocked works on a secondary display. |
| */ |
| @Test |
| public void testSecondaryDisplayShowWhenLocked() throws Exception { |
| try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession(); |
| final LockScreenSession lockScreenSession = new LockScreenSession()) { |
| lockScreenSession.setLockCredential(); |
| |
| launchActivity(TEST_ACTIVITY); |
| |
| final ActivityDisplay newDisplay = |
| externalDisplaySession.createVirtualDisplay(false /* showContentWhenLocked */); |
| launchActivityOnDisplay(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, newDisplay.mId); |
| |
| lockScreenSession.gotoKeyguard(); |
| |
| waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED, |
| "Expected stopped activity on default display"); |
| waitAndAssertTopResumedActivity(SHOW_WHEN_LOCKED_ATTR_ACTIVITY, newDisplay.mId, |
| "Expected resumed activity on secondary display"); |
| } |
| } |
| |
| /** |
| * Tests tap and set focus between displays. |
| */ |
| @Test |
| public void testSecondaryDisplayFocus() throws Exception { |
| try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) { |
| launchActivity(TEST_ACTIVITY); |
| mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED); |
| |
| final ActivityDisplay newDisplay = |
| externalDisplaySession.createVirtualDisplay(false /* showContentWhenLocked */); |
| launchActivityOnDisplay(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId); |
| waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId, |
| "Virtual activity should be Top Resumed Activity."); |
| mAmWmState.assertFocusedAppOnDisplay("Activity on second display must be focused.", |
| VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId); |
| |
| final ReportedDisplayMetrics displayMetrics = getDisplayMetrics(); |
| final int width = displayMetrics.getSize().getWidth(); |
| final int height = displayMetrics.getSize().getHeight(); |
| tapOnDisplay(width / 2, height / 2, DEFAULT_DISPLAY); |
| |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY, |
| "Activity should be top resumed when tapped."); |
| mAmWmState.assertFocusedActivity("Activity on default display must be top focused.", |
| TEST_ACTIVITY); |
| |
| tapOnDisplay(VirtualDisplayHelper.WIDTH / 2, VirtualDisplayHelper.HEIGHT / 2, |
| newDisplay.mId); |
| waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId, |
| "Virtual display activity should be top resumed when tapped."); |
| mAmWmState.assertFocusedActivity("Activity on second display must be top focused.", |
| VIRTUAL_DISPLAY_ACTIVITY); |
| mAmWmState.assertFocusedAppOnDisplay( |
| "Activity on default display must be still focused.", |
| TEST_ACTIVITY, DEFAULT_DISPLAY); |
| } |
| } |
| |
| /** |
| * 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. |
| mAmWmState.getAmState().computeState(); |
| final int displayCount = mAmWmState.getAmState().getDisplayCount(); |
| try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) { |
| final ActivityDisplay newDisplay = |
| externalDisplaySession.createVirtualDisplay(false /* showContentWhenLocked */); |
| launchActivityOnDisplay(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId); |
| waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId, |
| "Virtual activity should be Top Resumed Activity."); |
| mAmWmState.assertFocusedAppOnDisplay("Activity on second display must be focused.", |
| VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId); |
| } |
| mAmWmState.waitFor((amState, wmState) -> amState.getDisplayCount() == displayCount, |
| "Waiting for external displays to be removed"); |
| assertEquals(displayCount, mAmWmState.getAmState().getDisplayCount()); |
| assertEquals(displayCount, mAmWmState.getAmState().getKeyguardControllerState(). |
| mKeyguardOccludedStates.size()); |
| } |
| |
| @Test |
| public void testImeWindowCanSwitchToDifferentDisplays() throws Exception { |
| try (final TestActivitySession<ImeTestActivity> imeTestActivitySession = new |
| TestActivitySession<>(); |
| final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 = new |
| TestActivitySession<>(); |
| final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession(); |
| |
| // Leverage MockImeSession to ensure at least an IME exists as default. |
| final MockImeSession mockImeSession = MockImeSession.create( |
| mContext, InstrumentationRegistry.getInstrumentation().getUiAutomation(), |
| new ImeSettings.Builder())) { |
| |
| // Create a virtual display and launch an activity on it. |
| final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true) |
| .setShowSystemDecorations(true).createDisplay(); |
| imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, |
| newDisplay.mId); |
| |
| // Make the activity to show soft input. |
| final ImeEventStream stream = mockImeSession.openEventStream(); |
| imeTestActivitySession.runOnMainSyncAndWait( |
| imeTestActivitySession.getActivity()::showSoftInput); |
| waitOrderedImeEventsThenAssertImeShown(stream, newDisplay.mId, |
| editorMatcher("onStartInput", |
| imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), |
| event -> "showSoftInput".equals(event.getEventName())); |
| |
| // Assert the configuration of the IME window is the same as the configuration of the |
| // virtual display. |
| assertImeWindowAndDisplayConfiguration(mAmWmState.getImeWindowState(), newDisplay); |
| |
| // Launch another activity on the default display. |
| imeTestActivitySession2.launchTestActivityOnDisplaySync(ImeTestActivity2.class, |
| DEFAULT_DISPLAY); |
| |
| // Make the activity to show soft input. |
| imeTestActivitySession2.runOnMainSyncAndWait( |
| imeTestActivitySession2.getActivity()::showSoftInput); |
| waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY, |
| editorMatcher("onStartInput", |
| imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), |
| event -> "showSoftInput".equals(event.getEventName())); |
| |
| // Assert the configuration of the IME window is the same as the configuration of the |
| // default display. |
| assertImeWindowAndDisplayConfiguration(mAmWmState.getImeWindowState(), |
| mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY)); |
| } |
| } |
| |
| @Test |
| public void testImeApiForBug118341760() throws Exception { |
| final long TIMEOUT_START_INPUT = TimeUnit.SECONDS.toMillis(5); |
| |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession(); |
| final TestActivitySession<ImeTestActivityWithBrokenContextWrapper> |
| imeTestActivitySession = new TestActivitySession<>(); |
| |
| // Leverage MockImeSession to ensure at least an IME exists as default. |
| final MockImeSession mockImeSession = MockImeSession.create( |
| mContext, InstrumentationRegistry.getInstrumentation().getUiAutomation(), |
| new ImeSettings.Builder())) { |
| |
| // Create a virtual display and launch an activity on it. |
| final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true) |
| .setShowSystemDecorations(true).createDisplay(); |
| imeTestActivitySession.launchTestActivityOnDisplaySync( |
| ImeTestActivityWithBrokenContextWrapper.class, newDisplay.mId); |
| |
| final ImeTestActivityWithBrokenContextWrapper activity = |
| imeTestActivitySession.getActivity(); |
| final ImeEventStream stream = mockImeSession.openEventStream(); |
| final String privateImeOption = activity.getEditText().getPrivateImeOptions(); |
| expectEvent(stream, event -> { |
| if (!TextUtils.equals("onStartInput", event.getEventName())) { |
| return false; |
| } |
| final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo"); |
| return TextUtils.equals(editorInfo.packageName, mContext.getPackageName()) |
| && TextUtils.equals(editorInfo.privateImeOptions, privateImeOption); |
| }, TIMEOUT_START_INPUT); |
| |
| imeTestActivitySession.runOnMainSyncAndWait(() -> { |
| final InputMethodManager imm = activity.getSystemService(InputMethodManager.class); |
| assertTrue("InputMethodManager.isActive() should work", |
| imm.isActive(activity.getEditText())); |
| }); |
| } |
| } |
| |
| @Test |
| public void testImeWindowCanSwitchWhenTopFocusedDisplayChange() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession(); |
| final TestActivitySession<ImeTestActivity> imeTestActivitySession = new |
| TestActivitySession<>(); |
| final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 = new |
| TestActivitySession<>(); |
| // Leverage MockImeSession to ensure at least an IME exists as default. |
| final MockImeSession mockImeSession1 = MockImeSession.create( |
| mContext, InstrumentationRegistry.getInstrumentation().getUiAutomation(), |
| new ImeSettings.Builder())) { |
| |
| // Create 2 virtual displays and launch an activity on each display. |
| final List<ActivityDisplay> newDisplays = virtualDisplaySession.setPublicDisplay(true) |
| .setShowSystemDecorations(true).createDisplays(2); |
| final ActivityDisplay display1 = newDisplays.get(0); |
| final ActivityDisplay display2 = newDisplays.get(1); |
| |
| imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, |
| display1.mId); |
| imeTestActivitySession2.launchTestActivityOnDisplaySync(ImeTestActivity2.class, |
| display2.mId); |
| final ImeEventStream stream = mockImeSession1.openEventStream(); |
| |
| // Tap display1 as top focused display & request focus on EditText to show soft input. |
| tapOnDisplay(display1.mOverrideConfiguration.screenWidthDp / 2, |
| display1.mOverrideConfiguration.screenHeightDp / 2, display1.mId); |
| imeTestActivitySession.runOnMainSyncAndWait( |
| imeTestActivitySession.getActivity()::showSoftInput); |
| waitOrderedImeEventsThenAssertImeShown(stream, display1.mId, |
| editorMatcher("onStartInput", |
| imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), |
| event -> "showSoftInput".equals(event.getEventName())); |
| |
| // Tap display2 as top focused display & request focus on EditText to show soft input. |
| tapOnDisplay(display2.mOverrideConfiguration.screenWidthDp / 2, |
| display2.mOverrideConfiguration.screenHeightDp / 2, display2.mId); |
| imeTestActivitySession2.runOnMainSyncAndWait( |
| imeTestActivitySession2.getActivity()::showSoftInput); |
| waitOrderedImeEventsThenAssertImeShown(stream, display2.mId, |
| editorMatcher("onStartInput", |
| imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), |
| event -> "showSoftInput".equals(event.getEventName())); |
| |
| // Tap display1 again to make sure the IME window will come back. |
| tapOnDisplay(display1.mOverrideConfiguration.screenWidthDp / 2, |
| display1.mOverrideConfiguration.screenHeightDp / 2, display1.mId); |
| imeTestActivitySession.runOnMainSyncAndWait( |
| imeTestActivitySession.getActivity()::showSoftInput); |
| waitOrderedImeEventsThenAssertImeShown(stream, display1.mId, |
| editorMatcher("onStartInput", |
| imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), |
| event -> "showSoftInput".equals(event.getEventName())); |
| } |
| } |
| |
| /** |
| * Tests that toast works on a secondary display. |
| */ |
| @Test |
| public void testSecondaryDisplayShowToast() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = |
| virtualDisplaySession.setPublicDisplay(true).createDisplay(); |
| final String TOAST_NAME = "Toast"; |
| launchActivityOnDisplay(TOAST_ACTIVITY, newDisplay.mId); |
| waitAndAssertTopResumedActivity(TOAST_ACTIVITY, newDisplay.mId, |
| "Activity launched on external display must be resumed"); |
| mAmWmState.waitForWithWmState((state) -> state.containsWindow(TOAST_NAME), |
| "Waiting for toast window to show"); |
| |
| assertTrue("Toast window must be shown", |
| mAmWmState.getWmState().containsWindow(TOAST_NAME)); |
| assertTrue("Toast window must be visible", |
| mAmWmState.getWmState().isWindowVisible(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() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new simulated display and launch an activity on it. |
| final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true) |
| .createDisplay(); |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| |
| waitAndAssertTopResumedActivity(TEST_ACTIVITY, 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); |
| } |
| |
| /** |
| * Tests that wallpaper shows on secondary displays. |
| */ |
| @Test |
| public void testWallpaperShowOnSecondaryDisplays() throws Exception { |
| mAmWmState.computeState(true); |
| final WindowManagerState.WindowState wallpaper = |
| mAmWmState.getWmState().findFirstWindowWithType(TYPE_WALLPAPER); |
| // Skip if there is no wallpaper. |
| assumeNotNull(wallpaper); |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay noDecorDisplay = virtualDisplaySession.setPublicDisplay(true) |
| .setShowSystemDecorations(false).createDisplay(); |
| // Tests when the system decor flag is included in that display, the wallpaper must |
| // be displayed on the secondary display. And at the same time we do not need to wait |
| // for the wallpaper which should not to be displayed. |
| final ActivityDisplay decorDisplay = virtualDisplaySession.setPublicDisplay(true) |
| .setShowSystemDecorations(true).createDisplay(); |
| mAmWmState.waitForWithWmState((state) -> isWallpaperOnDisplay(state, decorDisplay.mId), |
| "Waiting for wallpaper window to show"); |
| assertTrue("Wallpaper must be displayed on secondary display with system decor flag", |
| isWallpaperOnDisplay(mAmWmState.getWmState(), decorDisplay.mId)); |
| |
| assertFalse("Wallpaper must not be displayed on the display without system decor flag", |
| isWallpaperOnDisplay(mAmWmState.getWmState(), noDecorDisplay.mId)); |
| } |
| } |
| |
| private boolean isWallpaperOnDisplay(WindowManagerState windowManagerState, int displayId) { |
| List<WindowManagerState.WindowState> states = |
| windowManagerState.getMatchingWindowType(TYPE_WALLPAPER); |
| for (WindowManagerState.WindowState ws : states) { |
| if (ws.getDisplayId() == displayId) return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Test that the IME should be shown in default display and verify committed texts can deliver |
| * to target display which does not support system decoration. |
| */ |
| @Test |
| public void testImeShowAndCommitTextsInDefaultDisplayWhenNoSysDecor() throws Exception { |
| final long TIMEOUT = TimeUnit.SECONDS.toMillis(5); |
| |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession(); |
| final TestActivitySession<ImeTestActivity> |
| imeTestActivitySession = new TestActivitySession<>(); |
| // Leverage MockImeSession to ensure at least a test Ime exists as default. |
| final MockImeSession mockImeSession = MockImeSession.create( |
| mContext, InstrumentationRegistry.getInstrumentation().getUiAutomation(), |
| new ImeSettings.Builder())) { |
| |
| // Create a virtual display and pretend display does not support system decoration. |
| final ActivityDisplay newDisplay = virtualDisplaySession.setPublicDisplay(true) |
| .setShowSystemDecorations(false).createDisplay(); |
| // Verify the virtual display should not support system decoration. |
| final DisplayManager displayManager = InstrumentationRegistry.getTargetContext() |
| .getSystemService(DisplayManager.class); |
| final Display display = displayManager.getDisplay(newDisplay.mId); |
| final boolean supportSystemDecoration = |
| display != null && display.supportsSystemDecorations(); |
| assertFalse("Display should not support system decoration", supportSystemDecoration); |
| |
| // Launch Ime test activity in virtual display. |
| imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, |
| newDisplay.mId); |
| // Make the activity to show soft input on the default display. |
| final ImeEventStream stream = mockImeSession.openEventStream(); |
| final EditText editText = imeTestActivitySession.getActivity().mEditText; |
| imeTestActivitySession.runOnMainSyncAndWait( |
| imeTestActivitySession.getActivity()::showSoftInput); |
| waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY, |
| editorMatcher("onStartInput", editText.getPrivateImeOptions()), |
| event -> "showSoftInput".equals(event.getEventName())); |
| |
| // Commit text & make sure the input texts should be delivered to focused EditText on |
| // virtual display. |
| final String commitText = "test commit"; |
| expectCommand(stream, mockImeSession.callCommitText(commitText, 1), TIMEOUT); |
| imeTestActivitySession.runOnMainAndAssertWithTimeout( |
| () -> TextUtils.equals(commitText, editText.getText()), TIMEOUT, |
| "The input text should be delivered"); |
| } |
| } |
| |
| @Test |
| public void testAppTransitionForActivityOnDifferentDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession(); |
| final TestActivitySession<StandardActivity> transitionActivitySession = new |
| TestActivitySession<>()) { |
| // Create new simulated display. |
| final ActivityDisplay newDisplay = virtualDisplaySession |
| .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); |
| mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); |
| mAmWmState.assertSanity(); |
| assertEquals(TRANSIT_TASK_OPEN, |
| mAmWmState.getWmState().getDisplay(DEFAULT_DISPLAY).getLastTransition()); |
| |
| // Finish current activity & launch another TestActivity in virtual display in parallel. |
| transitionActivitySession.finishCurrentActivityNoWait(); |
| launchActivityOnDisplayNoWait(TEST_ACTIVITY, newDisplay.mId); |
| mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); |
| mAmWmState.waitForAppTransitionIdleOnDisplay(newDisplay.mId); |
| mAmWmState.assertSanity(); |
| |
| // Verify each display's last transition if is correct as expected. |
| assertEquals(TRANSIT_TASK_CLOSE, |
| mAmWmState.getWmState().getDisplay(DEFAULT_DISPLAY).getLastTransition()); |
| assertEquals(TRANSIT_TASK_OPEN, |
| mAmWmState.getWmState().getDisplay(newDisplay.mId).getLastTransition()); |
| } |
| } |
| |
| @Test |
| public void testNoTransitionWhenMovingActivityToDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| // Create new simulated display & capture new display's transition state. |
| final ActivityDisplay newDisplay = virtualDisplaySession |
| .setSimulateDisplay(true).createDisplay(); |
| |
| // Launch TestActivity in virtual display & capture its transition state. |
| launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId); |
| mAmWmState.waitForAppTransitionIdleOnDisplay(newDisplay.mId); |
| mAmWmState.assertSanity(); |
| final String lastTranstionOnVirtualDisplay = mAmWmState.getWmState() |
| .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, |
| mAmWmState.getWmState().getDisplay(newDisplay.mId).getLastTransition()); |
| } |
| } |
| |
| private void assertTopTaskSameSurfaceSizeWithDisplay(int displayId) { |
| final WindowManagerState.Display display = mAmWmState.getWmState().getDisplay(displayId); |
| final int stackId = mAmWmState.getWmState().getFrontStackId(displayId); |
| final WindowManagerState.WindowTask task = |
| mAmWmState.getWmState().getStack(stackId).mTasks.get(0); |
| |
| 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()); |
| } |
| |
| // TODO(115978725): add runtime sys decor change test once we can do this. |
| /** |
| * Test that navigation bar should show on display with system decoration. |
| */ |
| @Test |
| @FlakyTest(bugId = 120748674) |
| public void testNavBarShowingOnDisplayWithDecor() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay newDisplay = virtualDisplaySession |
| .setPublicDisplay(true).setShowSystemDecorations(true).createDisplay(); |
| |
| mAmWmState.waitAndAssertNavBarShownOnDisplay(newDisplay.mId); |
| } |
| } |
| |
| /** |
| * Test that navigation bar should not show on display without system decoration. |
| */ |
| @Test |
| @FlakyTest(bugId = 120748674) |
| public void testNavBarNotShowingOnDisplayWithoutDecor() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| virtualDisplaySession.setPublicDisplay(true) |
| .setShowSystemDecorations(false).createDisplay(); |
| |
| final List<WindowState> expected = mAmWmState.getWmState().getAllNavigationBarStates(); |
| |
| waitAndAssertNavBarStatesAreTheSame(expected); |
| } |
| } |
| |
| /** |
| * Test that navigation bar should not show on private display even if the display |
| * supports system decoration. |
| */ |
| @Test |
| @FlakyTest(bugId = 120748674) |
| public void testNavBarNotShowingOnPrivateDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| virtualDisplaySession.setPublicDisplay(false) |
| .setShowSystemDecorations(true).createDisplay(); |
| |
| final List<WindowState> expected = mAmWmState.getWmState().getAllNavigationBarStates(); |
| |
| waitAndAssertNavBarStatesAreTheSame(expected); |
| } |
| } |
| |
| /** Tests launching of activities on a single task instance display. */ |
| @Test |
| public void testSingleTaskInstanceDisplay() throws Exception { |
| try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) { |
| ActivityDisplay display = |
| virtualDisplaySession.setSimulateDisplay(true).createDisplay(); |
| final int displayId = display.mId; |
| |
| SystemUtil.runWithShellPermissionIdentity( |
| () -> mAtm.setDisplayToSingleTaskInstance(displayId)); |
| display = getDisplayState(displayId); |
| assertTrue("Display must be set to singleTaskInstance", display.mSingleTaskInstance); |
| |
| // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY will launch |
| // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2 in the same task and |
| // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3 in different task. |
| launchActivityOnDisplay(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY, displayId); |
| |
| waitAndAssertTopResumedActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3, DEFAULT_DISPLAY, |
| "Activity should be resumed on default display"); |
| |
| display = getDisplayState(displayId); |
| // Verify that the 2 activities in the same task are on the display and the one in a |
| // different task isn't on the display, but on the default display |
| assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY", |
| display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY)); |
| assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2", |
| display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2)); |
| |
| assertFalse("Display shouldn't contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3", |
| display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3)); |
| assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3", |
| getDisplayState(DEFAULT_DISPLAY).containsActivity( |
| SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3)); |
| } |
| } |
| |
| private void waitAndAssertNavBarStatesAreTheSame(List<WindowState> expected) throws Exception { |
| // This is used to verify that we have nav bars shown on the same displays |
| // as before the test. |
| // |
| // The strategy is: |
| // Once a display with system ui decor support is created and a nav bar shows on the |
| // display, go back to verify whether the nav bar states are unchanged to verify that no nav |
| // bars were added to a display that was added before executing this method that shouldn't |
| // have nav bars (i.e. private or without system ui decor). |
| try (final VirtualDisplaySession secondDisplaySession = new VirtualDisplaySession()) { |
| final ActivityDisplay supportsSysDecorDisplay = secondDisplaySession |
| .setPublicDisplay(true).setShowSystemDecorations(true).createDisplay(); |
| mAmWmState.waitAndAssertNavBarShownOnDisplay(supportsSysDecorDisplay.mId); |
| // This display has finished his task. Just close it. |
| } |
| |
| final List<WindowState> result = mAmWmState.getWmState().getAllNavigationBarStates(); |
| |
| assertEquals("The number of nav bars should be the same", expected.size(), result.size()); |
| |
| // Nav bars should show on the same displays |
| for (int i = 0; i < expected.size(); i++) { |
| final int expectedDisplayId = expected.get(i).getDisplayId(); |
| mAmWmState.waitAndAssertNavBarShownOnDisplay(expectedDisplayId); |
| } |
| } |
| |
| private class ExternalDisplaySession implements AutoCloseable { |
| |
| @Nullable |
| private VirtualDisplayHelper mExternalDisplayHelper; |
| |
| /** |
| * Creates a private virtual display with the external and show with insecure |
| * keyguard flags set. |
| */ |
| ActivityDisplay createVirtualDisplay(boolean showContentWhenLocked) |
| throws Exception { |
| final List<ActivityDisplay> originalDS = getDisplaysStates(); |
| final int originalDisplayCount = originalDS.size(); |
| |
| mExternalDisplayHelper = new VirtualDisplayHelper(); |
| mExternalDisplayHelper.createAndWaitForDisplay(showContentWhenLocked); |
| |
| // Wait for the virtual display to be created and get configurations. |
| final List<ActivityDisplay> ds = getDisplayStateAfterChange(originalDisplayCount + 1); |
| assertEquals("New virtual display must be created", originalDisplayCount + 1, |
| ds.size()); |
| |
| // Find the newly added display. |
| final List<ActivityDisplay> newDisplays = findNewDisplayStates(originalDS, ds); |
| return newDisplays.get(0); |
| } |
| |
| void turnDisplayOff() { |
| if (mExternalDisplayHelper == null) { |
| throw new RuntimeException("No external display created"); |
| } |
| mExternalDisplayHelper.turnDisplayOff(); |
| } |
| |
| void turnDisplayOn() { |
| if (mExternalDisplayHelper == null) { |
| throw new RuntimeException("No external display created"); |
| } |
| mExternalDisplayHelper.turnDisplayOn(); |
| } |
| |
| @Override |
| public void close() throws Exception { |
| if (mExternalDisplayHelper != null) { |
| mExternalDisplayHelper.releaseDisplay(); |
| mExternalDisplayHelper = null; |
| } |
| } |
| } |
| |
| private static class PrimaryDisplayStateSession implements AutoCloseable { |
| |
| void turnScreenOff() { |
| setPrimaryDisplayState(false); |
| } |
| |
| @Override |
| public void close() throws Exception { |
| setPrimaryDisplayState(true); |
| } |
| |
| /** Turns the primary display on/off by pressing the power key */ |
| private void setPrimaryDisplayState(boolean wantOn) { |
| if (wantOn) { |
| pressWakeupButton(); |
| } else { |
| pressSleepButton(); |
| } |
| VirtualDisplayHelper.waitForDefaultDisplayState(wantOn); |
| } |
| } |
| |
| public static class ImeTestActivity extends Activity { |
| EditText mEditText; |
| |
| @Override |
| protected void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| mEditText = new EditText(this); |
| // Set private IME option for editorMatcher to identify which TextView received |
| // onStartInput event. |
| mEditText.setPrivateImeOptions( |
| getClass().getName() + "/" + Long.toString(SystemClock.elapsedRealtimeNanos())); |
| final LinearLayout layout = new LinearLayout(this); |
| layout.setOrientation(LinearLayout.VERTICAL); |
| layout.addView(mEditText); |
| mEditText.requestFocus(); |
| setContentView(layout); |
| } |
| |
| void showSoftInput() { |
| getSystemService(InputMethodManager.class).showSoftInput(mEditText, 0); |
| } |
| } |
| |
| public static class ImeTestActivity2 extends ImeTestActivity { } |
| |
| public static final class ImeTestActivityWithBrokenContextWrapper extends Activity { |
| private EditText mEditText; |
| |
| /** |
| * Emulates the behavior of certain {@link ContextWrapper} subclasses we found in the wild. |
| * |
| * <p> Certain {@link ContextWrapper} subclass in the wild delegate method calls to |
| * ApplicationContext except for {@link #getSystemService(String)}.</p> |
| * |
| **/ |
| private static final class Bug118341760ContextWrapper extends ContextWrapper { |
| private final Context mOriginalContext; |
| |
| Bug118341760ContextWrapper(Context base) { |
| super(base.getApplicationContext()); |
| mOriginalContext = base; |
| } |
| |
| /** |
| * Emulates the behavior of {@link ContextWrapper#getSystemService(String)} of certain |
| * {@link ContextWrapper} subclasses we found in the wild. |
| * |
| * @param name The name of the desired service. |
| * @return The service or {@link null} if the name does not exist. |
| */ |
| @Override |
| public Object getSystemService(String name) { |
| return mOriginalContext.getSystemService(name); |
| } |
| } |
| |
| @Override |
| protected void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| mEditText = new EditText(new Bug118341760ContextWrapper(this)); |
| // Use SystemClock.elapsedRealtimeNanos()) as a unique ID of this edit text. |
| mEditText.setPrivateImeOptions(Long.toString(SystemClock.elapsedRealtimeNanos())); |
| final LinearLayout layout = new LinearLayout(this); |
| layout.setOrientation(LinearLayout.VERTICAL); |
| layout.addView(mEditText); |
| mEditText.requestFocus(); |
| setContentView(layout); |
| } |
| |
| EditText getEditText() { |
| return mEditText; |
| } |
| } |
| |
| void assertImeWindowAndDisplayConfiguration( |
| WindowManagerState.WindowState imeWinState, ActivityDisplay display) { |
| final Configuration configurationForIme = imeWinState.mMergedOverrideConfiguration; |
| final Configuration configurationForDisplay = display.mMergedOverrideConfiguration; |
| final int displayDensityDpiForIme = configurationForIme.densityDpi; |
| final int displayDensityDpi = configurationForDisplay.densityDpi; |
| final Rect displayBoundsForIme = configurationForIme.windowConfiguration.getBounds(); |
| final Rect displayBounds = configurationForDisplay.windowConfiguration.getBounds(); |
| |
| assertEquals("Display density not the same", displayDensityDpi, displayDensityDpiForIme); |
| assertEquals("Display bounds not the same", displayBounds, displayBoundsForIme); |
| } |
| |
| void waitOrderedImeEventsThenAssertImeShown(ImeEventStream stream, int displayId, |
| Predicate<ImeEvent>... conditions) throws Exception { |
| for (Predicate<ImeEvent> condition : conditions) { |
| expectEvent(stream, condition, TimeUnit.SECONDS.toMillis(5) /* eventTimeout */); |
| } |
| // Assert the IME is shown on the expected display. |
| mAmWmState.waitAndAssertImeWindowShownOnDisplay(displayId); |
| } |
| } |