diff options
4 files changed, 199 insertions, 19 deletions
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java index 16a98454d135..898658e759c0 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java @@ -24,6 +24,7 @@ import android.app.Instrumentation; import android.content.Context; import android.content.res.Configuration; import android.os.RemoteException; +import android.provider.Settings; import android.support.test.uiautomator.By; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject2; @@ -31,7 +32,6 @@ import android.support.test.uiautomator.Until; import android.util.Log; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.MediumTest; @@ -40,6 +40,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper; import com.android.apps.inputmethod.simpleime.testing.TestActivity; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -54,15 +55,19 @@ public class InputMethodServiceTest { private static final String INPUT_METHOD_SERVICE_NAME = ".SimpleInputMethodService"; private static final String EDIT_TEXT_DESC = "Input box"; private static final long TIMEOUT_IN_SECONDS = 3; - - public Instrumentation mInstrumentation; - public UiDevice mUiDevice; - public Context mContext; - public String mTargetPackageName; - public TestActivity mActivity; - public EditText mEditText; - public InputMethodServiceWrapper mInputMethodService; - public String mInputMethodId; + private static final String ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD = + "settings put secure show_ime_with_hard_keyboard 1"; + private static final String DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD = + "settings put secure show_ime_with_hard_keyboard 0"; + + private Instrumentation mInstrumentation; + private UiDevice mUiDevice; + private Context mContext; + private String mTargetPackageName; + private TestActivity mActivity; + private InputMethodServiceWrapper mInputMethodService; + private String mInputMethodId; + private boolean mShowImeWithHardKeyboardEnabled; @Before public void setUp() throws Exception { @@ -73,7 +78,9 @@ public class InputMethodServiceTest { mInputMethodId = getInputMethodId(); prepareIme(); prepareEditor(); - + mInstrumentation.waitForIdleSync(); + mUiDevice.freezeRotation(); + mUiDevice.setOrientationNatural(); // Waits for input binding ready. eventually( () -> { @@ -85,6 +92,28 @@ public class InputMethodServiceTest { assertThat(mInputMethodService.getCurrentInputStarted()).isTrue(); assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse(); }); + // Save the original value of show_ime_with_hard_keyboard in Settings. + mShowImeWithHardKeyboardEnabled = Settings.Secure.getInt( + mInputMethodService.getContentResolver(), + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0; + // Disable showing Ime with hard keyboard because it is the precondition the for most test + // cases + if (mShowImeWithHardKeyboardEnabled) { + executeShellCommand(DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD); + } + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_NOKEYS; + mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = + Configuration.HARDKEYBOARDHIDDEN_YES; + } + + @After + public void tearDown() throws Exception { + mUiDevice.unfreezeRotation(); + executeShellCommand("ime disable " + mInputMethodId); + // Change back the original value of show_ime_with_hard_keyboard in Settings. + executeShellCommand(mShowImeWithHardKeyboardEnabled ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD + : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD); } @Test @@ -107,8 +136,6 @@ public class InputMethodServiceTest { true /* inputViewStarted */); // Triggers to hide IME via public API. - // TODO(b/242838873): investigate why WIC#hide(ime()) does not work, likely related to - // triggered from IME process. verifyInputViewStatusOnMainSync( () -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(), false /* inputViewStarted */); @@ -145,6 +172,141 @@ public class InputMethodServiceTest { false /* inputViewStarted */); } + @Test + public void testOnEvaluateInputViewShown_showImeWithHardKeyboard() throws Exception { + executeShellCommand(ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD); + mInstrumentation.waitForIdleSync(); + + // Simulate connecting a hard keyboard + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_QWERTY; + mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = + Configuration.HARDKEYBOARDHIDDEN_NO; + + eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue()); + } + + @Test + public void testOnEvaluateInputViewShown_disableShowImeWithHardKeyboard() { + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_QWERTY; + mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = + Configuration.HARDKEYBOARDHIDDEN_NO; + eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isFalse()); + + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_NOKEYS; + eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue()); + + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_QWERTY; + mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = + Configuration.HARDKEYBOARDHIDDEN_YES; + eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue()); + } + + @Test + public void testShowSoftInput_disableShowImeWithHardKeyboard() throws Exception { + // Simulate connecting a hard keyboard + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_QWERTY; + mInputMethodService.getResources().getConfiguration().hardKeyboardHidden = + Configuration.HARDKEYBOARDHIDDEN_NO; + // When InputMethodService#onEvaluateInputViewShown() returns false, the Ime should not be + // shown no matter what the show flag is. + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(), + false /* inputViewStarted */); + verifyInputViewStatusOnMainSync( + () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(), + false /* inputViewStarted */); + } + + @Test + public void testShowSoftInputExplicitly() throws Exception { + // When InputMethodService#onEvaluateInputViewShown() returns true and flag is EXPLICIT, the + // Ime should be shown. + verifyInputViewStatusOnMainSync( + () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(), + true /* inputViewStarted */); + } + + @Test + public void testShowSoftInputImplicitly() throws Exception { + // When InputMethodService#onEvaluateInputViewShown() returns true and flag is IMPLICIT, the + // Ime should be shown. + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(), + true /* inputViewStarted */); + } + + @Test + public void testShowSoftInputImplicitly_fullScreenMode() throws Exception { + // When keyboard is off, InputMethodService#onEvaluateInputViewShown returns true, flag is + // IMPLICIT and InputMethodService#onEvaluateFullScreenMode returns true, the Ime should not + // be shown. + setOrientation(2); + eventually(() -> assertThat(mUiDevice.isNaturalOrientation()).isFalse()); + // Wait for the TestActivity to be recreated + eventually(() -> + assertThat(TestActivity.getLastCreatedInstance()).isNotEqualTo(mActivity)); + // Get the new TestActivity + mActivity = TestActivity.getLastCreatedInstance(); + assertThat(mActivity).isNotNull(); + InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); + // Wait for the new EditText to be served by InputMethodManager + eventually(() -> + assertThat(imm.hasActiveInputConnection(mActivity.getEditText())).isTrue()); + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(), + false /* inputViewStarted */); + } + + @Test + public void testShowSoftInputImplicitly_withHardKeyboard() throws Exception { + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_QWERTY; + // When connecting to a hard keyboard and the flag is IMPLICIT, the Ime should not be shown. + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(), + false /* inputViewStarted */); + } + + @Test + public void testConfigurationChanged_withKeyboardShownExplicitly() throws InterruptedException { + verifyInputViewStatusOnMainSync( + () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(), + true /* inputViewStarted */); + // Simulate a fake configuration change to avoid triggering the recreation of TestActivity. + mInputMethodService.getResources().getConfiguration().orientation = + Configuration.ORIENTATION_LANDSCAPE; + verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged( + mInputMethodService.getResources().getConfiguration()), + true /* inputViewStarted */); + } + + @Test + public void testConfigurationChanged_withKeyboardShownImplicitly() throws InterruptedException { + verifyInputViewStatusOnMainSync(() -> assertThat( + mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(), + true /* inputViewStarted */); + // Simulate a fake configuration change to avoid triggering the recreation of TestActivity. + mInputMethodService.getResources().getConfiguration().orientation = + Configuration.ORIENTATION_LANDSCAPE; + mInputMethodService.getResources().getConfiguration().keyboard = + Configuration.KEYBOARD_QWERTY; + + // Normally, IMS#onFinishInputView will be called when finishing the input view by the user. + // But if IMS#hideWindow is called when receiving a new configuration change, we don't + // expect that it's user-driven to finish the lifecycle of input view with + // IMS#onFinishInputView, because the input view will be re-initialized according to the + // last mShowSoftRequested state. So in this case we treat the input view is still alive. + verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged( + mInputMethodService.getResources().getConfiguration()), + true /* inputViewStarted */); + assertThat(mInputMethodService.isInputViewShown()).isFalse(); + } + private void verifyInputViewStatus(Runnable runnable, boolean inputViewStarted) throws InterruptedException { verifyInputViewStatusInternal(runnable, inputViewStarted, false /*runOnMainSync*/); @@ -184,8 +346,6 @@ public class InputMethodServiceTest { Log.i(TAG, "Set orientation right"); verifyFullscreenMode(() -> setOrientation(2), false /* orientationPortrait */); - - mUiDevice.unfreezeRotation(); } private void setOrientation(int orientation) { @@ -249,7 +409,6 @@ public class InputMethodServiceTest { private void prepareEditor() { mActivity = TestActivity.start(mInstrumentation); - mEditText = mActivity.mEditText; Log.i(TAG, "Finish preparing activity with editor."); } diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp index ef50476c2928..8d0e0c4260e8 100644 --- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp +++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp @@ -58,5 +58,8 @@ android_library { srcs: [ "src/com/android/apps/inputmethod/simpleime/testing/*.java", ], + static_libs: [ + "androidx.annotation_annotation", + ], sdk_version: "current", } diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml index 802caf132db5..996322de2c5e 100644 --- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml +++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml @@ -20,6 +20,8 @@ <uses-sdk android:targetSdkVersion="31" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <application android:debuggable="true" android:label="@string/app_name"> <service diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java index 0eec7e629d87..73b45019e311 100644 --- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java +++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java @@ -31,6 +31,10 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.LinearLayout; +import androidx.annotation.Nullable; + +import java.lang.ref.WeakReference; + /** * A special activity for testing purpose. * @@ -40,6 +44,8 @@ import android.widget.LinearLayout; */ public class TestActivity extends Activity { private static final String TAG = "TestActivity"; + private static WeakReference<TestActivity> sLastCreatedInstance = + new WeakReference<>(null); /** * Start a new test activity with an editor and wait for it to begin running before returning. @@ -57,10 +63,15 @@ public class TestActivity extends Activity { return (TestActivity) instrumentation.startActivitySync(intent); } - public EditText mEditText; + private EditText mEditText; + + public EditText getEditText() { + return mEditText; + } @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); LinearLayout rootView = new LinearLayout(this); mEditText = new EditText(this); @@ -68,14 +79,19 @@ public class TestActivity extends Activity { rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); setContentView(rootView); mEditText.requestFocus(); - super.onCreate(savedInstanceState); + sLastCreatedInstance = new WeakReference<>(this); + } + + /** Get the last created TestActivity instance. */ + public static @Nullable TestActivity getLastCreatedInstance() { + return sLastCreatedInstance.get(); } /** Shows soft keyboard via InputMethodManager. */ public boolean showImeWithInputMethodManager(int flags) { InputMethodManager imm = getSystemService(InputMethodManager.class); boolean result = imm.showSoftInput(mEditText, flags); - Log.i(TAG, "hideIme() via InputMethodManager, result=" + result); + Log.i(TAG, "showIme() via InputMethodManager, result=" + result); return result; } |