Keyboard.java revision d24b8183b93e781080b2c16c487e60d51c12da31
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.mDisplayHeight, 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.mDisplayHeight, 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 * Returns the square of the distance between the center of the key and the given point. 443 * @param x the x-coordinate of the point 444 * @param y the y-coordinate of the point 445 * @return the square of the distance of the point from the center of the key 446 */ 447 public int squaredDistanceFrom(int x, int y) { 448 int xDist = this.x + width / 2 - x; 449 int yDist = this.y + height / 2 - y; 450 return xDist * xDist + yDist * yDist; 451 } 452 453 /** 454 * Returns the drawable state for the key, based on the current state and type of the key. 455 * @return the drawable state of the key. 456 * @see android.graphics.drawable.StateListDrawable#setState(int[]) 457 */ 458 public int[] getCurrentDrawableState() { 459 int[] states = KEY_STATE_NORMAL; 460 461 if (on) { 462 if (pressed) { 463 states = KEY_STATE_PRESSED_ON; 464 } else { 465 states = KEY_STATE_NORMAL_ON; 466 } 467 } else { 468 if (sticky) { 469 if (pressed) { 470 states = KEY_STATE_PRESSED_OFF; 471 } else { 472 states = KEY_STATE_NORMAL_OFF; 473 } 474 } else { 475 if (pressed) { 476 states = KEY_STATE_PRESSED; 477 } 478 } 479 } 480 return states; 481 } 482 } 483 484 /** 485 * Creates a keyboard from the given xml key layout file. 486 * @param context the application or service context 487 * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. 488 */ 489 public Keyboard(Context context, int xmlLayoutResId) { 490 this(context, xmlLayoutResId, 0); 491 } 492 493 /** 494 * Creates a keyboard from the given xml key layout file. Weeds out rows 495 * that have a keyboard mode defined but don't match the specified mode. 496 * @param context the application or service context 497 * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. 498 * @param modeId keyboard mode identifier 499 */ 500 public Keyboard(Context context, int xmlLayoutResId, int modeId) { 501 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 502 final Display display = wm.getDefaultDisplay(); 503 mDisplayWidth = display.getWidth(); 504 mDisplayHeight = display.getHeight(); 505 mDefaultHorizontalGap = 0; 506 mDefaultWidth = mDisplayWidth / 10; 507 mDefaultVerticalGap = 0; 508 mDefaultHeight = mDefaultWidth; 509 mKeys = new ArrayList<Key>(); 510 mModifierKeys = new ArrayList<Key>(); 511 mKeyboardMode = modeId; 512 loadKeyboard(context, context.getResources().getXml(xmlLayoutResId)); 513 } 514 515 /** 516 * <p>Creates a blank keyboard from the given resource file and populates it with the specified 517 * characters in left-to-right, top-to-bottom fashion, using the specified number of columns. 518 * </p> 519 * <p>If the specified number of columns is -1, then the keyboard will fit as many keys as 520 * possible in each row.</p> 521 * @param context the application or service context 522 * @param layoutTemplateResId the layout template file, containing no keys. 523 * @param characters the list of characters to display on the keyboard. One key will be created 524 * for each character. 525 * @param columns the number of columns of keys to display. If this number is greater than the 526 * number of keys that can fit in a row, it will be ignored. If this number is -1, the 527 * keyboard will fit as many keys as possible in each row. 528 */ 529 public Keyboard(Context context, int layoutTemplateResId, 530 CharSequence characters, int columns, int horizontalPadding) { 531 this(context, layoutTemplateResId); 532 int x = 0; 533 int y = 0; 534 int column = 0; 535 mTotalWidth = 0; 536 537 Row row = new Row(this); 538 row.defaultHeight = mDefaultHeight; 539 row.defaultWidth = mDefaultWidth; 540 row.defaultHorizontalGap = mDefaultHorizontalGap; 541 row.verticalGap = mDefaultVerticalGap; 542 row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM; 543 final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns; 544 for (int i = 0; i < characters.length(); i++) { 545 char c = characters.charAt(i); 546 if (column >= maxColumns 547 || x + mDefaultWidth + horizontalPadding > mDisplayWidth) { 548 x = 0; 549 y += mDefaultVerticalGap + mDefaultHeight; 550 column = 0; 551 } 552 final Key key = new Key(row); 553 key.x = x; 554 key.y = y; 555 key.width = mDefaultWidth; 556 key.height = mDefaultHeight; 557 key.gap = mDefaultHorizontalGap; 558 key.label = String.valueOf(c); 559 key.codes = new int[] { c }; 560 column++; 561 x += key.width + key.gap; 562 mKeys.add(key); 563 if (x > mTotalWidth) { 564 mTotalWidth = x; 565 } 566 } 567 mTotalHeight = y + mDefaultHeight; 568 } 569 570 public List<Key> getKeys() { 571 return mKeys; 572 } 573 574 public List<Key> getModifierKeys() { 575 return mModifierKeys; 576 } 577 578 protected int getHorizontalGap() { 579 return mDefaultHorizontalGap; 580 } 581 582 protected void setHorizontalGap(int gap) { 583 mDefaultHorizontalGap = gap; 584 } 585 586 protected int getVerticalGap() { 587 return mDefaultVerticalGap; 588 } 589 590 protected void setVerticalGap(int gap) { 591 mDefaultVerticalGap = gap; 592 } 593 594 protected int getKeyHeight() { 595 return mDefaultHeight; 596 } 597 598 protected void setKeyHeight(int height) { 599 mDefaultHeight = height; 600 } 601 602 protected int getKeyWidth() { 603 return mDefaultWidth; 604 } 605 606 protected void setKeyWidth(int width) { 607 mDefaultWidth = width; 608 } 609 610 /** 611 * Returns the total height of the keyboard 612 * @return the total height of the keyboard 613 */ 614 public int getHeight() { 615 return mTotalHeight; 616 } 617 618 public int getMinWidth() { 619 return mTotalWidth; 620 } 621 622 public boolean setShifted(boolean shiftState) { 623 if (mShiftKey != null) { 624 mShiftKey.on = shiftState; 625 } 626 if (mShifted != shiftState) { 627 mShifted = shiftState; 628 return true; 629 } 630 return false; 631 } 632 633 public boolean isShifted() { 634 return mShifted; 635 } 636 637 public int getShiftKeyIndex() { 638 return mShiftKeyIndex; 639 } 640 641 protected Row createRowFromXml(Resources res, XmlResourceParser parser) { 642 return new Row(res, this, parser); 643 } 644 645 protected Key createKeyFromXml(Resources res, Row parent, int x, int y, 646 XmlResourceParser parser) { 647 return new Key(res, parent, x, y, parser); 648 } 649 650 private void loadKeyboard(Context context, XmlResourceParser parser) { 651 boolean inKey = false; 652 boolean inRow = false; 653 boolean leftMostKey = false; 654 int row = 0; 655 int x = 0; 656 int y = 0; 657 Key key = null; 658 Row currentRow = null; 659 Resources res = context.getResources(); 660 boolean skipRow = false; 661 662 try { 663 int event; 664 while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { 665 if (event == XmlResourceParser.START_TAG) { 666 String tag = parser.getName(); 667 if (TAG_ROW.equals(tag)) { 668 inRow = true; 669 x = 0; 670 currentRow = createRowFromXml(res, parser); 671 skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode; 672 if (skipRow) { 673 skipToEndOfRow(parser); 674 inRow = false; 675 } 676 } else if (TAG_KEY.equals(tag)) { 677 inKey = true; 678 key = createKeyFromXml(res, currentRow, x, y, parser); 679 mKeys.add(key); 680 if (key.codes[0] == KEYCODE_SHIFT) { 681 mShiftKey = key; 682 mShiftKeyIndex = mKeys.size()-1; 683 mModifierKeys.add(key); 684 } else if (key.codes[0] == KEYCODE_ALT) { 685 mModifierKeys.add(key); 686 } 687 } else if (TAG_KEYBOARD.equals(tag)) { 688 parseKeyboardAttributes(res, parser); 689 } 690 } else if (event == XmlResourceParser.END_TAG) { 691 if (inKey) { 692 inKey = false; 693 x += key.gap + key.width; 694 if (x > mTotalWidth) { 695 mTotalWidth = x; 696 } 697 } else if (inRow) { 698 inRow = false; 699 y += currentRow.verticalGap; 700 y += currentRow.defaultHeight; 701 row++; 702 } else { 703 // TODO: error or extend? 704 } 705 } 706 } 707 } catch (Exception e) { 708 Log.e(TAG, "Parse error:" + e); 709 e.printStackTrace(); 710 } 711 mTotalHeight = y - mDefaultVerticalGap; 712 } 713 714 private void skipToEndOfRow(XmlResourceParser parser) 715 throws XmlPullParserException, IOException { 716 int event; 717 while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { 718 if (event == XmlResourceParser.END_TAG 719 && parser.getName().equals(TAG_ROW)) { 720 break; 721 } 722 } 723 } 724 725 private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) { 726 TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), 727 com.android.internal.R.styleable.Keyboard); 728 729 mDefaultWidth = getDimensionOrFraction(a, 730 com.android.internal.R.styleable.Keyboard_keyWidth, 731 mDisplayWidth, mDisplayWidth / 10); 732 mDefaultHeight = getDimensionOrFraction(a, 733 com.android.internal.R.styleable.Keyboard_keyHeight, 734 mDisplayHeight, 50); 735 mDefaultHorizontalGap = getDimensionOrFraction(a, 736 com.android.internal.R.styleable.Keyboard_horizontalGap, 737 mDisplayWidth, 0); 738 mDefaultVerticalGap = getDimensionOrFraction(a, 739 com.android.internal.R.styleable.Keyboard_verticalGap, 740 mDisplayHeight, 0); 741 a.recycle(); 742 } 743 744 static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) { 745 TypedValue value = a.peekValue(index); 746 if (value == null) return defValue; 747 if (value.type == TypedValue.TYPE_DIMENSION) { 748 return a.getDimensionPixelOffset(index, defValue); 749 } else if (value.type == TypedValue.TYPE_FRACTION) { 750 // Round it to avoid values like 47.9999 from getting truncated 751 return Math.round(a.getFraction(index, base, base, defValue)); 752 } 753 return defValue; 754 } 755} 756