• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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