Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4d653ef3 authored by czq's avatar czq
Browse files

Add tests for IMS#showSoftInput and IMS#onConfigurationChanged

Add more test cases for Ime visibility under different flags and system configs of IMS#showSoftInput and IMS#onConfigurationChanged.

Bug: 242838873
Bug: 240359838

Test: atest com.android.inputmethodservice.InputMethodServiceTest
Change-Id: I66ce0a525e6867a783da20188f0d657a3e70d90d
parent 4d814d3c
Loading
Loading
Loading
Loading
+175 −16
Original line number Original line Diff line number Diff line
@@ -24,6 +24,7 @@ import android.app.Instrumentation;
import android.content.Context;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Configuration;
import android.os.RemoteException;
import android.os.RemoteException;
import android.provider.Settings;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.UiObject2;
@@ -31,7 +32,6 @@ import android.support.test.uiautomator.Until;
import android.util.Log;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;


import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
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.ims.InputMethodServiceWrapper;
import com.android.apps.inputmethod.simpleime.testing.TestActivity;
import com.android.apps.inputmethod.simpleime.testing.TestActivity;


import org.junit.After;
import org.junit.Before;
import org.junit.Before;
import org.junit.Test;
import org.junit.Test;
import org.junit.runner.RunWith;
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 INPUT_METHOD_SERVICE_NAME = ".SimpleInputMethodService";
    private static final String EDIT_TEXT_DESC = "Input box";
    private static final String EDIT_TEXT_DESC = "Input box";
    private static final long TIMEOUT_IN_SECONDS = 3;
    private static final long TIMEOUT_IN_SECONDS = 3;

    private static final String ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
    public Instrumentation mInstrumentation;
            "settings put secure show_ime_with_hard_keyboard 1";
    public UiDevice mUiDevice;
    private static final String DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
    public Context mContext;
            "settings put secure show_ime_with_hard_keyboard 0";
    public String mTargetPackageName;

    public TestActivity mActivity;
    private Instrumentation mInstrumentation;
    public EditText mEditText;
    private UiDevice mUiDevice;
    public InputMethodServiceWrapper mInputMethodService;
    private Context mContext;
    public String mInputMethodId;
    private String mTargetPackageName;
    private TestActivity mActivity;
    private InputMethodServiceWrapper mInputMethodService;
    private String mInputMethodId;
    private boolean mShowImeWithHardKeyboardEnabled;


    @Before
    @Before
    public void setUp() throws Exception {
    public void setUp() throws Exception {
@@ -73,7 +78,9 @@ public class InputMethodServiceTest {
        mInputMethodId = getInputMethodId();
        mInputMethodId = getInputMethodId();
        prepareIme();
        prepareIme();
        prepareEditor();
        prepareEditor();

        mInstrumentation.waitForIdleSync();
        mUiDevice.freezeRotation();
        mUiDevice.setOrientationNatural();
        // Waits for input binding ready.
        // Waits for input binding ready.
        eventually(
        eventually(
                () -> {
                () -> {
@@ -85,6 +92,28 @@ public class InputMethodServiceTest {
                    assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
                    assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
                    assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse();
                    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
    @Test
@@ -107,8 +136,6 @@ public class InputMethodServiceTest {
                true /* inputViewStarted */);
                true /* inputViewStarted */);


        // Triggers to hide IME via public API.
        // 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(
        verifyInputViewStatusOnMainSync(
                () -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(),
                () -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(),
                false /* inputViewStarted */);
                false /* inputViewStarted */);
@@ -145,6 +172,141 @@ public class InputMethodServiceTest {
                false /* inputViewStarted */);
                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)
    private void verifyInputViewStatus(Runnable runnable, boolean inputViewStarted)
            throws InterruptedException {
            throws InterruptedException {
        verifyInputViewStatusInternal(runnable, inputViewStarted, false /*runOnMainSync*/);
        verifyInputViewStatusInternal(runnable, inputViewStarted, false /*runOnMainSync*/);
@@ -184,8 +346,6 @@ public class InputMethodServiceTest {


        Log.i(TAG, "Set orientation right");
        Log.i(TAG, "Set orientation right");
        verifyFullscreenMode(() -> setOrientation(2), false /* orientationPortrait */);
        verifyFullscreenMode(() -> setOrientation(2), false /* orientationPortrait */);

        mUiDevice.unfreezeRotation();
    }
    }


    private void setOrientation(int orientation) {
    private void setOrientation(int orientation) {
@@ -249,7 +409,6 @@ public class InputMethodServiceTest {


    private void prepareEditor() {
    private void prepareEditor() {
        mActivity = TestActivity.start(mInstrumentation);
        mActivity = TestActivity.start(mInstrumentation);
        mEditText = mActivity.mEditText;
        Log.i(TAG, "Finish preparing activity with editor.");
        Log.i(TAG, "Finish preparing activity with editor.");
    }
    }


+3 −0
Original line number Original line Diff line number Diff line
@@ -58,5 +58,8 @@ android_library {
    srcs: [
    srcs: [
        "src/com/android/apps/inputmethod/simpleime/testing/*.java",
        "src/com/android/apps/inputmethod/simpleime/testing/*.java",
    ],
    ],
    static_libs: [
        "androidx.annotation_annotation",
    ],
    sdk_version: "current",
    sdk_version: "current",
}
}
+2 −0
Original line number Original line Diff line number Diff line
@@ -20,6 +20,8 @@


    <uses-sdk android:targetSdkVersion="31" />
    <uses-sdk android:targetSdkVersion="31" />


    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />

    <application android:debuggable="true"
    <application android:debuggable="true"
                 android:label="@string/app_name">
                 android:label="@string/app_name">
        <service
        <service
+19 −3
Original line number Original line Diff line number Diff line
@@ -31,6 +31,10 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.LinearLayout;


import androidx.annotation.Nullable;

import java.lang.ref.WeakReference;

/**
/**
 * A special activity for testing purpose.
 * A special activity for testing purpose.
 *
 *
@@ -40,6 +44,8 @@ import android.widget.LinearLayout;
 */
 */
public class TestActivity extends Activity {
public class TestActivity extends Activity {
    private static final String TAG = "TestActivity";
    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.
     * 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);
        return (TestActivity) instrumentation.startActivitySync(intent);
    }
    }


    public EditText mEditText;
    private EditText mEditText;

    public EditText getEditText() {
        return mEditText;
    }


    @Override
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
        LinearLayout rootView = new LinearLayout(this);
        LinearLayout rootView = new LinearLayout(this);
        mEditText = new EditText(this);
        mEditText = new EditText(this);
@@ -68,14 +79,19 @@ public class TestActivity extends Activity {
        rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
        rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
        setContentView(rootView);
        setContentView(rootView);
        mEditText.requestFocus();
        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. */
    /** Shows soft keyboard via InputMethodManager. */
    public boolean showImeWithInputMethodManager(int flags) {
    public boolean showImeWithInputMethodManager(int flags) {
        InputMethodManager imm = getSystemService(InputMethodManager.class);
        InputMethodManager imm = getSystemService(InputMethodManager.class);
        boolean result = imm.showSoftInput(mEditText, flags);
        boolean result = imm.showSoftInput(mEditText, flags);
        Log.i(TAG, "hideIme() via InputMethodManager, result=" + result);
        Log.i(TAG, "showIme() via InputMethodManager, result=" + result);
        return result;
        return result;
    }
    }