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