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