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