• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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