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