1 /*
2  * Copyright (C) 2008-2012  OMRON SOFTWARE Co., Ltd.
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  * This file is porting from Android framework.
18  *   frameworks/base/core/java/android/inputmethodservice/Keyboard.java
19  *
20  * package android.inputmethodservice;
21  */
22 package jp.co.omronsoft.openwnn;
23 
24 import android.content.Context;
25 import android.content.res.Resources;
26 import android.content.res.TypedArray;
27 import android.content.res.XmlResourceParser;
28 import android.graphics.drawable.Drawable;
29 import android.text.TextUtils;
30 import android.util.Log;
31 import android.util.TypedValue;
32 import android.util.Xml;
33 import android.util.DisplayMetrics;
34 
35 import java.io.IOException;
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.StringTokenizer;
39 
40 import org.xmlpull.v1.XmlPullParserException;
41 
42 /**
43  * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
44  * consists of rows of keys.
45  * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p>
46  * <pre>
47  * &lt;Keyboard
48  *         android:keyWidth="%10p"
49  *         android:keyHeight="50px"
50  *         android:horizontalGap="2px"
51  *         android:verticalGap="2px" &gt;
52  *     &lt;Row android:keyWidth="32px" &gt;
53  *         &lt;Key android:keyLabel="A" /&gt;
54  *         ...
55  *     &lt;/Row&gt;
56  *     ...
57  * &lt;/Keyboard&gt;
58  * </pre>
59  */
60 public class Keyboard {
61 
62     static final String TAG = "Keyboard";
63 
64     private static final String TAG_KEYBOARD = "Keyboard";
65     private static final String TAG_ROW = "Row";
66     private static final String TAG_KEY = "Key";
67 
68     /** Edge of left */
69     public static final int EDGE_LEFT = 0x01;
70 
71     /** Edge of right */
72     public static final int EDGE_RIGHT = 0x02;
73 
74     /** Edge of top */
75     public static final int EDGE_TOP = 0x04;
76 
77     /** Edge of bottom */
78     public static final int EDGE_BOTTOM = 0x08;
79 
80     /** Keycode of SHIFT */
81     public static final int KEYCODE_SHIFT = -1;
82 
83     /** Keycode of MODE_CHANGE */
84     public static final int KEYCODE_MODE_CHANGE = -2;
85 
86     /** Keycode of CANCEL */
87     public static final int KEYCODE_CANCEL = -3;
88 
89     /** Keycode of DONE */
90     public static final int KEYCODE_DONE = -4;
91 
92     /** Keycode of DELETE */
93     public static final int KEYCODE_DELETE = -5;
94 
95     /** Keycode of ALT */
96     public static final int KEYCODE_ALT = -6;
97 
98     /** Keyboard label **/
99     private CharSequence mLabel;
100 
101     /** Horizontal gap default for all rows */
102     private int mDefaultHorizontalGap;
103 
104     /** Default key width */
105     private int mDefaultWidth;
106 
107     /** Default key height */
108     private int mDefaultHeight;
109 
110     /** Default gap between rows */
111     private int mDefaultVerticalGap;
112 
113     /** Is the keyboard in the shifted state */
114     private boolean mShifted;
115 
116     /** Key instance for the shift key, if present */
117     private Key mShiftKey;
118 
119     /** Key index for the shift key, if present */
120     private int mShiftKeyIndex = -1;
121 
122     /** Current key width, while loading the keyboard */
123     private int mKeyWidth;
124 
125     /** Current key height, while loading the keyboard */
126     private int mKeyHeight;
127 
128     /** Total height of the keyboard, including the padding and keys */
129     private int mTotalHeight;
130 
131     /**
132      * Total width of the keyboard, including left side gaps and keys, but not any gaps on the
133      * right side.
134      */
135     private int mTotalWidth;
136 
137     /** List of keys in this keyboard */
138     private List<Key> mKeys;
139 
140     /** List of modifier keys such as Shift & Alt, if any */
141     private List<Key> mModifierKeys;
142 
143     /** Width of the screen available to fit the keyboard */
144     private int mDisplayWidth;
145 
146     /** Height of the screen */
147     private int mDisplayHeight;
148 
149     /** Keyboard mode, or zero, if none.  */
150     private int mKeyboardMode;
151 
152 
153     private static final int GRID_WIDTH = 10;
154     private static final int GRID_HEIGHT = 5;
155     private static final int GRID_SIZE = GRID_WIDTH * GRID_HEIGHT;
156     private int mCellWidth;
157     private int mCellHeight;
158     private int[][] mGridNeighbors;
159     private int mProximityThreshold;
160     /** Number of key widths from current touch point to search for nearest keys. */
161     private static float SEARCH_DISTANCE = 1.8f;
162 
163     /**
164      * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
165      * Some of the key size defaults can be overridden per row from what the {@link Keyboard}
166      * defines.
167      */
168     public static class Row {
169         /** Default width of a key in this row. */
170         public int defaultWidth;
171         /** Default height of a key in this row. */
172         public int defaultHeight;
173         /** Default horizontal gap between keys in this row. */
174         public int defaultHorizontalGap;
175         /** Vertical gap following this row. */
176         public int verticalGap;
177         /**
178          * Edge flags for this row of keys. Possible values that can be assigned are
179          * {@link Keyboard#EDGE_TOP EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM EDGE_BOTTOM}
180          */
181         public int rowEdgeFlags;
182 
183         /** The keyboard mode for this row */
184         public int mode;
185 
186         private Keyboard parent;
187 
188         /** Constructor */
Row(Keyboard parent)189         public Row(Keyboard parent) {
190             this.parent = parent;
191         }
192 
193         /** Constructor */
Row(Resources res, Keyboard parent, XmlResourceParser parser)194         public Row(Resources res, Keyboard parent, XmlResourceParser parser) {
195             this.parent = parent;
196             TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
197                     android.R.styleable.Keyboard);
198             defaultWidth = getDimensionOrFraction(a,
199                     android.R.styleable.Keyboard_keyWidth,
200                     parent.mDisplayWidth, parent.mDefaultWidth);
201             defaultHeight = getDimensionOrFraction(a,
202                     android.R.styleable.Keyboard_keyHeight,
203                     parent.mDisplayHeight, parent.mDefaultHeight);
204             defaultHorizontalGap = getDimensionOrFraction(a,
205                     android.R.styleable.Keyboard_horizontalGap,
206                     parent.mDisplayWidth, parent.mDefaultHorizontalGap);
207             verticalGap = getDimensionOrFraction(a,
208                     android.R.styleable.Keyboard_verticalGap,
209                     parent.mDisplayHeight, parent.mDefaultVerticalGap);
210             a.recycle();
211             a = res.obtainAttributes(Xml.asAttributeSet(parser),
212                     android.R.styleable.Keyboard_Row);
213             rowEdgeFlags = a.getInt(android.R.styleable.Keyboard_Row_rowEdgeFlags, 0);
214             mode = a.getResourceId(android.R.styleable.Keyboard_Row_keyboardMode,
215                     0);
216         }
217     }
218 
219     /**
220      * Class for describing the position and characteristics of a single key in the keyboard.
221      */
222     public static class Key {
223         /**
224          * All the key codes (unicode or custom code) that this key could generate, zero'th
225          * being the most important.
226          */
227         public int[] codes;
228 
229         /** Label to display */
230         public CharSequence label;
231 
232         /** Icon to display instead of a label. Icon takes precedence over a label */
233         public Drawable icon;
234         /** Preview version of the icon, for the preview popup */
235         public Drawable iconPreview;
236         /** Width of the key, not including the gap */
237         public int width;
238         /** Height of the key, not including the gap */
239         public int height;
240         /** The horizontal gap before this key */
241         public int gap;
242         /** Whether this key is sticky, i.e., a toggle key */
243         public boolean sticky;
244         /** X coordinate of the key in the keyboard layout */
245         public int x;
246         /** Y coordinate of the key in the keyboard layout */
247         public int y;
248         /** The current pressed state of this key */
249         public boolean pressed;
250         /** If this is a sticky key, is it on? */
251         public boolean on;
252         /** Text to output when pressed. This can be multiple characters, like ".com" */
253         public CharSequence text;
254         /** Popup characters */
255         public CharSequence popupCharacters;
256 
257         /**
258          * Flags that specify the anchoring to edges of the keyboard for detecting touch events
259          * that are just out of the boundary of the key. This is a bit mask of
260          * {@link Keyboard#EDGE_LEFT}, {@link Keyboard#EDGE_RIGHT}, {@link Keyboard#EDGE_TOP} and
261          * {@link Keyboard#EDGE_BOTTOM}.
262          */
263         public int edgeFlags;
264         /** Whether this is a modifier key, such as Shift or Alt */
265         public boolean modifier;
266         /** The keyboard that this key belongs to */
267         private Keyboard keyboard;
268         /**
269          * If this key pops up a mini keyboard, this is the resource id for the XML layout for that
270          * keyboard.
271          */
272         public int popupResId;
273         /** Whether this key repeats itself when held down */
274         public boolean repeatable;
275         /** Whether this key is 2nd key */
276         public boolean isSecondKey;
277 
278         private final static int[] KEY_STATE_NORMAL_ON = {
279             android.R.attr.state_checkable,
280             android.R.attr.state_checked
281         };
282 
283         private final static int[] KEY_STATE_PRESSED_ON = {
284             android.R.attr.state_pressed,
285             android.R.attr.state_checkable,
286             android.R.attr.state_checked
287         };
288 
289         private final static int[] KEY_STATE_NORMAL_OFF = {
290             android.R.attr.state_checkable
291         };
292 
293         private final static int[] KEY_STATE_PRESSED_OFF = {
294             android.R.attr.state_pressed,
295             android.R.attr.state_checkable
296         };
297 
298         private final static int[] KEY_STATE_NORMAL = {
299         };
300 
301         private final static int[] KEY_STATE_PRESSED = {
302             android.R.attr.state_pressed
303         };
304 
305         /** Create an empty key with no attributes. */
Key(Row parent)306         public Key(Row parent) {
307             keyboard = parent.parent;
308             height = parent.defaultHeight;
309             width = parent.defaultWidth;
310             gap = parent.defaultHorizontalGap;
311             edgeFlags = parent.rowEdgeFlags;
312         }
313 
314         /** Create a key with the given top-left coordinate and extract its attributes from
315          * the XML parser.
316          * @param res resources associated with the caller's context
317          * @param parent the row that this key belongs to. The row must already be attached to
318          * a {@link Keyboard}.
319          * @param x the x coordinate of the top-left
320          * @param y the y coordinate of the top-left
321          * @param parser the XML parser containing the attributes for this key
322          */
Key(Resources res, Row parent, int x, int y, XmlResourceParser parser)323         public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser) {
324             this(parent);
325 
326             this.x = x;
327             this.y = y;
328 
329             TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
330                     android.R.styleable.Keyboard);
331 
332             width = getDimensionOrFraction(a,
333                     android.R.styleable.Keyboard_keyWidth,
334                     keyboard.mDisplayWidth, parent.defaultWidth);
335             height = getDimensionOrFraction(a,
336                     android.R.styleable.Keyboard_keyHeight,
337                     keyboard.mDisplayHeight, parent.defaultHeight);
338             gap = getDimensionOrFraction(a,
339                     android.R.styleable.Keyboard_horizontalGap,
340                     keyboard.mDisplayWidth, parent.defaultHorizontalGap);
341             a.recycle();
342             a = res.obtainAttributes(Xml.asAttributeSet(parser),
343                     android.R.styleable.Keyboard_Key);
344             this.x += gap;
345             TypedValue codesValue = new TypedValue();
346             a.getValue(android.R.styleable.Keyboard_Key_codes,
347                     codesValue);
348             if (codesValue.type == TypedValue.TYPE_INT_DEC
349                     || codesValue.type == TypedValue.TYPE_INT_HEX) {
350                 codes = new int[] { codesValue.data };
351             } else if (codesValue.type == TypedValue.TYPE_STRING) {
352                 codes = parseCSV(codesValue.string.toString());
353             }
354 
355             iconPreview = a.getDrawable(android.R.styleable.Keyboard_Key_iconPreview);
356             if (iconPreview != null) {
357                 iconPreview.setBounds(0, 0, iconPreview.getIntrinsicWidth(),
358                         iconPreview.getIntrinsicHeight());
359             }
360             popupCharacters = a.getText(
361                     android.R.styleable.Keyboard_Key_popupCharacters);
362             popupResId = a.getResourceId(
363                     android.R.styleable.Keyboard_Key_popupKeyboard, 0);
364             repeatable = a.getBoolean(
365                     android.R.styleable.Keyboard_Key_isRepeatable, false);
366             modifier = a.getBoolean(
367                     android.R.styleable.Keyboard_Key_isModifier, false);
368             sticky = a.getBoolean(
369                     android.R.styleable.Keyboard_Key_isSticky, false);
370             edgeFlags = a.getInt(android.R.styleable.Keyboard_Key_keyEdgeFlags, 0);
371             edgeFlags |= parent.rowEdgeFlags;
372 
373             icon = a.getDrawable(
374                     android.R.styleable.Keyboard_Key_keyIcon);
375             if (icon != null) {
376                 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
377             }
378             label = a.getText(android.R.styleable.Keyboard_Key_keyLabel);
379             text = a.getText(android.R.styleable.Keyboard_Key_keyOutputText);
380 
381             if (codes == null && !TextUtils.isEmpty(label)) {
382                 codes = new int[] { label.charAt(0) };
383             }
384             a.recycle();
385             a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.WnnKeyboard_Key);
386             isSecondKey = a.getBoolean(R.styleable.WnnKeyboard_Key_isSecondKey, false);
387             a.recycle();
388         }
389 
390         /**
391          * Informs the key that it has been pressed, in case it needs to change its appearance or
392          * state.
393          * @see #onReleased(boolean)
394          */
onPressed()395         public void onPressed() {
396             pressed = !pressed;
397         }
398 
399         /**
400          * Changes the pressed state of the key. If it is a sticky key, it will also change the
401          * toggled state of the key if the finger was release inside.
402          * @param inside whether the finger was released inside the key
403          * @see #onPressed()
404          */
onReleased(boolean inside)405         public void onReleased(boolean inside) {
406             pressed = !pressed;
407             if (sticky) {
408                 on = !on;
409             }
410         }
411 
parseCSV(String value)412         int[] parseCSV(String value) {
413             int count = 0;
414             int lastIndex = 0;
415             if (value.length() > 0) {
416                 count++;
417                 while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
418                     count++;
419                 }
420             }
421             int[] values = new int[count];
422             count = 0;
423             StringTokenizer st = new StringTokenizer(value, ",");
424             while (st.hasMoreTokens()) {
425                 try {
426                     values[count++] = Integer.parseInt(st.nextToken());
427                 } catch (NumberFormatException nfe) {
428                     Log.e(TAG, "Error parsing keycodes " + value);
429                 }
430             }
431             return values;
432         }
433 
434         /**
435          * Detects if a point falls inside this key.
436          * @param x the x-coordinate of the point
437          * @param y the y-coordinate of the point
438          * @return whether or not the point falls inside the key. If the key is attached to an edge,
439          * it will assume that all points between the key and the edge are considered to be inside
440          * the key.
441          */
isInside(int x, int y)442         public boolean isInside(int x, int y) {
443             boolean leftEdge = (edgeFlags & EDGE_LEFT) > 0;
444             boolean rightEdge = (edgeFlags & EDGE_RIGHT) > 0;
445             boolean topEdge = (edgeFlags & EDGE_TOP) > 0;
446             boolean bottomEdge = (edgeFlags & EDGE_BOTTOM) > 0;
447             if ((x >= this.x || (leftEdge && x <= this.x + this.width))
448                     && (x < this.x + this.width || (rightEdge && x >= this.x))
449                     && (y >= this.y || (topEdge && y <= this.y + this.height))
450                     && (y < this.y + this.height || (bottomEdge && y >= this.y))) {
451                 return true;
452             } else {
453                 return false;
454             }
455         }
456 
457         /**
458          * Detects if a area falls inside this key.
459          * @param x the x-coordinate of the area
460          * @param y the y-coordinate of the area
461          * @param w the width of the area
462          * @param h the height of the area
463          * @return whether or not the area falls inside the key.
464          */
isInside(int x, int y, int w, int h)465         public boolean isInside(int x, int y, int w, int h) {
466             if ((this.x <= (x + w)) && (x <= (this.x + this.width))
467                     && (this.y <= (y + h)) && (y <= (this.y + this.height))) {
468                 return true;
469             } else {
470                 return false;
471             }
472         }
473 
474         /**
475          * Returns the square of the distance between the center of the key and the given point.
476          * @param x the x-coordinate of the point
477          * @param y the y-coordinate of the point
478          * @return the square of the distance of the point from the center of the key
479          */
squaredDistanceFrom(int x, int y)480         public int squaredDistanceFrom(int x, int y) {
481             int xDist = this.x + width / 2 - x;
482             int yDist = this.y + height / 2 - y;
483             return xDist * xDist + yDist * yDist;
484         }
485 
486         /**
487          * Returns the drawable state for the key, based on the current state and type of the key.
488          * @return the drawable state of the key.
489          * @see android.graphics.drawable.StateListDrawable#setState(int[])
490          */
getCurrentDrawableState()491         public int[] getCurrentDrawableState() {
492             int[] states = KEY_STATE_NORMAL;
493 
494             if (on) {
495                 if (pressed) {
496                     states = KEY_STATE_PRESSED_ON;
497                 } else {
498                     states = KEY_STATE_NORMAL_ON;
499                 }
500             } else {
501                 if (sticky) {
502                     if (pressed) {
503                         states = KEY_STATE_PRESSED_OFF;
504                     } else {
505                         states = KEY_STATE_NORMAL_OFF;
506                     }
507                 } else {
508                     if (pressed) {
509                         states = KEY_STATE_PRESSED;
510                     }
511                 }
512             }
513             return states;
514         }
515     }
516 
517     /**
518      * Creates a keyboard from the given xml key layout file.
519      * @param context the application or service context
520      * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
521      */
Keyboard(Context context, int xmlLayoutResId)522     public Keyboard(Context context, int xmlLayoutResId) {
523         this(context, xmlLayoutResId, 0);
524     }
525 
526     /**
527      * Creates a keyboard from the given xml key layout file. Weeds out rows
528      * that have a keyboard mode defined but don't match the specified mode.
529      * @param context the application or service context
530      * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
531      * @param modeId keyboard mode identifier
532      */
Keyboard(Context context, int xmlLayoutResId, int modeId)533     public Keyboard(Context context, int xmlLayoutResId, int modeId) {
534         DisplayMetrics dm = context.getResources().getDisplayMetrics();
535         mDisplayWidth = dm.widthPixels;
536         mDisplayHeight = dm.heightPixels;
537 
538         mDefaultHorizontalGap = 0;
539         mDefaultWidth = mDisplayWidth / 10;
540         mDefaultVerticalGap = 0;
541         mDefaultHeight = mDefaultWidth;
542         mKeys = new ArrayList<Key>();
543         mModifierKeys = new ArrayList<Key>();
544         mKeyboardMode = modeId;
545         loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
546     }
547 
548     /**
549      * <p>Creates a blank keyboard from the given resource file and populates it with the specified
550      * characters in left-to-right, top-to-bottom fashion, using the specified number of columns.
551      * </p>
552      * <p>If the specified number of columns is -1, then the keyboard will fit as many keys as
553      * possible in each row.</p>
554      * @param context the application or service context
555      * @param layoutTemplateResId the layout template file, containing no keys.
556      * @param characters the list of characters to display on the keyboard. One key will be created
557      * for each character.
558      * @param columns the number of columns of keys to display. If this number is greater than the
559      * number of keys that can fit in a row, it will be ignored. If this number is -1, the
560      * keyboard will fit as many keys as possible in each row.
561      */
Keyboard(Context context, int layoutTemplateResId, CharSequence characters, int columns, int horizontalPadding)562     public Keyboard(Context context, int layoutTemplateResId,
563             CharSequence characters, int columns, int horizontalPadding) {
564         this(context, layoutTemplateResId);
565         int x = 0;
566         int y = 0;
567         int column = 0;
568         mTotalWidth = 0;
569 
570         Row row = new Row(this);
571         row.defaultHeight = mDefaultHeight;
572         row.defaultWidth = mDefaultWidth;
573         row.defaultHorizontalGap = mDefaultHorizontalGap;
574         row.verticalGap = mDefaultVerticalGap;
575         row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM;
576         final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns;
577         for (int i = 0; i < characters.length(); i++) {
578             char c = characters.charAt(i);
579             if (column >= maxColumns
580                     || x + mDefaultWidth + horizontalPadding > mDisplayWidth) {
581                 x = 0;
582                 y += mDefaultVerticalGap + mDefaultHeight;
583                 column = 0;
584             }
585             final Key key = new Key(row);
586             key.x = x;
587             key.y = y;
588             key.label = String.valueOf(c);
589             key.codes = new int[] { c };
590             column++;
591             x += key.width + key.gap;
592             mKeys.add(key);
593             if (x > mTotalWidth) {
594                 mTotalWidth = x;
595             }
596         }
597         mTotalHeight = y + mDefaultHeight;
598     }
599 
600     /**
601      * Get the list of keys in this keyboard.
602      *
603      * @return The list of keys.
604      */
getKeys()605     public List<Key> getKeys() {
606         return mKeys;
607     }
608 
609     /**
610      * Get the list of modifier keys such as Shift & Alt, if any.
611      *
612      * @return The list of modifier keys.
613      */
getModifierKeys()614     public List<Key> getModifierKeys() {
615         return mModifierKeys;
616     }
617 
getHorizontalGap()618     protected int getHorizontalGap() {
619         return mDefaultHorizontalGap;
620     }
621 
setHorizontalGap(int gap)622     protected void setHorizontalGap(int gap) {
623         mDefaultHorizontalGap = gap;
624     }
625 
getVerticalGap()626     protected int getVerticalGap() {
627         return mDefaultVerticalGap;
628     }
629 
setVerticalGap(int gap)630     protected void setVerticalGap(int gap) {
631         mDefaultVerticalGap = gap;
632     }
633 
getKeyHeight()634     protected int getKeyHeight() {
635         return mDefaultHeight;
636     }
637 
setKeyHeight(int height)638     protected void setKeyHeight(int height) {
639         mDefaultHeight = height;
640     }
641 
getKeyWidth()642     protected int getKeyWidth() {
643         return mDefaultWidth;
644     }
645 
setKeyWidth(int width)646     protected void setKeyWidth(int width) {
647         mDefaultWidth = width;
648     }
649 
650     /**
651      * Returns the total height of the keyboard
652      * @return the total height of the keyboard
653      */
getHeight()654     public int getHeight() {
655         return mTotalHeight;
656     }
657 
658     /**
659      * Returns the total minimum width of the keyboard
660      * @return the total minimum width of the keyboard
661      */
getMinWidth()662     public int getMinWidth() {
663         return mTotalWidth;
664     }
665 
666     /**
667      * Sets the keyboard to be shifted.
668      *
669      * @param shiftState  the keyboard shift state.
670      * @return {@code true} if shift state changed.
671      */
setShifted(boolean shiftState)672     public boolean setShifted(boolean shiftState) {
673         if (mShiftKey != null) {
674             mShiftKey.on = shiftState;
675         }
676         if (mShifted != shiftState) {
677             mShifted = shiftState;
678             return true;
679         }
680         return false;
681     }
682 
683     /**
684      * Returns whether keyboard is shift state or not.
685      *
686      * @return  {@code true} if keyboard is shift state; otherwise, {@code false}.
687      */
isShifted()688     public boolean isShifted() {
689         return mShifted;
690     }
691 
692     /**
693      * Returns the shift key index.
694      *
695      * @return  the shift key index.
696      */
getShiftKeyIndex()697     public int getShiftKeyIndex() {
698         return mShiftKeyIndex;
699     }
700 
computeNearestNeighbors()701     private void computeNearestNeighbors() {
702         mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH;
703         mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT;
704         mGridNeighbors = new int[GRID_SIZE][];
705         int[] indices = new int[mKeys.size()];
706         final int gridWidth = GRID_WIDTH * mCellWidth;
707         final int gridHeight = GRID_HEIGHT * mCellHeight;
708         for (int x = 0; x < gridWidth; x += mCellWidth) {
709             for (int y = 0; y < gridHeight; y += mCellHeight) {
710                 int count = 0;
711                 for (int i = 0; i < mKeys.size(); i++) {
712                     final Key key = mKeys.get(i);
713                     if (key.squaredDistanceFrom(x, y) < mProximityThreshold ||
714                             key.squaredDistanceFrom(x + mCellWidth - 1, y) < mProximityThreshold ||
715                             key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1)
716                                 < mProximityThreshold ||
717                             key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold ||
718                             key.isInside(x, y, mCellWidth, mCellHeight)) {
719                         indices[count++] = i;
720                     }
721                 }
722                 int [] cell = new int[count];
723                 System.arraycopy(indices, 0, cell, 0, count);
724                 mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell;
725             }
726         }
727     }
728 
729     /**
730      * Returns the indices of the keys that are closest to the given point.
731      * @param x the x-coordinate of the point
732      * @param y the y-coordinate of the point
733      * @return the array of integer indices for the nearest keys to the given point. If the given
734      * point is out of range, then an array of size zero is returned.
735      */
getNearestKeys(int x, int y)736     public int[] getNearestKeys(int x, int y) {
737         if (mGridNeighbors == null) computeNearestNeighbors();
738         if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) {
739             int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth);
740             if (index < GRID_SIZE) {
741                 return mGridNeighbors[index];
742             }
743         }
744         return new int[0];
745     }
746 
createRowFromXml(Resources res, XmlResourceParser parser)747     protected Row createRowFromXml(Resources res, XmlResourceParser parser) {
748         return new Row(res, this, parser);
749     }
750 
createKeyFromXml(Resources res, Row parent, int x, int y, XmlResourceParser parser)751     protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
752             XmlResourceParser parser) {
753         return new Key(res, parent, x, y, parser);
754     }
755 
loadKeyboard(Context context, XmlResourceParser parser)756     private void loadKeyboard(Context context, XmlResourceParser parser) {
757         boolean inKey = false;
758         boolean inRow = false;
759         boolean leftMostKey = false;
760         int row = 0;
761         int x = 0;
762         int y = 0;
763         Key key = null;
764         Row currentRow = null;
765         Resources res = context.getResources();
766         boolean skipRow = false;
767 
768         try {
769             int event;
770             while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
771                 if (event == XmlResourceParser.START_TAG) {
772                     String tag = parser.getName();
773                     if (TAG_ROW.equals(tag)) {
774                         inRow = true;
775                         x = 0;
776                         currentRow = createRowFromXml(res, parser);
777                         skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode;
778                         if (skipRow) {
779                             skipToEndOfRow(parser);
780                             inRow = false;
781                         }
782                    } else if (TAG_KEY.equals(tag)) {
783                         inKey = true;
784                         key = createKeyFromXml(res, currentRow, x, y, parser);
785                         mKeys.add(key);
786                         if (key.codes[0] == KEYCODE_SHIFT) {
787                             mShiftKey = key;
788                             mShiftKeyIndex = mKeys.size()-1;
789                             mModifierKeys.add(key);
790                         } else if (key.codes[0] == KEYCODE_ALT) {
791                             mModifierKeys.add(key);
792                         }
793                     } else if (TAG_KEYBOARD.equals(tag)) {
794                         parseKeyboardAttributes(res, parser);
795                     }
796                 } else if (event == XmlResourceParser.END_TAG) {
797                     if (inKey) {
798                         inKey = false;
799                         x += key.gap + key.width;
800                         if (x > mTotalWidth) {
801                             mTotalWidth = x;
802                         }
803                     } else if (inRow) {
804                         inRow = false;
805                         y += currentRow.verticalGap;
806                         y += currentRow.defaultHeight;
807                         row++;
808                     } else {
809                     }
810                 }
811             }
812         } catch (Exception e) {
813             Log.e(TAG, "Parse error:" + e);
814             e.printStackTrace();
815         }
816         mTotalHeight = y - mDefaultVerticalGap;
817     }
818 
skipToEndOfRow(XmlResourceParser parser)819     private void skipToEndOfRow(XmlResourceParser parser)
820             throws XmlPullParserException, IOException {
821         int event;
822         while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
823             if (event == XmlResourceParser.END_TAG
824                     && parser.getName().equals(TAG_ROW)) {
825                 break;
826             }
827         }
828     }
829 
parseKeyboardAttributes(Resources res, XmlResourceParser parser)830     private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) {
831         TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
832                 android.R.styleable.Keyboard);
833 
834         mDefaultWidth = getDimensionOrFraction(a,
835                 android.R.styleable.Keyboard_keyWidth,
836                 mDisplayWidth, mDisplayWidth / 10);
837         mDefaultHeight = getDimensionOrFraction(a,
838                 android.R.styleable.Keyboard_keyHeight,
839                 mDisplayHeight, 75);
840         mDefaultHorizontalGap = getDimensionOrFraction(a,
841                 android.R.styleable.Keyboard_horizontalGap,
842                 mDisplayWidth, 0);
843         mDefaultVerticalGap = getDimensionOrFraction(a,
844                 android.R.styleable.Keyboard_verticalGap,
845                 mDisplayHeight, 0);
846         mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE);
847         mProximityThreshold = mProximityThreshold * mProximityThreshold;
848         a.recycle();
849     }
850 
getDimensionOrFraction(TypedArray a, int index, int base, int defValue)851     static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) {
852         TypedValue value = a.peekValue(index);
853         if (value == null) return defValue;
854         if (value.type == TypedValue.TYPE_DIMENSION) {
855             return a.getDimensionPixelOffset(index, defValue);
856         } else if (value.type == TypedValue.TYPE_FRACTION) {
857             return Math.round(a.getFraction(index, base, base, defValue));
858         }
859         return defValue;
860     }
861 }
862