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