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