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