Key.java revision 17f53103ee0d0360d8f93f6bac41ef37a0021402
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 android.content.res.Resources;
20import android.content.res.TypedArray;
21import android.graphics.Rect;
22import android.graphics.Typeface;
23import android.graphics.drawable.Drawable;
24import android.text.TextUtils;
25import android.util.Log;
26import android.util.Xml;
27
28import com.android.inputmethod.keyboard.internal.KeySpecParser;
29import com.android.inputmethod.keyboard.internal.KeyStyles;
30import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle;
31import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
32import com.android.inputmethod.latin.R;
33import com.android.inputmethod.latin.Utils;
34import com.android.inputmethod.latin.XmlParseUtils;
35
36import org.xmlpull.v1.XmlPullParser;
37import org.xmlpull.v1.XmlPullParserException;
38
39import java.util.Arrays;
40
41/**
42 * Class for describing the position and characteristics of a single key in the keyboard.
43 */
44public class Key {
45    private static final String TAG = Key.class.getSimpleName();
46
47    /**
48     * The key code (unicode or custom code) that this key generates.
49     */
50    public final int mCode;
51    public final int mAltCode;
52
53    /** Label to display */
54    public final String mLabel;
55    /** Hint label to display on the key in conjunction with the label */
56    public final String mHintLabel;
57    /** Flags of the label */
58    private final int mLabelFlags;
59    private static final int LABEL_FLAGS_ALIGN_LEFT = 0x01;
60    private static final int LABEL_FLAGS_ALIGN_RIGHT = 0x02;
61    private static final int LABEL_FLAGS_ALIGN_LEFT_OF_CENTER = 0x08;
62    private static final int LABEL_FLAGS_LARGE_LETTER = 0x10;
63    private static final int LABEL_FLAGS_FONT_NORMAL = 0x20;
64    private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x40;
65    private static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80;
66    private static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x100;
67    private static final int LABEL_FLAGS_HAS_POPUP_HINT = 0x200;
68    private static final int LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT = 0x400;
69    private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800;
70    private static final int LABEL_FLAGS_WITH_ICON_LEFT = 0x1000;
71    private static final int LABEL_FLAGS_WITH_ICON_RIGHT = 0x2000;
72    private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000;
73    private static final int LABEL_FLAGS_PRESERVE_CASE = 0x8000;
74    private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x10000;
75
76    /** Icon to display instead of a label. Icon takes precedence over a label */
77    private final int mIconId;
78    /** Icon for disabled state */
79    private final int mDisabledIconId;
80    /** Preview version of the icon, for the preview popup */
81    private final int mPreviewIconId;
82
83    /** Width of the key, not including the gap */
84    public final int mWidth;
85    /** Height of the key, not including the gap */
86    public final int mHeight;
87    /** The horizontal gap around this key */
88    public final int mHorizontalGap;
89    /** The vertical gap below this key */
90    public final int mVerticalGap;
91    /** The visual insets */
92    public final int mVisualInsetsLeft;
93    public final int mVisualInsetsRight;
94    /** X coordinate of the key in the keyboard layout */
95    public final int mX;
96    /** Y coordinate of the key in the keyboard layout */
97    public final int mY;
98    /** Hit bounding box of the key */
99    public final Rect mHitBox = new Rect();
100
101    /** Text to output when pressed. This can be multiple characters, like ".com" */
102    public final CharSequence mOutputText;
103    /** More keys */
104    public final String[] mMoreKeys;
105    /** More keys maximum column number */
106    public final int mMaxMoreKeysColumn;
107
108    /** Background type that represents different key background visual than normal one. */
109    public final int mBackgroundType;
110    public static final int BACKGROUND_TYPE_NORMAL = 0;
111    public static final int BACKGROUND_TYPE_FUNCTIONAL = 1;
112    public static final int BACKGROUND_TYPE_ACTION = 2;
113    public static final int BACKGROUND_TYPE_STICKY_OFF = 3;
114    public static final int BACKGROUND_TYPE_STICKY_ON = 4;
115
116    private final int mActionFlags;
117    private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01;
118    private static final int ACTION_FLAGS_NO_KEY_PREVIEW = 0x02;
119    private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04;
120    private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08;
121
122    private final int mHashCode;
123
124    /** The current pressed state of this key */
125    private boolean mPressed;
126    /** Key is enabled and responds on press */
127    private boolean mEnabled = true;
128
129    /**
130     * This constructor is being used only for key in more keys keyboard.
131     */
132    public Key(Resources res, Keyboard.Params params, String moreKeySpec,
133            int x, int y, int width, int height) {
134        this(params, KeySpecParser.getLabel(moreKeySpec), null,
135                KeySpecParser.getIconId(moreKeySpec),
136                KeySpecParser.getCode(res, moreKeySpec),
137                KeySpecParser.getOutputText(moreKeySpec),
138                x, y, width, height);
139    }
140
141    /**
142     * This constructor is being used only for key in popup suggestions pane.
143     */
144    public Key(Keyboard.Params params, String label, String hintLabel, int iconId,
145            int code, CharSequence outputText, int x, int y, int width, int height) {
146        mHeight = height - params.mVerticalGap;
147        mHorizontalGap = params.mHorizontalGap;
148        mVerticalGap = params.mVerticalGap;
149        mVisualInsetsLeft = mVisualInsetsRight = 0;
150        mWidth = width - mHorizontalGap;
151        mHintLabel = hintLabel;
152        mLabelFlags = 0;
153        mBackgroundType = BACKGROUND_TYPE_NORMAL;
154        mActionFlags = 0;
155        mMoreKeys = null;
156        mMaxMoreKeysColumn = 0;
157        mLabel = label;
158        mOutputText = outputText;
159        mCode = code;
160        mAltCode = Keyboard.CODE_UNSPECIFIED;
161        mIconId = iconId;
162        mDisabledIconId = KeyboardIconsSet.ICON_UNDEFINED;
163        mPreviewIconId = KeyboardIconsSet.ICON_UNDEFINED;
164        // Horizontal gap is divided equally to both sides of the key.
165        mX = x + mHorizontalGap / 2;
166        mY = y;
167        mHitBox.set(x, y, x + width + 1, y + height);
168
169        mHashCode = hashCode(this);
170    }
171
172    /**
173     * Create a key with the given top-left coordinate and extract its attributes from the XML
174     * parser.
175     * @param res resources associated with the caller's context
176     * @param params the keyboard building parameters.
177     * @param row the row that this key belongs to. row's x-coordinate will be the right edge of
178     *        this key.
179     * @param parser the XML parser containing the attributes for this key
180     * @param keyStyles active key styles set
181     * @throws XmlPullParserException
182     */
183    public Key(Resources res, Keyboard.Params params, Keyboard.Builder.Row row,
184            XmlPullParser parser, KeyStyles keyStyles) throws XmlPullParserException {
185        final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
186        final int keyHeight = row.mRowHeight;
187        mVerticalGap = params.mVerticalGap;
188        mHeight = keyHeight - mVerticalGap;
189
190        final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
191                R.styleable.Keyboard_Key);
192
193        final KeyStyle style;
194        if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyStyle)) {
195            String styleName = keyAttr.getString(R.styleable.Keyboard_Key_keyStyle);
196            style = keyStyles.getKeyStyle(styleName);
197            if (style == null)
198                throw new XmlParseUtils.ParseException(
199                        "Unknown key style: " + styleName, parser);
200        } else {
201            style = KeyStyles.getEmptyKeyStyle();
202        }
203
204        final float keyXPos = row.getKeyX(keyAttr);
205        final float keyWidth = row.getKeyWidth(keyAttr, keyXPos);
206        final int keyYPos = row.getKeyY();
207
208        // Horizontal gap is divided equally to both sides of the key.
209        mX = (int) (keyXPos + horizontalGap / 2);
210        mY = keyYPos;
211        mWidth = (int) (keyWidth - horizontalGap);
212        mHorizontalGap = (int) horizontalGap;
213        mHitBox.set((int)keyXPos, keyYPos, (int)(keyXPos + keyWidth) + 1, keyYPos + keyHeight);
214        // Update row to have current x coordinate.
215        row.setXPos(keyXPos + keyWidth);
216
217        mBackgroundType = style.getInt(keyAttr,
218                R.styleable.Keyboard_Key_backgroundType, BACKGROUND_TYPE_NORMAL);
219
220        mVisualInsetsLeft = (int) Keyboard.Builder.getDimensionOrFraction(keyAttr,
221                R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0);
222        mVisualInsetsRight = (int) Keyboard.Builder.getDimensionOrFraction(keyAttr,
223                R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0);
224        mPreviewIconId = style.getInt(keyAttr,
225                R.styleable.Keyboard_Key_keyIconPreview, KeyboardIconsSet.ICON_UNDEFINED);
226        mIconId = style.getInt(keyAttr,
227                R.styleable.Keyboard_Key_keyIcon, KeyboardIconsSet.ICON_UNDEFINED);
228        mDisabledIconId = style.getInt(keyAttr,
229                R.styleable.Keyboard_Key_keyIconDisabled, KeyboardIconsSet.ICON_UNDEFINED);
230
231        mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags);
232        final boolean preserveCase = (mLabelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0;
233        int actionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
234        final String[] additionalMoreKeys = style.getStringArray(
235                keyAttr, R.styleable.Keyboard_Key_additionalMoreKeys);
236        final String[] moreKeys = KeySpecParser.insertAddtionalMoreKeys(style.getStringArray(
237                keyAttr, R.styleable.Keyboard_Key_moreKeys), additionalMoreKeys);
238        if (moreKeys != null) {
239            actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS;
240            for (int i = 0; i < moreKeys.length; i++) {
241                moreKeys[i] = adjustCaseOfStringForKeyboardId(
242                        moreKeys[i], preserveCase, params.mId);
243            }
244        }
245        mActionFlags = actionFlags;
246        mMoreKeys = moreKeys;
247        mMaxMoreKeysColumn = style.getInt(keyAttr,
248                R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMiniKeyboardColumn);
249
250        mLabel = adjustCaseOfStringForKeyboardId(style.getString(
251                keyAttr, R.styleable.Keyboard_Key_keyLabel), preserveCase, params.mId);
252        mHintLabel = adjustCaseOfStringForKeyboardId(style.getString(
253                keyAttr, R.styleable.Keyboard_Key_keyHintLabel), preserveCase, params.mId);
254        String outputText = adjustCaseOfStringForKeyboardId(style.getString(
255                keyAttr, R.styleable.Keyboard_Key_keyOutputText), preserveCase, params.mId);
256        final int code = style.getInt(
257                keyAttr, R.styleable.Keyboard_Key_code, Keyboard.CODE_UNSPECIFIED);
258        // Choose the first letter of the label as primary code if not specified.
259        if (code == Keyboard.CODE_UNSPECIFIED && TextUtils.isEmpty(outputText)
260                && !TextUtils.isEmpty(mLabel)) {
261            if (Utils.codePointCount(mLabel) == 1) {
262                // Use the first letter of the hint label if shiftedLetterActivated flag is
263                // specified.
264                if (hasShiftedLetterHint() && isShiftedLetterActivated()
265                        && !TextUtils.isEmpty(mHintLabel)) {
266                    mCode = mHintLabel.codePointAt(0);
267                } else {
268                    mCode = mLabel.codePointAt(0);
269                }
270            } else {
271                // In some locale and case, the character might be represented by multiple code
272                // points, such as upper case Eszett of German alphabet.
273                outputText = mLabel;
274                mCode = Keyboard.CODE_OUTPUT_TEXT;
275            }
276        } else if (code == Keyboard.CODE_UNSPECIFIED && outputText != null) {
277            mCode = Keyboard.CODE_OUTPUT_TEXT;
278        } else {
279            mCode = adjustCaseOfCodeForKeyboardId(code, preserveCase, params.mId);
280        }
281        mOutputText = outputText;
282        mAltCode = adjustCaseOfCodeForKeyboardId(style.getInt(keyAttr,
283                R.styleable.Keyboard_Key_altCode, Keyboard.CODE_UNSPECIFIED), preserveCase,
284                params.mId);
285        mHashCode = hashCode(this);
286
287        keyAttr.recycle();
288
289        if (hasShiftedLetterHint() && TextUtils.isEmpty(mHintLabel)) {
290            Log.w(TAG, "hasShiftedLetterHint specified without keyHintLabel: " + this);
291        }
292    }
293
294    private static int adjustCaseOfCodeForKeyboardId(int code, boolean preserveCase,
295            KeyboardId id) {
296        if (!Keyboard.isLetterCode(code) || preserveCase) return code;
297        final String text = new String(new int[] { code } , 0, 1);
298        final String casedText = adjustCaseOfStringForKeyboardId(text, preserveCase, id);
299        return Utils.codePointCount(casedText) == 1
300                ? casedText.codePointAt(0) : Keyboard.CODE_UNSPECIFIED;
301    }
302
303    private static String adjustCaseOfStringForKeyboardId(String text, boolean preserveCase,
304            KeyboardId id) {
305        if (text == null || preserveCase) return text;
306        switch (id.mElementId) {
307        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
308        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
309        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
310        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
311            return text.toUpperCase(id.mLocale);
312        default:
313            return text;
314        }
315    }
316
317    private static int hashCode(Key key) {
318        return Arrays.hashCode(new Object[] {
319                key.mX,
320                key.mY,
321                key.mWidth,
322                key.mHeight,
323                key.mCode,
324                key.mLabel,
325                key.mHintLabel,
326                key.mIconId,
327                key.mBackgroundType,
328                // Key can be distinguishable without the following members.
329                // key.mAltCode,
330                // key.mOutputText,
331                // key.mActionFlags,
332                // key.mLabelFlags,
333                // key.mDisabledIconId,
334                // key.mPreviewIconId,
335                // key.mHorizontalGap,
336                // key.mVerticalGap,
337                // key.mVisualInsetLeft,
338                // key.mVisualInsetRight,
339                // Arrays.hashCode(key.mMoreKeys),
340                // key.mMaxMoreKeysColumn,
341        });
342    }
343
344    private boolean equals(Key o) {
345        if (this == o) return true;
346        return o.mX == mX
347                && o.mY == mY
348                && o.mWidth == mWidth
349                && o.mHeight == mHeight
350                && o.mCode == mCode
351                && TextUtils.equals(o.mLabel, mLabel)
352                && TextUtils.equals(o.mHintLabel, mHintLabel)
353                && o.mIconId == mIconId
354                && o.mBackgroundType == mBackgroundType;
355    }
356
357    @Override
358    public int hashCode() {
359        return mHashCode;
360    }
361
362    @Override
363    public boolean equals(Object o) {
364        return o instanceof Key && equals((Key)o);
365    }
366
367    @Override
368    public String toString() {
369        return String.format("%s/%s %d,%d %dx%d %s/%s/%s",
370                Keyboard.printableCode(mCode), mLabel, mX, mY, mWidth, mHeight, mHintLabel,
371                KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType));
372    }
373
374    private static String backgroundName(int backgroundType) {
375        switch (backgroundType) {
376        case BACKGROUND_TYPE_NORMAL: return "normal";
377        case BACKGROUND_TYPE_FUNCTIONAL: return "functional";
378        case BACKGROUND_TYPE_ACTION: return "action";
379        case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff";
380        case BACKGROUND_TYPE_STICKY_ON: return "stickyOn";
381        default: return null;
382        }
383    }
384
385    public void markAsLeftEdge(Keyboard.Params params) {
386        mHitBox.left = params.mHorizontalEdgesPadding;
387    }
388
389    public void markAsRightEdge(Keyboard.Params params) {
390        mHitBox.right = params.mOccupiedWidth - params.mHorizontalEdgesPadding;
391    }
392
393    public void markAsTopEdge(Keyboard.Params params) {
394        mHitBox.top = params.mTopPadding;
395    }
396
397    public void markAsBottomEdge(Keyboard.Params params) {
398        mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding;
399    }
400
401    public final boolean isSpacer() {
402        return this instanceof Spacer;
403    }
404
405    public boolean isShift() {
406        return mCode == Keyboard.CODE_SHIFT;
407    }
408
409    public boolean isModifier() {
410        return mCode == Keyboard.CODE_SHIFT || mCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL;
411    }
412
413    public boolean isRepeatable() {
414        return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0;
415    }
416
417    public boolean noKeyPreview() {
418        return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0;
419    }
420
421    public boolean altCodeWhileTyping() {
422        return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0;
423    }
424
425    public boolean isLongPressEnabled() {
426        // We need not start long press timer on the key which has activated shifted letter.
427        return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0
428                && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0;
429    }
430
431    public Typeface selectTypeface(Typeface defaultTypeface) {
432        // TODO: Handle "bold" here too?
433        if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) {
434            return Typeface.DEFAULT;
435        } else if ((mLabelFlags & LABEL_FLAGS_FONT_MONO_SPACE) != 0) {
436            return Typeface.MONOSPACE;
437        } else {
438            return defaultTypeface;
439        }
440    }
441
442    public int selectTextSize(int letter, int largeLetter, int label, int hintLabel) {
443        if (mLabel.length() > 1
444                && (mLabelFlags & (LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO
445                        | LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO)) == 0) {
446            return label;
447        } else if ((mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO) != 0) {
448            return hintLabel;
449        } else if ((mLabelFlags & LABEL_FLAGS_LARGE_LETTER) != 0) {
450            return largeLetter;
451        } else {
452            return letter;
453        }
454    }
455
456    public boolean isAlignLeft() {
457        return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0;
458    }
459
460    public boolean isAlignRight() {
461        return (mLabelFlags & LABEL_FLAGS_ALIGN_RIGHT) != 0;
462    }
463
464    public boolean isAlignLeftOfCenter() {
465        return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0;
466    }
467
468    public boolean hasPopupHint() {
469        return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0;
470    }
471
472    public boolean hasShiftedLetterHint() {
473        return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0;
474    }
475
476    public boolean hasHintLabel() {
477        return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0;
478    }
479
480    public boolean hasLabelWithIconLeft() {
481        return (mLabelFlags & LABEL_FLAGS_WITH_ICON_LEFT) != 0;
482    }
483
484    public boolean hasLabelWithIconRight() {
485        return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0;
486    }
487
488    public boolean needsXScale() {
489        return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0;
490    }
491
492    public boolean isShiftedLetterActivated() {
493        return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0;
494    }
495
496    public Drawable getIcon(KeyboardIconsSet iconSet) {
497        return iconSet.getIconDrawable(mIconId);
498    }
499
500    public Drawable getDisabledIcon(KeyboardIconsSet iconSet) {
501        return iconSet.getIconDrawable(mDisabledIconId);
502    }
503
504    public Drawable getPreviewIcon(KeyboardIconsSet iconSet) {
505        return mPreviewIconId != KeyboardIconsSet.ICON_UNDEFINED
506                ? iconSet.getIconDrawable(mPreviewIconId)
507                : iconSet.getIconDrawable(mIconId);
508    }
509
510    /**
511     * Informs the key that it has been pressed, in case it needs to change its appearance or
512     * state.
513     * @see #onReleased()
514     */
515    public void onPressed() {
516        mPressed = true;
517    }
518
519    /**
520     * Informs the key that it has been released, in case it needs to change its appearance or
521     * state.
522     * @see #onPressed()
523     */
524    public void onReleased() {
525        mPressed = false;
526    }
527
528    public boolean isEnabled() {
529        return mEnabled;
530    }
531
532    public void setEnabled(boolean enabled) {
533        mEnabled = enabled;
534    }
535
536    /**
537     * Detects if a point falls on this key.
538     * @param x the x-coordinate of the point
539     * @param y the y-coordinate of the point
540     * @return whether or not the point falls on the key. If the key is attached to an edge, it
541     * will assume that all points between the key and the edge are considered to be on the key.
542     * @see #markAsLeftEdge(Keyboard.Params) etc.
543     */
544    public boolean isOnKey(int x, int y) {
545        return mHitBox.contains(x, y);
546    }
547
548    /**
549     * Returns the square of the distance to the nearest edge of the key and the given point.
550     * @param x the x-coordinate of the point
551     * @param y the y-coordinate of the point
552     * @return the square of the distance of the point from the nearest edge of the key
553     */
554    public int squaredDistanceToEdge(int x, int y) {
555        final int left = mX;
556        final int right = left + mWidth;
557        final int top = mY;
558        final int bottom = top + mHeight;
559        final int edgeX = x < left ? left : (x > right ? right : x);
560        final int edgeY = y < top ? top : (y > bottom ? bottom : y);
561        final int dx = x - edgeX;
562        final int dy = y - edgeY;
563        return dx * dx + dy * dy;
564    }
565
566    private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_ON = {
567        android.R.attr.state_checkable,
568        android.R.attr.state_checked
569    };
570
571    private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_ON = {
572        android.R.attr.state_pressed,
573        android.R.attr.state_checkable,
574        android.R.attr.state_checked
575    };
576
577    private final static int[] KEY_STATE_NORMAL_HIGHLIGHT_OFF = {
578        android.R.attr.state_checkable
579    };
580
581    private final static int[] KEY_STATE_PRESSED_HIGHLIGHT_OFF = {
582        android.R.attr.state_pressed,
583        android.R.attr.state_checkable
584    };
585
586    private final static int[] KEY_STATE_NORMAL = {
587    };
588
589    private final static int[] KEY_STATE_PRESSED = {
590        android.R.attr.state_pressed
591    };
592
593    // functional normal state (with properties)
594    private static final int[] KEY_STATE_FUNCTIONAL_NORMAL = {
595            android.R.attr.state_single
596    };
597
598    // functional pressed state (with properties)
599    private static final int[] KEY_STATE_FUNCTIONAL_PRESSED = {
600            android.R.attr.state_single,
601            android.R.attr.state_pressed
602    };
603
604    // action normal state (with properties)
605    private static final int[] KEY_STATE_ACTIVE_NORMAL = {
606            android.R.attr.state_active
607    };
608
609    // action pressed state (with properties)
610    private static final int[] KEY_STATE_ACTIVE_PRESSED = {
611            android.R.attr.state_active,
612            android.R.attr.state_pressed
613    };
614
615    /**
616     * Returns the drawable state for the key, based on the current state and type of the key.
617     * @return the drawable state of the key.
618     * @see android.graphics.drawable.StateListDrawable#setState(int[])
619     */
620    public int[] getCurrentDrawableState() {
621        switch (mBackgroundType) {
622        case BACKGROUND_TYPE_FUNCTIONAL:
623            return mPressed ? KEY_STATE_FUNCTIONAL_PRESSED : KEY_STATE_FUNCTIONAL_NORMAL;
624        case BACKGROUND_TYPE_ACTION:
625            return mPressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL;
626        case BACKGROUND_TYPE_STICKY_OFF:
627            return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF;
628        case BACKGROUND_TYPE_STICKY_ON:
629            return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON;
630        default: /* BACKGROUND_TYPE_NORMAL */
631            return mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL;
632        }
633    }
634
635    public static class Spacer extends Key {
636        public Spacer(Resources res, Keyboard.Params params, Keyboard.Builder.Row row,
637                XmlPullParser parser, KeyStyles keyStyles) throws XmlPullParserException {
638            super(res, params, row, parser, keyStyles);
639        }
640
641        /**
642         * This constructor is being used only for divider in more keys keyboard.
643         */
644        protected Spacer(Keyboard.Params params, int x, int y, int width, int height) {
645            super(params, null, null, KeyboardIconsSet.ICON_UNDEFINED, Keyboard.CODE_UNSPECIFIED,
646                    null, x, y, width, height);
647        }
648    }
649}
650