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