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