1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.android.inputmethod.keyboard; 18 19 import static com.android.inputmethod.keyboard.Keyboard.CODE_OUTPUT_TEXT; 20 import static com.android.inputmethod.keyboard.Keyboard.CODE_SHIFT; 21 import static com.android.inputmethod.keyboard.Keyboard.CODE_SWITCH_ALPHA_SYMBOL; 22 import static com.android.inputmethod.keyboard.Keyboard.CODE_UNSPECIFIED; 23 import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED; 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.R; 43 import com.android.inputmethod.latin.ResourceUtils; 44 import com.android.inputmethod.latin.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 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 OptionalAttributes(final String outputText, final int altCode, final int disabledIconId, final int previewIconId, final int visualInsetsLeft, final int visualInsetsRight)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 */ Key(final KeyboardParams params, final MoreKeySpec moreKeySpec, final int x, final int y, final int width, final int height, final int labelFlags)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 */ Key(final KeyboardParams params, final String label, final String hintLabel, final int iconId, final int code, final String outputText, final int x, final int y, final int width, final int height, final int labelFlags)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 */ Key(final Resources res, final KeyboardParams params, final KeyboardRow row, final XmlPullParser parser)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 keyHeight = row.mRowHeight; 228 mHeight = keyHeight - 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 + keyHeight); 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 visualInsetsLeft = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr, 251 R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0)); 252 final int visualInsetsRight = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr, 253 R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0)); 254 mIconId = KeySpecParser.getIconId(style.getString(keyAttr, 255 R.styleable.Keyboard_Key_keyIcon)); 256 final int disabledIconId = KeySpecParser.getIconId(style.getString(keyAttr, 257 R.styleable.Keyboard_Key_keyIconDisabled)); 258 final int previewIconId = KeySpecParser.getIconId(style.getString(keyAttr, 259 R.styleable.Keyboard_Key_keyIconPreview)); 260 261 mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags) 262 | row.getDefaultKeyLabelFlags(); 263 final boolean needsToUpperCase = needsToUpperCase(mLabelFlags, params.mId.mElementId); 264 final Locale locale = params.mId.mLocale; 265 int actionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags); 266 String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys); 267 268 int moreKeysColumn = style.getInt(keyAttr, 269 R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMoreKeysKeyboardColumn); 270 int value; 271 if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) { 272 moreKeysColumn = value & MORE_KEYS_COLUMN_MASK; 273 } 274 if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) { 275 moreKeysColumn = MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER | (value & MORE_KEYS_COLUMN_MASK); 276 } 277 if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) { 278 moreKeysColumn |= MORE_KEYS_FLAGS_HAS_LABELS; 279 } 280 if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) { 281 moreKeysColumn |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS; 282 } 283 if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_EMBEDDED_MORE_KEY)) { 284 moreKeysColumn |= MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY; 285 } 286 mMoreKeysColumnAndFlags = moreKeysColumn; 287 288 final String[] additionalMoreKeys; 289 if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) { 290 additionalMoreKeys = null; 291 } else { 292 additionalMoreKeys = style.getStringArray(keyAttr, 293 R.styleable.Keyboard_Key_additionalMoreKeys); 294 } 295 moreKeys = KeySpecParser.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys); 296 if (moreKeys != null) { 297 actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS; 298 mMoreKeys = new MoreKeySpec[moreKeys.length]; 299 for (int i = 0; i < moreKeys.length; i++) { 300 mMoreKeys[i] = new MoreKeySpec( 301 moreKeys[i], needsToUpperCase, locale, params.mCodesSet); 302 } 303 } else { 304 mMoreKeys = null; 305 } 306 mActionFlags = actionFlags; 307 308 if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) { 309 mLabel = params.mId.mCustomActionLabel; 310 } else { 311 mLabel = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr, 312 R.styleable.Keyboard_Key_keyLabel), needsToUpperCase, locale); 313 } 314 if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) { 315 mHintLabel = null; 316 } else { 317 mHintLabel = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr, 318 R.styleable.Keyboard_Key_keyHintLabel), needsToUpperCase, locale); 319 } 320 String outputText = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr, 321 R.styleable.Keyboard_Key_keyOutputText), needsToUpperCase, locale); 322 final int code = KeySpecParser.parseCode(style.getString(keyAttr, 323 R.styleable.Keyboard_Key_code), params.mCodesSet, CODE_UNSPECIFIED); 324 // Choose the first letter of the label as primary code if not specified. 325 if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText) 326 && !TextUtils.isEmpty(mLabel)) { 327 if (StringUtils.codePointCount(mLabel) == 1) { 328 // Use the first letter of the hint label if shiftedLetterActivated flag is 329 // specified. 330 if (hasShiftedLetterHint() && isShiftedLetterActivated() 331 && !TextUtils.isEmpty(mHintLabel)) { 332 mCode = mHintLabel.codePointAt(0); 333 } else { 334 mCode = mLabel.codePointAt(0); 335 } 336 } else { 337 // In some locale and case, the character might be represented by multiple code 338 // points, such as upper case Eszett of German alphabet. 339 outputText = mLabel; 340 mCode = CODE_OUTPUT_TEXT; 341 } 342 } else if (code == CODE_UNSPECIFIED && outputText != null) { 343 if (StringUtils.codePointCount(outputText) == 1) { 344 mCode = outputText.codePointAt(0); 345 outputText = null; 346 } else { 347 mCode = CODE_OUTPUT_TEXT; 348 } 349 } else { 350 mCode = KeySpecParser.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale); 351 } 352 final int altCode = KeySpecParser.toUpperCaseOfCodeForLocale( 353 KeySpecParser.parseCode(style.getString(keyAttr, 354 R.styleable.Keyboard_Key_altCode), params.mCodesSet, CODE_UNSPECIFIED), 355 needsToUpperCase, locale); 356 if (outputText == null && altCode == CODE_UNSPECIFIED 357 && disabledIconId == ICON_UNDEFINED && previewIconId == ICON_UNDEFINED 358 && visualInsetsLeft == 0 && visualInsetsRight == 0) { 359 mOptionalAttributes = null; 360 } else { 361 mOptionalAttributes = new OptionalAttributes(outputText, altCode, 362 disabledIconId, previewIconId, 363 visualInsetsLeft, visualInsetsRight); 364 } 365 mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr); 366 keyAttr.recycle(); 367 mHashCode = computeHashCode(this); 368 if (hasShiftedLetterHint() && TextUtils.isEmpty(mHintLabel)) { 369 Log.w(TAG, "hasShiftedLetterHint specified without keyHintLabel: " + this); 370 } 371 } 372 needsToUpperCase(final int labelFlags, final int keyboardElementId)373 private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) { 374 if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false; 375 switch (keyboardElementId) { 376 case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: 377 case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: 378 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: 379 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: 380 return true; 381 default: 382 return false; 383 } 384 } 385 computeHashCode(final Key key)386 private static int computeHashCode(final Key key) { 387 return Arrays.hashCode(new Object[] { 388 key.mX, 389 key.mY, 390 key.mWidth, 391 key.mHeight, 392 key.mCode, 393 key.mLabel, 394 key.mHintLabel, 395 key.mIconId, 396 key.mBackgroundType, 397 Arrays.hashCode(key.mMoreKeys), 398 key.getOutputText(), 399 key.mActionFlags, 400 key.mLabelFlags, 401 // Key can be distinguishable without the following members. 402 // key.mOptionalAttributes.mAltCode, 403 // key.mOptionalAttributes.mDisabledIconId, 404 // key.mOptionalAttributes.mPreviewIconId, 405 // key.mHorizontalGap, 406 // key.mVerticalGap, 407 // key.mOptionalAttributes.mVisualInsetLeft, 408 // key.mOptionalAttributes.mVisualInsetRight, 409 // key.mMaxMoreKeysColumn, 410 }); 411 } 412 equalsInternal(final Key o)413 private boolean equalsInternal(final Key o) { 414 if (this == o) return true; 415 return o.mX == mX 416 && o.mY == mY 417 && o.mWidth == mWidth 418 && o.mHeight == mHeight 419 && o.mCode == mCode 420 && TextUtils.equals(o.mLabel, mLabel) 421 && TextUtils.equals(o.mHintLabel, mHintLabel) 422 && o.mIconId == mIconId 423 && o.mBackgroundType == mBackgroundType 424 && Arrays.equals(o.mMoreKeys, mMoreKeys) 425 && TextUtils.equals(o.getOutputText(), getOutputText()) 426 && o.mActionFlags == mActionFlags 427 && o.mLabelFlags == mLabelFlags; 428 } 429 430 @Override compareTo(Key o)431 public int compareTo(Key o) { 432 if (equalsInternal(o)) return 0; 433 if (mHashCode > o.mHashCode) return 1; 434 return -1; 435 } 436 437 @Override hashCode()438 public int hashCode() { 439 return mHashCode; 440 } 441 442 @Override equals(final Object o)443 public boolean equals(final Object o) { 444 return o instanceof Key && equalsInternal((Key)o); 445 } 446 447 @Override toString()448 public String toString() { 449 final String label; 450 if (StringUtils.codePointCount(mLabel) == 1 && mLabel.codePointAt(0) == mCode) { 451 label = ""; 452 } else { 453 label = "/" + mLabel; 454 } 455 return String.format("%s%s %d,%d %dx%d %s/%s/%s", 456 Keyboard.printableCode(mCode), label, mX, mY, mWidth, mHeight, mHintLabel, 457 KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType)); 458 } 459 backgroundName(final int backgroundType)460 private static String backgroundName(final int backgroundType) { 461 switch (backgroundType) { 462 case BACKGROUND_TYPE_NORMAL: return "normal"; 463 case BACKGROUND_TYPE_FUNCTIONAL: return "functional"; 464 case BACKGROUND_TYPE_ACTION: return "action"; 465 case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff"; 466 case BACKGROUND_TYPE_STICKY_ON: return "stickyOn"; 467 default: return null; 468 } 469 } 470 markAsLeftEdge(final KeyboardParams params)471 public void markAsLeftEdge(final KeyboardParams params) { 472 mHitBox.left = params.mHorizontalEdgesPadding; 473 } 474 markAsRightEdge(final KeyboardParams params)475 public void markAsRightEdge(final KeyboardParams params) { 476 mHitBox.right = params.mOccupiedWidth - params.mHorizontalEdgesPadding; 477 } 478 markAsTopEdge(final KeyboardParams params)479 public void markAsTopEdge(final KeyboardParams params) { 480 mHitBox.top = params.mTopPadding; 481 } 482 markAsBottomEdge(final KeyboardParams params)483 public void markAsBottomEdge(final KeyboardParams params) { 484 mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding; 485 } 486 isSpacer()487 public final boolean isSpacer() { 488 return this instanceof Spacer; 489 } 490 isShift()491 public final boolean isShift() { 492 return mCode == CODE_SHIFT; 493 } 494 isModifier()495 public final boolean isModifier() { 496 return mCode == CODE_SHIFT || mCode == CODE_SWITCH_ALPHA_SYMBOL; 497 } 498 isRepeatable()499 public final boolean isRepeatable() { 500 return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0; 501 } 502 noKeyPreview()503 public final boolean noKeyPreview() { 504 return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0; 505 } 506 altCodeWhileTyping()507 public final boolean altCodeWhileTyping() { 508 return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0; 509 } 510 isLongPressEnabled()511 public final boolean isLongPressEnabled() { 512 // We need not start long press timer on the key which has activated shifted letter. 513 return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0 514 && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0; 515 } 516 selectTypeface(final KeyDrawParams params)517 public final Typeface selectTypeface(final KeyDrawParams params) { 518 // TODO: Handle "bold" here too? 519 if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) { 520 return Typeface.DEFAULT; 521 } else if ((mLabelFlags & LABEL_FLAGS_FONT_MONO_SPACE) != 0) { 522 return Typeface.MONOSPACE; 523 } else { 524 return params.mTypeface; 525 } 526 } 527 selectTextSize(final KeyDrawParams params)528 public final int selectTextSize(final KeyDrawParams params) { 529 switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) { 530 case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO: 531 return params.mLetterSize; 532 case LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO: 533 return params.mLargeLetterSize; 534 case LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO: 535 return params.mLabelSize; 536 case LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO: 537 return params.mLargeLabelSize; 538 case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO: 539 return params.mHintLabelSize; 540 default: // No follow key ratio flag specified. 541 return StringUtils.codePointCount(mLabel) == 1 ? params.mLetterSize : params.mLabelSize; 542 } 543 } 544 selectTextColor(final KeyDrawParams params)545 public final int selectTextColor(final KeyDrawParams params) { 546 return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor; 547 } 548 selectHintTextSize(final KeyDrawParams params)549 public final int selectHintTextSize(final KeyDrawParams params) { 550 if (hasHintLabel()) { 551 return params.mHintLabelSize; 552 } else if (hasShiftedLetterHint()) { 553 return params.mShiftedLetterHintSize; 554 } else { 555 return params.mHintLetterSize; 556 } 557 } 558 selectHintTextColor(final KeyDrawParams params)559 public final int selectHintTextColor(final KeyDrawParams params) { 560 if (hasHintLabel()) { 561 return params.mHintLabelColor; 562 } else if (hasShiftedLetterHint()) { 563 return isShiftedLetterActivated() ? params.mShiftedLetterHintActivatedColor 564 : params.mShiftedLetterHintInactivatedColor; 565 } else { 566 return params.mHintLetterColor; 567 } 568 } 569 selectMoreKeyTextSize(final KeyDrawParams params)570 public final int selectMoreKeyTextSize(final KeyDrawParams params) { 571 return hasLabelsInMoreKeys() ? params.mLabelSize : params.mLetterSize; 572 } 573 isAlignLeft()574 public final boolean isAlignLeft() { 575 return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0; 576 } 577 isAlignRight()578 public final boolean isAlignRight() { 579 return (mLabelFlags & LABEL_FLAGS_ALIGN_RIGHT) != 0; 580 } 581 isAlignLeftOfCenter()582 public final boolean isAlignLeftOfCenter() { 583 return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0; 584 } 585 hasPopupHint()586 public final boolean hasPopupHint() { 587 return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0; 588 } 589 hasShiftedLetterHint()590 public final boolean hasShiftedLetterHint() { 591 return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0; 592 } 593 hasHintLabel()594 public final boolean hasHintLabel() { 595 return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0; 596 } 597 hasLabelWithIconLeft()598 public final boolean hasLabelWithIconLeft() { 599 return (mLabelFlags & LABEL_FLAGS_WITH_ICON_LEFT) != 0; 600 } 601 hasLabelWithIconRight()602 public final boolean hasLabelWithIconRight() { 603 return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0; 604 } 605 needsXScale()606 public final boolean needsXScale() { 607 return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0; 608 } 609 isShiftedLetterActivated()610 public final boolean isShiftedLetterActivated() { 611 return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0; 612 } 613 getMoreKeysColumn()614 public final int getMoreKeysColumn() { 615 return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_MASK; 616 } 617 isFixedColumnOrderMoreKeys()618 public final boolean isFixedColumnOrderMoreKeys() { 619 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER) != 0; 620 } 621 hasLabelsInMoreKeys()622 public final boolean hasLabelsInMoreKeys() { 623 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0; 624 } 625 getMoreKeyLabelFlags()626 public final int getMoreKeyLabelFlags() { 627 return hasLabelsInMoreKeys() 628 ? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO 629 : LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO; 630 } 631 needsDividersInMoreKeys()632 public final boolean needsDividersInMoreKeys() { 633 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0; 634 } 635 hasEmbeddedMoreKey()636 public final boolean hasEmbeddedMoreKey() { 637 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY) != 0; 638 } 639 getOutputText()640 public final String getOutputText() { 641 final OptionalAttributes attrs = mOptionalAttributes; 642 return (attrs != null) ? attrs.mOutputText : null; 643 } 644 getAltCode()645 public final int getAltCode() { 646 final OptionalAttributes attrs = mOptionalAttributes; 647 return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED; 648 } 649 getIcon(final KeyboardIconsSet iconSet, final int alpha)650 public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { 651 final OptionalAttributes attrs = mOptionalAttributes; 652 final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED; 653 final int iconId = mEnabled ? mIconId : disabledIconId; 654 final Drawable icon = iconSet.getIconDrawable(iconId); 655 if (icon != null) { 656 icon.setAlpha(alpha); 657 } 658 return icon; 659 } 660 getPreviewIcon(final KeyboardIconsSet iconSet)661 public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) { 662 final OptionalAttributes attrs = mOptionalAttributes; 663 final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED; 664 return previewIconId != ICON_UNDEFINED 665 ? iconSet.getIconDrawable(previewIconId) : iconSet.getIconDrawable(mIconId); 666 } 667 getDrawX()668 public final int getDrawX() { 669 final OptionalAttributes attrs = mOptionalAttributes; 670 return (attrs == null) ? mX : mX + attrs.mVisualInsetsLeft; 671 } 672 getDrawWidth()673 public final int getDrawWidth() { 674 final OptionalAttributes attrs = mOptionalAttributes; 675 return (attrs == null) ? mWidth 676 : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight; 677 } 678 679 /** 680 * Informs the key that it has been pressed, in case it needs to change its appearance or 681 * state. 682 * @see #onReleased() 683 */ onPressed()684 public void onPressed() { 685 mPressed = true; 686 } 687 688 /** 689 * Informs the key that it has been released, in case it needs to change its appearance or 690 * state. 691 * @see #onPressed() 692 */ onReleased()693 public void onReleased() { 694 mPressed = false; 695 } 696 isEnabled()697 public final boolean isEnabled() { 698 return mEnabled; 699 } 700 setEnabled(final boolean enabled)701 public void setEnabled(final boolean enabled) { 702 mEnabled = enabled; 703 } 704 705 /** 706 * Detects if a point falls on this key. 707 * @param x the x-coordinate of the point 708 * @param y the y-coordinate of the point 709 * @return whether or not the point falls on the key. If the key is attached to an edge, it 710 * will assume that all points between the key and the edge are considered to be on the key. 711 * @see #markAsLeftEdge(KeyboardParams) etc. 712 */ isOnKey(final int x, final int y)713 public boolean isOnKey(final int x, final int y) { 714 return mHitBox.contains(x, y); 715 } 716 717 /** 718 * Returns the square of the distance to the nearest edge of the key and the given point. 719 * @param x the x-coordinate of the point 720 * @param y the y-coordinate of the point 721 * @return the square of the distance of the point from the nearest edge of the key 722 */ squaredDistanceToEdge(final int x, final int y)723 public int squaredDistanceToEdge(final int x, final int y) { 724 final int left = mX; 725 final int right = left + mWidth; 726 final int top = mY; 727 final int bottom = top + mHeight; 728 final int edgeX = x < left ? left : (x > right ? right : x); 729 final int edgeY = y < top ? top : (y > bottom ? bottom : y); 730 final int dx = x - edgeX; 731 final int dy = y - edgeY; 732 return dx * dx + dy * dy; 733 } 734 735 private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_ON = { 736 android.R.attr.state_checkable, 737 android.R.attr.state_checked 738 }; 739 740 private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_ON = { 741 android.R.attr.state_pressed, 742 android.R.attr.state_checkable, 743 android.R.attr.state_checked 744 }; 745 746 private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_OFF = { 747 android.R.attr.state_checkable 748 }; 749 750 private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_OFF = { 751 android.R.attr.state_pressed, 752 android.R.attr.state_checkable 753 }; 754 755 private final static int[] KEY_STATE_NORMAL = { 756 }; 757 758 private final static int[] KEY_STATE_PRESSED = { 759 android.R.attr.state_pressed 760 }; 761 762 // functional normal state (with properties) 763 private static final int[] KEY_STATE_FUNCTIONAL_NORMAL = { 764 android.R.attr.state_single 765 }; 766 767 // functional pressed state (with properties) 768 private static final int[] KEY_STATE_FUNCTIONAL_PRESSED = { 769 android.R.attr.state_single, 770 android.R.attr.state_pressed 771 }; 772 773 // action normal state (with properties) 774 private static final int[] KEY_STATE_ACTIVE_NORMAL = { 775 android.R.attr.state_active 776 }; 777 778 // action pressed state (with properties) 779 private static final int[] KEY_STATE_ACTIVE_PRESSED = { 780 android.R.attr.state_active, 781 android.R.attr.state_pressed 782 }; 783 784 /** 785 * Returns the drawable state for the key, based on the current state and type of the key. 786 * @return the drawable state of the key. 787 * @see android.graphics.drawable.StateListDrawable#setState(int[]) 788 */ getCurrentDrawableState()789 public final int[] getCurrentDrawableState() { 790 switch (mBackgroundType) { 791 case BACKGROUND_TYPE_FUNCTIONAL: 792 return mPressed ? KEY_STATE_FUNCTIONAL_PRESSED : KEY_STATE_FUNCTIONAL_NORMAL; 793 case BACKGROUND_TYPE_ACTION: 794 return mPressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL; 795 case BACKGROUND_TYPE_STICKY_OFF: 796 return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF; 797 case BACKGROUND_TYPE_STICKY_ON: 798 return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON; 799 default: /* BACKGROUND_TYPE_NORMAL */ 800 return mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL; 801 } 802 } 803 804 public static class Spacer extends Key { Spacer(final Resources res, final KeyboardParams params, final KeyboardRow row, final XmlPullParser parser)805 public Spacer(final Resources res, final KeyboardParams params, final KeyboardRow row, 806 final XmlPullParser parser) throws XmlPullParserException { 807 super(res, params, row, parser); 808 } 809 810 /** 811 * This constructor is being used only for divider in more keys keyboard. 812 */ Spacer(final KeyboardParams params, final int x, final int y, final int width, final int height)813 protected Spacer(final KeyboardParams params, final int x, final int y, final int width, 814 final int height) { 815 super(params, null, null, ICON_UNDEFINED, CODE_UNSPECIFIED, 816 null, x, y, width, height, 0); 817 } 818 } 819 } 820