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