1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.apps.inputmethod.simpleime; 18 19 import android.content.Context; 20 import android.text.TextUtils; 21 import android.util.AttributeSet; 22 import android.util.Log; 23 import android.util.SparseArray; 24 import android.view.KeyEvent; 25 import android.view.LayoutInflater; 26 import android.view.WindowInsets; 27 import android.widget.FrameLayout; 28 import android.widget.TextView; 29 30 import androidx.annotation.AttrRes; 31 import androidx.annotation.NonNull; 32 import androidx.annotation.Nullable; 33 34 /** A simple implementation of a software keyboard view. */ 35 final class SimpleKeyboardView extends FrameLayout { 36 37 private static final String TAG = "SimpleKeyboard"; 38 39 private static final int[] SOFT_KEY_IDS = new int[]{ 40 R.id.key_pos_0_0, 41 R.id.key_pos_0_1, 42 R.id.key_pos_0_2, 43 R.id.key_pos_0_3, 44 R.id.key_pos_0_4, 45 R.id.key_pos_0_5, 46 R.id.key_pos_0_6, 47 R.id.key_pos_0_7, 48 R.id.key_pos_0_8, 49 R.id.key_pos_0_9, 50 R.id.key_pos_1_0, 51 R.id.key_pos_1_1, 52 R.id.key_pos_1_2, 53 R.id.key_pos_1_3, 54 R.id.key_pos_1_4, 55 R.id.key_pos_1_5, 56 R.id.key_pos_1_6, 57 R.id.key_pos_1_7, 58 R.id.key_pos_1_8, 59 R.id.key_pos_2_0, 60 R.id.key_pos_2_1, 61 R.id.key_pos_2_2, 62 R.id.key_pos_2_3, 63 R.id.key_pos_2_4, 64 R.id.key_pos_2_5, 65 R.id.key_pos_2_6, 66 R.id.key_pos_shift, 67 R.id.key_pos_del, 68 R.id.key_pos_symbol, 69 R.id.key_pos_comma, 70 R.id.key_pos_space, 71 R.id.key_pos_period, 72 R.id.key_pos_enter, 73 }; 74 75 private final SparseArray<TextView> mSoftKeyViews = new SparseArray<>(); 76 77 @FunctionalInterface 78 interface KeyPressListener { 79 80 /** 81 * Called when a key is pressed. 82 * 83 * @param keyCodeName the keycode of the key, as a string. 84 * @param metaState the flags indicating which meta keys are currently pressed. 85 */ onKeyPress(@onNull String keyCodeName, int metaState)86 void onKeyPress(@NonNull String keyCodeName, int metaState); 87 } 88 89 /** A listener to react to key presses. */ 90 @Nullable 91 private KeyPressListener mKeyPressListener; 92 93 /** The flags indicating which meta keys are currently pressed. */ 94 private int mMetaState; 95 SimpleKeyboardView(@onNull Context context)96 SimpleKeyboardView(@NonNull Context context) { 97 this(context, null); 98 } 99 SimpleKeyboardView(@onNull Context context, @Nullable AttributeSet attrs)100 SimpleKeyboardView(@NonNull Context context, @Nullable AttributeSet attrs) { 101 this(context, attrs, 0 /* defStyleAttr */); 102 } 103 SimpleKeyboardView(@onNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr)104 SimpleKeyboardView(@NonNull Context context, @Nullable AttributeSet attrs, 105 @AttrRes int defStyleAttr) { 106 super(context, attrs, defStyleAttr, 0 /* defStyleRes */); 107 LayoutInflater.from(context).inflate(R.layout.qwerty_10_9_9, this, true); 108 mapSoftKeys(); 109 } 110 111 @Override onApplyWindowInsets(WindowInsets insets)112 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 113 // Handle edge to edge for navigationBars insets (system nav bar) 114 // and captionBars insets (IME navigation bar). 115 final int insetBottom = insets.getInsets(WindowInsets.Type.systemBars()).bottom; 116 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), insetBottom); 117 return insets.inset(0, 0, 0, insetBottom); 118 } 119 120 /** 121 * Sets the key press listener. 122 * 123 * @param listener the listener to set. 124 */ setKeyPressListener(@onNull KeyPressListener listener)125 void setKeyPressListener(@NonNull KeyPressListener listener) { 126 mKeyPressListener = listener; 127 } 128 129 /** Maps the soft key ids to their corresponding views, and sets their on click listener. */ mapSoftKeys()130 private void mapSoftKeys() { 131 for (final int id : SOFT_KEY_IDS) { 132 final TextView softKeyView = requireViewById(id); 133 mSoftKeyViews.put(id, softKeyView); 134 final var keyCodeName = softKeyView.getTag() != null 135 ? softKeyView.getTag().toString() : null; 136 softKeyView.setOnClickListener(v -> onKeyPress(keyCodeName)); 137 } 138 } 139 140 /** 141 * Called when a key is pressed. 142 * 143 * @param keyCodeName the keycode of the key, as a string. 144 */ onKeyPress(@ullable String keyCodeName)145 private void onKeyPress(@Nullable String keyCodeName) { 146 Log.i(TAG, "onKeyPress: " + keyCodeName); 147 if (TextUtils.isEmpty(keyCodeName)) { 148 return; 149 } 150 if ("KEYCODE_SHIFT".equals(keyCodeName)) { 151 onShiftPress(); 152 return; 153 } 154 155 if (mKeyPressListener != null) { 156 mKeyPressListener.onKeyPress(keyCodeName, mMetaState); 157 } 158 } 159 160 /** 161 * Called when the shift key is pressed. This will toggle the capitalization of all the keys. 162 */ onShiftPress()163 private void onShiftPress() { 164 mMetaState = toggleShiftState(mMetaState); 165 Log.v(TAG, "onShiftPress, new metaState: " + mMetaState); 166 final boolean isShiftOn = isShiftOn(mMetaState); 167 for (int i = 0; i < mSoftKeyViews.size(); i++) { 168 final TextView softKeyView = mSoftKeyViews.valueAt(i); 169 softKeyView.setAllCaps(isShiftOn); 170 } 171 } 172 173 /** 174 * Checks whether the shift meta key is pressed. 175 * 176 * @param state the flags indicating which meta keys are currently pressed. 177 */ isShiftOn(int state)178 private static boolean isShiftOn(int state) { 179 return (state & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON; 180 } 181 182 /** 183 * Toggles the value of the shift meta key being pressed. 184 * 185 * @param state the flags indicating which meta keys are currently pressed. 186 * @return a new flag state, with the shift meta key value flipped. 187 */ toggleShiftState(int state)188 private static int toggleShiftState(int state) { 189 return state ^ KeyEvent.META_SHIFT_ON; 190 } 191 } 192