Key.java revision 6efe9ae1c073bdfbc36bd88e4d71c1b7505b39e9
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.TypedArray;
26import android.graphics.Rect;
27import android.graphics.Typeface;
28import android.graphics.drawable.Drawable;
29import android.text.TextUtils;
30
31import com.android.inputmethod.keyboard.internal.KeyDrawParams;
32import com.android.inputmethod.keyboard.internal.KeySpecParser;
33import com.android.inputmethod.keyboard.internal.KeyStyle;
34import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
35import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
36import com.android.inputmethod.keyboard.internal.KeyboardParams;
37import com.android.inputmethod.keyboard.internal.KeyboardRow;
38import com.android.inputmethod.keyboard.internal.MoreKeySpec;
39import com.android.inputmethod.latin.Constants;
40import com.android.inputmethod.latin.R;
41import com.android.inputmethod.latin.utils.StringUtils;
42
43import java.util.Arrays;
44import java.util.Locale;
45
46/**
47 * Class for describing the position and characteristics of a single key in the keyboard.
48 */
49public class Key implements Comparable<Key> {
50    /**
51     * The key code (unicode or custom code) that this key generates.
52     */
53    private final int mCode;
54
55    /** Label to display */
56    private final String mLabel;
57    /** Hint label to display on the key in conjunction with the label */
58    private final String mHintLabel;
59    /** Flags of the label */
60    private final int mLabelFlags;
61    private static final int LABEL_FLAGS_ALIGN_LEFT = 0x01;
62    private static final int LABEL_FLAGS_ALIGN_RIGHT = 0x02;
63    private static final int LABEL_FLAGS_ALIGN_BUTTOM = 0x04;
64    private static final int LABEL_FLAGS_ALIGN_LEFT_OF_CENTER = 0x08;
65    // Font typeface specification.
66    private static final int LABEL_FLAGS_FONT_MASK = 0x30;
67    private static final int LABEL_FLAGS_FONT_NORMAL = 0x10;
68    private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x20;
69    private static final int LABEL_FLAGS_FONT_DEFAULT = 0x30;
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    // The bit to calculate the ratio of key label width against key width. If autoXScale bit is on
84    // and autoYScale bit is off, the key label may be shrunk only for X-direction.
85    // If both autoXScale and autoYScale bits are on, the key label text size may be auto scaled.
86    private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000;
87    private static final int LABEL_FLAGS_AUTO_Y_SCALE = 0x8000;
88    private static final int LABEL_FLAGS_AUTO_SCALE = LABEL_FLAGS_AUTO_X_SCALE
89            | LABEL_FLAGS_AUTO_Y_SCALE;
90    private static final int LABEL_FLAGS_PRESERVE_CASE = 0x10000;
91    private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x20000;
92    private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x40000;
93    private static final int LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR = 0x80000;
94    private static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000;
95    private static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000;
96
97    /** Icon to display instead of a label. Icon takes precedence over a label */
98    private final int mIconId;
99
100    /** Width of the key, not including the gap */
101    private final int mWidth;
102    /** Height of the key, not including the gap */
103    private final int mHeight;
104    /** X coordinate of the key in the keyboard layout */
105    private final int mX;
106    /** Y coordinate of the key in the keyboard layout */
107    private final int mY;
108    /** Hit bounding box of the key */
109    private final Rect mHitBox = new Rect();
110
111    /** More keys. It is guaranteed that this is null or an array of one or more elements */
112    private final MoreKeySpec[] mMoreKeys;
113    /** More keys column number and flags */
114    private final int mMoreKeysColumnAndFlags;
115    private static final int MORE_KEYS_COLUMN_MASK = 0x000000ff;
116    private static final int MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER = 0x80000000;
117    private static final int MORE_KEYS_FLAGS_HAS_LABELS = 0x40000000;
118    private static final int MORE_KEYS_FLAGS_NEEDS_DIVIDERS = 0x20000000;
119    private static final int MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY = 0x10000000;
120    private static final String MORE_KEYS_AUTO_COLUMN_ORDER = "!autoColumnOrder!";
121    private static final String MORE_KEYS_FIXED_COLUMN_ORDER = "!fixedColumnOrder!";
122    private static final String MORE_KEYS_HAS_LABELS = "!hasLabels!";
123    private static final String MORE_KEYS_NEEDS_DIVIDERS = "!needsDividers!";
124    private static final String MORE_KEYS_NO_PANEL_AUTO_MORE_KEY = "!noPanelAutoMoreKey!";
125
126    /** Background type that represents different key background visual than normal one. */
127    private final int mBackgroundType;
128    public static final int BACKGROUND_TYPE_EMPTY = 0;
129    public static final int BACKGROUND_TYPE_NORMAL = 1;
130    public static final int BACKGROUND_TYPE_FUNCTIONAL = 2;
131    public static final int BACKGROUND_TYPE_ACTION = 3;
132    public static final int BACKGROUND_TYPE_STICKY_OFF = 4;
133    public static final int BACKGROUND_TYPE_STICKY_ON = 5;
134
135    private final int mActionFlags;
136    private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01;
137    private static final int ACTION_FLAGS_NO_KEY_PREVIEW = 0x02;
138    private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04;
139    private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08;
140
141    private final KeyVisualAttributes mKeyVisualAttributes;
142
143    private final OptionalAttributes mOptionalAttributes;
144
145    private static final class OptionalAttributes {
146        /** Text to output when pressed. This can be multiple characters, like ".com" */
147        public final String mOutputText;
148        public final int mAltCode;
149        /** Icon for disabled state */
150        public final int mDisabledIconId;
151        /** The visual insets */
152        public final int mVisualInsetsLeft;
153        public final int mVisualInsetsRight;
154
155        private OptionalAttributes(final String outputText, final int altCode,
156                final int disabledIconId, final int visualInsetsLeft, final int visualInsetsRight) {
157            mOutputText = outputText;
158            mAltCode = altCode;
159            mDisabledIconId = disabledIconId;
160            mVisualInsetsLeft = visualInsetsLeft;
161            mVisualInsetsRight = visualInsetsRight;
162        }
163
164        public static OptionalAttributes newInstance(final String outputText, final int altCode,
165                final int disabledIconId, final int visualInsetsLeft, final int visualInsetsRight) {
166            if (outputText == null && altCode == CODE_UNSPECIFIED
167                    && disabledIconId == ICON_UNDEFINED && visualInsetsLeft == 0
168                    && visualInsetsRight == 0) {
169                return null;
170            }
171            return new OptionalAttributes(outputText, altCode, disabledIconId, visualInsetsLeft,
172                    visualInsetsRight);
173        }
174    }
175
176    private final int mHashCode;
177
178    /** The current pressed state of this key */
179    private boolean mPressed;
180    /** Key is enabled and responds on press */
181    private boolean mEnabled = true;
182
183    /**
184     * Constructor for a key on <code>MoreKeyKeyboard</code>, on <code>MoreSuggestions</code>,
185     * and in a <GridRows/>.
186     */
187    public Key(final String label, final int iconId, final int code, final String outputText,
188            final String hintLabel, final int labelFlags, final int backgroundType, final int x,
189            final int y, final int width, final int height, final int horizontalGap,
190            final int verticalGap) {
191        mHeight = height - verticalGap;
192        mWidth = width - horizontalGap;
193        mHintLabel = hintLabel;
194        mLabelFlags = labelFlags;
195        mBackgroundType = backgroundType;
196        // TODO: Pass keyActionFlags as an argument.
197        mActionFlags = ACTION_FLAGS_NO_KEY_PREVIEW;
198        mMoreKeys = null;
199        mMoreKeysColumnAndFlags = 0;
200        mLabel = label;
201        mOptionalAttributes = OptionalAttributes.newInstance(outputText, CODE_UNSPECIFIED,
202                ICON_UNDEFINED, 0 /* visualInsetsLeft */, 0 /* visualInsetsRight */);
203        mCode = code;
204        mEnabled = (code != CODE_UNSPECIFIED);
205        mIconId = iconId;
206        // Horizontal gap is divided equally to both sides of the key.
207        mX = x + horizontalGap / 2;
208        mY = y;
209        mHitBox.set(x, y, x + width + 1, y + height);
210        mKeyVisualAttributes = null;
211
212        mHashCode = computeHashCode(this);
213    }
214
215    /**
216     * Create a key with the given top-left coordinate and extract its attributes from a key
217     * specification string, Key attribute array, key style, and etc.
218     *
219     * @param keySpec the key specification.
220     * @param keyAttr the Key XML attributes array.
221     * @param style the {@link KeyStyle} of this key.
222     * @param params the keyboard building parameters.
223     * @param row the row that this key belongs to. row's x-coordinate will be the right edge of
224     *        this key.
225     */
226    public Key(final String keySpec, final TypedArray keyAttr, final KeyStyle style,
227            final KeyboardParams params, final KeyboardRow row) {
228        final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
229        final int rowHeight = row.getRowHeight();
230        mHeight = rowHeight - params.mVerticalGap;
231
232        final float keyXPos = row.getKeyX(keyAttr);
233        final float keyWidth = row.getKeyWidth(keyAttr, keyXPos);
234        final int keyYPos = row.getKeyY();
235
236        // Horizontal gap is divided equally to both sides of the key.
237        mX = Math.round(keyXPos + horizontalGap / 2);
238        mY = keyYPos;
239        mWidth = Math.round(keyWidth - horizontalGap);
240        mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1,
241                keyYPos + rowHeight);
242        // Update row to have current x coordinate.
243        row.setXPos(keyXPos + keyWidth);
244
245        mBackgroundType = style.getInt(keyAttr,
246                R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType());
247
248        final int baseWidth = params.mBaseWidth;
249        final int visualInsetsLeft = Math.round(keyAttr.getFraction(
250                R.styleable.Keyboard_Key_visualInsetsLeft, baseWidth, baseWidth, 0));
251        final int visualInsetsRight = Math.round(keyAttr.getFraction(
252                R.styleable.Keyboard_Key_visualInsetsRight, baseWidth, baseWidth, 0));
253
254        mLabelFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags)
255                | row.getDefaultKeyLabelFlags();
256        final boolean needsToUpperCase = needsToUpperCase(mLabelFlags, params.mId.mElementId);
257        final Locale locale = params.mId.mLocale;
258        int actionFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
259        String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
260
261        int moreKeysColumn = style.getInt(keyAttr,
262                R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMoreKeysKeyboardColumn);
263        int value;
264        if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
265            moreKeysColumn = value & MORE_KEYS_COLUMN_MASK;
266        }
267        if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
268            moreKeysColumn = MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER | (value & MORE_KEYS_COLUMN_MASK);
269        }
270        if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
271            moreKeysColumn |= MORE_KEYS_FLAGS_HAS_LABELS;
272        }
273        if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
274            moreKeysColumn |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS;
275        }
276        if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) {
277            moreKeysColumn |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY;
278        }
279        mMoreKeysColumnAndFlags = moreKeysColumn;
280
281        final String[] additionalMoreKeys;
282        if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) {
283            additionalMoreKeys = null;
284        } else {
285            additionalMoreKeys = style.getStringArray(keyAttr,
286                    R.styleable.Keyboard_Key_additionalMoreKeys);
287        }
288        moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys);
289        if (moreKeys != null) {
290            actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS;
291            mMoreKeys = new MoreKeySpec[moreKeys.length];
292            for (int i = 0; i < moreKeys.length; i++) {
293                mMoreKeys[i] = new MoreKeySpec(moreKeys[i], needsToUpperCase, locale);
294            }
295        } else {
296            mMoreKeys = null;
297        }
298        mActionFlags = actionFlags;
299
300        mIconId = KeySpecParser.getIconId(keySpec);
301        final int disabledIconId = KeySpecParser.getIconId(style.getString(keyAttr,
302                R.styleable.Keyboard_Key_keyIconDisabled));
303
304        final int code = KeySpecParser.getCode(keySpec);
305        if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) {
306            mLabel = params.mId.mCustomActionLabel;
307        } else if (code >= Character.MIN_SUPPLEMENTARY_CODE_POINT) {
308            // This is a workaround to have a key that has a supplementary code point in its label.
309            // Because we can put a string in resource neither as a XML entity of a supplementary
310            // code point nor as a surrogate pair.
311            mLabel = new StringBuilder().appendCodePoint(code).toString();
312        } else {
313            mLabel = StringUtils.toUpperCaseOfStringForLocale(
314                    KeySpecParser.getLabel(keySpec), needsToUpperCase, locale);
315        }
316        if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) {
317            mHintLabel = null;
318        } else {
319            mHintLabel = StringUtils.toUpperCaseOfStringForLocale(style.getString(keyAttr,
320                    R.styleable.Keyboard_Key_keyHintLabel), needsToUpperCase, locale);
321        }
322        String outputText = StringUtils.toUpperCaseOfStringForLocale(
323                KeySpecParser.getOutputText(keySpec), needsToUpperCase, locale);
324        // Choose the first letter of the label as primary code if not specified.
325        if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText)
326                && !TextUtils.isEmpty(mLabel)) {
327            if (StringUtils.codePointCount(mLabel) == 1) {
328                // Use the first letter of the hint label if shiftedLetterActivated flag is
329                // specified.
330                if (hasShiftedLetterHint() && isShiftedLetterActivated()) {
331                    mCode = mHintLabel.codePointAt(0);
332                } else {
333                    mCode = mLabel.codePointAt(0);
334                }
335            } else {
336                // In some locale and case, the character might be represented by multiple code
337                // points, such as upper case Eszett of German alphabet.
338                outputText = mLabel;
339                mCode = CODE_OUTPUT_TEXT;
340            }
341        } else if (code == CODE_UNSPECIFIED && outputText != null) {
342            if (StringUtils.codePointCount(outputText) == 1) {
343                mCode = outputText.codePointAt(0);
344                outputText = null;
345            } else {
346                mCode = CODE_OUTPUT_TEXT;
347            }
348        } else {
349            mCode = StringUtils.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale);
350        }
351        final int altCodeInAttr = KeySpecParser.parseCode(
352                style.getString(keyAttr, R.styleable.Keyboard_Key_altCode), CODE_UNSPECIFIED);
353        final int altCode = StringUtils.toUpperCaseOfCodeForLocale(
354                altCodeInAttr, needsToUpperCase, locale);
355        mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode,
356                disabledIconId, visualInsetsLeft, visualInsetsRight);
357        mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
358        mHashCode = computeHashCode(this);
359    }
360
361    /**
362     * Copy constructor for DynamicGridKeyboard.GridKey.
363     *
364     * @param key the original key.
365     */
366    protected Key(final Key key) {
367        // Final attributes.
368        mCode = key.mCode;
369        mLabel = key.mLabel;
370        mHintLabel = key.mHintLabel;
371        mLabelFlags = key.mLabelFlags;
372        mIconId = key.mIconId;
373        mWidth = key.mWidth;
374        mHeight = key.mHeight;
375        mX = key.mX;
376        mY = key.mY;
377        mHitBox.set(key.mHitBox);
378        mMoreKeys = key.mMoreKeys;
379        mMoreKeysColumnAndFlags = key.mMoreKeysColumnAndFlags;
380        mBackgroundType = key.mBackgroundType;
381        mActionFlags = key.mActionFlags;
382        mKeyVisualAttributes = key.mKeyVisualAttributes;
383        mOptionalAttributes = key.mOptionalAttributes;
384        mHashCode = key.mHashCode;
385        // Key state.
386        mPressed = key.mPressed;
387        mEnabled = key.mEnabled;
388    }
389
390    private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) {
391        if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false;
392        switch (keyboardElementId) {
393        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
394        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
395        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
396        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
397            return true;
398        default:
399            return false;
400        }
401    }
402
403    private static int computeHashCode(final Key key) {
404        return Arrays.hashCode(new Object[] {
405                key.mX,
406                key.mY,
407                key.mWidth,
408                key.mHeight,
409                key.mCode,
410                key.mLabel,
411                key.mHintLabel,
412                key.mIconId,
413                key.mBackgroundType,
414                Arrays.hashCode(key.mMoreKeys),
415                key.getOutputText(),
416                key.mActionFlags,
417                key.mLabelFlags,
418                // Key can be distinguishable without the following members.
419                // key.mOptionalAttributes.mAltCode,
420                // key.mOptionalAttributes.mDisabledIconId,
421                // key.mOptionalAttributes.mPreviewIconId,
422                // key.mHorizontalGap,
423                // key.mVerticalGap,
424                // key.mOptionalAttributes.mVisualInsetLeft,
425                // key.mOptionalAttributes.mVisualInsetRight,
426                // key.mMaxMoreKeysColumn,
427        });
428    }
429
430    private boolean equalsInternal(final Key o) {
431        if (this == o) return true;
432        return o.mX == mX
433                && o.mY == mY
434                && o.mWidth == mWidth
435                && o.mHeight == mHeight
436                && o.mCode == mCode
437                && TextUtils.equals(o.mLabel, mLabel)
438                && TextUtils.equals(o.mHintLabel, mHintLabel)
439                && o.mIconId == mIconId
440                && o.mBackgroundType == mBackgroundType
441                && Arrays.equals(o.mMoreKeys, mMoreKeys)
442                && TextUtils.equals(o.getOutputText(), getOutputText())
443                && o.mActionFlags == mActionFlags
444                && o.mLabelFlags == mLabelFlags;
445    }
446
447    @Override
448    public int compareTo(Key o) {
449        if (equalsInternal(o)) return 0;
450        if (mHashCode > o.mHashCode) return 1;
451        return -1;
452    }
453
454    @Override
455    public int hashCode() {
456        return mHashCode;
457    }
458
459    @Override
460    public boolean equals(final Object o) {
461        return o instanceof Key && equalsInternal((Key)o);
462    }
463
464    @Override
465    public String toString() {
466        return toShortString() + " " + getX() + "," + getY() + " " + getWidth() + "x" + getHeight();
467    }
468
469    public String toShortString() {
470        final int code = getCode();
471        if (code == Constants.CODE_OUTPUT_TEXT) {
472            return getOutputText();
473        }
474        return Constants.printableCode(code);
475    }
476
477    public String toLongString() {
478        final int iconId = getIconId();
479        final String topVisual = (iconId == KeyboardIconsSet.ICON_UNDEFINED)
480                ? KeyboardIconsSet.PREFIX_ICON + KeyboardIconsSet.getIconName(iconId) : getLabel();
481        final String hintLabel = getHintLabel();
482        final String visual = (hintLabel == null) ? topVisual : topVisual + "^" + hintLabel;
483        return toString() + " " + visual + "/" + backgroundName(mBackgroundType);
484    }
485
486    private static String backgroundName(final int backgroundType) {
487        switch (backgroundType) {
488        case BACKGROUND_TYPE_EMPTY: return "empty";
489        case BACKGROUND_TYPE_NORMAL: return "normal";
490        case BACKGROUND_TYPE_FUNCTIONAL: return "functional";
491        case BACKGROUND_TYPE_ACTION: return "action";
492        case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff";
493        case BACKGROUND_TYPE_STICKY_ON: return "stickyOn";
494        default: return null;
495        }
496    }
497
498    public int getCode() {
499        return mCode;
500    }
501
502    public String getLabel() {
503        return mLabel;
504    }
505
506    public String getHintLabel() {
507        return mHintLabel;
508    }
509
510    public MoreKeySpec[] getMoreKeys() {
511        return mMoreKeys;
512    }
513
514    public void markAsLeftEdge(final KeyboardParams params) {
515        mHitBox.left = params.mLeftPadding;
516    }
517
518    public void markAsRightEdge(final KeyboardParams params) {
519        mHitBox.right = params.mOccupiedWidth - params.mRightPadding;
520    }
521
522    public void markAsTopEdge(final KeyboardParams params) {
523        mHitBox.top = params.mTopPadding;
524    }
525
526    public void markAsBottomEdge(final KeyboardParams params) {
527        mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding;
528    }
529
530    public final boolean isSpacer() {
531        return this instanceof Spacer;
532    }
533
534    public final boolean isShift() {
535        return mCode == CODE_SHIFT;
536    }
537
538    public final boolean isModifier() {
539        return mCode == CODE_SHIFT || mCode == CODE_SWITCH_ALPHA_SYMBOL;
540    }
541
542    public final boolean isRepeatable() {
543        return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0;
544    }
545
546    public final boolean noKeyPreview() {
547        return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0;
548    }
549
550    public final boolean altCodeWhileTyping() {
551        return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0;
552    }
553
554    public final boolean isLongPressEnabled() {
555        // We need not start long press timer on the key which has activated shifted letter.
556        return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0
557                && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0;
558    }
559
560    public KeyVisualAttributes getVisualAttributes() {
561        return mKeyVisualAttributes;
562    }
563
564    public final Typeface selectTypeface(final KeyDrawParams params) {
565        switch (mLabelFlags & LABEL_FLAGS_FONT_MASK) {
566        case LABEL_FLAGS_FONT_NORMAL:
567            return Typeface.DEFAULT;
568        case LABEL_FLAGS_FONT_MONO_SPACE:
569            return Typeface.MONOSPACE;
570        case LABEL_FLAGS_FONT_DEFAULT:
571        default:
572            // The type-face is specified by keyTypeface attribute.
573            return params.mTypeface;
574        }
575    }
576
577    public final int selectTextSize(final KeyDrawParams params) {
578        switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) {
579        case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO:
580            return params.mLetterSize;
581        case LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO:
582            return params.mLargeLetterSize;
583        case LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO:
584            return params.mLabelSize;
585        case LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO:
586            return params.mLargeLabelSize;
587        case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO:
588            return params.mHintLabelSize;
589        default: // No follow key ratio flag specified.
590            return StringUtils.codePointCount(mLabel) == 1 ? params.mLetterSize : params.mLabelSize;
591        }
592    }
593
594    public final int selectTextColor(final KeyDrawParams params) {
595        if ((mLabelFlags & LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR) != 0) {
596            return params.mFunctionalTextColor;
597        }
598        return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor;
599    }
600
601    public final int selectHintTextSize(final KeyDrawParams params) {
602        if (hasHintLabel()) {
603            return params.mHintLabelSize;
604        }
605        if (hasShiftedLetterHint()) {
606            return params.mShiftedLetterHintSize;
607        }
608        return params.mHintLetterSize;
609    }
610
611    public final int selectHintTextColor(final KeyDrawParams params) {
612        if (hasHintLabel()) {
613            return params.mHintLabelColor;
614        }
615        if (hasShiftedLetterHint()) {
616            return isShiftedLetterActivated() ? params.mShiftedLetterHintActivatedColor
617                    : params.mShiftedLetterHintInactivatedColor;
618        }
619        return params.mHintLetterColor;
620    }
621
622    public final int selectMoreKeyTextSize(final KeyDrawParams params) {
623        return hasLabelsInMoreKeys() ? params.mLabelSize : params.mLetterSize;
624    }
625
626    public final String getPreviewLabel() {
627        return isShiftedLetterActivated() ? mHintLabel : mLabel;
628    }
629
630    private boolean previewHasLetterSize() {
631        return (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO) != 0
632                || StringUtils.codePointCount(getPreviewLabel()) == 1;
633    }
634
635    public final int selectPreviewTextSize(final KeyDrawParams params) {
636        if (previewHasLetterSize()) {
637            return params.mPreviewTextSize;
638        }
639        return params.mLetterSize;
640    }
641
642    public Typeface selectPreviewTypeface(final KeyDrawParams params) {
643        if (previewHasLetterSize()) {
644            return selectTypeface(params);
645        }
646        return Typeface.DEFAULT_BOLD;
647    }
648
649    public final boolean isAlignLeft() {
650        return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0;
651    }
652
653    public final boolean isAlignRight() {
654        return (mLabelFlags & LABEL_FLAGS_ALIGN_RIGHT) != 0;
655    }
656
657    public final boolean isAlignButtom() {
658        return (mLabelFlags & LABEL_FLAGS_ALIGN_BUTTOM) != 0;
659    }
660
661    public final boolean isAlignLeftOfCenter() {
662        return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0;
663    }
664
665    public final boolean hasPopupHint() {
666        return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0;
667    }
668
669    public final boolean hasShiftedLetterHint() {
670        return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0
671                && !TextUtils.isEmpty(mHintLabel);
672    }
673
674    public final boolean hasHintLabel() {
675        return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0;
676    }
677
678    public final boolean hasLabelWithIconLeft() {
679        return (mLabelFlags & LABEL_FLAGS_WITH_ICON_LEFT) != 0;
680    }
681
682    public final boolean hasLabelWithIconRight() {
683        return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0;
684    }
685
686    public final boolean needsAutoXScale() {
687        return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0;
688    }
689
690    public final boolean needsAutoScale() {
691        return (mLabelFlags & LABEL_FLAGS_AUTO_SCALE) == LABEL_FLAGS_AUTO_SCALE;
692    }
693
694    private final boolean isShiftedLetterActivated() {
695        return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0
696                && !TextUtils.isEmpty(mHintLabel);
697    }
698
699    public final int getMoreKeysColumn() {
700        return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_MASK;
701    }
702
703    public final boolean isFixedColumnOrderMoreKeys() {
704        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER) != 0;
705    }
706
707    public final boolean hasLabelsInMoreKeys() {
708        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0;
709    }
710
711    public final int getMoreKeyLabelFlags() {
712        return hasLabelsInMoreKeys()
713                ? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO
714                : LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO;
715    }
716
717    public final boolean needsDividersInMoreKeys() {
718        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0;
719    }
720
721    public final boolean hasNoPanelAutoMoreKey() {
722        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY) != 0;
723    }
724
725    public final String getOutputText() {
726        final OptionalAttributes attrs = mOptionalAttributes;
727        return (attrs != null) ? attrs.mOutputText : null;
728    }
729
730    public final int getAltCode() {
731        final OptionalAttributes attrs = mOptionalAttributes;
732        return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED;
733    }
734
735    public int getIconId() {
736        return mIconId;
737    }
738
739    public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
740        final OptionalAttributes attrs = mOptionalAttributes;
741        final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED;
742        final int iconId = mEnabled ? getIconId() : disabledIconId;
743        final Drawable icon = iconSet.getIconDrawable(iconId);
744        if (icon != null) {
745            icon.setAlpha(alpha);
746        }
747        return icon;
748    }
749
750    public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) {
751        return iconSet.getIconDrawable(getIconId());
752    }
753
754    public int getWidth() {
755        return mWidth;
756    }
757
758    public int getHeight() {
759        return mHeight;
760    }
761
762    public int getX() {
763        return mX;
764    }
765
766    public int getY() {
767        return mY;
768    }
769
770    public final int getDrawX() {
771        final int x = getX();
772        final OptionalAttributes attrs = mOptionalAttributes;
773        return (attrs == null) ? x : x + attrs.mVisualInsetsLeft;
774    }
775
776    public final int getDrawWidth() {
777        final OptionalAttributes attrs = mOptionalAttributes;
778        return (attrs == null) ? mWidth
779                : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight;
780    }
781
782    /**
783     * Informs the key that it has been pressed, in case it needs to change its appearance or
784     * state.
785     * @see #onReleased()
786     */
787    public void onPressed() {
788        mPressed = true;
789    }
790
791    /**
792     * Informs the key that it has been released, in case it needs to change its appearance or
793     * state.
794     * @see #onPressed()
795     */
796    public void onReleased() {
797        mPressed = false;
798    }
799
800    public final boolean isEnabled() {
801        return mEnabled;
802    }
803
804    public void setEnabled(final boolean enabled) {
805        mEnabled = enabled;
806    }
807
808    public Rect getHitBox() {
809        return mHitBox;
810    }
811
812    /**
813     * Detects if a point falls on this key.
814     * @param x the x-coordinate of the point
815     * @param y the y-coordinate of the point
816     * @return whether or not the point falls on the key. If the key is attached to an edge, it
817     * will assume that all points between the key and the edge are considered to be on the key.
818     * @see #markAsLeftEdge(KeyboardParams) etc.
819     */
820    public boolean isOnKey(final int x, final int y) {
821        return mHitBox.contains(x, y);
822    }
823
824    /**
825     * Returns the square of the distance to the nearest edge of the key and the given point.
826     * @param x the x-coordinate of the point
827     * @param y the y-coordinate of the point
828     * @return the square of the distance of the point from the nearest edge of the key
829     */
830    public int squaredDistanceToEdge(final int x, final int y) {
831        final int left = getX();
832        final int right = left + mWidth;
833        final int top = getY();
834        final int bottom = top + mHeight;
835        final int edgeX = x < left ? left : (x > right ? right : x);
836        final int edgeY = y < top ? top : (y > bottom ? bottom : y);
837        final int dx = x - edgeX;
838        final int dy = y - edgeY;
839        return dx * dx + dy * dy;
840    }
841
842    private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_ON = {
843        android.R.attr.state_checkable,
844        android.R.attr.state_checked
845    };
846
847    private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_ON = {
848        android.R.attr.state_pressed,
849        android.R.attr.state_checkable,
850        android.R.attr.state_checked
851    };
852
853    private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_OFF = {
854        android.R.attr.state_checkable
855    };
856
857    private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_OFF = {
858        android.R.attr.state_pressed,
859        android.R.attr.state_checkable
860    };
861
862    private final static int[] KEY_STATE_NORMAL = {
863    };
864
865    private final static int[] KEY_STATE_PRESSED = {
866        android.R.attr.state_pressed
867    };
868
869    private final static int[] KEY_STATE_EMPTY = {
870        android.R.attr.state_empty
871    };
872
873    // action normal state (with properties)
874    private static final int[] KEY_STATE_ACTIVE_NORMAL = {
875            android.R.attr.state_active
876    };
877
878    // action pressed state (with properties)
879    private static final int[] KEY_STATE_ACTIVE_PRESSED = {
880            android.R.attr.state_active,
881            android.R.attr.state_pressed
882    };
883
884    /**
885     * Returns the background drawable for the key, based on the current state and type of the key.
886     * @return the background drawable of the key.
887     * @see android.graphics.drawable.StateListDrawable#setState(int[])
888     */
889    public final Drawable selectBackgroundDrawable(final Drawable keyBackground,
890            final Drawable functionalKeyBackground, final Drawable spacebarBackground) {
891        final Drawable background;
892        if (mBackgroundType == BACKGROUND_TYPE_FUNCTIONAL) {
893            background = functionalKeyBackground;
894        } else if (getCode() == Constants.CODE_SPACE) {
895            background = spacebarBackground;
896        } else {
897            background = keyBackground;
898        }
899        final int[] stateSet;
900        switch (mBackgroundType) {
901        case BACKGROUND_TYPE_ACTION:
902            stateSet = mPressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL;
903            break;
904        case BACKGROUND_TYPE_STICKY_OFF:
905            stateSet = mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF;
906            break;
907        case BACKGROUND_TYPE_STICKY_ON:
908            stateSet = mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON;
909            break;
910        case BACKGROUND_TYPE_EMPTY:
911            stateSet = mPressed ? KEY_STATE_PRESSED : KEY_STATE_EMPTY;
912            break;
913        case BACKGROUND_TYPE_FUNCTIONAL:
914            stateSet = mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL;
915            break;
916        default: /* BACKGROUND_TYPE_NORMAL */
917            stateSet = mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL;
918            break;
919        }
920        background.setState(stateSet);
921        return background;
922    }
923
924    public static class Spacer extends Key {
925        public Spacer(final TypedArray keyAttr, final KeyStyle keyStyle,
926                final KeyboardParams params, final KeyboardRow row) {
927            super(null /* keySpec */, keyAttr, keyStyle, params, row);
928        }
929
930        /**
931         * This constructor is being used only for divider in more keys keyboard.
932         */
933        protected Spacer(final KeyboardParams params, final int x, final int y, final int width,
934                final int height) {
935            super(null /* label */, ICON_UNDEFINED, CODE_UNSPECIFIED, null /* outputText */,
936                    null /* hintLabel */, 0 /* labelFlags */, BACKGROUND_TYPE_EMPTY, x, y, width,
937                    height, params.mHorizontalGap, params.mVerticalGap);
938        }
939    }
940}
941