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