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

Commit eeccf514 authored by Cosmin Băieș's avatar Cosmin Băieș
Browse files

Cleanup InputMethodServiceTest and SimpleIMS

This cleans up the InputMethodServiceTest code, as well as SimpleIMS and
its TestActivity, by clarifying method/field names, adding nullability
annotations, and introducing assertion messages.

Additionally removed the return value from showing/hiding the IME via
the InsetsController APIs, as these were always returning true.
Extracted separate methods for checking whether there is a navigation
bar, and setting the IME NavButtonFlags manually. Ensured the
countDownLatchForTesting is reset after usage. Explicilty handled
exceptions by failing the test with a clear message. Added the missing
testShowHideKeyboard_byInputMethodManager test. Removed an incorrect
assertion from testImeSwitchButtonLongClick.

Flag: EXEMPT testfix
Bug: 394328311
Test: atest InputMethodServiceTest
Change-Id: Id4aaa987114d4a90213f20a2e0895776b4eab0a5
parent 8c3f859c
Loading
Loading
Loading
Loading
+368 −287

File changed.

Preview size limit exceeded, changes collapsed.

+12 −15
Original line number Diff line number Diff line
@@ -23,13 +23,15 @@ import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;

import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper;

/** The {@link InputMethodService} implementation for SimpeTestIme app. */
public class SimpleInputMethodService extends InputMethodServiceWrapper {
/** The {@link InputMethodService} implementation for SimpleTestIme app. */
public final class SimpleInputMethodService extends InputMethodServiceWrapper {

    private static final String TAG = "SimpleIMS";

    private FrameLayout mInputView;
@@ -45,23 +47,18 @@ public class SimpleInputMethodService extends InputMethodServiceWrapper {
    public void onStartInputView(EditorInfo info, boolean restarting) {
        super.onStartInputView(info, restarting);
        mInputView.removeAllViews();
        SimpleKeyboard keyboard = new SimpleKeyboard(this, R.layout.qwerty_10_9_9);
        final var keyboard = new SimpleKeyboard(this, R.layout.qwerty_10_9_9);
        mInputView.addView(keyboard.inflateKeyboardView(LayoutInflater.from(this), mInputView));
    }

    void handle(String data, int keyboardState) {
        InputConnection inputConnection = getCurrentInputConnection();
        Integer keyCode = KeyCodeConstants.KEY_NAME_TO_CODE_MAP.get(data);
    void handleKeyPress(@NonNull String keyCodeName, int keyboardState) {
        final Integer keyCode = KeyCodeConstants.KEY_NAME_TO_CODE_MAP.get(keyCodeName);
        Log.v(TAG, "keyCode: " + keyCode);
        if (keyCode != null) {
            inputConnection.sendKeyEvent(
                    new KeyEvent(
                            SystemClock.uptimeMillis(),
                            SystemClock.uptimeMillis(),
                            KeyEvent.ACTION_DOWN,
                            keyCode,
                            0,
                            KeyCodeConstants.isAlphaKeyCode(keyCode) ? keyboardState : 0));
            final var downTime = SystemClock.uptimeMillis();
            getCurrentInputConnection().sendKeyEvent(new KeyEvent(downTime, downTime,
                    KeyEvent.ACTION_DOWN, keyCode, 0 /* repeat */,
                    KeyCodeConstants.isAlphaKeyCode(keyCode) ? keyboardState : 0) /* metaState */);
        }
    }
}
+56 −50
Original line number Diff line number Diff line
@@ -25,12 +25,15 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/** Controls the visible virtual keyboard view. */
final class SimpleKeyboard {

    private static final String TAG = "SimpleKeyboard";

    private static final int[] SOFT_KEY_IDS =
            new int[] {
    private static final int[] SOFT_KEY_IDS = new int[]{
            R.id.key_pos_0_0,
            R.id.key_pos_0_1,
            R.id.key_pos_0_2,
@@ -66,19 +69,21 @@ final class SimpleKeyboard {
            R.id.key_pos_enter,
    };

    @NonNull
    private final SimpleInputMethodService mSimpleInputMethodService;
    private final int mViewResId;
    private final SparseArray<TextView> mSoftKeyViews = new SparseArray<>();
    private View mKeyboardView;
    private int mKeyboardState;

    SimpleKeyboard(SimpleInputMethodService simpleInputMethodService, int viewResId) {
        this.mSimpleInputMethodService = simpleInputMethodService;
        this.mViewResId = viewResId;
        this.mKeyboardState = 0;
    SimpleKeyboard(@NonNull SimpleInputMethodService simpleInputMethodService, int viewResId) {
        mSimpleInputMethodService = simpleInputMethodService;
        mViewResId = viewResId;
        mKeyboardState = 0;
    }

    View inflateKeyboardView(LayoutInflater inflater, ViewGroup inputView) {
    @NonNull
    View inflateKeyboardView(@NonNull LayoutInflater inflater, @NonNull ViewGroup inputView) {
        mKeyboardView = inflater.inflate(mViewResId, inputView, false);
        mapSoftKeys();
        return mKeyboardView;
@@ -86,30 +91,31 @@ final class SimpleKeyboard {

    private void mapSoftKeys() {
        for (int id : SOFT_KEY_IDS) {
            TextView softKeyView = mKeyboardView.findViewById(id);
            final TextView softKeyView = mKeyboardView.requireViewById(id);
            mSoftKeyViews.put(id, softKeyView);
            String tagData = softKeyView.getTag() != null ? softKeyView.getTag().toString() : null;
            softKeyView.setOnClickListener(v -> handle(tagData));
            final var keyCodeName = softKeyView.getTag() != null
                    ? softKeyView.getTag().toString() : null;
            softKeyView.setOnClickListener(v -> handleKeyPress(keyCodeName));
        }
    }

    private void handle(String data) {
        Log.i(TAG, "handle(): " + data);
        if (TextUtils.isEmpty(data)) {
    private void handleKeyPress(@Nullable String keyCodeName) {
        Log.i(TAG, "handle(): " + keyCodeName);
        if (TextUtils.isEmpty(keyCodeName)) {
            return;
        }
        if ("KEYCODE_SHIFT".equals(data)) {
        if ("KEYCODE_SHIFT".equals(keyCodeName)) {
            handleShift();
            return;
        }

        mSimpleInputMethodService.handle(data, mKeyboardState);
        mSimpleInputMethodService.handleKeyPress(keyCodeName, mKeyboardState);
    }

    private void handleShift() {
        mKeyboardState = toggleShiftState(mKeyboardState);
        Log.v(TAG, "currentKeyboardState: " + mKeyboardState);
        boolean isShiftOn = isShiftOn(mKeyboardState);
        final boolean isShiftOn = isShiftOn(mKeyboardState);
        for (int i = 0; i < mSoftKeyViews.size(); i++) {
            TextView softKeyView = mSoftKeyViews.valueAt(i);
            softKeyView.setAllCaps(isShiftOn);
+28 −13
Original line number Diff line number Diff line
@@ -25,21 +25,35 @@ import java.util.concurrent.CountDownLatch;

/** Wrapper of {@link InputMethodService} to expose interfaces for testing purpose. */
public class InputMethodServiceWrapper extends InputMethodService {
    private static final String TAG = "InputMethodServiceWrapper";

    private static InputMethodServiceWrapper sInputMethodServiceWrapper;
    private static final String TAG = "InputMethodServiceWrapper";

    public static InputMethodServiceWrapper getInputMethodServiceWrapperForTesting() {
        return sInputMethodServiceWrapper;
    }
    /** Last created instance of this wrapper. */
    private static InputMethodServiceWrapper sInstance;

    private boolean mInputViewStarted;

    /**
     * @see #setCountDownLatchForTesting
     */
    private CountDownLatch mCountDownLatchForTesting;

    /** Gets the last created instance of this wrapper, if available. */
    public static InputMethodServiceWrapper getInstance() {
        return sInstance;
    }

    public boolean getCurrentInputViewStarted() {
        return mInputViewStarted;
    }

    /**
     * Sets the latch used to wait for the IME to start showing ({@link #onStartInputView},
     * start hiding ({@link #onFinishInputView}) or receive a configuration change
     * ({@link #onConfigurationChanged}).
     *
     * @param countDownLatchForTesting the latch to wait on.
     */
    public void setCountDownLatchForTesting(CountDownLatch countDownLatchForTesting) {
        mCountDownLatchForTesting = countDownLatchForTesting;
    }
@@ -48,7 +62,7 @@ public class InputMethodServiceWrapper extends InputMethodService {
    public void onCreate() {
        Log.i(TAG, "onCreate()");
        super.onCreate();
        sInputMethodServiceWrapper = this;
        sInstance = this;
    }

    @Override
@@ -102,12 +116,13 @@ public class InputMethodServiceWrapper extends InputMethodService {
    }

    private String dumpEditorInfo(EditorInfo info) {
        var sb = new StringBuilder();
        sb.append("EditorInfo{packageName=").append(info.packageName);
        sb.append(" fieldId=").append(info.fieldId);
        sb.append(" hintText=").append(info.hintText);
        sb.append(" privateImeOptions=").append(info.privateImeOptions);
        sb.append("}");
        return sb.toString();
        if (info == null) {
            return "null";
        }
        return "EditorInfo{packageName=" + info.packageName
                + " fieldId=" + info.fieldId
                + " hintText=" + info.hintText
                + " privateImeOptions=" + info.privateImeOptions
                + "}";
    }
}
+26 −27
Original line number Diff line number Diff line
@@ -25,12 +25,12 @@ import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.LinearLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.lang.ref.WeakReference;
@@ -42,10 +42,10 @@ import java.lang.ref.WeakReference;
 * in the instruments package. More details see {@link
 * Instrumentation#startActivitySync(Intent)}.</>
 */
public class TestActivity extends Activity {
public final class TestActivity extends Activity {

    private static final String TAG = "TestActivity";
    private static WeakReference<TestActivity> sLastCreatedInstance =
            new WeakReference<>(null);
    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.
@@ -53,9 +53,9 @@ public class TestActivity extends Activity {
     * @param instrumentation application instrumentation
     * @return the newly started activity
     */
    public static TestActivity start(Instrumentation instrumentation) {
        Intent intent =
                new Intent()
    @NonNull
    public static TestActivity startSync(@NonNull Instrumentation instrumentation) {
        final var intent = new Intent()
                .setAction(Intent.ACTION_MAIN)
                .setClass(instrumentation.getTargetContext(), TestActivity.class)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -70,10 +70,10 @@ public class TestActivity extends Activity {
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
        LinearLayout rootView = new LinearLayout(this);
        final var rootView = new LinearLayout(this);
        mEditText = new EditText(this);
        mEditText.setContentDescription("Input box");
        rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
@@ -83,40 +83,39 @@ public class TestActivity extends Activity {
        sLastCreatedInstance = new WeakReference<>(this);
    }

    /** Get the last created TestActivity instance. */
    public static @Nullable TestActivity getLastCreatedInstance() {
    /** Get the last created TestActivity instance, if available. */
    @Nullable
    public static 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);
        final var imm = getSystemService(InputMethodManager.class);
        final boolean result = imm.showSoftInput(mEditText, flags);
        Log.i(TAG, "showIme() via InputMethodManager, result=" + result);
        return result;
    }

    /** Shows soft keyboard via WindowInsetsController. */
    public boolean showImeWithWindowInsetsController() {
        WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
        windowInsetsController.show(WindowInsets.Type.ime());
    public void showImeWithWindowInsetsController() {
        final var controller = mEditText.getWindowInsetsController();
        controller.show(WindowInsets.Type.ime());
        Log.i(TAG, "showIme() via WindowInsetsController");
        return true;
    }

    /** Hides soft keyboard via InputMethodManager. */
    public boolean hideImeWithInputMethodManager(int flags) {
        InputMethodManager imm = getSystemService(InputMethodManager.class);
        boolean result = imm.hideSoftInputFromWindow(mEditText.getWindowToken(), flags);
        final var imm = getSystemService(InputMethodManager.class);
        final boolean result = imm.hideSoftInputFromWindow(mEditText.getWindowToken(), flags);
        Log.i(TAG, "hideIme() via InputMethodManager, result=" + result);
        return result;
    }

    /** Hides soft keyboard via WindowInsetsController. */
    public boolean hideImeWithWindowInsetsController() {
        WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
        windowInsetsController.hide(WindowInsets.Type.ime());
    public void hideImeWithWindowInsetsController() {
        final var controller = mEditText.getWindowInsetsController();
        controller.hide(WindowInsets.Type.ime());
        Log.i(TAG, "hideIme() via WindowInsetsController");
        return true;
    }
}