Key.java revision f7f657436872959fa9f8c5b90d2a4a97d104eaae
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.inputmethod.keyboard; 18 19import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED; 20import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT; 21import static com.android.inputmethod.latin.Constants.CODE_SHIFT; 22import static com.android.inputmethod.latin.Constants.CODE_SWITCH_ALPHA_SYMBOL; 23import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; 24 25import android.content.res.TypedArray; 26import android.graphics.Rect; 27import android.graphics.Typeface; 28import android.graphics.drawable.Drawable; 29import android.text.TextUtils; 30 31import com.android.inputmethod.keyboard.internal.KeyDrawParams; 32import com.android.inputmethod.keyboard.internal.KeySpecParser; 33import com.android.inputmethod.keyboard.internal.KeyStyle; 34import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; 35import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; 36import com.android.inputmethod.keyboard.internal.KeyboardParams; 37import com.android.inputmethod.keyboard.internal.KeyboardRow; 38import com.android.inputmethod.keyboard.internal.MoreKeySpec; 39import com.android.inputmethod.latin.Constants; 40import com.android.inputmethod.latin.R; 41import com.android.inputmethod.latin.utils.StringUtils; 42 43import java.util.Arrays; 44import java.util.Locale; 45 46/** 47 * Class for describing the position and characteristics of a single key in the keyboard. 48 */ 49public class Key implements Comparable<Key> { 50 /** 51 * The key code (unicode or custom code) that this key generates. 52 */ 53 private final int mCode; 54 55 /** Label to display */ 56 private final String mLabel; 57 /** Hint label to display on the key in conjunction with the label */ 58 private final String mHintLabel; 59 /** Flags of the label */ 60 private final int mLabelFlags; 61 private static final int LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM = 0x04; 62 private static final int LABEL_FLAGS_ALIGN_LABEL_OFF_CENTER = 0x08; 63 // Font typeface specification. 64 private static final int LABEL_FLAGS_FONT_MASK = 0x30; 65 private static final int LABEL_FLAGS_FONT_NORMAL = 0x10; 66 private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x20; 67 private static final int LABEL_FLAGS_FONT_DEFAULT = 0x30; 68 // Start of key text ratio enum values 69 private static final int LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK = 0x1C0; 70 private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO = 0x40; 71 private static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80; 72 private static final int LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO = 0xC0; 73 private static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x140; 74 // End of key text ratio mask enum values 75 private static final int LABEL_FLAGS_HAS_POPUP_HINT = 0x200; 76 private static final int LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT = 0x400; 77 private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800; 78 // The bit to calculate the ratio of key label width against key width. If autoXScale bit is on 79 // and autoYScale bit is off, the key label may be shrunk only for X-direction. 80 // If both autoXScale and autoYScale bits are on, the key label text size may be auto scaled. 81 private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000; 82 private static final int LABEL_FLAGS_AUTO_Y_SCALE = 0x8000; 83 private static final int LABEL_FLAGS_AUTO_SCALE = LABEL_FLAGS_AUTO_X_SCALE 84 | LABEL_FLAGS_AUTO_Y_SCALE; 85 private static final int LABEL_FLAGS_PRESERVE_CASE = 0x10000; 86 private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x20000; 87 private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x40000; 88 private static final int LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR = 0x80000; 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 private final int mWidth; 97 /** Height of the key, not including the gap */ 98 private final int mHeight; 99 /** X coordinate of the key in the keyboard layout */ 100 private final int mX; 101 /** Y coordinate of the key in the keyboard layout */ 102 private final int mY; 103 /** Hit bounding box of the key */ 104 private final Rect mHitBox = new Rect(); 105 106 /** More keys. It is guaranteed that this is null or an array of one or more elements */ 107 private 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_NO_PANEL_AUTO_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_NO_PANEL_AUTO_MORE_KEY = "!noPanelAutoMoreKey!"; 120 121 /** Background type that represents different key background visual than normal one. */ 122 private final int mBackgroundType; 123 public static final int BACKGROUND_TYPE_EMPTY = 0; 124 public static final int BACKGROUND_TYPE_NORMAL = 1; 125 public static final int BACKGROUND_TYPE_FUNCTIONAL = 2; 126 public static final int BACKGROUND_TYPE_STICKY_OFF = 3; 127 public static final int BACKGROUND_TYPE_STICKY_ON = 4; 128 public static final int BACKGROUND_TYPE_ACTION = 5; 129 public static final int BACKGROUND_TYPE_CUSTOM_ACTION = 6; 130 131 private final int mActionFlags; 132 private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01; 133 private static final int ACTION_FLAGS_NO_KEY_PREVIEW = 0x02; 134 private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04; 135 private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08; 136 137 private final KeyVisualAttributes mKeyVisualAttributes; 138 139 private final OptionalAttributes mOptionalAttributes; 140 141 private static final class OptionalAttributes { 142 /** Text to output when pressed. This can be multiple characters, like ".com" */ 143 public final String mOutputText; 144 public final int mAltCode; 145 /** Icon for disabled state */ 146 public final int mDisabledIconId; 147 /** The visual insets */ 148 public final int mVisualInsetsLeft; 149 public final int mVisualInsetsRight; 150 151 private OptionalAttributes(final String outputText, final int altCode, 152 final int disabledIconId, final int visualInsetsLeft, final int visualInsetsRight) { 153 mOutputText = outputText; 154 mAltCode = altCode; 155 mDisabledIconId = disabledIconId; 156 mVisualInsetsLeft = visualInsetsLeft; 157 mVisualInsetsRight = visualInsetsRight; 158 } 159 160 public static OptionalAttributes newInstance(final String outputText, final int altCode, 161 final int disabledIconId, final int visualInsetsLeft, final int visualInsetsRight) { 162 if (outputText == null && altCode == CODE_UNSPECIFIED 163 && disabledIconId == ICON_UNDEFINED && visualInsetsLeft == 0 164 && visualInsetsRight == 0) { 165 return null; 166 } 167 return new OptionalAttributes(outputText, altCode, disabledIconId, visualInsetsLeft, 168 visualInsetsRight); 169 } 170 } 171 172 private final int mHashCode; 173 174 /** The current pressed state of this key */ 175 private boolean mPressed; 176 /** Key is enabled and responds on press */ 177 private boolean mEnabled = true; 178 179 /** 180 * Constructor for a key on <code>MoreKeyKeyboard</code>, on <code>MoreSuggestions</code>, 181 * and in a <GridRows/>. 182 */ 183 public Key(final String label, final int iconId, final int code, final String outputText, 184 final String hintLabel, final int labelFlags, final int backgroundType, final int x, 185 final int y, final int width, final int height, final int horizontalGap, 186 final int verticalGap) { 187 mHeight = height - verticalGap; 188 mWidth = width - horizontalGap; 189 mHintLabel = hintLabel; 190 mLabelFlags = labelFlags; 191 mBackgroundType = backgroundType; 192 // TODO: Pass keyActionFlags as an argument. 193 mActionFlags = ACTION_FLAGS_NO_KEY_PREVIEW; 194 mMoreKeys = null; 195 mMoreKeysColumnAndFlags = 0; 196 mLabel = label; 197 mOptionalAttributes = OptionalAttributes.newInstance(outputText, CODE_UNSPECIFIED, 198 ICON_UNDEFINED, 0 /* visualInsetsLeft */, 0 /* visualInsetsRight */); 199 mCode = code; 200 mEnabled = (code != CODE_UNSPECIFIED); 201 mIconId = iconId; 202 // Horizontal gap is divided equally to both sides of the key. 203 mX = x + horizontalGap / 2; 204 mY = y; 205 mHitBox.set(x, y, x + width + 1, y + height); 206 mKeyVisualAttributes = null; 207 208 mHashCode = computeHashCode(this); 209 } 210 211 /** 212 * Create a key with the given top-left coordinate and extract its attributes from a key 213 * specification string, Key attribute array, key style, and etc. 214 * 215 * @param keySpec the key specification. 216 * @param keyAttr the Key XML attributes array. 217 * @param style the {@link KeyStyle} of this key. 218 * @param params the keyboard building parameters. 219 * @param row the row that this key belongs to. row's x-coordinate will be the right edge of 220 * this key. 221 */ 222 public Key(final String keySpec, final TypedArray keyAttr, final KeyStyle style, 223 final KeyboardParams params, final KeyboardRow row) { 224 final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap; 225 final int rowHeight = row.getRowHeight(); 226 mHeight = rowHeight - params.mVerticalGap; 227 228 final float keyXPos = row.getKeyX(keyAttr); 229 final float keyWidth = row.getKeyWidth(keyAttr, keyXPos); 230 final int keyYPos = row.getKeyY(); 231 232 // Horizontal gap is divided equally to both sides of the key. 233 mX = Math.round(keyXPos + horizontalGap / 2); 234 mY = keyYPos; 235 mWidth = Math.round(keyWidth - horizontalGap); 236 mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1, 237 keyYPos + rowHeight); 238 // Update row to have current x coordinate. 239 row.setXPos(keyXPos + keyWidth); 240 241 mBackgroundType = style.getInt(keyAttr, 242 R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType()); 243 244 final int baseWidth = params.mBaseWidth; 245 final int visualInsetsLeft = Math.round(keyAttr.getFraction( 246 R.styleable.Keyboard_Key_visualInsetsLeft, baseWidth, baseWidth, 0)); 247 final int visualInsetsRight = Math.round(keyAttr.getFraction( 248 R.styleable.Keyboard_Key_visualInsetsRight, baseWidth, baseWidth, 0)); 249 250 mLabelFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags) 251 | row.getDefaultKeyLabelFlags(); 252 final boolean needsToUpperCase = needsToUpperCase(mLabelFlags, params.mId.mElementId); 253 final Locale locale = params.mId.mLocale; 254 int actionFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags); 255 String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys); 256 257 int moreKeysColumn = style.getInt(keyAttr, 258 R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMoreKeysKeyboardColumn); 259 int value; 260 if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) { 261 moreKeysColumn = value & MORE_KEYS_COLUMN_MASK; 262 } 263 if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) { 264 moreKeysColumn = MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER | (value & MORE_KEYS_COLUMN_MASK); 265 } 266 if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) { 267 moreKeysColumn |= MORE_KEYS_FLAGS_HAS_LABELS; 268 } 269 if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) { 270 moreKeysColumn |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS; 271 } 272 if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) { 273 moreKeysColumn |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY; 274 } 275 mMoreKeysColumnAndFlags = moreKeysColumn; 276 277 final String[] additionalMoreKeys; 278 if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) { 279 additionalMoreKeys = null; 280 } else { 281 additionalMoreKeys = style.getStringArray(keyAttr, 282 R.styleable.Keyboard_Key_additionalMoreKeys); 283 } 284 moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys); 285 if (moreKeys != null) { 286 actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS; 287 mMoreKeys = new MoreKeySpec[moreKeys.length]; 288 for (int i = 0; i < moreKeys.length; i++) { 289 mMoreKeys[i] = new MoreKeySpec(moreKeys[i], needsToUpperCase, locale); 290 } 291 } else { 292 mMoreKeys = null; 293 } 294 mActionFlags = actionFlags; 295 296 mIconId = KeySpecParser.getIconId(keySpec); 297 final int disabledIconId = KeySpecParser.getIconId(style.getString(keyAttr, 298 R.styleable.Keyboard_Key_keyIconDisabled)); 299 300 final int code = KeySpecParser.getCode(keySpec); 301 if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) { 302 mLabel = params.mId.mCustomActionLabel; 303 } else if (code >= Character.MIN_SUPPLEMENTARY_CODE_POINT) { 304 // This is a workaround to have a key that has a supplementary code point in its label. 305 // Because we can put a string in resource neither as a XML entity of a supplementary 306 // code point nor as a surrogate pair. 307 mLabel = new StringBuilder().appendCodePoint(code).toString(); 308 } else { 309 mLabel = StringUtils.toUpperCaseOfStringForLocale( 310 KeySpecParser.getLabel(keySpec), needsToUpperCase, locale); 311 } 312 if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) { 313 mHintLabel = null; 314 } else { 315 mHintLabel = StringUtils.toUpperCaseOfStringForLocale(style.getString(keyAttr, 316 R.styleable.Keyboard_Key_keyHintLabel), needsToUpperCase, locale); 317 } 318 String outputText = StringUtils.toUpperCaseOfStringForLocale( 319 KeySpecParser.getOutputText(keySpec), needsToUpperCase, locale); 320 // Choose the first letter of the label as primary code if not specified. 321 if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText) 322 && !TextUtils.isEmpty(mLabel)) { 323 if (StringUtils.codePointCount(mLabel) == 1) { 324 // Use the first letter of the hint label if shiftedLetterActivated flag is 325 // specified. 326 if (hasShiftedLetterHint() && isShiftedLetterActivated()) { 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 = StringUtils.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale); 346 } 347 final int altCodeInAttr = KeySpecParser.parseCode( 348 style.getString(keyAttr, R.styleable.Keyboard_Key_altCode), CODE_UNSPECIFIED); 349 final int altCode = StringUtils.toUpperCaseOfCodeForLocale( 350 altCodeInAttr, needsToUpperCase, locale); 351 mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode, 352 disabledIconId, visualInsetsLeft, visualInsetsRight); 353 mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr); 354 mHashCode = computeHashCode(this); 355 } 356 357 /** 358 * Copy constructor for DynamicGridKeyboard.GridKey. 359 * 360 * @param key the original key. 361 */ 362 protected Key(final Key key) { 363 // Final attributes. 364 mCode = key.mCode; 365 mLabel = key.mLabel; 366 mHintLabel = key.mHintLabel; 367 mLabelFlags = key.mLabelFlags; 368 mIconId = key.mIconId; 369 mWidth = key.mWidth; 370 mHeight = key.mHeight; 371 mX = key.mX; 372 mY = key.mY; 373 mHitBox.set(key.mHitBox); 374 mMoreKeys = key.mMoreKeys; 375 mMoreKeysColumnAndFlags = key.mMoreKeysColumnAndFlags; 376 mBackgroundType = key.mBackgroundType; 377 mActionFlags = key.mActionFlags; 378 mKeyVisualAttributes = key.mKeyVisualAttributes; 379 mOptionalAttributes = key.mOptionalAttributes; 380 mHashCode = key.mHashCode; 381 // Key state. 382 mPressed = key.mPressed; 383 mEnabled = key.mEnabled; 384 } 385 386 private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) { 387 if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false; 388 switch (keyboardElementId) { 389 case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: 390 case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: 391 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: 392 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: 393 return true; 394 default: 395 return false; 396 } 397 } 398 399 private static int computeHashCode(final Key key) { 400 return Arrays.hashCode(new Object[] { 401 key.mX, 402 key.mY, 403 key.mWidth, 404 key.mHeight, 405 key.mCode, 406 key.mLabel, 407 key.mHintLabel, 408 key.mIconId, 409 key.mBackgroundType, 410 Arrays.hashCode(key.mMoreKeys), 411 key.getOutputText(), 412 key.mActionFlags, 413 key.mLabelFlags, 414 // Key can be distinguishable without the following members. 415 // key.mOptionalAttributes.mAltCode, 416 // key.mOptionalAttributes.mDisabledIconId, 417 // key.mOptionalAttributes.mPreviewIconId, 418 // key.mHorizontalGap, 419 // key.mVerticalGap, 420 // key.mOptionalAttributes.mVisualInsetLeft, 421 // key.mOptionalAttributes.mVisualInsetRight, 422 // key.mMaxMoreKeysColumn, 423 }); 424 } 425 426 private boolean equalsInternal(final Key o) { 427 if (this == o) return true; 428 return o.mX == mX 429 && o.mY == mY 430 && o.mWidth == mWidth 431 && o.mHeight == mHeight 432 && o.mCode == mCode 433 && TextUtils.equals(o.mLabel, mLabel) 434 && TextUtils.equals(o.mHintLabel, mHintLabel) 435 && o.mIconId == mIconId 436 && o.mBackgroundType == mBackgroundType 437 && Arrays.equals(o.mMoreKeys, mMoreKeys) 438 && TextUtils.equals(o.getOutputText(), getOutputText()) 439 && o.mActionFlags == mActionFlags 440 && o.mLabelFlags == mLabelFlags; 441 } 442 443 @Override 444 public int compareTo(Key o) { 445 if (equalsInternal(o)) return 0; 446 if (mHashCode > o.mHashCode) return 1; 447 return -1; 448 } 449 450 @Override 451 public int hashCode() { 452 return mHashCode; 453 } 454 455 @Override 456 public boolean equals(final Object o) { 457 return o instanceof Key && equalsInternal((Key)o); 458 } 459 460 @Override 461 public String toString() { 462 return toShortString() + " " + getX() + "," + getY() + " " + getWidth() + "x" + getHeight(); 463 } 464 465 public String toShortString() { 466 final int code = getCode(); 467 if (code == Constants.CODE_OUTPUT_TEXT) { 468 return getOutputText(); 469 } 470 return Constants.printableCode(code); 471 } 472 473 public String toLongString() { 474 final int iconId = getIconId(); 475 final String topVisual = (iconId == KeyboardIconsSet.ICON_UNDEFINED) 476 ? KeyboardIconsSet.PREFIX_ICON + KeyboardIconsSet.getIconName(iconId) : getLabel(); 477 final String hintLabel = getHintLabel(); 478 final String visual = (hintLabel == null) ? topVisual : topVisual + "^" + hintLabel; 479 return toString() + " " + visual + "/" + backgroundName(mBackgroundType); 480 } 481 482 private static String backgroundName(final int backgroundType) { 483 switch (backgroundType) { 484 case BACKGROUND_TYPE_EMPTY: return "empty"; 485 case BACKGROUND_TYPE_NORMAL: return "normal"; 486 case BACKGROUND_TYPE_FUNCTIONAL: return "functional"; 487 case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff"; 488 case BACKGROUND_TYPE_STICKY_ON: return "stickyOn"; 489 case BACKGROUND_TYPE_ACTION: return "action"; 490 case BACKGROUND_TYPE_CUSTOM_ACTION: return "customAction"; 491 default: return null; 492 } 493 } 494 495 public int getCode() { 496 return mCode; 497 } 498 499 public String getLabel() { 500 return mLabel; 501 } 502 503 public String getHintLabel() { 504 return mHintLabel; 505 } 506 507 public MoreKeySpec[] getMoreKeys() { 508 return mMoreKeys; 509 } 510 511 public void markAsLeftEdge(final KeyboardParams params) { 512 mHitBox.left = params.mLeftPadding; 513 } 514 515 public void markAsRightEdge(final KeyboardParams params) { 516 mHitBox.right = params.mOccupiedWidth - params.mRightPadding; 517 } 518 519 public void markAsTopEdge(final KeyboardParams params) { 520 mHitBox.top = params.mTopPadding; 521 } 522 523 public void markAsBottomEdge(final KeyboardParams params) { 524 mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding; 525 } 526 527 public final boolean isSpacer() { 528 return this instanceof Spacer; 529 } 530 531 public final boolean isShift() { 532 return mCode == CODE_SHIFT; 533 } 534 535 public final boolean isModifier() { 536 return mCode == CODE_SHIFT || mCode == CODE_SWITCH_ALPHA_SYMBOL; 537 } 538 539 public final boolean isRepeatable() { 540 return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0; 541 } 542 543 public final boolean noKeyPreview() { 544 return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0; 545 } 546 547 public final boolean altCodeWhileTyping() { 548 return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0; 549 } 550 551 public final boolean isLongPressEnabled() { 552 // We need not start long press timer on the key which has activated shifted letter. 553 return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0 554 && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0; 555 } 556 557 public KeyVisualAttributes getVisualAttributes() { 558 return mKeyVisualAttributes; 559 } 560 561 public final Typeface selectTypeface(final KeyDrawParams params) { 562 switch (mLabelFlags & LABEL_FLAGS_FONT_MASK) { 563 case LABEL_FLAGS_FONT_NORMAL: 564 return Typeface.DEFAULT; 565 case LABEL_FLAGS_FONT_MONO_SPACE: 566 return Typeface.MONOSPACE; 567 case LABEL_FLAGS_FONT_DEFAULT: 568 default: 569 // The type-face is specified by keyTypeface attribute. 570 return params.mTypeface; 571 } 572 } 573 574 public final int selectTextSize(final KeyDrawParams params) { 575 switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) { 576 case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO: 577 return params.mLetterSize; 578 case LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO: 579 return params.mLargeLetterSize; 580 case LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO: 581 return params.mLabelSize; 582 case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO: 583 return params.mHintLabelSize; 584 default: // No follow key ratio flag specified. 585 return StringUtils.codePointCount(mLabel) == 1 ? params.mLetterSize : params.mLabelSize; 586 } 587 } 588 589 public final int selectTextColor(final KeyDrawParams params) { 590 if ((mLabelFlags & LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR) != 0) { 591 return params.mFunctionalTextColor; 592 } 593 return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor; 594 } 595 596 public final int selectHintTextSize(final KeyDrawParams params) { 597 if (hasHintLabel()) { 598 return params.mHintLabelSize; 599 } 600 if (hasShiftedLetterHint()) { 601 return params.mShiftedLetterHintSize; 602 } 603 return params.mHintLetterSize; 604 } 605 606 public final int selectHintTextColor(final KeyDrawParams params) { 607 if (hasHintLabel()) { 608 return params.mHintLabelColor; 609 } 610 if (hasShiftedLetterHint()) { 611 return isShiftedLetterActivated() ? params.mShiftedLetterHintActivatedColor 612 : params.mShiftedLetterHintInactivatedColor; 613 } 614 return params.mHintLetterColor; 615 } 616 617 public final int selectMoreKeyTextSize(final KeyDrawParams params) { 618 return hasLabelsInMoreKeys() ? params.mLabelSize : params.mLetterSize; 619 } 620 621 public final String getPreviewLabel() { 622 return isShiftedLetterActivated() ? mHintLabel : mLabel; 623 } 624 625 private boolean previewHasLetterSize() { 626 return (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO) != 0 627 || StringUtils.codePointCount(getPreviewLabel()) == 1; 628 } 629 630 public final int selectPreviewTextSize(final KeyDrawParams params) { 631 if (previewHasLetterSize()) { 632 return params.mPreviewTextSize; 633 } 634 return params.mLetterSize; 635 } 636 637 public Typeface selectPreviewTypeface(final KeyDrawParams params) { 638 if (previewHasLetterSize()) { 639 return selectTypeface(params); 640 } 641 return Typeface.DEFAULT_BOLD; 642 } 643 644 public final boolean isAlignIconToBottom() { 645 return (mLabelFlags & LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM) != 0; 646 } 647 648 public final boolean isAlignLabelOffCenter() { 649 return (mLabelFlags & LABEL_FLAGS_ALIGN_LABEL_OFF_CENTER) != 0; 650 } 651 652 public final boolean hasPopupHint() { 653 return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0; 654 } 655 656 public final boolean hasShiftedLetterHint() { 657 return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0 658 && !TextUtils.isEmpty(mHintLabel); 659 } 660 661 public final boolean hasHintLabel() { 662 return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0; 663 } 664 665 public final boolean needsAutoXScale() { 666 return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0; 667 } 668 669 public final boolean needsAutoScale() { 670 return (mLabelFlags & LABEL_FLAGS_AUTO_SCALE) == LABEL_FLAGS_AUTO_SCALE; 671 } 672 673 private final boolean isShiftedLetterActivated() { 674 return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0 675 && !TextUtils.isEmpty(mHintLabel); 676 } 677 678 public final int getMoreKeysColumn() { 679 return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_MASK; 680 } 681 682 public final boolean isFixedColumnOrderMoreKeys() { 683 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER) != 0; 684 } 685 686 public final boolean hasLabelsInMoreKeys() { 687 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0; 688 } 689 690 public final int getMoreKeyLabelFlags() { 691 return hasLabelsInMoreKeys() 692 ? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO 693 : LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO; 694 } 695 696 public final boolean needsDividersInMoreKeys() { 697 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0; 698 } 699 700 public final boolean hasNoPanelAutoMoreKey() { 701 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY) != 0; 702 } 703 704 public final String getOutputText() { 705 final OptionalAttributes attrs = mOptionalAttributes; 706 return (attrs != null) ? attrs.mOutputText : null; 707 } 708 709 public final int getAltCode() { 710 final OptionalAttributes attrs = mOptionalAttributes; 711 return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED; 712 } 713 714 public int getIconId() { 715 return mIconId; 716 } 717 718 public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { 719 final OptionalAttributes attrs = mOptionalAttributes; 720 final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED; 721 final int iconId = mEnabled ? getIconId() : disabledIconId; 722 final Drawable icon = iconSet.getIconDrawable(iconId); 723 if (icon != null) { 724 icon.setAlpha(alpha); 725 } 726 return icon; 727 } 728 729 public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) { 730 return iconSet.getIconDrawable(getIconId()); 731 } 732 733 public int getWidth() { 734 return mWidth; 735 } 736 737 public int getHeight() { 738 return mHeight; 739 } 740 741 public int getX() { 742 return mX; 743 } 744 745 public int getY() { 746 return mY; 747 } 748 749 public final int getDrawX() { 750 final int x = getX(); 751 final OptionalAttributes attrs = mOptionalAttributes; 752 return (attrs == null) ? x : x + attrs.mVisualInsetsLeft; 753 } 754 755 public final int getDrawWidth() { 756 final OptionalAttributes attrs = mOptionalAttributes; 757 return (attrs == null) ? mWidth 758 : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight; 759 } 760 761 /** 762 * Informs the key that it has been pressed, in case it needs to change its appearance or 763 * state. 764 * @see #onReleased() 765 */ 766 public void onPressed() { 767 mPressed = true; 768 } 769 770 /** 771 * Informs the key that it has been released, in case it needs to change its appearance or 772 * state. 773 * @see #onPressed() 774 */ 775 public void onReleased() { 776 mPressed = false; 777 } 778 779 public final boolean isEnabled() { 780 return mEnabled; 781 } 782 783 public void setEnabled(final boolean enabled) { 784 mEnabled = enabled; 785 } 786 787 public Rect getHitBox() { 788 return mHitBox; 789 } 790 791 /** 792 * Detects if a point falls on this key. 793 * @param x the x-coordinate of the point 794 * @param y the y-coordinate of the point 795 * @return whether or not the point falls on the key. If the key is attached to an edge, it 796 * will assume that all points between the key and the edge are considered to be on the key. 797 * @see #markAsLeftEdge(KeyboardParams) etc. 798 */ 799 public boolean isOnKey(final int x, final int y) { 800 return mHitBox.contains(x, y); 801 } 802 803 /** 804 * Returns the square of the distance to the nearest edge of the key and the given point. 805 * @param x the x-coordinate of the point 806 * @param y the y-coordinate of the point 807 * @return the square of the distance of the point from the nearest edge of the key 808 */ 809 public int squaredDistanceToEdge(final int x, final int y) { 810 final int left = getX(); 811 final int right = left + mWidth; 812 final int top = getY(); 813 final int bottom = top + mHeight; 814 final int edgeX = x < left ? left : (x > right ? right : x); 815 final int edgeY = y < top ? top : (y > bottom ? bottom : y); 816 final int dx = x - edgeX; 817 final int dy = y - edgeY; 818 return dx * dx + dy * dy; 819 } 820 821 static class KeyBackgroundState { 822 private final int[] mReleasedState; 823 private final int[] mPressedState; 824 825 private KeyBackgroundState(final int ... attrs) { 826 mReleasedState = attrs; 827 mPressedState = Arrays.copyOf(attrs, attrs.length + 1); 828 mPressedState[attrs.length] = android.R.attr.state_pressed; 829 } 830 831 public int[] getState(final boolean pressed) { 832 return pressed ? mPressedState : mReleasedState; 833 } 834 835 public static final KeyBackgroundState[] STATES = { 836 // 0: BACKGROUND_TYPE_EMPTY 837 new KeyBackgroundState(android.R.attr.state_empty), 838 // 1: BACKGROUND_TYPE_NORMAL 839 new KeyBackgroundState(), 840 // 2: BACKGROUND_TYPE_FUNCTIONAL 841 new KeyBackgroundState(), 842 // 3: BACKGROUND_TYPE_STICKY_OFF 843 new KeyBackgroundState(android.R.attr.state_checkable), 844 // 4: BACKGROUND_TYPE_STICKY_ON 845 new KeyBackgroundState(android.R.attr.state_checkable, android.R.attr.state_checked), 846 // 5: BACKGROUND_TYPE_ACTION 847 new KeyBackgroundState(android.R.attr.state_active), 848 // 6: BACKGROUND_TYPE_CUSTOM_ACTION 849 new KeyBackgroundState(android.R.attr.state_active, android.R.attr.state_checked) 850 }; 851 } 852 853 /** 854 * Returns the background drawable for the key, based on the current state and type of the key. 855 * @return the background drawable of the key. 856 * @see android.graphics.drawable.StateListDrawable#setState(int[]) 857 */ 858 public final Drawable selectBackgroundDrawable(final Drawable keyBackground, 859 final Drawable functionalKeyBackground, final Drawable spacebarBackground) { 860 final Drawable background; 861 if (mBackgroundType == BACKGROUND_TYPE_FUNCTIONAL) { 862 background = functionalKeyBackground; 863 } else if (getCode() == Constants.CODE_SPACE) { 864 background = spacebarBackground; 865 } else { 866 background = keyBackground; 867 } 868 final int[] state = KeyBackgroundState.STATES[mBackgroundType].getState(mPressed); 869 background.setState(state); 870 return background; 871 } 872 873 public static class Spacer extends Key { 874 public Spacer(final TypedArray keyAttr, final KeyStyle keyStyle, 875 final KeyboardParams params, final KeyboardRow row) { 876 super(null /* keySpec */, keyAttr, keyStyle, params, row); 877 } 878 879 /** 880 * This constructor is being used only for divider in more keys keyboard. 881 */ 882 protected Spacer(final KeyboardParams params, final int x, final int y, final int width, 883 final int height) { 884 super(null /* label */, ICON_UNDEFINED, CODE_UNSPECIFIED, null /* outputText */, 885 null /* hintLabel */, 0 /* labelFlags */, BACKGROUND_TYPE_EMPTY, x, y, width, 886 height, params.mHorizontalGap, params.mVerticalGap); 887 } 888 } 889} 890