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