From 35ff94547c16c84c5b6fafdae0b4a683be782b97 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Thu, 30 Aug 2012 14:22:40 +0900 Subject: [PATCH] Separate inner classes of keyboard package out under internal package Change-Id: Ia3969bd5ddec5aa5d81d05ad4cf676d818587922 --- .../com/android/inputmethod/keyboard/Key.java | 62 +- .../inputmethod/keyboard/Keyboard.java | 1122 +---------------- .../keyboard/KeyboardLayoutSet.java | 67 +- .../keyboard/MoreKeysKeyboard.java | 467 +++---- .../inputmethod/keyboard/ProximityInfo.java | 11 +- .../keyboard/internal/KeySpecParser.java | 92 +- .../keyboard/internal/KeyStyle.java | 46 + .../{KeyStyles.java => KeyStylesSet.java} | 80 +- .../keyboard/internal/KeyboardBuilder.java | 829 ++++++++++++ .../keyboard/internal/KeyboardParams.java | 143 +++ .../keyboard/internal/KeyboardRow.java | 154 +++ .../keyboard/internal/KeysCache.java | 40 + .../keyboard/internal/MoreKeySpec.java | 61 + .../internal/TouchPositionCorrection.java | 76 ++ .../latin/suggestions/MoreSuggestions.java | 298 ++--- ...oreKeysKeyboardBuilderFixedOrderTests.java | 2 +- .../MoreKeysKeyboardBuilderTests.java | 2 +- .../keyboard/internal/KeySpecParserTests.java | 1 - 18 files changed, 1875 insertions(+), 1678 deletions(-) create mode 100644 java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java rename java/src/com/android/inputmethod/keyboard/internal/{KeyStyles.java => KeyStylesSet.java} (73%) create mode 100644 java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java create mode 100644 java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java create mode 100644 java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java create mode 100644 java/src/com/android/inputmethod/keyboard/internal/KeysCache.java create mode 100644 java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java create mode 100644 java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index d18be4c71..e941cc7fc 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -32,9 +32,11 @@ import android.util.Log; import android.util.Xml; import com.android.inputmethod.keyboard.internal.KeySpecParser; -import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec; -import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle; +import com.android.inputmethod.keyboard.internal.KeyStyle; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; +import com.android.inputmethod.keyboard.internal.KeyboardParams; +import com.android.inputmethod.keyboard.internal.KeyboardRow; +import com.android.inputmethod.keyboard.internal.MoreKeySpec; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.ResourceUtils; import com.android.inputmethod.latin.StringUtils; @@ -166,8 +168,8 @@ public class Key { /** * This constructor is being used only for keys in more keys keyboard. */ - public Key(Keyboard.Params params, MoreKeySpec moreKeySpec, int x, int y, int width, int height, - int labelFlags) { + public Key(final KeyboardParams params, final MoreKeySpec moreKeySpec, final int x, final int y, + final int width, final int height, final int labelFlags) { this(params, moreKeySpec.mLabel, null, moreKeySpec.mIconId, moreKeySpec.mCode, moreKeySpec.mOutputText, x, y, width, height, labelFlags); } @@ -175,8 +177,9 @@ public class Key { /** * This constructor is being used only for key in popup suggestions pane. */ - public Key(Keyboard.Params params, String label, String hintLabel, int iconId, - int code, String outputText, int x, int y, int width, int height, int labelFlags) { + public Key(final KeyboardParams params, final String label, final String hintLabel, + final int iconId, final int code, final String outputText, final int x, final int y, + final int width, final int height, final int labelFlags) { mHeight = height - params.mVerticalGap; mWidth = width - params.mHorizontalGap; mHintLabel = hintLabel; @@ -213,8 +216,8 @@ public class Key { * @param parser the XML parser containing the attributes for this key * @throws XmlPullParserException */ - public Key(Resources res, Keyboard.Params params, Keyboard.Builder.Row row, - XmlPullParser parser) throws XmlPullParserException { + public Key(final Resources res, final KeyboardParams params, final KeyboardRow row, + final XmlPullParser parser) throws XmlPullParserException { final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap; final int keyHeight = row.mRowHeight; mHeight = keyHeight - params.mVerticalGap; @@ -364,7 +367,7 @@ public class Key { } } - private static boolean needsToUpperCase(int labelFlags, int keyboardElementId) { + private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) { if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false; switch (keyboardElementId) { case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: @@ -377,7 +380,7 @@ public class Key { } } - private static int computeHashCode(Key key) { + private static int computeHashCode(final Key key) { return Arrays.hashCode(new Object[] { key.mX, key.mY, @@ -404,7 +407,7 @@ public class Key { }); } - private boolean equals(Key o) { + private boolean equals(final Key o) { if (this == o) return true; return o.mX == mX && o.mY == mY @@ -427,7 +430,7 @@ public class Key { } @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { return o instanceof Key && equals((Key)o); } @@ -444,7 +447,7 @@ public class Key { KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType)); } - private static String backgroundName(int backgroundType) { + private static String backgroundName(final int backgroundType) { switch (backgroundType) { case BACKGROUND_TYPE_NORMAL: return "normal"; case BACKGROUND_TYPE_FUNCTIONAL: return "functional"; @@ -455,19 +458,19 @@ public class Key { } } - public void markAsLeftEdge(Keyboard.Params params) { + public void markAsLeftEdge(final KeyboardParams params) { mHitBox.left = params.mHorizontalEdgesPadding; } - public void markAsRightEdge(Keyboard.Params params) { + public void markAsRightEdge(final KeyboardParams params) { mHitBox.right = params.mOccupiedWidth - params.mHorizontalEdgesPadding; } - public void markAsTopEdge(Keyboard.Params params) { + public void markAsTopEdge(final KeyboardParams params) { mHitBox.top = params.mTopPadding; } - public void markAsBottomEdge(Keyboard.Params params) { + public void markAsBottomEdge(final KeyboardParams params) { mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding; } @@ -501,7 +504,7 @@ public class Key { && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0; } - public Typeface selectTypeface(Typeface defaultTypeface) { + public Typeface selectTypeface(final Typeface defaultTypeface) { // TODO: Handle "bold" here too? if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) { return Typeface.DEFAULT; @@ -512,8 +515,8 @@ public class Key { } } - public int selectTextSize(int letterSize, int largeLetterSize, int labelSize, - int largeLabelSize, int hintLabelSize) { + public int selectTextSize(final int letterSize, final int largeLetterSize, final int labelSize, + final int largeLabelSize, final int hintLabelSize) { switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) { case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO: return letterSize; @@ -606,7 +609,7 @@ public class Key { return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED; } - public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) { + public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { final OptionalAttributes attrs = mOptionalAttributes; final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED; final int iconId = mEnabled ? mIconId : disabledIconId; @@ -617,7 +620,7 @@ public class Key { return icon; } - public Drawable getPreviewIcon(KeyboardIconsSet iconSet) { + public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) { final OptionalAttributes attrs = mOptionalAttributes; final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED; return previewIconId != ICON_UNDEFINED @@ -657,7 +660,7 @@ public class Key { return mEnabled; } - public void setEnabled(boolean enabled) { + public void setEnabled(final boolean enabled) { mEnabled = enabled; } @@ -667,9 +670,9 @@ public class Key { * @param y the y-coordinate of the point * @return whether or not the point falls on the key. If the key is attached to an edge, it * will assume that all points between the key and the edge are considered to be on the key. - * @see #markAsLeftEdge(Keyboard.Params) etc. + * @see #markAsLeftEdge(KeyboardParams) etc. */ - public boolean isOnKey(int x, int y) { + public boolean isOnKey(final int x, final int y) { return mHitBox.contains(x, y); } @@ -679,7 +682,7 @@ public class Key { * @param y the y-coordinate of the point * @return the square of the distance of the point from the nearest edge of the key */ - public int squaredDistanceToEdge(int x, int y) { + public int squaredDistanceToEdge(final int x, final int y) { final int left = mX; final int right = left + mWidth; final int top = mY; @@ -761,15 +764,16 @@ public class Key { } public static class Spacer extends Key { - public Spacer(Resources res, Keyboard.Params params, Keyboard.Builder.Row row, - XmlPullParser parser) throws XmlPullParserException { + public Spacer(final Resources res, final KeyboardParams params, final KeyboardRow row, + final XmlPullParser parser) throws XmlPullParserException { super(res, params, row, parser); } /** * This constructor is being used only for divider in more keys keyboard. */ - protected Spacer(Keyboard.Params params, int x, int y, int width, int height) { + protected Spacer(final KeyboardParams params, final int x, final int y, final int width, + final int height) { super(params, null, null, ICON_UNDEFINED, CODE_UNSPECIFIED, null, x, y, width, height, 0); } diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index b8e5e9523..c1b007d2e 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -16,41 +16,15 @@ package com.android.inputmethod.keyboard; -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; import android.graphics.Typeface; -import android.util.AttributeSet; -import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; -import android.util.SparseIntArray; -import android.util.TypedValue; -import android.util.Xml; -import android.view.InflateException; -import com.android.inputmethod.keyboard.internal.KeyStyles; -import com.android.inputmethod.keyboard.internal.KeyboardCodesSet; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; -import com.android.inputmethod.keyboard.internal.KeyboardTextsSet; +import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.latin.CollectionUtils; -import com.android.inputmethod.latin.LatinImeLogger; -import com.android.inputmethod.latin.LocaleUtils.RunInLocale; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResourceUtils; -import com.android.inputmethod.latin.StringUtils; -import com.android.inputmethod.latin.SubtypeLocale; -import com.android.inputmethod.latin.XmlParseUtils; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Locale; /** * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard @@ -149,7 +123,7 @@ public class Keyboard { private final ProximityInfo mProximityInfo; private final boolean mProximityCharsCorrectionEnabled; - public Keyboard(Params params) { + public Keyboard(final KeyboardParams params) { mId = params.mId; mThemeId = params.mThemeId; mOccupiedHeight = params.mOccupiedHeight; @@ -180,7 +154,7 @@ public class Keyboard { mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled; } - public boolean hasProximityCharsCorrection(int code) { + public boolean hasProximityCharsCorrection(final int code) { if (!mProximityCharsCorrectionEnabled) { return false; } @@ -196,7 +170,7 @@ public class Keyboard { return mProximityInfo; } - public Key getKey(int code) { + public Key getKey(final int code) { if (code == CODE_UNSPECIFIED) { return null; } @@ -217,7 +191,7 @@ public class Keyboard { } } - public boolean hasKey(Key aKey) { + public boolean hasKey(final Key aKey) { if (mKeyCache.indexOfValue(aKey) >= 0) { return true; } @@ -231,7 +205,7 @@ public class Keyboard { return false; } - public static boolean isLetterCode(int code) { + public static boolean isLetterCode(final int code) { return code >= CODE_SPACE; } @@ -240,178 +214,6 @@ public class Keyboard { return mId.toString(); } - // TODO: Move this class to internal package - public static class Params { - public KeyboardId mId; - public int mThemeId; - - /** Total height and width of the keyboard, including the paddings and keys */ - public int mOccupiedHeight; - public int mOccupiedWidth; - - /** Base height and width of the keyboard used to calculate rows' or keys' heights and - * widths - */ - public int mBaseHeight; - public int mBaseWidth; - - public int mTopPadding; - public int mBottomPadding; - public int mHorizontalEdgesPadding; - public int mHorizontalCenterPadding; - - public Typeface mKeyTypeface = null; - public float mKeyLetterRatio = ResourceUtils.UNDEFINED_RATIO; - public int mKeyLetterSize = ResourceUtils.UNDEFINED_DIMENSION; - public float mKeyHintLetterRatio = ResourceUtils.UNDEFINED_RATIO;; - public float mKeyShiftedLetterHintRatio = ResourceUtils.UNDEFINED_RATIO; - - public int mDefaultRowHeight; - public int mDefaultKeyWidth; - public int mHorizontalGap; - public int mVerticalGap; - - public int mMoreKeysTemplate; - public int mMaxMoreKeysKeyboardColumn; - - public int GRID_WIDTH; - public int GRID_HEIGHT; - - public final HashSet mKeys = CollectionUtils.newHashSet(); - public final ArrayList mShiftKeys = CollectionUtils.newArrayList(); - public final ArrayList mAltCodeKeysWhileTyping = CollectionUtils.newArrayList(); - public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet(); - public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet(); - public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); - public final KeyStyles mKeyStyles = new KeyStyles(mTextsSet); - - public KeyboardLayoutSet.KeysCache mKeysCache; - - public int mMostCommonKeyHeight = 0; - public int mMostCommonKeyWidth = 0; - - public boolean mProximityCharsCorrectionEnabled; - - public final TouchPositionCorrection mTouchPositionCorrection = - new TouchPositionCorrection(); - - public static class TouchPositionCorrection { - private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3; - - public boolean mEnabled; - public float[] mXs; - public float[] mYs; - public float[] mRadii; - - public void load(String[] data) { - final int dataLength = data.length; - if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) { - if (LatinImeLogger.sDBG) { - throw new RuntimeException( - "the size of touch position correction data is invalid"); - } - return; - } - - final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE; - mXs = new float[length]; - mYs = new float[length]; - mRadii = new float[length]; - try { - for (int i = 0; i < dataLength; ++i) { - final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE; - final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE; - final float value = Float.parseFloat(data[i]); - if (type == 0) { - mXs[index] = value; - } else if (type == 1) { - mYs[index] = value; - } else { - mRadii[index] = value; - } - } - } catch (NumberFormatException e) { - if (LatinImeLogger.sDBG) { - throw new RuntimeException( - "the number format for touch position correction data is invalid"); - } - mXs = null; - mYs = null; - mRadii = null; - } - } - - // TODO: Remove this method. - public void setEnabled(boolean enabled) { - mEnabled = enabled; - } - - public boolean isValid() { - return mEnabled && mXs != null && mYs != null && mRadii != null - && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0; - } - } - - protected void clearKeys() { - mKeys.clear(); - mShiftKeys.clear(); - clearHistogram(); - } - - public void onAddKey(Key newKey) { - final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey; - final boolean zeroWidthSpacer = key.isSpacer() && key.mWidth == 0; - if (!zeroWidthSpacer) { - mKeys.add(key); - updateHistogram(key); - } - if (key.mCode == Keyboard.CODE_SHIFT) { - mShiftKeys.add(key); - } - if (key.altCodeWhileTyping()) { - mAltCodeKeysWhileTyping.add(key); - } - } - - private int mMaxHeightCount = 0; - private int mMaxWidthCount = 0; - private final SparseIntArray mHeightHistogram = new SparseIntArray(); - private final SparseIntArray mWidthHistogram = new SparseIntArray(); - - private void clearHistogram() { - mMostCommonKeyHeight = 0; - mMaxHeightCount = 0; - mHeightHistogram.clear(); - - mMaxWidthCount = 0; - mMostCommonKeyWidth = 0; - mWidthHistogram.clear(); - } - - private static int updateHistogramCounter(SparseIntArray histogram, int key) { - final int index = histogram.indexOfKey(key); - final int count = (index >= 0 ? histogram.get(key) : 0) + 1; - histogram.put(key, count); - return count; - } - - private void updateHistogram(Key key) { - final int height = key.mHeight + mVerticalGap; - final int heightCount = updateHistogramCounter(mHeightHistogram, height); - if (heightCount > mMaxHeightCount) { - mMaxHeightCount = heightCount; - mMostCommonKeyHeight = height; - } - - final int width = key.mWidth + mHorizontalGap; - final int widthCount = updateHistogramCounter(mWidthHistogram, width); - if (widthCount > mMaxWidthCount) { - mMaxWidthCount = widthCount; - mMostCommonKeyWidth = width; - } - } - } - /** * Returns the array of the keys that are closest to the given point. * @param x the x-coordinate of the point @@ -419,14 +221,14 @@ public class Keyboard { * @return the array of the nearest keys to the given point. If the given * point is out of range, then an array of size zero is returned. */ - public Key[] getNearestKeys(int x, int y) { + public Key[] getNearestKeys(final int x, final int y) { // Avoid dead pixels at edges of the keyboard final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1)); final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1)); return mProximityInfo.getNearestKeys(adjustedX, adjustedY); } - public static String printableCode(int code) { + public static String printableCode(final int code) { switch (code) { case CODE_SHIFT: return "shift"; case CODE_SWITCH_ALPHA_SYMBOL: return "symbol"; @@ -448,912 +250,4 @@ public class Keyboard { return String.format("'\\u%04x'", code); } } - - /** - * Keyboard Building helper. - * - * This class parses Keyboard XML file and eventually build a Keyboard. - * The Keyboard XML file looks like: - *
-     *   <!-- xml/keyboard.xml -->
-     *   <Keyboard keyboard_attributes*>
-     *     <!-- Keyboard Content -->
-     *     <Row row_attributes*>
-     *       <!-- Row Content -->
-     *       <Key key_attributes* />
-     *       <Spacer horizontalGap="32.0dp" />
-     *       <include keyboardLayout="@xml/other_keys">
-     *       ...
-     *     </Row>
-     *     <include keyboardLayout="@xml/other_rows">
-     *     ...
-     *   </Keyboard>
-     * 
- * The XML file which is included in other file must have <merge> as root element, - * such as: - *
-     *   <!-- xml/other_keys.xml -->
-     *   <merge>
-     *     <Key key_attributes* />
-     *     ...
-     *   </merge>
-     * 
- * and - *
-     *   <!-- xml/other_rows.xml -->
-     *   <merge>
-     *     <Row row_attributes*>
-     *       <Key key_attributes* />
-     *     </Row>
-     *     ...
-     *   </merge>
-     * 
- * You can also use switch-case-default tags to select Rows and Keys. - *
-     *   <switch>
-     *     <case case_attribute*>
-     *       <!-- Any valid tags at switch position -->
-     *     </case>
-     *     ...
-     *     <default>
-     *       <!-- Any valid tags at switch position -->
-     *     </default>
-     *   </switch>
-     * 
- * You can declare Key style and specify styles within Key tags. - *
-     *     <switch>
-     *       <case mode="email">
-     *         <key-style styleName="f1-key" parentStyle="modifier-key"
-     *           keyLabel=".com"
-     *         />
-     *       </case>
-     *       <case mode="url">
-     *         <key-style styleName="f1-key" parentStyle="modifier-key"
-     *           keyLabel="http://"
-     *         />
-     *       </case>
-     *     </switch>
-     *     ...
-     *     <Key keyStyle="shift-key" ... />
-     * 
- */ - - // TODO: Move this class to internal package. - public static class Builder { - private static final String BUILDER_TAG = "Keyboard.Builder"; - private static final boolean DEBUG = false; - - // Keyboard XML Tags - private static final String TAG_KEYBOARD = "Keyboard"; - private static final String TAG_ROW = "Row"; - private static final String TAG_KEY = "Key"; - private static final String TAG_SPACER = "Spacer"; - private static final String TAG_INCLUDE = "include"; - private static final String TAG_MERGE = "merge"; - private static final String TAG_SWITCH = "switch"; - private static final String TAG_CASE = "case"; - private static final String TAG_DEFAULT = "default"; - public static final String TAG_KEY_STYLE = "key-style"; - - private static final int DEFAULT_KEYBOARD_COLUMNS = 10; - private static final int DEFAULT_KEYBOARD_ROWS = 4; - - protected final KP mParams; - protected final Context mContext; - protected final Resources mResources; - private final DisplayMetrics mDisplayMetrics; - - private int mCurrentY = 0; - private Row mCurrentRow = null; - private boolean mLeftEdge; - private boolean mTopEdge; - private Key mRightEdgeKey = null; - - /** - * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate. - * Some of the key size defaults can be overridden per row from what the {@link Keyboard} - * defines. - */ - public static class Row { - // keyWidth enum constants - private static final int KEYWIDTH_NOT_ENUM = 0; - private static final int KEYWIDTH_FILL_RIGHT = -1; - - private final Params mParams; - /** Default width of a key in this row. */ - private float mDefaultKeyWidth; - /** Default height of a key in this row. */ - public final int mRowHeight; - /** Default keyLabelFlags in this row. */ - private int mDefaultKeyLabelFlags; - /** Default backgroundType for this row */ - private int mDefaultBackgroundType; - - private final int mCurrentY; - // Will be updated by {@link Key}'s constructor. - private float mCurrentX; - - public Row(Resources res, Params params, XmlPullParser parser, int y) { - mParams = params; - TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.Keyboard); - mRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_rowHeight, - params.mBaseHeight, params.mDefaultRowHeight); - keyboardAttr.recycle(); - TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.Keyboard_Key); - mDefaultKeyWidth = ResourceUtils.getDimensionOrFraction(keyAttr, - R.styleable.Keyboard_Key_keyWidth, - params.mBaseWidth, params.mDefaultKeyWidth); - mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType, - Key.BACKGROUND_TYPE_NORMAL); - keyAttr.recycle(); - - // TODO: Initialize this with attribute as backgroundType is done. - mDefaultKeyLabelFlags = 0; - mCurrentY = y; - mCurrentX = 0.0f; - } - - public float getDefaultKeyWidth() { - return mDefaultKeyWidth; - } - - public void setDefaultKeyWidth(float defaultKeyWidth) { - mDefaultKeyWidth = defaultKeyWidth; - } - - public int getDefaultKeyLabelFlags() { - return mDefaultKeyLabelFlags; - } - - public void setDefaultKeyLabelFlags(int keyLabelFlags) { - mDefaultKeyLabelFlags = keyLabelFlags; - } - - public int getDefaultBackgroundType() { - return mDefaultBackgroundType; - } - - public void setDefaultBackgroundType(int backgroundType) { - mDefaultBackgroundType = backgroundType; - } - - public void setXPos(float keyXPos) { - mCurrentX = keyXPos; - } - - public void advanceXPos(float width) { - mCurrentX += width; - } - - public int getKeyY() { - return mCurrentY; - } - - public float getKeyX(TypedArray keyAttr) { - final int keyboardRightEdge = mParams.mOccupiedWidth - - mParams.mHorizontalEdgesPadding; - if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) { - final float keyXPos = ResourceUtils.getDimensionOrFraction(keyAttr, - R.styleable.Keyboard_Key_keyXPos, mParams.mBaseWidth, 0); - if (keyXPos < 0) { - // If keyXPos is negative, the actual x-coordinate will be - // keyboardWidth + keyXPos. - // keyXPos shouldn't be less than mCurrentX because drawable area for this - // key starts at mCurrentX. Or, this key will overlaps the adjacent key on - // its left hand side. - return Math.max(keyXPos + keyboardRightEdge, mCurrentX); - } else { - return keyXPos + mParams.mHorizontalEdgesPadding; - } - } - return mCurrentX; - } - - public float getKeyWidth(TypedArray keyAttr) { - return getKeyWidth(keyAttr, mCurrentX); - } - - public float getKeyWidth(TypedArray keyAttr, float keyXPos) { - final int widthType = ResourceUtils.getEnumValue(keyAttr, - R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM); - switch (widthType) { - case KEYWIDTH_FILL_RIGHT: - final int keyboardRightEdge = - mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding; - // If keyWidth is fillRight, the actual key width will be determined to fill - // out the area up to the right edge of the keyboard. - return keyboardRightEdge - keyXPos; - default: // KEYWIDTH_NOT_ENUM - return ResourceUtils.getDimensionOrFraction(keyAttr, - R.styleable.Keyboard_Key_keyWidth, - mParams.mBaseWidth, mDefaultKeyWidth); - } - } - } - - public Builder(Context context, KP params) { - mContext = context; - final Resources res = context.getResources(); - mResources = res; - mDisplayMetrics = res.getDisplayMetrics(); - - mParams = params; - - params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width); - params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height); - } - - public void setAutoGenerate(KeyboardLayoutSet.KeysCache keysCache) { - mParams.mKeysCache = keysCache; - } - - public Builder load(int xmlId, KeyboardId id) { - mParams.mId = id; - final XmlResourceParser parser = mResources.getXml(xmlId); - try { - parseKeyboard(parser); - } catch (XmlPullParserException e) { - Log.w(BUILDER_TAG, "keyboard XML parse error: " + e); - throw new IllegalArgumentException(e); - } catch (IOException e) { - Log.w(BUILDER_TAG, "keyboard XML parse error: " + e); - throw new RuntimeException(e); - } finally { - parser.close(); - } - return this; - } - - // TODO: Remove this method. - public void setTouchPositionCorrectionEnabled(boolean enabled) { - mParams.mTouchPositionCorrection.setEnabled(enabled); - } - - public void setProximityCharsCorrectionEnabled(boolean enabled) { - mParams.mProximityCharsCorrectionEnabled = enabled; - } - - public Keyboard build() { - return new Keyboard(mParams); - } - - private int mIndent; - private static final String SPACES = " "; - - private static String spaces(int count) { - return (count < SPACES.length()) ? SPACES.substring(0, count) : SPACES; - } - - private void startTag(String format, Object ... args) { - Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args)); - } - - private void endTag(String format, Object ... args) { - Log.d(BUILDER_TAG, String.format(spaces(mIndent-- * 2) + format, args)); - } - - private void startEndTag(String format, Object ... args) { - Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args)); - mIndent--; - } - - private void parseKeyboard(XmlPullParser parser) - throws XmlPullParserException, IOException { - if (DEBUG) startTag("<%s> %s", TAG_KEYBOARD, mParams.mId); - int event; - while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { - if (event == XmlPullParser.START_TAG) { - final String tag = parser.getName(); - if (TAG_KEYBOARD.equals(tag)) { - parseKeyboardAttributes(parser); - startKeyboard(); - parseKeyboardContent(parser, false); - break; - } else { - throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD); - } - } - } - } - - private void parseKeyboardAttributes(XmlPullParser parser) { - final int displayWidth = mDisplayMetrics.widthPixels; - final TypedArray keyboardAttr = mContext.obtainStyledAttributes( - Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle, - R.style.Keyboard); - final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.Keyboard_Key); - final TypedArray keyboardViewAttr = mResources.obtainAttributes( - Xml.asAttributeSet(parser), R.styleable.KeyboardView); - try { - final int displayHeight = mDisplayMetrics.heightPixels; - final String keyboardHeightString = ResourceUtils.getDeviceOverrideValue( - mResources, R.array.keyboard_heights, null); - final float keyboardHeight; - if (keyboardHeightString != null) { - keyboardHeight = Float.parseFloat(keyboardHeightString) - * mDisplayMetrics.density; - } else { - keyboardHeight = keyboardAttr.getDimension( - R.styleable.Keyboard_keyboardHeight, displayHeight / 2); - } - final float maxKeyboardHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2); - float minKeyboardHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_minKeyboardHeight, displayHeight, displayHeight / 2); - if (minKeyboardHeight < 0) { - // Specified fraction was negative, so it should be calculated against display - // width. - minKeyboardHeight = -ResourceUtils.getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2); - } - final Params params = mParams; - // Keyboard height will not exceed maxKeyboardHeight and will not be less than - // minKeyboardHeight. - params.mOccupiedHeight = (int)Math.max( - Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight); - params.mOccupiedWidth = params.mId.mWidth; - params.mTopPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0); - params.mBottomPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0); - params.mHorizontalEdgesPadding = (int)ResourceUtils.getDimensionOrFraction( - keyboardAttr, - R.styleable.Keyboard_keyboardHorizontalEdgesPadding, - mParams.mOccupiedWidth, 0); - - params.mBaseWidth = params.mOccupiedWidth - params.mHorizontalEdgesPadding * 2 - - params.mHorizontalCenterPadding; - params.mDefaultKeyWidth = (int)ResourceUtils.getDimensionOrFraction(keyAttr, - R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth, - params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS); - params.mHorizontalGap = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0); - params.mVerticalGap = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_verticalGap, params.mOccupiedHeight, 0); - params.mBaseHeight = params.mOccupiedHeight - params.mTopPadding - - params.mBottomPadding + params.mVerticalGap; - params.mDefaultRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_rowHeight, params.mBaseHeight, - params.mBaseHeight / DEFAULT_KEYBOARD_ROWS); - - if (keyboardViewAttr.hasValue(R.styleable.KeyboardView_keyTypeface)) { - params.mKeyTypeface = Typeface.defaultFromStyle(keyboardViewAttr.getInt( - R.styleable.KeyboardView_keyTypeface, Typeface.NORMAL)); - } - params.mKeyLetterRatio = ResourceUtils.getFraction(keyboardViewAttr, - R.styleable.KeyboardView_keyLetterSize); - params.mKeyLetterSize = ResourceUtils.getDimensionPixelSize(keyboardViewAttr, - R.styleable.KeyboardView_keyLetterSize); - params.mKeyHintLetterRatio = ResourceUtils.getFraction(keyboardViewAttr, - R.styleable.KeyboardView_keyHintLetterRatio); - params.mKeyShiftedLetterHintRatio = ResourceUtils.getFraction(keyboardViewAttr, - R.styleable.KeyboardView_keyShiftedLetterHintRatio); - - params.mMoreKeysTemplate = keyboardAttr.getResourceId( - R.styleable.Keyboard_moreKeysTemplate, 0); - params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt( - R.styleable.Keyboard_Key_maxMoreKeysColumn, 5); - - params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0); - params.mIconsSet.loadIcons(keyboardAttr); - final String language = params.mId.mLocale.getLanguage(); - params.mCodesSet.setLanguage(language); - params.mTextsSet.setLanguage(language); - final RunInLocale job = new RunInLocale() { - @Override - protected Void job(Resources res) { - params.mTextsSet.loadStringResources(mContext); - return null; - } - }; - // Null means the current system locale. - final Locale locale = SubtypeLocale.isNoLanguage(params.mId.mSubtype) - ? null : params.mId.mLocale; - job.runInLocale(mResources, locale); - - final int resourceId = keyboardAttr.getResourceId( - R.styleable.Keyboard_touchPositionCorrectionData, 0); - params.mTouchPositionCorrection.setEnabled(resourceId != 0); - if (resourceId != 0) { - final String[] data = mResources.getStringArray(resourceId); - params.mTouchPositionCorrection.load(data); - } - } finally { - keyboardViewAttr.recycle(); - keyAttr.recycle(); - keyboardAttr.recycle(); - } - } - - private void parseKeyboardContent(XmlPullParser parser, boolean skip) - throws XmlPullParserException, IOException { - int event; - while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { - if (event == XmlPullParser.START_TAG) { - final String tag = parser.getName(); - if (TAG_ROW.equals(tag)) { - Row row = parseRowAttributes(parser); - if (DEBUG) startTag("<%s>%s", TAG_ROW, skip ? " skipped" : ""); - if (!skip) { - startRow(row); - } - parseRowContent(parser, row, skip); - } else if (TAG_INCLUDE.equals(tag)) { - parseIncludeKeyboardContent(parser, skip); - } else if (TAG_SWITCH.equals(tag)) { - parseSwitchKeyboardContent(parser, skip); - } else if (TAG_KEY_STYLE.equals(tag)) { - parseKeyStyle(parser, skip); - } else { - throw new XmlParseUtils.IllegalStartTag(parser, TAG_ROW); - } - } else if (event == XmlPullParser.END_TAG) { - final String tag = parser.getName(); - if (DEBUG) endTag("", tag); - if (TAG_KEYBOARD.equals(tag)) { - endKeyboard(); - break; - } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) - || TAG_MERGE.equals(tag)) { - break; - } else { - throw new XmlParseUtils.IllegalEndTag(parser, TAG_ROW); - } - } - } - } - - private Row parseRowAttributes(XmlPullParser parser) throws XmlPullParserException { - final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.Keyboard); - try { - if (a.hasValue(R.styleable.Keyboard_horizontalGap)) { - throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap"); - } - if (a.hasValue(R.styleable.Keyboard_verticalGap)) { - throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap"); - } - return new Row(mResources, mParams, parser, mCurrentY); - } finally { - a.recycle(); - } - } - - private void parseRowContent(XmlPullParser parser, Row row, boolean skip) - throws XmlPullParserException, IOException { - int event; - while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { - if (event == XmlPullParser.START_TAG) { - final String tag = parser.getName(); - if (TAG_KEY.equals(tag)) { - parseKey(parser, row, skip); - } else if (TAG_SPACER.equals(tag)) { - parseSpacer(parser, row, skip); - } else if (TAG_INCLUDE.equals(tag)) { - parseIncludeRowContent(parser, row, skip); - } else if (TAG_SWITCH.equals(tag)) { - parseSwitchRowContent(parser, row, skip); - } else if (TAG_KEY_STYLE.equals(tag)) { - parseKeyStyle(parser, skip); - } else { - throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY); - } - } else if (event == XmlPullParser.END_TAG) { - final String tag = parser.getName(); - if (DEBUG) endTag("", tag); - if (TAG_ROW.equals(tag)) { - if (!skip) { - endRow(row); - } - break; - } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) - || TAG_MERGE.equals(tag)) { - break; - } else { - throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY); - } - } - } - } - - private void parseKey(XmlPullParser parser, Row row, boolean skip) - throws XmlPullParserException, IOException { - if (skip) { - XmlParseUtils.checkEndTag(TAG_KEY, parser); - if (DEBUG) { - startEndTag("<%s /> skipped", TAG_KEY); - } - } else { - final Key key = new Key(mResources, mParams, row, parser); - if (DEBUG) { - startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY, - (key.isEnabled() ? "" : " disabled"), key, - Arrays.toString(key.mMoreKeys)); - } - XmlParseUtils.checkEndTag(TAG_KEY, parser); - endKey(key); - } - } - - private void parseSpacer(XmlPullParser parser, Row row, boolean skip) - throws XmlPullParserException, IOException { - if (skip) { - XmlParseUtils.checkEndTag(TAG_SPACER, parser); - if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER); - } else { - final Key.Spacer spacer = new Key.Spacer(mResources, mParams, row, parser); - if (DEBUG) startEndTag("<%s />", TAG_SPACER); - XmlParseUtils.checkEndTag(TAG_SPACER, parser); - endKey(spacer); - } - } - - private void parseIncludeKeyboardContent(XmlPullParser parser, boolean skip) - throws XmlPullParserException, IOException { - parseIncludeInternal(parser, null, skip); - } - - private void parseIncludeRowContent(XmlPullParser parser, Row row, boolean skip) - throws XmlPullParserException, IOException { - parseIncludeInternal(parser, row, skip); - } - - private void parseIncludeInternal(XmlPullParser parser, Row row, boolean skip) - throws XmlPullParserException, IOException { - if (skip) { - XmlParseUtils.checkEndTag(TAG_INCLUDE, parser); - if (DEBUG) startEndTag(" skipped", TAG_INCLUDE); - } else { - final AttributeSet attr = Xml.asAttributeSet(parser); - final TypedArray keyboardAttr = mResources.obtainAttributes(attr, - R.styleable.Keyboard_Include); - final TypedArray keyAttr = mResources.obtainAttributes(attr, - R.styleable.Keyboard_Key); - int keyboardLayout = 0; - float savedDefaultKeyWidth = 0; - int savedDefaultKeyLabelFlags = 0; - int savedDefaultBackgroundType = Key.BACKGROUND_TYPE_NORMAL; - try { - XmlParseUtils.checkAttributeExists(keyboardAttr, - R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout", - TAG_INCLUDE, parser); - keyboardLayout = keyboardAttr.getResourceId( - R.styleable.Keyboard_Include_keyboardLayout, 0); - if (row != null) { - if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) { - // Override current x coordinate. - row.setXPos(row.getKeyX(keyAttr)); - } - // TODO: Remove this if-clause and do the same as backgroundType below. - savedDefaultKeyWidth = row.getDefaultKeyWidth(); - if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyWidth)) { - // Override default key width. - row.setDefaultKeyWidth(row.getKeyWidth(keyAttr)); - } - savedDefaultKeyLabelFlags = row.getDefaultKeyLabelFlags(); - // Bitwise-or default keyLabelFlag if exists. - row.setDefaultKeyLabelFlags(keyAttr.getInt( - R.styleable.Keyboard_Key_keyLabelFlags, 0) - | savedDefaultKeyLabelFlags); - savedDefaultBackgroundType = row.getDefaultBackgroundType(); - // Override default backgroundType if exists. - row.setDefaultBackgroundType(keyAttr.getInt( - R.styleable.Keyboard_Key_backgroundType, - savedDefaultBackgroundType)); - } - } finally { - keyboardAttr.recycle(); - keyAttr.recycle(); - } - - XmlParseUtils.checkEndTag(TAG_INCLUDE, parser); - if (DEBUG) { - startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE, - mResources.getResourceEntryName(keyboardLayout)); - } - final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout); - try { - parseMerge(parserForInclude, row, skip); - } finally { - if (row != null) { - // Restore default keyWidth, keyLabelFlags, and backgroundType. - row.setDefaultKeyWidth(savedDefaultKeyWidth); - row.setDefaultKeyLabelFlags(savedDefaultKeyLabelFlags); - row.setDefaultBackgroundType(savedDefaultBackgroundType); - } - parserForInclude.close(); - } - } - } - - private void parseMerge(XmlPullParser parser, Row row, boolean skip) - throws XmlPullParserException, IOException { - if (DEBUG) startTag("<%s>", TAG_MERGE); - int event; - while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { - if (event == XmlPullParser.START_TAG) { - final String tag = parser.getName(); - if (TAG_MERGE.equals(tag)) { - if (row == null) { - parseKeyboardContent(parser, skip); - } else { - parseRowContent(parser, row, skip); - } - break; - } else { - throw new XmlParseUtils.ParseException( - "Included keyboard layout must have root element", parser); - } - } - } - } - - private void parseSwitchKeyboardContent(XmlPullParser parser, boolean skip) - throws XmlPullParserException, IOException { - parseSwitchInternal(parser, null, skip); - } - - private void parseSwitchRowContent(XmlPullParser parser, Row row, boolean skip) - throws XmlPullParserException, IOException { - parseSwitchInternal(parser, row, skip); - } - - private void parseSwitchInternal(XmlPullParser parser, Row row, boolean skip) - throws XmlPullParserException, IOException { - if (DEBUG) startTag("<%s> %s", TAG_SWITCH, mParams.mId); - boolean selected = false; - int event; - while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { - if (event == XmlPullParser.START_TAG) { - final String tag = parser.getName(); - if (TAG_CASE.equals(tag)) { - selected |= parseCase(parser, row, selected ? true : skip); - } else if (TAG_DEFAULT.equals(tag)) { - selected |= parseDefault(parser, row, selected ? true : skip); - } else { - throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY); - } - } else if (event == XmlPullParser.END_TAG) { - final String tag = parser.getName(); - if (TAG_SWITCH.equals(tag)) { - if (DEBUG) endTag("", TAG_SWITCH); - break; - } else { - throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY); - } - } - } - } - - private boolean parseCase(XmlPullParser parser, Row row, boolean skip) - throws XmlPullParserException, IOException { - final boolean selected = parseCaseCondition(parser); - if (row == null) { - // Processing Rows. - parseKeyboardContent(parser, selected ? skip : true); - } else { - // Processing Keys. - parseRowContent(parser, row, selected ? skip : true); - } - return selected; - } - - private boolean parseCaseCondition(XmlPullParser parser) { - final KeyboardId id = mParams.mId; - if (id == null) { - return true; - } - final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.Keyboard_Case); - try { - final boolean keyboardLayoutSetElementMatched = matchTypedValue(a, - R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId, - KeyboardId.elementIdToName(id.mElementId)); - final boolean modeMatched = matchTypedValue(a, - R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode)); - final boolean navigateNextMatched = matchBoolean(a, - R.styleable.Keyboard_Case_navigateNext, id.navigateNext()); - final boolean navigatePreviousMatched = matchBoolean(a, - R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious()); - final boolean passwordInputMatched = matchBoolean(a, - R.styleable.Keyboard_Case_passwordInput, id.passwordInput()); - final boolean clobberSettingsKeyMatched = matchBoolean(a, - R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey); - final boolean shortcutKeyEnabledMatched = matchBoolean(a, - R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled); - final boolean hasShortcutKeyMatched = matchBoolean(a, - R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey); - final boolean languageSwitchKeyEnabledMatched = matchBoolean(a, - R.styleable.Keyboard_Case_languageSwitchKeyEnabled, - id.mLanguageSwitchKeyEnabled); - final boolean isMultiLineMatched = matchBoolean(a, - R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine()); - final boolean imeActionMatched = matchInteger(a, - R.styleable.Keyboard_Case_imeAction, id.imeAction()); - final boolean localeCodeMatched = matchString(a, - R.styleable.Keyboard_Case_localeCode, id.mLocale.toString()); - final boolean languageCodeMatched = matchString(a, - R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage()); - final boolean countryCodeMatched = matchString(a, - R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry()); - final boolean selected = keyboardLayoutSetElementMatched && modeMatched - && navigateNextMatched && navigatePreviousMatched && passwordInputMatched - && clobberSettingsKeyMatched && shortcutKeyEnabledMatched - && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched - && isMultiLineMatched && imeActionMatched && localeCodeMatched - && languageCodeMatched && countryCodeMatched; - - if (DEBUG) { - startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE, - textAttr(a.getString( - R.styleable.Keyboard_Case_keyboardLayoutSetElement), - "keyboardLayoutSetElement"), - textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"), - textAttr(a.getString(R.styleable.Keyboard_Case_imeAction), - "imeAction"), - booleanAttr(a, R.styleable.Keyboard_Case_navigateNext, - "navigateNext"), - booleanAttr(a, R.styleable.Keyboard_Case_navigatePrevious, - "navigatePrevious"), - booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey, - "clobberSettingsKey"), - booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, - "passwordInput"), - booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled, - "shortcutKeyEnabled"), - booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey, - "hasShortcutKey"), - booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled, - "languageSwitchKeyEnabled"), - booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine, - "isMultiLine"), - textAttr(a.getString(R.styleable.Keyboard_Case_localeCode), - "localeCode"), - textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), - "languageCode"), - textAttr(a.getString(R.styleable.Keyboard_Case_countryCode), - "countryCode"), - selected ? "" : " skipped"); - } - - return selected; - } finally { - a.recycle(); - } - } - - private static boolean matchInteger(TypedArray a, int index, int value) { - // If does not have "index" attribute, that means this is wild-card for - // the attribute. - return !a.hasValue(index) || a.getInt(index, 0) == value; - } - - private static boolean matchBoolean(TypedArray a, int index, boolean value) { - // If does not have "index" attribute, that means this is wild-card for - // the attribute. - return !a.hasValue(index) || a.getBoolean(index, false) == value; - } - - private static boolean matchString(TypedArray a, int index, String value) { - // If does not have "index" attribute, that means this is wild-card for - // the attribute. - return !a.hasValue(index) - || StringUtils.containsInArray(value, a.getString(index).split("\\|")); - } - - private static boolean matchTypedValue(TypedArray a, int index, int intValue, - String strValue) { - // If does not have "index" attribute, that means this is wild-card for - // the attribute. - final TypedValue v = a.peekValue(index); - if (v == null) { - return true; - } - if (ResourceUtils.isIntegerValue(v)) { - return intValue == a.getInt(index, 0); - } else if (ResourceUtils.isStringValue(v)) { - return StringUtils.containsInArray(strValue, a.getString(index).split("\\|")); - } - return false; - } - - private boolean parseDefault(XmlPullParser parser, Row row, boolean skip) - throws XmlPullParserException, IOException { - if (DEBUG) startTag("<%s>", TAG_DEFAULT); - if (row == null) { - parseKeyboardContent(parser, skip); - } else { - parseRowContent(parser, row, skip); - } - return true; - } - - private void parseKeyStyle(XmlPullParser parser, boolean skip) - throws XmlPullParserException, IOException { - TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.Keyboard_KeyStyle); - TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.Keyboard_Key); - try { - if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) { - throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE - + "/> needs styleName attribute", parser); - } - if (DEBUG) { - startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE, - keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName), - skip ? " skipped" : ""); - } - if (!skip) { - mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser); - } - } finally { - keyStyleAttr.recycle(); - keyAttrs.recycle(); - } - XmlParseUtils.checkEndTag(TAG_KEY_STYLE, parser); - } - - private void startKeyboard() { - mCurrentY += mParams.mTopPadding; - mTopEdge = true; - } - - private void startRow(Row row) { - addEdgeSpace(mParams.mHorizontalEdgesPadding, row); - mCurrentRow = row; - mLeftEdge = true; - mRightEdgeKey = null; - } - - private void endRow(Row row) { - if (mCurrentRow == null) { - throw new InflateException("orphan end row tag"); - } - if (mRightEdgeKey != null) { - mRightEdgeKey.markAsRightEdge(mParams); - mRightEdgeKey = null; - } - addEdgeSpace(mParams.mHorizontalEdgesPadding, row); - mCurrentY += row.mRowHeight; - mCurrentRow = null; - mTopEdge = false; - } - - private void endKey(Key key) { - mParams.onAddKey(key); - if (mLeftEdge) { - key.markAsLeftEdge(mParams); - mLeftEdge = false; - } - if (mTopEdge) { - key.markAsTopEdge(mParams); - } - mRightEdgeKey = key; - } - - private void endKeyboard() { - // nothing to do here. - } - - private void addEdgeSpace(float width, Row row) { - row.advanceXPos(width); - mLeftEdge = false; - mRightEdgeKey = null; - } - - private static String textAttr(String value, String name) { - return value != null ? String.format(" %s=%s", name, value) : ""; - } - - private static String booleanAttr(TypedArray a, int index, String name) { - return a.hasValue(index) - ? String.format(" %s=%s", name, a.getBoolean(index, false)) : ""; - } - } } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index 76ac3de22..aaccf63ba 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -35,7 +35,9 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.compat.EditorInfoCompatUtils; -import com.android.inputmethod.keyboard.KeyboardLayoutSet.Params.ElementParams; +import com.android.inputmethod.keyboard.internal.KeyboardBuilder; +import com.android.inputmethod.keyboard.internal.KeyboardParams; +import com.android.inputmethod.keyboard.internal.KeysCache; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.InputTypeUtils; @@ -78,31 +80,19 @@ public class KeyboardLayoutSet { public static class KeyboardLayoutSetException extends RuntimeException { public final KeyboardId mKeyboardId; - public KeyboardLayoutSetException(Throwable cause, KeyboardId keyboardId) { + public KeyboardLayoutSetException(final Throwable cause, final KeyboardId keyboardId) { super(cause); mKeyboardId = keyboardId; } } - public static class KeysCache { - private final HashMap mMap = CollectionUtils.newHashMap(); - - public void clear() { - mMap.clear(); - } - - public Key get(Key key) { - final Key existingKey = mMap.get(key); - if (existingKey != null) { - // Reuse the existing element that equals to "key" without adding "key" to the map. - return existingKey; - } - mMap.put(key, key); - return key; - } + private static class ElementParams { + int mKeyboardXmlId; + boolean mProximityCharsCorrectionEnabled; + public ElementParams() {} } - static class Params { + private static class Params { String mKeyboardLayoutSetName; int mMode; EditorInfo mEditorInfo; @@ -118,11 +108,7 @@ public class KeyboardLayoutSet { // Sparse array of KeyboardLayoutSet element parameters indexed by element's id. final SparseArray mKeyboardLayoutSetElementIdToParamsMap = CollectionUtils.newSparseArray(); - - static class ElementParams { - int mKeyboardXmlId; - boolean mProximityCharsCorrectionEnabled; - } + public Params() {} } public static void clearKeyboardCache() { @@ -130,12 +116,12 @@ public class KeyboardLayoutSet { sKeysCache.clear(); } - private KeyboardLayoutSet(Context context, Params params) { + KeyboardLayoutSet(final Context context, final Params params) { mContext = context; mParams = params; } - public Keyboard getKeyboard(int baseKeyboardLayoutSetElementId) { + public Keyboard getKeyboard(final int baseKeyboardLayoutSetElementId) { final int keyboardLayoutSetElementId; switch (mParams.mMode) { case KeyboardId.MODE_PHONE: @@ -170,12 +156,12 @@ public class KeyboardLayoutSet { } } - private Keyboard getKeyboard(ElementParams elementParams, final KeyboardId id) { + private Keyboard getKeyboard(final ElementParams elementParams, final KeyboardId id) { final SoftReference ref = sKeyboardCache.get(id); Keyboard keyboard = (ref == null) ? null : ref.get(); if (keyboard == null) { - final Keyboard.Builder builder = - new Keyboard.Builder(mContext, new Keyboard.Params()); + final KeyboardBuilder builder = + new KeyboardBuilder(mContext, new KeyboardParams()); if (id.isAlphabetKeyboard()) { builder.setAutoGenerate(sKeysCache); } @@ -202,7 +188,7 @@ public class KeyboardLayoutSet { // KeyboardLayoutSet element id that is a key in keyboard_set.xml. Also that file specifies // which XML layout should be used for each keyboard. The KeyboardId is an internal key for // Keyboard object. - private KeyboardId getKeyboardId(int keyboardLayoutSetElementId) { + private KeyboardId getKeyboardId(final int keyboardLayoutSetElementId) { final Params params = mParams; final boolean isSymbols = (keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS || keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED); @@ -225,7 +211,7 @@ public class KeyboardLayoutSet { private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo(); - public Builder(Context context, EditorInfo editorInfo) { + public Builder(final Context context, final EditorInfo editorInfo) { mContext = context; mPackageName = context.getPackageName(); mResources = context.getResources(); @@ -238,7 +224,8 @@ public class KeyboardLayoutSet { mPackageName, NO_SETTINGS_KEY, mEditorInfo); } - public Builder setScreenGeometry(int deviceFormFactor, int orientation, int widthPixels) { + public Builder setScreenGeometry(final int deviceFormFactor, final int orientation, + final int widthPixels) { final Params params = mParams; params.mDeviceFormFactor = deviceFormFactor; params.mOrientation = orientation; @@ -246,7 +233,7 @@ public class KeyboardLayoutSet { return this; } - public Builder setSubtype(InputMethodSubtype subtype) { + public Builder setSubtype(final InputMethodSubtype subtype) { final boolean asciiCapable = subtype.containsExtraValueKey(ASCII_CAPABLE); @SuppressWarnings("deprecation") final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions( @@ -263,8 +250,8 @@ public class KeyboardLayoutSet { return this; } - public Builder setOptions(boolean voiceKeyEnabled, boolean voiceKeyOnMain, - boolean languageSwitchKeyEnabled) { + public Builder setOptions(final boolean voiceKeyEnabled, final boolean voiceKeyOnMain, + final boolean languageSwitchKeyEnabled) { @SuppressWarnings("deprecation") final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions( null, NO_MICROPHONE_COMPAT, mEditorInfo); @@ -277,7 +264,7 @@ public class KeyboardLayoutSet { return this; } - public void setTouchPositionCorrectionEnabled(boolean enabled) { + public void setTouchPositionCorrectionEnabled(final boolean enabled) { mParams.mTouchPositionCorrectionEnabled = enabled; } @@ -298,7 +285,7 @@ public class KeyboardLayoutSet { return new KeyboardLayoutSet(mContext, mParams); } - private void parseKeyboardLayoutSet(Resources res, int resId) + private void parseKeyboardLayoutSet(final Resources res, final int resId) throws XmlPullParserException, IOException { final XmlResourceParser parser = res.getXml(resId); try { @@ -318,7 +305,7 @@ public class KeyboardLayoutSet { } } - private void parseKeyboardLayoutSetContent(XmlPullParser parser) + private void parseKeyboardLayoutSetContent(final XmlPullParser parser) throws XmlPullParserException, IOException { int event; while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { @@ -340,7 +327,7 @@ public class KeyboardLayoutSet { } } - private void parseKeyboardLayoutSetElement(XmlPullParser parser) + private void parseKeyboardLayoutSetElement(final XmlPullParser parser) throws XmlPullParserException, IOException { final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.KeyboardLayoutSet_Element); @@ -367,7 +354,7 @@ public class KeyboardLayoutSet { } } - private static int getKeyboardMode(EditorInfo editorInfo) { + private static int getKeyboardMode(final EditorInfo editorInfo) { if (editorInfo == null) return KeyboardId.MODE_TEXT; diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java index a3741a2d8..51b157c28 100644 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java @@ -20,15 +20,17 @@ import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.view.View; -import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec; +import com.android.inputmethod.keyboard.internal.KeyboardBuilder; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; +import com.android.inputmethod.keyboard.internal.KeyboardParams; +import com.android.inputmethod.keyboard.internal.MoreKeySpec; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.StringUtils; public class MoreKeysKeyboard extends Keyboard { private final int mDefaultKeyCoordX; - MoreKeysKeyboard(Builder.MoreKeysKeyboardParams params) { + MoreKeysKeyboard(final MoreKeysKeyboardParams params) { super(params); mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2; } @@ -37,220 +39,222 @@ public class MoreKeysKeyboard extends Keyboard { return mDefaultKeyCoordX; } - public static class Builder extends Keyboard.Builder { + /* package for test */ + static class MoreKeysKeyboardParams extends KeyboardParams { + public boolean mIsFixedOrder; + /* package */int mTopRowAdjustment; + public int mNumRows; + public int mNumColumns; + public int mTopKeys; + public int mLeftKeys; + public int mRightKeys; // includes default key. + public int mDividerWidth; + public int mColumnWidth; + + public MoreKeysKeyboardParams() { + super(); + } + + /** + * Set keyboard parameters of more keys keyboard. + * + * @param numKeys number of keys in this more keys keyboard. + * @param maxColumns number of maximum columns of this more keys keyboard. + * @param keyWidth more keys keyboard key width in pixel, including horizontal gap. + * @param rowHeight more keys keyboard row height in pixel, including vertical gap. + * @param coordXInParent coordinate x of the key preview in parent keyboard. + * @param parentKeyboardWidth parent keyboard width in pixel. + * @param isFixedColumnOrder if true, more keys should be laid out in fixed order. + * @param dividerWidth width of divider, zero for no dividers. + */ + public void setParameters(final int numKeys, final int maxColumns, final int keyWidth, + final int rowHeight, final int coordXInParent, final int parentKeyboardWidth, + final boolean isFixedColumnOrder, final int dividerWidth) { + mIsFixedOrder = isFixedColumnOrder; + if (parentKeyboardWidth / keyWidth < maxColumns) { + throw new IllegalArgumentException( + "Keyboard is too small to hold more keys keyboard: " + + parentKeyboardWidth + " " + keyWidth + " " + maxColumns); + } + mDefaultKeyWidth = keyWidth; + mDefaultRowHeight = rowHeight; + + final int numRows = (numKeys + maxColumns - 1) / maxColumns; + mNumRows = numRows; + final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns) + : getOptimizedColumns(numKeys, maxColumns); + mNumColumns = numColumns; + final int topKeys = numKeys % numColumns; + mTopKeys = topKeys == 0 ? numColumns : topKeys; + + final int numLeftKeys = (numColumns - 1) / 2; + final int numRightKeys = numColumns - numLeftKeys; // including default key. + // Maximum number of keys we can layout both side of the parent key + final int maxLeftKeys = coordXInParent / keyWidth; + final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth; + int leftKeys, rightKeys; + if (numLeftKeys > maxLeftKeys) { + leftKeys = maxLeftKeys; + rightKeys = numColumns - leftKeys; + } else if (numRightKeys > maxRightKeys + 1) { + rightKeys = maxRightKeys + 1; // include default key + leftKeys = numColumns - rightKeys; + } else { + leftKeys = numLeftKeys; + rightKeys = numRightKeys; + } + // If the left keys fill the left side of the parent key, entire more keys keyboard + // should be shifted to the right unless the parent key is on the left edge. + if (maxLeftKeys == leftKeys && leftKeys > 0) { + leftKeys--; + rightKeys++; + } + // If the right keys fill the right side of the parent key, entire more keys + // should be shifted to the left unless the parent key is on the right edge. + if (maxRightKeys == rightKeys - 1 && rightKeys > 1) { + leftKeys++; + rightKeys--; + } + mLeftKeys = leftKeys; + mRightKeys = rightKeys; + + // Adjustment of the top row. + mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment() + : getAutoOrderTopRowAdjustment(); + mDividerWidth = dividerWidth; + mColumnWidth = mDefaultKeyWidth + mDividerWidth; + mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth; + // Need to subtract the bottom row's gutter only. + mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap + + mTopPadding + mBottomPadding; + } + + private int getFixedOrderTopRowAdjustment() { + if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns + || mLeftKeys == 0 || mRightKeys == 1) { + return 0; + } + return -1; + } + + private int getAutoOrderTopRowAdjustment() { + if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2 + || mLeftKeys == 0 || mRightKeys == 1) { + return 0; + } + return -1; + } + + // Return key position according to column count (0 is default). + /* package */int getColumnPos(final int n) { + return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n); + } + + private int getFixedOrderColumnPos(final int n) { + final int col = n % mNumColumns; + final int row = n / mNumColumns; + if (!isTopRow(row)) { + return col - mLeftKeys; + } + final int rightSideKeys = mTopKeys / 2; + final int leftSideKeys = mTopKeys - (rightSideKeys + 1); + final int pos = col - leftSideKeys; + final int numLeftKeys = mLeftKeys + mTopRowAdjustment; + final int numRightKeys = mRightKeys - 1; + if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) { + return pos; + } else if (numRightKeys < rightSideKeys) { + return pos - (rightSideKeys - numRightKeys); + } else { // numLeftKeys < leftSideKeys + return pos + (leftSideKeys - numLeftKeys); + } + } + + private int getAutomaticColumnPos(final int n) { + final int col = n % mNumColumns; + final int row = n / mNumColumns; + int leftKeys = mLeftKeys; + if (isTopRow(row)) { + leftKeys += mTopRowAdjustment; + } + if (col == 0) { + // default position. + return 0; + } + + int pos = 0; + int right = 1; // include default position key. + int left = 0; + int i = 0; + while (true) { + // Assign right key if available. + if (right < mRightKeys) { + pos = right; + right++; + i++; + } + if (i >= col) + break; + // Assign left key if available. + if (left < leftKeys) { + left++; + pos = -left; + i++; + } + if (i >= col) + break; + } + return pos; + } + + private static int getTopRowEmptySlots(final int numKeys, final int numColumns) { + final int remainings = numKeys % numColumns; + return remainings == 0 ? 0 : numColumns - remainings; + } + + private int getOptimizedColumns(final int numKeys, final int maxColumns) { + int numColumns = Math.min(numKeys, maxColumns); + while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) { + numColumns--; + } + return numColumns; + } + + public int getDefaultKeyCoordX() { + return mLeftKeys * mColumnWidth; + } + + public int getX(final int n, final int row) { + final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX(); + if (isTopRow(row)) { + return x + mTopRowAdjustment * (mColumnWidth / 2); + } + return x; + } + + public int getY(final int row) { + return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; + } + + public void markAsEdgeKey(final Key key, final int row) { + if (row == 0) + key.markAsTopEdge(this); + if (isTopRow(row)) + key.markAsBottomEdge(this); + } + + private boolean isTopRow(final int rowCount) { + return mNumRows > 1 && rowCount == mNumRows - 1; + } + } + + public static class Builder extends KeyboardBuilder { private final Key mParentKey; private final Drawable mDivider; private static final float LABEL_PADDING_RATIO = 0.2f; private static final float DIVIDER_RATIO = 0.2f; - public static class MoreKeysKeyboardParams extends Keyboard.Params { - public boolean mIsFixedOrder; - /* package */int mTopRowAdjustment; - public int mNumRows; - public int mNumColumns; - public int mTopKeys; - public int mLeftKeys; - public int mRightKeys; // includes default key. - public int mDividerWidth; - public int mColumnWidth; - - public MoreKeysKeyboardParams() { - super(); - } - - /** - * Set keyboard parameters of more keys keyboard. - * - * @param numKeys number of keys in this more keys keyboard. - * @param maxColumns number of maximum columns of this more keys keyboard. - * @param keyWidth more keys keyboard key width in pixel, including horizontal gap. - * @param rowHeight more keys keyboard row height in pixel, including vertical gap. - * @param coordXInParent coordinate x of the key preview in parent keyboard. - * @param parentKeyboardWidth parent keyboard width in pixel. - * @param isFixedColumnOrder if true, more keys should be laid out in fixed order. - * @param dividerWidth width of divider, zero for no dividers. - */ - public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight, - int coordXInParent, int parentKeyboardWidth, boolean isFixedColumnOrder, - int dividerWidth) { - mIsFixedOrder = isFixedColumnOrder; - if (parentKeyboardWidth / keyWidth < maxColumns) { - throw new IllegalArgumentException( - "Keyboard is too small to hold more keys keyboard: " - + parentKeyboardWidth + " " + keyWidth + " " + maxColumns); - } - mDefaultKeyWidth = keyWidth; - mDefaultRowHeight = rowHeight; - - final int numRows = (numKeys + maxColumns - 1) / maxColumns; - mNumRows = numRows; - final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns) - : getOptimizedColumns(numKeys, maxColumns); - mNumColumns = numColumns; - final int topKeys = numKeys % numColumns; - mTopKeys = topKeys == 0 ? numColumns : topKeys; - - final int numLeftKeys = (numColumns - 1) / 2; - final int numRightKeys = numColumns - numLeftKeys; // including default key. - // Maximum number of keys we can layout both side of the parent key - final int maxLeftKeys = coordXInParent / keyWidth; - final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth; - int leftKeys, rightKeys; - if (numLeftKeys > maxLeftKeys) { - leftKeys = maxLeftKeys; - rightKeys = numColumns - leftKeys; - } else if (numRightKeys > maxRightKeys + 1) { - rightKeys = maxRightKeys + 1; // include default key - leftKeys = numColumns - rightKeys; - } else { - leftKeys = numLeftKeys; - rightKeys = numRightKeys; - } - // If the left keys fill the left side of the parent key, entire more keys keyboard - // should be shifted to the right unless the parent key is on the left edge. - if (maxLeftKeys == leftKeys && leftKeys > 0) { - leftKeys--; - rightKeys++; - } - // If the right keys fill the right side of the parent key, entire more keys - // should be shifted to the left unless the parent key is on the right edge. - if (maxRightKeys == rightKeys - 1 && rightKeys > 1) { - leftKeys++; - rightKeys--; - } - mLeftKeys = leftKeys; - mRightKeys = rightKeys; - - // Adjustment of the top row. - mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment() - : getAutoOrderTopRowAdjustment(); - mDividerWidth = dividerWidth; - mColumnWidth = mDefaultKeyWidth + mDividerWidth; - mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth; - // Need to subtract the bottom row's gutter only. - mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap - + mTopPadding + mBottomPadding; - } - - private int getFixedOrderTopRowAdjustment() { - if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns - || mLeftKeys == 0 || mRightKeys == 1) { - return 0; - } - return -1; - } - - private int getAutoOrderTopRowAdjustment() { - if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2 - || mLeftKeys == 0 || mRightKeys == 1) { - return 0; - } - return -1; - } - - // Return key position according to column count (0 is default). - /* package */int getColumnPos(int n) { - return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n); - } - - private int getFixedOrderColumnPos(int n) { - final int col = n % mNumColumns; - final int row = n / mNumColumns; - if (!isTopRow(row)) { - return col - mLeftKeys; - } - final int rightSideKeys = mTopKeys / 2; - final int leftSideKeys = mTopKeys - (rightSideKeys + 1); - final int pos = col - leftSideKeys; - final int numLeftKeys = mLeftKeys + mTopRowAdjustment; - final int numRightKeys = mRightKeys - 1; - if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) { - return pos; - } else if (numRightKeys < rightSideKeys) { - return pos - (rightSideKeys - numRightKeys); - } else { // numLeftKeys < leftSideKeys - return pos + (leftSideKeys - numLeftKeys); - } - } - - private int getAutomaticColumnPos(int n) { - final int col = n % mNumColumns; - final int row = n / mNumColumns; - int leftKeys = mLeftKeys; - if (isTopRow(row)) { - leftKeys += mTopRowAdjustment; - } - if (col == 0) { - // default position. - return 0; - } - - int pos = 0; - int right = 1; // include default position key. - int left = 0; - int i = 0; - while (true) { - // Assign right key if available. - if (right < mRightKeys) { - pos = right; - right++; - i++; - } - if (i >= col) - break; - // Assign left key if available. - if (left < leftKeys) { - left++; - pos = -left; - i++; - } - if (i >= col) - break; - } - return pos; - } - - private static int getTopRowEmptySlots(int numKeys, int numColumns) { - final int remainings = numKeys % numColumns; - return remainings == 0 ? 0 : numColumns - remainings; - } - - private int getOptimizedColumns(int numKeys, int maxColumns) { - int numColumns = Math.min(numKeys, maxColumns); - while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) { - numColumns--; - } - return numColumns; - } - - public int getDefaultKeyCoordX() { - return mLeftKeys * mColumnWidth; - } - - public int getX(int n, int row) { - final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX(); - if (isTopRow(row)) { - return x + mTopRowAdjustment * (mColumnWidth / 2); - } - return x; - } - - public int getY(int row) { - return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; - } - - public void markAsEdgeKey(Key key, int row) { - if (row == 0) - key.markAsTopEdge(this); - if (isTopRow(row)) - key.markAsBottomEdge(this); - } - - private boolean isTopRow(int rowCount) { - return mNumRows > 1 && rowCount == mNumRows - 1; - } - } /** * The builder of MoreKeysKeyboard. @@ -258,7 +262,8 @@ public class MoreKeysKeyboard extends Keyboard { * @param parentKey the {@link Key} that invokes more keys keyboard. * @param parentKeyboardView the {@link KeyboardView} that contains the parentKey. */ - public Builder(View containerView, Key parentKey, KeyboardView parentKeyboardView) { + public Builder(final View containerView, final Key parentKey, + final KeyboardView parentKeyboardView) { super(containerView.getContext(), new MoreKeysKeyboardParams()); final Keyboard parentKeyboard = parentKeyboardView.getKeyboard(); load(parentKeyboard.mMoreKeysTemplate, parentKeyboard.mId); @@ -300,7 +305,8 @@ public class MoreKeysKeyboard extends Keyboard { dividerWidth); } - private static int getMaxKeyWidth(KeyboardView view, Key parentKey, int minKeyWidth) { + private static int getMaxKeyWidth(final KeyboardView view, final Key parentKey, + final int minKeyWidth) { final int padding = (int)(view.getResources() .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding) + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0)); @@ -322,24 +328,6 @@ public class MoreKeysKeyboard extends Keyboard { return maxWidth; } - private static class MoreKeyDivider extends Key.Spacer { - private final Drawable mIcon; - - public MoreKeyDivider(MoreKeysKeyboardParams params, Drawable icon, int x, int y) { - super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight); - mIcon = icon; - } - - @Override - public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) { - // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the - // constructor. - // TODO: Drawable itself should have an alpha value. - mIcon.setAlpha(128); - return mIcon; - } - } - @Override public MoreKeysKeyboard build() { final MoreKeysKeyboardParams params = mParams; @@ -368,4 +356,23 @@ public class MoreKeysKeyboard extends Keyboard { return new MoreKeysKeyboard(params); } } + + private static class MoreKeyDivider extends Key.Spacer { + private final Drawable mIcon; + + public MoreKeyDivider(final MoreKeysKeyboardParams params, final Drawable icon, + final int x, final int y) { + super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight); + mIcon = icon; + } + + @Override + public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { + // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the + // constructor. + // TODO: Drawable itself should have an alpha value. + mIcon.setAlpha(128); + return mIcon; + } + } } diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index 71bf31faa..e1b082c16 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -19,7 +19,7 @@ package com.android.inputmethod.keyboard; import android.graphics.Rect; import android.text.TextUtils; -import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection; +import com.android.inputmethod.keyboard.internal.TouchPositionCorrection; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.JniUtils; @@ -48,9 +48,10 @@ public class ProximityInfo { private final Key[][] mGridNeighbors; private final String mLocaleStr; - ProximityInfo(String localeStr, int gridWidth, int gridHeight, int minWidth, int height, - int mostCommonKeyWidth, int mostCommonKeyHeight, final Key[] keys, - TouchPositionCorrection touchPositionCorrection) { + ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight, + final int minWidth, final int height, final int mostCommonKeyWidth, + final int mostCommonKeyHeight, final Key[] keys, + final TouchPositionCorrection touchPositionCorrection) { if (TextUtils.isEmpty(localeStr)) { mLocaleStr = ""; } else { @@ -81,7 +82,7 @@ public class ProximityInfo { } public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity, - int rowSize, int gridWidth, int gridHeight) { + final int rowSize, final int gridWidth, final int gridHeight) { final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo(); spellCheckerProximityInfo.mNativeProximityInfo = spellCheckerProximityInfo.setProximityInfoNative("", diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java index 13214bb9f..2a57caa5f 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java @@ -56,59 +56,20 @@ public class KeySpecParser { private static final char ESCAPE_CHAR = '\\'; private static final char LABEL_END = '|'; private static final String PREFIX_TEXT = "!text/"; - private static final String PREFIX_ICON = "!icon/"; + static final String PREFIX_ICON = "!icon/"; private static final String PREFIX_CODE = "!code/"; private static final String PREFIX_HEX = "0x"; private static final String ADDITIONAL_MORE_KEY_MARKER = "%"; - public static class MoreKeySpec { - public final int mCode; - public final String mLabel; - public final String mOutputText; - public final int mIconId; - - public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, Locale locale, - final KeyboardCodesSet codesSet) { - mLabel = toUpperCaseOfStringForLocale(getLabel(moreKeySpec), - needsToUpperCase, locale); - final int code = toUpperCaseOfCodeForLocale(getCode(moreKeySpec, codesSet), - needsToUpperCase, locale); - if (code == Keyboard.CODE_UNSPECIFIED) { - // Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters - // upper case representation ("SS"). - mCode = Keyboard.CODE_OUTPUT_TEXT; - mOutputText = mLabel; - } else { - mCode = code; - mOutputText = toUpperCaseOfStringForLocale(getOutputText(moreKeySpec), - needsToUpperCase, locale); - } - mIconId = getIconId(moreKeySpec); - } - - @Override - public String toString() { - final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel - : PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId)); - final String output = (mCode == Keyboard.CODE_OUTPUT_TEXT ? mOutputText - : Keyboard.printableCode(mCode)); - if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) { - return output; - } else { - return label + "|" + output; - } - } - } - private KeySpecParser() { // Intentional empty constructor for utility class. } - private static boolean hasIcon(String moreKeySpec) { + private static boolean hasIcon(final String moreKeySpec) { return moreKeySpec.startsWith(PREFIX_ICON); } - private static boolean hasCode(String moreKeySpec) { + private static boolean hasCode(final String moreKeySpec) { final int end = indexOfLabelEnd(moreKeySpec, 0); if (end > 0 && end + 1 < moreKeySpec.length() && moreKeySpec.startsWith( PREFIX_CODE, end + 1)) { @@ -117,7 +78,7 @@ public class KeySpecParser { return false; } - private static String parseEscape(String text) { + private static String parseEscape(final String text) { if (text.indexOf(ESCAPE_CHAR) < 0) { return text; } @@ -136,7 +97,7 @@ public class KeySpecParser { return sb.toString(); } - private static int indexOfLabelEnd(String moreKeySpec, int start) { + private static int indexOfLabelEnd(final String moreKeySpec, final int start) { if (moreKeySpec.indexOf(ESCAPE_CHAR, start) < 0) { final int end = moreKeySpec.indexOf(LABEL_END, start); if (end == 0) { @@ -157,7 +118,7 @@ public class KeySpecParser { return -1; } - public static String getLabel(String moreKeySpec) { + public static String getLabel(final String moreKeySpec) { if (hasIcon(moreKeySpec)) { return null; } @@ -170,7 +131,7 @@ public class KeySpecParser { return label; } - private static String getOutputTextInternal(String moreKeySpec) { + private static String getOutputTextInternal(final String moreKeySpec) { final int end = indexOfLabelEnd(moreKeySpec, 0); if (end <= 0) { return null; @@ -181,7 +142,7 @@ public class KeySpecParser { return parseEscape(moreKeySpec.substring(end + /* LABEL_END */1)); } - static String getOutputText(String moreKeySpec) { + static String getOutputText(final String moreKeySpec) { if (hasCode(moreKeySpec)) { return null; } @@ -205,7 +166,7 @@ public class KeySpecParser { return (StringUtils.codePointCount(label) == 1) ? null : label; } - static int getCode(String moreKeySpec, KeyboardCodesSet codesSet) { + static int getCode(final String moreKeySpec, final KeyboardCodesSet codesSet) { if (hasCode(moreKeySpec)) { final int end = indexOfLabelEnd(moreKeySpec, 0); if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) { @@ -230,7 +191,8 @@ public class KeySpecParser { return Keyboard.CODE_OUTPUT_TEXT; } - public static int parseCode(String text, KeyboardCodesSet codesSet, int defCode) { + public static int parseCode(final String text, final KeyboardCodesSet codesSet, + final int defCode) { if (text == null) return defCode; if (text.startsWith(PREFIX_CODE)) { return codesSet.getCode(text.substring(PREFIX_CODE.length())); @@ -241,7 +203,7 @@ public class KeySpecParser { } } - public static int getIconId(String moreKeySpec) { + public static int getIconId(final String moreKeySpec) { if (moreKeySpec != null && hasIcon(moreKeySpec)) { final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length()); final String name = (end < 0) ? moreKeySpec.substring(PREFIX_ICON.length()) @@ -251,7 +213,7 @@ public class KeySpecParser { return KeyboardIconsSet.ICON_UNDEFINED; } - private static ArrayList arrayAsList(T[] array, int start, int end) { + private static ArrayList arrayAsList(final T[] array, final int start, final int end) { if (array == null) { throw new NullPointerException(); } @@ -268,7 +230,7 @@ public class KeySpecParser { private static final String[] EMPTY_STRING_ARRAY = new String[0]; - private static String[] filterOutEmptyString(String[] array) { + private static String[] filterOutEmptyString(final String[] array) { if (array == null) { return EMPTY_STRING_ARRAY; } @@ -289,8 +251,8 @@ public class KeySpecParser { return out.toArray(new String[out.size()]); } - public static String[] insertAdditionalMoreKeys(String[] moreKeySpecs, - String[] additionalMoreKeySpecs) { + public static String[] insertAdditionalMoreKeys(final String[] moreKeySpecs, + final String[] additionalMoreKeySpecs) { final String[] moreKeys = filterOutEmptyString(moreKeySpecs); final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs); final int moreKeysCount = moreKeys.length; @@ -357,12 +319,13 @@ public class KeySpecParser { @SuppressWarnings("serial") public static class KeySpecParserError extends RuntimeException { - public KeySpecParserError(String message) { + public KeySpecParserError(final String message) { super(message); } } - public static String resolveTextReference(String rawText, KeyboardTextsSet textsSet) { + public static String resolveTextReference(final String rawText, + final KeyboardTextsSet textsSet) { int level = 0; String text = rawText; StringBuilder sb; @@ -408,7 +371,7 @@ public class KeySpecParser { return text; } - private static int searchTextNameEnd(String text, int start) { + private static int searchTextNameEnd(final String text, final int start) { final int size = text.length(); for (int pos = start; pos < size; pos++) { final char c = text.charAt(pos); @@ -421,7 +384,7 @@ public class KeySpecParser { return size; } - public static String[] parseCsvString(String rawText, KeyboardTextsSet textsSet) { + public static String[] parseCsvString(final String rawText, final KeyboardTextsSet textsSet) { final String text = resolveTextReference(rawText, textsSet); final int size = text.length(); if (size == 0) { @@ -460,7 +423,8 @@ public class KeySpecParser { return list.toArray(new String[list.size()]); } - public static int getIntValue(String[] moreKeys, String key, int defaultValue) { + public static int getIntValue(final String[] moreKeys, final String key, + final int defaultValue) { if (moreKeys == null) { return defaultValue; } @@ -486,7 +450,7 @@ public class KeySpecParser { return value; } - public static boolean getBooleanValue(String[] moreKeys, String key) { + public static boolean getBooleanValue(final String[] moreKeys, final String key) { if (moreKeys == null) { return false; } @@ -502,8 +466,8 @@ public class KeySpecParser { return value; } - public static int toUpperCaseOfCodeForLocale(int code, boolean needsToUpperCase, - Locale locale) { + public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase, + final Locale locale) { if (!Keyboard.isLetterCode(code) || !needsToUpperCase) return code; final String text = new String(new int[] { code } , 0, 1); final String casedText = KeySpecParser.toUpperCaseOfStringForLocale( @@ -512,8 +476,8 @@ public class KeySpecParser { ? casedText.codePointAt(0) : CODE_UNSPECIFIED; } - public static String toUpperCaseOfStringForLocale(String text, boolean needsToUpperCase, - Locale locale) { + public static String toUpperCaseOfStringForLocale(final String text, + final boolean needsToUpperCase, final Locale locale) { if (text == null || !needsToUpperCase) return text; return text.toUpperCase(locale); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java new file mode 100644 index 000000000..e8cacf9e7 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.keyboard.internal; + +import android.content.res.TypedArray; + +public abstract class KeyStyle { + private final KeyboardTextsSet mTextsSet; + + public abstract String[] getStringArray(TypedArray a, int index); + public abstract String getString(TypedArray a, int index); + public abstract int getInt(TypedArray a, int index, int defaultValue); + public abstract int getFlag(TypedArray a, int index); + + protected KeyStyle(final KeyboardTextsSet textsSet) { + mTextsSet = textsSet; + } + + protected String parseString(final TypedArray a, final int index) { + if (a.hasValue(index)) { + return KeySpecParser.resolveTextReference(a.getString(index), mTextsSet); + } + return null; + } + + protected String[] parseStringArray(final TypedArray a, final int index) { + if (a.hasValue(index)) { + return KeySpecParser.parseCsvString(a.getString(index), mTextsSet); + } + return null; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java similarity index 73% rename from java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java rename to java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java index e40cf45cc..71fd30563 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java @@ -20,7 +20,6 @@ import android.content.res.TypedArray; import android.util.Log; import android.util.SparseArray; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.XmlParseUtils; @@ -30,75 +29,62 @@ import org.xmlpull.v1.XmlPullParserException; import java.util.HashMap; -public class KeyStyles { - private static final String TAG = KeyStyles.class.getSimpleName(); +public class KeyStylesSet { + private static final String TAG = KeyStylesSet.class.getSimpleName(); private static final boolean DEBUG = false; - final HashMap mStyles = CollectionUtils.newHashMap(); + private final HashMap mStyles = CollectionUtils.newHashMap(); - final KeyboardTextsSet mTextsSet; + private final KeyboardTextsSet mTextsSet; private final KeyStyle mEmptyKeyStyle; private static final String EMPTY_STYLE_NAME = ""; - public KeyStyles(KeyboardTextsSet textsSet) { + public KeyStylesSet(final KeyboardTextsSet textsSet) { mTextsSet = textsSet; - mEmptyKeyStyle = new EmptyKeyStyle(); + mEmptyKeyStyle = new EmptyKeyStyle(textsSet); mStyles.put(EMPTY_STYLE_NAME, mEmptyKeyStyle); } - public abstract class KeyStyle { - public abstract String[] getStringArray(TypedArray a, int index); - public abstract String getString(TypedArray a, int index); - public abstract int getInt(TypedArray a, int index, int defaultValue); - public abstract int getFlag(TypedArray a, int index); - - protected String parseString(TypedArray a, int index) { - if (a.hasValue(index)) { - return KeySpecParser.resolveTextReference(a.getString(index), mTextsSet); - } - return null; + private static class EmptyKeyStyle extends KeyStyle { + EmptyKeyStyle(final KeyboardTextsSet textsSet) { + super(textsSet); } - protected String[] parseStringArray(TypedArray a, int index) { - if (a.hasValue(index)) { - return KeySpecParser.parseCsvString(a.getString(index), mTextsSet); - } - return null; - } - } - - class EmptyKeyStyle extends KeyStyle { @Override - public String[] getStringArray(TypedArray a, int index) { + public String[] getStringArray(final TypedArray a, final int index) { return parseStringArray(a, index); } @Override - public String getString(TypedArray a, int index) { + public String getString(final TypedArray a, final int index) { return parseString(a, index); } @Override - public int getInt(TypedArray a, int index, int defaultValue) { + public int getInt(final TypedArray a, final int index, final int defaultValue) { return a.getInt(index, defaultValue); } @Override - public int getFlag(TypedArray a, int index) { + public int getFlag(final TypedArray a, final int index) { return a.getInt(index, 0); } } - private class DeclaredKeyStyle extends KeyStyle { + private static class DeclaredKeyStyle extends KeyStyle { + private final HashMap mStyles; private final String mParentStyleName; private final SparseArray mStyleAttributes = CollectionUtils.newSparseArray(); - public DeclaredKeyStyle(String parentStyleName) { + public DeclaredKeyStyle(final String parentStyleName, final KeyboardTextsSet textsSet, + final HashMap styles) { + super(textsSet); mParentStyleName = parentStyleName; + mStyles = styles; } @Override - public String[] getStringArray(TypedArray a, int index) { + public String[] getStringArray(final TypedArray a, final int index) { if (a.hasValue(index)) { return parseStringArray(a, index); } @@ -111,7 +97,7 @@ public class KeyStyles { } @Override - public String getString(TypedArray a, int index) { + public String getString(final TypedArray a, final int index) { if (a.hasValue(index)) { return parseString(a, index); } @@ -124,7 +110,7 @@ public class KeyStyles { } @Override - public int getInt(TypedArray a, int index, int defaultValue) { + public int getInt(final TypedArray a, final int index, final int defaultValue) { if (a.hasValue(index)) { return a.getInt(index, defaultValue); } @@ -137,7 +123,7 @@ public class KeyStyles { } @Override - public int getFlag(TypedArray a, int index) { + public int getFlag(final TypedArray a, final int index) { int flags = a.getInt(index, 0); final Object value = mStyleAttributes.get(index); if (value != null) { @@ -147,7 +133,7 @@ public class KeyStyles { return flags | parentStyle.getFlag(a, index); } - void readKeyAttributes(TypedArray keyAttr) { + public void readKeyAttributes(final TypedArray keyAttr) { // TODO: Currently not all Key attributes can be declared as style. readString(keyAttr, R.styleable.Keyboard_Key_code); readString(keyAttr, R.styleable.Keyboard_Key_altCode); @@ -165,38 +151,38 @@ public class KeyStyles { readFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags); } - private void readString(TypedArray a, int index) { + private void readString(final TypedArray a, final int index) { if (a.hasValue(index)) { mStyleAttributes.put(index, parseString(a, index)); } } - private void readInt(TypedArray a, int index) { + private void readInt(final TypedArray a, final int index) { if (a.hasValue(index)) { mStyleAttributes.put(index, a.getInt(index, 0)); } } - private void readFlag(TypedArray a, int index) { + private void readFlag(final TypedArray a, final int index) { if (a.hasValue(index)) { final Integer value = (Integer)mStyleAttributes.get(index); mStyleAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0)); } } - private void readStringArray(TypedArray a, int index) { + private void readStringArray(final TypedArray a, final int index) { if (a.hasValue(index)) { mStyleAttributes.put(index, parseStringArray(a, index)); } } } - public void parseKeyStyleAttributes(TypedArray keyStyleAttr, TypedArray keyAttrs, - XmlPullParser parser) throws XmlPullParserException { + public void parseKeyStyleAttributes(final TypedArray keyStyleAttr, final TypedArray keyAttrs, + final XmlPullParser parser) throws XmlPullParserException { final String styleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName); if (DEBUG) { Log.d(TAG, String.format("<%s styleName=%s />", - Keyboard.Builder.TAG_KEY_STYLE, styleName)); + KeyboardBuilder.TAG_KEY_STYLE, styleName)); if (mStyles.containsKey(styleName)) { Log.d(TAG, "key-style " + styleName + " is overridden at " + parser.getPositionDescription()); @@ -211,12 +197,12 @@ public class KeyStyles { "Unknown parentStyle " + parentStyleName, parser); } } - final DeclaredKeyStyle style = new DeclaredKeyStyle(parentStyleName); + final DeclaredKeyStyle style = new DeclaredKeyStyle(parentStyleName, mTextsSet, mStyles); style.readKeyAttributes(keyAttrs); mStyles.put(styleName, style); } - public KeyStyle getKeyStyle(TypedArray keyAttr, XmlPullParser parser) + public KeyStyle getKeyStyle(final TypedArray keyAttr, final XmlPullParser parser) throws XmlParseUtils.ParseException { if (!keyAttr.hasValue(R.styleable.Keyboard_Key_keyStyle)) { return mEmptyKeyStyle; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java new file mode 100644 index 000000000..c20b45534 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java @@ -0,0 +1,829 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.keyboard.internal; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.Typeface; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.util.Xml; +import android.view.InflateException; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardId; +import com.android.inputmethod.latin.LocaleUtils.RunInLocale; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.ResourceUtils; +import com.android.inputmethod.latin.StringUtils; +import com.android.inputmethod.latin.SubtypeLocale; +import com.android.inputmethod.latin.XmlParseUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Locale; + +/** + * Keyboard Building helper. + * + * This class parses Keyboard XML file and eventually build a Keyboard. + * The Keyboard XML file looks like: + *
+ *   <!-- xml/keyboard.xml -->
+ *   <Keyboard keyboard_attributes*>
+ *     <!-- Keyboard Content -->
+ *     <Row row_attributes*>
+ *       <!-- Row Content -->
+ *       <Key key_attributes* />
+ *       <Spacer horizontalGap="32.0dp" />
+ *       <include keyboardLayout="@xml/other_keys">
+ *       ...
+ *     </Row>
+ *     <include keyboardLayout="@xml/other_rows">
+ *     ...
+ *   </Keyboard>
+ * 
+ * The XML file which is included in other file must have <merge> as root element, + * such as: + *
+ *   <!-- xml/other_keys.xml -->
+ *   <merge>
+ *     <Key key_attributes* />
+ *     ...
+ *   </merge>
+ * 
+ * and + *
+ *   <!-- xml/other_rows.xml -->
+ *   <merge>
+ *     <Row row_attributes*>
+ *       <Key key_attributes* />
+ *     </Row>
+ *     ...
+ *   </merge>
+ * 
+ * You can also use switch-case-default tags to select Rows and Keys. + *
+ *   <switch>
+ *     <case case_attribute*>
+ *       <!-- Any valid tags at switch position -->
+ *     </case>
+ *     ...
+ *     <default>
+ *       <!-- Any valid tags at switch position -->
+ *     </default>
+ *   </switch>
+ * 
+ * You can declare Key style and specify styles within Key tags. + *
+ *     <switch>
+ *       <case mode="email">
+ *         <key-style styleName="f1-key" parentStyle="modifier-key"
+ *           keyLabel=".com"
+ *         />
+ *       </case>
+ *       <case mode="url">
+ *         <key-style styleName="f1-key" parentStyle="modifier-key"
+ *           keyLabel="http://"
+ *         />
+ *       </case>
+ *     </switch>
+ *     ...
+ *     <Key keyStyle="shift-key" ... />
+ * 
+ */ + +public class KeyboardBuilder { + private static final String BUILDER_TAG = "Keyboard.Builder"; + private static final boolean DEBUG = false; + + // Keyboard XML Tags + private static final String TAG_KEYBOARD = "Keyboard"; + private static final String TAG_ROW = "Row"; + private static final String TAG_KEY = "Key"; + private static final String TAG_SPACER = "Spacer"; + private static final String TAG_INCLUDE = "include"; + private static final String TAG_MERGE = "merge"; + private static final String TAG_SWITCH = "switch"; + private static final String TAG_CASE = "case"; + private static final String TAG_DEFAULT = "default"; + public static final String TAG_KEY_STYLE = "key-style"; + + private static final int DEFAULT_KEYBOARD_COLUMNS = 10; + private static final int DEFAULT_KEYBOARD_ROWS = 4; + + protected final KP mParams; + protected final Context mContext; + protected final Resources mResources; + private final DisplayMetrics mDisplayMetrics; + + private int mCurrentY = 0; + private KeyboardRow mCurrentRow = null; + private boolean mLeftEdge; + private boolean mTopEdge; + private Key mRightEdgeKey = null; + + public KeyboardBuilder(final Context context, final KP params) { + mContext = context; + final Resources res = context.getResources(); + mResources = res; + mDisplayMetrics = res.getDisplayMetrics(); + + mParams = params; + + params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width); + params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height); + } + + public void setAutoGenerate(final KeysCache keysCache) { + mParams.mKeysCache = keysCache; + } + + public KeyboardBuilder load(final int xmlId, final KeyboardId id) { + mParams.mId = id; + final XmlResourceParser parser = mResources.getXml(xmlId); + try { + parseKeyboard(parser); + } catch (XmlPullParserException e) { + Log.w(BUILDER_TAG, "keyboard XML parse error: " + e); + throw new IllegalArgumentException(e); + } catch (IOException e) { + Log.w(BUILDER_TAG, "keyboard XML parse error: " + e); + throw new RuntimeException(e); + } finally { + parser.close(); + } + return this; + } + + // TODO: Remove this method. + public void setTouchPositionCorrectionEnabled(final boolean enabled) { + mParams.mTouchPositionCorrection.setEnabled(enabled); + } + + public void setProximityCharsCorrectionEnabled(final boolean enabled) { + mParams.mProximityCharsCorrectionEnabled = enabled; + } + + public Keyboard build() { + return new Keyboard(mParams); + } + + private int mIndent; + private static final String SPACES = " "; + + private static String spaces(final int count) { + return (count < SPACES.length()) ? SPACES.substring(0, count) : SPACES; + } + + private void startTag(final String format, final Object ... args) { + Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args)); + } + + private void endTag(final String format, final Object ... args) { + Log.d(BUILDER_TAG, String.format(spaces(mIndent-- * 2) + format, args)); + } + + private void startEndTag(final String format, final Object ... args) { + Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args)); + mIndent--; + } + + private void parseKeyboard(final XmlPullParser parser) + throws XmlPullParserException, IOException { + if (DEBUG) startTag("<%s> %s", TAG_KEYBOARD, mParams.mId); + int event; + while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (event == XmlPullParser.START_TAG) { + final String tag = parser.getName(); + if (TAG_KEYBOARD.equals(tag)) { + parseKeyboardAttributes(parser); + startKeyboard(); + parseKeyboardContent(parser, false); + break; + } else { + throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD); + } + } + } + } + + private void parseKeyboardAttributes(final XmlPullParser parser) { + final int displayWidth = mDisplayMetrics.widthPixels; + final TypedArray keyboardAttr = mContext.obtainStyledAttributes( + Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle, + R.style.Keyboard); + final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard_Key); + final TypedArray keyboardViewAttr = mResources.obtainAttributes( + Xml.asAttributeSet(parser), R.styleable.KeyboardView); + try { + final int displayHeight = mDisplayMetrics.heightPixels; + final String keyboardHeightString = ResourceUtils.getDeviceOverrideValue( + mResources, R.array.keyboard_heights, null); + final float keyboardHeight; + if (keyboardHeightString != null) { + keyboardHeight = Float.parseFloat(keyboardHeightString) + * mDisplayMetrics.density; + } else { + keyboardHeight = keyboardAttr.getDimension( + R.styleable.Keyboard_keyboardHeight, displayHeight / 2); + } + final float maxKeyboardHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2); + float minKeyboardHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_minKeyboardHeight, displayHeight, displayHeight / 2); + if (minKeyboardHeight < 0) { + // Specified fraction was negative, so it should be calculated against display + // width. + minKeyboardHeight = -ResourceUtils.getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2); + } + final KeyboardParams params = mParams; + // Keyboard height will not exceed maxKeyboardHeight and will not be less than + // minKeyboardHeight. + params.mOccupiedHeight = (int)Math.max( + Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight); + params.mOccupiedWidth = params.mId.mWidth; + params.mTopPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0); + params.mBottomPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0); + params.mHorizontalEdgesPadding = (int)ResourceUtils.getDimensionOrFraction( + keyboardAttr, + R.styleable.Keyboard_keyboardHorizontalEdgesPadding, + mParams.mOccupiedWidth, 0); + + params.mBaseWidth = params.mOccupiedWidth - params.mHorizontalEdgesPadding * 2 + - params.mHorizontalCenterPadding; + params.mDefaultKeyWidth = (int)ResourceUtils.getDimensionOrFraction(keyAttr, + R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth, + params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS); + params.mHorizontalGap = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0); + params.mVerticalGap = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_verticalGap, params.mOccupiedHeight, 0); + params.mBaseHeight = params.mOccupiedHeight - params.mTopPadding + - params.mBottomPadding + params.mVerticalGap; + params.mDefaultRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_rowHeight, params.mBaseHeight, + params.mBaseHeight / DEFAULT_KEYBOARD_ROWS); + + if (keyboardViewAttr.hasValue(R.styleable.KeyboardView_keyTypeface)) { + params.mKeyTypeface = Typeface.defaultFromStyle(keyboardViewAttr.getInt( + R.styleable.KeyboardView_keyTypeface, Typeface.NORMAL)); + } + params.mKeyLetterRatio = ResourceUtils.getFraction(keyboardViewAttr, + R.styleable.KeyboardView_keyLetterSize); + params.mKeyLetterSize = ResourceUtils.getDimensionPixelSize(keyboardViewAttr, + R.styleable.KeyboardView_keyLetterSize); + params.mKeyHintLetterRatio = ResourceUtils.getFraction(keyboardViewAttr, + R.styleable.KeyboardView_keyHintLetterRatio); + params.mKeyShiftedLetterHintRatio = ResourceUtils.getFraction(keyboardViewAttr, + R.styleable.KeyboardView_keyShiftedLetterHintRatio); + + params.mMoreKeysTemplate = keyboardAttr.getResourceId( + R.styleable.Keyboard_moreKeysTemplate, 0); + params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt( + R.styleable.Keyboard_Key_maxMoreKeysColumn, 5); + + params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0); + params.mIconsSet.loadIcons(keyboardAttr); + final String language = params.mId.mLocale.getLanguage(); + params.mCodesSet.setLanguage(language); + params.mTextsSet.setLanguage(language); + final RunInLocale job = new RunInLocale() { + @Override + protected Void job(Resources res) { + params.mTextsSet.loadStringResources(mContext); + return null; + } + }; + // Null means the current system locale. + final Locale locale = SubtypeLocale.isNoLanguage(params.mId.mSubtype) + ? null : params.mId.mLocale; + job.runInLocale(mResources, locale); + + final int resourceId = keyboardAttr.getResourceId( + R.styleable.Keyboard_touchPositionCorrectionData, 0); + params.mTouchPositionCorrection.setEnabled(resourceId != 0); + if (resourceId != 0) { + final String[] data = mResources.getStringArray(resourceId); + params.mTouchPositionCorrection.load(data); + } + } finally { + keyboardViewAttr.recycle(); + keyAttr.recycle(); + keyboardAttr.recycle(); + } + } + + private void parseKeyboardContent(final XmlPullParser parser, final boolean skip) + throws XmlPullParserException, IOException { + int event; + while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (event == XmlPullParser.START_TAG) { + final String tag = parser.getName(); + if (TAG_ROW.equals(tag)) { + final KeyboardRow row = parseRowAttributes(parser); + if (DEBUG) startTag("<%s>%s", TAG_ROW, skip ? " skipped" : ""); + if (!skip) { + startRow(row); + } + parseRowContent(parser, row, skip); + } else if (TAG_INCLUDE.equals(tag)) { + parseIncludeKeyboardContent(parser, skip); + } else if (TAG_SWITCH.equals(tag)) { + parseSwitchKeyboardContent(parser, skip); + } else if (TAG_KEY_STYLE.equals(tag)) { + parseKeyStyle(parser, skip); + } else { + throw new XmlParseUtils.IllegalStartTag(parser, TAG_ROW); + } + } else if (event == XmlPullParser.END_TAG) { + final String tag = parser.getName(); + if (DEBUG) endTag("", tag); + if (TAG_KEYBOARD.equals(tag)) { + endKeyboard(); + break; + } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) + || TAG_MERGE.equals(tag)) { + break; + } else { + throw new XmlParseUtils.IllegalEndTag(parser, TAG_ROW); + } + } + } + } + + private KeyboardRow parseRowAttributes(final XmlPullParser parser) + throws XmlPullParserException { + final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard); + try { + if (a.hasValue(R.styleable.Keyboard_horizontalGap)) { + throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap"); + } + if (a.hasValue(R.styleable.Keyboard_verticalGap)) { + throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap"); + } + return new KeyboardRow(mResources, mParams, parser, mCurrentY); + } finally { + a.recycle(); + } + } + + private void parseRowContent(final XmlPullParser parser, final KeyboardRow row, + final boolean skip) throws XmlPullParserException, IOException { + int event; + while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (event == XmlPullParser.START_TAG) { + final String tag = parser.getName(); + if (TAG_KEY.equals(tag)) { + parseKey(parser, row, skip); + } else if (TAG_SPACER.equals(tag)) { + parseSpacer(parser, row, skip); + } else if (TAG_INCLUDE.equals(tag)) { + parseIncludeRowContent(parser, row, skip); + } else if (TAG_SWITCH.equals(tag)) { + parseSwitchRowContent(parser, row, skip); + } else if (TAG_KEY_STYLE.equals(tag)) { + parseKeyStyle(parser, skip); + } else { + throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY); + } + } else if (event == XmlPullParser.END_TAG) { + final String tag = parser.getName(); + if (DEBUG) endTag("", tag); + if (TAG_ROW.equals(tag)) { + if (!skip) { + endRow(row); + } + break; + } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) + || TAG_MERGE.equals(tag)) { + break; + } else { + throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY); + } + } + } + } + + private void parseKey(final XmlPullParser parser, final KeyboardRow row, final boolean skip) + throws XmlPullParserException, IOException { + if (skip) { + XmlParseUtils.checkEndTag(TAG_KEY, parser); + if (DEBUG) { + startEndTag("<%s /> skipped", TAG_KEY); + } + } else { + final Key key = new Key(mResources, mParams, row, parser); + if (DEBUG) { + startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY, + (key.isEnabled() ? "" : " disabled"), key, + Arrays.toString(key.mMoreKeys)); + } + XmlParseUtils.checkEndTag(TAG_KEY, parser); + endKey(key); + } + } + + private void parseSpacer(final XmlPullParser parser, final KeyboardRow row, final boolean skip) + throws XmlPullParserException, IOException { + if (skip) { + XmlParseUtils.checkEndTag(TAG_SPACER, parser); + if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER); + } else { + final Key.Spacer spacer = new Key.Spacer(mResources, mParams, row, parser); + if (DEBUG) startEndTag("<%s />", TAG_SPACER); + XmlParseUtils.checkEndTag(TAG_SPACER, parser); + endKey(spacer); + } + } + + private void parseIncludeKeyboardContent(final XmlPullParser parser, final boolean skip) + throws XmlPullParserException, IOException { + parseIncludeInternal(parser, null, skip); + } + + private void parseIncludeRowContent(final XmlPullParser parser, final KeyboardRow row, + final boolean skip) throws XmlPullParserException, IOException { + parseIncludeInternal(parser, row, skip); + } + + private void parseIncludeInternal(final XmlPullParser parser, final KeyboardRow row, + final boolean skip) throws XmlPullParserException, IOException { + if (skip) { + XmlParseUtils.checkEndTag(TAG_INCLUDE, parser); + if (DEBUG) startEndTag(" skipped", TAG_INCLUDE); + } else { + final AttributeSet attr = Xml.asAttributeSet(parser); + final TypedArray keyboardAttr = mResources.obtainAttributes(attr, + R.styleable.Keyboard_Include); + final TypedArray keyAttr = mResources.obtainAttributes(attr, + R.styleable.Keyboard_Key); + int keyboardLayout = 0; + float savedDefaultKeyWidth = 0; + int savedDefaultKeyLabelFlags = 0; + int savedDefaultBackgroundType = Key.BACKGROUND_TYPE_NORMAL; + try { + XmlParseUtils.checkAttributeExists(keyboardAttr, + R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout", + TAG_INCLUDE, parser); + keyboardLayout = keyboardAttr.getResourceId( + R.styleable.Keyboard_Include_keyboardLayout, 0); + if (row != null) { + if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) { + // Override current x coordinate. + row.setXPos(row.getKeyX(keyAttr)); + } + // TODO: Remove this if-clause and do the same as backgroundType below. + savedDefaultKeyWidth = row.getDefaultKeyWidth(); + if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyWidth)) { + // Override default key width. + row.setDefaultKeyWidth(row.getKeyWidth(keyAttr)); + } + savedDefaultKeyLabelFlags = row.getDefaultKeyLabelFlags(); + // Bitwise-or default keyLabelFlag if exists. + row.setDefaultKeyLabelFlags(keyAttr.getInt( + R.styleable.Keyboard_Key_keyLabelFlags, 0) + | savedDefaultKeyLabelFlags); + savedDefaultBackgroundType = row.getDefaultBackgroundType(); + // Override default backgroundType if exists. + row.setDefaultBackgroundType(keyAttr.getInt( + R.styleable.Keyboard_Key_backgroundType, + savedDefaultBackgroundType)); + } + } finally { + keyboardAttr.recycle(); + keyAttr.recycle(); + } + + XmlParseUtils.checkEndTag(TAG_INCLUDE, parser); + if (DEBUG) { + startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE, + mResources.getResourceEntryName(keyboardLayout)); + } + final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout); + try { + parseMerge(parserForInclude, row, skip); + } finally { + if (row != null) { + // Restore default keyWidth, keyLabelFlags, and backgroundType. + row.setDefaultKeyWidth(savedDefaultKeyWidth); + row.setDefaultKeyLabelFlags(savedDefaultKeyLabelFlags); + row.setDefaultBackgroundType(savedDefaultBackgroundType); + } + parserForInclude.close(); + } + } + } + + private void parseMerge(final XmlPullParser parser, final KeyboardRow row, final boolean skip) + throws XmlPullParserException, IOException { + if (DEBUG) startTag("<%s>", TAG_MERGE); + int event; + while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (event == XmlPullParser.START_TAG) { + final String tag = parser.getName(); + if (TAG_MERGE.equals(tag)) { + if (row == null) { + parseKeyboardContent(parser, skip); + } else { + parseRowContent(parser, row, skip); + } + break; + } else { + throw new XmlParseUtils.ParseException( + "Included keyboard layout must have root element", parser); + } + } + } + } + + private void parseSwitchKeyboardContent(final XmlPullParser parser, final boolean skip) + throws XmlPullParserException, IOException { + parseSwitchInternal(parser, null, skip); + } + + private void parseSwitchRowContent(final XmlPullParser parser, final KeyboardRow row, + final boolean skip) throws XmlPullParserException, IOException { + parseSwitchInternal(parser, row, skip); + } + + private void parseSwitchInternal(final XmlPullParser parser, final KeyboardRow row, + final boolean skip) throws XmlPullParserException, IOException { + if (DEBUG) startTag("<%s> %s", TAG_SWITCH, mParams.mId); + boolean selected = false; + int event; + while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (event == XmlPullParser.START_TAG) { + final String tag = parser.getName(); + if (TAG_CASE.equals(tag)) { + selected |= parseCase(parser, row, selected ? true : skip); + } else if (TAG_DEFAULT.equals(tag)) { + selected |= parseDefault(parser, row, selected ? true : skip); + } else { + throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY); + } + } else if (event == XmlPullParser.END_TAG) { + final String tag = parser.getName(); + if (TAG_SWITCH.equals(tag)) { + if (DEBUG) endTag("", TAG_SWITCH); + break; + } else { + throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY); + } + } + } + } + + private boolean parseCase(final XmlPullParser parser, final KeyboardRow row, final boolean skip) + throws XmlPullParserException, IOException { + final boolean selected = parseCaseCondition(parser); + if (row == null) { + // Processing Rows. + parseKeyboardContent(parser, selected ? skip : true); + } else { + // Processing Keys. + parseRowContent(parser, row, selected ? skip : true); + } + return selected; + } + + private boolean parseCaseCondition(final XmlPullParser parser) { + final KeyboardId id = mParams.mId; + if (id == null) { + return true; + } + final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard_Case); + try { + final boolean keyboardLayoutSetElementMatched = matchTypedValue(a, + R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId, + KeyboardId.elementIdToName(id.mElementId)); + final boolean modeMatched = matchTypedValue(a, + R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode)); + final boolean navigateNextMatched = matchBoolean(a, + R.styleable.Keyboard_Case_navigateNext, id.navigateNext()); + final boolean navigatePreviousMatched = matchBoolean(a, + R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious()); + final boolean passwordInputMatched = matchBoolean(a, + R.styleable.Keyboard_Case_passwordInput, id.passwordInput()); + final boolean clobberSettingsKeyMatched = matchBoolean(a, + R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey); + final boolean shortcutKeyEnabledMatched = matchBoolean(a, + R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled); + final boolean hasShortcutKeyMatched = matchBoolean(a, + R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey); + final boolean languageSwitchKeyEnabledMatched = matchBoolean(a, + R.styleable.Keyboard_Case_languageSwitchKeyEnabled, + id.mLanguageSwitchKeyEnabled); + final boolean isMultiLineMatched = matchBoolean(a, + R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine()); + final boolean imeActionMatched = matchInteger(a, + R.styleable.Keyboard_Case_imeAction, id.imeAction()); + final boolean localeCodeMatched = matchString(a, + R.styleable.Keyboard_Case_localeCode, id.mLocale.toString()); + final boolean languageCodeMatched = matchString(a, + R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage()); + final boolean countryCodeMatched = matchString(a, + R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry()); + final boolean selected = keyboardLayoutSetElementMatched && modeMatched + && navigateNextMatched && navigatePreviousMatched && passwordInputMatched + && clobberSettingsKeyMatched && shortcutKeyEnabledMatched + && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched + && isMultiLineMatched && imeActionMatched && localeCodeMatched + && languageCodeMatched && countryCodeMatched; + + if (DEBUG) { + startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE, + textAttr(a.getString( + R.styleable.Keyboard_Case_keyboardLayoutSetElement), + "keyboardLayoutSetElement"), + textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"), + textAttr(a.getString(R.styleable.Keyboard_Case_imeAction), + "imeAction"), + booleanAttr(a, R.styleable.Keyboard_Case_navigateNext, + "navigateNext"), + booleanAttr(a, R.styleable.Keyboard_Case_navigatePrevious, + "navigatePrevious"), + booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey, + "clobberSettingsKey"), + booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, + "passwordInput"), + booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled, + "shortcutKeyEnabled"), + booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey, + "hasShortcutKey"), + booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled, + "languageSwitchKeyEnabled"), + booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine, + "isMultiLine"), + textAttr(a.getString(R.styleable.Keyboard_Case_localeCode), + "localeCode"), + textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), + "languageCode"), + textAttr(a.getString(R.styleable.Keyboard_Case_countryCode), + "countryCode"), + selected ? "" : " skipped"); + } + + return selected; + } finally { + a.recycle(); + } + } + + private static boolean matchInteger(final TypedArray a, final int index, final int value) { + // If does not have "index" attribute, that means this is wild-card for + // the attribute. + return !a.hasValue(index) || a.getInt(index, 0) == value; + } + + private static boolean matchBoolean(final TypedArray a, final int index, final boolean value) { + // If does not have "index" attribute, that means this is wild-card for + // the attribute. + return !a.hasValue(index) || a.getBoolean(index, false) == value; + } + + private static boolean matchString(final TypedArray a, final int index, final String value) { + // If does not have "index" attribute, that means this is wild-card for + // the attribute. + return !a.hasValue(index) + || StringUtils.containsInArray(value, a.getString(index).split("\\|")); + } + + private static boolean matchTypedValue(final TypedArray a, final int index, final int intValue, + final String strValue) { + // If does not have "index" attribute, that means this is wild-card for + // the attribute. + final TypedValue v = a.peekValue(index); + if (v == null) { + return true; + } + if (ResourceUtils.isIntegerValue(v)) { + return intValue == a.getInt(index, 0); + } else if (ResourceUtils.isStringValue(v)) { + return StringUtils.containsInArray(strValue, a.getString(index).split("\\|")); + } + return false; + } + + private boolean parseDefault(final XmlPullParser parser, final KeyboardRow row, + final boolean skip) throws XmlPullParserException, IOException { + if (DEBUG) startTag("<%s>", TAG_DEFAULT); + if (row == null) { + parseKeyboardContent(parser, skip); + } else { + parseRowContent(parser, row, skip); + } + return true; + } + + private void parseKeyStyle(final XmlPullParser parser, final boolean skip) + throws XmlPullParserException, IOException { + TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard_KeyStyle); + TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard_Key); + try { + if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) { + throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE + + "/> needs styleName attribute", parser); + } + if (DEBUG) { + startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE, + keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName), + skip ? " skipped" : ""); + } + if (!skip) { + mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser); + } + } finally { + keyStyleAttr.recycle(); + keyAttrs.recycle(); + } + XmlParseUtils.checkEndTag(TAG_KEY_STYLE, parser); + } + + private void startKeyboard() { + mCurrentY += mParams.mTopPadding; + mTopEdge = true; + } + + private void startRow(final KeyboardRow row) { + addEdgeSpace(mParams.mHorizontalEdgesPadding, row); + mCurrentRow = row; + mLeftEdge = true; + mRightEdgeKey = null; + } + + private void endRow(final KeyboardRow row) { + if (mCurrentRow == null) { + throw new InflateException("orphan end row tag"); + } + if (mRightEdgeKey != null) { + mRightEdgeKey.markAsRightEdge(mParams); + mRightEdgeKey = null; + } + addEdgeSpace(mParams.mHorizontalEdgesPadding, row); + mCurrentY += row.mRowHeight; + mCurrentRow = null; + mTopEdge = false; + } + + private void endKey(final Key key) { + mParams.onAddKey(key); + if (mLeftEdge) { + key.markAsLeftEdge(mParams); + mLeftEdge = false; + } + if (mTopEdge) { + key.markAsTopEdge(mParams); + } + mRightEdgeKey = key; + } + + private void endKeyboard() { + // nothing to do here. + } + + private void addEdgeSpace(final float width, final KeyboardRow row) { + row.advanceXPos(width); + mLeftEdge = false; + mRightEdgeKey = null; + } + + private static String textAttr(final String value, final String name) { + return value != null ? String.format(" %s=%s", name, value) : ""; + } + + private static String booleanAttr(final TypedArray a, final int index, final String name) { + return a.hasValue(index) + ? String.format(" %s=%s", name, a.getBoolean(index, false)) : ""; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java new file mode 100644 index 000000000..ff5d31528 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.keyboard.internal; + +import android.graphics.Typeface; +import android.util.SparseIntArray; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardId; +import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.ResourceUtils; + +import java.util.ArrayList; +import java.util.HashSet; + +public class KeyboardParams { + public KeyboardId mId; + public int mThemeId; + + /** Total height and width of the keyboard, including the paddings and keys */ + public int mOccupiedHeight; + public int mOccupiedWidth; + + /** Base height and width of the keyboard used to calculate rows' or keys' heights and + * widths + */ + public int mBaseHeight; + public int mBaseWidth; + + public int mTopPadding; + public int mBottomPadding; + public int mHorizontalEdgesPadding; + public int mHorizontalCenterPadding; + + public Typeface mKeyTypeface = null; + public float mKeyLetterRatio = ResourceUtils.UNDEFINED_RATIO; + public int mKeyLetterSize = ResourceUtils.UNDEFINED_DIMENSION; + public float mKeyHintLetterRatio = ResourceUtils.UNDEFINED_RATIO; + public float mKeyShiftedLetterHintRatio = ResourceUtils.UNDEFINED_RATIO; + + public int mDefaultRowHeight; + public int mDefaultKeyWidth; + public int mHorizontalGap; + public int mVerticalGap; + + public int mMoreKeysTemplate; + public int mMaxMoreKeysKeyboardColumn; + + public int GRID_WIDTH; + public int GRID_HEIGHT; + + public final HashSet mKeys = CollectionUtils.newHashSet(); + public final ArrayList mShiftKeys = CollectionUtils.newArrayList(); + public final ArrayList mAltCodeKeysWhileTyping = CollectionUtils.newArrayList(); + public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet(); + public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet(); + public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); + public final KeyStylesSet mKeyStyles = new KeyStylesSet(mTextsSet); + + public KeysCache mKeysCache; + + public int mMostCommonKeyHeight = 0; + public int mMostCommonKeyWidth = 0; + + public boolean mProximityCharsCorrectionEnabled; + + public final TouchPositionCorrection mTouchPositionCorrection = + new TouchPositionCorrection(); + + protected void clearKeys() { + mKeys.clear(); + mShiftKeys.clear(); + clearHistogram(); + } + + public void onAddKey(final Key newKey) { + final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey; + final boolean zeroWidthSpacer = key.isSpacer() && key.mWidth == 0; + if (!zeroWidthSpacer) { + mKeys.add(key); + updateHistogram(key); + } + if (key.mCode == Keyboard.CODE_SHIFT) { + mShiftKeys.add(key); + } + if (key.altCodeWhileTyping()) { + mAltCodeKeysWhileTyping.add(key); + } + } + + private int mMaxHeightCount = 0; + private int mMaxWidthCount = 0; + private final SparseIntArray mHeightHistogram = new SparseIntArray(); + private final SparseIntArray mWidthHistogram = new SparseIntArray(); + + private void clearHistogram() { + mMostCommonKeyHeight = 0; + mMaxHeightCount = 0; + mHeightHistogram.clear(); + + mMaxWidthCount = 0; + mMostCommonKeyWidth = 0; + mWidthHistogram.clear(); + } + + private static int updateHistogramCounter(final SparseIntArray histogram, final int key) { + final int index = histogram.indexOfKey(key); + final int count = (index >= 0 ? histogram.get(key) : 0) + 1; + histogram.put(key, count); + return count; + } + + private void updateHistogram(final Key key) { + final int height = key.mHeight + mVerticalGap; + final int heightCount = updateHistogramCounter(mHeightHistogram, height); + if (heightCount > mMaxHeightCount) { + mMaxHeightCount = heightCount; + mMostCommonKeyHeight = height; + } + + final int width = key.mWidth + mHorizontalGap; + final int widthCount = updateHistogramCounter(mWidthHistogram, width); + if (widthCount > mMaxWidthCount) { + mMaxWidthCount = widthCount; + mMostCommonKeyWidth = width; + } + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java new file mode 100644 index 000000000..eb17b0ea4 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.keyboard.internal; + +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.util.Xml; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.ResourceUtils; + +import org.xmlpull.v1.XmlPullParser; + +/** + * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate. + * Some of the key size defaults can be overridden per row from what the {@link Keyboard} + * defines. + */ +public class KeyboardRow { + // keyWidth enum constants + private static final int KEYWIDTH_NOT_ENUM = 0; + private static final int KEYWIDTH_FILL_RIGHT = -1; + + private final KeyboardParams mParams; + /** Default width of a key in this row. */ + private float mDefaultKeyWidth; + /** Default height of a key in this row. */ + public final int mRowHeight; + /** Default keyLabelFlags in this row. */ + private int mDefaultKeyLabelFlags; + /** Default backgroundType for this row */ + private int mDefaultBackgroundType; + + private final int mCurrentY; + // Will be updated by {@link Key}'s constructor. + private float mCurrentX; + + public KeyboardRow(final Resources res, final KeyboardParams params, final XmlPullParser parser, + final int y) { + mParams = params; + TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard); + mRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_rowHeight, + params.mBaseHeight, params.mDefaultRowHeight); + keyboardAttr.recycle(); + TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard_Key); + mDefaultKeyWidth = ResourceUtils.getDimensionOrFraction(keyAttr, + R.styleable.Keyboard_Key_keyWidth, + params.mBaseWidth, params.mDefaultKeyWidth); + mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType, + Key.BACKGROUND_TYPE_NORMAL); + keyAttr.recycle(); + + // TODO: Initialize this with attribute as backgroundType is done. + mDefaultKeyLabelFlags = 0; + mCurrentY = y; + mCurrentX = 0.0f; + } + + public float getDefaultKeyWidth() { + return mDefaultKeyWidth; + } + + public void setDefaultKeyWidth(final float defaultKeyWidth) { + mDefaultKeyWidth = defaultKeyWidth; + } + + public int getDefaultKeyLabelFlags() { + return mDefaultKeyLabelFlags; + } + + public void setDefaultKeyLabelFlags(final int keyLabelFlags) { + mDefaultKeyLabelFlags = keyLabelFlags; + } + + public int getDefaultBackgroundType() { + return mDefaultBackgroundType; + } + + public void setDefaultBackgroundType(final int backgroundType) { + mDefaultBackgroundType = backgroundType; + } + + public void setXPos(final float keyXPos) { + mCurrentX = keyXPos; + } + + public void advanceXPos(final float width) { + mCurrentX += width; + } + + public int getKeyY() { + return mCurrentY; + } + + public float getKeyX(final TypedArray keyAttr) { + final int keyboardRightEdge = mParams.mOccupiedWidth + - mParams.mHorizontalEdgesPadding; + if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) { + final float keyXPos = ResourceUtils.getDimensionOrFraction(keyAttr, + R.styleable.Keyboard_Key_keyXPos, mParams.mBaseWidth, 0); + if (keyXPos < 0) { + // If keyXPos is negative, the actual x-coordinate will be + // keyboardWidth + keyXPos. + // keyXPos shouldn't be less than mCurrentX because drawable area for this + // key starts at mCurrentX. Or, this key will overlaps the adjacent key on + // its left hand side. + return Math.max(keyXPos + keyboardRightEdge, mCurrentX); + } else { + return keyXPos + mParams.mHorizontalEdgesPadding; + } + } + return mCurrentX; + } + + public float getKeyWidth(final TypedArray keyAttr) { + return getKeyWidth(keyAttr, mCurrentX); + } + + public float getKeyWidth(final TypedArray keyAttr, final float keyXPos) { + final int widthType = ResourceUtils.getEnumValue(keyAttr, + R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM); + switch (widthType) { + case KEYWIDTH_FILL_RIGHT: + final int keyboardRightEdge = + mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding; + // If keyWidth is fillRight, the actual key width will be determined to fill + // out the area up to the right edge of the keyboard. + return keyboardRightEdge - keyXPos; + default: // KEYWIDTH_NOT_ENUM + return ResourceUtils.getDimensionOrFraction(keyAttr, + R.styleable.Keyboard_Key_keyWidth, + mParams.mBaseWidth, mDefaultKeyWidth); + } + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java new file mode 100644 index 000000000..f54617c98 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.keyboard.internal; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.latin.CollectionUtils; + +import java.util.HashMap; + +public class KeysCache { + private final HashMap mMap = CollectionUtils.newHashMap(); + + public void clear() { + mMap.clear(); + } + + public Key get(final Key key) { + final Key existingKey = mMap.get(key); + if (existingKey != null) { + // Reuse the existing element that equals to "key" without adding "key" to the map. + return existingKey; + } + mMap.put(key, key); + return key; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java new file mode 100644 index 000000000..5da26543f --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.keyboard.internal; + +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.latin.StringUtils; + +import java.util.Locale; + +public class MoreKeySpec { + public final int mCode; + public final String mLabel; + public final String mOutputText; + public final int mIconId; + + public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, final Locale locale, + final KeyboardCodesSet codesSet) { + mLabel = KeySpecParser.toUpperCaseOfStringForLocale( + KeySpecParser.getLabel(moreKeySpec), needsToUpperCase, locale); + final int code = KeySpecParser.toUpperCaseOfCodeForLocale( + KeySpecParser.getCode(moreKeySpec, codesSet), needsToUpperCase, locale); + if (code == Keyboard.CODE_UNSPECIFIED) { + // Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters + // upper case representation ("SS"). + mCode = Keyboard.CODE_OUTPUT_TEXT; + mOutputText = mLabel; + } else { + mCode = code; + mOutputText = KeySpecParser.toUpperCaseOfStringForLocale( + KeySpecParser.getOutputText(moreKeySpec), needsToUpperCase, locale); + } + mIconId = KeySpecParser.getIconId(moreKeySpec); + } + + @Override + public String toString() { + final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel + : KeySpecParser.PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId)); + final String output = (mCode == Keyboard.CODE_OUTPUT_TEXT ? mOutputText + : Keyboard.printableCode(mCode)); + if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) { + return output; + } else { + return label + "|" + output; + } + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java new file mode 100644 index 000000000..69dc01cd6 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.keyboard.internal; + +import com.android.inputmethod.latin.LatinImeLogger; + +public class TouchPositionCorrection { + private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3; + + public boolean mEnabled; + public float[] mXs; + public float[] mYs; + public float[] mRadii; + + public void load(final String[] data) { + final int dataLength = data.length; + if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) { + if (LatinImeLogger.sDBG) { + throw new RuntimeException( + "the size of touch position correction data is invalid"); + } + return; + } + + final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE; + mXs = new float[length]; + mYs = new float[length]; + mRadii = new float[length]; + try { + for (int i = 0; i < dataLength; ++i) { + final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE; + final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE; + final float value = Float.parseFloat(data[i]); + if (type == 0) { + mXs[index] = value; + } else if (type == 1) { + mYs[index] = value; + } else { + mRadii[index] = value; + } + } + } catch (NumberFormatException e) { + if (LatinImeLogger.sDBG) { + throw new RuntimeException( + "the number format for touch position correction data is invalid"); + } + mXs = null; + mYs = null; + mRadii = null; + } + } + + // TODO: Remove this method. + public void setEnabled(final boolean enabled) { + mEnabled = enabled; + } + + public boolean isValid() { + return mEnabled && mXs != null && mYs != null && mRadii != null + && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0; + } +} diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java index 58b01aa55..1f883aa60 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java @@ -23,7 +23,9 @@ import android.graphics.drawable.Drawable; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardSwitcher; +import com.android.inputmethod.keyboard.internal.KeyboardBuilder; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; +import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.Utils; @@ -31,145 +33,149 @@ import com.android.inputmethod.latin.Utils; public class MoreSuggestions extends Keyboard { public static final int SUGGESTION_CODE_BASE = 1024; - MoreSuggestions(Builder.MoreSuggestionsParam params) { + MoreSuggestions(final MoreSuggestionsParam params) { super(params); } - public static class Builder extends Keyboard.Builder { + private static class MoreSuggestionsParam extends KeyboardParams { + private final int[] mWidths = new int[SuggestionStripView.MAX_SUGGESTIONS]; + private final int[] mRowNumbers = new int[SuggestionStripView.MAX_SUGGESTIONS]; + private final int[] mColumnOrders = new int[SuggestionStripView.MAX_SUGGESTIONS]; + private final int[] mNumColumnsInRow = new int[SuggestionStripView.MAX_SUGGESTIONS]; + private static final int MAX_COLUMNS_IN_ROW = 3; + private int mNumRows; + public Drawable mDivider; + public int mDividerWidth; + + public MoreSuggestionsParam() { + super(); + } + + public int layout(final SuggestedWords suggestions, final int fromPos, final int maxWidth, + final int minWidth, final int maxRow, final MoreSuggestionsView view) { + clearKeys(); + final Resources res = view.getContext().getResources(); + mDivider = res.getDrawable(R.drawable.more_suggestions_divider); + mDividerWidth = mDivider.getIntrinsicWidth(); + final int padding = (int) res.getDimension( + R.dimen.more_suggestions_key_horizontal_padding); + final Paint paint = view.newDefaultLabelPaint(); + + int row = 0; + int pos = fromPos, rowStartPos = fromPos; + final int size = Math.min(suggestions.size(), SuggestionStripView.MAX_SUGGESTIONS); + while (pos < size) { + final String word = suggestions.getWord(pos).toString(); + // TODO: Should take care of text x-scaling. + mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding; + final int numColumn = pos - rowStartPos + 1; + final int columnWidth = + (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn; + if (numColumn > MAX_COLUMNS_IN_ROW + || !fitInWidth(rowStartPos, pos + 1, columnWidth)) { + if ((row + 1) >= maxRow) { + break; + } + mNumColumnsInRow[row] = pos - rowStartPos; + rowStartPos = pos; + row++; + } + mColumnOrders[pos] = pos - rowStartPos; + mRowNumbers[pos] = row; + pos++; + } + mNumColumnsInRow[row] = pos - rowStartPos; + mNumRows = row + 1; + mBaseWidth = mOccupiedWidth = Math.max( + minWidth, calcurateMaxRowWidth(fromPos, pos)); + mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap; + return pos - fromPos; + } + + private boolean fitInWidth(final int startPos, final int endPos, final int width) { + for (int pos = startPos; pos < endPos; pos++) { + if (mWidths[pos] > width) + return false; + } + return true; + } + + private int calcurateMaxRowWidth(final int startPos, final int endPos) { + int maxRowWidth = 0; + int pos = startPos; + for (int row = 0; row < mNumRows; row++) { + final int numColumnInRow = mNumColumnsInRow[row]; + int maxKeyWidth = 0; + while (pos < endPos && mRowNumbers[pos] == row) { + maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]); + pos++; + } + maxRowWidth = Math.max(maxRowWidth, + maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1)); + } + return maxRowWidth; + } + + private static final int[][] COLUMN_ORDER_TO_NUMBER = { + { 0, }, + { 1, 0, }, + { 2, 0, 1}, + }; + + public int getNumColumnInRow(final int pos) { + return mNumColumnsInRow[mRowNumbers[pos]]; + } + + public int getColumnNumber(final int pos) { + final int columnOrder = mColumnOrders[pos]; + final int numColumn = getNumColumnInRow(pos); + return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder]; + } + + public int getX(final int pos) { + final int columnNumber = getColumnNumber(pos); + return columnNumber * (getWidth(pos) + mDividerWidth); + } + + public int getY(final int pos) { + final int row = mRowNumbers[pos]; + return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding; + } + + public int getWidth(final int pos) { + final int numColumnInRow = getNumColumnInRow(pos); + return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow; + } + + public void markAsEdgeKey(final Key key, final int pos) { + final int row = mRowNumbers[pos]; + if (row == 0) + key.markAsBottomEdge(this); + if (row == mNumRows - 1) + key.markAsTopEdge(this); + + final int numColumnInRow = mNumColumnsInRow[row]; + final int column = getColumnNumber(pos); + if (column == 0) + key.markAsLeftEdge(this); + if (column == numColumnInRow - 1) + key.markAsRightEdge(this); + } + } + + public static class Builder extends KeyboardBuilder { private final MoreSuggestionsView mPaneView; private SuggestedWords mSuggestions; private int mFromPos; private int mToPos; - public static class MoreSuggestionsParam extends Keyboard.Params { - private final int[] mWidths = new int[SuggestionStripView.MAX_SUGGESTIONS]; - private final int[] mRowNumbers = new int[SuggestionStripView.MAX_SUGGESTIONS]; - private final int[] mColumnOrders = new int[SuggestionStripView.MAX_SUGGESTIONS]; - private final int[] mNumColumnsInRow = new int[SuggestionStripView.MAX_SUGGESTIONS]; - private static final int MAX_COLUMNS_IN_ROW = 3; - private int mNumRows; - public Drawable mDivider; - public int mDividerWidth; - - public int layout(SuggestedWords suggestions, int fromPos, int maxWidth, int minWidth, - int maxRow, MoreSuggestionsView view) { - clearKeys(); - final Resources res = view.getContext().getResources(); - mDivider = res.getDrawable(R.drawable.more_suggestions_divider); - mDividerWidth = mDivider.getIntrinsicWidth(); - final int padding = (int) res.getDimension( - R.dimen.more_suggestions_key_horizontal_padding); - final Paint paint = view.newDefaultLabelPaint(); - - int row = 0; - int pos = fromPos, rowStartPos = fromPos; - final int size = Math.min(suggestions.size(), SuggestionStripView.MAX_SUGGESTIONS); - while (pos < size) { - final String word = suggestions.getWord(pos).toString(); - // TODO: Should take care of text x-scaling. - mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding; - final int numColumn = pos - rowStartPos + 1; - final int columnWidth = - (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn; - if (numColumn > MAX_COLUMNS_IN_ROW - || !fitInWidth(rowStartPos, pos + 1, columnWidth)) { - if ((row + 1) >= maxRow) { - break; - } - mNumColumnsInRow[row] = pos - rowStartPos; - rowStartPos = pos; - row++; - } - mColumnOrders[pos] = pos - rowStartPos; - mRowNumbers[pos] = row; - pos++; - } - mNumColumnsInRow[row] = pos - rowStartPos; - mNumRows = row + 1; - mBaseWidth = mOccupiedWidth = Math.max( - minWidth, calcurateMaxRowWidth(fromPos, pos)); - mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap; - return pos - fromPos; - } - - private boolean fitInWidth(int startPos, int endPos, int width) { - for (int pos = startPos; pos < endPos; pos++) { - if (mWidths[pos] > width) - return false; - } - return true; - } - - private int calcurateMaxRowWidth(int startPos, int endPos) { - int maxRowWidth = 0; - int pos = startPos; - for (int row = 0; row < mNumRows; row++) { - final int numColumnInRow = mNumColumnsInRow[row]; - int maxKeyWidth = 0; - while (pos < endPos && mRowNumbers[pos] == row) { - maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]); - pos++; - } - maxRowWidth = Math.max(maxRowWidth, - maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1)); - } - return maxRowWidth; - } - - private static final int[][] COLUMN_ORDER_TO_NUMBER = { - { 0, }, - { 1, 0, }, - { 2, 0, 1}, - }; - - public int getNumColumnInRow(int pos) { - return mNumColumnsInRow[mRowNumbers[pos]]; - } - - public int getColumnNumber(int pos) { - final int columnOrder = mColumnOrders[pos]; - final int numColumn = getNumColumnInRow(pos); - return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder]; - } - - public int getX(int pos) { - final int columnNumber = getColumnNumber(pos); - return columnNumber * (getWidth(pos) + mDividerWidth); - } - - public int getY(int pos) { - final int row = mRowNumbers[pos]; - return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding; - } - - public int getWidth(int pos) { - final int numColumnInRow = getNumColumnInRow(pos); - return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow; - } - - public void markAsEdgeKey(Key key, int pos) { - final int row = mRowNumbers[pos]; - if (row == 0) - key.markAsBottomEdge(this); - if (row == mNumRows - 1) - key.markAsTopEdge(this); - - final int numColumnInRow = mNumColumnsInRow[row]; - final int column = getColumnNumber(pos); - if (column == 0) - key.markAsLeftEdge(this); - if (column == numColumnInRow - 1) - key.markAsRightEdge(this); - } - } - - public Builder(MoreSuggestionsView paneView) { + public Builder(final MoreSuggestionsView paneView) { super(paneView.getContext(), new MoreSuggestionsParam()); mPaneView = paneView; } - public Builder layout(SuggestedWords suggestions, int fromPos, int maxWidth, - int minWidth, int maxRow) { + public Builder layout(final SuggestedWords suggestions, final int fromPos, + final int maxWidth, final int minWidth, final int maxRow) { final Keyboard keyboard = KeyboardSwitcher.getInstance().getKeyboard(); final int xmlId = R.xml.kbd_suggestions_pane_template; load(xmlId, keyboard.mId); @@ -183,25 +189,6 @@ public class MoreSuggestions extends Keyboard { return this; } - private static class Divider extends Key.Spacer { - private final Drawable mIcon; - - public Divider(Keyboard.Params params, Drawable icon, int x, int y, int width, - int height) { - super(params, x, y, width, height); - mIcon = icon; - } - - @Override - public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) { - // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the - // constructor. - // TODO: Drawable itself should have an alpha value. - mIcon.setAlpha(128); - return mIcon; - } - } - @Override public MoreSuggestions build() { final MoreSuggestionsParam params = mParams; @@ -228,4 +215,23 @@ public class MoreSuggestions extends Keyboard { return new MoreSuggestions(params); } } + + private static class Divider extends Key.Spacer { + private final Drawable mIcon; + + public Divider(final KeyboardParams params, final Drawable icon, final int x, + final int y, final int width, final int height) { + super(params, x, y, width, height); + mIcon = icon; + } + + @Override + public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { + // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the + // constructor. + // TODO: Drawable itself should have an alpha value. + mIcon.setAlpha(128); + return mIcon; + } + } } diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java index 5c6c83432..2a244a772 100644 --- a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java +++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java @@ -18,7 +18,7 @@ package com.android.inputmethod.keyboard; import android.test.AndroidTestCase; -import com.android.inputmethod.keyboard.MoreKeysKeyboard.Builder.MoreKeysKeyboardParams; +import com.android.inputmethod.keyboard.MoreKeysKeyboard.MoreKeysKeyboardParams; public class MoreKeysKeyboardBuilderFixedOrderTests extends AndroidTestCase { private static final int WIDTH = 10; diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java index 31f0e0fef..e6c76db85 100644 --- a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java +++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java @@ -18,7 +18,7 @@ package com.android.inputmethod.keyboard; import android.test.AndroidTestCase; -import com.android.inputmethod.keyboard.MoreKeysKeyboard.Builder.MoreKeysKeyboardParams; +import com.android.inputmethod.keyboard.MoreKeysKeyboard.MoreKeysKeyboardParams; public class MoreKeysKeyboardBuilderTests extends AndroidTestCase { private static final int WIDTH = 10; diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java index 0b174a7e6..1ab577557 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java @@ -23,7 +23,6 @@ import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UN import android.test.AndroidTestCase; import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec; import java.util.Arrays; import java.util.Locale;