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