Key.java revision 35ff94547c16c84c5b6fafdae0b4a683be782b97
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 static com.android.inputmethod.keyboard.Keyboard.CODE_OUTPUT_TEXT; 20import static com.android.inputmethod.keyboard.Keyboard.CODE_SHIFT; 21import static com.android.inputmethod.keyboard.Keyboard.CODE_SWITCH_ALPHA_SYMBOL; 22import static com.android.inputmethod.keyboard.Keyboard.CODE_UNSPECIFIED; 23import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED; 24 25import android.content.res.Resources; 26import android.content.res.TypedArray; 27import android.graphics.Rect; 28import android.graphics.Typeface; 29import android.graphics.drawable.Drawable; 30import android.text.TextUtils; 31import android.util.Log; 32import android.util.Xml; 33 34import com.android.inputmethod.keyboard.internal.KeySpecParser; 35import com.android.inputmethod.keyboard.internal.KeyStyle; 36import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; 37import com.android.inputmethod.keyboard.internal.KeyboardParams; 38import com.android.inputmethod.keyboard.internal.KeyboardRow; 39import com.android.inputmethod.keyboard.internal.MoreKeySpec; 40import com.android.inputmethod.latin.R; 41import com.android.inputmethod.latin.ResourceUtils; 42import com.android.inputmethod.latin.StringUtils; 43 44import org.xmlpull.v1.XmlPullParser; 45import org.xmlpull.v1.XmlPullParserException; 46 47import java.util.Arrays; 48import java.util.Locale; 49 50/** 51 * Class for describing the position and characteristics of a single key in the keyboard. 52 */ 53public class Key { 54 private static final String TAG = Key.class.getSimpleName(); 55 56 /** 57 * The key code (unicode or custom code) that this key generates. 58 */ 59 public final int mCode; 60 61 /** Label to display */ 62 public final String mLabel; 63 /** Hint label to display on the key in conjunction with the label */ 64 public final String mHintLabel; 65 /** Flags of the label */ 66 private final int mLabelFlags; 67 private static final int LABEL_FLAGS_ALIGN_LEFT = 0x01; 68 private static final int LABEL_FLAGS_ALIGN_RIGHT = 0x02; 69 private static final int LABEL_FLAGS_ALIGN_LEFT_OF_CENTER = 0x08; 70 private static final int LABEL_FLAGS_FONT_NORMAL = 0x10; 71 private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x20; 72 // Start of key text ratio enum values 73 private static final int LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK = 0x1C0; 74 private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO = 0x40; 75 private static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80; 76 private static final int LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO = 0xC0; 77 private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO = 0x100; 78 private static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x140; 79 // End of key text ratio mask enum values 80 private static final int LABEL_FLAGS_HAS_POPUP_HINT = 0x200; 81 private static final int LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT = 0x400; 82 private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800; 83 private static final int LABEL_FLAGS_WITH_ICON_LEFT = 0x1000; 84 private static final int LABEL_FLAGS_WITH_ICON_RIGHT = 0x2000; 85 private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000; 86 private static final int LABEL_FLAGS_PRESERVE_CASE = 0x8000; 87 private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x10000; 88 private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x20000; 89 private static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000; 90 private static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000; 91 92 /** Icon to display instead of a label. Icon takes precedence over a label */ 93 private final int mIconId; 94 95 /** Width of the key, not including the gap */ 96 public final int mWidth; 97 /** Height of the key, not including the gap */ 98 public final int mHeight; 99 /** X coordinate of the key in the keyboard layout */ 100 public final int mX; 101 /** Y coordinate of the key in the keyboard layout */ 102 public final int mY; 103 /** Hit bounding box of the key */ 104 public final Rect mHitBox = new Rect(); 105 106 /** More keys */ 107 public final MoreKeySpec[] mMoreKeys; 108 /** More keys column number and flags */ 109 private final int mMoreKeysColumnAndFlags; 110 private static final int MORE_KEYS_COLUMN_MASK = 0x000000ff; 111 private static final int MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER = 0x80000000; 112 private static final int MORE_KEYS_FLAGS_HAS_LABELS = 0x40000000; 113 private static final int MORE_KEYS_FLAGS_NEEDS_DIVIDERS = 0x20000000; 114 private static final int MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY = 0x10000000; 115 private static final String MORE_KEYS_AUTO_COLUMN_ORDER = "!autoColumnOrder!"; 116 private static final String MORE_KEYS_FIXED_COLUMN_ORDER = "!fixedColumnOrder!"; 117 private static final String MORE_KEYS_HAS_LABELS = "!hasLabels!"; 118 private static final String MORE_KEYS_NEEDS_DIVIDERS = "!needsDividers!"; 119 private static final String MORE_KEYS_EMBEDDED_MORE_KEY = "!embeddedMoreKey!"; 120 121 /** Background type that represents different key background visual than normal one. */ 122 public final int mBackgroundType; 123 public static final int BACKGROUND_TYPE_NORMAL = 0; 124 public static final int BACKGROUND_TYPE_FUNCTIONAL = 1; 125 public static final int BACKGROUND_TYPE_ACTION = 2; 126 public static final int BACKGROUND_TYPE_STICKY_OFF = 3; 127 public static final int BACKGROUND_TYPE_STICKY_ON = 4; 128 129 private final int mActionFlags; 130 private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01; 131 private static final int ACTION_FLAGS_NO_KEY_PREVIEW = 0x02; 132 private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04; 133 private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08; 134 135 private final OptionalAttributes mOptionalAttributes; 136 137 private static class OptionalAttributes { 138 /** Text to output when pressed. This can be multiple characters, like ".com" */ 139 public final String mOutputText; 140 public final int mAltCode; 141 /** Icon for disabled state */ 142 public final int mDisabledIconId; 143 /** Preview version of the icon, for the preview popup */ 144 public final int mPreviewIconId; 145 /** The visual insets */ 146 public final int mVisualInsetsLeft; 147 public final int mVisualInsetsRight; 148 149 public OptionalAttributes(final String outputText, final int altCode, 150 final int disabledIconId, final int previewIconId, 151 final int visualInsetsLeft, final int visualInsetsRight) { 152 mOutputText = outputText; 153 mAltCode = altCode; 154 mDisabledIconId = disabledIconId; 155 mPreviewIconId = previewIconId; 156 mVisualInsetsLeft = visualInsetsLeft; 157 mVisualInsetsRight = visualInsetsRight; 158 } 159 } 160 161 private final int mHashCode; 162 163 /** The current pressed state of this key */ 164 private boolean mPressed; 165 /** Key is enabled and responds on press */ 166 private boolean mEnabled = true; 167 168 /** 169 * This constructor is being used only for keys in more keys keyboard. 170 */ 171 public Key(final KeyboardParams params, final MoreKeySpec moreKeySpec, final int x, final int y, 172 final int width, final int height, final int labelFlags) { 173 this(params, moreKeySpec.mLabel, null, moreKeySpec.mIconId, moreKeySpec.mCode, 174 moreKeySpec.mOutputText, x, y, width, height, labelFlags); 175 } 176 177 /** 178 * This constructor is being used only for key in popup suggestions pane. 179 */ 180 public Key(final KeyboardParams params, final String label, final String hintLabel, 181 final int iconId, final int code, final String outputText, final int x, final int y, 182 final int width, final int height, final int labelFlags) { 183 mHeight = height - params.mVerticalGap; 184 mWidth = width - params.mHorizontalGap; 185 mHintLabel = hintLabel; 186 mLabelFlags = labelFlags; 187 mBackgroundType = BACKGROUND_TYPE_NORMAL; 188 mActionFlags = 0; 189 mMoreKeys = null; 190 mMoreKeysColumnAndFlags = 0; 191 mLabel = label; 192 if (outputText == null) { 193 mOptionalAttributes = null; 194 } else { 195 mOptionalAttributes = new OptionalAttributes(outputText, CODE_UNSPECIFIED, 196 ICON_UNDEFINED, ICON_UNDEFINED, 0, 0); 197 } 198 mCode = code; 199 mEnabled = (code != CODE_UNSPECIFIED); 200 mIconId = iconId; 201 // Horizontal gap is divided equally to both sides of the key. 202 mX = x + params.mHorizontalGap / 2; 203 mY = y; 204 mHitBox.set(x, y, x + width + 1, y + height); 205 206 mHashCode = computeHashCode(this); 207 } 208 209 /** 210 * Create a key with the given top-left coordinate and extract its attributes from the XML 211 * parser. 212 * @param res resources associated with the caller's context 213 * @param params the keyboard building parameters. 214 * @param row the row that this key belongs to. row's x-coordinate will be the right edge of 215 * this key. 216 * @param parser the XML parser containing the attributes for this key 217 * @throws XmlPullParserException 218 */ 219 public Key(final Resources res, final KeyboardParams params, final KeyboardRow row, 220 final XmlPullParser parser) throws XmlPullParserException { 221 final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap; 222 final int keyHeight = row.mRowHeight; 223 mHeight = keyHeight - params.mVerticalGap; 224 225 final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser), 226 R.styleable.Keyboard_Key); 227 228 final KeyStyle style = params.mKeyStyles.getKeyStyle(keyAttr, parser); 229 final float keyXPos = row.getKeyX(keyAttr); 230 final float keyWidth = row.getKeyWidth(keyAttr, keyXPos); 231 final int keyYPos = row.getKeyY(); 232 233 // Horizontal gap is divided equally to both sides of the key. 234 mX = Math.round(keyXPos + horizontalGap / 2); 235 mY = keyYPos; 236 mWidth = Math.round(keyWidth - horizontalGap); 237 mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1, 238 keyYPos + keyHeight); 239 // Update row to have current x coordinate. 240 row.setXPos(keyXPos + keyWidth); 241 242 mBackgroundType = style.getInt(keyAttr, 243 R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType()); 244 245 final int visualInsetsLeft = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr, 246 R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0)); 247 final int visualInsetsRight = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr, 248 R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0)); 249 mIconId = KeySpecParser.getIconId(style.getString(keyAttr, 250 R.styleable.Keyboard_Key_keyIcon)); 251 final int disabledIconId = KeySpecParser.getIconId(style.getString(keyAttr, 252 R.styleable.Keyboard_Key_keyIconDisabled)); 253 final int previewIconId = KeySpecParser.getIconId(style.getString(keyAttr, 254 R.styleable.Keyboard_Key_keyIconPreview)); 255 256 mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags) 257 | row.getDefaultKeyLabelFlags(); 258 final boolean needsToUpperCase = needsToUpperCase(mLabelFlags, params.mId.mElementId); 259 final Locale locale = params.mId.mLocale; 260 int actionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags); 261 String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys); 262 263 int moreKeysColumn = style.getInt(keyAttr, 264 R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMoreKeysKeyboardColumn); 265 int value; 266 if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) { 267 moreKeysColumn = value & MORE_KEYS_COLUMN_MASK; 268 } 269 if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) { 270 moreKeysColumn = MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER | (value & MORE_KEYS_COLUMN_MASK); 271 } 272 if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) { 273 moreKeysColumn |= MORE_KEYS_FLAGS_HAS_LABELS; 274 } 275 if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) { 276 moreKeysColumn |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS; 277 } 278 if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_EMBEDDED_MORE_KEY)) { 279 moreKeysColumn |= MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY; 280 } 281 mMoreKeysColumnAndFlags = moreKeysColumn; 282 283 final String[] additionalMoreKeys; 284 if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) { 285 additionalMoreKeys = null; 286 } else { 287 additionalMoreKeys = style.getStringArray(keyAttr, 288 R.styleable.Keyboard_Key_additionalMoreKeys); 289 } 290 moreKeys = KeySpecParser.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys); 291 if (moreKeys != null) { 292 actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS; 293 mMoreKeys = new MoreKeySpec[moreKeys.length]; 294 for (int i = 0; i < moreKeys.length; i++) { 295 mMoreKeys[i] = new MoreKeySpec( 296 moreKeys[i], needsToUpperCase, locale, params.mCodesSet); 297 } 298 } else { 299 mMoreKeys = null; 300 } 301 mActionFlags = actionFlags; 302 303 if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) { 304 mLabel = params.mId.mCustomActionLabel; 305 } else { 306 mLabel = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr, 307 R.styleable.Keyboard_Key_keyLabel), needsToUpperCase, locale); 308 } 309 if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) { 310 mHintLabel = null; 311 } else { 312 mHintLabel = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr, 313 R.styleable.Keyboard_Key_keyHintLabel), needsToUpperCase, locale); 314 } 315 String outputText = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr, 316 R.styleable.Keyboard_Key_keyOutputText), needsToUpperCase, locale); 317 final int code = KeySpecParser.parseCode(style.getString(keyAttr, 318 R.styleable.Keyboard_Key_code), params.mCodesSet, CODE_UNSPECIFIED); 319 // Choose the first letter of the label as primary code if not specified. 320 if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText) 321 && !TextUtils.isEmpty(mLabel)) { 322 if (StringUtils.codePointCount(mLabel) == 1) { 323 // Use the first letter of the hint label if shiftedLetterActivated flag is 324 // specified. 325 if (hasShiftedLetterHint() && isShiftedLetterActivated() 326 && !TextUtils.isEmpty(mHintLabel)) { 327 mCode = mHintLabel.codePointAt(0); 328 } else { 329 mCode = mLabel.codePointAt(0); 330 } 331 } else { 332 // In some locale and case, the character might be represented by multiple code 333 // points, such as upper case Eszett of German alphabet. 334 outputText = mLabel; 335 mCode = CODE_OUTPUT_TEXT; 336 } 337 } else if (code == CODE_UNSPECIFIED && outputText != null) { 338 if (StringUtils.codePointCount(outputText) == 1) { 339 mCode = outputText.codePointAt(0); 340 outputText = null; 341 } else { 342 mCode = CODE_OUTPUT_TEXT; 343 } 344 } else { 345 mCode = KeySpecParser.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale); 346 } 347 final int altCode = KeySpecParser.toUpperCaseOfCodeForLocale( 348 KeySpecParser.parseCode(style.getString(keyAttr, 349 R.styleable.Keyboard_Key_altCode), params.mCodesSet, CODE_UNSPECIFIED), 350 needsToUpperCase, locale); 351 if (outputText == null && altCode == CODE_UNSPECIFIED 352 && disabledIconId == ICON_UNDEFINED && previewIconId == ICON_UNDEFINED 353 && visualInsetsLeft == 0 && visualInsetsRight == 0) { 354 mOptionalAttributes = null; 355 } else { 356 mOptionalAttributes = new OptionalAttributes(outputText, altCode, 357 disabledIconId, previewIconId, 358 visualInsetsLeft, visualInsetsRight); 359 } 360 361 mHashCode = computeHashCode(this); 362 363 keyAttr.recycle(); 364 365 if (hasShiftedLetterHint() && TextUtils.isEmpty(mHintLabel)) { 366 Log.w(TAG, "hasShiftedLetterHint specified without keyHintLabel: " + this); 367 } 368 } 369 370 private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) { 371 if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false; 372 switch (keyboardElementId) { 373 case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: 374 case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: 375 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: 376 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: 377 return true; 378 default: 379 return false; 380 } 381 } 382 383 private static int computeHashCode(final Key key) { 384 return Arrays.hashCode(new Object[] { 385 key.mX, 386 key.mY, 387 key.mWidth, 388 key.mHeight, 389 key.mCode, 390 key.mLabel, 391 key.mHintLabel, 392 key.mIconId, 393 key.mBackgroundType, 394 Arrays.hashCode(key.mMoreKeys), 395 key.getOutputText(), 396 key.mActionFlags, 397 key.mLabelFlags, 398 // Key can be distinguishable without the following members. 399 // key.mOptionalAttributes.mAltCode, 400 // key.mOptionalAttributes.mDisabledIconId, 401 // key.mOptionalAttributes.mPreviewIconId, 402 // key.mHorizontalGap, 403 // key.mVerticalGap, 404 // key.mOptionalAttributes.mVisualInsetLeft, 405 // key.mOptionalAttributes.mVisualInsetRight, 406 // key.mMaxMoreKeysColumn, 407 }); 408 } 409 410 private boolean equals(final Key o) { 411 if (this == o) return true; 412 return o.mX == mX 413 && o.mY == mY 414 && o.mWidth == mWidth 415 && o.mHeight == mHeight 416 && o.mCode == mCode 417 && TextUtils.equals(o.mLabel, mLabel) 418 && TextUtils.equals(o.mHintLabel, mHintLabel) 419 && o.mIconId == mIconId 420 && o.mBackgroundType == mBackgroundType 421 && Arrays.equals(o.mMoreKeys, mMoreKeys) 422 && TextUtils.equals(o.getOutputText(), getOutputText()) 423 && o.mActionFlags == mActionFlags 424 && o.mLabelFlags == mLabelFlags; 425 } 426 427 @Override 428 public int hashCode() { 429 return mHashCode; 430 } 431 432 @Override 433 public boolean equals(final Object o) { 434 return o instanceof Key && equals((Key)o); 435 } 436 437 @Override 438 public String toString() { 439 final String label; 440 if (StringUtils.codePointCount(mLabel) == 1 && mLabel.codePointAt(0) == mCode) { 441 label = ""; 442 } else { 443 label = "/" + mLabel; 444 } 445 return String.format("%s%s %d,%d %dx%d %s/%s/%s", 446 Keyboard.printableCode(mCode), label, mX, mY, mWidth, mHeight, mHintLabel, 447 KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType)); 448 } 449 450 private static String backgroundName(final int backgroundType) { 451 switch (backgroundType) { 452 case BACKGROUND_TYPE_NORMAL: return "normal"; 453 case BACKGROUND_TYPE_FUNCTIONAL: return "functional"; 454 case BACKGROUND_TYPE_ACTION: return "action"; 455 case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff"; 456 case BACKGROUND_TYPE_STICKY_ON: return "stickyOn"; 457 default: return null; 458 } 459 } 460 461 public void markAsLeftEdge(final KeyboardParams params) { 462 mHitBox.left = params.mHorizontalEdgesPadding; 463 } 464 465 public void markAsRightEdge(final KeyboardParams params) { 466 mHitBox.right = params.mOccupiedWidth - params.mHorizontalEdgesPadding; 467 } 468 469 public void markAsTopEdge(final KeyboardParams params) { 470 mHitBox.top = params.mTopPadding; 471 } 472 473 public void markAsBottomEdge(final KeyboardParams params) { 474 mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding; 475 } 476 477 public final boolean isSpacer() { 478 return this instanceof Spacer; 479 } 480 481 public boolean isShift() { 482 return mCode == CODE_SHIFT; 483 } 484 485 public boolean isModifier() { 486 return mCode == CODE_SHIFT || mCode == CODE_SWITCH_ALPHA_SYMBOL; 487 } 488 489 public boolean isRepeatable() { 490 return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0; 491 } 492 493 public boolean noKeyPreview() { 494 return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0; 495 } 496 497 public boolean altCodeWhileTyping() { 498 return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0; 499 } 500 501 public boolean isLongPressEnabled() { 502 // We need not start long press timer on the key which has activated shifted letter. 503 return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0 504 && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0; 505 } 506 507 public Typeface selectTypeface(final Typeface defaultTypeface) { 508 // TODO: Handle "bold" here too? 509 if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) { 510 return Typeface.DEFAULT; 511 } else if ((mLabelFlags & LABEL_FLAGS_FONT_MONO_SPACE) != 0) { 512 return Typeface.MONOSPACE; 513 } else { 514 return defaultTypeface; 515 } 516 } 517 518 public int selectTextSize(final int letterSize, final int largeLetterSize, final int labelSize, 519 final int largeLabelSize, final int hintLabelSize) { 520 switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) { 521 case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO: 522 return letterSize; 523 case LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO: 524 return largeLetterSize; 525 case LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO: 526 return labelSize; 527 case LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO: 528 return largeLabelSize; 529 case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO: 530 return hintLabelSize; 531 default: // No follow key ratio flag specified. 532 return StringUtils.codePointCount(mLabel) == 1 ? letterSize : labelSize; 533 } 534 } 535 536 public boolean isAlignLeft() { 537 return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0; 538 } 539 540 public boolean isAlignRight() { 541 return (mLabelFlags & LABEL_FLAGS_ALIGN_RIGHT) != 0; 542 } 543 544 public boolean isAlignLeftOfCenter() { 545 return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0; 546 } 547 548 public boolean hasPopupHint() { 549 return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0; 550 } 551 552 public boolean hasShiftedLetterHint() { 553 return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0; 554 } 555 556 public boolean hasHintLabel() { 557 return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0; 558 } 559 560 public boolean hasLabelWithIconLeft() { 561 return (mLabelFlags & LABEL_FLAGS_WITH_ICON_LEFT) != 0; 562 } 563 564 public boolean hasLabelWithIconRight() { 565 return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0; 566 } 567 568 public boolean needsXScale() { 569 return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0; 570 } 571 572 public boolean isShiftedLetterActivated() { 573 return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0; 574 } 575 576 public int getMoreKeysColumn() { 577 return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_MASK; 578 } 579 580 public boolean isFixedColumnOrderMoreKeys() { 581 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER) != 0; 582 } 583 584 public boolean hasLabelsInMoreKeys() { 585 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0; 586 } 587 588 public int getMoreKeyLabelFlags() { 589 return hasLabelsInMoreKeys() 590 ? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO 591 : LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO; 592 } 593 594 public boolean needsDividersInMoreKeys() { 595 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0; 596 } 597 598 public boolean hasEmbeddedMoreKey() { 599 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY) != 0; 600 } 601 602 public String getOutputText() { 603 final OptionalAttributes attrs = mOptionalAttributes; 604 return (attrs != null) ? attrs.mOutputText : null; 605 } 606 607 public int getAltCode() { 608 final OptionalAttributes attrs = mOptionalAttributes; 609 return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED; 610 } 611 612 public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { 613 final OptionalAttributes attrs = mOptionalAttributes; 614 final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED; 615 final int iconId = mEnabled ? mIconId : disabledIconId; 616 final Drawable icon = iconSet.getIconDrawable(iconId); 617 if (icon != null) { 618 icon.setAlpha(alpha); 619 } 620 return icon; 621 } 622 623 public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) { 624 final OptionalAttributes attrs = mOptionalAttributes; 625 final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED; 626 return previewIconId != ICON_UNDEFINED 627 ? iconSet.getIconDrawable(previewIconId) : iconSet.getIconDrawable(mIconId); 628 } 629 630 public int getDrawX() { 631 final OptionalAttributes attrs = mOptionalAttributes; 632 return (attrs == null) ? mX : mX + attrs.mVisualInsetsLeft; 633 } 634 635 public int getDrawWidth() { 636 final OptionalAttributes attrs = mOptionalAttributes; 637 return (attrs == null) ? mWidth 638 : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight; 639 } 640 641 /** 642 * Informs the key that it has been pressed, in case it needs to change its appearance or 643 * state. 644 * @see #onReleased() 645 */ 646 public void onPressed() { 647 mPressed = true; 648 } 649 650 /** 651 * Informs the key that it has been released, in case it needs to change its appearance or 652 * state. 653 * @see #onPressed() 654 */ 655 public void onReleased() { 656 mPressed = false; 657 } 658 659 public boolean isEnabled() { 660 return mEnabled; 661 } 662 663 public void setEnabled(final boolean enabled) { 664 mEnabled = enabled; 665 } 666 667 /** 668 * Detects if a point falls on this key. 669 * @param x the x-coordinate of the point 670 * @param y the y-coordinate of the point 671 * @return whether or not the point falls on the key. If the key is attached to an edge, it 672 * will assume that all points between the key and the edge are considered to be on the key. 673 * @see #markAsLeftEdge(KeyboardParams) etc. 674 */ 675 public boolean isOnKey(final int x, final int y) { 676 return mHitBox.contains(x, y); 677 } 678 679 /** 680 * Returns the square of the distance to the nearest edge of the key and the given point. 681 * @param x the x-coordinate of the point 682 * @param y the y-coordinate of the point 683 * @return the square of the distance of the point from the nearest edge of the key 684 */ 685 public int squaredDistanceToEdge(final int x, final int y) { 686 final int left = mX; 687 final int right = left + mWidth; 688 final int top = mY; 689 final int bottom = top + mHeight; 690 final int edgeX = x < left ? left : (x > right ? right : x); 691 final int edgeY = y < top ? top : (y > bottom ? bottom : y); 692 final int dx = x - edgeX; 693 final int dy = y - edgeY; 694 return dx * dx + dy * dy; 695 } 696 697 private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_ON = { 698 android.R.attr.state_checkable, 699 android.R.attr.state_checked 700 }; 701 702 private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_ON = { 703 android.R.attr.state_pressed, 704 android.R.attr.state_checkable, 705 android.R.attr.state_checked 706 }; 707 708 private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_OFF = { 709 android.R.attr.state_checkable 710 }; 711 712 private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_OFF = { 713 android.R.attr.state_pressed, 714 android.R.attr.state_checkable 715 }; 716 717 private final static int[] KEY_STATE_NORMAL = { 718 }; 719 720 private final static int[] KEY_STATE_PRESSED = { 721 android.R.attr.state_pressed 722 }; 723 724 // functional normal state (with properties) 725 private static final int[] KEY_STATE_FUNCTIONAL_NORMAL = { 726 android.R.attr.state_single 727 }; 728 729 // functional pressed state (with properties) 730 private static final int[] KEY_STATE_FUNCTIONAL_PRESSED = { 731 android.R.attr.state_single, 732 android.R.attr.state_pressed 733 }; 734 735 // action normal state (with properties) 736 private static final int[] KEY_STATE_ACTIVE_NORMAL = { 737 android.R.attr.state_active 738 }; 739 740 // action pressed state (with properties) 741 private static final int[] KEY_STATE_ACTIVE_PRESSED = { 742 android.R.attr.state_active, 743 android.R.attr.state_pressed 744 }; 745 746 /** 747 * Returns the drawable state for the key, based on the current state and type of the key. 748 * @return the drawable state of the key. 749 * @see android.graphics.drawable.StateListDrawable#setState(int[]) 750 */ 751 public int[] getCurrentDrawableState() { 752 switch (mBackgroundType) { 753 case BACKGROUND_TYPE_FUNCTIONAL: 754 return mPressed ? KEY_STATE_FUNCTIONAL_PRESSED : KEY_STATE_FUNCTIONAL_NORMAL; 755 case BACKGROUND_TYPE_ACTION: 756 return mPressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL; 757 case BACKGROUND_TYPE_STICKY_OFF: 758 return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF; 759 case BACKGROUND_TYPE_STICKY_ON: 760 return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON; 761 default: /* BACKGROUND_TYPE_NORMAL */ 762 return mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL; 763 } 764 } 765 766 public static class Spacer extends Key { 767 public Spacer(final Resources res, final KeyboardParams params, final KeyboardRow row, 768 final XmlPullParser parser) throws XmlPullParserException { 769 super(res, params, row, parser); 770 } 771 772 /** 773 * This constructor is being used only for divider in more keys keyboard. 774 */ 775 protected Spacer(final KeyboardParams params, final int x, final int y, final int width, 776 final int height) { 777 super(params, null, null, ICON_UNDEFINED, CODE_UNSPECIFIED, 778 null, x, y, width, height, 0); 779 } 780 } 781} 782