Key.java revision ba49920e8046ca592d6c914b2e3f7394bf9c281c
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_ICON_TO_BOTTOM = 0x04;
62    private static final int LABEL_FLAGS_ALIGN_LABEL_OFF_CENTER = 0x08;
63    // Font typeface specification.
64    private static final int LABEL_FLAGS_FONT_MASK = 0x30;
65    private static final int LABEL_FLAGS_FONT_NORMAL = 0x10;
66    private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x20;
67    private static final int LABEL_FLAGS_FONT_DEFAULT = 0x30;
68    // Start of key text ratio enum values
69    private static final int LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK = 0x1C0;
70    private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO = 0x40;
71    private static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80;
72    private static final int LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO = 0xC0;
73    private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO = 0x100;
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_LARGE_LABEL_RATIO:
584            return params.mLargeLabelSize;
585        case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO:
586            return params.mHintLabelSize;
587        default: // No follow key ratio flag specified.
588            return StringUtils.codePointCount(mLabel) == 1 ? params.mLetterSize : params.mLabelSize;
589        }
590    }
591
592    public final int selectTextColor(final KeyDrawParams params) {
593        if ((mLabelFlags & LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR) != 0) {
594            return params.mFunctionalTextColor;
595        }
596        return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor;
597    }
598
599    public final int selectHintTextSize(final KeyDrawParams params) {
600        if (hasHintLabel()) {
601            return params.mHintLabelSize;
602        }
603        if (hasShiftedLetterHint()) {
604            return params.mShiftedLetterHintSize;
605        }
606        return params.mHintLetterSize;
607    }
608
609    public final int selectHintTextColor(final KeyDrawParams params) {
610        if (hasHintLabel()) {
611            return params.mHintLabelColor;
612        }
613        if (hasShiftedLetterHint()) {
614            return isShiftedLetterActivated() ? params.mShiftedLetterHintActivatedColor
615                    : params.mShiftedLetterHintInactivatedColor;
616        }
617        return params.mHintLetterColor;
618    }
619
620    public final int selectMoreKeyTextSize(final KeyDrawParams params) {
621        return hasLabelsInMoreKeys() ? params.mLabelSize : params.mLetterSize;
622    }
623
624    public final String getPreviewLabel() {
625        return isShiftedLetterActivated() ? mHintLabel : mLabel;
626    }
627
628    private boolean previewHasLetterSize() {
629        return (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO) != 0
630                || StringUtils.codePointCount(getPreviewLabel()) == 1;
631    }
632
633    public final int selectPreviewTextSize(final KeyDrawParams params) {
634        if (previewHasLetterSize()) {
635            return params.mPreviewTextSize;
636        }
637        return params.mLetterSize;
638    }
639
640    public Typeface selectPreviewTypeface(final KeyDrawParams params) {
641        if (previewHasLetterSize()) {
642            return selectTypeface(params);
643        }
644        return Typeface.DEFAULT_BOLD;
645    }
646
647    public final boolean isAlignIconToBottom() {
648        return (mLabelFlags & LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM) != 0;
649    }
650
651    public final boolean isAlignLabelOffCenter() {
652        return (mLabelFlags & LABEL_FLAGS_ALIGN_LABEL_OFF_CENTER) != 0;
653    }
654
655    public final boolean hasPopupHint() {
656        return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0;
657    }
658
659    public final boolean hasShiftedLetterHint() {
660        return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0
661                && !TextUtils.isEmpty(mHintLabel);
662    }
663
664    public final boolean hasHintLabel() {
665        return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0;
666    }
667
668    public final boolean needsAutoXScale() {
669        return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0;
670    }
671
672    public final boolean needsAutoScale() {
673        return (mLabelFlags & LABEL_FLAGS_AUTO_SCALE) == LABEL_FLAGS_AUTO_SCALE;
674    }
675
676    private final boolean isShiftedLetterActivated() {
677        return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0
678                && !TextUtils.isEmpty(mHintLabel);
679    }
680
681    public final int getMoreKeysColumn() {
682        return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_MASK;
683    }
684
685    public final boolean isFixedColumnOrderMoreKeys() {
686        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER) != 0;
687    }
688
689    public final boolean hasLabelsInMoreKeys() {
690        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0;
691    }
692
693    public final int getMoreKeyLabelFlags() {
694        return hasLabelsInMoreKeys()
695                ? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO
696                : LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO;
697    }
698
699    public final boolean needsDividersInMoreKeys() {
700        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0;
701    }
702
703    public final boolean hasNoPanelAutoMoreKey() {
704        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY) != 0;
705    }
706
707    public final String getOutputText() {
708        final OptionalAttributes attrs = mOptionalAttributes;
709        return (attrs != null) ? attrs.mOutputText : null;
710    }
711
712    public final int getAltCode() {
713        final OptionalAttributes attrs = mOptionalAttributes;
714        return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED;
715    }
716
717    public int getIconId() {
718        return mIconId;
719    }
720
721    public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
722        final OptionalAttributes attrs = mOptionalAttributes;
723        final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED;
724        final int iconId = mEnabled ? getIconId() : disabledIconId;
725        final Drawable icon = iconSet.getIconDrawable(iconId);
726        if (icon != null) {
727            icon.setAlpha(alpha);
728        }
729        return icon;
730    }
731
732    public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) {
733        return iconSet.getIconDrawable(getIconId());
734    }
735
736    public int getWidth() {
737        return mWidth;
738    }
739
740    public int getHeight() {
741        return mHeight;
742    }
743
744    public int getX() {
745        return mX;
746    }
747
748    public int getY() {
749        return mY;
750    }
751
752    public final int getDrawX() {
753        final int x = getX();
754        final OptionalAttributes attrs = mOptionalAttributes;
755        return (attrs == null) ? x : x + attrs.mVisualInsetsLeft;
756    }
757
758    public final int getDrawWidth() {
759        final OptionalAttributes attrs = mOptionalAttributes;
760        return (attrs == null) ? mWidth
761                : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight;
762    }
763
764    /**
765     * Informs the key that it has been pressed, in case it needs to change its appearance or
766     * state.
767     * @see #onReleased()
768     */
769    public void onPressed() {
770        mPressed = true;
771    }
772
773    /**
774     * Informs the key that it has been released, in case it needs to change its appearance or
775     * state.
776     * @see #onPressed()
777     */
778    public void onReleased() {
779        mPressed = false;
780    }
781
782    public final boolean isEnabled() {
783        return mEnabled;
784    }
785
786    public void setEnabled(final boolean enabled) {
787        mEnabled = enabled;
788    }
789
790    public Rect getHitBox() {
791        return mHitBox;
792    }
793
794    /**
795     * Detects if a point falls on this key.
796     * @param x the x-coordinate of the point
797     * @param y the y-coordinate of the point
798     * @return whether or not the point falls on the key. If the key is attached to an edge, it
799     * will assume that all points between the key and the edge are considered to be on the key.
800     * @see #markAsLeftEdge(KeyboardParams) etc.
801     */
802    public boolean isOnKey(final int x, final int y) {
803        return mHitBox.contains(x, y);
804    }
805
806    /**
807     * Returns the square of the distance to the nearest edge of the key and the given point.
808     * @param x the x-coordinate of the point
809     * @param y the y-coordinate of the point
810     * @return the square of the distance of the point from the nearest edge of the key
811     */
812    public int squaredDistanceToEdge(final int x, final int y) {
813        final int left = getX();
814        final int right = left + mWidth;
815        final int top = getY();
816        final int bottom = top + mHeight;
817        final int edgeX = x < left ? left : (x > right ? right : x);
818        final int edgeY = y < top ? top : (y > bottom ? bottom : y);
819        final int dx = x - edgeX;
820        final int dy = y - edgeY;
821        return dx * dx + dy * dy;
822    }
823
824    static class KeyBackgroundState {
825        private final int[] mReleasedState;
826        private final int[] mPressedState;
827
828        private KeyBackgroundState(final int ... attrs) {
829            mReleasedState = attrs;
830            mPressedState = Arrays.copyOf(attrs, attrs.length + 1);
831            mPressedState[attrs.length] = android.R.attr.state_pressed;
832        }
833
834        public int[] getState(final boolean pressed) {
835            return pressed ? mPressedState : mReleasedState;
836        }
837
838        public static final KeyBackgroundState[] STATES = {
839            // 0: BACKGROUND_TYPE_EMPTY
840            new KeyBackgroundState(android.R.attr.state_empty),
841            // 1: BACKGROUND_TYPE_NORMAL
842            new KeyBackgroundState(),
843            // 2: BACKGROUND_TYPE_FUNCTIONAL
844            new KeyBackgroundState(),
845            // 3: BACKGROUND_TYPE_STICKY_OFF
846            new KeyBackgroundState(android.R.attr.state_checkable),
847            // 4: BACKGROUND_TYPE_STICKY_ON
848            new KeyBackgroundState(android.R.attr.state_checkable, android.R.attr.state_checked),
849            // 5: BACKGROUND_TYPE_ACTION
850            new KeyBackgroundState(android.R.attr.state_active),
851            // 6: BACKGROUND_TYPE_CUSTOM_ACTION
852            new KeyBackgroundState(android.R.attr.state_active, android.R.attr.state_checked)
853        };
854    }
855
856    /**
857     * Returns the background drawable for the key, based on the current state and type of the key.
858     * @return the background drawable of the key.
859     * @see android.graphics.drawable.StateListDrawable#setState(int[])
860     */
861    public final Drawable selectBackgroundDrawable(final Drawable keyBackground,
862            final Drawable functionalKeyBackground, final Drawable spacebarBackground) {
863        final Drawable background;
864        if (mBackgroundType == BACKGROUND_TYPE_FUNCTIONAL) {
865            background = functionalKeyBackground;
866        } else if (getCode() == Constants.CODE_SPACE) {
867            background = spacebarBackground;
868        } else {
869            background = keyBackground;
870        }
871        final int[] state = KeyBackgroundState.STATES[mBackgroundType].getState(mPressed);
872        background.setState(state);
873        return background;
874    }
875
876    public static class Spacer extends Key {
877        public Spacer(final TypedArray keyAttr, final KeyStyle keyStyle,
878                final KeyboardParams params, final KeyboardRow row) {
879            super(null /* keySpec */, keyAttr, keyStyle, params, row);
880        }
881
882        /**
883         * This constructor is being used only for divider in more keys keyboard.
884         */
885        protected Spacer(final KeyboardParams params, final int x, final int y, final int width,
886                final int height) {
887            super(null /* label */, ICON_UNDEFINED, CODE_UNSPECIFIED, null /* outputText */,
888                    null /* hintLabel */, 0 /* labelFlags */, BACKGROUND_TYPE_EMPTY, x, y, width,
889                    height, params.mHorizontalGap, params.mVerticalGap);
890        }
891    }
892}
893