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