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