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.common.Constants.CODE_OUTPUT_TEXT; 21 import static com.android.inputmethod.latin.common.Constants.CODE_SHIFT; 22 import static com.android.inputmethod.latin.common.Constants.CODE_SWITCH_ALPHA_SYMBOL; 23 import static com.android.inputmethod.latin.common.Constants.CODE_UNSPECIFIED; 24 25 import android.content.res.TypedArray; 26 import android.graphics.Rect; 27 import android.graphics.Typeface; 28 import android.graphics.drawable.Drawable; 29 import android.text.TextUtils; 30 31 import com.android.inputmethod.keyboard.internal.KeyDrawParams; 32 import com.android.inputmethod.keyboard.internal.KeySpecParser; 33 import com.android.inputmethod.keyboard.internal.KeyStyle; 34 import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; 35 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; 36 import com.android.inputmethod.keyboard.internal.KeyboardParams; 37 import com.android.inputmethod.keyboard.internal.KeyboardRow; 38 import com.android.inputmethod.keyboard.internal.MoreKeySpec; 39 import com.android.inputmethod.latin.R; 40 import com.android.inputmethod.latin.common.Constants; 41 import com.android.inputmethod.latin.common.StringUtils; 42 43 import java.util.Arrays; 44 import java.util.Locale; 45 46 import javax.annotation.Nonnull; 47 import javax.annotation.Nullable; 48 49 /** 50 * Class for describing the position and characteristics of a single key in the keyboard. 51 */ 52 public class Key implements Comparable<Key> { 53 /** 54 * The key code (unicode or custom code) that this key generates. 55 */ 56 private final int mCode; 57 58 /** Label to display */ 59 private final String mLabel; 60 /** Hint label to display on the key in conjunction with the label */ 61 private final String mHintLabel; 62 /** Flags of the label */ 63 private final int mLabelFlags; 64 private static final int LABEL_FLAGS_ALIGN_HINT_LABEL_TO_BOTTOM = 0x02; 65 private static final int LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM = 0x04; 66 private static final int LABEL_FLAGS_ALIGN_LABEL_OFF_CENTER = 0x08; 67 // Font typeface specification. 68 private static final int LABEL_FLAGS_FONT_MASK = 0x30; 69 private static final int LABEL_FLAGS_FONT_NORMAL = 0x10; 70 private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x20; 71 private static final int LABEL_FLAGS_FONT_DEFAULT = 0x30; 72 // Start of key text ratio enum values 73 private static final int LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK = 0x1C0; 74 private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO = 0x40; 75 private static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80; 76 private static final int LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO = 0xC0; 77 private static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x140; 78 // End of key text ratio mask enum values 79 private static final int LABEL_FLAGS_HAS_POPUP_HINT = 0x200; 80 private static final int LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT = 0x400; 81 private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800; 82 // The bit to calculate the ratio of key label width against key width. If autoXScale bit is on 83 // and autoYScale bit is off, the key label may be shrunk only for X-direction. 84 // If both autoXScale and autoYScale bits are on, the key label text size may be auto scaled. 85 private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000; 86 private static final int LABEL_FLAGS_AUTO_Y_SCALE = 0x8000; 87 private static final int LABEL_FLAGS_AUTO_SCALE = LABEL_FLAGS_AUTO_X_SCALE 88 | LABEL_FLAGS_AUTO_Y_SCALE; 89 private static final int LABEL_FLAGS_PRESERVE_CASE = 0x10000; 90 private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x20000; 91 private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x40000; 92 private static final int LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR = 0x80000; 93 private static final int LABEL_FLAGS_KEEP_BACKGROUND_ASPECT_RATIO = 0x100000; 94 private static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000; 95 private static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000; 96 97 /** Icon to display instead of a label. Icon takes precedence over a label */ 98 private final int mIconId; 99 100 /** Width of the key, excluding the gap */ 101 private final int mWidth; 102 /** Height of the key, excluding the gap */ 103 private final int mHeight; 104 /** 105 * The combined width in pixels of the horizontal gaps belonging to this key, both to the left 106 * and to the right. I.e., mWidth + mHorizontalGap = total width belonging to the key. 107 */ 108 private final int mHorizontalGap; 109 /** 110 * The combined height in pixels of the vertical gaps belonging to this key, both above and 111 * below. I.e., mHeight + mVerticalGap = total height belonging to the key. 112 */ 113 private final int mVerticalGap; 114 /** X coordinate of the top-left corner of the key in the keyboard layout, excluding the gap. */ 115 private final int mX; 116 /** Y coordinate of the top-left corner of the key in the keyboard layout, excluding the gap. */ 117 private final int mY; 118 /** Hit bounding box of the key */ 119 @Nonnull 120 private final Rect mHitBox = new Rect(); 121 122 /** More keys. It is guaranteed that this is null or an array of one or more elements */ 123 @Nullable 124 private final MoreKeySpec[] mMoreKeys; 125 /** More keys column number and flags */ 126 private final int mMoreKeysColumnAndFlags; 127 private static final int MORE_KEYS_COLUMN_NUMBER_MASK = 0x000000ff; 128 // If this flag is specified, more keys keyboard should have the specified number of columns. 129 // Otherwise more keys keyboard should have less than or equal to the specified maximum number 130 // of columns. 131 private static final int MORE_KEYS_FLAGS_FIXED_COLUMN = 0x00000100; 132 // If this flag is specified, the order of more keys is determined by the order in the more 133 // keys' specification. Otherwise the order of more keys is automatically determined. 134 private static final int MORE_KEYS_FLAGS_FIXED_ORDER = 0x00000200; 135 private static final int MORE_KEYS_MODE_MAX_COLUMN_WITH_AUTO_ORDER = 0; 136 private static final int MORE_KEYS_MODE_FIXED_COLUMN_WITH_AUTO_ORDER = 137 MORE_KEYS_FLAGS_FIXED_COLUMN; 138 private static final int MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER = 139 (MORE_KEYS_FLAGS_FIXED_COLUMN | MORE_KEYS_FLAGS_FIXED_ORDER); 140 private static final int MORE_KEYS_FLAGS_HAS_LABELS = 0x40000000; 141 private static final int MORE_KEYS_FLAGS_NEEDS_DIVIDERS = 0x20000000; 142 private static final int MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY = 0x10000000; 143 // TODO: Rename these specifiers to !autoOrder! and !fixedOrder! respectively. 144 private static final String MORE_KEYS_AUTO_COLUMN_ORDER = "!autoColumnOrder!"; 145 private static final String MORE_KEYS_FIXED_COLUMN_ORDER = "!fixedColumnOrder!"; 146 private static final String MORE_KEYS_HAS_LABELS = "!hasLabels!"; 147 private static final String MORE_KEYS_NEEDS_DIVIDERS = "!needsDividers!"; 148 private static final String MORE_KEYS_NO_PANEL_AUTO_MORE_KEY = "!noPanelAutoMoreKey!"; 149 150 /** Background type that represents different key background visual than normal one. */ 151 private final int mBackgroundType; 152 public static final int BACKGROUND_TYPE_EMPTY = 0; 153 public static final int BACKGROUND_TYPE_NORMAL = 1; 154 public static final int BACKGROUND_TYPE_FUNCTIONAL = 2; 155 public static final int BACKGROUND_TYPE_STICKY_OFF = 3; 156 public static final int BACKGROUND_TYPE_STICKY_ON = 4; 157 public static final int BACKGROUND_TYPE_ACTION = 5; 158 public static final int BACKGROUND_TYPE_SPACEBAR = 6; 159 160 private final int mActionFlags; 161 private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01; 162 private static final int ACTION_FLAGS_NO_KEY_PREVIEW = 0x02; 163 private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04; 164 private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08; 165 166 @Nullable 167 private final KeyVisualAttributes mKeyVisualAttributes; 168 @Nullable 169 private final OptionalAttributes mOptionalAttributes; 170 171 private static final class OptionalAttributes { 172 /** Text to output when pressed. This can be multiple characters, like ".com" */ 173 public final String mOutputText; 174 public final int mAltCode; 175 /** Icon for disabled state */ 176 public final int mDisabledIconId; 177 /** The visual insets */ 178 public final int mVisualInsetsLeft; 179 public final int mVisualInsetsRight; 180 OptionalAttributes(final String outputText, final int altCode, final int disabledIconId, final int visualInsetsLeft, final int visualInsetsRight)181 private OptionalAttributes(final String outputText, final int altCode, 182 final int disabledIconId, final int visualInsetsLeft, final int visualInsetsRight) { 183 mOutputText = outputText; 184 mAltCode = altCode; 185 mDisabledIconId = disabledIconId; 186 mVisualInsetsLeft = visualInsetsLeft; 187 mVisualInsetsRight = visualInsetsRight; 188 } 189 190 @Nullable newInstance(final String outputText, final int altCode, final int disabledIconId, final int visualInsetsLeft, final int visualInsetsRight)191 public static OptionalAttributes newInstance(final String outputText, final int altCode, 192 final int disabledIconId, final int visualInsetsLeft, final int visualInsetsRight) { 193 if (outputText == null && altCode == CODE_UNSPECIFIED 194 && disabledIconId == ICON_UNDEFINED && visualInsetsLeft == 0 195 && visualInsetsRight == 0) { 196 return null; 197 } 198 return new OptionalAttributes(outputText, altCode, disabledIconId, visualInsetsLeft, 199 visualInsetsRight); 200 } 201 } 202 203 private final int mHashCode; 204 205 /** The current pressed state of this key */ 206 private boolean mPressed; 207 /** Key is enabled and responds on press */ 208 private boolean mEnabled = true; 209 210 /** 211 * Constructor for a key on <code>MoreKeyKeyboard</code>, on <code>MoreSuggestions</code>, 212 * and in a <GridRows/>. 213 */ Key(@ullable final String label, final int iconId, final int code, @Nullable final String outputText, @Nullable final String hintLabel, final int labelFlags, final int backgroundType, final int x, final int y, final int width, final int height, final int horizontalGap, final int verticalGap)214 public Key(@Nullable final String label, final int iconId, final int code, 215 @Nullable final String outputText, @Nullable final String hintLabel, 216 final int labelFlags, final int backgroundType, final int x, final int y, 217 final int width, final int height, final int horizontalGap, final int verticalGap) { 218 mWidth = width - horizontalGap; 219 mHeight = height - verticalGap; 220 mHorizontalGap = horizontalGap; 221 mVerticalGap = verticalGap; 222 mHintLabel = hintLabel; 223 mLabelFlags = labelFlags; 224 mBackgroundType = backgroundType; 225 // TODO: Pass keyActionFlags as an argument. 226 mActionFlags = ACTION_FLAGS_NO_KEY_PREVIEW; 227 mMoreKeys = null; 228 mMoreKeysColumnAndFlags = 0; 229 mLabel = label; 230 mOptionalAttributes = OptionalAttributes.newInstance(outputText, CODE_UNSPECIFIED, 231 ICON_UNDEFINED, 0 /* visualInsetsLeft */, 0 /* visualInsetsRight */); 232 mCode = code; 233 mEnabled = (code != CODE_UNSPECIFIED); 234 mIconId = iconId; 235 // Horizontal gap is divided equally to both sides of the key. 236 mX = x + mHorizontalGap / 2; 237 mY = y; 238 mHitBox.set(x, y, x + width + 1, y + height); 239 mKeyVisualAttributes = null; 240 241 mHashCode = computeHashCode(this); 242 } 243 244 /** 245 * Create a key with the given top-left coordinate and extract its attributes from a key 246 * specification string, Key attribute array, key style, and etc. 247 * 248 * @param keySpec the key specification. 249 * @param keyAttr the Key XML attributes array. 250 * @param style the {@link KeyStyle} of this key. 251 * @param params the keyboard building parameters. 252 * @param row the row that this key belongs to. row's x-coordinate will be the right edge of 253 * this key. 254 */ Key(@ullable final String keySpec, @Nonnull final TypedArray keyAttr, @Nonnull final KeyStyle style, @Nonnull final KeyboardParams params, @Nonnull final KeyboardRow row)255 public Key(@Nullable final String keySpec, @Nonnull final TypedArray keyAttr, 256 @Nonnull final KeyStyle style, @Nonnull final KeyboardParams params, 257 @Nonnull final KeyboardRow row) { 258 mHorizontalGap = isSpacer() ? 0 : params.mHorizontalGap; 259 mVerticalGap = params.mVerticalGap; 260 261 final float horizontalGapFloat = mHorizontalGap; 262 final int rowHeight = row.getRowHeight(); 263 mHeight = rowHeight - mVerticalGap; 264 265 final float keyXPos = row.getKeyX(keyAttr); 266 final float keyWidth = row.getKeyWidth(keyAttr, keyXPos); 267 final int keyYPos = row.getKeyY(); 268 269 // Horizontal gap is divided equally to both sides of the key. 270 mX = Math.round(keyXPos + horizontalGapFloat / 2); 271 mY = keyYPos; 272 mWidth = Math.round(keyWidth - horizontalGapFloat); 273 mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1, 274 keyYPos + rowHeight); 275 // Update row to have current x coordinate. 276 row.setXPos(keyXPos + keyWidth); 277 278 mBackgroundType = style.getInt(keyAttr, 279 R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType()); 280 281 final int baseWidth = params.mBaseWidth; 282 final int visualInsetsLeft = Math.round(keyAttr.getFraction( 283 R.styleable.Keyboard_Key_visualInsetsLeft, baseWidth, baseWidth, 0)); 284 final int visualInsetsRight = Math.round(keyAttr.getFraction( 285 R.styleable.Keyboard_Key_visualInsetsRight, baseWidth, baseWidth, 0)); 286 287 mLabelFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags) 288 | row.getDefaultKeyLabelFlags(); 289 final boolean needsToUpcase = needsToUpcase(mLabelFlags, params.mId.mElementId); 290 final Locale localeForUpcasing = params.mId.getLocale(); 291 int actionFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags); 292 String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys); 293 294 // Get maximum column order number and set a relevant mode value. 295 int moreKeysColumnAndFlags = MORE_KEYS_MODE_MAX_COLUMN_WITH_AUTO_ORDER 296 | style.getInt(keyAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn, 297 params.mMaxMoreKeysKeyboardColumn); 298 int value; 299 if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) { 300 // Override with fixed column order number and set a relevant mode value. 301 moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_AUTO_ORDER 302 | (value & MORE_KEYS_COLUMN_NUMBER_MASK); 303 } 304 if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) { 305 // Override with fixed column order number and set a relevant mode value. 306 moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER 307 | (value & MORE_KEYS_COLUMN_NUMBER_MASK); 308 } 309 if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) { 310 moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_HAS_LABELS; 311 } 312 if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) { 313 moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS; 314 } 315 if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) { 316 moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY; 317 } 318 mMoreKeysColumnAndFlags = moreKeysColumnAndFlags; 319 320 final String[] additionalMoreKeys; 321 if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) { 322 additionalMoreKeys = null; 323 } else { 324 additionalMoreKeys = style.getStringArray(keyAttr, 325 R.styleable.Keyboard_Key_additionalMoreKeys); 326 } 327 moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys); 328 if (moreKeys != null) { 329 actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS; 330 mMoreKeys = new MoreKeySpec[moreKeys.length]; 331 for (int i = 0; i < moreKeys.length; i++) { 332 mMoreKeys[i] = new MoreKeySpec(moreKeys[i], needsToUpcase, localeForUpcasing); 333 } 334 } else { 335 mMoreKeys = null; 336 } 337 mActionFlags = actionFlags; 338 339 mIconId = KeySpecParser.getIconId(keySpec); 340 final int disabledIconId = KeySpecParser.getIconId(style.getString(keyAttr, 341 R.styleable.Keyboard_Key_keyIconDisabled)); 342 343 final int code = KeySpecParser.getCode(keySpec); 344 if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) { 345 mLabel = params.mId.mCustomActionLabel; 346 } else if (code >= Character.MIN_SUPPLEMENTARY_CODE_POINT) { 347 // This is a workaround to have a key that has a supplementary code point in its label. 348 // Because we can put a string in resource neither as a XML entity of a supplementary 349 // code point nor as a surrogate pair. 350 mLabel = new StringBuilder().appendCodePoint(code).toString(); 351 } else { 352 final String label = KeySpecParser.getLabel(keySpec); 353 mLabel = needsToUpcase 354 ? StringUtils.toTitleCaseOfKeyLabel(label, localeForUpcasing) 355 : label; 356 } 357 if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) { 358 mHintLabel = null; 359 } else { 360 final String hintLabel = style.getString( 361 keyAttr, R.styleable.Keyboard_Key_keyHintLabel); 362 mHintLabel = needsToUpcase 363 ? StringUtils.toTitleCaseOfKeyLabel(hintLabel, localeForUpcasing) 364 : hintLabel; 365 } 366 String outputText = KeySpecParser.getOutputText(keySpec); 367 if (needsToUpcase) { 368 outputText = StringUtils.toTitleCaseOfKeyLabel(outputText, localeForUpcasing); 369 } 370 // Choose the first letter of the label as primary code if not specified. 371 if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText) 372 && !TextUtils.isEmpty(mLabel)) { 373 if (StringUtils.codePointCount(mLabel) == 1) { 374 // Use the first letter of the hint label if shiftedLetterActivated flag is 375 // specified. 376 if (hasShiftedLetterHint() && isShiftedLetterActivated()) { 377 mCode = mHintLabel.codePointAt(0); 378 } else { 379 mCode = mLabel.codePointAt(0); 380 } 381 } else { 382 // In some locale and case, the character might be represented by multiple code 383 // points, such as upper case Eszett of German alphabet. 384 outputText = mLabel; 385 mCode = CODE_OUTPUT_TEXT; 386 } 387 } else if (code == CODE_UNSPECIFIED && outputText != null) { 388 if (StringUtils.codePointCount(outputText) == 1) { 389 mCode = outputText.codePointAt(0); 390 outputText = null; 391 } else { 392 mCode = CODE_OUTPUT_TEXT; 393 } 394 } else { 395 mCode = needsToUpcase ? StringUtils.toTitleCaseOfKeyCode(code, localeForUpcasing) 396 : code; 397 } 398 final int altCodeInAttr = KeySpecParser.parseCode( 399 style.getString(keyAttr, R.styleable.Keyboard_Key_altCode), CODE_UNSPECIFIED); 400 final int altCode = needsToUpcase 401 ? StringUtils.toTitleCaseOfKeyCode(altCodeInAttr, localeForUpcasing) 402 : altCodeInAttr; 403 mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode, 404 disabledIconId, visualInsetsLeft, visualInsetsRight); 405 mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr); 406 mHashCode = computeHashCode(this); 407 } 408 409 /** 410 * Copy constructor for DynamicGridKeyboard.GridKey. 411 * 412 * @param key the original key. 413 */ Key(@onnull final Key key)414 protected Key(@Nonnull final Key key) { 415 this(key, key.mMoreKeys); 416 } 417 Key(@onnull final Key key, @Nullable final MoreKeySpec[] moreKeys)418 private Key(@Nonnull final Key key, @Nullable final MoreKeySpec[] moreKeys) { 419 // Final attributes. 420 mCode = key.mCode; 421 mLabel = key.mLabel; 422 mHintLabel = key.mHintLabel; 423 mLabelFlags = key.mLabelFlags; 424 mIconId = key.mIconId; 425 mWidth = key.mWidth; 426 mHeight = key.mHeight; 427 mHorizontalGap = key.mHorizontalGap; 428 mVerticalGap = key.mVerticalGap; 429 mX = key.mX; 430 mY = key.mY; 431 mHitBox.set(key.mHitBox); 432 mMoreKeys = moreKeys; 433 mMoreKeysColumnAndFlags = key.mMoreKeysColumnAndFlags; 434 mBackgroundType = key.mBackgroundType; 435 mActionFlags = key.mActionFlags; 436 mKeyVisualAttributes = key.mKeyVisualAttributes; 437 mOptionalAttributes = key.mOptionalAttributes; 438 mHashCode = key.mHashCode; 439 // Key state. 440 mPressed = key.mPressed; 441 mEnabled = key.mEnabled; 442 } 443 444 @Nonnull removeRedundantMoreKeys(@onnull final Key key, @Nonnull final MoreKeySpec.LettersOnBaseLayout lettersOnBaseLayout)445 public static Key removeRedundantMoreKeys(@Nonnull final Key key, 446 @Nonnull final MoreKeySpec.LettersOnBaseLayout lettersOnBaseLayout) { 447 final MoreKeySpec[] moreKeys = key.getMoreKeys(); 448 final MoreKeySpec[] filteredMoreKeys = MoreKeySpec.removeRedundantMoreKeys( 449 moreKeys, lettersOnBaseLayout); 450 return (filteredMoreKeys == moreKeys) ? key : new Key(key, filteredMoreKeys); 451 } 452 needsToUpcase(final int labelFlags, final int keyboardElementId)453 private static boolean needsToUpcase(final int labelFlags, final int keyboardElementId) { 454 if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false; 455 switch (keyboardElementId) { 456 case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: 457 case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: 458 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: 459 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: 460 return true; 461 default: 462 return false; 463 } 464 } 465 computeHashCode(final Key key)466 private static int computeHashCode(final Key key) { 467 return Arrays.hashCode(new Object[] { 468 key.mX, 469 key.mY, 470 key.mWidth, 471 key.mHeight, 472 key.mCode, 473 key.mLabel, 474 key.mHintLabel, 475 key.mIconId, 476 key.mBackgroundType, 477 Arrays.hashCode(key.mMoreKeys), 478 key.getOutputText(), 479 key.mActionFlags, 480 key.mLabelFlags, 481 // Key can be distinguishable without the following members. 482 // key.mOptionalAttributes.mAltCode, 483 // key.mOptionalAttributes.mDisabledIconId, 484 // key.mOptionalAttributes.mPreviewIconId, 485 // key.mHorizontalGap, 486 // key.mVerticalGap, 487 // key.mOptionalAttributes.mVisualInsetLeft, 488 // key.mOptionalAttributes.mVisualInsetRight, 489 // key.mMaxMoreKeysColumn, 490 }); 491 } 492 equalsInternal(final Key o)493 private boolean equalsInternal(final Key o) { 494 if (this == o) return true; 495 return o.mX == mX 496 && o.mY == mY 497 && o.mWidth == mWidth 498 && o.mHeight == mHeight 499 && o.mCode == mCode 500 && TextUtils.equals(o.mLabel, mLabel) 501 && TextUtils.equals(o.mHintLabel, mHintLabel) 502 && o.mIconId == mIconId 503 && o.mBackgroundType == mBackgroundType 504 && Arrays.equals(o.mMoreKeys, mMoreKeys) 505 && TextUtils.equals(o.getOutputText(), getOutputText()) 506 && o.mActionFlags == mActionFlags 507 && o.mLabelFlags == mLabelFlags; 508 } 509 510 @Override compareTo(Key o)511 public int compareTo(Key o) { 512 if (equalsInternal(o)) return 0; 513 if (mHashCode > o.mHashCode) return 1; 514 return -1; 515 } 516 517 @Override hashCode()518 public int hashCode() { 519 return mHashCode; 520 } 521 522 @Override equals(final Object o)523 public boolean equals(final Object o) { 524 return o instanceof Key && equalsInternal((Key)o); 525 } 526 527 @Override toString()528 public String toString() { 529 return toShortString() + " " + getX() + "," + getY() + " " + getWidth() + "x" + getHeight(); 530 } 531 toShortString()532 public String toShortString() { 533 final int code = getCode(); 534 if (code == Constants.CODE_OUTPUT_TEXT) { 535 return getOutputText(); 536 } 537 return Constants.printableCode(code); 538 } 539 toLongString()540 public String toLongString() { 541 final int iconId = getIconId(); 542 final String topVisual = (iconId == KeyboardIconsSet.ICON_UNDEFINED) 543 ? KeyboardIconsSet.PREFIX_ICON + KeyboardIconsSet.getIconName(iconId) : getLabel(); 544 final String hintLabel = getHintLabel(); 545 final String visual = (hintLabel == null) ? topVisual : topVisual + "^" + hintLabel; 546 return toString() + " " + visual + "/" + backgroundName(mBackgroundType); 547 } 548 backgroundName(final int backgroundType)549 private static String backgroundName(final int backgroundType) { 550 switch (backgroundType) { 551 case BACKGROUND_TYPE_EMPTY: return "empty"; 552 case BACKGROUND_TYPE_NORMAL: return "normal"; 553 case BACKGROUND_TYPE_FUNCTIONAL: return "functional"; 554 case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff"; 555 case BACKGROUND_TYPE_STICKY_ON: return "stickyOn"; 556 case BACKGROUND_TYPE_ACTION: return "action"; 557 case BACKGROUND_TYPE_SPACEBAR: return "spacebar"; 558 default: return null; 559 } 560 } 561 getCode()562 public int getCode() { 563 return mCode; 564 } 565 566 @Nullable getLabel()567 public String getLabel() { 568 return mLabel; 569 } 570 571 @Nullable getHintLabel()572 public String getHintLabel() { 573 return mHintLabel; 574 } 575 576 @Nullable getMoreKeys()577 public MoreKeySpec[] getMoreKeys() { 578 return mMoreKeys; 579 } 580 markAsLeftEdge(final KeyboardParams params)581 public void markAsLeftEdge(final KeyboardParams params) { 582 mHitBox.left = params.mLeftPadding; 583 } 584 markAsRightEdge(final KeyboardParams params)585 public void markAsRightEdge(final KeyboardParams params) { 586 mHitBox.right = params.mOccupiedWidth - params.mRightPadding; 587 } 588 markAsTopEdge(final KeyboardParams params)589 public void markAsTopEdge(final KeyboardParams params) { 590 mHitBox.top = params.mTopPadding; 591 } 592 markAsBottomEdge(final KeyboardParams params)593 public void markAsBottomEdge(final KeyboardParams params) { 594 mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding; 595 } 596 isSpacer()597 public final boolean isSpacer() { 598 return this instanceof Spacer; 599 } 600 isActionKey()601 public final boolean isActionKey() { 602 return mBackgroundType == BACKGROUND_TYPE_ACTION; 603 } 604 isShift()605 public final boolean isShift() { 606 return mCode == CODE_SHIFT; 607 } 608 isModifier()609 public final boolean isModifier() { 610 return mCode == CODE_SHIFT || mCode == CODE_SWITCH_ALPHA_SYMBOL; 611 } 612 isRepeatable()613 public final boolean isRepeatable() { 614 return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0; 615 } 616 noKeyPreview()617 public final boolean noKeyPreview() { 618 return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0; 619 } 620 altCodeWhileTyping()621 public final boolean altCodeWhileTyping() { 622 return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0; 623 } 624 isLongPressEnabled()625 public final boolean isLongPressEnabled() { 626 // We need not start long press timer on the key which has activated shifted letter. 627 return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0 628 && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0; 629 } 630 getVisualAttributes()631 public KeyVisualAttributes getVisualAttributes() { 632 return mKeyVisualAttributes; 633 } 634 635 @Nonnull selectTypeface(final KeyDrawParams params)636 public final Typeface selectTypeface(final KeyDrawParams params) { 637 switch (mLabelFlags & LABEL_FLAGS_FONT_MASK) { 638 case LABEL_FLAGS_FONT_NORMAL: 639 return Typeface.DEFAULT; 640 case LABEL_FLAGS_FONT_MONO_SPACE: 641 return Typeface.MONOSPACE; 642 case LABEL_FLAGS_FONT_DEFAULT: 643 default: 644 // The type-face is specified by keyTypeface attribute. 645 return params.mTypeface; 646 } 647 } 648 selectTextSize(final KeyDrawParams params)649 public final int selectTextSize(final KeyDrawParams params) { 650 switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) { 651 case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO: 652 return params.mLetterSize; 653 case LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO: 654 return params.mLargeLetterSize; 655 case LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO: 656 return params.mLabelSize; 657 case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO: 658 return params.mHintLabelSize; 659 default: // No follow key ratio flag specified. 660 return StringUtils.codePointCount(mLabel) == 1 ? params.mLetterSize : params.mLabelSize; 661 } 662 } 663 selectTextColor(final KeyDrawParams params)664 public final int selectTextColor(final KeyDrawParams params) { 665 if ((mLabelFlags & LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR) != 0) { 666 return params.mFunctionalTextColor; 667 } 668 return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor; 669 } 670 selectHintTextSize(final KeyDrawParams params)671 public final int selectHintTextSize(final KeyDrawParams params) { 672 if (hasHintLabel()) { 673 return params.mHintLabelSize; 674 } 675 if (hasShiftedLetterHint()) { 676 return params.mShiftedLetterHintSize; 677 } 678 return params.mHintLetterSize; 679 } 680 selectHintTextColor(final KeyDrawParams params)681 public final int selectHintTextColor(final KeyDrawParams params) { 682 if (hasHintLabel()) { 683 return params.mHintLabelColor; 684 } 685 if (hasShiftedLetterHint()) { 686 return isShiftedLetterActivated() ? params.mShiftedLetterHintActivatedColor 687 : params.mShiftedLetterHintInactivatedColor; 688 } 689 return params.mHintLetterColor; 690 } 691 selectMoreKeyTextSize(final KeyDrawParams params)692 public final int selectMoreKeyTextSize(final KeyDrawParams params) { 693 return hasLabelsInMoreKeys() ? params.mLabelSize : params.mLetterSize; 694 } 695 getPreviewLabel()696 public final String getPreviewLabel() { 697 return isShiftedLetterActivated() ? mHintLabel : mLabel; 698 } 699 previewHasLetterSize()700 private boolean previewHasLetterSize() { 701 return (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO) != 0 702 || StringUtils.codePointCount(getPreviewLabel()) == 1; 703 } 704 selectPreviewTextSize(final KeyDrawParams params)705 public final int selectPreviewTextSize(final KeyDrawParams params) { 706 if (previewHasLetterSize()) { 707 return params.mPreviewTextSize; 708 } 709 return params.mLetterSize; 710 } 711 712 @Nonnull selectPreviewTypeface(final KeyDrawParams params)713 public Typeface selectPreviewTypeface(final KeyDrawParams params) { 714 if (previewHasLetterSize()) { 715 return selectTypeface(params); 716 } 717 return Typeface.DEFAULT_BOLD; 718 } 719 isAlignHintLabelToBottom(final int defaultFlags)720 public final boolean isAlignHintLabelToBottom(final int defaultFlags) { 721 return ((mLabelFlags | defaultFlags) & LABEL_FLAGS_ALIGN_HINT_LABEL_TO_BOTTOM) != 0; 722 } 723 isAlignIconToBottom()724 public final boolean isAlignIconToBottom() { 725 return (mLabelFlags & LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM) != 0; 726 } 727 isAlignLabelOffCenter()728 public final boolean isAlignLabelOffCenter() { 729 return (mLabelFlags & LABEL_FLAGS_ALIGN_LABEL_OFF_CENTER) != 0; 730 } 731 hasPopupHint()732 public final boolean hasPopupHint() { 733 return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0; 734 } 735 hasShiftedLetterHint()736 public final boolean hasShiftedLetterHint() { 737 return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0 738 && !TextUtils.isEmpty(mHintLabel); 739 } 740 hasHintLabel()741 public final boolean hasHintLabel() { 742 return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0; 743 } 744 needsAutoXScale()745 public final boolean needsAutoXScale() { 746 return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0; 747 } 748 needsAutoScale()749 public final boolean needsAutoScale() { 750 return (mLabelFlags & LABEL_FLAGS_AUTO_SCALE) == LABEL_FLAGS_AUTO_SCALE; 751 } 752 needsToKeepBackgroundAspectRatio(final int defaultFlags)753 public final boolean needsToKeepBackgroundAspectRatio(final int defaultFlags) { 754 return ((mLabelFlags | defaultFlags) & LABEL_FLAGS_KEEP_BACKGROUND_ASPECT_RATIO) != 0; 755 } 756 hasCustomActionLabel()757 public final boolean hasCustomActionLabel() { 758 return (mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0; 759 } 760 isShiftedLetterActivated()761 private final boolean isShiftedLetterActivated() { 762 return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0 763 && !TextUtils.isEmpty(mHintLabel); 764 } 765 getMoreKeysColumnNumber()766 public final int getMoreKeysColumnNumber() { 767 return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_NUMBER_MASK; 768 } 769 isMoreKeysFixedColumn()770 public final boolean isMoreKeysFixedColumn() { 771 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN) != 0; 772 } 773 isMoreKeysFixedOrder()774 public final boolean isMoreKeysFixedOrder() { 775 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_ORDER) != 0; 776 } 777 hasLabelsInMoreKeys()778 public final boolean hasLabelsInMoreKeys() { 779 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0; 780 } 781 getMoreKeyLabelFlags()782 public final int getMoreKeyLabelFlags() { 783 final int labelSizeFlag = hasLabelsInMoreKeys() 784 ? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO 785 : LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO; 786 return labelSizeFlag | LABEL_FLAGS_AUTO_X_SCALE; 787 } 788 needsDividersInMoreKeys()789 public final boolean needsDividersInMoreKeys() { 790 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0; 791 } 792 hasNoPanelAutoMoreKey()793 public final boolean hasNoPanelAutoMoreKey() { 794 return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY) != 0; 795 } 796 797 @Nullable getOutputText()798 public final String getOutputText() { 799 final OptionalAttributes attrs = mOptionalAttributes; 800 return (attrs != null) ? attrs.mOutputText : null; 801 } 802 getAltCode()803 public final int getAltCode() { 804 final OptionalAttributes attrs = mOptionalAttributes; 805 return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED; 806 } 807 getIconId()808 public int getIconId() { 809 return mIconId; 810 } 811 812 @Nullable getIcon(final KeyboardIconsSet iconSet, final int alpha)813 public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { 814 final OptionalAttributes attrs = mOptionalAttributes; 815 final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED; 816 final int iconId = mEnabled ? getIconId() : disabledIconId; 817 final Drawable icon = iconSet.getIconDrawable(iconId); 818 if (icon != null) { 819 icon.setAlpha(alpha); 820 } 821 return icon; 822 } 823 824 @Nullable getPreviewIcon(final KeyboardIconsSet iconSet)825 public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) { 826 return iconSet.getIconDrawable(getIconId()); 827 } 828 829 /** 830 * Gets the width of the key in pixels, excluding the gap. 831 * @return The width of the key in pixels, excluding the gap. 832 */ getWidth()833 public int getWidth() { 834 return mWidth; 835 } 836 837 /** 838 * Gets the height of the key in pixels, excluding the gap. 839 * @return The height of the key in pixels, excluding the gap. 840 */ getHeight()841 public int getHeight() { 842 return mHeight; 843 } 844 845 /** 846 * The combined width in pixels of the horizontal gaps belonging to this key, both above and 847 * below. I.e., getWidth() + getHorizontalGap() = total width belonging to the key. 848 * @return Horizontal gap belonging to this key. 849 */ getHorizontalGap()850 public int getHorizontalGap() { 851 return mHorizontalGap; 852 } 853 854 /** 855 * The combined height in pixels of the vertical gaps belonging to this key, both above and 856 * below. I.e., getHeight() + getVerticalGap() = total height belonging to the key. 857 * @return Vertical gap belonging to this key. 858 */ getVerticalGap()859 public int getVerticalGap() { 860 return mVerticalGap; 861 } 862 863 /** 864 * Gets the x-coordinate of the top-left corner of the key in pixels, excluding the gap. 865 * @return The x-coordinate of the top-left corner of the key in pixels, excluding the gap. 866 */ getX()867 public int getX() { 868 return mX; 869 } 870 871 /** 872 * Gets the y-coordinate of the top-left corner of the key in pixels, excluding the gap. 873 * @return The y-coordinate of the top-left corner of the key in pixels, excluding the gap. 874 */ getY()875 public int getY() { 876 return mY; 877 } 878 getDrawX()879 public final int getDrawX() { 880 final int x = getX(); 881 final OptionalAttributes attrs = mOptionalAttributes; 882 return (attrs == null) ? x : x + attrs.mVisualInsetsLeft; 883 } 884 getDrawWidth()885 public final int getDrawWidth() { 886 final OptionalAttributes attrs = mOptionalAttributes; 887 return (attrs == null) ? mWidth 888 : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight; 889 } 890 891 /** 892 * Informs the key that it has been pressed, in case it needs to change its appearance or 893 * state. 894 * @see #onReleased() 895 */ onPressed()896 public void onPressed() { 897 mPressed = true; 898 } 899 900 /** 901 * Informs the key that it has been released, in case it needs to change its appearance or 902 * state. 903 * @see #onPressed() 904 */ onReleased()905 public void onReleased() { 906 mPressed = false; 907 } 908 isEnabled()909 public final boolean isEnabled() { 910 return mEnabled; 911 } 912 setEnabled(final boolean enabled)913 public void setEnabled(final boolean enabled) { 914 mEnabled = enabled; 915 } 916 917 @Nonnull getHitBox()918 public Rect getHitBox() { 919 return mHitBox; 920 } 921 922 /** 923 * Detects if a point falls on this key. 924 * @param x the x-coordinate of the point 925 * @param y the y-coordinate of the point 926 * @return whether or not the point falls on the key. If the key is attached to an edge, it 927 * will assume that all points between the key and the edge are considered to be on the key. 928 * @see #markAsLeftEdge(KeyboardParams) etc. 929 */ isOnKey(final int x, final int y)930 public boolean isOnKey(final int x, final int y) { 931 return mHitBox.contains(x, y); 932 } 933 934 /** 935 * Returns the square of the distance to the nearest edge of the key and the given point. 936 * @param x the x-coordinate of the point 937 * @param y the y-coordinate of the point 938 * @return the square of the distance of the point from the nearest edge of the key 939 */ squaredDistanceToEdge(final int x, final int y)940 public int squaredDistanceToEdge(final int x, final int y) { 941 final int left = getX(); 942 final int right = left + mWidth; 943 final int top = getY(); 944 final int bottom = top + mHeight; 945 final int edgeX = x < left ? left : (x > right ? right : x); 946 final int edgeY = y < top ? top : (y > bottom ? bottom : y); 947 final int dx = x - edgeX; 948 final int dy = y - edgeY; 949 return dx * dx + dy * dy; 950 } 951 952 static class KeyBackgroundState { 953 private final int[] mReleasedState; 954 private final int[] mPressedState; 955 KeyBackgroundState(final int ... attrs)956 private KeyBackgroundState(final int ... attrs) { 957 mReleasedState = attrs; 958 mPressedState = Arrays.copyOf(attrs, attrs.length + 1); 959 mPressedState[attrs.length] = android.R.attr.state_pressed; 960 } 961 getState(final boolean pressed)962 public int[] getState(final boolean pressed) { 963 return pressed ? mPressedState : mReleasedState; 964 } 965 966 public static final KeyBackgroundState[] STATES = { 967 // 0: BACKGROUND_TYPE_EMPTY 968 new KeyBackgroundState(android.R.attr.state_empty), 969 // 1: BACKGROUND_TYPE_NORMAL 970 new KeyBackgroundState(), 971 // 2: BACKGROUND_TYPE_FUNCTIONAL 972 new KeyBackgroundState(), 973 // 3: BACKGROUND_TYPE_STICKY_OFF 974 new KeyBackgroundState(android.R.attr.state_checkable), 975 // 4: BACKGROUND_TYPE_STICKY_ON 976 new KeyBackgroundState(android.R.attr.state_checkable, android.R.attr.state_checked), 977 // 5: BACKGROUND_TYPE_ACTION 978 new KeyBackgroundState(android.R.attr.state_active), 979 // 6: BACKGROUND_TYPE_SPACEBAR 980 new KeyBackgroundState(), 981 }; 982 } 983 984 /** 985 * Returns the background drawable for the key, based on the current state and type of the key. 986 * @return the background drawable of the key. 987 * @see android.graphics.drawable.StateListDrawable#setState(int[]) 988 */ 989 @Nonnull selectBackgroundDrawable(@onnull final Drawable keyBackground, @Nonnull final Drawable functionalKeyBackground, @Nonnull final Drawable spacebarBackground)990 public final Drawable selectBackgroundDrawable(@Nonnull final Drawable keyBackground, 991 @Nonnull final Drawable functionalKeyBackground, 992 @Nonnull final Drawable spacebarBackground) { 993 final Drawable background; 994 if (mBackgroundType == BACKGROUND_TYPE_FUNCTIONAL) { 995 background = functionalKeyBackground; 996 } else if (mBackgroundType == BACKGROUND_TYPE_SPACEBAR) { 997 background = spacebarBackground; 998 } else { 999 background = keyBackground; 1000 } 1001 final int[] state = KeyBackgroundState.STATES[mBackgroundType].getState(mPressed); 1002 background.setState(state); 1003 return background; 1004 } 1005 1006 public static class Spacer extends Key { Spacer(final TypedArray keyAttr, final KeyStyle keyStyle, final KeyboardParams params, final KeyboardRow row)1007 public Spacer(final TypedArray keyAttr, final KeyStyle keyStyle, 1008 final KeyboardParams params, final KeyboardRow row) { 1009 super(null /* keySpec */, keyAttr, keyStyle, params, row); 1010 } 1011 1012 /** 1013 * This constructor is being used only for divider in more keys keyboard. 1014 */ Spacer(final KeyboardParams params, final int x, final int y, final int width, final int height)1015 protected Spacer(final KeyboardParams params, final int x, final int y, final int width, 1016 final int height) { 1017 super(null /* label */, ICON_UNDEFINED, CODE_UNSPECIFIED, null /* outputText */, 1018 null /* hintLabel */, 0 /* labelFlags */, BACKGROUND_TYPE_EMPTY, x, y, width, 1019 height, params.mHorizontalGap, params.mVerticalGap); 1020 } 1021 } 1022 } 1023