• 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"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.android.inputmethod.keyboard;
18 
19 import static com.android.inputmethod.keyboard.Keyboard.CODE_OUTPUT_TEXT;
20 import static com.android.inputmethod.keyboard.Keyboard.CODE_SHIFT;
21 import static com.android.inputmethod.keyboard.Keyboard.CODE_SWITCH_ALPHA_SYMBOL;
22 import static com.android.inputmethod.keyboard.Keyboard.CODE_UNSPECIFIED;
23 import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED;
24 
25 import android.content.res.Resources;
26 import android.content.res.TypedArray;
27 import android.graphics.Rect;
28 import android.graphics.Typeface;
29 import android.graphics.drawable.Drawable;
30 import android.text.TextUtils;
31 import android.util.Log;
32 import android.util.Xml;
33 
34 import com.android.inputmethod.keyboard.internal.KeySpecParser;
35 import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec;
36 import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle;
37 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
38 import com.android.inputmethod.latin.R;
39 import com.android.inputmethod.latin.StringUtils;
40 
41 import org.xmlpull.v1.XmlPullParser;
42 import org.xmlpull.v1.XmlPullParserException;
43 
44 import java.util.Arrays;
45 import java.util.Locale;
46 
47 /**
48  * Class for describing the position and characteristics of a single key in the keyboard.
49  */
50 public class Key {
51     private static final String TAG = Key.class.getSimpleName();
52 
53     /**
54      * The key code (unicode or custom code) that this key generates.
55      */
56     public final int mCode;
57     public final int mAltCode;
58 
59     /** Label to display */
60     public final String mLabel;
61     /** Hint label to display on the key in conjunction with the label */
62     public final String mHintLabel;
63     /** Flags of the label */
64     private final int mLabelFlags;
65     private static final int LABEL_FLAGS_ALIGN_LEFT = 0x01;
66     private static final int LABEL_FLAGS_ALIGN_RIGHT = 0x02;
67     private static final int LABEL_FLAGS_ALIGN_LEFT_OF_CENTER = 0x08;
68     private static final int LABEL_FLAGS_FONT_NORMAL = 0x10;
69     private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x20;
70     // Start of key text ratio enum values
71     private static final int LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK = 0x1C0;
72     private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO = 0x40;
73     private static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80;
74     private static final int LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO = 0xC0;
75     private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO = 0x100;
76     private static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x140;
77     // End of key text ratio mask enum values
78     private static final int LABEL_FLAGS_HAS_POPUP_HINT = 0x200;
79     private static final int LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT = 0x400;
80     private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800;
81     private static final int LABEL_FLAGS_WITH_ICON_LEFT = 0x1000;
82     private static final int LABEL_FLAGS_WITH_ICON_RIGHT = 0x2000;
83     private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000;
84     private static final int LABEL_FLAGS_PRESERVE_CASE = 0x8000;
85     private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x10000;
86     private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x20000;
87     private static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000;
88     private static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000;
89 
90     /** Icon to display instead of a label. Icon takes precedence over a label */
91     private final int mIconId;
92     /** Icon for disabled state */
93     private final int mDisabledIconId;
94     /** Preview version of the icon, for the preview popup */
95     private final int mPreviewIconId;
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     /** The horizontal gap around this key */
102     public final int mHorizontalGap;
103     /** The vertical gap below this key */
104     public final int mVerticalGap;
105     /** The visual insets */
106     public final int mVisualInsetsLeft;
107     public final int mVisualInsetsRight;
108     /** X coordinate of the key in the keyboard layout */
109     public final int mX;
110     /** Y coordinate of the key in the keyboard layout */
111     public final int mY;
112     /** Hit bounding box of the key */
113     public final Rect mHitBox = new Rect();
114 
115     /** Text to output when pressed. This can be multiple characters, like ".com" */
116     public final CharSequence mOutputText;
117     /** More keys */
118     public final MoreKeySpec[] mMoreKeys;
119     /** More keys column number and flags */
120     private final int mMoreKeysColumnAndFlags;
121     private static final int MORE_KEYS_COLUMN_MASK = 0x000000ff;
122     private static final int MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER = 0x80000000;
123     private static final int MORE_KEYS_FLAGS_HAS_LABELS = 0x40000000;
124     private static final int MORE_KEYS_FLAGS_NEEDS_DIVIDERS = 0x20000000;
125     private static final int MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY = 0x10000000;
126     private static final String MORE_KEYS_AUTO_COLUMN_ORDER = "!autoColumnOrder!";
127     private static final String MORE_KEYS_FIXED_COLUMN_ORDER = "!fixedColumnOrder!";
128     private static final String MORE_KEYS_HAS_LABELS = "!hasLabels!";
129     private static final String MORE_KEYS_NEEDS_DIVIDERS = "!needsDividers!";
130     private static final String MORE_KEYS_EMBEDDED_MORE_KEY = "!embeddedMoreKey!";
131 
132     /** Background type that represents different key background visual than normal one. */
133     public final int mBackgroundType;
134     public static final int BACKGROUND_TYPE_NORMAL = 0;
135     public static final int BACKGROUND_TYPE_FUNCTIONAL = 1;
136     public static final int BACKGROUND_TYPE_ACTION = 2;
137     public static final int BACKGROUND_TYPE_STICKY_OFF = 3;
138     public static final int BACKGROUND_TYPE_STICKY_ON = 4;
139 
140     private final int mActionFlags;
141     private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01;
142     private static final int ACTION_FLAGS_NO_KEY_PREVIEW = 0x02;
143     private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04;
144     private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08;
145 
146     private final int mHashCode;
147 
148     /** The current pressed state of this key */
149     private boolean mPressed;
150     /** Key is enabled and responds on press */
151     private boolean mEnabled = true;
152 
153     /**
154      * This constructor is being used only for keys in more keys keyboard.
155      */
Key(Keyboard.Params params, MoreKeySpec moreKeySpec, int x, int y, int width, int height, int labelFlags)156     public Key(Keyboard.Params params, MoreKeySpec moreKeySpec, int x, int y, int width, int height,
157             int labelFlags) {
158         this(params, moreKeySpec.mLabel, null, moreKeySpec.mIconId, moreKeySpec.mCode,
159                 moreKeySpec.mOutputText, x, y, width, height, labelFlags);
160     }
161 
162     /**
163      * This constructor is being used only for key in popup suggestions pane.
164      */
Key(Keyboard.Params params, String label, String hintLabel, int iconId, int code, String outputText, int x, int y, int width, int height, int labelFlags)165     public Key(Keyboard.Params params, String label, String hintLabel, int iconId,
166             int code, String outputText, int x, int y, int width, int height, int labelFlags) {
167         mHeight = height - params.mVerticalGap;
168         mHorizontalGap = params.mHorizontalGap;
169         mVerticalGap = params.mVerticalGap;
170         mVisualInsetsLeft = mVisualInsetsRight = 0;
171         mWidth = width - mHorizontalGap;
172         mHintLabel = hintLabel;
173         mLabelFlags = labelFlags;
174         mBackgroundType = BACKGROUND_TYPE_NORMAL;
175         mActionFlags = 0;
176         mMoreKeys = null;
177         mMoreKeysColumnAndFlags = 0;
178         mLabel = label;
179         mOutputText = outputText;
180         mCode = code;
181         mEnabled = (code != CODE_UNSPECIFIED);
182         mAltCode = CODE_UNSPECIFIED;
183         mIconId = iconId;
184         mDisabledIconId = ICON_UNDEFINED;
185         mPreviewIconId = ICON_UNDEFINED;
186         // Horizontal gap is divided equally to both sides of the key.
187         mX = x + mHorizontalGap / 2;
188         mY = y;
189         mHitBox.set(x, y, x + width + 1, y + height);
190 
191         mHashCode = computeHashCode(this);
192     }
193 
194     /**
195      * Create a key with the given top-left coordinate and extract its attributes from the XML
196      * parser.
197      * @param res resources associated with the caller's context
198      * @param params the keyboard building parameters.
199      * @param row the row that this key belongs to. row's x-coordinate will be the right edge of
200      *        this key.
201      * @param parser the XML parser containing the attributes for this key
202      * @throws XmlPullParserException
203      */
Key(Resources res, Keyboard.Params params, Keyboard.Builder.Row row, XmlPullParser parser)204     public Key(Resources res, Keyboard.Params params, Keyboard.Builder.Row row,
205             XmlPullParser parser) throws XmlPullParserException {
206         final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
207         final int keyHeight = row.mRowHeight;
208         mVerticalGap = params.mVerticalGap;
209         mHeight = keyHeight - mVerticalGap;
210 
211         final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
212                 R.styleable.Keyboard_Key);
213 
214         final KeyStyle style = params.mKeyStyles.getKeyStyle(keyAttr, parser);
215         final float keyXPos = row.getKeyX(keyAttr);
216         final float keyWidth = row.getKeyWidth(keyAttr, keyXPos);
217         final int keyYPos = row.getKeyY();
218 
219         // Horizontal gap is divided equally to both sides of the key.
220         mX = Math.round(keyXPos + horizontalGap / 2);
221         mY = keyYPos;
222         mWidth = Math.round(keyWidth - horizontalGap);
223         mHorizontalGap = Math.round(horizontalGap);
224         mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1,
225                 keyYPos + keyHeight);
226         // Update row to have current x coordinate.
227         row.setXPos(keyXPos + keyWidth);
228 
229         mBackgroundType = style.getInt(keyAttr,
230                 R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType());
231 
232         mVisualInsetsLeft = Math.round(Keyboard.Builder.getDimensionOrFraction(keyAttr,
233                 R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0));
234         mVisualInsetsRight = Math.round(Keyboard.Builder.getDimensionOrFraction(keyAttr,
235                 R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0));
236         mIconId = KeySpecParser.getIconId(style.getString(keyAttr,
237                 R.styleable.Keyboard_Key_keyIcon));
238         mDisabledIconId = KeySpecParser.getIconId(style.getString(keyAttr,
239                 R.styleable.Keyboard_Key_keyIconDisabled));
240         mPreviewIconId = KeySpecParser.getIconId(style.getString(keyAttr,
241                 R.styleable.Keyboard_Key_keyIconPreview));
242 
243         mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags)
244                 | row.getDefaultKeyLabelFlags();
245         final boolean needsToUpperCase = needsToUpperCase(mLabelFlags, params.mId.mElementId);
246         final Locale locale = params.mId.mLocale;
247         int actionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
248         String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
249 
250         int moreKeysColumn = style.getInt(keyAttr,
251                 R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMoreKeysKeyboardColumn);
252         int value;
253         if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
254             moreKeysColumn = value & MORE_KEYS_COLUMN_MASK;
255         }
256         if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
257             moreKeysColumn = MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER | (value & MORE_KEYS_COLUMN_MASK);
258         }
259         if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
260             moreKeysColumn |= MORE_KEYS_FLAGS_HAS_LABELS;
261         }
262         if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
263             moreKeysColumn |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS;
264         }
265         if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_EMBEDDED_MORE_KEY)) {
266             moreKeysColumn |= MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY;
267         }
268         mMoreKeysColumnAndFlags = moreKeysColumn;
269 
270         final String[] additionalMoreKeys;
271         if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) {
272             additionalMoreKeys = null;
273         } else {
274             additionalMoreKeys = style.getStringArray(keyAttr,
275                     R.styleable.Keyboard_Key_additionalMoreKeys);
276         }
277         moreKeys = KeySpecParser.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys);
278         if (moreKeys != null) {
279             actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS;
280             mMoreKeys = new MoreKeySpec[moreKeys.length];
281             for (int i = 0; i < moreKeys.length; i++) {
282                 mMoreKeys[i] = new MoreKeySpec(
283                         moreKeys[i], needsToUpperCase, locale, params.mCodesSet);
284             }
285         } else {
286             mMoreKeys = null;
287         }
288         mActionFlags = actionFlags;
289 
290         if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) {
291             mLabel = params.mId.mCustomActionLabel;
292         } else {
293             mLabel = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr,
294                     R.styleable.Keyboard_Key_keyLabel), needsToUpperCase, locale);
295         }
296         if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) {
297             mHintLabel = null;
298         } else {
299             mHintLabel = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr,
300                     R.styleable.Keyboard_Key_keyHintLabel), needsToUpperCase, locale);
301         }
302         String outputText = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr,
303                 R.styleable.Keyboard_Key_keyOutputText), needsToUpperCase, locale);
304         final int code = KeySpecParser.parseCode(style.getString(keyAttr,
305                 R.styleable.Keyboard_Key_code), params.mCodesSet, CODE_UNSPECIFIED);
306         // Choose the first letter of the label as primary code if not specified.
307         if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText)
308                 && !TextUtils.isEmpty(mLabel)) {
309             if (StringUtils.codePointCount(mLabel) == 1) {
310                 // Use the first letter of the hint label if shiftedLetterActivated flag is
311                 // specified.
312                 if (hasShiftedLetterHint() && isShiftedLetterActivated()
313                         && !TextUtils.isEmpty(mHintLabel)) {
314                     mCode = mHintLabel.codePointAt(0);
315                 } else {
316                     mCode = mLabel.codePointAt(0);
317                 }
318             } else {
319                 // In some locale and case, the character might be represented by multiple code
320                 // points, such as upper case Eszett of German alphabet.
321                 outputText = mLabel;
322                 mCode = CODE_OUTPUT_TEXT;
323             }
324         } else if (code == CODE_UNSPECIFIED && outputText != null) {
325             if (StringUtils.codePointCount(outputText) == 1) {
326                 mCode = outputText.codePointAt(0);
327                 outputText = null;
328             } else {
329                 mCode = CODE_OUTPUT_TEXT;
330             }
331         } else {
332             mCode = KeySpecParser.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale);
333         }
334         mOutputText = outputText;
335         mAltCode = KeySpecParser.toUpperCaseOfCodeForLocale(
336                 KeySpecParser.parseCode(style.getString(keyAttr,
337                 R.styleable.Keyboard_Key_altCode), params.mCodesSet, CODE_UNSPECIFIED),
338                 needsToUpperCase, locale);
339         mHashCode = computeHashCode(this);
340 
341         keyAttr.recycle();
342 
343         if (hasShiftedLetterHint() && TextUtils.isEmpty(mHintLabel)) {
344             Log.w(TAG, "hasShiftedLetterHint specified without keyHintLabel: " + this);
345         }
346     }
347 
needsToUpperCase(int labelFlags, int keyboardElementId)348     private static boolean needsToUpperCase(int labelFlags, int keyboardElementId) {
349         if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false;
350         switch (keyboardElementId) {
351         case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
352         case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
353         case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
354         case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
355             return true;
356         default:
357             return false;
358         }
359     }
360 
computeHashCode(Key key)361     private static int computeHashCode(Key key) {
362         return Arrays.hashCode(new Object[] {
363                 key.mX,
364                 key.mY,
365                 key.mWidth,
366                 key.mHeight,
367                 key.mCode,
368                 key.mLabel,
369                 key.mHintLabel,
370                 key.mIconId,
371                 key.mBackgroundType,
372                 Arrays.hashCode(key.mMoreKeys),
373                 key.mOutputText,
374                 key.mActionFlags,
375                 key.mLabelFlags,
376                 // Key can be distinguishable without the following members.
377                 // key.mAltCode,
378                 // key.mDisabledIconId,
379                 // key.mPreviewIconId,
380                 // key.mHorizontalGap,
381                 // key.mVerticalGap,
382                 // key.mVisualInsetLeft,
383                 // key.mVisualInsetRight,
384                 // key.mMaxMoreKeysColumn,
385         });
386     }
387 
equals(Key o)388     private boolean equals(Key o) {
389         if (this == o) return true;
390         return o.mX == mX
391                 && o.mY == mY
392                 && o.mWidth == mWidth
393                 && o.mHeight == mHeight
394                 && o.mCode == mCode
395                 && TextUtils.equals(o.mLabel, mLabel)
396                 && TextUtils.equals(o.mHintLabel, mHintLabel)
397                 && o.mIconId == mIconId
398                 && o.mBackgroundType == mBackgroundType
399                 && Arrays.equals(o.mMoreKeys, mMoreKeys)
400                 && TextUtils.equals(o.mOutputText, mOutputText)
401                 && o.mActionFlags == mActionFlags
402                 && o.mLabelFlags == mLabelFlags;
403     }
404 
405     @Override
hashCode()406     public int hashCode() {
407         return mHashCode;
408     }
409 
410     @Override
equals(Object o)411     public boolean equals(Object o) {
412         return o instanceof Key && equals((Key)o);
413     }
414 
415     @Override
toString()416     public String toString() {
417         return String.format("%s/%s %d,%d %dx%d %s/%s/%s",
418                 Keyboard.printableCode(mCode), mLabel, mX, mY, mWidth, mHeight, mHintLabel,
419                 KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType));
420     }
421 
backgroundName(int backgroundType)422     private static String backgroundName(int backgroundType) {
423         switch (backgroundType) {
424         case BACKGROUND_TYPE_NORMAL: return "normal";
425         case BACKGROUND_TYPE_FUNCTIONAL: return "functional";
426         case BACKGROUND_TYPE_ACTION: return "action";
427         case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff";
428         case BACKGROUND_TYPE_STICKY_ON: return "stickyOn";
429         default: return null;
430         }
431     }
432 
markAsLeftEdge(Keyboard.Params params)433     public void markAsLeftEdge(Keyboard.Params params) {
434         mHitBox.left = params.mHorizontalEdgesPadding;
435     }
436 
markAsRightEdge(Keyboard.Params params)437     public void markAsRightEdge(Keyboard.Params params) {
438         mHitBox.right = params.mOccupiedWidth - params.mHorizontalEdgesPadding;
439     }
440 
markAsTopEdge(Keyboard.Params params)441     public void markAsTopEdge(Keyboard.Params params) {
442         mHitBox.top = params.mTopPadding;
443     }
444 
markAsBottomEdge(Keyboard.Params params)445     public void markAsBottomEdge(Keyboard.Params params) {
446         mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding;
447     }
448 
isSpacer()449     public final boolean isSpacer() {
450         return this instanceof Spacer;
451     }
452 
isShift()453     public boolean isShift() {
454         return mCode == CODE_SHIFT;
455     }
456 
isModifier()457     public boolean isModifier() {
458         return mCode == CODE_SHIFT || mCode == CODE_SWITCH_ALPHA_SYMBOL;
459     }
460 
isRepeatable()461     public boolean isRepeatable() {
462         return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0;
463     }
464 
noKeyPreview()465     public boolean noKeyPreview() {
466         return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0;
467     }
468 
altCodeWhileTyping()469     public boolean altCodeWhileTyping() {
470         return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0;
471     }
472 
isLongPressEnabled()473     public boolean isLongPressEnabled() {
474         // We need not start long press timer on the key which has activated shifted letter.
475         return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0
476                 && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0;
477     }
478 
selectTypeface(Typeface defaultTypeface)479     public Typeface selectTypeface(Typeface defaultTypeface) {
480         // TODO: Handle "bold" here too?
481         if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) {
482             return Typeface.DEFAULT;
483         } else if ((mLabelFlags & LABEL_FLAGS_FONT_MONO_SPACE) != 0) {
484             return Typeface.MONOSPACE;
485         } else {
486             return defaultTypeface;
487         }
488     }
489 
selectTextSize(int letterSize, int largeLetterSize, int labelSize, int largeLabelSize, int hintLabelSize)490     public int selectTextSize(int letterSize, int largeLetterSize, int labelSize,
491             int largeLabelSize, int hintLabelSize) {
492         switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) {
493         case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO:
494             return letterSize;
495         case LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO:
496             return largeLetterSize;
497         case LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO:
498             return labelSize;
499         case LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO:
500             return largeLabelSize;
501         case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO:
502             return hintLabelSize;
503         default: // No follow key ratio flag specified.
504             return StringUtils.codePointCount(mLabel) == 1 ? letterSize : labelSize;
505         }
506     }
507 
isAlignLeft()508     public boolean isAlignLeft() {
509         return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0;
510     }
511 
isAlignRight()512     public boolean isAlignRight() {
513         return (mLabelFlags & LABEL_FLAGS_ALIGN_RIGHT) != 0;
514     }
515 
isAlignLeftOfCenter()516     public boolean isAlignLeftOfCenter() {
517         return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0;
518     }
519 
hasPopupHint()520     public boolean hasPopupHint() {
521         return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0;
522     }
523 
hasShiftedLetterHint()524     public boolean hasShiftedLetterHint() {
525         return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0;
526     }
527 
hasHintLabel()528     public boolean hasHintLabel() {
529         return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0;
530     }
531 
hasLabelWithIconLeft()532     public boolean hasLabelWithIconLeft() {
533         return (mLabelFlags & LABEL_FLAGS_WITH_ICON_LEFT) != 0;
534     }
535 
hasLabelWithIconRight()536     public boolean hasLabelWithIconRight() {
537         return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0;
538     }
539 
needsXScale()540     public boolean needsXScale() {
541         return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0;
542     }
543 
isShiftedLetterActivated()544     public boolean isShiftedLetterActivated() {
545         return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0;
546     }
547 
getMoreKeysColumn()548     public int getMoreKeysColumn() {
549         return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_MASK;
550     }
551 
isFixedColumnOrderMoreKeys()552     public boolean isFixedColumnOrderMoreKeys() {
553         return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER) != 0;
554     }
555 
hasLabelsInMoreKeys()556     public boolean hasLabelsInMoreKeys() {
557         return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0;
558     }
559 
getMoreKeyLabelFlags()560     public int getMoreKeyLabelFlags() {
561         return hasLabelsInMoreKeys()
562                 ? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO
563                 : LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO;
564     }
565 
needsDividersInMoreKeys()566     public boolean needsDividersInMoreKeys() {
567         return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0;
568     }
569 
hasEmbeddedMoreKey()570     public boolean hasEmbeddedMoreKey() {
571         return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY) != 0;
572     }
573 
getIcon(KeyboardIconsSet iconSet, int alpha)574     public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
575         final int iconId = mEnabled ? mIconId : mDisabledIconId;
576         final Drawable icon = iconSet.getIconDrawable(iconId);
577         if (icon != null) {
578             icon.setAlpha(alpha);
579         }
580         return icon;
581     }
582 
getPreviewIcon(KeyboardIconsSet iconSet)583     public Drawable getPreviewIcon(KeyboardIconsSet iconSet) {
584         return mPreviewIconId != ICON_UNDEFINED
585                 ? iconSet.getIconDrawable(mPreviewIconId)
586                 : iconSet.getIconDrawable(mIconId);
587     }
588 
589     /**
590      * Informs the key that it has been pressed, in case it needs to change its appearance or
591      * state.
592      * @see #onReleased()
593      */
onPressed()594     public void onPressed() {
595         mPressed = true;
596     }
597 
598     /**
599      * Informs the key that it has been released, in case it needs to change its appearance or
600      * state.
601      * @see #onPressed()
602      */
onReleased()603     public void onReleased() {
604         mPressed = false;
605     }
606 
isEnabled()607     public boolean isEnabled() {
608         return mEnabled;
609     }
610 
setEnabled(boolean enabled)611     public void setEnabled(boolean enabled) {
612         mEnabled = enabled;
613     }
614 
615     /**
616      * Detects if a point falls on this key.
617      * @param x the x-coordinate of the point
618      * @param y the y-coordinate of the point
619      * @return whether or not the point falls on the key. If the key is attached to an edge, it
620      * will assume that all points between the key and the edge are considered to be on the key.
621      * @see #markAsLeftEdge(Keyboard.Params) etc.
622      */
isOnKey(int x, int y)623     public boolean isOnKey(int x, int y) {
624         return mHitBox.contains(x, y);
625     }
626 
627     /**
628      * Returns the square of the distance to the nearest edge of the key and the given point.
629      * @param x the x-coordinate of the point
630      * @param y the y-coordinate of the point
631      * @return the square of the distance of the point from the nearest edge of the key
632      */
squaredDistanceToEdge(int x, int y)633     public int squaredDistanceToEdge(int x, int y) {
634         final int left = mX;
635         final int right = left + mWidth;
636         final int top = mY;
637         final int bottom = top + mHeight;
638         final int edgeX = x < left ? left : (x > right ? right : x);
639         final int edgeY = y < top ? top : (y > bottom ? bottom : y);
640         final int dx = x - edgeX;
641         final int dy = y - edgeY;
642         return dx * dx + dy * dy;
643     }
644 
645     private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_ON = {
646         android.R.attr.state_checkable,
647         android.R.attr.state_checked
648     };
649 
650     private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_ON = {
651         android.R.attr.state_pressed,
652         android.R.attr.state_checkable,
653         android.R.attr.state_checked
654     };
655 
656     private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_OFF = {
657         android.R.attr.state_checkable
658     };
659 
660     private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_OFF = {
661         android.R.attr.state_pressed,
662         android.R.attr.state_checkable
663     };
664 
665     private final static int[] KEY_STATE_NORMAL = {
666     };
667 
668     private final static int[] KEY_STATE_PRESSED = {
669         android.R.attr.state_pressed
670     };
671 
672     // functional normal state (with properties)
673     private static final int[] KEY_STATE_FUNCTIONAL_NORMAL = {
674             android.R.attr.state_single
675     };
676 
677     // functional pressed state (with properties)
678     private static final int[] KEY_STATE_FUNCTIONAL_PRESSED = {
679             android.R.attr.state_single,
680             android.R.attr.state_pressed
681     };
682 
683     // action normal state (with properties)
684     private static final int[] KEY_STATE_ACTIVE_NORMAL = {
685             android.R.attr.state_active
686     };
687 
688     // action pressed state (with properties)
689     private static final int[] KEY_STATE_ACTIVE_PRESSED = {
690             android.R.attr.state_active,
691             android.R.attr.state_pressed
692     };
693 
694     /**
695      * Returns the drawable state for the key, based on the current state and type of the key.
696      * @return the drawable state of the key.
697      * @see android.graphics.drawable.StateListDrawable#setState(int[])
698      */
getCurrentDrawableState()699     public int[] getCurrentDrawableState() {
700         switch (mBackgroundType) {
701         case BACKGROUND_TYPE_FUNCTIONAL:
702             return mPressed ? KEY_STATE_FUNCTIONAL_PRESSED : KEY_STATE_FUNCTIONAL_NORMAL;
703         case BACKGROUND_TYPE_ACTION:
704             return mPressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL;
705         case BACKGROUND_TYPE_STICKY_OFF:
706             return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF;
707         case BACKGROUND_TYPE_STICKY_ON:
708             return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON;
709         default: /* BACKGROUND_TYPE_NORMAL */
710             return mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL;
711         }
712     }
713 
714     public static class Spacer extends Key {
Spacer(Resources res, Keyboard.Params params, Keyboard.Builder.Row row, XmlPullParser parser)715         public Spacer(Resources res, Keyboard.Params params, Keyboard.Builder.Row row,
716                 XmlPullParser parser) throws XmlPullParserException {
717             super(res, params, row, parser);
718         }
719 
720         /**
721          * This constructor is being used only for divider in more keys keyboard.
722          */
Spacer(Keyboard.Params params, int x, int y, int width, int height)723         protected Spacer(Keyboard.Params params, int x, int y, int width, int height) {
724             super(params, null, null, ICON_UNDEFINED, CODE_UNSPECIFIED,
725                     null, x, y, width, height, 0);
726         }
727     }
728 }
729