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