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