1 /*
2  * Copyright (C) 2010 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.inputmethod.keyboard;
18 
19 import android.util.SparseArray;
20 
21 import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
22 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
23 import com.android.inputmethod.keyboard.internal.KeyboardParams;
24 import com.android.inputmethod.latin.common.Constants;
25 import com.android.inputmethod.latin.common.CoordinateUtils;
26 
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 
31 import javax.annotation.Nonnull;
32 import javax.annotation.Nullable;
33 
34 /**
35  * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
36  * consists of rows of keys.
37  * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p>
38  * <pre>
39  * &lt;Keyboard
40  *         latin:keyWidth="10%p"
41  *         latin:rowHeight="50px"
42  *         latin:horizontalGap="2%p"
43  *         latin:verticalGap="2%p" &gt;
44  *     &lt;Row latin:keyWidth="10%p" &gt;
45  *         &lt;Key latin:keyLabel="A" /&gt;
46  *         ...
47  *     &lt;/Row&gt;
48  *     ...
49  * &lt;/Keyboard&gt;
50  * </pre>
51  */
52 public class Keyboard {
53     @Nonnull
54     public final KeyboardId mId;
55     public final int mThemeId;
56 
57     /** Total height of the keyboard, including the padding and keys */
58     public final int mOccupiedHeight;
59     /** Total width of the keyboard, including the padding and keys */
60     public final int mOccupiedWidth;
61 
62     /** Base height of the keyboard, used to calculate rows' height */
63     public final int mBaseHeight;
64     /** Base width of the keyboard, used to calculate keys' width */
65     public final int mBaseWidth;
66 
67     /** The padding above the keyboard */
68     public final int mTopPadding;
69     /** Default gap between rows */
70     public final int mVerticalGap;
71 
72     /** Per keyboard key visual parameters */
73     public final KeyVisualAttributes mKeyVisualAttributes;
74 
75     public final int mMostCommonKeyHeight;
76     public final int mMostCommonKeyWidth;
77 
78     /** More keys keyboard template */
79     public final int mMoreKeysTemplate;
80 
81     /** Maximum column for more keys keyboard */
82     public final int mMaxMoreKeysKeyboardColumn;
83 
84     /** List of keys in this keyboard */
85     @Nonnull
86     private final List<Key> mSortedKeys;
87     @Nonnull
88     public final List<Key> mShiftKeys;
89     @Nonnull
90     public final List<Key> mAltCodeKeysWhileTyping;
91     @Nonnull
92     public final KeyboardIconsSet mIconsSet;
93 
94     private final SparseArray<Key> mKeyCache = new SparseArray<>();
95 
96     @Nonnull
97     private final ProximityInfo mProximityInfo;
98     @Nonnull
99     private final KeyboardLayout mKeyboardLayout;
100 
101     private final boolean mProximityCharsCorrectionEnabled;
102 
Keyboard(@onnull final KeyboardParams params)103     public Keyboard(@Nonnull final KeyboardParams params) {
104         mId = params.mId;
105         mThemeId = params.mThemeId;
106         mOccupiedHeight = params.mOccupiedHeight;
107         mOccupiedWidth = params.mOccupiedWidth;
108         mBaseHeight = params.mBaseHeight;
109         mBaseWidth = params.mBaseWidth;
110         mMostCommonKeyHeight = params.mMostCommonKeyHeight;
111         mMostCommonKeyWidth = params.mMostCommonKeyWidth;
112         mMoreKeysTemplate = params.mMoreKeysTemplate;
113         mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn;
114         mKeyVisualAttributes = params.mKeyVisualAttributes;
115         mTopPadding = params.mTopPadding;
116         mVerticalGap = params.mVerticalGap;
117 
118         mSortedKeys = Collections.unmodifiableList(new ArrayList<>(params.mSortedKeys));
119         mShiftKeys = Collections.unmodifiableList(params.mShiftKeys);
120         mAltCodeKeysWhileTyping = Collections.unmodifiableList(params.mAltCodeKeysWhileTyping);
121         mIconsSet = params.mIconsSet;
122 
123         mProximityInfo = new ProximityInfo(params.GRID_WIDTH, params.GRID_HEIGHT,
124                 mOccupiedWidth, mOccupiedHeight, mMostCommonKeyWidth, mMostCommonKeyHeight,
125                 mSortedKeys, params.mTouchPositionCorrection);
126         mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled;
127         mKeyboardLayout = KeyboardLayout.newKeyboardLayout(mSortedKeys, mMostCommonKeyWidth,
128                 mMostCommonKeyHeight, mOccupiedWidth, mOccupiedHeight);
129     }
130 
Keyboard(@onnull final Keyboard keyboard)131     protected Keyboard(@Nonnull final Keyboard keyboard) {
132         mId = keyboard.mId;
133         mThemeId = keyboard.mThemeId;
134         mOccupiedHeight = keyboard.mOccupiedHeight;
135         mOccupiedWidth = keyboard.mOccupiedWidth;
136         mBaseHeight = keyboard.mBaseHeight;
137         mBaseWidth = keyboard.mBaseWidth;
138         mMostCommonKeyHeight = keyboard.mMostCommonKeyHeight;
139         mMostCommonKeyWidth = keyboard.mMostCommonKeyWidth;
140         mMoreKeysTemplate = keyboard.mMoreKeysTemplate;
141         mMaxMoreKeysKeyboardColumn = keyboard.mMaxMoreKeysKeyboardColumn;
142         mKeyVisualAttributes = keyboard.mKeyVisualAttributes;
143         mTopPadding = keyboard.mTopPadding;
144         mVerticalGap = keyboard.mVerticalGap;
145 
146         mSortedKeys = keyboard.mSortedKeys;
147         mShiftKeys = keyboard.mShiftKeys;
148         mAltCodeKeysWhileTyping = keyboard.mAltCodeKeysWhileTyping;
149         mIconsSet = keyboard.mIconsSet;
150 
151         mProximityInfo = keyboard.mProximityInfo;
152         mProximityCharsCorrectionEnabled = keyboard.mProximityCharsCorrectionEnabled;
153         mKeyboardLayout = keyboard.mKeyboardLayout;
154     }
155 
hasProximityCharsCorrection(final int code)156     public boolean hasProximityCharsCorrection(final int code) {
157         if (!mProximityCharsCorrectionEnabled) {
158             return false;
159         }
160         // Note: The native code has the main keyboard layout only at this moment.
161         // TODO: Figure out how to handle proximity characters information of all layouts.
162         final boolean canAssumeNativeHasProximityCharsInfoOfAllKeys = (
163                 mId.mElementId == KeyboardId.ELEMENT_ALPHABET
164                 || mId.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED);
165         return canAssumeNativeHasProximityCharsInfoOfAllKeys || Character.isLetter(code);
166     }
167 
168     @Nonnull
getProximityInfo()169     public ProximityInfo getProximityInfo() {
170         return mProximityInfo;
171     }
172 
173     @Nonnull
getKeyboardLayout()174     public KeyboardLayout getKeyboardLayout() {
175         return mKeyboardLayout;
176     }
177 
178     /**
179      * Return the sorted list of keys of this keyboard.
180      * The keys are sorted from top-left to bottom-right order.
181      * The list may contain {@link Key.Spacer} object as well.
182      * @return the sorted unmodifiable list of {@link Key}s of this keyboard.
183      */
184     @Nonnull
getSortedKeys()185     public List<Key> getSortedKeys() {
186         return mSortedKeys;
187     }
188 
189     @Nullable
getKey(final int code)190     public Key getKey(final int code) {
191         if (code == Constants.CODE_UNSPECIFIED) {
192             return null;
193         }
194         synchronized (mKeyCache) {
195             final int index = mKeyCache.indexOfKey(code);
196             if (index >= 0) {
197                 return mKeyCache.valueAt(index);
198             }
199 
200             for (final Key key : getSortedKeys()) {
201                 if (key.getCode() == code) {
202                     mKeyCache.put(code, key);
203                     return key;
204                 }
205             }
206             mKeyCache.put(code, null);
207             return null;
208         }
209     }
210 
hasKey(@onnull final Key aKey)211     public boolean hasKey(@Nonnull final Key aKey) {
212         if (mKeyCache.indexOfValue(aKey) >= 0) {
213             return true;
214         }
215 
216         for (final Key key : getSortedKeys()) {
217             if (key == aKey) {
218                 mKeyCache.put(key.getCode(), key);
219                 return true;
220             }
221         }
222         return false;
223     }
224 
225     @Override
toString()226     public String toString() {
227         return mId.toString();
228     }
229 
230     /**
231      * Returns the array of the keys that are closest to the given point.
232      * @param x the x-coordinate of the point
233      * @param y the y-coordinate of the point
234      * @return the list of the nearest keys to the given point. If the given
235      * point is out of range, then an array of size zero is returned.
236      */
237     @Nonnull
getNearestKeys(final int x, final int y)238     public List<Key> getNearestKeys(final int x, final int y) {
239         // Avoid dead pixels at edges of the keyboard
240         final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1));
241         final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1));
242         return mProximityInfo.getNearestKeys(adjustedX, adjustedY);
243     }
244 
245     @Nonnull
getCoordinates(@onnull final int[] codePoints)246     public int[] getCoordinates(@Nonnull final int[] codePoints) {
247         final int length = codePoints.length;
248         final int[] coordinates = CoordinateUtils.newCoordinateArray(length);
249         for (int i = 0; i < length; ++i) {
250             final Key key = getKey(codePoints[i]);
251             if (null != key) {
252                 CoordinateUtils.setXYInArray(coordinates, i,
253                         key.getX() + key.getWidth() / 2, key.getY() + key.getHeight() / 2);
254             } else {
255                 CoordinateUtils.setXYInArray(coordinates, i,
256                         Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
257             }
258         }
259         return coordinates;
260     }
261 }
262