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