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