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