/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.inputmethod.stresstest; 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.requestFocusAndVerify; 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.waitOnMainUntilImeIsShown; import static com.google.common.truth.Truth.assertThat; import android.app.Instrumentation; import android.content.Intent; import android.content.res.Configuration; 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 androidx.test.platform.app.InstrumentationRegistry; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.util.Collections; import java.util.List; /** * 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(order = 0) public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule(); @Rule(order = 1) public ImeStressTestRule mImeStressTestRule = new ImeStressTestRule(true /* useSimpleTestIme */); @Rule(order = 2) public ScreenCaptureRule mScreenCaptureRule = new ScreenCaptureRule("/sdcard/InputMethodStressTest"); @Parameterized.Parameters( name = "windowFocusFlags={0}, softInputVisibility={1}, softInputAdjustment={2}") public static List<Object[]> windowAndSoftInputFlagParameters() { return getWindowAndSoftInputFlagParameters(); } private final int mSoftInputFlags; private final int mWindowFocusFlags; private final Instrumentation mInstrumentation; private final boolean mIsLargeScreen; public AutoShowTest(int windowFocusFlags, int softInputVisibility, int softInputAdjustment) { mSoftInputFlags = softInputVisibility | softInputAdjustment; mWindowFocusFlags = windowFocusFlags; mInstrumentation = InstrumentationRegistry.getInstrumentation(); mIsLargeScreen = mInstrumentation.getContext().getResources() .getConfiguration().isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); } /** * 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. callOnMainSync(firstActivity::showImeWithInputMethodManager); 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 requestFocusAndVerify(firstActivity); 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 requestFocusAndVerify(activity); // 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 callOnMainSync(secondActivity::showImeWithInputMethodManager); 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 requestFocusAndVerify(activity); // 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); } private void verifyAutoShowBehavior_forwardWithKeyboardOff(TestActivity activity) { 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) || mIsLargeScreen) { // 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 or running on a large screen device. waitOnMainUntilImeIsShown(editText); } else { verifyImeIsAlwaysHidden(editText); } break; } default: break; } } private 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) || mIsLargeScreen) { // 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 or // running on a large screen device. waitOnMainUntilImeIsShown(editText); } else { verifyImeIsAlwaysHidden(editText); } break; } default: break; } } 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); } } }