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