Verify onConfigurationChanged behavior for IME
Bug: 149463653
Test: atest InputmethodServiceTest MultiDisplaySystemDecorationTests
Change-Id: Ic29767d1d0d75d65e7b7cc71cce48cc29b074d3a
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
index 8dbc12c..a4148f0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
@@ -396,6 +396,7 @@
final ImeEventStream stream = mockImeSession.openEventStream();
imeTestActivitySession.runOnMainSyncAndWait(
imeTestActivitySession.getActivity()::showSoftInput);
+ ImeEventStream configChangeVerifyStream = stream.copy();
waitOrderedImeEventsThenAssertImeShown(stream, newDisplay.mId,
editorMatcher("onStartInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
@@ -403,6 +404,8 @@
// Assert the configuration of the IME window is the same as the configuration of the
// virtual display.
+ waitAndAssertImeConfigurationChanged(configChangeVerifyStream);
+ configChangeVerifyStream = clearOnConfigurationChangedFromStream(configChangeVerifyStream);
assertImeWindowAndDisplayConfiguration(mWmState.getImeWindowState(), newDisplay);
// Launch another activity on the default display.
@@ -419,6 +422,7 @@
// Assert the configuration of the IME window is the same as the configuration of the
// default display.
+ waitAndAssertImeConfigurationChanged(configChangeVerifyStream);
assertImeWindowAndDisplayConfiguration(mWmState.getImeWindowState(),
mWmState.getDisplay(DEFAULT_DISPLAY));
}
@@ -492,6 +496,7 @@
tapOnDisplayCenter(defDisplay.mId);
imeTestActivitySession.runOnMainSyncAndWait(
imeTestActivitySession.getActivity()::showSoftInput);
+ ImeEventStream configChangeVerifyStream = stream.copy();
waitOrderedImeEventsThenAssertImeShown(stream, defDisplay.mId,
editorMatcher("onStartInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
@@ -507,6 +512,9 @@
imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()),
event -> "showSoftInput".equals(event.getEventName()));
+ waitAndAssertImeConfigurationChanged(configChangeVerifyStream);
+ configChangeVerifyStream = clearOnConfigurationChangedFromStream(configChangeVerifyStream);
+
// Tap default display again to make sure the IME window will come back.
tapOnDisplayCenter(defDisplay.mId);
imeTestActivitySession.runOnMainSyncAndWait(
@@ -515,6 +523,8 @@
editorMatcher("onStartInput",
imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
event -> "showSoftInput".equals(event.getEventName()));
+
+ waitAndAssertImeConfigurationChanged(configChangeVerifyStream);
}
/**
@@ -746,6 +756,73 @@
event -> "showSoftInput".equals(event.getEventName()));
}
+ @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();
+
+ // Make firstDisplay the top focus display.
+ tapOnDisplayCenter(firstDisplay.mId);
+ // Filter out onConfigurationChanged events in case that IME is moved from the default
+ // display to the firstDisplay.
+ ImeEventStream configChangeVerifyStream = clearOnConfigurationChangedFromStream(stream);
+ 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 configuration changes.
+ waitAndAssertNoImeConfigurationChanged(configChangeVerifyStream);
+
+ // 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 trigger
+ // onConfigurationChanged callback.
+ waitAndAssertNoImeConfigurationChanged(configChangeVerifyStream);
+ }
+
public static class ImeTestActivity extends Activity {
ImeAwareEditText mEditText;
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
index e145c52..fdd22d3 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
@@ -41,7 +41,9 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.clearAllEvents;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
@@ -53,6 +55,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.inputmethodservice.InputMethodService;
import android.os.Bundle;
import android.provider.Settings;
import android.server.wm.CommandSession.ActivitySession;
@@ -69,6 +72,7 @@
import com.android.compatibility.common.util.SystemUtil;
import com.android.cts.mockime.ImeEvent;
import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeEventStreamTestUtils;
import org.junit.AfterClass;
import org.junit.Before;
@@ -491,7 +495,7 @@
@NonNull
List<DisplayContent> createDisplays(int count) {
if (mSimulateDisplay) {
- return simulateDisplay();
+ return simulateDisplays(count);
} else {
return createVirtualDisplays(count);
}
@@ -525,13 +529,10 @@
* </pre>
* @return {@link DisplayContent} of newly created display.
*/
- private List<DisplayContent> simulateDisplay() {
+ private List<DisplayContent> simulateDisplays(int count) {
mOverlayDisplayDeviceSession = new OverlayDisplayDevicesSession(mContext);
- mOverlayDisplayDeviceSession.createDisplay(
- mSimulationDisplaySize,
- mDensityDpi,
- mOwnContentOnly,
- mShowSystemDecorations);
+ mOverlayDisplayDeviceSession.createDisplays(mSimulationDisplaySize, mDensityDpi,
+ mOwnContentOnly, mShowSystemDecorations, count);
mOverlayDisplayDeviceSession.configureDisplays(mDisplayImePolicy /* imePolicy */);
return mOverlayDisplayDeviceSession.getCreatedDisplays();
}
@@ -692,16 +693,24 @@
}
/** Creates overlay display with custom density dpi, specified size, and test flags. */
- void createDisplay(Size displaySize, int densityDpi, boolean ownContentOnly,
- boolean shouldShowSystemDecorations) {
- String displaySettingsEntry = displaySize + "/" + densityDpi;
- if (ownContentOnly) {
- displaySettingsEntry += OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+ void createDisplays(Size displaySize, int densityDpi, boolean ownContentOnly,
+ boolean shouldShowSystemDecorations, int count) {
+ final StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < count; i++) {
+ String displaySettingsEntry = displaySize + "/" + densityDpi;
+ if (ownContentOnly) {
+ displaySettingsEntry += OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+ }
+ if (shouldShowSystemDecorations) {
+ displaySettingsEntry += OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
+ }
+ builder.append(displaySettingsEntry);
+ // Creating n displays needs (n - 1) ';'.
+ if (i < count - 1) {
+ builder.append(';');
+ }
}
- if (shouldShowSystemDecorations) {
- displaySettingsEntry += OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
- }
- set(displaySettingsEntry);
+ set(builder.toString());
}
void configureDisplays(int imePolicy) {
@@ -853,6 +862,26 @@
mWmState.waitAndAssertImeWindowShownOnDisplay(displayId);
}
+ protected void waitAndAssertImeConfigurationChanged(ImeEventStream stream) throws Exception {
+ expectEvent(stream, event -> "onConfigurationChanged".equals(event.getEventName()),
+ TimeUnit.SECONDS.toMillis(5) /* eventTimeout */);
+ }
+
+ protected void waitAndAssertNoImeConfigurationChanged(ImeEventStream stream) {
+ notExpectEvent(stream, event -> "onConfigurationChanged".equals(event.getEventName()),
+ TimeUnit.SECONDS.toMillis(1) /* eventTimeout */);
+ }
+
+ /**
+ * Clears all {@link InputMethodService#onConfigurationChanged(Configuration)} events from the
+ * given {@code stream} and returns a forked {@link ImeEventStream}.
+ *
+ * @see ImeEventStreamTestUtils#clearAllEvents(ImeEventStream, String)
+ */
+ protected ImeEventStream clearOnConfigurationChangedFromStream(ImeEventStream stream) {
+ return clearAllEvents(stream, "onConfigurationChanged");
+ }
+
/**
* This class is used when you need to test virtual display created by a privileged app.
*
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index e5344b8..b2955b2 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -994,6 +994,11 @@
});
}
+ @Override
+ public void onConfigurationChanged(Configuration configuration) {
+ getTracer().onConfigurationChanged(() -> {}, configuration);
+ }
+
/**
* Event tracing helper class for {@link MockIme}.
*/
@@ -1271,5 +1276,11 @@
final Bundle arguments = new Bundle();
recordEventInternal("onInlineSuggestionLongClickedEvent", runnable, arguments);
}
+
+ void onConfigurationChanged(@NonNull Runnable runnable, Configuration configuration) {
+ final Bundle arguments = new Bundle();
+ arguments.putParcelable("Configuration", configuration);
+ recordEventInternal("onConfigurationChanged", runnable, arguments);
+ }
}
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
index 1dc5148..6eeb1da 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
@@ -713,6 +713,23 @@
}
}
+ @Test
+ public void testNoConfigurationChangedOnStartInput() throws Exception {
+ try (MockImeSession imeSession = MockImeSession.create(
+ mInstrumentation.getContext(), mInstrumentation.getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+
+ final ImeEventStream forkedStream = stream.copy();
+ expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+ // Verify if InputMethodService#isUiContext returns true
+ notExpectEvent(forkedStream, event -> "onConfigurationChanged".equals(
+ event.getEventName()), EXPECTED_TIMEOUT);
+ }
+ }
+
/** Test case for committing and setting composing region after cursor. */
private static UpdateSelectionTest getCommitAndSetComposingRegionTest(
long timeout, String makerPrefix) throws Exception {