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