Key.java revision da500b7ec3093178922140cb67beca1e3578661d
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_LEFT_OF_CENTER = 0x08;
64    private static final int LABEL_FLAGS_FONT_NORMAL = 0x10;
65    private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x20;
66    // Start of key text ratio enum values
67    private static final int LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK = 0x1C0;
68    private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO = 0x40;
69    private static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80;
70    private static final int LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO = 0xC0;
71    private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO = 0x100;
72    private static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x140;
73    // End of key text ratio mask enum values
74    private static final int LABEL_FLAGS_HAS_POPUP_HINT = 0x200;
75    private static final int LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT = 0x400;
76    private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800;
77    private static final int LABEL_FLAGS_WITH_ICON_LEFT = 0x1000;
78    private static final int LABEL_FLAGS_WITH_ICON_RIGHT = 0x2000;
79    // The bit to calculate the ratio of key label width against key width. If autoXScale bit is on
80    // and autoYScale bit is off, the key label may be shrunk only for X-direction.
81    // If both autoXScale and autoYScale bits are on, the key label text size may be auto scaled.
82    private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000;
83    private static final int LABEL_FLAGS_AUTO_Y_SCALE = 0x8000;
84    private static final int LABEL_FLAGS_AUTO_SCALE = LABEL_FLAGS_AUTO_X_SCALE
85            | LABEL_FLAGS_AUTO_Y_SCALE;
86    private static final int LABEL_FLAGS_PRESERVE_CASE = 0x10000;
87    private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x20000;
88    private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x40000;
89    private static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000;
90    private static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000;
91
92    /** Icon to display instead of a label. Icon takes precedence over a label */
93    private final int mIconId;
94
95    /** Width of the key, not including the gap */
96    private final int mWidth;
97    /** Height of the key, not including the gap */
98    private final int mHeight;
99    /** X coordinate of the key in the keyboard layout */
100    private final int mX;
101    /** Y coordinate of the key in the keyboard layout */
102    private final int mY;
103    /** Hit bounding box of the key */
104    private final Rect mHitBox = new Rect();
105
106    /** More keys. It is guaranteed that this is null or an array of one or more elements */
107    private final MoreKeySpec[] mMoreKeys;
108    /** More keys column number and flags */
109    private final int mMoreKeysColumnAndFlags;
110    private static final int MORE_KEYS_COLUMN_MASK = 0x000000ff;
111    private static final int MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER = 0x80000000;
112    private static final int MORE_KEYS_FLAGS_HAS_LABELS = 0x40000000;
113    private static final int MORE_KEYS_FLAGS_NEEDS_DIVIDERS = 0x20000000;
114    private static final int MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY = 0x10000000;
115    private static final String MORE_KEYS_AUTO_COLUMN_ORDER = "!autoColumnOrder!";
116    private static final String MORE_KEYS_FIXED_COLUMN_ORDER = "!fixedColumnOrder!";
117    private static final String MORE_KEYS_HAS_LABELS = "!hasLabels!";
118    private static final String MORE_KEYS_NEEDS_DIVIDERS = "!needsDividers!";
119    private static final String MORE_KEYS_NO_PANEL_AUTO_MORE_KEY = "!noPanelAutoMoreKey!";
120
121    /** Background type that represents different key background visual than normal one. */
122    private final int mBackgroundType;
123    public static final int BACKGROUND_TYPE_EMPTY = 0;
124    public static final int BACKGROUND_TYPE_NORMAL = 1;
125    public static final int BACKGROUND_TYPE_FUNCTIONAL = 2;
126    public static final int BACKGROUND_TYPE_ACTION = 3;
127    public static final int BACKGROUND_TYPE_STICKY_OFF = 4;
128    public static final int BACKGROUND_TYPE_STICKY_ON = 5;
129
130    private final int mActionFlags;
131    private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01;
132    private static final int ACTION_FLAGS_NO_KEY_PREVIEW = 0x02;
133    private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04;
134    private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08;
135
136    private final KeyVisualAttributes mKeyVisualAttributes;
137
138    private final OptionalAttributes mOptionalAttributes;
139
140    private static final class OptionalAttributes {
141        /** Text to output when pressed. This can be multiple characters, like ".com" */
142        public final String mOutputText;
143        public final int mAltCode;
144        /** Icon for disabled state */
145        public final int mDisabledIconId;
146        /** Preview version of the icon, for the preview popup */
147        public final int mPreviewIconId;
148        /** The visual insets */
149        public final int mVisualInsetsLeft;
150        public final int mVisualInsetsRight;
151
152        private OptionalAttributes(final String outputText, final int altCode,
153                final int disabledIconId, final int previewIconId,
154                final int visualInsetsLeft, final int visualInsetsRight) {
155            mOutputText = outputText;
156            mAltCode = altCode;
157            mDisabledIconId = disabledIconId;
158            mPreviewIconId = previewIconId;
159            mVisualInsetsLeft = visualInsetsLeft;
160            mVisualInsetsRight = visualInsetsRight;
161        }
162
163        public static OptionalAttributes newInstance(final String outputText, final int altCode,
164                final int disabledIconId, final int previewIconId,
165                final int visualInsetsLeft, final int visualInsetsRight) {
166            if (outputText == null && altCode == CODE_UNSPECIFIED
167                    && disabledIconId == ICON_UNDEFINED && previewIconId == ICON_UNDEFINED
168                    && visualInsetsLeft == 0 && visualInsetsRight == 0) {
169                return null;
170            }
171            return new OptionalAttributes(outputText, altCode, disabledIconId, previewIconId,
172                    visualInsetsLeft, 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        mActionFlags = 0;
197        mMoreKeys = null;
198        mMoreKeysColumnAndFlags = 0;
199        mLabel = label;
200        mOptionalAttributes = OptionalAttributes.newInstance(outputText, CODE_UNSPECIFIED,
201                ICON_UNDEFINED, ICON_UNDEFINED,
202                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 keyStyle 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        final int previewIconId = KeySpecParser.getIconId(style.getString(keyAttr,
304                R.styleable.Keyboard_Key_keyIconPreview));
305
306        final int code = KeySpecParser.getCode(keySpec);
307        if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) {
308            mLabel = params.mId.mCustomActionLabel;
309        } else if (code >= Character.MIN_SUPPLEMENTARY_CODE_POINT) {
310            // This is a workaround to have a key that has a supplementary code point in its label.
311            // Because we can put a string in resource neither as a XML entity of a supplementary
312            // code point nor as a surrogate pair.
313            mLabel = new StringBuilder().appendCodePoint(code).toString();
314        } else {
315            mLabel = StringUtils.toUpperCaseOfStringForLocale(
316                    KeySpecParser.getLabel(keySpec), needsToUpperCase, locale);
317        }
318        if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) {
319            mHintLabel = null;
320        } else {
321            mHintLabel = StringUtils.toUpperCaseOfStringForLocale(style.getString(keyAttr,
322                    R.styleable.Keyboard_Key_keyHintLabel), needsToUpperCase, locale);
323        }
324        String outputText = StringUtils.toUpperCaseOfStringForLocale(
325                KeySpecParser.getOutputText(keySpec), needsToUpperCase, locale);
326        // Choose the first letter of the label as primary code if not specified.
327        if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText)
328                && !TextUtils.isEmpty(mLabel)) {
329            if (StringUtils.codePointCount(mLabel) == 1) {
330                // Use the first letter of the hint label if shiftedLetterActivated flag is
331                // specified.
332                if (hasShiftedLetterHint() && isShiftedLetterActivated()) {
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 = StringUtils.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale);
352        }
353        final int altCodeInAttr = KeySpecParser.parseCode(
354                style.getString(keyAttr, R.styleable.Keyboard_Key_altCode), CODE_UNSPECIFIED);
355        final int altCode = StringUtils.toUpperCaseOfCodeForLocale(
356                altCodeInAttr, needsToUpperCase, locale);
357        mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode,
358                disabledIconId, previewIconId, visualInsetsLeft, visualInsetsRight);
359        mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
360        mHashCode = computeHashCode(this);
361    }
362
363    /**
364     * Copy constructor for DynamicGridKeyboard.GridKey.
365     *
366     * @param key the original key.
367     */
368    protected Key(final Key key) {
369        // Final attributes.
370        mCode = key.mCode;
371        mLabel = key.mLabel;
372        mHintLabel = key.mHintLabel;
373        mLabelFlags = key.mLabelFlags;
374        mIconId = key.mIconId;
375        mWidth = key.mWidth;
376        mHeight = key.mHeight;
377        mX = key.mX;
378        mY = key.mY;
379        mHitBox.set(key.mHitBox);
380        mMoreKeys = key.mMoreKeys;
381        mMoreKeysColumnAndFlags = key.mMoreKeysColumnAndFlags;
382        mBackgroundType = key.mBackgroundType;
383        mActionFlags = key.mActionFlags;
384        mKeyVisualAttributes = key.mKeyVisualAttributes;
385        mOptionalAttributes = key.mOptionalAttributes;
386        mHashCode = key.mHashCode;
387        // Key state.
388        mPressed = key.mPressed;
389        mEnabled = key.mEnabled;
390    }
391
392    private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) {
393        if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false;
394        switch (keyboardElementId) {
395        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
396        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
397        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
398        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
399            return true;
400        default:
401            return false;
402        }
403    }
404
405    private static int computeHashCode(final Key key) {
406        return Arrays.hashCode(new Object[] {
407                key.mX,
408                key.mY,
409                key.mWidth,
410                key.mHeight,
411                key.mCode,
412                key.mLabel,
413                key.mHintLabel,
414                key.mIconId,
415                key.mBackgroundType,
416                Arrays.hashCode(key.mMoreKeys),
417                key.getOutputText(),
418                key.mActionFlags,
419                key.mLabelFlags,
420                // Key can be distinguishable without the following members.
421                // key.mOptionalAttributes.mAltCode,
422                // key.mOptionalAttributes.mDisabledIconId,
423                // key.mOptionalAttributes.mPreviewIconId,
424                // key.mHorizontalGap,
425                // key.mVerticalGap,
426                // key.mOptionalAttributes.mVisualInsetLeft,
427                // key.mOptionalAttributes.mVisualInsetRight,
428                // key.mMaxMoreKeysColumn,
429        });
430    }
431
432    private boolean equalsInternal(final Key o) {
433        if (this == o) return true;
434        return o.mX == mX
435                && o.mY == mY
436                && o.mWidth == mWidth
437                && o.mHeight == mHeight
438                && o.mCode == mCode
439                && TextUtils.equals(o.mLabel, mLabel)
440                && TextUtils.equals(o.mHintLabel, mHintLabel)
441                && o.mIconId == mIconId
442                && o.mBackgroundType == mBackgroundType
443                && Arrays.equals(o.mMoreKeys, mMoreKeys)
444                && TextUtils.equals(o.getOutputText(), getOutputText())
445                && o.mActionFlags == mActionFlags
446                && o.mLabelFlags == mLabelFlags;
447    }
448
449    @Override
450    public int compareTo(Key o) {
451        if (equalsInternal(o)) return 0;
452        if (mHashCode > o.mHashCode) return 1;
453        return -1;
454    }
455
456    @Override
457    public int hashCode() {
458        return mHashCode;
459    }
460
461    @Override
462    public boolean equals(final Object o) {
463        return o instanceof Key && equalsInternal((Key)o);
464    }
465
466    @Override
467    public String toString() {
468        final String label;
469        if (StringUtils.codePointCount(mLabel) == 1 && mLabel.codePointAt(0) == mCode) {
470            label = "";
471        } else {
472            label = "/" + mLabel;
473        }
474        return String.format(Locale.ROOT, "%s%s %d,%d %dx%d %s/%s/%s",
475                Constants.printableCode(mCode), label, mX, mY, mWidth, mHeight, mHintLabel,
476                KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType));
477    }
478
479    private static String backgroundName(final int backgroundType) {
480        switch (backgroundType) {
481        case BACKGROUND_TYPE_EMPTY: return "empty";
482        case BACKGROUND_TYPE_NORMAL: return "normal";
483        case BACKGROUND_TYPE_FUNCTIONAL: return "functional";
484        case BACKGROUND_TYPE_ACTION: return "action";
485        case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff";
486        case BACKGROUND_TYPE_STICKY_ON: return "stickyOn";
487        default: return null;
488        }
489    }
490
491    public int getCode() {
492        return mCode;
493    }
494
495    public String getLabel() {
496        return mLabel;
497    }
498
499    public String getHintLabel() {
500        return mHintLabel;
501    }
502
503    public MoreKeySpec[] getMoreKeys() {
504        return mMoreKeys;
505    }
506
507    public void markAsLeftEdge(final KeyboardParams params) {
508        mHitBox.left = params.mLeftPadding;
509    }
510
511    public void markAsRightEdge(final KeyboardParams params) {
512        mHitBox.right = params.mOccupiedWidth - params.mRightPadding;
513    }
514
515    public void markAsTopEdge(final KeyboardParams params) {
516        mHitBox.top = params.mTopPadding;
517    }
518
519    public void markAsBottomEdge(final KeyboardParams params) {
520        mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding;
521    }
522
523    public final boolean isSpacer() {
524        return this instanceof Spacer;
525    }
526
527    public final boolean isShift() {
528        return mCode == CODE_SHIFT;
529    }
530
531    public final boolean isModifier() {
532        return mCode == CODE_SHIFT || mCode == CODE_SWITCH_ALPHA_SYMBOL;
533    }
534
535    public final boolean isRepeatable() {
536        return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0;
537    }
538
539    public final boolean noKeyPreview() {
540        return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0;
541    }
542
543    public final boolean altCodeWhileTyping() {
544        return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0;
545    }
546
547    public final boolean isLongPressEnabled() {
548        // We need not start long press timer on the key which has activated shifted letter.
549        return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0
550                && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0;
551    }
552
553    public KeyVisualAttributes getVisualAttributes() {
554        return mKeyVisualAttributes;
555    }
556
557    public final Typeface selectTypeface(final KeyDrawParams params) {
558        // TODO: Handle "bold" here too?
559        if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) {
560            return Typeface.DEFAULT;
561        }
562        if ((mLabelFlags & LABEL_FLAGS_FONT_MONO_SPACE) != 0) {
563            return Typeface.MONOSPACE;
564        }
565        return params.mTypeface;
566    }
567
568    public final int selectTextSize(final KeyDrawParams params) {
569        switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) {
570        case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO:
571            return params.mLetterSize;
572        case LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO:
573            return params.mLargeLetterSize;
574        case LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO:
575            return params.mLabelSize;
576        case LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO:
577            return params.mLargeLabelSize;
578        case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO:
579            return params.mHintLabelSize;
580        default: // No follow key ratio flag specified.
581            return StringUtils.codePointCount(mLabel) == 1 ? params.mLetterSize : params.mLabelSize;
582        }
583    }
584
585    public final int selectTextColor(final KeyDrawParams params) {
586        return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor;
587    }
588
589    public final int selectHintTextSize(final KeyDrawParams params) {
590        if (hasHintLabel()) {
591            return params.mHintLabelSize;
592        }
593        if (hasShiftedLetterHint()) {
594            return params.mShiftedLetterHintSize;
595        }
596        return params.mHintLetterSize;
597    }
598
599    public final int selectHintTextColor(final KeyDrawParams params) {
600        if (hasHintLabel()) {
601            return params.mHintLabelColor;
602        }
603        if (hasShiftedLetterHint()) {
604            return isShiftedLetterActivated() ? params.mShiftedLetterHintActivatedColor
605                    : params.mShiftedLetterHintInactivatedColor;
606        }
607        return params.mHintLetterColor;
608    }
609
610    public final int selectMoreKeyTextSize(final KeyDrawParams params) {
611        return hasLabelsInMoreKeys() ? params.mLabelSize : params.mLetterSize;
612    }
613
614    public final String getPreviewLabel() {
615        return isShiftedLetterActivated() ? mHintLabel : mLabel;
616    }
617
618    private boolean previewHasLetterSize() {
619        return (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO) != 0
620                || StringUtils.codePointCount(getPreviewLabel()) == 1;
621    }
622
623    public final int selectPreviewTextSize(final KeyDrawParams params) {
624        if (previewHasLetterSize()) {
625            return params.mPreviewTextSize;
626        }
627        return params.mLetterSize;
628    }
629
630    public Typeface selectPreviewTypeface(final KeyDrawParams params) {
631        if (previewHasLetterSize()) {
632            return selectTypeface(params);
633        }
634        return Typeface.DEFAULT_BOLD;
635    }
636
637    public final boolean isAlignLeft() {
638        return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0;
639    }
640
641    public final boolean isAlignRight() {
642        return (mLabelFlags & LABEL_FLAGS_ALIGN_RIGHT) != 0;
643    }
644
645    public final boolean isAlignLeftOfCenter() {
646        return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0;
647    }
648
649    public final boolean hasPopupHint() {
650        return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0;
651    }
652
653    public final boolean hasShiftedLetterHint() {
654        return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0
655                && !TextUtils.isEmpty(mHintLabel);
656    }
657
658    public final boolean hasHintLabel() {
659        return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0;
660    }
661
662    public final boolean hasLabelWithIconLeft() {
663        return (mLabelFlags & LABEL_FLAGS_WITH_ICON_LEFT) != 0;
664    }
665
666    public final boolean hasLabelWithIconRight() {
667        return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0;
668    }
669
670    public final boolean needsAutoXScale() {
671        return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0;
672    }
673
674    public final boolean needsAutoScale() {
675        return (mLabelFlags & LABEL_FLAGS_AUTO_SCALE) == LABEL_FLAGS_AUTO_SCALE;
676    }
677
678    private final boolean isShiftedLetterActivated() {
679        return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0
680                && !TextUtils.isEmpty(mHintLabel);
681    }
682
683    public final int getMoreKeysColumn() {
684        return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_MASK;
685    }
686
687    public final boolean isFixedColumnOrderMoreKeys() {
688        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER) != 0;
689    }
690
691    public final boolean hasLabelsInMoreKeys() {
692        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0;
693    }
694
695    public final int getMoreKeyLabelFlags() {
696        return hasLabelsInMoreKeys()
697                ? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO
698                : LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO;
699    }
700
701    public final boolean needsDividersInMoreKeys() {
702        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0;
703    }
704
705    public final boolean hasNoPanelAutoMoreKey() {
706        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY) != 0;
707    }
708
709    public final String getOutputText() {
710        final OptionalAttributes attrs = mOptionalAttributes;
711        return (attrs != null) ? attrs.mOutputText : null;
712    }
713
714    public final int getAltCode() {
715        final OptionalAttributes attrs = mOptionalAttributes;
716        return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED;
717    }
718
719    public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
720        final OptionalAttributes attrs = mOptionalAttributes;
721        final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED;
722        final int iconId = mEnabled ? mIconId : disabledIconId;
723        final Drawable icon = iconSet.getIconDrawable(iconId);
724        if (icon != null) {
725            icon.setAlpha(alpha);
726        }
727        return icon;
728    }
729
730    public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) {
731        final OptionalAttributes attrs = mOptionalAttributes;
732        final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED;
733        return previewIconId != ICON_UNDEFINED
734                ? iconSet.getIconDrawable(previewIconId) : iconSet.getIconDrawable(mIconId);
735    }
736
737    public int getWidth() {
738        return mWidth;
739    }
740
741    public int getHeight() {
742        return mHeight;
743    }
744
745    public int getX() {
746        return mX;
747    }
748
749    public int getY() {
750        return mY;
751    }
752
753    public final int getDrawX() {
754        final int x = getX();
755        final OptionalAttributes attrs = mOptionalAttributes;
756        return (attrs == null) ? x : x + attrs.mVisualInsetsLeft;
757    }
758
759    public final int getDrawWidth() {
760        final OptionalAttributes attrs = mOptionalAttributes;
761        return (attrs == null) ? mWidth
762                : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight;
763    }
764
765    /**
766     * Informs the key that it has been pressed, in case it needs to change its appearance or
767     * state.
768     * @see #onReleased()
769     */
770    public void onPressed() {
771        mPressed = true;
772    }
773
774    /**
775     * Informs the key that it has been released, in case it needs to change its appearance or
776     * state.
777     * @see #onPressed()
778     */
779    public void onReleased() {
780        mPressed = false;
781    }
782
783    public final boolean isEnabled() {
784        return mEnabled;
785    }
786
787    public void setEnabled(final boolean enabled) {
788        mEnabled = enabled;
789    }
790
791    public Rect getHitBox() {
792        return mHitBox;
793    }
794
795    /**
796     * Detects if a point falls on this key.
797     * @param x the x-coordinate of the point
798     * @param y the y-coordinate of the point
799     * @return whether or not the point falls on the key. If the key is attached to an edge, it
800     * will assume that all points between the key and the edge are considered to be on the key.
801     * @see #markAsLeftEdge(KeyboardParams) etc.
802     */
803    public boolean isOnKey(final int x, final int y) {
804        return mHitBox.contains(x, y);
805    }
806
807    /**
808     * Returns the square of the distance to the nearest edge of the key and the given point.
809     * @param x the x-coordinate of the point
810     * @param y the y-coordinate of the point
811     * @return the square of the distance of the point from the nearest edge of the key
812     */
813    public int squaredDistanceToEdge(final int x, final int y) {
814        final int left = getX();
815        final int right = left + mWidth;
816        final int top = getY();
817        final int bottom = top + mHeight;
818        final int edgeX = x < left ? left : (x > right ? right : x);
819        final int edgeY = y < top ? top : (y > bottom ? bottom : y);
820        final int dx = x - edgeX;
821        final int dy = y - edgeY;
822        return dx * dx + dy * dy;
823    }
824
825    private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_ON = {
826        android.R.attr.state_checkable,
827        android.R.attr.state_checked
828    };
829
830    private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_ON = {
831        android.R.attr.state_pressed,
832        android.R.attr.state_checkable,
833        android.R.attr.state_checked
834    };
835
836    private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_OFF = {
837        android.R.attr.state_checkable
838    };
839
840    private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_OFF = {
841        android.R.attr.state_pressed,
842        android.R.attr.state_checkable
843    };
844
845    private final static int[] KEY_STATE_NORMAL = {
846    };
847
848    private final static int[] KEY_STATE_PRESSED = {
849        android.R.attr.state_pressed
850    };
851
852    private final static int[] KEY_STATE_EMPTY = {
853        android.R.attr.state_empty
854    };
855
856    // functional normal state (with properties)
857    private static final int[] KEY_STATE_FUNCTIONAL_NORMAL = {
858            android.R.attr.state_single
859    };
860
861    // functional pressed state (with properties)
862    private static final int[] KEY_STATE_FUNCTIONAL_PRESSED = {
863            android.R.attr.state_single,
864            android.R.attr.state_pressed
865    };
866
867    // action normal state (with properties)
868    private static final int[] KEY_STATE_ACTIVE_NORMAL = {
869            android.R.attr.state_active
870    };
871
872    // action pressed state (with properties)
873    private static final int[] KEY_STATE_ACTIVE_PRESSED = {
874            android.R.attr.state_active,
875            android.R.attr.state_pressed
876    };
877
878    /**
879     * Returns the drawable state for the key, based on the current state and type of the key.
880     * @return the drawable state of the key.
881     * @see android.graphics.drawable.StateListDrawable#setState(int[])
882     */
883    public final int[] getCurrentDrawableState() {
884        switch (mBackgroundType) {
885        case BACKGROUND_TYPE_FUNCTIONAL:
886            return mPressed ? KEY_STATE_FUNCTIONAL_PRESSED : KEY_STATE_FUNCTIONAL_NORMAL;
887        case BACKGROUND_TYPE_ACTION:
888            return mPressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL;
889        case BACKGROUND_TYPE_STICKY_OFF:
890            return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF;
891        case BACKGROUND_TYPE_STICKY_ON:
892            return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON;
893        case BACKGROUND_TYPE_EMPTY:
894            return mPressed ? KEY_STATE_PRESSED : KEY_STATE_EMPTY;
895        default: /* BACKGROUND_TYPE_NORMAL */
896            return mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL;
897        }
898    }
899
900    public static class Spacer extends Key {
901        public Spacer(final TypedArray keyAttr, final KeyStyle keyStyle,
902                final KeyboardParams params, final KeyboardRow row) {
903            super(null /* keySpec */, keyAttr, keyStyle, params, row);
904        }
905
906        /**
907         * This constructor is being used only for divider in more keys keyboard.
908         */
909        protected Spacer(final KeyboardParams params, final int x, final int y, final int width,
910                final int height) {
911            super(null /* label */, ICON_UNDEFINED, CODE_UNSPECIFIED, null /* outputText */,
912                    null /* hintLabel */, 0 /* labelFlags */, BACKGROUND_TYPE_EMPTY, x, y, width,
913                    height, params.mHorizontalGap, params.mVerticalGap);
914        }
915    }
916}
917