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