blob: d0a083a1c56386ea48257d719335771f45673ae3 [file] [log] [blame]
/*
* 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);
}
}