blob: cb2fb3f34394d9b9b38031fd4d23c3ecc3b8485a [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.server.wm.UiDeviceUtils.pressSleepButton;
import static android.server.wm.UiDeviceUtils.pressUnlockButton;
import static android.server.wm.UiDeviceUtils.pressWakeupButton;
import static android.server.wm.WindowManagerState.STATE_RESUMED;
import static android.server.wm.app.Components.HOME_ACTIVITY;
import static android.server.wm.app.Components.SECONDARY_HOME_ACTIVITY;
import static android.server.wm.app.Components.SINGLE_HOME_ACTIVITY;
import static android.server.wm.app.Components.SINGLE_SECONDARY_HOME_ACTIVITY;
import static android.server.wm.app.Components.TEST_LIVE_WALLPAPER_SERVICE;
import static android.server.wm.app.Components.TestLiveWallpaperKeys.COMPONENT;
import static android.server.wm.app.Components.TestLiveWallpaperKeys.ENGINE_DISPLAY_ID;
import static android.server.wm.BarTestUtils.assumeHasBars;
import static android.server.wm.MockImeHelper.createManagedMockImeSession;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
import android.app.WallpaperManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.server.wm.WindowManagerState.DisplayContent;
import android.server.wm.TestJournalProvider.TestJournalContainer;
import android.server.wm.WindowManagerState.WindowState;
import android.server.wm.intent.Activities;
import android.text.TextUtils;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import com.android.compatibility.common.util.ImeAwareEditText;
import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.TestUtils;
import com.android.cts.mockime.ImeCommand;
import com.android.cts.mockime.ImeEventStream;
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.stream.Collectors;
/**
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:MultiDisplaySystemDecorationTests
*
* This tests that verify the following should not be run for OEM device verification:
* Wallpaper added if display supports system decorations (and not added otherwise)
* Navigation bar is added if display supports system decorations (and not added otherwise)
* Secondary Home is shown if display supports system decorations (and not shown otherwise)
* IME is shown if display supports system decorations (and not shown otherwise)
*/
@Presubmit
@android.server.wm.annotation.Group3
public class MultiDisplaySystemDecorationTests extends MultiDisplayTestBase {
final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
@Before
@Override
public void setUp() throws Exception {
super.setUp();
assumeTrue(supportsMultiDisplay());
assumeTrue(supportsSystemDecorsOnSecondaryDisplays());
}
// Wallpaper related tests
/**
* Test WallpaperService.Engine#getDisplayContext can work on secondary display.
*/
@Test
public void testWallpaperGetDisplayContext() throws Exception {
assumeTrue(supportsLiveWallpaper());
final ChangeWallpaperSession wallpaperSession = createManagedChangeWallpaperSession();
final VirtualDisplaySession virtualDisplaySession = createManagedVirtualDisplaySession();
TestJournalContainer.start();
final DisplayContent newDisplay = virtualDisplaySession
.setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay();
wallpaperSession.setWallpaperComponent(TEST_LIVE_WALLPAPER_SERVICE);
final String TARGET_ENGINE_DISPLAY_ID = ENGINE_DISPLAY_ID + newDisplay.mId;
final TestJournalProvider.TestJournal journal = TestJournalContainer.get(COMPONENT);
TestUtils.waitUntil("Waiting for wallpaper engine bounded", 5 /* timeoutSecond */,
() -> journal.extras.getBoolean(TARGET_ENGINE_DISPLAY_ID));
}
/**
* Tests that wallpaper shows on secondary displays.
*/
@Test
public void testWallpaperShowOnSecondaryDisplays() {
final ChangeWallpaperSession wallpaperSession = createManagedChangeWallpaperSession();
final DisplayContent untrustedDisplay = createManagedExternalDisplaySession()
.setPublicDisplay(true).setShowSystemDecorations(true).createVirtualDisplay();
final DisplayContent decoredSystemDisplay = createManagedVirtualDisplaySession()
.setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay();
final Bitmap tmpWallpaper = wallpaperSession.getTestBitmap();
wallpaperSession.setImageWallpaper(tmpWallpaper);
assertTrue("Wallpaper must be displayed on system owned display with system decor flag",
mWmState.waitForWithAmState(
state -> isWallpaperOnDisplay(state, decoredSystemDisplay.mId),
"wallpaper window to show"));
assertFalse("Wallpaper must not be displayed on the untrusted display",
isWallpaperOnDisplay(mWmState, untrustedDisplay.mId));
}
private ChangeWallpaperSession createManagedChangeWallpaperSession() {
return mObjectTracker.manage(new ChangeWallpaperSession());
}
private class ChangeWallpaperSession implements AutoCloseable {
private final WallpaperManager mWallpaperManager;
private Bitmap mTestBitmap;
public ChangeWallpaperSession() {
mWallpaperManager = WallpaperManager.getInstance(mContext);
}
public Bitmap getTestBitmap() {
if (mTestBitmap == null) {
mTestBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(mTestBitmap);
canvas.drawColor(Color.BLUE);
}
return mTestBitmap;
}
public void setImageWallpaper(Bitmap bitmap) {
SystemUtil.runWithShellPermissionIdentity(() ->
mWallpaperManager.setBitmap(bitmap));
}
public void setWallpaperComponent(ComponentName componentName) {
SystemUtil.runWithShellPermissionIdentity(() ->
mWallpaperManager.setWallpaperComponent(componentName));
}
@Override
public void close() {
SystemUtil.runWithShellPermissionIdentity(() -> mWallpaperManager.clearWallpaper());
if (mTestBitmap != null) {
mTestBitmap.recycle();
}
// Turning screen off/on to flush deferred color events due to wallpaper changed.
pressSleepButton();
pressWakeupButton();
pressUnlockButton();
}
}
private boolean isWallpaperOnDisplay(WindowManagerState windowManagerState, int displayId) {
return windowManagerState.getMatchingWindowType(TYPE_WALLPAPER).stream().anyMatch(
w -> w.getDisplayId() == displayId);
}
// Navigation bar related tests
// 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
public void testNavBarShowingOnDisplayWithDecor() {
assumeHasBars();
final DisplayContent newDisplay = createManagedVirtualDisplaySession()
.setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay();
mWmState.waitAndAssertNavBarShownOnDisplay(newDisplay.mId);
}
/**
* Test that navigation bar should not show on display without system decoration.
*/
@Test
public void testNavBarNotShowingOnDisplayWithoutDecor() {
assumeHasBars();
// Wait for system decoration showing and record current nav states.
mWmState.waitForHomeActivityVisible();
final List<WindowState> expected = mWmState.getAllNavigationBarStates();
createManagedVirtualDisplaySession().setSimulateDisplay(true)
.setShowSystemDecorations(false).createDisplay();
waitAndAssertNavBarStatesAreTheSame(expected);
}
/**
* Test that navigation bar should not show on private display even if the display
* supports system decoration.
*/
@Test
public void testNavBarNotShowingOnPrivateDisplay() {
assumeHasBars();
// Wait for system decoration showing and record current nav states.
mWmState.waitForHomeActivityVisible();
final List<WindowState> expected = mWmState.getAllNavigationBarStates();
createManagedExternalDisplaySession().setPublicDisplay(false)
.setShowSystemDecorations(true).createVirtualDisplay();
waitAndAssertNavBarStatesAreTheSame(expected);
}
private void waitAndAssertNavBarStatesAreTheSame(List<WindowState> expected) {
// 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 DisplayContent supportsSysDecorDisplay = secondDisplaySession
.setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay();
mWmState.waitAndAssertNavBarShownOnDisplay(supportsSysDecorDisplay.mId);
// This display has finished his task. Just close it.
}
mWmState.computeState();
final List<WindowState> result = mWmState.getAllNavigationBarStates();
assertEquals("The number of nav bars should be the same", expected.size(), result.size());
mWmState.getDisplays().forEach(displayContent -> {
List<WindowState> navWindows = expected.stream().filter(ws ->
ws.getDisplayId() == displayContent.mId)
.collect(Collectors.toList());
mWmState.waitAndAssertNavBarShownOnDisplay(displayContent.mId, navWindows.size());
});
}
// Secondary Home related tests
/**
* Tests launching a home activity on virtual display without system decoration support.
*/
@Test
public void testLaunchHomeActivityOnSecondaryDisplayWithoutDecorations() {
createManagedHomeActivitySession(SECONDARY_HOME_ACTIVITY);
// Create new virtual display without system decoration support.
final DisplayContent newDisplay = createManagedExternalDisplaySession()
.createVirtualDisplay();
// Secondary home activity can't be launched on the display without system decoration
// support.
assertEquals("No stacks on newly launched virtual display", 0, newDisplay.mRootTasks.size());
}
/** Tests launching a home activity on untrusted virtual display. */
@Test
public void testLaunchHomeActivityOnUntrustedVirtualSecondaryDisplay() {
createManagedHomeActivitySession(SECONDARY_HOME_ACTIVITY);
// Create new virtual display with system decoration support flag.
final DisplayContent newDisplay = createManagedExternalDisplaySession()
.setPublicDisplay(true).setShowSystemDecorations(true).createVirtualDisplay();
// Secondary home activity can't be launched on the untrusted virtual display.
assertEquals("No stacks on untrusted virtual display", 0, newDisplay.mRootTasks.size());
}
/**
* Tests launching a single instance home activity on virtual display with system decoration
* support.
*/
@Test
public void testLaunchSingleHomeActivityOnDisplayWithDecorations() {
createManagedHomeActivitySession(SINGLE_HOME_ACTIVITY);
// If default home doesn't support multi-instance, default secondary home activity
// should be automatically launched on the new display.
assertSecondaryHomeResumedOnNewDisplay(getDefaultSecondaryHomeComponent());
}
/**
* Tests launching a single instance home activity with SECONDARY_HOME on virtual display with
* system decoration support.
*/
@Test
public void testLaunchSingleSecondaryHomeActivityOnDisplayWithDecorations() {
createManagedHomeActivitySession(SINGLE_SECONDARY_HOME_ACTIVITY);
// If provided secondary home doesn't support multi-instance, default secondary home
// activity should be automatically launched on the new display.
assertSecondaryHomeResumedOnNewDisplay(getDefaultSecondaryHomeComponent());
}
/**
* Tests launching a multi-instance home activity on virtual display with system decoration
* support.
*/
@Test
public void testLaunchHomeActivityOnDisplayWithDecorations() {
createManagedHomeActivitySession(HOME_ACTIVITY);
// If default home doesn't have SECONDARY_HOME category, default secondary home
// activity should be automatically launched on the new display.
assertSecondaryHomeResumedOnNewDisplay(getDefaultSecondaryHomeComponent());
}
/**
* Tests launching a multi-instance home activity with SECONDARY_HOME on virtual display with
* system decoration support.
*/
@Test
public void testLaunchSecondaryHomeActivityOnDisplayWithDecorations() {
createManagedHomeActivitySession(SECONDARY_HOME_ACTIVITY);
// Provided secondary home activity should be automatically launched on the new display.
assertSecondaryHomeResumedOnNewDisplay(SECONDARY_HOME_ACTIVITY);
}
private void assertSecondaryHomeResumedOnNewDisplay(ComponentName homeComponentName) {
// Create new simulated display with system decoration support.
final DisplayContent newDisplay = createManagedVirtualDisplaySession()
.setSimulateDisplay(true)
.setShowSystemDecorations(true)
.createDisplay();
waitAndAssertActivityStateOnDisplay(homeComponentName, STATE_RESUMED,
newDisplay.mId, "Activity launched on secondary display must be resumed");
tapOnDisplayCenter(newDisplay.mId);
assertEquals("Top activity must be home type", ACTIVITY_TYPE_HOME,
mWmState.getFrontRootTaskActivityType(newDisplay.mId));
}
// IME related tests
@Test
public void testImeWindowCanSwitchToDifferentDisplays() throws Exception {
assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
final MockImeSession mockImeSession = createManagedMockImeSession(this);
final TestActivitySession<ImeTestActivity> imeTestActivitySession =
createManagedTestActivitySession();
final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 =
createManagedTestActivitySession();
// Create a virtual display and launch an activity on it.
final DisplayContent newDisplay = createManagedVirtualDisplaySession()
.setShowSystemDecorations(true)
.setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
.setSimulateDisplay(true)
.createDisplay();
final ImeEventStream stream = mockImeSession.openEventStream();
imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
newDisplay.mId);
expectEvent(stream, editorMatcher("onStartInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
// Make the activity to show soft input.
showSoftInputAndAssertImeShownOnDisplay(newDisplay.mId, imeTestActivitySession, stream);
// Assert the configuration of the IME window is the same as the configuration of the
// virtual display.
assertImeWindowAndDisplayConfiguration(mWmState.getImeWindowState(), newDisplay);
// Launch another activity on the default display.
imeTestActivitySession2.launchTestActivityOnDisplaySync(
ImeTestActivity2.class, DEFAULT_DISPLAY);
expectEvent(stream, editorMatcher("onStartInput",
imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
// Make the activity to show soft input.
showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeTestActivitySession2, stream);
// Assert the configuration of the IME window is the same as the configuration of the
// default display.
assertImeWindowAndDisplayConfiguration(mWmState.getImeWindowState(),
mWmState.getDisplay(DEFAULT_DISPLAY));
}
@Test
public void testImeApiForBug118341760() throws Exception {
assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
final long TIMEOUT_START_INPUT = TimeUnit.SECONDS.toMillis(5);
final MockImeSession mockImeSession = createManagedMockImeSession(this);
final TestActivitySession<ImeTestActivityWithBrokenContextWrapper> imeTestActivitySession =
createManagedTestActivitySession();
// Create a virtual display and launch an activity on it.
final DisplayContent newDisplay = createManagedVirtualDisplaySession()
.setShowSystemDecorations(true)
.setSimulateDisplay(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 {
// If config_perDisplayFocusEnabled, the focus will not move even if touching on
// the Activity in the different display.
assumeFalse(perDisplayFocusEnabled());
assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
final MockImeSession mockImeSession = createManagedMockImeSession(this);
final TestActivitySession<ImeTestActivity> imeTestActivitySession =
createManagedTestActivitySession();
final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 =
createManagedTestActivitySession();
// Create a virtual display and launch an activity on virtual & default display.
final DisplayContent newDisplay = createManagedVirtualDisplaySession()
.setShowSystemDecorations(true)
.setSimulateDisplay(true)
.setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
.createDisplay();
imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
DEFAULT_DISPLAY);
imeTestActivitySession2.launchTestActivityOnDisplaySync(ImeTestActivity2.class,
newDisplay.mId);
final DisplayContent defDisplay = mWmState.getDisplay(DEFAULT_DISPLAY);
final ImeEventStream stream = mockImeSession.openEventStream();
// Tap default display as top focused display & request focus on EditText to show
// soft input.
tapOnDisplayCenter(defDisplay.mId);
expectEvent(stream, editorMatcher("onStartInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream);
// Tap virtual display as top focused display & request focus on EditText to show
// soft input.
tapOnDisplayCenter(newDisplay.mId);
expectEvent(stream, editorMatcher("onStartInput",
imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
showSoftInputAndAssertImeShownOnDisplay(newDisplay.mId, imeTestActivitySession2, stream);
// Tap default display again to make sure the IME window will come back.
tapOnDisplayCenter(defDisplay.mId);
expectEvent(stream, editorMatcher("onStartInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream);
}
/**
* Test that the IME can be shown in a different display (actually the default display) than
* the display on which the target IME application is shown. Then test several basic operations
* in {@link InputConnection}.
*/
@Test
public void testCrossDisplayBasicImeOperations() throws Exception {
assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
final MockImeSession mockImeSession = createManagedMockImeSession(this);
final TestActivitySession<ImeTestActivity> imeTestActivitySession =
createManagedTestActivitySession();
// Create a virtual display by app and assume the display should not show IME window.
final DisplayContent newDisplay = createManagedVirtualDisplaySession()
.setPublicDisplay(true)
.createDisplay();
SystemUtil.runWithShellPermissionIdentity(
() -> assertTrue("Display should not support showing IME window",
mTargetContext.getSystemService(WindowManager.class)
.getDisplayImePolicy(newDisplay.mId)
== DISPLAY_IME_POLICY_FALLBACK_DISPLAY));
// Launch Ime test activity in virtual display.
imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class,
newDisplay.mId);
final ImeEventStream stream = mockImeSession.openEventStream();
// Expect onStartInput would be executed when user tapping on the
// non-system created display intentionally.
tapAndAssertEditorFocusedOnImeActivity(imeTestActivitySession, newDisplay.mId);
expectEvent(stream, editorMatcher("onStartInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
// Verify the activity to show soft input on the default display.
showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeTestActivitySession, stream);
// Commit text & make sure the input texts should be delivered to focused EditText on
// virtual display.
final EditText editText = imeTestActivitySession.getActivity().mEditText;
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");
// Since the IME and the IME target app are running in different displays,
// InputConnection#requestCursorUpdates() is not supported and it should return false.
// See InputMethodServiceTest#testOnUpdateCursorAnchorInfo() for the normal scenario.
final ImeCommand callCursorUpdates = mockImeSession.callRequestCursorUpdates(
InputConnection.CURSOR_UPDATE_IMMEDIATE);
assertFalse(expectCommand(stream, callCursorUpdates, TIMEOUT).getReturnBooleanValue());
}
/**
* Test that the IME can be hidden with the {@link WindowManager#DISPLAY_IME_POLICY_HIDE} flag.
*/
@Test
public void testDisplayPolicyImeHideImeOperation() throws Exception {
assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
final MockImeSession mockImeSession = createManagedMockImeSession(this);
final TestActivitySession<ImeTestActivity> imeTestActivitySession =
createManagedTestActivitySession();
// Create a virtual display and launch an activity on virtual display.
final DisplayContent newDisplay = createManagedVirtualDisplaySession()
.setShowSystemDecorations(true)
.setDisplayImePolicy(DISPLAY_IME_POLICY_HIDE)
.setSimulateDisplay(true)
.createDisplay();
// Launch Ime test activity and initial the editor focus on virtual display.
imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
newDisplay.mId);
// Verify the activity is launched to the secondary display.
final ComponentName imeTestActivityName =
imeTestActivitySession.getActivity().getComponentName();
assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isTrue();
// Verify invoking showSoftInput will be ignored when the display with the HIDE policy.
final ImeEventStream stream = mockImeSession.openEventStream();
imeTestActivitySession.runOnMainSyncAndWait(
imeTestActivitySession.getActivity()::showSoftInput);
notExpectEvent(stream, editorMatcher("showSoftInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
NOT_EXPECT_TIMEOUT);
}
/**
* Test that the IME remains hidden with the {@link WindowManager#DISPLAY_IME_POLICY_HIDE} flag
* if the user taps the EditText on displays with no system decorations.
*/
@Test
public void testDisplayPolicyImeHideImeNoSystemDecorations() throws Exception {
assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
final MockImeSession mockImeSession = createManagedMockImeSession(this);
final ImeEventStream stream = mockImeSession.openEventStream();
// Create a virtual display with the policy to hide the IME.
final DisplayContent newDisplay = createManagedVirtualDisplaySession()
.setShowSystemDecorations(false)
.setDisplayImePolicy(DISPLAY_IME_POLICY_HIDE)
.setSimulateDisplay(true)
.createDisplay();
SystemUtil.runWithShellPermissionIdentity(
() -> assertTrue("Display should not support showing IME window",
mTargetContext.getSystemService(WindowManager.class)
.getDisplayImePolicy(newDisplay.mId)
== DISPLAY_IME_POLICY_HIDE));
final TestActivitySession<ImeTestActivity> imeTestActivitySession =
createManagedTestActivitySession();
// Launch Ime test activity and initial the editor focus on virtual display.
imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class,
newDisplay.mId);
// Expect no onStartInput and the activity does not show soft input when user taps the
// editor on the display with the HIDE policy.
tapAndAssertEditorFocusedOnImeActivity(imeTestActivitySession, newDisplay.mId);
notExpectEvent(stream, editorMatcher("onStartInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
NOT_EXPECT_TIMEOUT);
InputMethodVisibilityVerifier.expectImeInvisible(NOT_EXPECT_TIMEOUT);
}
@Test
public void testImeWindowCanShownWhenActivityMovedToDisplay() throws Exception {
// If config_perDisplayFocusEnabled, the focus will not move even if touching on
// the Activity in the different display.
assumeFalse(perDisplayFocusEnabled());
assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
// Launch a regular activity on default display at the test beginning to prevent the test
// may mis-touch the launcher icon that breaks the test expectation.
final TestActivitySession<Activities.RegularActivity> testActivitySession =
createManagedTestActivitySession();
testActivitySession.launchTestActivityOnDisplaySync(Activities.RegularActivity.class,
DEFAULT_DISPLAY);
// Create a virtual display and launch an activity on virtual display.
final DisplayContent newDisplay = createManagedVirtualDisplaySession()
.setShowSystemDecorations(true)
.setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
.setSimulateDisplay(true)
.createDisplay();
// Leverage MockImeSession to ensure at least an IME exists as default.
final MockImeSession mockImeSession = createManagedMockImeSession(this);
final TestActivitySession<ImeTestActivity> imeTestActivitySession =
createManagedTestActivitySession();
// Launch Ime test activity and initial the editor focus on virtual display.
imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
newDisplay.mId);
// Verify the activity is launched to the secondary display.
final ComponentName imeTestActivityName =
imeTestActivitySession.getActivity().getComponentName();
assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isTrue();
// Tap default display, assume a pointer-out-side event will happened to change the top
// display.
final DisplayContent defDisplay = mWmState.getDisplay(DEFAULT_DISPLAY);
tapOnDisplayCenter(defDisplay.mId);
mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
mWmState.assertValidity();
// Reparent ImeTestActivity from virtual display to default display.
getLaunchActivityBuilder()
.setUseInstrumentation()
.setTargetActivity(imeTestActivitySession.getActivity().getComponentName())
.setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.allowMultipleInstances(false)
.setDisplayId(DEFAULT_DISPLAY).execute();
waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(),
DEFAULT_DISPLAY, "Activity launched on default display and on top");
final ImeEventStream stream = mockImeSession.openEventStream();
expectEvent(stream, editorMatcher("onStartInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
// Activity is no longer on the secondary display
assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isFalse();
// Verify the activity shows soft input on the default display.
showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeTestActivitySession, stream);
}
@Test
public void testNoConfigurationChangedWhenSwitchBetweenTwoIdenticalDisplays() throws Exception {
// If config_perDisplayFocusEnabled, the focus will not move even if touching on
// the Activity in the different display.
assumeFalse(perDisplayFocusEnabled());
assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
// Create two displays with the same display metrics
final List<DisplayContent> newDisplays = createManagedVirtualDisplaySession()
.setShowSystemDecorations(true)
.setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
.setSimulateDisplay(true)
.createDisplays(2);
final DisplayContent firstDisplay = newDisplays.get(0);
final DisplayContent secondDisplay = newDisplays.get(1);
// Initialize IME test environment
final MockImeSession mockImeSession = createManagedMockImeSession(this);
final TestActivitySession<ImeTestActivity> imeTestActivitySession =
createManagedTestActivitySession();
ImeEventStream stream = mockImeSession.openEventStream();
// Filter out onConfigurationChanged events in case that IME is moved from the default
// display to the firstDisplay.
ImeEventStream configChangeVerifyStream = clearOnConfigurationChangedFromStream(stream);
// Make firstDisplay the top focus display.
tapOnDisplayCenter(firstDisplay.mId);
imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
firstDisplay.mId);
imeTestActivitySession.runOnMainSyncAndWait(
imeTestActivitySession.getActivity()::showSoftInput);
waitOrderedImeEventsThenAssertImeShown(stream, firstDisplay.mId,
editorMatcher("onStartInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
event -> "showSoftInput".equals(event.getEventName()));
// Launch Ime must not lead to screen size changes.
waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream);
final Rect currentBoundsOnFirstDisplay = expectCommand(stream,
mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
.getReturnParcelableValue();
// Clear onConfigurationChanged events before IME moves to the secondary display to prevent
// flaky because IME may receive configuration updates which we don't care about.
// An example is CONFIG_KEYBOARD_HIDDEN.
configChangeVerifyStream = clearOnConfigurationChangedFromStream(stream);
// Tap secondDisplay to change it to the top focused display.
tapOnDisplayCenter(secondDisplay.mId);
// Move ImeTestActivity from firstDisplay to secondDisplay.
getLaunchActivityBuilder()
.setUseInstrumentation()
.setTargetActivity(imeTestActivitySession.getActivity().getComponentName())
.setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.allowMultipleInstances(false)
.setDisplayId(secondDisplay.mId).execute();
// Make sure ImeTestActivity is move from the firstDisplay to the secondDisplay
waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(),
secondDisplay.mId, "ImeTestActivity must be top-resumed on display#"
+ secondDisplay.mId);
assertThat(mWmState.hasActivityInDisplay(firstDisplay.mId,
imeTestActivitySession.getActivity().getComponentName())).isFalse();
// Show soft input again to trigger IME movement.
imeTestActivitySession.runOnMainSyncAndWait(
imeTestActivitySession.getActivity()::showSoftInput);
waitOrderedImeEventsThenAssertImeShown(stream, secondDisplay.mId,
editorMatcher("onStartInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
event -> "showSoftInput".equals(event.getEventName()));
// Moving IME to the display with the same display metrics must not lead to
// screen size changes.
waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream);
final Rect currentBoundsOnSecondDisplay = expectCommand(stream,
mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
.getReturnParcelableValue();
assertWithMessage("The current WindowMetrics bounds of IME must not be changed.")
.that(currentBoundsOnFirstDisplay).isEqualTo(currentBoundsOnSecondDisplay);
}
public static class ImeTestActivity extends Activity {
ImeAwareEditText mEditText;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
mEditText = new ImeAwareEditText(this);
// Set private IME option for editorMatcher to identify which TextView received
// onStartInput event.
resetPrivateImeOptionsIdentifier();
final LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.addView(mEditText);
mEditText.requestFocus();
setContentView(layout);
}
void showSoftInput() {
final InputMethodManager imm = getSystemService(InputMethodManager.class);
imm.showSoftInput(mEditText, 0);
}
void resetPrivateImeOptionsIdentifier() {
mEditText.setPrivateImeOptions(
getClass().getName() + "/" + Long.toString(SystemClock.elapsedRealtimeNanos()));
}
}
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;
}
}
private void assertImeWindowAndDisplayConfiguration(
WindowState imeWinState, DisplayContent 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);
}
private void tapAndAssertEditorFocusedOnImeActivity(
TestActivitySession<? extends ImeTestActivity> activitySession, int expectDisplayId) {
final int[] location = new int[2];
waitAndAssertActivityStateOnDisplay(activitySession.getActivity().getComponentName(),
STATE_RESUMED, expectDisplayId,
"ImeActivity failed to appear on display#" + expectDisplayId);
activitySession.runOnMainSyncAndWait(() -> {
final EditText editText = activitySession.getActivity().mEditText;
editText.getLocationOnScreen(location);
});
final ComponentName expectComponent = activitySession.getActivity().getComponentName();
tapOnDisplaySync(location[0], location[1], expectDisplayId);
mWmState.computeState(activitySession.getActivity().getComponentName());
mWmState.assertFocusedAppOnDisplay("Activity not focus on the display", expectComponent,
expectDisplayId);
}
private void showSoftInputAndAssertImeShownOnDisplay(int displayId,
TestActivitySession<? extends ImeTestActivity> activitySession, ImeEventStream stream)
throws Exception {
activitySession.runOnMainSyncAndWait(
activitySession.getActivity()::showSoftInput);
expectEvent(stream, editorMatcher("onStartInputView",
activitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
// Assert the IME is shown on the expected display.
mWmState.waitAndAssertImeWindowShownOnDisplay(displayId);
}
}