diff options
author | 2022-11-01 09:56:42 +0800 | |
---|---|---|
committer | 2022-11-22 13:27:29 +0800 | |
commit | 19e01a7c8ba203e90eb735523ff2c980507c3f9e (patch) | |
tree | cefad9ac7be4e0cc971ac195e9c95d2325c27282 | |
parent | 32ce057dd1c301a999ee7bcdbfa7c5785cf86143 (diff) |
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
5 files changed, 1218 insertions, 243 deletions
diff --git a/tests/InputMethodStressTest/AndroidManifest.xml b/tests/InputMethodStressTest/AndroidManifest.xml index f5fe8f2e8e76..2d183bcb81fd 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 ad07205ab02d..06e2ce84eb0f 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 92ea029bf469..8e4ecf1b2a26 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.Test; 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() { - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - int flags = mSoftInputVisibility | mSoftInputAdjustment; - if (mSoftInputIsForwardNavigation) { - flags |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; + 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 = - 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); + createIntent( + mWindowFocusFlags, + mSoftInputFlags, + Collections.singletonList(UNFOCUSABLE_VIEW)); + TestActivity activity = TestActivity.start(intent); 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); + 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 { - // 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*/ 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 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; + } + + 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(); + + 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; + } + } + + 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(); + + 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 static class TestActivity extends Activity { - private EditText mEditText; - - @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(); + 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); } + } - public EditText getEditText() { - return mEditText; + 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 8419276f4406..82acfb646d19 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 static com.google.common.truth.Truth.assertThat; -import android.app.Activity; 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); + } + + @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); + + // 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); + } + + @Test + public void testShowWithInputMethodManager_notRequestFocus() { + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent); + + // Show InputMethodManager without requesting focus + boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager); + assertThat(showResult).isFalse(); + + 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); + } + + @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); + } } - public static class TestActivity extends 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); - private EditText mEditText; - private boolean mIsAnimating; + 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(); - 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); - } + // 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); + } - @Override - public void onEnd(WindowInsetsAnimation animation) { - super.onEnd(animation); - mIsAnimating = false; - } + @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); - @Override - public WindowInsets onProgress(WindowInsets insets, - List<WindowInsetsAnimation> runningAnimations) { - return insets; - } - }; + verifyShowBehavior(activity); + } - 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 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); + } - @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); + @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); + } - public EditText getEditText() { - return mEditText; + @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); + } - public void showIme() { - Log.i(TAG, "TestActivity.showIme"); - mEditText.requestFocus(); - InputMethodManager imm = getSystemService(InputMethodManager.class); - imm.showSoftInput(mEditText, 0); + @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(); } - public void hideIme() { - Log.i(TAG, "TestActivity.hideIme"); - InputMethodManager imm = getSystemService(InputMethodManager.class); - imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); + verifyShowBehavior(activity); + } + + @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); + + UiDevice uiDevice = UiDevice.getInstance(mInstrumentation); + + uiDevice.freezeRotation(); + uiDevice.setOrientationRight(); + uiDevice.waitForIdle(); + Thread.sleep(1000); + Log.i(TAG, "Rotate screen right"); + assertThat(uiDevice.isNaturalOrientation()).isFalse(); + verifyShowBehavior(activity); + + uiDevice.setOrientationLeft(); + uiDevice.waitForIdle(); + Thread.sleep(1000); + Log.i(TAG, "Rotate screen left"); + assertThat(uiDevice.isNaturalOrientation()).isFalse(); + verifyShowBehavior(activity); + + uiDevice.setOrientationNatural(); + uiDevice.waitForIdle(); + uiDevice.unfreezeRotation(); + } + + private static void verifyShowBehavior(TestActivity activity) { + if (hasUnfocusableWindowFlags(activity)) { + verifyImeAlwaysHiddenWithWindowFlagSet(activity); + return; } + EditText editText = activity.getEditText(); - public void enableAnimationMonitoring() { - // Enable WindowInsetsAnimation. - // Note that this has a side effect of disabling InsetsAnimationThreadControlRunner. - InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - getWindow().setDecorFitsSystemWindows(false); - mEditText.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback); - }); + 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(); - public boolean isAnimating() { - return mIsAnimating; + 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 b6d462c6203d..e16c9152df3e 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 @@ import java.util.concurrent.atomic.AtomicReference; /** 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 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"; - private ImeStressTestUtil() { + /** 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 @@ public final class ImeStressTestUtil { /** 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 @@ public final class ImeStressTestUtil { 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 final class ImeStressTestUtil { } } } + + 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"); + } + } + } } |