Keyboard.java revision 167e77f17084da5c223a3a790d3dd3d749e68ae3
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.inputmethod.keyboard;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.graphics.drawable.Drawable;
22import android.text.TextUtils;
23import android.util.Log;
24
25import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
26import com.android.inputmethod.keyboard.internal.KeyboardParser;
27import com.android.inputmethod.keyboard.internal.KeyboardShiftState;
28import com.android.inputmethod.latin.R;
29
30import org.xmlpull.v1.XmlPullParserException;
31
32import java.io.IOException;
33import java.util.ArrayList;
34import java.util.HashMap;
35import java.util.HashSet;
36import java.util.List;
37import java.util.Map;
38import java.util.Set;
39
40/**
41 * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
42 * consists of rows of keys.
43 * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p>
44 * <pre>
45 * &lt;Keyboard
46 *         latin:keyWidth="%10p"
47 *         latin:keyHeight="50px"
48 *         latin:horizontalGap="2px"
49 *         latin:verticalGap="2px" &gt;
50 *     &lt;Row latin:keyWidth="32px" &gt;
51 *         &lt;Key latin:keyLabel="A" /&gt;
52 *         ...
53 *     &lt;/Row&gt;
54 *     ...
55 * &lt;/Keyboard&gt;
56 * </pre>
57 */
58public class Keyboard {
59    private static final String TAG = Keyboard.class.getSimpleName();
60
61    public static final int EDGE_LEFT = 0x01;
62    public static final int EDGE_RIGHT = 0x02;
63    public static final int EDGE_TOP = 0x04;
64    public static final int EDGE_BOTTOM = 0x08;
65
66    /** Some common keys code.  These should be aligned with values/keycodes.xml */
67    public static final int CODE_ENTER = '\n';
68    public static final int CODE_TAB = '\t';
69    public static final int CODE_SPACE = ' ';
70    public static final int CODE_PERIOD = '.';
71    public static final int CODE_DASH = '-';
72    public static final int CODE_SINGLE_QUOTE = '\'';
73    public static final int CODE_DOUBLE_QUOTE = '"';
74    // TODO: Check how this should work for right-to-left languages. It seems to stand
75    // that for rtl languages, a closing parenthesis is a left parenthesis. Is this
76    // managed by the font? Or is it a different char?
77    public static final int CODE_CLOSING_PARENTHESIS = ')';
78    public static final int CODE_CLOSING_SQUARE_BRACKET = ']';
79    public static final int CODE_CLOSING_CURLY_BRACKET = '}';
80    public static final int CODE_CLOSING_ANGLE_BRACKET = '>';
81    public static final int CODE_DIGIT0 = '0';
82    public static final int CODE_PLUS = '+';
83
84
85    /** Special keys code.  These should be aligned with values/keycodes.xml */
86    public static final int CODE_DUMMY = 0;
87    public static final int CODE_SHIFT = -1;
88    public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;
89    public static final int CODE_CAPSLOCK = -3;
90    public static final int CODE_CANCEL = -4;
91    public static final int CODE_DELETE = -5;
92    public static final int CODE_SETTINGS = -6;
93    public static final int CODE_SETTINGS_LONGPRESS = -7;
94    public static final int CODE_SHORTCUT = -8;
95    // Code value representing the code is not specified.
96    public static final int CODE_UNSPECIFIED = -99;
97
98    public final KeyboardId mId;
99
100    /** Total height of the keyboard, including the padding and keys */
101    private int mTotalHeight;
102
103    /**
104     * Total width (minimum width) of the keyboard, including left side gaps and keys, but not any
105     * gaps on the right side.
106     */
107    private int mMinWidth;
108
109    /** Horizontal gap default for all rows */
110    private int mHorizontalGap;
111
112    /** Default key width */
113    private int mDefaultKeyWidth;
114
115    /** Default row height */
116    private int mDefaultRowHeight;
117
118    /** Default gap between rows */
119    private int mVerticalGap;
120
121    /** Popup keyboard template */
122    private int mPopupKeyboardResId;
123
124    /** Maximum column for popup keyboard */
125    private int mMaxPopupColumn;
126
127    /** True if Right-To-Left keyboard */
128    private boolean mIsRtlKeyboard;
129
130    /** List of keys in this keyboard */
131    private final List<Key> mKeys = new ArrayList<Key>();
132    /** List of shift keys in this keyboard and its icons and state */
133    private final List<Key> mShiftKeys = new ArrayList<Key>();
134    private final Map<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>();
135    private final Map<Key, Drawable> mUnshiftedIcons = new HashMap<Key, Drawable>();
136    private final Set<Key> mShiftLockKeys = new HashSet<Key>();
137    public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
138
139
140    /** Width of the screen available to fit the keyboard */
141    private final int mDisplayWidth;
142
143    /** Height of the screen */
144    private final int mDisplayHeight;
145
146    /** Height of keyboard */
147    private int mKeyboardHeight;
148
149    private int mMostCommonKeyWidth = 0;
150
151    private final KeyboardShiftState mShiftState = new KeyboardShiftState();
152
153    // Variables for pre-computing nearest keys.
154
155    // TODO: Change GRID_WIDTH and GRID_HEIGHT to private.
156    public final int GRID_WIDTH;
157    public final int GRID_HEIGHT;
158
159    private final ProximityInfo mProximityInfo;
160
161    /**
162     * Creates a keyboard from the given xml key layout file.
163     * @param context the application or service context
164     * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
165     * @param id keyboard identifier
166     * @param width keyboard width
167     */
168
169    public Keyboard(Context context, int xmlLayoutResId, KeyboardId id, int width) {
170        final Resources res = context.getResources();
171        GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
172        GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
173
174        final int horizontalEdgesPadding = (int)res.getDimension(
175                R.dimen.keyboard_horizontal_edges_padding);
176        mDisplayWidth = width - horizontalEdgesPadding * 2;
177        // TODO: Adjust the height by referring to the height of area available for drawing as well.
178        mDisplayHeight = res.getDisplayMetrics().heightPixels;
179
180        mHorizontalGap = 0;
181        setKeyWidth(mDisplayWidth / 10);
182        mVerticalGap = 0;
183        mDefaultRowHeight = mDefaultKeyWidth;
184        mId = id;
185        loadKeyboard(context, xmlLayoutResId);
186        mProximityInfo = new ProximityInfo(
187                GRID_WIDTH, GRID_HEIGHT, getMinWidth(), getHeight(), getKeyWidth(), mKeys);
188    }
189
190    public int getProximityInfo() {
191        return mProximityInfo.getNativeProximityInfo();
192    }
193
194    public List<Key> getKeys() {
195        return mKeys;
196    }
197
198    public int getHorizontalGap() {
199        return mHorizontalGap;
200    }
201
202    public void setHorizontalGap(int gap) {
203        mHorizontalGap = gap;
204    }
205
206    public int getVerticalGap() {
207        return mVerticalGap;
208    }
209
210    public void setVerticalGap(int gap) {
211        mVerticalGap = gap;
212    }
213
214    public int getRowHeight() {
215        return mDefaultRowHeight;
216    }
217
218    public void setRowHeight(int height) {
219        mDefaultRowHeight = height;
220    }
221
222    public int getKeyWidth() {
223        return mDefaultKeyWidth;
224    }
225
226    public void setKeyWidth(int width) {
227        mDefaultKeyWidth = width;
228    }
229
230    /**
231     * Returns the total height of the keyboard
232     * @return the total height of the keyboard
233     */
234    public int getHeight() {
235        return mTotalHeight;
236    }
237
238    public void setHeight(int height) {
239        mTotalHeight = height;
240    }
241
242    public int getMinWidth() {
243        return mMinWidth;
244    }
245
246    public void setMinWidth(int minWidth) {
247        mMinWidth = minWidth;
248    }
249
250    public int getDisplayHeight() {
251        return mDisplayHeight;
252    }
253
254    public int getDisplayWidth() {
255        return mDisplayWidth;
256    }
257
258    public int getKeyboardHeight() {
259        return mKeyboardHeight;
260    }
261
262    public void setKeyboardHeight(int height) {
263        mKeyboardHeight = height;
264    }
265
266    public boolean isRtlKeyboard() {
267        return mIsRtlKeyboard;
268    }
269
270    public void setRtlKeyboard(boolean isRtl) {
271        mIsRtlKeyboard = isRtl;
272    }
273
274    public int getPopupKeyboardResId() {
275        return mPopupKeyboardResId;
276    }
277
278    public void setPopupKeyboardResId(int resId) {
279        mPopupKeyboardResId = resId;
280    }
281
282    public int getMaxPopupKeyboardColumn() {
283        return mMaxPopupColumn;
284    }
285
286    public void setMaxPopupKeyboardColumn(int column) {
287        mMaxPopupColumn = column;
288    }
289
290    public void addShiftKey(Key key) {
291        if (key == null) return;
292        mShiftKeys.add(key);
293        if (key.mSticky) {
294            mShiftLockKeys.add(key);
295        }
296    }
297
298    public void addShiftedIcon(Key key, Drawable icon) {
299        if (key == null) return;
300        mUnshiftedIcons.put(key, key.getIcon());
301        mShiftedIcons.put(key, icon);
302    }
303
304    public boolean hasShiftLockKey() {
305        return !mShiftLockKeys.isEmpty();
306    }
307
308    public boolean setShiftLocked(boolean newShiftLockState) {
309        for (final Key key : mShiftLockKeys) {
310            // To represent "shift locked" state. The highlight is handled by background image that
311            // might be a StateListDrawable.
312            key.setHighlightOn(newShiftLockState);
313            // To represent "shifted" state. The key might have a shifted icon.
314            if (newShiftLockState && mShiftedIcons.containsKey(key)) {
315                key.setIcon(mShiftedIcons.get(key));
316            } else {
317                key.setIcon(mUnshiftedIcons.get(key));
318            }
319        }
320        mShiftState.setShiftLocked(newShiftLockState);
321        return true;
322    }
323
324    public boolean isShiftLocked() {
325        return mShiftState.isShiftLocked();
326    }
327
328    public boolean setShifted(boolean newShiftState) {
329        for (final Key key : mShiftKeys) {
330            if (!newShiftState && !mShiftState.isShiftLocked()) {
331                key.setIcon(mUnshiftedIcons.get(key));
332            } else if (newShiftState && !mShiftState.isShiftedOrShiftLocked()) {
333                key.setIcon(mShiftedIcons.get(key));
334            }
335        }
336        return mShiftState.setShifted(newShiftState);
337    }
338
339    public boolean isShiftedOrShiftLocked() {
340        return mShiftState.isShiftedOrShiftLocked();
341    }
342
343    public void setAutomaticTemporaryUpperCase() {
344        setShifted(true);
345        mShiftState.setAutomaticTemporaryUpperCase();
346    }
347
348    public boolean isAutomaticTemporaryUpperCase() {
349        return isAlphaKeyboard() && mShiftState.isAutomaticTemporaryUpperCase();
350    }
351
352    public boolean isManualTemporaryUpperCase() {
353        return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCase();
354    }
355
356    public boolean isManualTemporaryUpperCaseFromAuto() {
357        return isAlphaKeyboard() && mShiftState.isManualTemporaryUpperCaseFromAuto();
358    }
359
360    public KeyboardShiftState getKeyboardShiftState() {
361        return mShiftState;
362    }
363
364    public boolean isAlphaKeyboard() {
365        return mId.isAlphabetKeyboard();
366    }
367
368    public boolean isPhoneKeyboard() {
369        return mId.isPhoneKeyboard();
370    }
371
372    public boolean isNumberKeyboard() {
373        return mId.isNumberKeyboard();
374    }
375
376    public CharSequence adjustLabelCase(CharSequence label) {
377        if (isShiftedOrShiftLocked() && !TextUtils.isEmpty(label) && label.length() < 3
378                && Character.isLowerCase(label.charAt(0))) {
379            return label.toString().toUpperCase(mId.mLocale);
380        }
381        return label;
382    }
383
384    /**
385     * Returns the indices of the keys that are closest to the given point.
386     * @param x the x-coordinate of the point
387     * @param y the y-coordinate of the point
388     * @return the array of integer indices for the nearest keys to the given point. If the given
389     * point is out of range, then an array of size zero is returned.
390     */
391    public int[] getNearestKeys(int x, int y) {
392        return mProximityInfo.getNearestKeys(x, y);
393    }
394
395    /**
396     * Compute the most common key width in order to use it as proximity key detection threshold.
397     *
398     * @return The most common key width in the keyboard
399     */
400    public int getMostCommonKeyWidth() {
401        if (mMostCommonKeyWidth == 0) {
402            final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>();
403            int maxCount = 0;
404            int mostCommonWidth = 0;
405            for (final Key key : mKeys) {
406                final Integer width = key.mWidth + key.mHorizontalGap;
407                Integer count = histogram.get(width);
408                if (count == null)
409                    count = 0;
410                histogram.put(width, ++count);
411                if (count > maxCount) {
412                    maxCount = count;
413                    mostCommonWidth = width;
414                }
415            }
416            mMostCommonKeyWidth = mostCommonWidth;
417        }
418        return mMostCommonKeyWidth;
419    }
420
421    private void loadKeyboard(Context context, int xmlLayoutResId) {
422        try {
423            KeyboardParser parser = new KeyboardParser(this, context);
424            parser.parseKeyboard(xmlLayoutResId);
425            // mMinWidth is the width of this keyboard which is maximum width of row.
426            mMinWidth = parser.getMaxRowWidth();
427            mTotalHeight = parser.getTotalHeight();
428        } catch (XmlPullParserException e) {
429            Log.w(TAG, "keyboard XML parse error: " + e);
430            throw new IllegalArgumentException(e);
431        } catch (IOException e) {
432            Log.w(TAG, "keyboard XML parse error: " + e);
433            throw new RuntimeException(e);
434        }
435    }
436}
437