Keyboard.java revision e49a987c23e2de67618edf12d66dd1a69f4614af
1/* 2 * Copyright (C) 2010 The Android Open Source Project 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 com.android.inputmethod.keyboard; 18 19import android.content.Context; 20import android.content.res.Resources; 21import android.content.res.TypedArray; 22import android.content.res.XmlResourceParser; 23import android.util.AttributeSet; 24import android.util.DisplayMetrics; 25import android.util.Log; 26import android.util.TypedValue; 27import android.util.Xml; 28import android.view.InflateException; 29 30import com.android.inputmethod.keyboard.internal.KeyStyles; 31import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; 32import com.android.inputmethod.latin.LatinImeLogger; 33import com.android.inputmethod.latin.R; 34import com.android.inputmethod.latin.Utils; 35import com.android.inputmethod.latin.XmlParseUtils; 36 37import org.xmlpull.v1.XmlPullParser; 38import org.xmlpull.v1.XmlPullParserException; 39 40import java.io.IOException; 41import java.util.ArrayList; 42import java.util.Arrays; 43import java.util.HashMap; 44import java.util.HashSet; 45 46/** 47 * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard 48 * consists of rows of keys. 49 * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p> 50 * <pre> 51 * <Keyboard 52 * latin:keyWidth="%10p" 53 * latin:keyHeight="50px" 54 * latin:horizontalGap="2px" 55 * latin:verticalGap="2px" > 56 * <Row latin:keyWidth="32px" > 57 * <Key latin:keyLabel="A" /> 58 * ... 59 * </Row> 60 * ... 61 * </Keyboard> 62 * </pre> 63 */ 64public class Keyboard { 65 private static final String TAG = Keyboard.class.getSimpleName(); 66 67 /** Some common keys code. Must be positive. 68 * These should be aligned with values/keycodes.xml 69 */ 70 public static final int CODE_ENTER = '\n'; 71 public static final int CODE_TAB = '\t'; 72 public static final int CODE_SPACE = ' '; 73 public static final int CODE_PERIOD = '.'; 74 public static final int CODE_DASH = '-'; 75 public static final int CODE_SINGLE_QUOTE = '\''; 76 public static final int CODE_DOUBLE_QUOTE = '"'; 77 // TODO: Check how this should work for right-to-left languages. It seems to stand 78 // that for rtl languages, a closing parenthesis is a left parenthesis. Is this 79 // managed by the font? Or is it a different char? 80 public static final int CODE_CLOSING_PARENTHESIS = ')'; 81 public static final int CODE_CLOSING_SQUARE_BRACKET = ']'; 82 public static final int CODE_CLOSING_CURLY_BRACKET = '}'; 83 public static final int CODE_CLOSING_ANGLE_BRACKET = '>'; 84 private static final int MINIMUM_LETTER_CODE = CODE_TAB; 85 86 /** Special keys code. Must be negative. 87 * These should be aligned with values/keycodes.xml 88 */ 89 public static final int CODE_SHIFT = -1; 90 public static final int CODE_SWITCH_ALPHA_SYMBOL = -2; 91 public static final int CODE_OUTPUT_TEXT = -3; 92 public static final int CODE_DELETE = -4; 93 public static final int CODE_SETTINGS = -5; 94 public static final int CODE_SHORTCUT = -6; 95 public static final int CODE_ACTION_ENTER = -7; 96 public static final int CODE_ACTION_NEXT = -8; 97 public static final int CODE_ACTION_PREVIOUS = -9; 98 public static final int CODE_LANGUAGE_SWITCH = -10; 99 // Code value representing the code is not specified. 100 public static final int CODE_UNSPECIFIED = -11; 101 102 public final KeyboardId mId; 103 public final int mThemeId; 104 105 /** Total height of the keyboard, including the padding and keys */ 106 public final int mOccupiedHeight; 107 /** Total width of the keyboard, including the padding and keys */ 108 public final int mOccupiedWidth; 109 110 /** The padding above the keyboard */ 111 public final int mTopPadding; 112 /** Default gap between rows */ 113 public final int mVerticalGap; 114 115 public final int mMostCommonKeyHeight; 116 public final int mMostCommonKeyWidth; 117 118 /** More keys keyboard template */ 119 public final int mMoreKeysTemplate; 120 121 /** Maximum column for more keys keyboard */ 122 public final int mMaxMoreKeysKeyboardColumn; 123 124 /** Array of keys and icons in this keyboard */ 125 public final Key[] mKeys; 126 public final Key[] mShiftKeys; 127 public final Key[] mAltCodeKeysWhileTyping; 128 public final KeyboardIconsSet mIconsSet; 129 130 private final HashMap<Integer, Key> mKeyCache = new HashMap<Integer, Key>(); 131 132 private final ProximityInfo mProximityInfo; 133 134 public Keyboard(Params params) { 135 mId = params.mId; 136 mThemeId = params.mThemeId; 137 mOccupiedHeight = params.mOccupiedHeight; 138 mOccupiedWidth = params.mOccupiedWidth; 139 mMostCommonKeyHeight = params.mMostCommonKeyHeight; 140 mMostCommonKeyWidth = params.mMostCommonKeyWidth; 141 mMoreKeysTemplate = params.mMoreKeysTemplate; 142 mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn; 143 144 mTopPadding = params.mTopPadding; 145 mVerticalGap = params.mVerticalGap; 146 147 mKeys = params.mKeys.toArray(new Key[params.mKeys.size()]); 148 mShiftKeys = params.mShiftKeys.toArray(new Key[params.mShiftKeys.size()]); 149 mAltCodeKeysWhileTyping = params.mAltCodeKeysWhileTyping.toArray( 150 new Key[params.mAltCodeKeysWhileTyping.size()]); 151 mIconsSet = params.mIconsSet; 152 153 mProximityInfo = new ProximityInfo(params.mId.mLocale.toString(), 154 params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight, 155 mMostCommonKeyWidth, mMostCommonKeyHeight, mKeys, params.mTouchPositionCorrection); 156 } 157 158 public ProximityInfo getProximityInfo() { 159 return mProximityInfo; 160 } 161 162 public Key getKey(int code) { 163 if (code == CODE_UNSPECIFIED) { 164 return null; 165 } 166 final Integer keyCode = code; 167 if (mKeyCache.containsKey(keyCode)) { 168 return mKeyCache.get(keyCode); 169 } 170 171 for (final Key key : mKeys) { 172 if (key.mCode == code) { 173 mKeyCache.put(keyCode, key); 174 return key; 175 } 176 } 177 mKeyCache.put(keyCode, null); 178 return null; 179 } 180 181 // TODO: Remove this method. 182 public boolean isShiftedOrShiftLocked() { 183 // Alphabet mode have unshifted, manual shifted, automatic shifted, shift locked, and 184 // shift lock shifted element. So that unshifed element is the only one that is NOT in 185 // shifted or shift locked state. 186 return mId.isAlphabetKeyboard() && mId.mElementId != KeyboardId.ELEMENT_ALPHABET; 187 } 188 189 public static boolean isLetterCode(int code) { 190 return code >= MINIMUM_LETTER_CODE; 191 } 192 193 public static class Params { 194 public KeyboardId mId; 195 public int mThemeId; 196 197 /** Total height and width of the keyboard, including the paddings and keys */ 198 public int mOccupiedHeight; 199 public int mOccupiedWidth; 200 201 /** Base height and width of the keyboard used to calculate rows' or keys' heights and 202 * widths 203 */ 204 public int mBaseHeight; 205 public int mBaseWidth; 206 207 public int mTopPadding; 208 public int mBottomPadding; 209 public int mHorizontalEdgesPadding; 210 public int mHorizontalCenterPadding; 211 212 public int mDefaultRowHeight; 213 public int mDefaultKeyWidth; 214 public int mHorizontalGap; 215 public int mVerticalGap; 216 217 public int mMoreKeysTemplate; 218 public int mMaxMoreKeysKeyboardColumn; 219 220 public int GRID_WIDTH; 221 public int GRID_HEIGHT; 222 223 public final HashSet<Key> mKeys = new HashSet<Key>(); 224 public final ArrayList<Key> mShiftKeys = new ArrayList<Key>(); 225 public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<Key>(); 226 public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet(); 227 228 public KeyboardSet.KeysCache mKeysCache; 229 230 public int mMostCommonKeyHeight = 0; 231 public int mMostCommonKeyWidth = 0; 232 233 public final TouchPositionCorrection mTouchPositionCorrection = 234 new TouchPositionCorrection(); 235 236 public static class TouchPositionCorrection { 237 private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3; 238 239 public boolean mEnabled; 240 public float[] mXs; 241 public float[] mYs; 242 public float[] mRadii; 243 244 public void load(String[] data) { 245 final int dataLength = data.length; 246 if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) { 247 if (LatinImeLogger.sDBG) 248 throw new RuntimeException( 249 "the size of touch position correction data is invalid"); 250 return; 251 } 252 253 final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE; 254 mXs = new float[length]; 255 mYs = new float[length]; 256 mRadii = new float[length]; 257 try { 258 for (int i = 0; i < dataLength; ++i) { 259 final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE; 260 final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE; 261 final float value = Float.parseFloat(data[i]); 262 if (type == 0) { 263 mXs[index] = value; 264 } else if (type == 1) { 265 mYs[index] = value; 266 } else { 267 mRadii[index] = value; 268 } 269 } 270 } catch (NumberFormatException e) { 271 if (LatinImeLogger.sDBG) { 272 throw new RuntimeException( 273 "the number format for touch position correction data is invalid"); 274 } 275 mXs = null; 276 mYs = null; 277 mRadii = null; 278 } 279 } 280 281 public void setEnabled(boolean enabled) { 282 mEnabled = enabled; 283 } 284 285 public boolean isValid() { 286 return mEnabled && mXs != null && mYs != null && mRadii != null 287 && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0; 288 } 289 } 290 291 protected void clearKeys() { 292 mKeys.clear(); 293 mShiftKeys.clear(); 294 clearHistogram(); 295 } 296 297 public void onAddKey(Key newKey) { 298 final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey; 299 mKeys.add(key); 300 updateHistogram(key); 301 if (key.mCode == Keyboard.CODE_SHIFT) { 302 mShiftKeys.add(key); 303 } 304 if (key.altCodeWhileTyping()) { 305 mAltCodeKeysWhileTyping.add(key); 306 } 307 } 308 309 private int mMaxHeightCount = 0; 310 private int mMaxWidthCount = 0; 311 private final HashMap<Integer, Integer> mHeightHistogram = new HashMap<Integer, Integer>(); 312 private final HashMap<Integer, Integer> mWidthHistogram = new HashMap<Integer, Integer>(); 313 314 private void clearHistogram() { 315 mMostCommonKeyHeight = 0; 316 mMaxHeightCount = 0; 317 mHeightHistogram.clear(); 318 319 mMaxWidthCount = 0; 320 mMostCommonKeyWidth = 0; 321 mWidthHistogram.clear(); 322 } 323 324 private static int updateHistogramCounter(HashMap<Integer, Integer> histogram, 325 Integer key) { 326 final int count = (histogram.containsKey(key) ? histogram.get(key) : 0) + 1; 327 histogram.put(key, count); 328 return count; 329 } 330 331 private void updateHistogram(Key key) { 332 final Integer height = key.mHeight + key.mVerticalGap; 333 final int heightCount = updateHistogramCounter(mHeightHistogram, height); 334 if (heightCount > mMaxHeightCount) { 335 mMaxHeightCount = heightCount; 336 mMostCommonKeyHeight = height; 337 } 338 339 final Integer width = key.mWidth + key.mHorizontalGap; 340 final int widthCount = updateHistogramCounter(mWidthHistogram, width); 341 if (widthCount > mMaxWidthCount) { 342 mMaxWidthCount = widthCount; 343 mMostCommonKeyWidth = width; 344 } 345 } 346 } 347 348 /** 349 * Returns the array of the keys that are closest to the given point. 350 * @param x the x-coordinate of the point 351 * @param y the y-coordinate of the point 352 * @return the array of the nearest keys to the given point. If the given 353 * point is out of range, then an array of size zero is returned. 354 */ 355 public Key[] getNearestKeys(int x, int y) { 356 // Avoid dead pixels at edges of the keyboard 357 final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1)); 358 final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1)); 359 return mProximityInfo.getNearestKeys(adjustedX, adjustedY); 360 } 361 362 public static String printableCode(int code) { 363 switch (code) { 364 case CODE_SHIFT: return "shift"; 365 case CODE_SWITCH_ALPHA_SYMBOL: return "symbol"; 366 case CODE_OUTPUT_TEXT: return "text"; 367 case CODE_DELETE: return "delete"; 368 case CODE_SETTINGS: return "settings"; 369 case CODE_SHORTCUT: return "shortcut"; 370 case CODE_ACTION_ENTER: return "actionEnter"; 371 case CODE_ACTION_NEXT: return "actionNext"; 372 case CODE_ACTION_PREVIOUS: return "actionPrevious"; 373 case CODE_LANGUAGE_SWITCH: return "languageSwitch"; 374 case CODE_UNSPECIFIED: return "unspec"; 375 case CODE_TAB: return "tab"; 376 case CODE_ENTER: return "enter"; 377 default: 378 if (code <= 0) Log.w(TAG, "Unknown non-positive key code=" + code); 379 if (code < CODE_SPACE) return String.format("'\\u%02x'", code); 380 if (code < 0x100) return String.format("'%c'", code); 381 return String.format("'\\u%04x'", code); 382 } 383 } 384 385 /** 386 * Keyboard Building helper. 387 * 388 * This class parses Keyboard XML file and eventually build a Keyboard. 389 * The Keyboard XML file looks like: 390 * <pre> 391 * >!-- xml/keyboard.xml --< 392 * >Keyboard keyboard_attributes*< 393 * >!-- Keyboard Content --< 394 * >Row row_attributes*< 395 * >!-- Row Content --< 396 * >Key key_attributes* /< 397 * >Spacer horizontalGap="0.2in" /< 398 * >include keyboardLayout="@xml/other_keys"< 399 * ... 400 * >/Row< 401 * >include keyboardLayout="@xml/other_rows"< 402 * ... 403 * >/Keyboard< 404 * </pre> 405 * The XML file which is included in other file must have >merge< as root element, 406 * such as: 407 * <pre> 408 * >!-- xml/other_keys.xml --< 409 * >merge< 410 * >Key key_attributes* /< 411 * ... 412 * >/merge< 413 * </pre> 414 * and 415 * <pre> 416 * >!-- xml/other_rows.xml --< 417 * >merge< 418 * >Row row_attributes*< 419 * >Key key_attributes* /< 420 * >/Row< 421 * ... 422 * >/merge< 423 * </pre> 424 * You can also use switch-case-default tags to select Rows and Keys. 425 * <pre> 426 * >switch< 427 * >case case_attribute*< 428 * >!-- Any valid tags at switch position --< 429 * >/case< 430 * ... 431 * >default< 432 * >!-- Any valid tags at switch position --< 433 * >/default< 434 * >/switch< 435 * </pre> 436 * You can declare Key style and specify styles within Key tags. 437 * <pre> 438 * >switch< 439 * >case mode="email"< 440 * >key-style styleName="f1-key" parentStyle="modifier-key" 441 * keyLabel=".com" 442 * /< 443 * >/case< 444 * >case mode="url"< 445 * >key-style styleName="f1-key" parentStyle="modifier-key" 446 * keyLabel="http://" 447 * /< 448 * >/case< 449 * >/switch< 450 * ... 451 * >Key keyStyle="shift-key" ... /< 452 * </pre> 453 */ 454 455 public static class Builder<KP extends Params> { 456 private static final String BUILDER_TAG = "Keyboard.Builder"; 457 private static final boolean DEBUG = false; 458 459 // Keyboard XML Tags 460 private static final String TAG_KEYBOARD = "Keyboard"; 461 private static final String TAG_ROW = "Row"; 462 private static final String TAG_KEY = "Key"; 463 private static final String TAG_SPACER = "Spacer"; 464 private static final String TAG_INCLUDE = "include"; 465 private static final String TAG_MERGE = "merge"; 466 private static final String TAG_SWITCH = "switch"; 467 private static final String TAG_CASE = "case"; 468 private static final String TAG_DEFAULT = "default"; 469 public static final String TAG_KEY_STYLE = "key-style"; 470 471 private static final int DEFAULT_KEYBOARD_COLUMNS = 10; 472 private static final int DEFAULT_KEYBOARD_ROWS = 4; 473 474 protected final KP mParams; 475 protected final Context mContext; 476 protected final Resources mResources; 477 private final DisplayMetrics mDisplayMetrics; 478 479 private int mCurrentY = 0; 480 private Row mCurrentRow = null; 481 private boolean mLeftEdge; 482 private boolean mTopEdge; 483 private Key mRightEdgeKey = null; 484 private final KeyStyles mKeyStyles = new KeyStyles(); 485 486 /** 487 * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate. 488 * Some of the key size defaults can be overridden per row from what the {@link Keyboard} 489 * defines. 490 */ 491 public static class Row { 492 // keyWidth enum constants 493 private static final int KEYWIDTH_NOT_ENUM = 0; 494 private static final int KEYWIDTH_FILL_RIGHT = -1; 495 private static final int KEYWIDTH_FILL_BOTH = -2; 496 497 private final Params mParams; 498 /** Default width of a key in this row. */ 499 private float mDefaultKeyWidth; 500 /** Default height of a key in this row. */ 501 public final int mRowHeight; 502 /** Default keyLabelFlags in this row. */ 503 private int mDefaultKeyLabelFlags; 504 505 private final int mCurrentY; 506 // Will be updated by {@link Key}'s constructor. 507 private float mCurrentX; 508 509 public Row(Resources res, Params params, XmlPullParser parser, int y) { 510 mParams = params; 511 TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser), 512 R.styleable.Keyboard); 513 mRowHeight = (int)Builder.getDimensionOrFraction(keyboardAttr, 514 R.styleable.Keyboard_rowHeight, 515 params.mBaseHeight, params.mDefaultRowHeight); 516 keyboardAttr.recycle(); 517 TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser), 518 R.styleable.Keyboard_Key); 519 mDefaultKeyWidth = Builder.getDimensionOrFraction(keyAttr, 520 R.styleable.Keyboard_Key_keyWidth, 521 params.mBaseWidth, params.mDefaultKeyWidth); 522 keyAttr.recycle(); 523 524 mDefaultKeyLabelFlags = 0; 525 mCurrentY = y; 526 mCurrentX = 0.0f; 527 } 528 529 public float getDefaultKeyWidth() { 530 return mDefaultKeyWidth; 531 } 532 533 public void setDefaultKeyWidth(float defaultKeyWidth) { 534 mDefaultKeyWidth = defaultKeyWidth; 535 } 536 537 public int getDefaultKeyLabelFlags() { 538 return mDefaultKeyLabelFlags; 539 } 540 541 public void setDefaultKeyLabelFlags(int keyLabelFlags) { 542 mDefaultKeyLabelFlags = keyLabelFlags; 543 } 544 545 public void setXPos(float keyXPos) { 546 mCurrentX = keyXPos; 547 } 548 549 public void advanceXPos(float width) { 550 mCurrentX += width; 551 } 552 553 public int getKeyY() { 554 return mCurrentY; 555 } 556 557 public float getKeyX(TypedArray keyAttr) { 558 final int widthType = Builder.getEnumValue(keyAttr, 559 R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM); 560 if (widthType == KEYWIDTH_FILL_BOTH) { 561 // If keyWidth is fillBoth, the key width should start right after the nearest 562 // key on the left hand side. 563 return mCurrentX; 564 } 565 566 final int keyboardRightEdge = mParams.mOccupiedWidth 567 - mParams.mHorizontalEdgesPadding; 568 if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) { 569 final float keyXPos = Builder.getDimensionOrFraction(keyAttr, 570 R.styleable.Keyboard_Key_keyXPos, mParams.mBaseWidth, 0); 571 if (keyXPos < 0) { 572 // If keyXPos is negative, the actual x-coordinate will be 573 // keyboardWidth + keyXPos. 574 // keyXPos shouldn't be less than mCurrentX because drawable area for this 575 // key starts at mCurrentX. Or, this key will overlaps the adjacent key on 576 // its left hand side. 577 return Math.max(keyXPos + keyboardRightEdge, mCurrentX); 578 } else { 579 return keyXPos + mParams.mHorizontalEdgesPadding; 580 } 581 } 582 return mCurrentX; 583 } 584 585 public float getKeyWidth(TypedArray keyAttr) { 586 return getKeyWidth(keyAttr, mCurrentX); 587 } 588 589 public float getKeyWidth(TypedArray keyAttr, float keyXPos) { 590 final int widthType = Builder.getEnumValue(keyAttr, 591 R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM); 592 switch (widthType) { 593 case KEYWIDTH_FILL_RIGHT: 594 case KEYWIDTH_FILL_BOTH: 595 final int keyboardRightEdge = 596 mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding; 597 // If keyWidth is fillRight, the actual key width will be determined to fill 598 // out the area up to the right edge of the keyboard. 599 // If keyWidth is fillBoth, the actual key width will be determined to fill out 600 // the area between the nearest key on the left hand side and the right edge of 601 // the keyboard. 602 return keyboardRightEdge - keyXPos; 603 default: // KEYWIDTH_NOT_ENUM 604 return Builder.getDimensionOrFraction(keyAttr, 605 R.styleable.Keyboard_Key_keyWidth, 606 mParams.mBaseWidth, mDefaultKeyWidth); 607 } 608 } 609 } 610 611 public Builder(Context context, KP params) { 612 mContext = context; 613 final Resources res = context.getResources(); 614 mResources = res; 615 mDisplayMetrics = res.getDisplayMetrics(); 616 617 mParams = params; 618 619 setTouchPositionCorrectionData(context, params); 620 621 params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width); 622 params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height); 623 } 624 625 private static void setTouchPositionCorrectionData(Context context, Params params) { 626 final TypedArray a = context.obtainStyledAttributes( 627 null, R.styleable.Keyboard, R.attr.keyboardStyle, 0); 628 params.mThemeId = a.getInt(R.styleable.Keyboard_themeId, 0); 629 final int resourceId = a.getResourceId( 630 R.styleable.Keyboard_touchPositionCorrectionData, 0); 631 a.recycle(); 632 if (resourceId == 0) { 633 if (LatinImeLogger.sDBG) 634 Log.e(BUILDER_TAG, "touchPositionCorrectionData is not defined"); 635 return; 636 } 637 638 final String[] data = context.getResources().getStringArray(resourceId); 639 params.mTouchPositionCorrection.load(data); 640 } 641 642 public void setAutoGenerate(KeyboardSet.KeysCache keysCache) { 643 mParams.mKeysCache = keysCache; 644 } 645 646 public Builder<KP> load(int xmlId, KeyboardId id) { 647 mParams.mId = id; 648 final XmlResourceParser parser = mResources.getXml(xmlId); 649 try { 650 parseKeyboard(parser); 651 } catch (XmlPullParserException e) { 652 Log.w(BUILDER_TAG, "keyboard XML parse error: " + e); 653 throw new IllegalArgumentException(e); 654 } catch (IOException e) { 655 Log.w(BUILDER_TAG, "keyboard XML parse error: " + e); 656 throw new RuntimeException(e); 657 } finally { 658 parser.close(); 659 } 660 return this; 661 } 662 663 public void setTouchPositionCorrectionEnabled(boolean enabled) { 664 mParams.mTouchPositionCorrection.setEnabled(enabled); 665 } 666 667 public Keyboard build() { 668 return new Keyboard(mParams); 669 } 670 671 private int mIndent; 672 private static final String SPACES = " "; 673 674 private static String spaces(int count) { 675 return (count < SPACES.length()) ? SPACES.substring(0, count) : SPACES; 676 } 677 678 private void startTag(String format, Object ... args) { 679 Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args)); 680 } 681 682 private void endTag(String format, Object ... args) { 683 Log.d(BUILDER_TAG, String.format(spaces(mIndent-- * 2) + format, args)); 684 } 685 686 private void startEndTag(String format, Object ... args) { 687 Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args)); 688 mIndent--; 689 } 690 691 private void parseKeyboard(XmlPullParser parser) 692 throws XmlPullParserException, IOException { 693 if (DEBUG) startTag("<%s> %s", TAG_KEYBOARD, mParams.mId); 694 int event; 695 while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { 696 if (event == XmlPullParser.START_TAG) { 697 final String tag = parser.getName(); 698 if (TAG_KEYBOARD.equals(tag)) { 699 parseKeyboardAttributes(parser); 700 startKeyboard(); 701 parseKeyboardContent(parser, false); 702 break; 703 } else { 704 throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD); 705 } 706 } 707 } 708 } 709 710 private void parseKeyboardAttributes(XmlPullParser parser) { 711 final int displayWidth = mDisplayMetrics.widthPixels; 712 final TypedArray keyboardAttr = mContext.obtainStyledAttributes( 713 Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle, 714 R.style.Keyboard); 715 final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), 716 R.styleable.Keyboard_Key); 717 try { 718 final int displayHeight = mDisplayMetrics.heightPixels; 719 final String keyboardHeightString = Utils.getDeviceOverrideValue( 720 mResources, R.array.keyboard_heights, null); 721 final float keyboardHeight; 722 if (keyboardHeightString != null) { 723 keyboardHeight = Float.parseFloat(keyboardHeightString) 724 * mDisplayMetrics.density; 725 } else { 726 keyboardHeight = keyboardAttr.getDimension( 727 R.styleable.Keyboard_keyboardHeight, displayHeight / 2); 728 } 729 final float maxKeyboardHeight = getDimensionOrFraction(keyboardAttr, 730 R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2); 731 float minKeyboardHeight = getDimensionOrFraction(keyboardAttr, 732 R.styleable.Keyboard_minKeyboardHeight, displayHeight, displayHeight / 2); 733 if (minKeyboardHeight < 0) { 734 // Specified fraction was negative, so it should be calculated against display 735 // width. 736 minKeyboardHeight = -getDimensionOrFraction(keyboardAttr, 737 R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2); 738 } 739 final Params params = mParams; 740 // Keyboard height will not exceed maxKeyboardHeight and will not be less than 741 // minKeyboardHeight. 742 params.mOccupiedHeight = (int)Math.max( 743 Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight); 744 params.mOccupiedWidth = params.mId.mWidth; 745 params.mTopPadding = (int)getDimensionOrFraction(keyboardAttr, 746 R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0); 747 params.mBottomPadding = (int)getDimensionOrFraction(keyboardAttr, 748 R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0); 749 params.mHorizontalEdgesPadding = (int)getDimensionOrFraction(keyboardAttr, 750 R.styleable.Keyboard_keyboardHorizontalEdgesPadding, 751 mParams.mOccupiedWidth, 0); 752 753 params.mBaseWidth = params.mOccupiedWidth - params.mHorizontalEdgesPadding * 2 754 - params.mHorizontalCenterPadding; 755 params.mDefaultKeyWidth = (int)getDimensionOrFraction(keyAttr, 756 R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth, 757 params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS); 758 params.mHorizontalGap = (int)getDimensionOrFraction(keyboardAttr, 759 R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0); 760 params.mVerticalGap = (int)getDimensionOrFraction(keyboardAttr, 761 R.styleable.Keyboard_verticalGap, params.mOccupiedHeight, 0); 762 params.mBaseHeight = params.mOccupiedHeight - params.mTopPadding 763 - params.mBottomPadding + params.mVerticalGap; 764 params.mDefaultRowHeight = (int)getDimensionOrFraction(keyboardAttr, 765 R.styleable.Keyboard_rowHeight, params.mBaseHeight, 766 params.mBaseHeight / DEFAULT_KEYBOARD_ROWS); 767 768 params.mMoreKeysTemplate = keyboardAttr.getResourceId( 769 R.styleable.Keyboard_moreKeysTemplate, 0); 770 params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt( 771 R.styleable.Keyboard_Key_maxMoreKeysColumn, 5); 772 773 params.mIconsSet.loadIcons(keyboardAttr); 774 } finally { 775 keyAttr.recycle(); 776 keyboardAttr.recycle(); 777 } 778 } 779 780 private void parseKeyboardContent(XmlPullParser parser, boolean skip) 781 throws XmlPullParserException, IOException { 782 int event; 783 while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { 784 if (event == XmlPullParser.START_TAG) { 785 final String tag = parser.getName(); 786 if (TAG_ROW.equals(tag)) { 787 Row row = parseRowAttributes(parser); 788 if (DEBUG) startTag("<%s>%s", TAG_ROW, skip ? " skipped" : ""); 789 if (!skip) { 790 startRow(row); 791 } 792 parseRowContent(parser, row, skip); 793 } else if (TAG_INCLUDE.equals(tag)) { 794 parseIncludeKeyboardContent(parser, skip); 795 } else if (TAG_SWITCH.equals(tag)) { 796 parseSwitchKeyboardContent(parser, skip); 797 } else if (TAG_KEY_STYLE.equals(tag)) { 798 parseKeyStyle(parser, skip); 799 } else { 800 throw new XmlParseUtils.IllegalStartTag(parser, TAG_ROW); 801 } 802 } else if (event == XmlPullParser.END_TAG) { 803 final String tag = parser.getName(); 804 if (DEBUG) endTag("</%s>", tag); 805 if (TAG_KEYBOARD.equals(tag)) { 806 endKeyboard(); 807 break; 808 } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) 809 || TAG_MERGE.equals(tag)) { 810 break; 811 } else { 812 throw new XmlParseUtils.IllegalEndTag(parser, TAG_ROW); 813 } 814 } 815 } 816 } 817 818 private Row parseRowAttributes(XmlPullParser parser) throws XmlPullParserException { 819 final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), 820 R.styleable.Keyboard); 821 try { 822 if (a.hasValue(R.styleable.Keyboard_horizontalGap)) 823 throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap"); 824 if (a.hasValue(R.styleable.Keyboard_verticalGap)) 825 throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap"); 826 return new Row(mResources, mParams, parser, mCurrentY); 827 } finally { 828 a.recycle(); 829 } 830 } 831 832 private void parseRowContent(XmlPullParser parser, Row row, boolean skip) 833 throws XmlPullParserException, IOException { 834 int event; 835 while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { 836 if (event == XmlPullParser.START_TAG) { 837 final String tag = parser.getName(); 838 if (TAG_KEY.equals(tag)) { 839 parseKey(parser, row, skip); 840 } else if (TAG_SPACER.equals(tag)) { 841 parseSpacer(parser, row, skip); 842 } else if (TAG_INCLUDE.equals(tag)) { 843 parseIncludeRowContent(parser, row, skip); 844 } else if (TAG_SWITCH.equals(tag)) { 845 parseSwitchRowContent(parser, row, skip); 846 } else if (TAG_KEY_STYLE.equals(tag)) { 847 parseKeyStyle(parser, skip); 848 } else { 849 throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY); 850 } 851 } else if (event == XmlPullParser.END_TAG) { 852 final String tag = parser.getName(); 853 if (DEBUG) endTag("</%s>", tag); 854 if (TAG_ROW.equals(tag)) { 855 if (!skip) { 856 endRow(row); 857 } 858 break; 859 } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) 860 || TAG_MERGE.equals(tag)) { 861 break; 862 } else { 863 throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY); 864 } 865 } 866 } 867 } 868 869 private void parseKey(XmlPullParser parser, Row row, boolean skip) 870 throws XmlPullParserException, IOException { 871 if (skip) { 872 XmlParseUtils.checkEndTag(TAG_KEY, parser); 873 if (DEBUG) startEndTag("<%s /> skipped", TAG_KEY); 874 } else { 875 final Key key = new Key(mResources, mParams, row, parser, mKeyStyles); 876 if (DEBUG) { 877 startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY, 878 (key.isEnabled() ? "" : " disabled"), key, 879 Arrays.toString(key.mMoreKeys)); 880 } 881 XmlParseUtils.checkEndTag(TAG_KEY, parser); 882 endKey(key); 883 } 884 } 885 886 private void parseSpacer(XmlPullParser parser, Row row, boolean skip) 887 throws XmlPullParserException, IOException { 888 if (skip) { 889 XmlParseUtils.checkEndTag(TAG_SPACER, parser); 890 if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER); 891 } else { 892 final Key.Spacer spacer = new Key.Spacer( 893 mResources, mParams, row, parser, mKeyStyles); 894 if (DEBUG) startEndTag("<%s />", TAG_SPACER); 895 XmlParseUtils.checkEndTag(TAG_SPACER, parser); 896 endKey(spacer); 897 } 898 } 899 900 private void parseIncludeKeyboardContent(XmlPullParser parser, boolean skip) 901 throws XmlPullParserException, IOException { 902 parseIncludeInternal(parser, null, skip); 903 } 904 905 private void parseIncludeRowContent(XmlPullParser parser, Row row, boolean skip) 906 throws XmlPullParserException, IOException { 907 parseIncludeInternal(parser, row, skip); 908 } 909 910 private void parseIncludeInternal(XmlPullParser parser, Row row, boolean skip) 911 throws XmlPullParserException, IOException { 912 if (skip) { 913 XmlParseUtils.checkEndTag(TAG_INCLUDE, parser); 914 if (DEBUG) startEndTag("</%s> skipped", TAG_INCLUDE); 915 } else { 916 final AttributeSet attr = Xml.asAttributeSet(parser); 917 final TypedArray keyboardAttr = mResources.obtainAttributes(attr, 918 R.styleable.Keyboard_Include); 919 final TypedArray keyAttr = mResources.obtainAttributes(attr, 920 R.styleable.Keyboard_Key); 921 int keyboardLayout = 0; 922 float savedDefaultKeyWidth = 0; 923 int savedDefaultKeyLabelFlags = 0; 924 try { 925 XmlParseUtils.checkAttributeExists(keyboardAttr, 926 R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout", 927 TAG_INCLUDE, parser); 928 keyboardLayout = keyboardAttr.getResourceId( 929 R.styleable.Keyboard_Include_keyboardLayout, 0); 930 if (row != null) { 931 savedDefaultKeyWidth = row.getDefaultKeyWidth(); 932 savedDefaultKeyLabelFlags = row.getDefaultKeyLabelFlags(); 933 if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) { 934 // Override current x coordinate. 935 row.setXPos(row.getKeyX(keyAttr)); 936 } 937 if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyWidth)) { 938 // Override default key width. 939 row.setDefaultKeyWidth(row.getKeyWidth(keyAttr)); 940 } 941 if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyLabelFlags)) { 942 // Override default key label flags. 943 row.setDefaultKeyLabelFlags( 944 keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0) 945 | savedDefaultKeyLabelFlags); 946 } 947 } 948 } finally { 949 keyboardAttr.recycle(); 950 keyAttr.recycle(); 951 } 952 953 XmlParseUtils.checkEndTag(TAG_INCLUDE, parser); 954 if (DEBUG) { 955 startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE, 956 mResources.getResourceEntryName(keyboardLayout)); 957 } 958 final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout); 959 try { 960 parseMerge(parserForInclude, row, skip); 961 } finally { 962 if (row != null) { 963 // Restore default key width and key label flags. 964 row.setDefaultKeyWidth(savedDefaultKeyWidth); 965 row.setDefaultKeyLabelFlags(savedDefaultKeyLabelFlags); 966 } 967 parserForInclude.close(); 968 } 969 } 970 } 971 972 private void parseMerge(XmlPullParser parser, Row row, boolean skip) 973 throws XmlPullParserException, IOException { 974 if (DEBUG) startTag("<%s>", TAG_MERGE); 975 int event; 976 while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { 977 if (event == XmlPullParser.START_TAG) { 978 final String tag = parser.getName(); 979 if (TAG_MERGE.equals(tag)) { 980 if (row == null) { 981 parseKeyboardContent(parser, skip); 982 } else { 983 parseRowContent(parser, row, skip); 984 } 985 break; 986 } else { 987 throw new XmlParseUtils.ParseException( 988 "Included keyboard layout must have <merge> root element", parser); 989 } 990 } 991 } 992 } 993 994 private void parseSwitchKeyboardContent(XmlPullParser parser, boolean skip) 995 throws XmlPullParserException, IOException { 996 parseSwitchInternal(parser, null, skip); 997 } 998 999 private void parseSwitchRowContent(XmlPullParser parser, Row row, boolean skip) 1000 throws XmlPullParserException, IOException { 1001 parseSwitchInternal(parser, row, skip); 1002 } 1003 1004 private void parseSwitchInternal(XmlPullParser parser, Row row, boolean skip) 1005 throws XmlPullParserException, IOException { 1006 if (DEBUG) startTag("<%s> %s", TAG_SWITCH, mParams.mId); 1007 boolean selected = false; 1008 int event; 1009 while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { 1010 if (event == XmlPullParser.START_TAG) { 1011 final String tag = parser.getName(); 1012 if (TAG_CASE.equals(tag)) { 1013 selected |= parseCase(parser, row, selected ? true : skip); 1014 } else if (TAG_DEFAULT.equals(tag)) { 1015 selected |= parseDefault(parser, row, selected ? true : skip); 1016 } else { 1017 throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY); 1018 } 1019 } else if (event == XmlPullParser.END_TAG) { 1020 final String tag = parser.getName(); 1021 if (TAG_SWITCH.equals(tag)) { 1022 if (DEBUG) endTag("</%s>", TAG_SWITCH); 1023 break; 1024 } else { 1025 throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY); 1026 } 1027 } 1028 } 1029 } 1030 1031 private boolean parseCase(XmlPullParser parser, Row row, boolean skip) 1032 throws XmlPullParserException, IOException { 1033 final boolean selected = parseCaseCondition(parser); 1034 if (row == null) { 1035 // Processing Rows. 1036 parseKeyboardContent(parser, selected ? skip : true); 1037 } else { 1038 // Processing Keys. 1039 parseRowContent(parser, row, selected ? skip : true); 1040 } 1041 return selected; 1042 } 1043 1044 private boolean parseCaseCondition(XmlPullParser parser) { 1045 final KeyboardId id = mParams.mId; 1046 if (id == null) 1047 return true; 1048 1049 final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), 1050 R.styleable.Keyboard_Case); 1051 try { 1052 final boolean keyboardSetElementMatched = matchTypedValue(a, 1053 R.styleable.Keyboard_Case_keyboardSetElement, id.mElementId, 1054 KeyboardId.elementIdToName(id.mElementId)); 1055 final boolean modeMatched = matchTypedValue(a, 1056 R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode)); 1057 final boolean navigateNextMatched = matchBoolean(a, 1058 R.styleable.Keyboard_Case_navigateNext, id.navigateNext()); 1059 final boolean navigatePreviousMatched = matchBoolean(a, 1060 R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious()); 1061 final boolean passwordInputMatched = matchBoolean(a, 1062 R.styleable.Keyboard_Case_passwordInput, id.passwordInput()); 1063 final boolean clobberSettingsKeyMatched = matchBoolean(a, 1064 R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey); 1065 final boolean shortcutKeyEnabledMatched = matchBoolean(a, 1066 R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled); 1067 final boolean hasShortcutKeyMatched = matchBoolean(a, 1068 R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey); 1069 final boolean languageSwitchKeyEnabledMatched = matchBoolean(a, 1070 R.styleable.Keyboard_Case_languageSwitchKeyEnabled, 1071 id.mLanguageSwitchKeyEnabled); 1072 final boolean isMultiLineMatched = matchBoolean(a, 1073 R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine()); 1074 final boolean imeActionMatched = matchInteger(a, 1075 R.styleable.Keyboard_Case_imeAction, id.imeAction()); 1076 final boolean localeCodeMatched = matchString(a, 1077 R.styleable.Keyboard_Case_localeCode, id.mLocale.toString()); 1078 final boolean languageCodeMatched = matchString(a, 1079 R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage()); 1080 final boolean countryCodeMatched = matchString(a, 1081 R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry()); 1082 final boolean selected = keyboardSetElementMatched && modeMatched 1083 && navigateNextMatched && navigatePreviousMatched && passwordInputMatched 1084 && clobberSettingsKeyMatched && shortcutKeyEnabledMatched 1085 && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched 1086 && isMultiLineMatched && imeActionMatched && localeCodeMatched 1087 && languageCodeMatched && countryCodeMatched; 1088 1089 if (DEBUG) { 1090 startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE, 1091 textAttr(a.getString(R.styleable.Keyboard_Case_keyboardSetElement), 1092 "keyboardSetElement"), 1093 textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"), 1094 textAttr(a.getString(R.styleable.Keyboard_Case_imeAction), 1095 "imeAction"), 1096 booleanAttr(a, R.styleable.Keyboard_Case_navigateNext, 1097 "navigateNext"), 1098 booleanAttr(a, R.styleable.Keyboard_Case_navigatePrevious, 1099 "navigatePrevious"), 1100 booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey, 1101 "clobberSettingsKey"), 1102 booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, 1103 "passwordInput"), 1104 booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled, 1105 "shortcutKeyEnabled"), 1106 booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey, 1107 "hasShortcutKey"), 1108 booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled, 1109 "languageSwitchKeyEnabled"), 1110 booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine, 1111 "isMultiLine"), 1112 textAttr(a.getString(R.styleable.Keyboard_Case_localeCode), 1113 "localeCode"), 1114 textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), 1115 "languageCode"), 1116 textAttr(a.getString(R.styleable.Keyboard_Case_countryCode), 1117 "countryCode"), 1118 selected ? "" : " skipped"); 1119 } 1120 1121 return selected; 1122 } finally { 1123 a.recycle(); 1124 } 1125 } 1126 1127 private static boolean matchInteger(TypedArray a, int index, int value) { 1128 // If <case> does not have "index" attribute, that means this <case> is wild-card for 1129 // the attribute. 1130 return !a.hasValue(index) || a.getInt(index, 0) == value; 1131 } 1132 1133 private static boolean matchBoolean(TypedArray a, int index, boolean value) { 1134 // If <case> does not have "index" attribute, that means this <case> is wild-card for 1135 // the attribute. 1136 return !a.hasValue(index) || a.getBoolean(index, false) == value; 1137 } 1138 1139 private static boolean matchString(TypedArray a, int index, String value) { 1140 // If <case> does not have "index" attribute, that means this <case> is wild-card for 1141 // the attribute. 1142 return !a.hasValue(index) 1143 || stringArrayContains(a.getString(index).split("\\|"), value); 1144 } 1145 1146 private static boolean matchTypedValue(TypedArray a, int index, int intValue, 1147 String strValue) { 1148 // If <case> does not have "index" attribute, that means this <case> is wild-card for 1149 // the attribute. 1150 final TypedValue v = a.peekValue(index); 1151 if (v == null) 1152 return true; 1153 1154 if (isIntegerValue(v)) { 1155 return intValue == a.getInt(index, 0); 1156 } else if (isStringValue(v)) { 1157 return stringArrayContains(a.getString(index).split("\\|"), strValue); 1158 } 1159 return false; 1160 } 1161 1162 private static boolean stringArrayContains(String[] array, String value) { 1163 for (final String elem : array) { 1164 if (elem.equals(value)) 1165 return true; 1166 } 1167 return false; 1168 } 1169 1170 private boolean parseDefault(XmlPullParser parser, Row row, boolean skip) 1171 throws XmlPullParserException, IOException { 1172 if (DEBUG) startTag("<%s>", TAG_DEFAULT); 1173 if (row == null) { 1174 parseKeyboardContent(parser, skip); 1175 } else { 1176 parseRowContent(parser, row, skip); 1177 } 1178 return true; 1179 } 1180 1181 private void parseKeyStyle(XmlPullParser parser, boolean skip) 1182 throws XmlPullParserException, IOException { 1183 TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), 1184 R.styleable.Keyboard_KeyStyle); 1185 TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser), 1186 R.styleable.Keyboard_Key); 1187 try { 1188 if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) 1189 throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE 1190 + "/> needs styleName attribute", parser); 1191 if (DEBUG) { 1192 startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE, 1193 keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName), 1194 skip ? " skipped" : ""); 1195 } 1196 if (!skip) 1197 mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser); 1198 } finally { 1199 keyStyleAttr.recycle(); 1200 keyAttrs.recycle(); 1201 } 1202 XmlParseUtils.checkEndTag(TAG_KEY_STYLE, parser); 1203 } 1204 1205 private void startKeyboard() { 1206 mCurrentY += mParams.mTopPadding; 1207 mTopEdge = true; 1208 } 1209 1210 private void startRow(Row row) { 1211 addEdgeSpace(mParams.mHorizontalEdgesPadding, row); 1212 mCurrentRow = row; 1213 mLeftEdge = true; 1214 mRightEdgeKey = null; 1215 } 1216 1217 private void endRow(Row row) { 1218 if (mCurrentRow == null) 1219 throw new InflateException("orphant end row tag"); 1220 if (mRightEdgeKey != null) { 1221 mRightEdgeKey.markAsRightEdge(mParams); 1222 mRightEdgeKey = null; 1223 } 1224 addEdgeSpace(mParams.mHorizontalEdgesPadding, row); 1225 mCurrentY += row.mRowHeight; 1226 mCurrentRow = null; 1227 mTopEdge = false; 1228 } 1229 1230 private void endKey(Key key) { 1231 mParams.onAddKey(key); 1232 if (mLeftEdge) { 1233 key.markAsLeftEdge(mParams); 1234 mLeftEdge = false; 1235 } 1236 if (mTopEdge) { 1237 key.markAsTopEdge(mParams); 1238 } 1239 mRightEdgeKey = key; 1240 } 1241 1242 private void endKeyboard() { 1243 // nothing to do here. 1244 } 1245 1246 private void addEdgeSpace(float width, Row row) { 1247 row.advanceXPos(width); 1248 mLeftEdge = false; 1249 mRightEdgeKey = null; 1250 } 1251 1252 public static float getDimensionOrFraction(TypedArray a, int index, int base, 1253 float defValue) { 1254 final TypedValue value = a.peekValue(index); 1255 if (value == null) 1256 return defValue; 1257 if (isFractionValue(value)) { 1258 return a.getFraction(index, base, base, defValue); 1259 } else if (isDimensionValue(value)) { 1260 return a.getDimension(index, defValue); 1261 } 1262 return defValue; 1263 } 1264 1265 public static int getEnumValue(TypedArray a, int index, int defValue) { 1266 final TypedValue value = a.peekValue(index); 1267 if (value == null) 1268 return defValue; 1269 if (isIntegerValue(value)) { 1270 return a.getInt(index, defValue); 1271 } 1272 return defValue; 1273 } 1274 1275 private static boolean isFractionValue(TypedValue v) { 1276 return v.type == TypedValue.TYPE_FRACTION; 1277 } 1278 1279 private static boolean isDimensionValue(TypedValue v) { 1280 return v.type == TypedValue.TYPE_DIMENSION; 1281 } 1282 1283 private static boolean isIntegerValue(TypedValue v) { 1284 return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT; 1285 } 1286 1287 private static boolean isStringValue(TypedValue v) { 1288 return v.type == TypedValue.TYPE_STRING; 1289 } 1290 1291 private static String textAttr(String value, String name) { 1292 return value != null ? String.format(" %s=%s", name, value) : ""; 1293 } 1294 1295 private static String booleanAttr(TypedArray a, int index, String name) { 1296 return a.hasValue(index) 1297 ? String.format(" %s=%s", name, a.getBoolean(index, false)) : ""; 1298 } 1299 } 1300} 1301