Merge "Add tests for IMS#showSoftInput and IMS#onConfigurationChanged"

This commit is contained in:
Ziqi Chen
2023-01-07 15:44:16 +00:00
committed by Android (Google) Code Review
4 changed files with 198 additions and 18 deletions

View File

@@ -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;
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";
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 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.");
}

View File

@@ -58,5 +58,8 @@ android_library {
srcs: [
"src/com/android/apps/inputmethod/simpleime/testing/*.java",
],
static_libs: [
"androidx.annotation_annotation",
],
sdk_version: "current",
}

View File

@@ -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

View File

@@ -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;
}