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