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