Keyboard.java revision b798689749c64baba81f02e10cf2157c747d6b46
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 * 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 544 final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns; 545 for (int i = 0; i < characters.length(); i++) { 546 char c = characters.charAt(i); 547 if (column >= maxColumns 548 || x + mDefaultWidth + horizontalPadding > mDisplayWidth) { 549 x = 0; 550 y += mDefaultVerticalGap + mDefaultHeight; 551 column = 0; 552 } 553 final Key key = new Key(row); 554 key.x = x; 555 key.y = y; 556 key.width = mDefaultWidth; 557 key.height = mDefaultHeight; 558 key.gap = mDefaultHorizontalGap; 559 key.label = String.valueOf(c); 560 key.codes = new int[] { c }; 561 column++; 562 x += key.width + key.gap; 563 mKeys.add(key); 564 if (x > mTotalWidth) { 565 mTotalWidth = x; 566 } 567 } 568 mTotalHeight = y + mDefaultHeight; 569 } 570 571 public List<Key> getKeys() { 572 return mKeys; 573 } 574 575 public List<Key> getModifierKeys() { 576 return mModifierKeys; 577 } 578 579 protected int getHorizontalGap() { 580 return mDefaultHorizontalGap; 581 } 582 583 protected void setHorizontalGap(int gap) { 584 mDefaultHorizontalGap = gap; 585 } 586 587 protected int getVerticalGap() { 588 return mDefaultVerticalGap; 589 } 590 591 protected void setVerticalGap(int gap) { 592 mDefaultVerticalGap = gap; 593 } 594 595 protected int getKeyHeight() { 596 return mDefaultHeight; 597 } 598 599 protected void setKeyHeight(int height) { 600 mDefaultHeight = height; 601 } 602 603 protected int getKeyWidth() { 604 return mDefaultWidth; 605 } 606 607 protected void setKeyWidth(int width) { 608 mDefaultWidth = width; 609 } 610 611 /** 612 * Returns the total height of the keyboard 613 * @return the total height of the keyboard 614 */ 615 public int getHeight() { 616 return mTotalHeight; 617 } 618 619 public int getMinWidth() { 620 return mTotalWidth; 621 } 622 623 public boolean setShifted(boolean shiftState) { 624 if (mShiftKey != null) { 625 mShiftKey.on = shiftState; 626 } 627 if (mShifted != shiftState) { 628 mShifted = shiftState; 629 return true; 630 } 631 return false; 632 } 633 634 public boolean isShifted() { 635 return mShifted; 636 } 637 638 public int getShiftKeyIndex() { 639 return mShiftKeyIndex; 640 } 641 642 protected Row createRowFromXml(Resources res, XmlResourceParser parser) { 643 return new Row(res, this, parser); 644 } 645 646 protected Key createKeyFromXml(Resources res, Row parent, int x, int y, 647 XmlResourceParser parser) { 648 return new Key(res, parent, x, y, parser); 649 } 650 651 private void loadKeyboard(Context context, XmlResourceParser parser) { 652 boolean inKey = false; 653 boolean inRow = false; 654 boolean leftMostKey = false; 655 int row = 0; 656 int x = 0; 657 int y = 0; 658 Key key = null; 659 Row currentRow = null; 660 Resources res = context.getResources(); 661 boolean skipRow = false; 662 663 try { 664 int event; 665 while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { 666 if (event == XmlResourceParser.START_TAG) { 667 String tag = parser.getName(); 668 if (TAG_ROW.equals(tag)) { 669 inRow = true; 670 x = 0; 671 currentRow = createRowFromXml(res, parser); 672 skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode; 673 if (skipRow) { 674 skipToEndOfRow(parser); 675 inRow = false; 676 } 677 } else if (TAG_KEY.equals(tag)) { 678 inKey = true; 679 key = createKeyFromXml(res, currentRow, x, y, parser); 680 mKeys.add(key); 681 if (key.codes[0] == KEYCODE_SHIFT) { 682 mShiftKey = key; 683 mShiftKeyIndex = mKeys.size()-1; 684 mModifierKeys.add(key); 685 } else if (key.codes[0] == KEYCODE_ALT) { 686 mModifierKeys.add(key); 687 } 688 } else if (TAG_KEYBOARD.equals(tag)) { 689 parseKeyboardAttributes(res, parser); 690 } 691 } else if (event == XmlResourceParser.END_TAG) { 692 if (inKey) { 693 inKey = false; 694 x += key.gap + key.width; 695 if (x > mTotalWidth) { 696 mTotalWidth = x; 697 } 698 } else if (inRow) { 699 inRow = false; 700 y += currentRow.verticalGap; 701 y += currentRow.defaultHeight; 702 row++; 703 } else { 704 // TODO: error or extend? 705 } 706 } 707 } 708 } catch (Exception e) { 709 Log.e(TAG, "Parse error:" + e); 710 e.printStackTrace(); 711 } 712 mTotalHeight = y - mDefaultVerticalGap; 713 } 714 715 private void skipToEndOfRow(XmlResourceParser parser) 716 throws XmlPullParserException, IOException { 717 int event; 718 while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) { 719 if (event == XmlResourceParser.END_TAG 720 && parser.getName().equals(TAG_ROW)) { 721 break; 722 } 723 } 724 } 725 726 private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) { 727 TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), 728 com.android.internal.R.styleable.Keyboard); 729 730 mDefaultWidth = getDimensionOrFraction(a, 731 com.android.internal.R.styleable.Keyboard_keyWidth, 732 mDisplayWidth, mDisplayWidth / 10); 733 mDefaultHeight = getDimensionOrFraction(a, 734 com.android.internal.R.styleable.Keyboard_keyHeight, 735 mDisplayHeight, 50); 736 mDefaultHorizontalGap = getDimensionOrFraction(a, 737 com.android.internal.R.styleable.Keyboard_horizontalGap, 738 mDisplayWidth, 0); 739 mDefaultVerticalGap = getDimensionOrFraction(a, 740 com.android.internal.R.styleable.Keyboard_verticalGap, 741 mDisplayHeight, 0); 742 a.recycle(); 743 } 744 745 static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) { 746 TypedValue value = a.peekValue(index); 747 if (value == null) return defValue; 748 if (value.type == TypedValue.TYPE_DIMENSION) { 749 return a.getDimensionPixelOffset(index, defValue); 750 } else if (value.type == TypedValue.TYPE_FRACTION) { 751 // Round it to avoid values like 47.9999 from getting truncated 752 return Math.round(a.getFraction(index, base, base, defValue)); 753 } 754 return defValue; 755 } 756} 757