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