Extend InputMethodStressTest to add more IME show/hide e2e test cases.
Add more IME show/hide test cases to cover different flag settings(window flags, soft input mode flags), show/hide methods (from auto-show/IMM/WIC/Click), call times and user actions.
Merge the two TestActivitys in AutoShowTest and ImeOpenCloseStressTest into one in ImeStressTestUtil.
Modify test type in TEST_MAPPING to "presubmit-large" to avoid timeout.
See go/imf-test-cases for clearer test descriptions and results.
Bug: 242838873
Bug: 240359838
Test: atest com.android.inputmethod.stresstest.AutoShowTest
atest com.android.inputmethod.stresstest.ImeOpenCloseStressTest
Change-Id: Ibff3a85fa5b66c692a2fe8cd69fb1ca521b1b8d8
diff --git a/tests/InputMethodStressTest/AndroidManifest.xml b/tests/InputMethodStressTest/AndroidManifest.xml
index f5fe8f2..2d183bc 100644
--- a/tests/InputMethodStressTest/AndroidManifest.xml
+++ b/tests/InputMethodStressTest/AndroidManifest.xml
@@ -16,11 +16,11 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.inputmethod.stresstest">
+ package="com.android.inputmethod.stresstest">
<application>
- <activity android:name=".AutoShowTest$TestActivity"/>
- <activity android:name=".ImeOpenCloseStressTest$TestActivity"/>
+ <activity android:name=".ImeStressTestUtil$TestActivity"
+ android:configChanges="orientation|screenSize"/>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/InputMethodStressTest/TEST_MAPPING b/tests/InputMethodStressTest/TEST_MAPPING
index ad07205..06e2ce8 100644
--- a/tests/InputMethodStressTest/TEST_MAPPING
+++ b/tests/InputMethodStressTest/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "presubmit": [
+ "presubmit-large": [
{
"name": "InputMethodStressTest"
}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
index 92ea029..8e4ecf1 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -16,24 +16,32 @@
package com.android.inputmethod.stresstest;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.UNFOCUSABLE_VIEW;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet;
import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden;
-import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus;
import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
-import android.app.Activity;
+import static com.google.common.truth.Truth.assertThat;
+
import android.app.Instrumentation;
import android.content.Intent;
-import android.os.Bundle;
+import android.os.SystemClock;
import android.platform.test.annotations.RootPermissionTest;
import android.platform.test.rule.UnlockScreenRule;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
import android.view.WindowManager;
import android.widget.EditText;
-import android.widget.LinearLayout;
-import androidx.annotation.Nullable;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Rule;
@@ -41,135 +49,428 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
- * Tests to verify the "auto show" behavior in {@code InputMethodManagerService} when the window
+ * Tests to verify the "auto-show" behavior in {@code InputMethodManagerService} when the window
* gaining the focus to start the input.
*/
@RootPermissionTest
@RunWith(Parameterized.class)
public final class AutoShowTest {
- @Rule
- public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
+ @Rule public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
@Rule
public ScreenCaptureRule mScreenCaptureRule =
new ScreenCaptureRule("/sdcard/InputMethodStressTest");
- private static final int[] SOFT_INPUT_VISIBILITY_FLAGS =
- new int[] {
- WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED,
- WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN,
- WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN,
- WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE,
- WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE,
- };
-
- private static final int[] SOFT_INPUT_ADJUST_FLAGS =
- new int[] {
- WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED,
- WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
- WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN,
- WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
- };
-
// TODO(b/240359838): add test case {@code Configuration.SCREENLAYOUT_SIZE_LARGE}.
@Parameterized.Parameters(
- name =
- "softInputVisibility={0}, softInputAdjustment={1},"
- + " softInputModeIsForwardNavigation={2}")
- public static List<Object[]> softInputModeConfigs() {
- ArrayList<Object[]> params = new ArrayList<>();
- for (int softInputVisibility : SOFT_INPUT_VISIBILITY_FLAGS) {
- for (int softInputAdjust : SOFT_INPUT_ADJUST_FLAGS) {
- params.add(new Object[] {softInputVisibility, softInputAdjust, true});
- params.add(new Object[] {softInputVisibility, softInputAdjust, false});
- }
- }
- return params;
+ name = "windowFocusFlags={0}, softInputVisibility={1}, softInputAdjustment={2}")
+ public static List<Object[]> windowAndSoftInputFlagParameters() {
+ return getWindowAndSoftInputFlagParameters();
}
- private static final String SOFT_INPUT_FLAGS = "soft_input_flags";
+ private final int mSoftInputFlags;
+ private final int mWindowFocusFlags;
+ private final Instrumentation mInstrumentation;
- private final int mSoftInputVisibility;
- private final int mSoftInputAdjustment;
- private final boolean mSoftInputIsForwardNavigation;
+ public AutoShowTest(int windowFocusFlags, int softInputVisibility, int softInputAdjustment) {
+ mSoftInputFlags = softInputVisibility | softInputAdjustment;
+ mWindowFocusFlags = windowFocusFlags;
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ }
- public AutoShowTest(
- int softInputVisibility,
- int softInputAdjustment,
- boolean softInputIsForwardNavigation) {
- mSoftInputVisibility = softInputVisibility;
- mSoftInputAdjustment = softInputAdjustment;
- mSoftInputIsForwardNavigation = softInputIsForwardNavigation;
+ /**
+ * Test auto-show IME behavior when the {@link EditText} is focusable ({@link
+ * EditText#isFocusableInTouchMode} is {@code true}) and has called {@link
+ * EditText#requestFocus}.
+ */
+ @Test
+ public void autoShow_hasFocusedView_requestFocus() {
+ // request focus at onCreate()
+ Intent intent =
+ createIntent(
+ mWindowFocusFlags,
+ mSoftInputFlags,
+ Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+ TestActivity activity = TestActivity.start(intent);
+
+ verifyAutoShowBehavior_forwardWithKeyboardOff(activity);
+ }
+
+ /**
+ * Test auto-show IME behavior when the {@link EditText} is focusable ({@link
+ * EditText#isFocusableInTouchMode} is {@code true}) and {@link EditText#requestFocus} is not
+ * called. The IME should never be shown because there is no focused editor in the window.
+ */
+ @Test
+ public void autoShow_hasFocusedView_notRequestFocus() {
+ // request focus not set
+ Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity activity = TestActivity.start(intent);
+ EditText editText = activity.getEditText();
+
+ int windowFlags = activity.getWindow().getAttributes().flags;
+ if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+ // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus.
+ verifyWindowAndViewFocus(
+ editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false);
+ } else {
+ verifyWindowAndViewFocus(
+ editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+ }
+ // IME is always hidden because there is no view focus.
+ verifyImeIsAlwaysHidden(editText);
+ }
+
+ /**
+ * Test auto-show IME behavior when the {@link EditText} is not focusable ({@link
+ * EditText#isFocusableInTouchMode} is {@code false}) and {@link EditText#requestFocus} is not
+ * called. The IME should never be shown because there is no focusable editor in the window.
+ */
+ @Test
+ public void autoShow_notFocusedView_notRequestFocus() {
+ // Unfocusable view, request focus not set
+ Intent intent =
+ createIntent(
+ mWindowFocusFlags,
+ mSoftInputFlags,
+ Collections.singletonList(UNFOCUSABLE_VIEW));
+ TestActivity activity = TestActivity.start(intent);
+ EditText editText = activity.getEditText();
+
+ int windowFlags = activity.getWindow().getAttributes().flags;
+ if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+ // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus.
+ verifyWindowAndViewFocus(
+ editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false);
+ } else {
+ verifyWindowAndViewFocus(
+ editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+ }
+ // IME is always hidden because there is no focused view.
+ verifyImeIsAlwaysHidden(editText);
+ }
+
+ /**
+ * Test auto-show IME behavior when the activity is navigated forward from another activity with
+ * keyboard off.
+ */
+ @Test
+ public void autoShow_forwardWithKeyboardOff() {
+ // Create first activity with keyboard off
+ Intent intent1 =
+ createIntent(
+ 0x0 /* No window focus flags */,
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+ | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+ Collections.emptyList());
+ TestActivity firstActivity = TestActivity.start(intent1);
+
+ // Create second activity with parameterized flags:
+ Intent intent2 =
+ createIntent(
+ mWindowFocusFlags,
+ mSoftInputFlags,
+ Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+ TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2);
+
+ // The auto-show behavior should be the same as opening the app
+ verifyAutoShowBehavior_forwardWithKeyboardOff(secondActivity);
+ }
+
+ /**
+ * Test auto-show IME behavior when the activity is navigated forward from another activity with
+ * keyboard on.
+ */
+ @Test
+ public void autoShow_forwardWithKeyboardOn() {
+ // Create first activity with keyboard on
+ Intent intent1 =
+ createIntent(
+ 0x0 /* No window focus flags */,
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+ | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+ Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+ TestActivity firstActivity = TestActivity.start(intent1);
+ // Show Ime with InputMethodManager to ensure the keyboard is on.
+ boolean succ = callOnMainSync(firstActivity::showImeWithInputMethodManager);
+ assertThat(succ).isTrue();
+ SystemClock.sleep(1000);
+ mInstrumentation.waitForIdleSync();
+
+ // Create second activity with parameterized flags:
+ Intent intent2 =
+ createIntent(
+ mWindowFocusFlags,
+ mSoftInputFlags,
+ Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+ TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2);
+
+ // The auto-show behavior should be the same as open app
+ verifyAutoShowBehavior_forwardWithKeyboardOn(secondActivity);
+ }
+
+ /**
+ * Test auto-show IME behavior when the activity is navigated back from another activity with
+ * keyboard off.
+ */
+ @Test
+ public void autoShow_backwardWithKeyboardOff() {
+ // Not request focus at onCreate() to avoid triggering auto-show behavior
+ Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity firstActivity = TestActivity.start(intent1);
+ // Request view focus after app starts
+ mInstrumentation.runOnMainSync(firstActivity::requestFocus);
+
+ Intent intent2 =
+ createIntent(
+ 0x0 /* No window focus flags */,
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+ | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+ Collections.emptyList());
+ TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2);
+ secondActivity.finish();
+ mInstrumentation.waitForIdleSync();
+
+ // When activity is navigated back from another activity with keyboard off, the keyboard
+ // will not show except when soft input visibility flag is SOFT_INPUT_STATE_ALWAYS_VISIBLE.
+ verifyAutoShowBehavior_backwardWithKeyboardOff(firstActivity);
+ }
+
+ /**
+ * Test auto-show IME behavior when the activity is navigated back from another activity with
+ * keyboard on.
+ */
+ @Test
+ public void autoShow_backwardWithKeyboardOn() {
+ // Not request focus at onCreate() to avoid triggering auto-show behavior
+ Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity activity = TestActivity.start(intent1);
+ // Request view focus after app starts
+ mInstrumentation.runOnMainSync(activity::requestFocus);
+
+ // Create second TestActivity
+ Intent intent2 =
+ createIntent(
+ 0x0 /* No window focus flags */,
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+ | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+ Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+ ImeStressTestUtil.TestActivity secondActivity = activity.startSecondTestActivity(intent2);
+ // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
+ boolean succ = callOnMainSync(secondActivity::showImeWithInputMethodManager);
+ assertThat(succ).isTrue();
+ SystemClock.sleep(1000);
+ mInstrumentation.waitForIdleSync();
+ // Close the second activity
+ secondActivity.finish();
+ SystemClock.sleep(1000);
+ mInstrumentation.waitForIdleSync();
+ // When activity is navigated back from another activity with keyboard on, the keyboard
+ // will not hide except when soft input visibility flag is SOFT_INPUT_STATE_ALWAYS_HIDDEN.
+ verifyAutoShowBehavior_backwardWithKeyboardOn(activity);
}
@Test
- public void autoShow() {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- int flags = mSoftInputVisibility | mSoftInputAdjustment;
- if (mSoftInputIsForwardNavigation) {
- flags |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+ public void clickFocusableView_requestFocus() {
+ if ((mWindowFocusFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+ // UiAutomator cannot get UiObject if FLAG_NOT_FOCUSABLE is set
+ return;
+ }
+ Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity activity = TestActivity.start(intent);
+ // Request view focus after app starts
+ mInstrumentation.runOnMainSync(activity::requestFocus);
+
+ // Find the editText and click it
+ UiObject2 editTextUiObject =
+ UiDevice.getInstance(mInstrumentation)
+ .wait(Until.findObject(By.clazz(EditText.class)), 5000);
+ assertThat(editTextUiObject).isNotNull();
+ editTextUiObject.click();
+
+ // Ime will show unless window flag is set
+ verifyClickBehavior(activity);
+ }
+
+ @Test
+ public void clickFocusableView_notRequestFocus() {
+ if ((mWindowFocusFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+ // UiAutomator cannot get UiObject if FLAG_NOT_FOCUSABLE is set
+ return;
+ }
+ // Not request focus
+ Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity activity = TestActivity.start(intent1);
+
+ // Find the editText and click it
+ UiObject2 editTextUiObject =
+ UiDevice.getInstance(mInstrumentation)
+ .wait(Until.findObject(By.clazz(EditText.class)), 5000);
+ assertThat(editTextUiObject).isNotNull();
+ editTextUiObject.click();
+
+ // Ime will show unless window flag is set
+ verifyClickBehavior(activity);
+ }
+
+ public static void verifyAutoShowBehavior_forwardWithKeyboardOff(TestActivity activity) {
+ // public: also used by ImeOpenCloseStressTest
+ if (hasUnfocusableWindowFlags(activity)) {
+ verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+ return;
}
- Intent intent =
- new Intent()
- .setAction(Intent.ACTION_MAIN)
- .setClass(instrumentation.getContext(), TestActivity.class)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
- .putExtra(SOFT_INPUT_FLAGS, flags);
- TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent);
+ int softInputMode = activity.getWindow().getAttributes().softInputMode;
+ int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
+ int softInputAdjustment = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
EditText editText = activity.getEditText();
- waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
- if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
- || mSoftInputVisibility
- == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) {
- // IME will be auto-shown if softInputMode is set with flag:
- // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE
- waitOnMainUntilImeIsShown(editText);
- } else if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
- || mSoftInputVisibility
- == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) {
- // IME will be not be shown if softInputMode is set with flag:
- // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN
- verifyImeIsAlwaysHidden(editText);
- } else {
- // The current system behavior will choose to show IME automatically when navigating
- // forward to an app that has no visibility state specified (i.e.
- // SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE flag.
- if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
- && mSoftInputAdjustment == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
- && mSoftInputIsForwardNavigation) {
+ verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+ switch (softInputVisibility) {
+ case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
+ case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: {
+ // IME will be auto-shown if softInputMode is set with flag:
+ // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE
waitOnMainUntilImeIsShown(editText);
+ break;
}
+ case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
+ case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
+ case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: {
+ // IME will be not be auto-shown if softInputMode is set with flag:
+ // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN,
+ // or stay unchanged if set SOFT_INPUT_STATE_UNCHANGED
+ verifyImeIsAlwaysHidden(editText);
+ break;
+ }
+ case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: {
+ if (softInputAdjustment
+ == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
+ // The current system behavior will choose to show IME automatically when
+ // navigating forward to an app that has no visibility state specified
+ // (i.e. SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE
+ // flag.
+ waitOnMainUntilImeIsShown(editText);
+ } else {
+ verifyImeIsAlwaysHidden(editText);
+ }
+ break;
+ }
+ default:
+ break;
}
}
- public static class TestActivity extends Activity {
- private EditText mEditText;
+ private static void verifyAutoShowBehavior_forwardWithKeyboardOn(TestActivity activity) {
+ int windowFlags = activity.getWindow().getAttributes().flags;
+ int softInputMode = activity.getWindow().getAttributes().softInputMode;
+ int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
+ int softInputAdjustment = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+ EditText editText = activity.getEditText();
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- int flags = getIntent().getIntExtra(SOFT_INPUT_FLAGS, 0);
- getWindow().setSoftInputMode(flags);
- LinearLayout rootView = new LinearLayout(this);
- rootView.setOrientation(LinearLayout.VERTICAL);
- mEditText = new EditText(this);
- rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
- setContentView(rootView);
- // Ensure the focused view is a text editor (View#onCheckIsTextEditor() returns true) to
- // automatically display a soft input window.
- mEditText.requestFocus();
+ if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+ // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus. The IME
+ // will always be hidden even though the view can get focus itself.
+ verifyWindowAndViewFocus(
+ editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ true);
+ // TODO(b/252192121): Ime should be hidden but is shown.
+ // waitOnMainUntilImeIsHidden(editText);
+ return;
+ } else if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+ || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) {
+ // When FLAG_ALT_FOCUSABLE_IM or FLAG_LOCAL_FOCUS_MODE is set, the view can gain both
+ // window focus and view focus but not IME focus. The IME will always be hidden.
+ verifyWindowAndViewFocus(
+ editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+ // TODO(b/252192121): Ime should be hidden but is shown.
+ // waitOnMainUntilImeIsHidden(editText);
+ return;
}
+ verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+ switch (softInputVisibility) {
+ case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
+ case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
+ case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: {
+ // IME will be auto-shown if softInputMode is set with flag:
+ // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE
+ waitOnMainUntilImeIsShown(editText);
+ break;
+ }
+ case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
+ case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: {
+ // IME will be not be auto-shown if softInputMode is set with flag:
+ // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN
+ // or stay unchanged if set SOFT_INPUT_STATE_UNCHANGED
+ verifyImeIsAlwaysHidden(editText);
+ break;
+ }
+ case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: {
+ if (softInputAdjustment
+ == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
+ // The current system behavior will choose to show IME automatically when
+ // navigating
+ // forward to an app that has no visibility state specified (i.e.
+ // SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE flag.
+ waitOnMainUntilImeIsShown(editText);
+ } else {
+ verifyImeIsAlwaysHidden(editText);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
- public EditText getEditText() {
- return mEditText;
+ private static void verifyAutoShowBehavior_backwardWithKeyboardOff(TestActivity activity) {
+ if (hasUnfocusableWindowFlags(activity)) {
+ verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+ return;
+ }
+ int softInputMode = activity.getWindow().getAttributes().softInputMode;
+ int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
+ EditText editText = activity.getEditText();
+
+ verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+ if (softInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) {
+ waitOnMainUntilImeIsShown(editText);
+ } else {
+ verifyImeIsAlwaysHidden(editText);
+ }
+ }
+
+ private static void verifyAutoShowBehavior_backwardWithKeyboardOn(TestActivity activity) {
+ if (hasUnfocusableWindowFlags(activity)) {
+ verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+ return;
+ }
+ int softInputMode = activity.getWindow().getAttributes().softInputMode;
+ int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
+ EditText editText = activity.getEditText();
+
+ verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+ if (softInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) {
+ verifyImeIsAlwaysHidden(editText);
+ } else {
+ waitOnMainUntilImeIsShown(editText);
+ }
+ }
+
+ private static void verifyClickBehavior(TestActivity activity) {
+ int windowFlags = activity.getWindow().getAttributes().flags;
+ EditText editText = activity.getEditText();
+
+ verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+ if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+ || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) {
+ verifyImeIsAlwaysHidden(editText);
+ } else {
+ waitOnMainUntilImeIsShown(editText);
}
}
}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
index 8419276..82acfb6 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
@@ -16,224 +16,535 @@
package com.android.inputmethod.stresstest;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
-
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.INPUT_METHOD_MANAGER_HIDE_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.INPUT_METHOD_MANAGER_SHOW_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags;
import static com.android.inputmethod.stresstest.ImeStressTestUtil.isImeShown;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus;
import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
-import android.app.Activity;
+import static com.google.common.truth.Truth.assertThat;
+
import android.app.Instrumentation;
import android.content.Intent;
-import android.os.Bundle;
+import android.os.Build;
import android.os.SystemClock;
import android.platform.test.annotations.RootPermissionTest;
import android.platform.test.rule.UnlockScreenRule;
+import android.support.test.uiautomator.UiDevice;
import android.util.Log;
-import android.view.WindowInsets;
-import android.view.WindowInsetsAnimation;
-import android.view.inputmethod.InputMethodManager;
+import android.view.WindowManager;
import android.widget.EditText;
-import android.widget.LinearLayout;
-import androidx.annotation.Nullable;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
@RootPermissionTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
public final class ImeOpenCloseStressTest {
private static final String TAG = "ImeOpenCloseStressTest";
private static final int NUM_TEST_ITERATIONS = 10;
- @Rule
- public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
+ @Rule public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
@Rule
public ScreenCaptureRule mScreenCaptureRule =
new ScreenCaptureRule("/sdcard/InputMethodStressTest");
- private Instrumentation mInstrumentation;
- @Before
- public void setUp() {
+ private final Instrumentation mInstrumentation;
+ private final int mSoftInputFlags;
+ private final int mWindowFocusFlags;
+
+ @Parameterized.Parameters(
+ name = "windowFocusFlags={0}, softInputVisibility={1}, softInputAdjustment={2}")
+ public static List<Object[]> windowAndSoftInputFlagParameters() {
+ return getWindowAndSoftInputFlagParameters();
+ }
+
+ public ImeOpenCloseStressTest(
+ int windowFocusFlags, int softInputVisibility, int softInputAdjustment) {
+ mSoftInputFlags = softInputVisibility | softInputAdjustment;
+ mWindowFocusFlags = windowFocusFlags;
mInstrumentation = InstrumentationRegistry.getInstrumentation();
}
@Test
- public void testShowHide_waitingVisibilityChange() {
- TestActivity activity = TestActivity.start();
- EditText editText = activity.getEditText();
- waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
- for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+ public void testShowHideWithInputMethodManager_waitingVisibilityChange() {
+ Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity activity = TestActivity.start(intent);
+ // Request focus after app starts to avoid triggering auto-show behavior.
+ mInstrumentation.runOnMainSync(activity::requestFocus);
+ // Test only once if window flags set to save time.
+ int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS;
+ for (int i = 0; i < iterNum; i++) {
String msgPrefix = "Iteration #" + i + " ";
Log.i(TAG, msgPrefix + "start");
- mInstrumentation.runOnMainSync(activity::showIme);
- waitOnMainUntil(msgPrefix + "IME should be visible", () -> isImeShown(editText));
- mInstrumentation.runOnMainSync(activity::hideIme);
- waitOnMainUntil(msgPrefix + "IME should be hidden", () -> !isImeShown(editText));
+ boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+ assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+ verifyShowBehavior(activity);
+
+ boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
+ assertThat(hideResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+
+ verifyHideBehavior(activity);
}
}
@Test
- public void testShowHide_waitingAnimationEnd() {
- TestActivity activity = TestActivity.start();
+ public void testShowHideWithInputMethodManager_waitingAnimationEnd() {
+ Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity activity = TestActivity.start(intent);
+ // Request focus after app starts to avoid triggering auto-show behavior.
+ mInstrumentation.runOnMainSync(activity::requestFocus);
+
+ if (hasUnfocusableWindowFlags(activity)) {
+ return; // Skip to save time.
+ }
activity.enableAnimationMonitoring();
EditText editText = activity.getEditText();
- waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
String msgPrefix = "Iteration #" + i + " ";
Log.i(TAG, msgPrefix + "start");
- mInstrumentation.runOnMainSync(activity::showIme);
- waitOnMainUntil(msgPrefix + "IME should be visible",
+ boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+ assertThat(showResult).isTrue();
+ waitOnMainUntil(
+ msgPrefix + "IME should be visible",
() -> !activity.isAnimating() && isImeShown(editText));
- mInstrumentation.runOnMainSync(activity::hideIme);
- waitOnMainUntil(msgPrefix + "IME should be hidden",
+
+ boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
+ assertThat(hideResult).isTrue();
+ waitOnMainUntil(
+ msgPrefix + "IME should be hidden",
() -> !activity.isAnimating() && !isImeShown(editText));
}
}
@Test
- public void testShowHide_intervalAfterHide() {
+ public void testShowHideWithInputMethodManager_intervalAfterHide() {
// Regression test for b/221483132
- TestActivity activity = TestActivity.start();
- EditText editText = activity.getEditText();
+ Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity activity = TestActivity.start(intent);
+ // Request focus after app starts to avoid triggering auto-show behavior.
+ mInstrumentation.runOnMainSync(activity::requestFocus);
+ if (hasUnfocusableWindowFlags(activity)) {
+ return; // Skip to save time.
+ }
// Intervals = 10, 20, 30, ..., 100, 150, 200, ...
List<Integer> intervals = new ArrayList<>();
for (int i = 10; i < 100; i += 10) intervals.add(i);
for (int i = 100; i < 1000; i += 50) intervals.add(i);
- waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
+ boolean firstHide = false;
for (int intervalMillis : intervals) {
String msgPrefix = "Interval = " + intervalMillis + " ";
Log.i(TAG, msgPrefix + " start");
- mInstrumentation.runOnMainSync(activity::hideIme);
+ boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
+ assertThat(hideResult).isEqualTo(firstHide);
+ firstHide = true;
SystemClock.sleep(intervalMillis);
- mInstrumentation.runOnMainSync(activity::showIme);
- waitOnMainUntil(msgPrefix + "IME should be visible",
- () -> isImeShown(editText));
+
+ boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+ assertThat(showResult).isTrue();
+ verifyShowBehavior(activity);
}
}
@Test
- public void testShowHideInSameFrame() {
- TestActivity activity = TestActivity.start();
- activity.enableAnimationMonitoring();
- EditText editText = activity.getEditText();
- waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
+ public void testShowHideWithInputMethodManager_inSameFrame() {
+ Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity activity = TestActivity.start(intent);
+ // Request focus after app starts to avoid triggering auto-show behavior.
+ mInstrumentation.runOnMainSync(activity::requestFocus);
+ if (hasUnfocusableWindowFlags(activity)) {
+ return; // Skip to save time.
+ }
// hidden -> show -> hide
- mInstrumentation.runOnMainSync(() -> {
- Log.i(TAG, "Calling showIme() and hideIme()");
- activity.showIme();
- activity.hideIme();
- });
+ mInstrumentation.runOnMainSync(
+ () -> {
+ Log.i(TAG, "Calling showIme() and hideIme()");
+ activity.showImeWithInputMethodManager();
+ activity.hideImeWithInputMethodManager();
+ });
// Wait until IMMS / IMS handles messages.
SystemClock.sleep(1000);
mInstrumentation.waitForIdleSync();
- waitOnMainUntil("IME should be invisible after show/hide", () -> !isImeShown(editText));
+ verifyHideBehavior(activity);
- mInstrumentation.runOnMainSync(activity::showIme);
- waitOnMainUntil("IME should be visible",
- () -> !activity.isAnimating() && isImeShown(editText));
+ mInstrumentation.runOnMainSync(activity::showImeWithInputMethodManager);
+ verifyShowBehavior(activity);
mInstrumentation.waitForIdleSync();
// shown -> hide -> show
- mInstrumentation.runOnMainSync(() -> {
- Log.i(TAG, "Calling hideIme() and showIme()");
- activity.hideIme();
- activity.showIme();
- });
+ mInstrumentation.runOnMainSync(
+ () -> {
+ Log.i(TAG, "Calling hideIme() and showIme()");
+ activity.hideImeWithInputMethodManager();
+ activity.showImeWithInputMethodManager();
+ });
// Wait until IMMS / IMS handles messages.
SystemClock.sleep(1000);
mInstrumentation.waitForIdleSync();
- waitOnMainUntil("IME should be visible after hide/show",
- () -> !activity.isAnimating() && isImeShown(editText));
+ verifyShowBehavior(activity);
}
- public static class TestActivity extends Activity {
+ @Test
+ public void testShowHideWithInputMethodManager_onCreate() {
+ // Show and hide with InputMethodManager at onCreate()
+ Intent intent =
+ createIntent(
+ mWindowFocusFlags,
+ mSoftInputFlags,
+ Arrays.asList(
+ REQUEST_FOCUS_ON_CREATE,
+ INPUT_METHOD_MANAGER_SHOW_ON_CREATE,
+ INPUT_METHOD_MANAGER_HIDE_ON_CREATE));
+ TestActivity activity = TestActivity.start(intent);
- private EditText mEditText;
- private boolean mIsAnimating;
+ // TODO: The Ime is expected to show first and then hide. But show or hide
+ // with InputMethodManager at onCreate() would always fail because the window
+ // has not gained focus, so the actual behavior will be the same as auto-show.
+ // verifyHideBehavior(activity);
+ }
- private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
- new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
- @Override
- public WindowInsetsAnimation.Bounds onStart(WindowInsetsAnimation animation,
- WindowInsetsAnimation.Bounds bounds) {
- mIsAnimating = true;
- return super.onStart(animation, bounds);
- }
+ @Test
+ public void testShowWithInputMethodManager_notRequestFocus() {
+ Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity activity = TestActivity.start(intent);
- @Override
- public void onEnd(WindowInsetsAnimation animation) {
- super.onEnd(animation);
- mIsAnimating = false;
- }
+ // Show InputMethodManager without requesting focus
+ boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+ assertThat(showResult).isFalse();
- @Override
- public WindowInsets onProgress(WindowInsets insets,
- List<WindowInsetsAnimation> runningAnimations) {
- return insets;
- }
- };
+ int windowFlags = activity.getWindow().getAttributes().flags;
+ EditText editText = activity.getEditText();
+ if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+ verifyWindowAndViewFocus(
+ editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false);
+ } else {
+ verifyWindowAndViewFocus(
+ editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+ }
+ // The Ime should always be hidden because view never gains focus.
+ verifyImeIsAlwaysHidden(editText);
+ }
- public static TestActivity start() {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- Intent intent = new Intent()
- .setAction(Intent.ACTION_MAIN)
- .setClass(instrumentation.getContext(), TestActivity.class)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- return (TestActivity) instrumentation.startActivitySync(intent);
+ @Test
+ public void testShowHideWithWindowInsetsController_waitingVisibilityChange() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return;
+ }
+ Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity activity = TestActivity.start(intent);
+ // Request focus after app starts to avoid triggering auto-show behavior.
+ mInstrumentation.runOnMainSync(activity::requestFocus);
+ // Test only once if window flags set to save time.
+ int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS;
+ for (int i = 0; i < iterNum; i++) {
+ String msgPrefix = "Iteration #" + i + " ";
+ Log.i(TAG, msgPrefix + "start");
+ mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+ verifyShowBehavior(activity);
+ mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController);
+ verifyHideBehavior(activity);
+ }
+ }
+
+ @Test
+ public void testShowHideWithWindowInsetsController_waitingAnimationEnd() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return;
+ }
+ Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity activity = TestActivity.start(intent);
+ // Request focus after app starts to avoid triggering auto-show behavior.
+ mInstrumentation.runOnMainSync(activity::requestFocus);
+
+ if (hasUnfocusableWindowFlags(activity)) {
+ return; // Skip to save time.
+ }
+ activity.enableAnimationMonitoring();
+ EditText editText = activity.getEditText();
+ for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+ String msgPrefix = "Iteration #" + i + " ";
+ Log.i(TAG, msgPrefix + "start");
+ mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+ waitOnMainUntil(
+ msgPrefix + "IME should be visible",
+ () -> !activity.isAnimating() && isImeShown(editText));
+
+ mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController);
+ waitOnMainUntil(
+ msgPrefix + "IME should be hidden",
+ () -> !activity.isAnimating() && !isImeShown(editText));
+ }
+ }
+
+ @Test
+ public void testShowHideWithWindowInsetsController_intervalAfterHide() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return;
+ }
+ Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity activity = TestActivity.start(intent);
+ // Request focus after app starts to avoid triggering auto-show behavior.
+ mInstrumentation.runOnMainSync(activity::requestFocus);
+
+ if (hasUnfocusableWindowFlags(activity)) {
+ return; // Skip to save time.
+ }
+ // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
+ List<Integer> intervals = new ArrayList<>();
+ for (int i = 10; i < 100; i += 10) intervals.add(i);
+ for (int i = 100; i < 1000; i += 50) intervals.add(i);
+ for (int intervalMillis : intervals) {
+ String msgPrefix = "Interval = " + intervalMillis + " ";
+ Log.i(TAG, msgPrefix + " start");
+ mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController);
+ SystemClock.sleep(intervalMillis);
+
+ mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+ verifyShowBehavior(activity);
+ }
+ }
+
+ @Test
+ public void testShowHideWithWindowInsetsController_inSameFrame() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return;
+ }
+ Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity activity = TestActivity.start(intent);
+ // Request focus after app starts to avoid triggering auto-show behavior.
+ mInstrumentation.runOnMainSync(activity::requestFocus);
+
+ if (hasUnfocusableWindowFlags(activity)) {
+ return; // Skip to save time.
+ }
+ // hidden -> show -> hide
+ mInstrumentation.runOnMainSync(
+ () -> {
+ Log.i(TAG, "Calling showIme() and hideIme()");
+ activity.showImeWithWindowInsetsController();
+ activity.hideImeWithWindowInsetsController();
+ });
+ // Wait until IMMS / IMS handles messages.
+ SystemClock.sleep(1000);
+ mInstrumentation.waitForIdleSync();
+ // TODO(b/248456059): Ime should be hidden but is shown.
+ // verifyHideBehavior(activity);
+
+ mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+ verifyShowBehavior(activity);
+ mInstrumentation.waitForIdleSync();
+
+ // shown -> hide -> show
+ mInstrumentation.runOnMainSync(
+ () -> {
+ Log.i(TAG, "Calling hideIme() and showIme()");
+ activity.hideImeWithWindowInsetsController();
+ activity.showImeWithWindowInsetsController();
+ });
+ // Wait until IMMS / IMS handles messages.
+ SystemClock.sleep(1000);
+ mInstrumentation.waitForIdleSync();
+ verifyShowBehavior(activity);
+ }
+
+ @Test
+ public void testShowWithWindowInsetsController_onCreate_requestFocus() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return;
+ }
+ // Show with InputMethodManager at onCreate()
+ Intent intent =
+ createIntent(
+ mWindowFocusFlags,
+ mSoftInputFlags,
+ Arrays.asList(
+ REQUEST_FOCUS_ON_CREATE, WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE));
+ TestActivity activity = TestActivity.start(intent);
+
+ verifyShowBehavior(activity);
+ }
+
+ @Test
+ public void testShowWithWindowInsetsController_onCreate_notRequestFocus() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return;
+ }
+ // Show and hide with InputMethodManager at onCreate()
+ Intent intent =
+ createIntent(
+ mWindowFocusFlags,
+ mSoftInputFlags,
+ Collections.singletonList(WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE));
+ TestActivity activity = TestActivity.start(intent);
+
+ // Ime is shown but with a fallback InputConnection
+ verifyShowBehaviorNotRequestFocus(activity);
+ }
+
+ @Test
+ public void testShowWithWindowInsetsController_afterStart_notRequestFocus() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return;
+ }
+ // Show and hide with InputMethodManager at onCreate()
+ Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
+ TestActivity activity = TestActivity.start(intent);
+ mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
+
+ // Ime is shown but with a fallback InputConnection
+ verifyShowBehaviorNotRequestFocus(activity);
+ }
+
+ @Test
+ public void testHideWithWindowInsetsController_onCreate_requestFocus() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return;
+ }
+ // Show and hide with InputMethodManager at onCreate()
+ Intent intent =
+ createIntent(
+ mWindowFocusFlags,
+ mSoftInputFlags,
+ Arrays.asList(
+ REQUEST_FOCUS_ON_CREATE,
+ WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE,
+ WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE));
+ TestActivity activity = TestActivity.start(intent);
+
+ // TODO(b/248456059): Ime should be hidden but is shown.
+ //verifyHideBehavior(activity);
+ }
+
+ @Test
+ public void testScreenOffOn() throws Exception {
+ Intent intent1 =
+ createIntent(
+ mWindowFocusFlags,
+ mSoftInputFlags,
+ Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+ TestActivity activity = TestActivity.start(intent1);
+ // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
+ boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+ assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+
+ Thread.sleep(1000);
+ verifyShowBehavior(activity);
+
+ UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
+
+ if (uiDevice.isScreenOn()) {
+ uiDevice.sleep();
+ }
+ Thread.sleep(1000);
+ if (!uiDevice.isScreenOn()) {
+ uiDevice.wakeUp();
}
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- LinearLayout rootView = new LinearLayout(this);
- rootView.setOrientation(LinearLayout.VERTICAL);
- mEditText = new EditText(this);
- rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
- setContentView(rootView);
- }
+ verifyShowBehavior(activity);
+ }
- public EditText getEditText() {
- return mEditText;
- }
+ @Test
+ public void testRotateScreenWithKeyboardOn() throws Exception {
+ // TODO(b/256739702): Keyboard disappears after rotating screen to landscape mode if
+ // android:configChanges="orientation|screenSize" is not set
+ Intent intent =
+ createIntent(
+ mWindowFocusFlags,
+ mSoftInputFlags,
+ Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
+ TestActivity activity = TestActivity.start(intent);
+ // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
+ boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
+ assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+ Thread.sleep(2000);
+ verifyShowBehavior(activity);
- public void showIme() {
- Log.i(TAG, "TestActivity.showIme");
- mEditText.requestFocus();
- InputMethodManager imm = getSystemService(InputMethodManager.class);
- imm.showSoftInput(mEditText, 0);
- }
+ UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
- public void hideIme() {
- Log.i(TAG, "TestActivity.hideIme");
- InputMethodManager imm = getSystemService(InputMethodManager.class);
- imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
- }
+ uiDevice.freezeRotation();
+ uiDevice.setOrientationRight();
+ uiDevice.waitForIdle();
+ Thread.sleep(1000);
+ Log.i(TAG, "Rotate screen right");
+ assertThat(uiDevice.isNaturalOrientation()).isFalse();
+ verifyShowBehavior(activity);
- public void enableAnimationMonitoring() {
- // Enable WindowInsetsAnimation.
- // Note that this has a side effect of disabling InsetsAnimationThreadControlRunner.
- InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- getWindow().setDecorFitsSystemWindows(false);
- mEditText.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
- });
- }
+ uiDevice.setOrientationLeft();
+ uiDevice.waitForIdle();
+ Thread.sleep(1000);
+ Log.i(TAG, "Rotate screen left");
+ assertThat(uiDevice.isNaturalOrientation()).isFalse();
+ verifyShowBehavior(activity);
- public boolean isAnimating() {
- return mIsAnimating;
+ uiDevice.setOrientationNatural();
+ uiDevice.waitForIdle();
+ uiDevice.unfreezeRotation();
+ }
+
+ private static void verifyShowBehavior(TestActivity activity) {
+ if (hasUnfocusableWindowFlags(activity)) {
+ verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+ return;
+ }
+ EditText editText = activity.getEditText();
+
+ verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+ waitOnMainUntilImeIsShown(editText);
+ }
+
+ private static void verifyHideBehavior(TestActivity activity) {
+ if (hasUnfocusableWindowFlags(activity)) {
+ verifyImeAlwaysHiddenWithWindowFlagSet(activity);
+ return;
+ }
+ EditText editText = activity.getEditText();
+
+ verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+ waitOnMainUntilImeIsHidden(editText);
+ }
+
+ private static void verifyShowBehaviorNotRequestFocus(TestActivity activity) {
+ int windowFlags = activity.getWindow().getAttributes().flags;
+ EditText editText = activity.getEditText();
+
+ if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+ verifyWindowAndViewFocus(
+ editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false);
+ verifyImeIsAlwaysHidden(editText);
+ } else if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+ || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) {
+ verifyWindowAndViewFocus(
+ editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+ verifyImeIsAlwaysHidden(editText);
+ } else {
+ verifyWindowAndViewFocus(
+ editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
+ // Ime is shown but with a fallback InputConnection
+ waitOnMainUntilImeIsShown(editText);
}
}
}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
index b6d462c..e16c915 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -16,17 +16,37 @@
package com.android.inputmethod.stresstest;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
+
import static com.android.compatibility.common.util.SystemUtil.eventually;
import static com.google.common.truth.Truth.assertWithMessage;
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
import android.view.View;
import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import androidx.annotation.Nullable;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.ThrowingRunnable;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -34,28 +54,96 @@
/** Utility methods for IME stress test. */
public final class ImeStressTestUtil {
- private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
- private static final long VERIFY_DURATION = TimeUnit.SECONDS.toMillis(2);
+ private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(3);
- private ImeStressTestUtil() {
+ private ImeStressTestUtil() {}
+
+ private static final int[] WINDOW_FOCUS_FLAGS =
+ new int[] {
+ LayoutParams.FLAG_NOT_FOCUSABLE,
+ LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ LayoutParams.FLAG_LOCAL_FOCUS_MODE
+ };
+
+ private static final int[] SOFT_INPUT_VISIBILITY_FLAGS =
+ new int[] {
+ LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED,
+ LayoutParams.SOFT_INPUT_STATE_UNCHANGED,
+ LayoutParams.SOFT_INPUT_STATE_HIDDEN,
+ LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN,
+ LayoutParams.SOFT_INPUT_STATE_VISIBLE,
+ LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE,
+ };
+
+ private static final int[] SOFT_INPUT_ADJUST_FLAGS =
+ new int[] {
+ LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED,
+ LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+ LayoutParams.SOFT_INPUT_ADJUST_PAN,
+ LayoutParams.SOFT_INPUT_ADJUST_NOTHING
+ };
+
+ public static final String SOFT_INPUT_FLAGS = "soft_input_flags";
+ public static final String WINDOW_FLAGS = "window_flags";
+ public static final String UNFOCUSABLE_VIEW = "unfocusable_view";
+ public static final String REQUEST_FOCUS_ON_CREATE = "request_focus_on_create";
+ public static final String INPUT_METHOD_MANAGER_SHOW_ON_CREATE =
+ "input_method_manager_show_on_create";
+ public static final String INPUT_METHOD_MANAGER_HIDE_ON_CREATE =
+ "input_method_manager_hide_on_create";
+ public static final String WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE =
+ "window_insets_controller_show_on_create";
+ public static final String WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE =
+ "window_insets_controller_hide_on_create";
+
+ /** Parameters for show/hide ime parameterized tests. */
+ public static ArrayList<Object[]> getWindowAndSoftInputFlagParameters() {
+ ArrayList<Object[]> params = new ArrayList<>();
+
+ // Set different window focus flags and keep soft input flags as default values (4 cases)
+ for (int windowFocusFlags : WINDOW_FOCUS_FLAGS) {
+ params.add(
+ new Object[] {
+ windowFocusFlags,
+ LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED,
+ LayoutParams.SOFT_INPUT_ADJUST_RESIZE
+ });
+ }
+ // Set the combinations of different softInputVisibility, softInputAdjustment flags,
+ // keep the window focus flag as default value ( 6 * 4 = 24 cases)
+ for (int softInputVisibility : SOFT_INPUT_VISIBILITY_FLAGS) {
+ for (int softInputAdjust : SOFT_INPUT_ADJUST_FLAGS) {
+ params.add(
+ new Object[] {
+ 0x0 /* No window focus flags */, softInputVisibility, softInputAdjust
+ });
+ }
+ }
+ return params;
}
/** Checks if the IME is shown on the window that the given view belongs to. */
public static boolean isImeShown(View view) {
WindowInsets insets = view.getRootWindowInsets();
+ if (insets == null) {
+ return false;
+ }
return insets.isVisible(WindowInsets.Type.ime());
}
/** Calls the callable on the main thread and returns the result. */
public static <V> V callOnMainSync(Callable<V> callable) {
AtomicReference<V> result = new AtomicReference<>();
- InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- try {
- result.set(callable.call());
- } catch (Exception e) {
- throw new RuntimeException("Exception was thrown", e);
- }
- });
+ InstrumentationRegistry.getInstrumentation()
+ .runOnMainSync(
+ () -> {
+ try {
+ result.set(callable.call());
+ } catch (Exception e) {
+ throw new RuntimeException("Exception was thrown", e);
+ }
+ });
return result.get();
}
@@ -70,15 +158,42 @@
/** Waits until IME is shown, or throws on timeout. */
public static void waitOnMainUntilImeIsShown(View view) {
- eventually(() -> assertWithMessage("IME should be shown").that(
- callOnMainSync(() -> isImeShown(view))).isTrue(), TIMEOUT);
+ eventually(
+ () ->
+ assertWithMessage("IME should be shown")
+ .that(callOnMainSync(() -> isImeShown(view)))
+ .isTrue(),
+ TIMEOUT);
}
/** Waits until IME is hidden, or throws on timeout. */
public static void waitOnMainUntilImeIsHidden(View view) {
- //eventually(() -> assertThat(callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT);
- eventually(() -> assertWithMessage("IME should be hidden").that(
- callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT);
+ eventually(
+ () ->
+ assertWithMessage("IME should be hidden")
+ .that(callOnMainSync(() -> isImeShown(view)))
+ .isFalse(),
+ TIMEOUT);
+ }
+
+ /** Waits until window get focus, or throws on timeout. */
+ public static void waitOnMainUntilWindowGainsFocus(View view) {
+ eventually(
+ () ->
+ assertWithMessage("Window should gain focus")
+ .that(callOnMainSync(view::hasWindowFocus))
+ .isTrue(),
+ TIMEOUT);
+ }
+
+ /** Waits until view get focus, or throws on timeout. */
+ public static void waitOnMainUntilViewGainsFocus(View view) {
+ eventually(
+ () ->
+ assertWithMessage("View should gain focus")
+ .that(callOnMainSync(view::hasFocus))
+ .isTrue(),
+ TIMEOUT);
}
/** Verify IME is always hidden within the given time duration. */
@@ -88,7 +203,27 @@
assertWithMessage("IME should be hidden")
.that(callOnMainSync(() -> isImeShown(view)))
.isFalse(),
- VERIFY_DURATION);
+ TIMEOUT);
+ }
+
+ /** Verify the window never gains focus within the given time duration. */
+ public static void verifyWindowNeverGainsFocus(View view) {
+ always(
+ () ->
+ assertWithMessage("window should never gain focus")
+ .that(callOnMainSync(view::hasWindowFocus))
+ .isFalse(),
+ TIMEOUT);
+ }
+
+ /** Verify the view never gains focus within the given time duration. */
+ public static void verifyViewNeverGainsFocus(View view) {
+ always(
+ () ->
+ assertWithMessage("view should never gain ime focus")
+ .that(callOnMainSync(view::hasFocus))
+ .isFalse(),
+ TIMEOUT);
}
/**
@@ -117,4 +252,232 @@
}
}
}
+
+ public static boolean hasUnfocusableWindowFlags(Activity activity) {
+ int windowFlags = activity.getWindow().getAttributes().flags;
+ return (windowFlags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0
+ || (windowFlags & LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+ || (windowFlags & LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
+ }
+
+ public static void verifyWindowAndViewFocus(
+ View view, boolean expectWindowFocus, boolean expectViewFocus) {
+ if (expectWindowFocus) {
+ waitOnMainUntilWindowGainsFocus(view);
+ } else {
+ verifyWindowNeverGainsFocus(view);
+ }
+ if (expectViewFocus) {
+ waitOnMainUntilViewGainsFocus(view);
+ } else {
+ verifyViewNeverGainsFocus(view);
+ }
+ }
+
+ public static void verifyImeAlwaysHiddenWithWindowFlagSet(TestActivity activity) {
+ int windowFlags = activity.getWindow().getAttributes().flags;
+ View view = activity.getEditText();
+ if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
+ // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus. The IME
+ // will always be hidden even though the view can get focus itself.
+ verifyWindowAndViewFocus(view, /*expectWindowFocus*/ false, /*expectViewFocus*/ true);
+ verifyImeIsAlwaysHidden(view);
+ } else if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
+ || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) {
+ // When FLAG_ALT_FOCUSABLE_IM or FLAG_LOCAL_FOCUS_MODE is set, the view can gain both
+ // window focus and view focus but not IME focus. The IME will always be hidden.
+ verifyWindowAndViewFocus(view, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
+ verifyImeIsAlwaysHidden(view);
+ }
+ }
+
+ /** Activity to help test show/hide behavior of IME. */
+ public static class TestActivity extends Activity {
+ private static final String TAG = "ImeStressTestUtil.TestActivity";
+ private EditText mEditText;
+ private boolean mIsAnimating;
+
+ private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
+ new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+ @Override
+ public WindowInsetsAnimation.Bounds onStart(
+ WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds) {
+ mIsAnimating = true;
+ return super.onStart(animation, bounds);
+ }
+
+ @Override
+ public void onEnd(WindowInsetsAnimation animation) {
+ super.onEnd(animation);
+ mIsAnimating = false;
+ }
+
+ @Override
+ public WindowInsets onProgress(
+ WindowInsets insets, List<WindowInsetsAnimation> runningAnimations) {
+ return insets;
+ }
+ };
+
+ /** Create intent with extras. */
+ public static Intent createIntent(
+ int windowFlags, int softInputFlags, List<String> extras) {
+ Intent intent =
+ new Intent()
+ .putExtra(WINDOW_FLAGS, windowFlags)
+ .putExtra(SOFT_INPUT_FLAGS, softInputFlags);
+ for (String extra : extras) {
+ intent.putExtra(extra, true);
+ }
+ return intent;
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.i(TAG, "onCreate()");
+ boolean isUnfocusableView = getIntent().getBooleanExtra(UNFOCUSABLE_VIEW, false);
+ boolean requestFocus = getIntent().getBooleanExtra(REQUEST_FOCUS_ON_CREATE, false);
+ int softInputFlags = getIntent().getIntExtra(SOFT_INPUT_FLAGS, 0);
+ int windowFlags = getIntent().getIntExtra(WINDOW_FLAGS, 0);
+ boolean showWithInputMethodManagerOnCreate =
+ getIntent().getBooleanExtra(INPUT_METHOD_MANAGER_SHOW_ON_CREATE, false);
+ boolean hideWithInputMethodManagerOnCreate =
+ getIntent().getBooleanExtra(INPUT_METHOD_MANAGER_HIDE_ON_CREATE, false);
+ boolean showWithWindowInsetsControllerOnCreate =
+ getIntent().getBooleanExtra(WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE, false);
+ boolean hideWithWindowInsetsControllerOnCreate =
+ getIntent().getBooleanExtra(WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE, false);
+
+ getWindow().addFlags(windowFlags);
+ getWindow().setSoftInputMode(softInputFlags);
+
+ LinearLayout rootView = new LinearLayout(this);
+ rootView.setOrientation(LinearLayout.VERTICAL);
+ mEditText = new EditText(this);
+ if (isUnfocusableView) {
+ mEditText.setFocusableInTouchMode(false);
+ }
+ rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ setContentView(rootView);
+
+ if (requestFocus) {
+ requestFocus();
+ }
+ if (showWithInputMethodManagerOnCreate) {
+ showImeWithInputMethodManager();
+ }
+ if (hideWithInputMethodManagerOnCreate) {
+ hideImeWithInputMethodManager();
+ }
+ if (showWithWindowInsetsControllerOnCreate) {
+ showImeWithWindowInsetsController();
+ }
+ if (hideWithWindowInsetsControllerOnCreate) {
+ hideImeWithWindowInsetsController();
+ }
+ }
+
+ /** Show IME with InputMethodManager. */
+ public boolean showImeWithInputMethodManager() {
+ boolean showResult =
+ getInputMethodManager()
+ .showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
+ if (showResult) {
+ Log.i(TAG, "IMM#showSoftInput successfully");
+ } else {
+ Log.i(TAG, "IMM#showSoftInput failed");
+ }
+ return showResult;
+ }
+
+ /** Hide IME with InputMethodManager. */
+ public boolean hideImeWithInputMethodManager() {
+ boolean hideResult =
+ getInputMethodManager().hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
+ if (hideResult) {
+ Log.i(TAG, "IMM#hideSoftInput successfully");
+ } else {
+ Log.i(TAG, "IMM#hideSoftInput failed");
+ }
+ return hideResult;
+ }
+
+ /** Show IME with WindowInsetsController */
+ public void showImeWithWindowInsetsController() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return;
+ }
+ Log.i(TAG, "showImeWithWIC()");
+ WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
+ assertWithMessage("WindowInsetsController shouldn't be null.")
+ .that(windowInsetsController)
+ .isNotNull();
+ windowInsetsController.show(WindowInsets.Type.ime());
+ }
+
+ /** Hide IME with WindowInsetsController. */
+ public void hideImeWithWindowInsetsController() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ return;
+ }
+ Log.i(TAG, "hideImeWithWIC()");
+ WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
+ assertWithMessage("WindowInsetsController shouldn't be null.")
+ .that(windowInsetsController)
+ .isNotNull();
+ windowInsetsController.hide(WindowInsets.Type.ime());
+ }
+
+ private InputMethodManager getInputMethodManager() {
+ return getSystemService(InputMethodManager.class);
+ }
+
+ public EditText getEditText() {
+ return mEditText;
+ }
+
+ /** Start TestActivity with intent. */
+ public static TestActivity start(Intent intent) {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ intent.setAction(Intent.ACTION_MAIN)
+ .setClass(instrumentation.getContext(), TestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ return (TestActivity) instrumentation.startActivitySync(intent);
+ }
+
+ /** Start the second TestActivity with intent. */
+ public TestActivity startSecondTestActivity(Intent intent) {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ intent.setClass(TestActivity.this, TestActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return (TestActivity) instrumentation.startActivitySync(intent);
+ }
+
+ public void enableAnimationMonitoring() {
+ // Enable WindowInsetsAnimation.
+ // Note that this has a side effect of disabling InsetsAnimationThreadControlRunner.
+ InstrumentationRegistry.getInstrumentation()
+ .runOnMainSync(
+ () -> {
+ getWindow().setDecorFitsSystemWindows(false);
+ mEditText.setWindowInsetsAnimationCallback(
+ mWindowInsetsAnimationCallback);
+ });
+ }
+
+ public boolean isAnimating() {
+ return mIsAnimating;
+ }
+
+ public void requestFocus() {
+ boolean requestFocusResult = getEditText().requestFocus();
+ if (requestFocusResult) {
+ Log.i(TAG, "Request focus successfully");
+ } else {
+ Log.i(TAG, "Request focus failed");
+ }
+ }
+ }
}