Key.java revision 7ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4
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.StringUtils;
40
41import org.xmlpull.v1.XmlPullParser;
42import org.xmlpull.v1.XmlPullParserException;
43
44import java.util.Arrays;
45import java.util.Locale;
46
47/**
48 * Class for describing the position and characteristics of a single key in the keyboard.
49 */
50public 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     */
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     */
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     */
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 = (int) (keyXPos + horizontalGap / 2);
221        mY = keyYPos;
222        mWidth = (int) (keyWidth - horizontalGap);
223        mHorizontalGap = (int) horizontalGap;
224        mHitBox.set((int)keyXPos, keyYPos, (int)(keyXPos + keyWidth) + 1, keyYPos + keyHeight);
225        // Update row to have current x coordinate.
226        row.setXPos(keyXPos + keyWidth);
227
228        mBackgroundType = style.getInt(keyAttr,
229                R.styleable.Keyboard_Key_backgroundType, BACKGROUND_TYPE_NORMAL);
230
231        mVisualInsetsLeft = (int) Keyboard.Builder.getDimensionOrFraction(keyAttr,
232                R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0);
233        mVisualInsetsRight = (int) Keyboard.Builder.getDimensionOrFraction(keyAttr,
234                R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0);
235        mIconId = KeySpecParser.getIconId(style.getString(keyAttr,
236                R.styleable.Keyboard_Key_keyIcon));
237        mDisabledIconId = KeySpecParser.getIconId(style.getString(keyAttr,
238                R.styleable.Keyboard_Key_keyIconDisabled));
239        mPreviewIconId = KeySpecParser.getIconId(style.getString(keyAttr,
240                R.styleable.Keyboard_Key_keyIconPreview));
241
242        mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags)
243                | row.getDefaultKeyLabelFlags();
244        final boolean needsToUpperCase = needsToUpperCase(mLabelFlags, params.mId.mElementId);
245        final Locale locale = params.mId.mLocale;
246        int actionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
247        String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
248
249        int moreKeysColumn = style.getInt(keyAttr,
250                R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMoreKeysKeyboardColumn);
251        int value;
252        if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
253            moreKeysColumn = value & MORE_KEYS_COLUMN_MASK;
254        }
255        if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
256            moreKeysColumn = MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER | (value & MORE_KEYS_COLUMN_MASK);
257        }
258        if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
259            moreKeysColumn |= MORE_KEYS_FLAGS_HAS_LABELS;
260        }
261        if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
262            moreKeysColumn |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS;
263        }
264        if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_EMBEDDED_MORE_KEY)) {
265            moreKeysColumn |= MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY;
266        }
267        mMoreKeysColumnAndFlags = moreKeysColumn;
268
269        final String[] additionalMoreKeys;
270        if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) {
271            additionalMoreKeys = null;
272        } else {
273            additionalMoreKeys = style.getStringArray(keyAttr,
274                    R.styleable.Keyboard_Key_additionalMoreKeys);
275        }
276        moreKeys = KeySpecParser.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys);
277        if (moreKeys != null) {
278            actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS;
279            mMoreKeys = new MoreKeySpec[moreKeys.length];
280            for (int i = 0; i < moreKeys.length; i++) {
281                mMoreKeys[i] = new MoreKeySpec(
282                        moreKeys[i], needsToUpperCase, locale, params.mCodesSet);
283            }
284        } else {
285            mMoreKeys = null;
286        }
287        mActionFlags = actionFlags;
288
289        if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) {
290            mLabel = params.mId.mCustomActionLabel;
291        } else {
292            mLabel = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr,
293                    R.styleable.Keyboard_Key_keyLabel), needsToUpperCase, locale);
294        }
295        if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) {
296            mHintLabel = null;
297        } else {
298            mHintLabel = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr,
299                    R.styleable.Keyboard_Key_keyHintLabel), needsToUpperCase, locale);
300        }
301        String outputText = KeySpecParser.toUpperCaseOfStringForLocale(style.getString(keyAttr,
302                R.styleable.Keyboard_Key_keyOutputText), needsToUpperCase, locale);
303        final int code = KeySpecParser.parseCode(style.getString(keyAttr,
304                R.styleable.Keyboard_Key_code), params.mCodesSet, CODE_UNSPECIFIED);
305        // Choose the first letter of the label as primary code if not specified.
306        if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText)
307                && !TextUtils.isEmpty(mLabel)) {
308            if (StringUtils.codePointCount(mLabel) == 1) {
309                // Use the first letter of the hint label if shiftedLetterActivated flag is
310                // specified.
311                if (hasShiftedLetterHint() && isShiftedLetterActivated()
312                        && !TextUtils.isEmpty(mHintLabel)) {
313                    mCode = mHintLabel.codePointAt(0);
314                } else {
315                    mCode = mLabel.codePointAt(0);
316                }
317            } else {
318                // In some locale and case, the character might be represented by multiple code
319                // points, such as upper case Eszett of German alphabet.
320                outputText = mLabel;
321                mCode = CODE_OUTPUT_TEXT;
322            }
323        } else if (code == CODE_UNSPECIFIED && outputText != null) {
324            if (StringUtils.codePointCount(outputText) == 1) {
325                mCode = outputText.codePointAt(0);
326                outputText = null;
327            } else {
328                mCode = CODE_OUTPUT_TEXT;
329            }
330        } else {
331            mCode = KeySpecParser.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale);
332        }
333        mOutputText = outputText;
334        mAltCode = KeySpecParser.toUpperCaseOfCodeForLocale(
335                KeySpecParser.parseCode(style.getString(keyAttr,
336                R.styleable.Keyboard_Key_altCode), params.mCodesSet, CODE_UNSPECIFIED),
337                needsToUpperCase, locale);
338        mHashCode = computeHashCode(this);
339
340        keyAttr.recycle();
341
342        if (hasShiftedLetterHint() && TextUtils.isEmpty(mHintLabel)) {
343            Log.w(TAG, "hasShiftedLetterHint specified without keyHintLabel: " + this);
344        }
345    }
346
347    private static boolean needsToUpperCase(int labelFlags, int keyboardElementId) {
348        if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false;
349        switch (keyboardElementId) {
350        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
351        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
352        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
353        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
354            return true;
355        default:
356            return false;
357        }
358    }
359
360    private static int computeHashCode(Key key) {
361        return Arrays.hashCode(new Object[] {
362                key.mX,
363                key.mY,
364                key.mWidth,
365                key.mHeight,
366                key.mCode,
367                key.mLabel,
368                key.mHintLabel,
369                key.mIconId,
370                key.mBackgroundType,
371                Arrays.hashCode(key.mMoreKeys),
372                key.mOutputText,
373                key.mActionFlags,
374                key.mLabelFlags,
375                // Key can be distinguishable without the following members.
376                // key.mAltCode,
377                // key.mDisabledIconId,
378                // key.mPreviewIconId,
379                // key.mHorizontalGap,
380                // key.mVerticalGap,
381                // key.mVisualInsetLeft,
382                // key.mVisualInsetRight,
383                // key.mMaxMoreKeysColumn,
384        });
385    }
386
387    private boolean equals(Key o) {
388        if (this == o) return true;
389        return o.mX == mX
390                && o.mY == mY
391                && o.mWidth == mWidth
392                && o.mHeight == mHeight
393                && o.mCode == mCode
394                && TextUtils.equals(o.mLabel, mLabel)
395                && TextUtils.equals(o.mHintLabel, mHintLabel)
396                && o.mIconId == mIconId
397                && o.mBackgroundType == mBackgroundType
398                && Arrays.equals(o.mMoreKeys, mMoreKeys)
399                && TextUtils.equals(o.mOutputText, mOutputText)
400                && o.mActionFlags == mActionFlags
401                && o.mLabelFlags == mLabelFlags;
402    }
403
404    @Override
405    public int hashCode() {
406        return mHashCode;
407    }
408
409    @Override
410    public boolean equals(Object o) {
411        return o instanceof Key && equals((Key)o);
412    }
413
414    @Override
415    public String toString() {
416        return String.format("%s/%s %d,%d %dx%d %s/%s/%s",
417                Keyboard.printableCode(mCode), mLabel, mX, mY, mWidth, mHeight, mHintLabel,
418                KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType));
419    }
420
421    private static String backgroundName(int backgroundType) {
422        switch (backgroundType) {
423        case BACKGROUND_TYPE_NORMAL: return "normal";
424        case BACKGROUND_TYPE_FUNCTIONAL: return "functional";
425        case BACKGROUND_TYPE_ACTION: return "action";
426        case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff";
427        case BACKGROUND_TYPE_STICKY_ON: return "stickyOn";
428        default: return null;
429        }
430    }
431
432    public void markAsLeftEdge(Keyboard.Params params) {
433        mHitBox.left = params.mHorizontalEdgesPadding;
434    }
435
436    public void markAsRightEdge(Keyboard.Params params) {
437        mHitBox.right = params.mOccupiedWidth - params.mHorizontalEdgesPadding;
438    }
439
440    public void markAsTopEdge(Keyboard.Params params) {
441        mHitBox.top = params.mTopPadding;
442    }
443
444    public void markAsBottomEdge(Keyboard.Params params) {
445        mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding;
446    }
447
448    public final boolean isSpacer() {
449        return this instanceof Spacer;
450    }
451
452    public boolean isShift() {
453        return mCode == CODE_SHIFT;
454    }
455
456    public boolean isModifier() {
457        return mCode == CODE_SHIFT || mCode == CODE_SWITCH_ALPHA_SYMBOL;
458    }
459
460    public boolean isRepeatable() {
461        return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0;
462    }
463
464    public boolean noKeyPreview() {
465        return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0;
466    }
467
468    public boolean altCodeWhileTyping() {
469        return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0;
470    }
471
472    public boolean isLongPressEnabled() {
473        // We need not start long press timer on the key which has activated shifted letter.
474        return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0
475                && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0;
476    }
477
478    public Typeface selectTypeface(Typeface defaultTypeface) {
479        // TODO: Handle "bold" here too?
480        if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) {
481            return Typeface.DEFAULT;
482        } else if ((mLabelFlags & LABEL_FLAGS_FONT_MONO_SPACE) != 0) {
483            return Typeface.MONOSPACE;
484        } else {
485            return defaultTypeface;
486        }
487    }
488
489    public int selectTextSize(int letterSize, int largeLetterSize, int labelSize,
490            int largeLabelSize, int hintLabelSize) {
491        switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) {
492        case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO:
493            return letterSize;
494        case LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO:
495            return largeLetterSize;
496        case LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO:
497            return labelSize;
498        case LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO:
499            return largeLabelSize;
500        case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO:
501            return hintLabelSize;
502        default: // No follow key ratio flag specified.
503            return StringUtils.codePointCount(mLabel) == 1 ? letterSize : labelSize;
504        }
505    }
506
507    public boolean isAlignLeft() {
508        return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0;
509    }
510
511    public boolean isAlignRight() {
512        return (mLabelFlags & LABEL_FLAGS_ALIGN_RIGHT) != 0;
513    }
514
515    public boolean isAlignLeftOfCenter() {
516        return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0;
517    }
518
519    public boolean hasPopupHint() {
520        return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0;
521    }
522
523    public boolean hasShiftedLetterHint() {
524        return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0;
525    }
526
527    public boolean hasHintLabel() {
528        return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0;
529    }
530
531    public boolean hasLabelWithIconLeft() {
532        return (mLabelFlags & LABEL_FLAGS_WITH_ICON_LEFT) != 0;
533    }
534
535    public boolean hasLabelWithIconRight() {
536        return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0;
537    }
538
539    public boolean needsXScale() {
540        return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0;
541    }
542
543    public boolean isShiftedLetterActivated() {
544        return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0;
545    }
546
547    public int getMoreKeysColumn() {
548        return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_MASK;
549    }
550
551    public boolean isFixedColumnOrderMoreKeys() {
552        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER) != 0;
553    }
554
555    public boolean hasLabelsInMoreKeys() {
556        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0;
557    }
558
559    public int getMoreKeyLabelFlags() {
560        return hasLabelsInMoreKeys()
561                ? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO
562                : LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO;
563    }
564
565    public boolean needsDividersInMoreKeys() {
566        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0;
567    }
568
569    public boolean hasEmbeddedMoreKey() {
570        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY) != 0;
571    }
572
573    public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
574        final int iconId = mEnabled ? mIconId : mDisabledIconId;
575        final Drawable icon = iconSet.getIconDrawable(iconId);
576        if (icon != null) {
577            icon.setAlpha(alpha);
578        }
579        return icon;
580    }
581
582    public Drawable getPreviewIcon(KeyboardIconsSet iconSet) {
583        return mPreviewIconId != ICON_UNDEFINED
584                ? iconSet.getIconDrawable(mPreviewIconId)
585                : iconSet.getIconDrawable(mIconId);
586    }
587
588    /**
589     * Informs the key that it has been pressed, in case it needs to change its appearance or
590     * state.
591     * @see #onReleased()
592     */
593    public void onPressed() {
594        mPressed = true;
595    }
596
597    /**
598     * Informs the key that it has been released, in case it needs to change its appearance or
599     * state.
600     * @see #onPressed()
601     */
602    public void onReleased() {
603        mPressed = false;
604    }
605
606    public boolean isEnabled() {
607        return mEnabled;
608    }
609
610    public void setEnabled(boolean enabled) {
611        mEnabled = enabled;
612    }
613
614    /**
615     * Detects if a point falls on this key.
616     * @param x the x-coordinate of the point
617     * @param y the y-coordinate of the point
618     * @return whether or not the point falls on the key. If the key is attached to an edge, it
619     * will assume that all points between the key and the edge are considered to be on the key.
620     * @see #markAsLeftEdge(Keyboard.Params) etc.
621     */
622    public boolean isOnKey(int x, int y) {
623        return mHitBox.contains(x, y);
624    }
625
626    /**
627     * Returns the square of the distance to the nearest edge of the key and the given point.
628     * @param x the x-coordinate of the point
629     * @param y the y-coordinate of the point
630     * @return the square of the distance of the point from the nearest edge of the key
631     */
632    public int squaredDistanceToEdge(int x, int y) {
633        final int left = mX;
634        final int right = left + mWidth;
635        final int top = mY;
636        final int bottom = top + mHeight;
637        final int edgeX = x < left ? left : (x > right ? right : x);
638        final int edgeY = y < top ? top : (y > bottom ? bottom : y);
639        final int dx = x - edgeX;
640        final int dy = y - edgeY;
641        return dx * dx + dy * dy;
642    }
643
644    private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_ON = {
645        android.R.attr.state_checkable,
646        android.R.attr.state_checked
647    };
648
649    private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_ON = {
650        android.R.attr.state_pressed,
651        android.R.attr.state_checkable,
652        android.R.attr.state_checked
653    };
654
655    private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_OFF = {
656        android.R.attr.state_checkable
657    };
658
659    private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_OFF = {
660        android.R.attr.state_pressed,
661        android.R.attr.state_checkable
662    };
663
664    private final static int[] KEY_STATE_NORMAL = {
665    };
666
667    private final static int[] KEY_STATE_PRESSED = {
668        android.R.attr.state_pressed
669    };
670
671    // functional normal state (with properties)
672    private static final int[] KEY_STATE_FUNCTIONAL_NORMAL = {
673            android.R.attr.state_single
674    };
675
676    // functional pressed state (with properties)
677    private static final int[] KEY_STATE_FUNCTIONAL_PRESSED = {
678            android.R.attr.state_single,
679            android.R.attr.state_pressed
680    };
681
682    // action normal state (with properties)
683    private static final int[] KEY_STATE_ACTIVE_NORMAL = {
684            android.R.attr.state_active
685    };
686
687    // action pressed state (with properties)
688    private static final int[] KEY_STATE_ACTIVE_PRESSED = {
689            android.R.attr.state_active,
690            android.R.attr.state_pressed
691    };
692
693    /**
694     * Returns the drawable state for the key, based on the current state and type of the key.
695     * @return the drawable state of the key.
696     * @see android.graphics.drawable.StateListDrawable#setState(int[])
697     */
698    public int[] getCurrentDrawableState() {
699        switch (mBackgroundType) {
700        case BACKGROUND_TYPE_FUNCTIONAL:
701            return mPressed ? KEY_STATE_FUNCTIONAL_PRESSED : KEY_STATE_FUNCTIONAL_NORMAL;
702        case BACKGROUND_TYPE_ACTION:
703            return mPressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL;
704        case BACKGROUND_TYPE_STICKY_OFF:
705            return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF;
706        case BACKGROUND_TYPE_STICKY_ON:
707            return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON;
708        default: /* BACKGROUND_TYPE_NORMAL */
709            return mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL;
710        }
711    }
712
713    public static class Spacer extends Key {
714        public Spacer(Resources res, Keyboard.Params params, Keyboard.Builder.Row row,
715                XmlPullParser parser) throws XmlPullParserException {
716            super(res, params, row, parser);
717        }
718
719        /**
720         * This constructor is being used only for divider in more keys keyboard.
721         */
722        protected Spacer(Keyboard.Params params, int x, int y, int width, int height) {
723            super(params, null, null, ICON_UNDEFINED, CODE_UNSPECIFIED,
724                    null, x, y, width, height, 0);
725        }
726    }
727}
728