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