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

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

Refactor SimpleKeyboard into SimpleKeyboardView

This modifies SimpleKeyboard to extend FrameLayout (and consequently
removes the now redundant input_view layout). Also removes the strong
dependency on SimpleInputMethodService by introducing KeyPressListener.
Renames some fields and methods to better reflect what they represent.
Modifies SimpleInputMethodService to only inflate the views once in
onCreateInputView, rather than re-inflate every time onStartInputView is
called. Lastly optimized KeyCodeConstats to have a static switch
statement rather than initialize a HashMap.

Flag: EXEMPT testfix
Bug: 394328311
Test: atest InputMethodServiceTest
Change-Id: I14c3ad5d7f800e421fd4377c9ded5bf3d0af232f
parent ad7eeefc
Loading
Loading
Loading
Loading
+0 −22
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2022 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.
  -->

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/input"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
 No newline at end of file
+46 −39
Original line number Diff line number Diff line
@@ -18,51 +18,58 @@ package com.android.apps.inputmethod.simpleime;

import android.view.KeyEvent;

import java.util.HashMap;
import androidx.annotation.NonNull;

/** Holder of key codes and their name. */
public final class KeyCodeConstants {
    private KeyCodeConstants() {}
final class KeyCodeConstants {

    static final HashMap<String, Integer> KEY_NAME_TO_CODE_MAP = new HashMap<>();
    private KeyCodeConstants() {
    }

    static {
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_A", KeyEvent.KEYCODE_A);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_B", KeyEvent.KEYCODE_B);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_C", KeyEvent.KEYCODE_C);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_D", KeyEvent.KEYCODE_D);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_E", KeyEvent.KEYCODE_E);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_F", KeyEvent.KEYCODE_F);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_G", KeyEvent.KEYCODE_G);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_H", KeyEvent.KEYCODE_H);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_I", KeyEvent.KEYCODE_I);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_J", KeyEvent.KEYCODE_J);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_K", KeyEvent.KEYCODE_K);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_L", KeyEvent.KEYCODE_L);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_M", KeyEvent.KEYCODE_M);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_N", KeyEvent.KEYCODE_N);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_O", KeyEvent.KEYCODE_O);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_P", KeyEvent.KEYCODE_P);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_Q", KeyEvent.KEYCODE_Q);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_R", KeyEvent.KEYCODE_R);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_S", KeyEvent.KEYCODE_S);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_T", KeyEvent.KEYCODE_T);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_U", KeyEvent.KEYCODE_U);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_V", KeyEvent.KEYCODE_V);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_W", KeyEvent.KEYCODE_W);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_X", KeyEvent.KEYCODE_X);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_Y", KeyEvent.KEYCODE_Y);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_Z", KeyEvent.KEYCODE_Z);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_SHIFT", KeyEvent.KEYCODE_SHIFT_LEFT);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_DEL", KeyEvent.KEYCODE_DEL);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_SPACE", KeyEvent.KEYCODE_SPACE);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_ENTER", KeyEvent.KEYCODE_ENTER);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_COMMA", KeyEvent.KEYCODE_COMMA);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_PERIOD", KeyEvent.KEYCODE_PERIOD);
        KEY_NAME_TO_CODE_MAP.put("KEYCODE_TAB", KeyEvent.KEYCODE_TAB);
    /**
     * Returns the keyCode value corresponding to the given keyCode name.
     *
     * @param keyCodeName the keyCode name.
     */
    static int getKeyCode(@NonNull String keyCodeName) {
        return switch (keyCodeName) {
            case "KEYCODE_A" -> KeyEvent.KEYCODE_A;
            case "KEYCODE_B" -> KeyEvent.KEYCODE_B;
            case "KEYCODE_C" -> KeyEvent.KEYCODE_C;
            case "KEYCODE_D" -> KeyEvent.KEYCODE_D;
            case "KEYCODE_E" -> KeyEvent.KEYCODE_E;
            case "KEYCODE_F" -> KeyEvent.KEYCODE_F;
            case "KEYCODE_G" -> KeyEvent.KEYCODE_G;
            case "KEYCODE_H" -> KeyEvent.KEYCODE_H;
            case "KEYCODE_I" -> KeyEvent.KEYCODE_I;
            case "KEYCODE_J" -> KeyEvent.KEYCODE_J;
            case "KEYCODE_K" -> KeyEvent.KEYCODE_K;
            case "KEYCODE_L" -> KeyEvent.KEYCODE_L;
            case "KEYCODE_M" -> KeyEvent.KEYCODE_M;
            case "KEYCODE_N" -> KeyEvent.KEYCODE_N;
            case "KEYCODE_O" -> KeyEvent.KEYCODE_O;
            case "KEYCODE_P" -> KeyEvent.KEYCODE_P;
            case "KEYCODE_Q" -> KeyEvent.KEYCODE_Q;
            case "KEYCODE_R" -> KeyEvent.KEYCODE_R;
            case "KEYCODE_S" -> KeyEvent.KEYCODE_S;
            case "KEYCODE_T" -> KeyEvent.KEYCODE_T;
            case "KEYCODE_U" -> KeyEvent.KEYCODE_U;
            case "KEYCODE_V" -> KeyEvent.KEYCODE_V;
            case "KEYCODE_W" -> KeyEvent.KEYCODE_W;
            case "KEYCODE_X" -> KeyEvent.KEYCODE_X;
            case "KEYCODE_Y" -> KeyEvent.KEYCODE_Y;
            case "KEYCODE_Z" -> KeyEvent.KEYCODE_Z;
            case "KEYCODE_SHIFT" -> KeyEvent.KEYCODE_SHIFT_LEFT;
            case "KEYCODE_DEL" -> KeyEvent.KEYCODE_DEL;
            case "KEYCODE_SPACE" -> KeyEvent.KEYCODE_SPACE;
            case "KEYCODE_ENTER" -> KeyEvent.KEYCODE_ENTER;
            case "KEYCODE_COMMA" -> KeyEvent.KEYCODE_COMMA;
            case "KEYCODE_PERIOD" -> KeyEvent.KEYCODE_PERIOD;
            default -> KeyEvent.KEYCODE_UNKNOWN;
        };
    }

    public static boolean isAlphaKeyCode(int keyCode) {
    static boolean isAlphaKeyCode(int keyCode) {
        return keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z;
    }
}
+20 −24
Original line number Diff line number Diff line
@@ -20,45 +20,41 @@ import android.inputmethodservice.InputMethodService;
import android.os.SystemClock;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;

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

/** The {@link InputMethodService} implementation for SimpleTestIme app. */
/** A simple implementation of an {@link InputMethodService}. */
public final class SimpleInputMethodService extends InputMethodServiceWrapper {

    private static final String TAG = "SimpleIMS";

    private FrameLayout mInputView;

    @Override
    public View onCreateInputView() {
        Log.i(TAG, "onCreateInputView()");
        mInputView = (FrameLayout) LayoutInflater.from(this).inflate(R.layout.input_view, null);
        return mInputView;
    }

    @Override
    public void onStartInputView(EditorInfo info, boolean restarting) {
        super.onStartInputView(info, restarting);
        mInputView.removeAllViews();
        final var keyboard = new SimpleKeyboard(this, R.layout.qwerty_10_9_9);
        mInputView.addView(keyboard.inflateKeyboardView(LayoutInflater.from(this), mInputView));
        final var simpleKeyboard = new SimpleKeyboardView(this);
        simpleKeyboard.setKeyPressListener(this::onKeyPress);
        return simpleKeyboard;
    }

    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) {
    /**
     * Called when a key is pressed.
     *
     * @param keyCodeName the keycode of the key, as a string.
     * @param metaState   the flags indicating which meta keys are currently pressed.
     */
    private void onKeyPress(@NonNull String keyCodeName, int metaState) {
        final int keyCode = KeyCodeConstants.getKeyCode(keyCodeName);
        Log.v(TAG, "onKeyPress: " + keyCode);
        if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
            final var ic = getCurrentInputConnection();
            if (ic != null) {
                final var downTime = SystemClock.uptimeMillis();
            getCurrentInputConnection().sendKeyEvent(new KeyEvent(downTime, downTime,
                    KeyEvent.ACTION_DOWN, keyCode, 0 /* repeat */,
                    KeyCodeConstants.isAlphaKeyCode(keyCode) ? keyboardState : 0) /* metaState */);
                ic.sendKeyEvent(new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode,
                        0 /* repeat */, KeyCodeConstants.isAlphaKeyCode(keyCode) ? metaState : 0));
            }
        }
    }
}
+181 −0
Original line number Diff line number Diff line
@@ -16,20 +16,22 @@

package com.android.apps.inputmethod.simpleime;

import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;

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

/** Controls the visible virtual keyboard view. */
final class SimpleKeyboard {
/** A simple implementation of a software keyboard view. */
final class SimpleKeyboardView extends FrameLayout {

    private static final String TAG = "SimpleKeyboard";

@@ -69,63 +71,110 @@ 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(@NonNull SimpleInputMethodService simpleInputMethodService, int viewResId) {
        mSimpleInputMethodService = simpleInputMethodService;
        mViewResId = viewResId;
        mKeyboardState = 0;
    @FunctionalInterface
    interface KeyPressListener {

        /**
         * Called when a key is pressed.
         *
         * @param keyCodeName the keycode of the key, as a string.
         * @param metaState   the flags indicating which meta keys are currently pressed.
         */
        void onKeyPress(@NonNull String keyCodeName, int metaState);
    }

    @NonNull
    View inflateKeyboardView(@NonNull LayoutInflater inflater, @NonNull ViewGroup inputView) {
        mKeyboardView = inflater.inflate(mViewResId, inputView, false);
    /** A listener to react to key presses. */
    @Nullable
    private KeyPressListener mKeyPressListener;

    /** The flags indicating which meta keys are currently pressed. */
    private int mMetaState;

    SimpleKeyboardView(@NonNull Context context) {
        this(context, null);
    }

    SimpleKeyboardView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0 /* defStyleAttr */);
    }

    SimpleKeyboardView(@NonNull Context context, @Nullable AttributeSet attrs,
            @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr, 0 /* defStyleRes */);
        LayoutInflater.from(context).inflate(R.layout.qwerty_10_9_9, this, true);
        mapSoftKeys();
        return mKeyboardView;
    }

    /**
     * Sets the key press listener.
     *
     * @param listener the listener to set.
     */
    void setKeyPressListener(@NonNull KeyPressListener listener) {
        mKeyPressListener = listener;
    }

    /** Maps the soft key ids to their corresponding views, and sets their on click listener. */
    private void mapSoftKeys() {
        for (int id : SOFT_KEY_IDS) {
            final TextView softKeyView = mKeyboardView.requireViewById(id);
        for (final int id : SOFT_KEY_IDS) {
            final TextView softKeyView = requireViewById(id);
            mSoftKeyViews.put(id, softKeyView);
            final var keyCodeName = softKeyView.getTag() != null
                    ? softKeyView.getTag().toString() : null;
            softKeyView.setOnClickListener(v -> handleKeyPress(keyCodeName));
            softKeyView.setOnClickListener(v -> onKeyPress(keyCodeName));
        }
    }

    private void handleKeyPress(@Nullable String keyCodeName) {
        Log.i(TAG, "handle(): " + keyCodeName);
    /**
     * Called when a key is pressed.
     *
     * @param keyCodeName the keycode of the key, as a string.
     */
    private void onKeyPress(@Nullable String keyCodeName) {
        Log.i(TAG, "onKeyPress: " + keyCodeName);
        if (TextUtils.isEmpty(keyCodeName)) {
            return;
        }
        if ("KEYCODE_SHIFT".equals(keyCodeName)) {
            handleShift();
            onShiftPress();
            return;
        }

        mSimpleInputMethodService.handleKeyPress(keyCodeName, mKeyboardState);
        if (mKeyPressListener != null) {
            mKeyPressListener.onKeyPress(keyCodeName, mMetaState);
        }
    }

    private void handleShift() {
        mKeyboardState = toggleShiftState(mKeyboardState);
        Log.v(TAG, "currentKeyboardState: " + mKeyboardState);
        final boolean isShiftOn = isShiftOn(mKeyboardState);
    /**
     * Called when the shift key is pressed. This will toggle the capitalization of all the keys.
     */
    private void onShiftPress() {
        mMetaState = toggleShiftState(mMetaState);
        Log.v(TAG, "onShiftPress, new metaState: " + mMetaState);
        final boolean isShiftOn = isShiftOn(mMetaState);
        for (int i = 0; i < mSoftKeyViews.size(); i++) {
            TextView softKeyView = mSoftKeyViews.valueAt(i);
            final TextView softKeyView = mSoftKeyViews.valueAt(i);
            softKeyView.setAllCaps(isShiftOn);
        }
    }

    /**
     * Checks whether the shift meta key is pressed.
     *
     * @param state the flags indicating which meta keys are currently pressed.
     */
    private static boolean isShiftOn(int state) {
        return (state & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON;
    }

    /**
     * Toggles the value of the shift meta key being pressed.
     *
     * @param state the flags indicating which meta keys are currently pressed.
     * @return a new flag state, with the shift meta key value flipped.
     */
    private static int toggleShiftState(int state) {
        return state ^ KeyEvent.META_SHIFT_ON;
    }