Key.java revision a83a1feb62c4b4ff1a7cf5b6f58ad115491de76f
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 if (TextUtils.isEmpty(keySpec)) { 320 throw new RuntimeException("Empty keySpec"); 321 } 322 323 mIconId = 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 code = KeySpecParser.getCode(keySpec, params.mCodesSet); 330 if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) { 331 mLabel = params.mId.mCustomActionLabel; 332 } else if (code >= Character.MIN_SUPPLEMENTARY_CODE_POINT) { 333 // This is a workaround to have a key that has a supplementary code point in its label. 334 // Because we can put a string in resource neither as a XML entity of a supplementary 335 // code point nor as a surrogate pair. 336 mLabel = new StringBuilder().appendCodePoint(code).toString(); 337 } else { 338 mLabel = StringUtils.toUpperCaseOfStringForLocale( 339 KeySpecParser.getLabel(keySpec), needsToUpperCase, locale); 340 } 341 if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) { 342 mHintLabel = null; 343 } else { 344 mHintLabel = StringUtils.toUpperCaseOfStringForLocale(style.getString(keyAttr, 345 R.styleable.Keyboard_Key_keyHintLabel), needsToUpperCase, locale); 346 } 347 String outputText = StringUtils.toUpperCaseOfStringForLocale( 348 KeySpecParser.getOutputText(keySpec), needsToUpperCase, locale); 349 // Choose the first letter of the label as primary code if not specified. 350 if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText) 351 && !TextUtils.isEmpty(mLabel)) { 352 if (StringUtils.codePointCount(mLabel) == 1) { 353 // Use the first letter of the hint label if shiftedLetterActivated flag is 354 // specified. 355 if (hasShiftedLetterHint() && isShiftedLetterActivated()) { 356 mCode = mHintLabel.codePointAt(0); 357 } else { 358 mCode = mLabel.codePointAt(0); 359 } 360 } else { 361 // In some locale and case, the character might be represented by multiple code 362 // points, such as upper case Eszett of German alphabet. 363 outputText = mLabel; 364 mCode = CODE_OUTPUT_TEXT; 365 } 366 } else if (code == CODE_UNSPECIFIED && outputText != null) { 367 if (StringUtils.codePointCount(outputText) == 1) { 368 mCode = outputText.codePointAt(0); 369 outputText = null; 370 } else { 371 mCode = CODE_OUTPUT_TEXT; 372 } 373 } else { 374 mCode = StringUtils.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale); 375 } 376 final int altCodeInAttr = KeySpecParser.parseCode(style.getString(keyAttr, 377 R.styleable.Keyboard_Key_altCode), params.mCodesSet, CODE_UNSPECIFIED); 378 final int altCode = StringUtils.toUpperCaseOfCodeForLocale( 379 altCodeInAttr, needsToUpperCase, locale); 380 mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode, 381 disabledIconId, previewIconId, visualInsetsLeft, visualInsetsRight); 382 mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr); 383 keyAttr.recycle(); 384 mHashCode = computeHashCode(this); 385 } 386 387 /** 388 * Copy constructor. 389 * 390 * @param key the original key. 391 */ 392 protected Key(final Key key) { 393 // Final attributes. 394 mCode = key.mCode; 395 mLabel = key.mLabel; 396 mHintLabel = key.mHintLabel; 397 mLabelFlags = key.mLabelFlags; 398 mIconId = key.mIconId; 399 mWidth = key.mWidth; 400 mHeight = key.mHeight; 401 mX = key.mX; 402 mY = key.mY; 403 mHitBox.set(key.mHitBox); 404 mMoreKeys = key.mMoreKeys; 405 mMoreKeysColumnAndFlags = key.mMoreKeysColumnAndFlags; 406 mBackgroundType = key.mBackgroundType; 407 mActionFlags = key.mActionFlags; 408 mKeyVisualAttributes = key.mKeyVisualAttributes; 409 mOptionalAttributes = key.mOptionalAttributes; 410 mHashCode = key.mHashCode; 411 // Key state. 412 mPressed = key.mPressed; 413 mEnabled = key.mEnabled; 414 } 415 416 private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) { 417 if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false; 418 switch (keyboardElementId) { 419 case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: 420 case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: 421 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: 422 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: 423 return true; 424 default: 425 return false; 426 } 427 } 428 429 private static int computeHashCode(final Key key) { 430 return Arrays.hashCode(new Object[] { 431 key.mX, 432 key.mY, 433 key.mWidth, 434 key.mHeight, 435 key.mCode, 436 key.mLabel, 437 key.mHintLabel, 438 key.mIconId, 439 key.mBackgroundType, 440 Arrays.hashCode(key.mMoreKeys), 441 key.getOutputText(), 442 key.mActionFlags, 443 key.mLabelFlags, 444 // Key can be distinguishable without the following members. 445 // key.mOptionalAttributes.mAltCode, 446 // key.mOptionalAttributes.mDisabledIconId, 447 // key.mOptionalAttributes.mPreviewIconId, 448 // key.mHorizontalGap, 449 // key.mVerticalGap, 450 // key.mOptionalAttributes.mVisualInsetLeft, 451 // key.mOptionalAttributes.mVisualInsetRight, 452 // key.mMaxMoreKeysColumn, 453 }); 454 } 455 456 private boolean equalsInternal(final Key o) { 457 if (this == o) return true; 458 return o.mX == mX 459 && o.mY == mY 460 && o.mWidth == mWidth 461 && o.mHeight == mHeight 462 && o.mCode == mCode 463 && TextUtils.equals(o.mLabel, mLabel) 464 && TextUtils.equals(o.mHintLabel, mHintLabel) 465 && o.mIconId == mIconId 466 && o.mBackgroundType == mBackgroundType 467 && Arrays.equals(o.mMoreKeys, mMoreKeys) 468 && TextUtils.equals(o.getOutputText(), getOutputText()) 469 && o.mActionFlags == mActionFlags 470 && o.mLabelFlags == mLabelFlags; 471 } 472 473 @Override 474 public int compareTo(Key o) { 475 if (equalsInternal(o)) return 0; 476 if (mHashCode > o.mHashCode) return 1; 477 return -1; 478 } 479 480 @Override 481 public int hashCode() { 482 return mHashCode; 483 } 484 485 @Override 486 public boolean equals(final Object o) { 487 return o instanceof Key && equalsInternal((Key)o); 488 } 489 490 @Override 491 public String toString() { 492 final String label; 493 if (StringUtils.codePointCount(mLabel) == 1 && mLabel.codePointAt(0) == mCode) { 494 label = ""; 495 } else { 496 label = "/" + mLabel; 497 } 498 return String.format(Locale.ROOT, "%s%s %d,%d %dx%d %s/%s/%s", 499 Constants.printableCode(mCode), label, mX, mY, mWidth, mHeight, mHintLabel, 500 KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType)); 501 } 502 503 private static String backgroundName(final int backgroundType) { 504 switch (backgroundType) { 505 case BACKGROUND_TYPE_EMPTY: return "empty"; 506 case BACKGROUND_TYPE_NORMAL: return "normal"; 507 case BACKGROUND_TYPE_FUNCTIONAL: return "functional"; 508 case BACKGROUND_TYPE_ACTION: return "action"; 509 case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff"; 510 case BACKGROUND_TYPE_STICKY_ON: return "stickyOn"; 511 default: return null; 512 } 513 } 514 515 public int getCode() { 516 return mCode; 517 } 518 519 public String getLabel() { 520 return mLabel; 521 } 522 523 public String getHintLabel() { 524 return mHintLabel; 525 } 526 527 public MoreKeySpec[] getMoreKeys() { 528 return mMoreKeys; 529 } 530 531 public void markAsLeftEdge(final KeyboardParams params) { 532 mHitBox.left = params.mLeftPadding; 533 } 534 535 public void markAsRightEdge(final KeyboardParams params) { 536 mHitBox.right = params.mOccupiedWidth - params.mRightPadding; 537 } 538 539 public void markAsTopEdge(final KeyboardParams params) { 540 mHitBox.top = params.mTopPadding; 541 } 542 543 public void markAsBottomEdge(final KeyboardParams params) { 544 mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding; 545 } 546 547 public final boolean isSpacer() { 548 return this instanceof Spacer; 549 } 550 551 public final boolean isShift() { 552 return mCode == CODE_SHIFT; 553 } 554 555 public final boolean isModifier() { 556 return mCode == CODE_SHIFT || mCode == CODE_SWITCH_ALPHA_SYMBOL; 557 } 558 559 public final boolean isRepeatable() { 560 return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0; 561 } 562 563 public final boolean noKeyPreview() { 564 return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0; 565 } 566 567 public final boolean altCodeWhileTyping() { 568 return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0; 569 } 570 571 public final boolean isLongPressEnabled() { 572 // We need not start long press timer on the key which has activated shifted letter. 573 return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0 574 && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0; 575 } 576 577 public KeyVisualAttributes getVisualAttributes() { 578 return mKeyVisualAttributes; 579 } 580 581 public final Typeface selectTypeface(final KeyDrawParams params) { 582 // TODO: Handle "bold" here too? 583 if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) { 584 return Typeface.DEFAULT; 585 } 586 if ((mLabelFlags & LABEL_FLAGS_FONT_MONO_SPACE) != 0) { 587 return Typeface.MONOSPACE; 588 } 589 return params.mTypeface; 590 } 591 592 public final int selectTextSize(final KeyDrawParams params) { 593 switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) { 594 case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO: 595 return params.mLetterSize; 596 case LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO: 597 return params.mLargeLetterSize; 598 case LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO: 599 return params.mLabelSize; 600 case LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO: 601 return params.mLargeLabelSize; 602 case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO: 603 return params.mHintLabelSize; 604 default: // No follow key ratio flag specified. 605 return StringUtils.codePointCount(mLabel) == 1 ? params.mLetterSize : params.mLabelSize; 606 } 607 } 608 609 public final int selectTextColor(final KeyDrawParams params) { 610 if (isShiftedLetterActivated()) { 611 return params.mTextInactivatedColor; 612 } 613 if (params.mTextColorStateList == null) { 614 return DEFAULT_TEXT_COLOR; 615 } 616 final int[] state; 617 // TODO: Hack!!!!!!!! Consider having a new attribute for the functional text labels. 618 // Currently, we distinguish "input key" from "functional key" by checking the 619 // length of the label( > 1) and "functional" attributes (= true). 620 if (mLabel != null && mLabel.length() > 1) { 621 state = getCurrentDrawableState(); 622 } else { 623 state = KEY_STATE_NORMAL; 624 } 625 return params.mTextColorStateList.getColorForState(state, DEFAULT_TEXT_COLOR); 626 } 627 628 public final int selectHintTextSize(final KeyDrawParams params) { 629 if (hasHintLabel()) { 630 return params.mHintLabelSize; 631 } 632 if (hasShiftedLetterHint()) { 633 return params.mShiftedLetterHintSize; 634 } 635 return params.mHintLetterSize; 636 } 637 638 public final int selectHintTextColor(final KeyDrawParams params) { 639 if (hasHintLabel()) { 640 return params.mHintLabelColor; 641 } 642 if (hasShiftedLetterHint()) { 643 return isShiftedLetterActivated() ? params.mShiftedLetterHintActivatedColor 644 : params.mShiftedLetterHintInactivatedColor; 645 } 646 return params.mHintLetterColor; 647 } 648 649 public final int selectMoreKeyTextSize(final KeyDrawParams params) { 650 return hasLabelsInMoreKeys() ? params.mLabelSize : params.mLetterSize; 651 } 652 653 public final String getPreviewLabel() { 654 return isShiftedLetterActivated() ? mHintLabel : mLabel; 655 } 656 657 private boolean previewHasLetterSize() { 658 return (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO) != 0 659 || StringUtils.codePointCount(getPreviewLabel()) == 1; 660 } 661 662 public final int selectPreviewTextSize(final KeyDrawParams params) { 663 if (previewHasLetterSize()) { 664 return params.mPreviewTextSize; 665 } 666 return params.mLetterSize; 667 } 668 669 public Typeface selectPreviewTypeface(final KeyDrawParams params) { 670 if (previewHasLetterSize()) { 671 return selectTypeface(params); 672 } 673 return Typeface.DEFAULT_BOLD; 674 } 675 676 public final boolean isAlignLeft() { 677 return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0; 678 } 679 680 public final boolean isAlignRight() { 681 return (mLabelFlags & LABEL_FLAGS_ALIGN_RIGHT) != 0; 682 } 683 684 public final boolean isAlignLeftOfCenter() { 685 return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0; 686 } 687 688 public final boolean hasPopupHint() { 689 return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0; 690 } 691 692 public final boolean hasShiftedLetterHint() { 693 return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0 694 && !TextUtils.isEmpty(mHintLabel); 695 } 696 697 public final boolean hasHintLabel() { 698 return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0; 699 } 700 701 public final boolean hasLabelWithIconLeft() { 702 return (mLabelFlags & LABEL_FLAGS_WITH_ICON_LEFT) != 0; 703 } 704 705 public final boolean hasLabelWithIconRight() { 706 return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0; 707 } 708 709 public final boolean needsAutoXScale() { 710 return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0; 711 } 712 713 public final boolean needsAutoScale() { 714 return (mLabelFlags & LABEL_FLAGS_AUTO_SCALE) == LABEL_FLAGS_AUTO_SCALE; 715 } 716 717 private final boolean isShiftedLetterActivated() { 718 return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0 719 && !TextUtils.isEmpty(mHintLabel); 720 } 721 722 public final int getMoreKeysColumn() { 723 return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_MASK; 724 } 725 726 public final boolean isFixedColumnOrderMoreKeys() { 727 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER) != 0; 728 } 729 730 public final boolean hasLabelsInMoreKeys() { 731 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0; 732 } 733 734 public final int getMoreKeyLabelFlags() { 735 return hasLabelsInMoreKeys() 736 ? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO 737 : LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO; 738 } 739 740 public final boolean needsDividersInMoreKeys() { 741 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0; 742 } 743 744 public final boolean hasNoPanelAutoMoreKey() { 745 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY) != 0; 746 } 747 748 public final String getOutputText() { 749 final OptionalAttributes attrs = mOptionalAttributes; 750 return (attrs != null) ? attrs.mOutputText : null; 751 } 752 753 public final int getAltCode() { 754 final OptionalAttributes attrs = mOptionalAttributes; 755 return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED; 756 } 757 758 public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { 759 final OptionalAttributes attrs = mOptionalAttributes; 760 final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED; 761 final int iconId = mEnabled ? mIconId : disabledIconId; 762 final Drawable icon = iconSet.getIconDrawable(iconId); 763 if (icon != null) { 764 icon.setAlpha(alpha); 765 } 766 return icon; 767 } 768 769 public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) { 770 final OptionalAttributes attrs = mOptionalAttributes; 771 final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED; 772 return previewIconId != ICON_UNDEFINED 773 ? iconSet.getIconDrawable(previewIconId) : iconSet.getIconDrawable(mIconId); 774 } 775 776 public int getWidth() { 777 return mWidth; 778 } 779 780 public int getHeight() { 781 return mHeight; 782 } 783 784 public int getX() { 785 return mX; 786 } 787 788 public int getY() { 789 return mY; 790 } 791 792 public final int getDrawX() { 793 final int x = getX(); 794 final OptionalAttributes attrs = mOptionalAttributes; 795 return (attrs == null) ? x : x + attrs.mVisualInsetsLeft; 796 } 797 798 public final int getDrawWidth() { 799 final OptionalAttributes attrs = mOptionalAttributes; 800 return (attrs == null) ? mWidth 801 : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight; 802 } 803 804 /** 805 * Informs the key that it has been pressed, in case it needs to change its appearance or 806 * state. 807 * @see #onReleased() 808 */ 809 public void onPressed() { 810 mPressed = true; 811 } 812 813 /** 814 * Informs the key that it has been released, in case it needs to change its appearance or 815 * state. 816 * @see #onPressed() 817 */ 818 public void onReleased() { 819 mPressed = false; 820 } 821 822 public final boolean isEnabled() { 823 return mEnabled; 824 } 825 826 public void setEnabled(final boolean enabled) { 827 mEnabled = enabled; 828 } 829 830 public Rect getHitBox() { 831 return mHitBox; 832 } 833 834 /** 835 * Detects if a point falls on this key. 836 * @param x the x-coordinate of the point 837 * @param y the y-coordinate of the point 838 * @return whether or not the point falls on the key. If the key is attached to an edge, it 839 * will assume that all points between the key and the edge are considered to be on the key. 840 * @see #markAsLeftEdge(KeyboardParams) etc. 841 */ 842 public boolean isOnKey(final int x, final int y) { 843 return mHitBox.contains(x, y); 844 } 845 846 /** 847 * Returns the square of the distance to the nearest edge of the key and the given point. 848 * @param x the x-coordinate of the point 849 * @param y the y-coordinate of the point 850 * @return the square of the distance of the point from the nearest edge of the key 851 */ 852 public int squaredDistanceToEdge(final int x, final int y) { 853 final int left = getX(); 854 final int right = left + mWidth; 855 final int top = getY(); 856 final int bottom = top + mHeight; 857 final int edgeX = x < left ? left : (x > right ? right : x); 858 final int edgeY = y < top ? top : (y > bottom ? bottom : y); 859 final int dx = x - edgeX; 860 final int dy = y - edgeY; 861 return dx * dx + dy * dy; 862 } 863 864 private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_ON = { 865 android.R.attr.state_checkable, 866 android.R.attr.state_checked 867 }; 868 869 private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_ON = { 870 android.R.attr.state_pressed, 871 android.R.attr.state_checkable, 872 android.R.attr.state_checked 873 }; 874 875 private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_OFF = { 876 android.R.attr.state_checkable 877 }; 878 879 private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_OFF = { 880 android.R.attr.state_pressed, 881 android.R.attr.state_checkable 882 }; 883 884 private final static int[] KEY_STATE_NORMAL = { 885 }; 886 887 private final static int[] KEY_STATE_PRESSED = { 888 android.R.attr.state_pressed 889 }; 890 891 private final static int[] KEY_STATE_EMPTY = { 892 android.R.attr.state_empty 893 }; 894 895 // functional normal state (with properties) 896 private static final int[] KEY_STATE_FUNCTIONAL_NORMAL = { 897 android.R.attr.state_single 898 }; 899 900 // functional pressed state (with properties) 901 private static final int[] KEY_STATE_FUNCTIONAL_PRESSED = { 902 android.R.attr.state_single, 903 android.R.attr.state_pressed 904 }; 905 906 // action normal state (with properties) 907 private static final int[] KEY_STATE_ACTIVE_NORMAL = { 908 android.R.attr.state_active 909 }; 910 911 // action pressed state (with properties) 912 private static final int[] KEY_STATE_ACTIVE_PRESSED = { 913 android.R.attr.state_active, 914 android.R.attr.state_pressed 915 }; 916 917 /** 918 * Returns the drawable state for the key, based on the current state and type of the key. 919 * @return the drawable state of the key. 920 * @see android.graphics.drawable.StateListDrawable#setState(int[]) 921 */ 922 public final int[] getCurrentDrawableState() { 923 switch (mBackgroundType) { 924 case BACKGROUND_TYPE_FUNCTIONAL: 925 return mPressed ? KEY_STATE_FUNCTIONAL_PRESSED : KEY_STATE_FUNCTIONAL_NORMAL; 926 case BACKGROUND_TYPE_ACTION: 927 return mPressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL; 928 case BACKGROUND_TYPE_STICKY_OFF: 929 return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF; 930 case BACKGROUND_TYPE_STICKY_ON: 931 return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON; 932 case BACKGROUND_TYPE_EMPTY: 933 return mPressed ? KEY_STATE_PRESSED : KEY_STATE_EMPTY; 934 default: /* BACKGROUND_TYPE_NORMAL */ 935 return mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL; 936 } 937 } 938 939 public static class Spacer extends Key { 940 public Spacer(final Resources res, final KeyboardParams params, final KeyboardRow row, 941 final XmlPullParser parser) throws XmlPullParserException { 942 super(res, params, row, parser); 943 } 944 945 /** 946 * This constructor is being used only for divider in more keys keyboard. 947 */ 948 protected Spacer(final KeyboardParams params, final int x, final int y, final int width, 949 final int height) { 950 super(params, null, null, ICON_UNDEFINED, CODE_UNSPECIFIED, 951 null, x, y, width, height, 0, BACKGROUND_TYPE_EMPTY); 952 } 953 } 954} 955