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