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