diff options
Diffstat (limited to 'java/src')
25 files changed, 2483 insertions, 2056 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index 178c9ff05..e941cc7fc 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -32,10 +32,13 @@ 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; import org.xmlpull.v1.XmlPullParser; @@ -54,7 +57,6 @@ public class Key { * The key code (unicode or custom code) that this key generates. */ public final int mCode; - public final int mAltCode; /** Label to display */ public final String mLabel; @@ -89,22 +91,11 @@ public class Key { /** Icon to display instead of a label. Icon takes precedence over a label */ private final int mIconId; - /** Icon for disabled state */ - private final int mDisabledIconId; - /** Preview version of the icon, for the preview popup */ - private final int mPreviewIconId; /** Width of the key, not including the gap */ public final int mWidth; /** Height of the key, not including the gap */ public final int mHeight; - /** The horizontal gap around this key */ - public final int mHorizontalGap; - /** The vertical gap below this key */ - public final int mVerticalGap; - /** The visual insets */ - public final int mVisualInsetsLeft; - public final int mVisualInsetsRight; /** X coordinate of the key in the keyboard layout */ public final int mX; /** Y coordinate of the key in the keyboard layout */ @@ -112,8 +103,6 @@ public class Key { /** Hit bounding box of the key */ public final Rect mHitBox = new Rect(); - /** Text to output when pressed. This can be multiple characters, like ".com" */ - public final CharSequence mOutputText; /** More keys */ public final MoreKeySpec[] mMoreKeys; /** More keys column number and flags */ @@ -143,6 +132,32 @@ public class Key { private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04; private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08; + private final OptionalAttributes mOptionalAttributes; + + private static class OptionalAttributes { + /** Text to output when pressed. This can be multiple characters, like ".com" */ + public final String mOutputText; + public final int mAltCode; + /** Icon for disabled state */ + public final int mDisabledIconId; + /** Preview version of the icon, for the preview popup */ + public final int mPreviewIconId; + /** The visual insets */ + public final int mVisualInsetsLeft; + public final int mVisualInsetsRight; + + public OptionalAttributes(final String outputText, final int altCode, + final int disabledIconId, final int previewIconId, + final int visualInsetsLeft, final int visualInsetsRight) { + mOutputText = outputText; + mAltCode = altCode; + mDisabledIconId = disabledIconId; + mPreviewIconId = previewIconId; + mVisualInsetsLeft = visualInsetsLeft; + mVisualInsetsRight = visualInsetsRight; + } + } + private final int mHashCode; /** The current pressed state of this key */ @@ -153,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); } @@ -162,13 +177,11 @@ 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; - mHorizontalGap = params.mHorizontalGap; - mVerticalGap = params.mVerticalGap; - mVisualInsetsLeft = mVisualInsetsRight = 0; - mWidth = width - mHorizontalGap; + mWidth = width - params.mHorizontalGap; mHintLabel = hintLabel; mLabelFlags = labelFlags; mBackgroundType = BACKGROUND_TYPE_NORMAL; @@ -176,15 +189,17 @@ public class Key { mMoreKeys = null; mMoreKeysColumnAndFlags = 0; mLabel = label; - mOutputText = outputText; + if (outputText == null) { + mOptionalAttributes = null; + } else { + mOptionalAttributes = new OptionalAttributes(outputText, CODE_UNSPECIFIED, + ICON_UNDEFINED, ICON_UNDEFINED, 0, 0); + } mCode = code; mEnabled = (code != CODE_UNSPECIFIED); - mAltCode = CODE_UNSPECIFIED; mIconId = iconId; - mDisabledIconId = ICON_UNDEFINED; - mPreviewIconId = ICON_UNDEFINED; // Horizontal gap is divided equally to both sides of the key. - mX = x + mHorizontalGap / 2; + mX = x + params.mHorizontalGap / 2; mY = y; mHitBox.set(x, y, x + width + 1, y + height); @@ -201,12 +216,11 @@ 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; - mVerticalGap = params.mVerticalGap; - mHeight = keyHeight - mVerticalGap; + mHeight = keyHeight - params.mVerticalGap; final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard_Key); @@ -220,7 +234,6 @@ public class Key { mX = Math.round(keyXPos + horizontalGap / 2); mY = keyYPos; mWidth = Math.round(keyWidth - horizontalGap); - mHorizontalGap = Math.round(horizontalGap); mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1, keyYPos + keyHeight); // Update row to have current x coordinate. @@ -229,15 +242,15 @@ public class Key { mBackgroundType = style.getInt(keyAttr, R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType()); - mVisualInsetsLeft = Math.round(Keyboard.Builder.getDimensionOrFraction(keyAttr, + final int visualInsetsLeft = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr, R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0)); - mVisualInsetsRight = Math.round(Keyboard.Builder.getDimensionOrFraction(keyAttr, + final int visualInsetsRight = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr, R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0)); mIconId = KeySpecParser.getIconId(style.getString(keyAttr, R.styleable.Keyboard_Key_keyIcon)); - mDisabledIconId = KeySpecParser.getIconId(style.getString(keyAttr, + final int disabledIconId = KeySpecParser.getIconId(style.getString(keyAttr, R.styleable.Keyboard_Key_keyIconDisabled)); - mPreviewIconId = KeySpecParser.getIconId(style.getString(keyAttr, + final int previewIconId = KeySpecParser.getIconId(style.getString(keyAttr, R.styleable.Keyboard_Key_keyIconPreview)); mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags) @@ -331,11 +344,20 @@ public class Key { } else { mCode = KeySpecParser.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale); } - mOutputText = outputText; - mAltCode = KeySpecParser.toUpperCaseOfCodeForLocale( + final int altCode = KeySpecParser.toUpperCaseOfCodeForLocale( KeySpecParser.parseCode(style.getString(keyAttr, R.styleable.Keyboard_Key_altCode), params.mCodesSet, CODE_UNSPECIFIED), needsToUpperCase, locale); + if (outputText == null && altCode == CODE_UNSPECIFIED + && disabledIconId == ICON_UNDEFINED && previewIconId == ICON_UNDEFINED + && visualInsetsLeft == 0 && visualInsetsRight == 0) { + mOptionalAttributes = null; + } else { + mOptionalAttributes = new OptionalAttributes(outputText, altCode, + disabledIconId, previewIconId, + visualInsetsLeft, visualInsetsRight); + } + mHashCode = computeHashCode(this); keyAttr.recycle(); @@ -345,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: @@ -358,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, @@ -370,22 +392,22 @@ public class Key { key.mIconId, key.mBackgroundType, Arrays.hashCode(key.mMoreKeys), - key.mOutputText, + key.getOutputText(), key.mActionFlags, key.mLabelFlags, // Key can be distinguishable without the following members. - // key.mAltCode, - // key.mDisabledIconId, - // key.mPreviewIconId, + // key.mOptionalAttributes.mAltCode, + // key.mOptionalAttributes.mDisabledIconId, + // key.mOptionalAttributes.mPreviewIconId, // key.mHorizontalGap, // key.mVerticalGap, - // key.mVisualInsetLeft, - // key.mVisualInsetRight, + // key.mOptionalAttributes.mVisualInsetLeft, + // key.mOptionalAttributes.mVisualInsetRight, // key.mMaxMoreKeysColumn, }); } - private boolean equals(Key o) { + private boolean equals(final Key o) { if (this == o) return true; return o.mX == mX && o.mY == mY @@ -397,7 +419,7 @@ public class Key { && o.mIconId == mIconId && o.mBackgroundType == mBackgroundType && Arrays.equals(o.mMoreKeys, mMoreKeys) - && TextUtils.equals(o.mOutputText, mOutputText) + && TextUtils.equals(o.getOutputText(), getOutputText()) && o.mActionFlags == mActionFlags && o.mLabelFlags == mLabelFlags; } @@ -408,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); } @@ -425,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"; @@ -436,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; } @@ -482,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; @@ -493,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; @@ -577,8 +599,20 @@ public class Key { return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY) != 0; } - public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) { - final int iconId = mEnabled ? mIconId : mDisabledIconId; + public String getOutputText() { + final OptionalAttributes attrs = mOptionalAttributes; + return (attrs != null) ? attrs.mOutputText : null; + } + + public int getAltCode() { + final OptionalAttributes attrs = mOptionalAttributes; + return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED; + } + + 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; final Drawable icon = iconSet.getIconDrawable(iconId); if (icon != null) { icon.setAlpha(alpha); @@ -586,10 +620,22 @@ public class Key { return icon; } - public Drawable getPreviewIcon(KeyboardIconsSet iconSet) { - return mPreviewIconId != ICON_UNDEFINED - ? iconSet.getIconDrawable(mPreviewIconId) - : iconSet.getIconDrawable(mIconId); + public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) { + final OptionalAttributes attrs = mOptionalAttributes; + final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED; + return previewIconId != ICON_UNDEFINED + ? iconSet.getIconDrawable(previewIconId) : iconSet.getIconDrawable(mIconId); + } + + public int getDrawX() { + final OptionalAttributes attrs = mOptionalAttributes; + return (attrs == null) ? mX : mX + attrs.mVisualInsetsLeft; + } + + public int getDrawWidth() { + final OptionalAttributes attrs = mOptionalAttributes; + return (attrs == null) ? mWidth + : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight; } /** @@ -614,7 +660,7 @@ public class Key { return mEnabled; } - public void setEnabled(boolean enabled) { + public void setEnabled(final boolean enabled) { mEnabled = enabled; } @@ -624,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); } @@ -636,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; @@ -718,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 a5f9e9e75..c1b007d2e 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -16,40 +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.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 @@ -123,6 +98,10 @@ public class Keyboard { /** Per keyboard key visual parameters */ public final Typeface mKeyTypeface; + public final float mKeyLetterRatio; + public final int mKeyLetterSize; + public final float mKeyHintLetterRatio; + public final float mKeyShiftedLetterHintRatio; public final int mMostCommonKeyHeight; public final int mMostCommonKeyWidth; @@ -144,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; @@ -155,6 +134,10 @@ public class Keyboard { mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn; mKeyTypeface = params.mKeyTypeface; + mKeyLetterRatio = params.mKeyLetterRatio; + mKeyLetterSize = params.mKeyLetterSize; + mKeyHintLetterRatio = params.mKeyHintLetterRatio; + mKeyShiftedLetterHintRatio = params.mKeyShiftedLetterHintRatio; mTopPadding = params.mTopPadding; mVerticalGap = params.mVerticalGap; @@ -171,7 +154,7 @@ public class Keyboard { mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled; } - public boolean hasProximityCharsCorrection(int code) { + public boolean hasProximityCharsCorrection(final int code) { if (!mProximityCharsCorrectionEnabled) { return false; } @@ -187,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; } @@ -208,7 +191,7 @@ public class Keyboard { } } - public boolean hasKey(Key aKey) { + public boolean hasKey(final Key aKey) { if (mKeyCache.indexOfValue(aKey) >= 0) { return true; } @@ -222,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; } @@ -231,174 +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 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<Key> mKeys = CollectionUtils.newHashSet(); - public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList(); - public final ArrayList<Key> 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 + key.mVerticalGap; - final int heightCount = updateHistogramCounter(mHeightHistogram, height); - if (heightCount > mMaxHeightCount) { - mMaxHeightCount = heightCount; - mMostCommonKeyHeight = height; - } - - final int width = key.mWidth + key.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 @@ -406,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"; @@ -435,953 +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: - * <pre> - * <!-- 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> - * </pre> - * The XML file which is included in other file must have <merge> as root element, - * such as: - * <pre> - * <!-- xml/other_keys.xml --> - * <merge> - * <Key key_attributes* /> - * ... - * </merge> - * </pre> - * and - * <pre> - * <!-- xml/other_rows.xml --> - * <merge> - * <Row row_attributes*> - * <Key key_attributes* /> - * </Row> - * ... - * </merge> - * </pre> - * You can also use switch-case-default tags to select Rows and Keys. - * <pre> - * <switch> - * <case case_attribute*> - * <!-- Any valid tags at switch position --> - * </case> - * ... - * <default> - * <!-- Any valid tags at switch position --> - * </default> - * </switch> - * </pre> - * You can declare Key style and specify styles within Key tags. - * <pre> - * <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" ... /> - * </pre> - */ - - // TODO: Move this class to internal package. - public static class Builder<KP extends Params> { - 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)Builder.getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_rowHeight, - params.mBaseHeight, params.mDefaultRowHeight); - keyboardAttr.recycle(); - TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser), - R.styleable.Keyboard_Key); - mDefaultKeyWidth = Builder.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 <Row> 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 = Builder.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 = Builder.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 Builder.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<KP> 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 = getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2); - float minKeyboardHeight = 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 = -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)getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0); - params.mBottomPadding = (int)getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0); - params.mHorizontalEdgesPadding = (int)getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_keyboardHorizontalEdgesPadding, - mParams.mOccupiedWidth, 0); - - params.mBaseWidth = params.mOccupiedWidth - params.mHorizontalEdgesPadding * 2 - - params.mHorizontalCenterPadding; - params.mDefaultKeyWidth = (int)getDimensionOrFraction(keyAttr, - R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth, - params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS); - params.mHorizontalGap = (int)getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0); - params.mVerticalGap = (int)getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_verticalGap, params.mOccupiedHeight, 0); - params.mBaseHeight = params.mOccupiedHeight - params.mTopPadding - - params.mBottomPadding + params.mVerticalGap; - params.mDefaultRowHeight = (int)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.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<Void> job = new RunInLocale<Void>() { - @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("</%s>", 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("</%s>", 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("</%s> 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 <merge> 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("</%s>", 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 <case> does not have "index" attribute, that means this <case> 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 <case> does not have "index" attribute, that means this <case> 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 <case> does not have "index" attribute, that means this <case> is wild-card for - // the attribute. - return !a.hasValue(index) - || stringArrayContains(a.getString(index).split("\\|"), value); - } - - private static boolean matchTypedValue(TypedArray a, int index, int intValue, - String strValue) { - // If <case> does not have "index" attribute, that means this <case> is wild-card for - // the attribute. - final TypedValue v = a.peekValue(index); - if (v == null) { - return true; - } - if (isIntegerValue(v)) { - return intValue == a.getInt(index, 0); - } else if (isStringValue(v)) { - return stringArrayContains(a.getString(index).split("\\|"), strValue); - } - return false; - } - - private static boolean stringArrayContains(String[] array, String value) { - for (final String elem : array) { - if (elem.equals(value)) { - return true; - } - } - 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; - } - - public static float getDimensionOrFraction(TypedArray a, int index, int base, - float defValue) { - final TypedValue value = a.peekValue(index); - if (value == null) { - return defValue; - } - if (isFractionValue(value)) { - return a.getFraction(index, base, base, defValue); - } else if (isDimensionValue(value)) { - return a.getDimension(index, defValue); - } - return defValue; - } - - public static int getEnumValue(TypedArray a, int index, int defValue) { - final TypedValue value = a.peekValue(index); - if (value == null) { - return defValue; - } - if (isIntegerValue(value)) { - return a.getInt(index, defValue); - } - return defValue; - } - - private static boolean isFractionValue(TypedValue v) { - return v.type == TypedValue.TYPE_FRACTION; - } - - private static boolean isDimensionValue(TypedValue v) { - return v.type == TypedValue.TYPE_DIMENSION; - } - - private static boolean isIntegerValue(TypedValue v) { - return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT; - } - - private static boolean isStringValue(TypedValue v) { - return v.type == TypedValue.TYPE_STRING; - } - - 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<Key, Key> 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<ElementParams> 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<Keyboard> ref = sKeyboardCache.get(id); Keyboard keyboard = (ref == null) ? null : ref.get(); if (keyboard == null) { - final Keyboard.Builder<Keyboard.Params> builder = - new Keyboard.Builder<Keyboard.Params>(mContext, new Keyboard.Params()); + final KeyboardBuilder<KeyboardParams> builder = + new KeyboardBuilder<KeyboardParams>(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/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index a4797095f..1e5ca9bf9 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -38,6 +38,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import com.android.inputmethod.keyboard.internal.KeyDrawParams; +import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams; import com.android.inputmethod.keyboard.internal.PreviewPlacerView; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; @@ -84,8 +86,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // Miscellaneous constants private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable }; - private static final float UNDEFINED_RATIO = -1.0f; - private static final int UNDEFINED_DIMENSION = -1; // XML attributes protected final float mVerticalCorrection; @@ -180,187 +180,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } } - // Move this class to internal package - protected static class KeyDrawParams { - // XML attributes - public final int mKeyTextColor; - public final int mKeyTextInactivatedColor; - public final float mKeyLabelHorizontalPadding; - public final float mKeyHintLetterPadding; - public final float mKeyPopupHintLetterPadding; - public final float mKeyShiftedLetterHintPadding; - public final int mShadowColor; - public final float mShadowRadius; - public final Drawable mKeyBackground; - public final int mKeyHintLetterColor; - public final int mKeyHintLabelColor; - public final int mKeyShiftedLetterHintInactivatedColor; - public final int mKeyShiftedLetterHintActivatedColor; - - private final Typeface mKeyTypefaceFromKeyboardView; - private final float mKeyLetterRatio; - private final float mKeyLargeLetterRatio; - private final float mKeyLabelRatio; - private final float mKeyLargeLabelRatio; - private final float mKeyHintLetterRatio; - private final float mKeyShiftedLetterHintRatio; - private final float mKeyHintLabelRatio; - - public final Rect mPadding = new Rect(); - public Typeface mKeyTypeface; - public int mKeyLetterSize; - public int mKeyLargeLetterSize; - public int mKeyLabelSize; - public int mKeyLargeLabelSize; - public int mKeyHintLetterSize; - public int mKeyShiftedLetterHintSize; - public int mKeyHintLabelSize; - public int mAnimAlpha; - - public KeyDrawParams(final TypedArray a) { - mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground); - if (!isValidFraction(mKeyLetterRatio = getFraction(a, - R.styleable.KeyboardView_keyLetterSize))) { - mKeyLetterSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLetterSize); - } - if (!isValidFraction(mKeyLabelRatio = getFraction(a, - R.styleable.KeyboardView_keyLabelSize))) { - mKeyLabelSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLabelSize); - } - mKeyLargeLabelRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLabelRatio); - mKeyLargeLetterRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLetterRatio); - mKeyHintLetterRatio = getFraction(a, R.styleable.KeyboardView_keyHintLetterRatio); - mKeyShiftedLetterHintRatio = getFraction(a, - R.styleable.KeyboardView_keyShiftedLetterHintRatio); - mKeyHintLabelRatio = getFraction(a, R.styleable.KeyboardView_keyHintLabelRatio); - mKeyLabelHorizontalPadding = a.getDimension( - R.styleable.KeyboardView_keyLabelHorizontalPadding, 0); - mKeyHintLetterPadding = a.getDimension( - R.styleable.KeyboardView_keyHintLetterPadding, 0); - mKeyPopupHintLetterPadding = a.getDimension( - R.styleable.KeyboardView_keyPopupHintLetterPadding, 0); - mKeyShiftedLetterHintPadding = a.getDimension( - R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0); - mKeyTextColor = a.getColor(R.styleable.KeyboardView_keyTextColor, 0xFF000000); - mKeyTextInactivatedColor = a.getColor( - R.styleable.KeyboardView_keyTextInactivatedColor, 0xFF000000); - mKeyHintLetterColor = a.getColor(R.styleable.KeyboardView_keyHintLetterColor, 0); - mKeyHintLabelColor = a.getColor(R.styleable.KeyboardView_keyHintLabelColor, 0); - mKeyShiftedLetterHintInactivatedColor = a.getColor( - R.styleable.KeyboardView_keyShiftedLetterHintInactivatedColor, 0); - mKeyShiftedLetterHintActivatedColor = a.getColor( - R.styleable.KeyboardView_keyShiftedLetterHintActivatedColor, 0); - mKeyTypefaceFromKeyboardView = Typeface.defaultFromStyle( - a.getInt(R.styleable.KeyboardView_keyTypeface, Typeface.NORMAL)); - mKeyTypeface = mKeyTypefaceFromKeyboardView; - mShadowColor = a.getColor(R.styleable.KeyboardView_shadowColor, 0); - mShadowRadius = a.getFloat(R.styleable.KeyboardView_shadowRadius, 0f); - - mKeyBackground.getPadding(mPadding); - } - - public void updateParams(final Keyboard keyboard) { - mKeyTypeface = (keyboard.mKeyTypeface != null) - ? keyboard.mKeyTypeface : mKeyTypefaceFromKeyboardView; - final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; - if (isValidFraction(mKeyLetterRatio)) { - mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio); - } - if (isValidFraction(mKeyLabelRatio)) { - mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio); - } - mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio); - mKeyLargeLetterSize = (int)(keyHeight * mKeyLargeLetterRatio); - mKeyHintLetterSize = (int)(keyHeight * mKeyHintLetterRatio); - mKeyShiftedLetterHintSize = (int)(keyHeight * mKeyShiftedLetterHintRatio); - mKeyHintLabelSize = (int)(keyHeight * mKeyHintLabelRatio); - } - - public void blendAlpha(final Paint paint) { - final int color = paint.getColor(); - paint.setARGB((paint.getAlpha() * mAnimAlpha) / Constants.Color.ALPHA_OPAQUE, - Color.red(color), Color.green(color), Color.blue(color)); - } - } - - // TODO: Move this class to internal package. - /* package */ static class KeyPreviewDrawParams { - // XML attributes. - public final Drawable mPreviewBackground; - public final Drawable mPreviewLeftBackground; - public final Drawable mPreviewRightBackground; - public final int mPreviewTextColor; - public final int mPreviewOffset; - public final int mPreviewHeight; - public final int mLingerTimeout; - - private final float mPreviewTextRatio; - - // The graphical geometry of the key preview. - // <-width-> - // +-------+ ^ - // | | | - // |preview| height (visible) - // | | | - // + + ^ v - // \ / |offset - // +-\ /-+ v - // | +-+ | - // |parent | - // | key| - // +-------+ - // The background of a {@link TextView} being used for a key preview may have invisible - // paddings. To align the more keys keyboard panel's visible part with the visible part of - // the background, we need to record the width and height of key preview that don't include - // invisible paddings. - public int mPreviewVisibleWidth; - public int mPreviewVisibleHeight; - // The key preview may have an arbitrary offset and its background that may have a bottom - // padding. To align the more keys keyboard and the key preview we also need to record the - // offset between the top edge of parent key and the bottom of the visible part of key - // preview background. - public int mPreviewVisibleOffset; - - public Typeface mKeyTypeface; - public int mPreviewTextSize; - public int mKeyLetterSize; - public final int[] mCoordinates = new int[2]; - - private static final int PREVIEW_ALPHA = 240; - - public KeyPreviewDrawParams(final TypedArray a) { - mPreviewBackground = a.getDrawable(R.styleable.KeyboardView_keyPreviewBackground); - mPreviewLeftBackground = a.getDrawable( - R.styleable.KeyboardView_keyPreviewLeftBackground); - mPreviewRightBackground = a.getDrawable( - R.styleable.KeyboardView_keyPreviewRightBackground); - setAlpha(mPreviewBackground, PREVIEW_ALPHA); - setAlpha(mPreviewLeftBackground, PREVIEW_ALPHA); - setAlpha(mPreviewRightBackground, PREVIEW_ALPHA); - mPreviewOffset = a.getDimensionPixelOffset( - R.styleable.KeyboardView_keyPreviewOffset, 0); - mPreviewHeight = a.getDimensionPixelSize( - R.styleable.KeyboardView_keyPreviewHeight, 80); - mPreviewTextRatio = getFraction(a, R.styleable.KeyboardView_keyPreviewTextRatio); - mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0); - mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0); - } - - public void updateParams(final Keyboard keyboard, final KeyDrawParams keyDrawParams) { - final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; - if (isValidFraction(mPreviewTextRatio)) { - mPreviewTextSize = (int)(keyHeight * mPreviewTextRatio); - } - mKeyLetterSize = keyDrawParams.mKeyLetterSize; - mKeyTypeface = keyDrawParams.mKeyTypeface; - } - - private static void setAlpha(final Drawable drawable, final int alpha) { - if (drawable == null) return; - drawable.setAlpha(alpha); - } - } - public KeyboardView(Context context, AttributeSet attrs) { this(context, attrs, R.attr.keyboardViewStyle); } @@ -368,45 +187,31 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { public KeyboardView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - final TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView); - mKeyDrawParams = new KeyDrawParams(a); - mKeyPreviewDrawParams = new KeyPreviewDrawParams(a); + final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs, + R.styleable.KeyboardView, defStyle, R.style.KeyboardView); + final TypedArray keyAttr = context.obtainStyledAttributes(attrs, + R.styleable.Keyboard_Key, defStyle, R.style.KeyboardView); + mKeyDrawParams = new KeyDrawParams(keyboardViewAttr, keyAttr); + mKeyPreviewDrawParams = new KeyPreviewDrawParams(keyboardViewAttr, keyAttr); mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout; - mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0); + mKeyPreviewLayoutId = keyboardViewAttr.getResourceId( + R.styleable.KeyboardView_keyPreviewLayout, 0); if (mKeyPreviewLayoutId == 0) { mShowKeyPreviewPopup = false; } - mVerticalCorrection = a.getDimensionPixelOffset( + mVerticalCorrection = keyboardViewAttr.getDimensionPixelOffset( R.styleable.KeyboardView_verticalCorrection, 0); - mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0); - mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0); - a.recycle(); + mMoreKeysLayout = keyboardViewAttr.getResourceId( + R.styleable.KeyboardView_moreKeysLayout, 0); + mBackgroundDimAlpha = keyboardViewAttr.getInt( + R.styleable.KeyboardView_backgroundDimAlpha, 0); + keyboardViewAttr.recycle(); + keyAttr.recycle(); mPreviewPlacerView = new PreviewPlacerView(context, attrs); mPaint.setAntiAlias(true); } - static boolean isValidFraction(final float fraction) { - return fraction >= 0.0f; - } - - static float getFraction(final TypedArray a, final int index) { - final TypedValue value = a.peekValue(index); - if (value == null || value.type != TypedValue.TYPE_FRACTION) { - return UNDEFINED_RATIO; - } - return a.getFraction(index, 1, 1, UNDEFINED_RATIO); - } - - public static int getDimensionPixelSize(final TypedArray a, final int index) { - final TypedValue value = a.peekValue(index); - if (value == null || value.type != TypedValue.TYPE_DIMENSION) { - return UNDEFINED_DIMENSION; - } - return a.getDimensionPixelSize(index, UNDEFINED_DIMENSION); - } - /** * Attaches a keyboard to this view. The keyboard can be switched at any time and the * view will re-layout itself to accommodate the keyboard. @@ -598,7 +403,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } private void onDrawKey(Key key, Canvas canvas, Paint paint, KeyDrawParams params) { - final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft(); + final int keyDrawX = key.getDrawX() + getPaddingLeft(); final int keyDrawY = key.mY + getPaddingTop(); canvas.translate(keyDrawX, keyDrawY); @@ -613,8 +418,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // Draw key background. protected void onDrawKeyBackground(Key key, Canvas canvas, KeyDrawParams params) { - final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight - + params.mPadding.left + params.mPadding.right; + final int bgWidth = key.getDrawWidth() + params.mPadding.left + params.mPadding.right; final int bgHeight = key.mHeight + params.mPadding.top + params.mPadding.bottom; final int bgX = -params.mPadding.left; final int bgY = -params.mPadding.top; @@ -635,7 +439,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // Draw key top visuals. protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) { - final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight; + final int keyWidth = key.getDrawWidth(); final int keyHeight = key.mHeight; final float centerX = keyWidth * 0.5f; final float centerY = keyHeight * 0.5f; @@ -696,7 +500,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { ? params.mKeyTextInactivatedColor : params.mKeyTextColor); if (key.isEnabled()) { // Set a drop shadow for the text - paint.setShadowLayer(params.mShadowRadius, 0, 0, params.mShadowColor); + paint.setShadowLayer(params.mKeyTextShadowRadius, 0, 0, params.mKeyTextShadowColor); } else { // Make label invisible paint.setColor(Color.TRANSPARENT); @@ -811,7 +615,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // Draw popup hint "..." at the bottom right corner of the key. protected void drawKeyPopupHint(Key key, Canvas canvas, Paint paint, KeyDrawParams params) { - final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight; + final int keyWidth = key.getDrawWidth(); final int keyHeight = key.mHeight; paint.setTypeface(params.mKeyTypeface); @@ -1002,7 +806,11 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { @SuppressWarnings("deprecation") // setBackgroundDrawable is replaced by setBackground in API16 @Override public void showKeyPreview(PointerTracker tracker) { - if (!mShowKeyPreviewPopup) return; + final KeyPreviewDrawParams params = mKeyPreviewDrawParams; + if (!mShowKeyPreviewPopup) { + params.mPreviewVisibleOffset = -mKeyboard.mVerticalGap; + return; + } final TextView previewText = getKeyPreviewText(tracker.mPointerId); // If the key preview has no parent view yet, add it to the ViewGroup which can place @@ -1019,7 +827,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { if (key == null) return; - final KeyPreviewDrawParams params = mKeyPreviewDrawParams; final String label = key.isShiftedLetterActivated() ? key.mHintLabel : key.mLabel; // What we show as preview should match what we show on a key top in onDraw(). if (label != null) { @@ -1042,7 +849,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { previewText.measure( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight; + final int keyDrawWidth = key.getDrawWidth(); final int previewWidth = previewText.getMeasuredWidth(); final int previewHeight = params.mPreviewHeight; // The width and height of visible part of the key preview background. The content marker @@ -1058,8 +865,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // The key preview is horizontally aligned with the center of the visible part of the // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and // the left/right background is used if such background is specified. - int previewX = key.mX + key.mVisualInsetsLeft - (previewWidth - keyDrawWidth) / 2 - + params.mCoordinates[0]; + int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2 + params.mCoordinates[0]; if (previewX < 0) { previewX = 0; if (params.mPreviewLeftBackground != null) { diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index 358061b47..2da2f6dc6 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -43,6 +43,7 @@ import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; +import com.android.inputmethod.keyboard.internal.KeyDrawParams; import com.android.inputmethod.keyboard.internal.SuddenJumpingTouchEventHandler; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinIME; @@ -334,7 +335,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); final Resources res = getResources(); final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean( - ResourceUtils.getDeviceOverrideValue(res, + ResourceUtils.getDeviceOverrideValue(res, R.array.phantom_sudden_move_event_device_list, "false")); PointerTracker.init(mHasDistinctMultitouch, needsPhantomSuddenMoveEventHack); @@ -618,9 +619,9 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key // The more keys keyboard is usually vertically aligned with the top edge of the parent key // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically // aligned with the bottom edge of the visible part of the key preview. - final int pointY = parentKey.mY + (keyPreviewEnabled - ? mKeyPreviewDrawParams.mPreviewVisibleOffset - : -parentKey.mVerticalGap); + // {@code mPreviewVisibleOffset} has been set appropriately in + // {@link KeyboardView#showKeyPreview(PointerTracker)}. + final int pointY = parentKey.mY + mKeyPreviewDrawParams.mPreviewVisibleOffset; moreKeysPanel.showMoreKeysPanel( this, this, pointX, pointY, mMoreKeysWindow, mKeyboardActionListener); final int translatedX = moreKeysPanel.translateX(tracker.getLastX()); 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,228 +39,231 @@ public class MoreKeysKeyboard extends Keyboard { return mDefaultKeyCoordX; } - public static class Builder extends Keyboard.Builder<Builder.MoreKeysKeyboardParams> { - 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; + /* 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(); + } - 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(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); } - - /** - * 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; + 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; } - - private int getFixedOrderTopRowAdjustment() { - if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns - || mLeftKeys == 0 || mRightKeys == 1) { - return 0; - } - return -1; + // 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++; } - - private int getAutoOrderTopRowAdjustment() { - if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2 - || mLeftKeys == 0 || mRightKeys == 1) { - return 0; - } - return -1; + // 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; + } - // Return key position according to column count (0 is default). - /* package */int getColumnPos(int n) { - return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n); + private int getFixedOrderTopRowAdjustment() { + if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns + || mLeftKeys == 0 || mRightKeys == 1) { + return 0; } + return -1; + } - 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 getAutoOrderTopRowAdjustment() { + if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2 + || mLeftKeys == 0 || mRightKeys == 1) { + return 0; } + return -1; + } - 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; - } + // Return key position according to column count (0 is default). + /* package */int getColumnPos(final int n) { + return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n); + } - 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; - } + 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 static int getTopRowEmptySlots(int numKeys, int numColumns) { - final int remainings = numKeys % numColumns; - return remainings == 0 ? 0 : numColumns - remainings; + 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; } - private int getOptimizedColumns(int numKeys, int maxColumns) { - int numColumns = Math.min(numKeys, maxColumns); - while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) { - numColumns--; + 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++; } - return numColumns; + if (i >= col) + break; + // Assign left key if available. + if (left < leftKeys) { + left++; + pos = -left; + i++; + } + if (i >= col) + break; } + return pos; + } - public int getDefaultKeyCoordX() { - return mLeftKeys * mColumnWidth; - } + private static int getTopRowEmptySlots(final int numKeys, final int numColumns) { + final int remainings = numKeys % numColumns; + return remainings == 0 ? 0 : numColumns - remainings; + } - public int getX(int n, int row) { - final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX(); - if (isTopRow(row)) { - return x + mTopRowAdjustment * (mColumnWidth / 2); - } - return x; + 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 getY(int row) { - return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; - } + public int getDefaultKeyCoordX() { + return mLeftKeys * mColumnWidth; + } - public void markAsEdgeKey(Key key, int row) { - if (row == 0) - key.markAsTopEdge(this); - if (isTopRow(row)) - key.markAsBottomEdge(this); + 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; + } - private boolean isTopRow(int rowCount) { - return mNumRows > 1 && rowCount == mNumRows - 1; - } + 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<MoreKeysKeyboardParams> { + 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; + + /** * The builder of MoreKeysKeyboard. * @param containerView the container of {@link MoreKeysKeyboardView}. * @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/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index be101cfb0..5a79d508f 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -330,10 +330,10 @@ public class PointerTracker implements PointerTrackerQueue.Element { final int y) { final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); - final int code = altersCode ? key.mAltCode : primaryCode; + final int code = altersCode ? key.getAltCode() : primaryCode; if (DEBUG_LISTENER) { - Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) + " text=" + key.mOutputText - + " x=" + x + " y=" + y + Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) + + " text=" + key.getOutputText() + " x=" + x + " y=" + y + " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode + " enabled=" + key.isEnabled()); } @@ -347,7 +347,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. if (key.isEnabled() || altersCode) { if (code == Keyboard.CODE_OUTPUT_TEXT) { - mListener.onTextInput(key.mOutputText); + mListener.onTextInput(key.getOutputText()); } else if (code != Keyboard.CODE_UNSPECIFIED) { mListener.onCodeInput(code, x, y); } @@ -440,13 +440,13 @@ public class PointerTracker implements PointerTrackerQueue.Element { } if (key.altCodeWhileTyping()) { - final int altCode = key.mAltCode; + final int altCode = key.getAltCode(); final Key altKey = mKeyboard.getKey(altCode); if (altKey != null) { updateReleaseKeyGraphics(altKey); } for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { - if (k != key && k.mAltCode == altCode) { + if (k != key && k.getAltCode() == altCode) { updateReleaseKeyGraphics(k); } } @@ -479,13 +479,13 @@ public class PointerTracker implements PointerTrackerQueue.Element { } if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) { - final int altCode = key.mAltCode; + final int altCode = key.getAltCode(); final Key altKey = mKeyboard.getKey(altCode); if (altKey != null) { updatePressKeyGraphics(altKey); } for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { - if (k != key && k.mAltCode == altCode) { + if (k != key && k.getAltCode() == altCode) { updatePressKeyGraphics(k); } } 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/KeyDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java new file mode 100644 index 000000000..971020bab --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java @@ -0,0 +1,163 @@ +/* + * 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; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; + +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.ResourceUtils; + +public class KeyDrawParams { + // XML attributes + public final int mKeyTextColor; + public final int mKeyTextInactivatedColor; + public final float mKeyLabelHorizontalPadding; + public final float mKeyHintLetterPadding; + public final float mKeyPopupHintLetterPadding; + public final float mKeyShiftedLetterHintPadding; + public final int mKeyTextShadowColor; + public final float mKeyTextShadowRadius; + public final Drawable mKeyBackground; + public final int mKeyHintLetterColor; + public final int mKeyHintLabelColor; + public final int mKeyShiftedLetterHintInactivatedColor; + public final int mKeyShiftedLetterHintActivatedColor; + + private final Typeface mKeyTypefaceFromKeyboardView; + private final float mKeyLetterRatio; + private final int mKeyLetterSizeFromKeyboardView; + private final float mKeyLargeLetterRatio; + private final float mKeyLabelRatio; + private final float mKeyLargeLabelRatio; + private final float mKeyHintLetterRatio; + private final float mKeyShiftedLetterHintRatio; + private final float mKeyHintLabelRatio; + + public final Rect mPadding = new Rect(); + public Typeface mKeyTypeface; + public int mKeyLetterSize; + public int mKeyLargeLetterSize; + public int mKeyLabelSize; + public int mKeyLargeLabelSize; + public int mKeyHintLetterSize; + public int mKeyShiftedLetterHintSize; + public int mKeyHintLabelSize; + public int mAnimAlpha; + + public KeyDrawParams(final TypedArray keyboardViewAttr, final TypedArray keyAttr) { + mKeyBackground = keyboardViewAttr.getDrawable(R.styleable.KeyboardView_keyBackground); + mKeyBackground.getPadding(mPadding); + + mKeyLetterRatio = ResourceUtils.getFraction(keyAttr, + R.styleable.Keyboard_Key_keyLetterSize); + mKeyLetterSizeFromKeyboardView = ResourceUtils.getDimensionPixelSize(keyAttr, + R.styleable.Keyboard_Key_keyLetterSize); + mKeyLabelRatio = ResourceUtils.getFraction(keyAttr, + R.styleable.Keyboard_Key_keyLabelSize); + mKeyLabelSize = ResourceUtils.getDimensionPixelSize(keyAttr, + R.styleable.Keyboard_Key_keyLabelSize); + mKeyLargeLabelRatio = ResourceUtils.getFraction(keyAttr, + R.styleable.Keyboard_Key_keyLargeLabelRatio); + mKeyLargeLetterRatio = ResourceUtils.getFraction(keyAttr, + R.styleable.Keyboard_Key_keyLargeLetterRatio); + mKeyHintLetterRatio = ResourceUtils.getFraction(keyAttr, + R.styleable.Keyboard_Key_keyHintLetterRatio); + mKeyShiftedLetterHintRatio = ResourceUtils.getFraction(keyAttr, + R.styleable.Keyboard_Key_keyShiftedLetterHintRatio); + mKeyHintLabelRatio = ResourceUtils.getFraction(keyAttr, + R.styleable.Keyboard_Key_keyHintLabelRatio); + mKeyLabelHorizontalPadding = keyAttr.getDimension( + R.styleable.Keyboard_Key_keyLabelHorizontalPadding, 0); + mKeyHintLetterPadding = keyAttr.getDimension( + R.styleable.Keyboard_Key_keyHintLetterPadding, 0); + mKeyPopupHintLetterPadding = keyAttr.getDimension( + R.styleable.Keyboard_Key_keyPopupHintLetterPadding, 0); + mKeyShiftedLetterHintPadding = keyAttr.getDimension( + R.styleable.Keyboard_Key_keyShiftedLetterHintPadding, 0); + mKeyTextColor = keyAttr.getColor( + R.styleable.Keyboard_Key_keyTextColor, Color.WHITE); + mKeyTextInactivatedColor = keyAttr.getColor( + R.styleable.Keyboard_Key_keyTextInactivatedColor, Color.WHITE); + mKeyHintLetterColor = keyAttr.getColor( + R.styleable.Keyboard_Key_keyHintLetterColor, Color.TRANSPARENT); + mKeyHintLabelColor = keyAttr.getColor( + R.styleable.Keyboard_Key_keyHintLabelColor, Color.TRANSPARENT); + mKeyShiftedLetterHintInactivatedColor = keyAttr.getColor( + R.styleable.Keyboard_Key_keyShiftedLetterHintInactivatedColor, Color.TRANSPARENT); + mKeyShiftedLetterHintActivatedColor = keyAttr.getColor( + R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor, Color.TRANSPARENT); + mKeyTypefaceFromKeyboardView = Typeface.defaultFromStyle( + keyAttr.getInt(R.styleable.Keyboard_Key_keyTypeface, Typeface.NORMAL)); + mKeyTextShadowColor = keyAttr.getColor( + R.styleable.Keyboard_Key_keyTextShadowColor, Color.TRANSPARENT); + mKeyTextShadowRadius = keyAttr.getFloat( + R.styleable.Keyboard_Key_keyTextShadowRadius, 0f); + } + + public void updateParams(final Keyboard keyboard) { + mKeyTypeface = (keyboard.mKeyTypeface != null) + ? keyboard.mKeyTypeface : mKeyTypefaceFromKeyboardView; + final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; + mKeyLetterSize = selectTextSizeFromDimensionOrRatio(keyHeight, + mKeyLetterSizeFromKeyboardView, mKeyLetterRatio, + mKeyLetterSizeFromKeyboardView); + // Override if size/ratio is specified in Keyboard. + mKeyLetterSize = selectTextSizeFromDimensionOrRatio(keyHeight, keyboard.mKeyLetterSize, + keyboard.mKeyLetterRatio, mKeyLetterSize); + if (ResourceUtils.isValidFraction(mKeyLabelRatio)) { + mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio); + } + mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio); + mKeyLargeLetterSize = (int)(keyHeight * mKeyLargeLetterRatio); + mKeyHintLetterSize = selectTextSizeFromKeyboardOrView(keyHeight, + keyboard.mKeyHintLetterRatio, mKeyHintLetterRatio); + mKeyShiftedLetterHintSize = selectTextSizeFromKeyboardOrView(keyHeight, + keyboard.mKeyShiftedLetterHintRatio, mKeyShiftedLetterHintRatio); + mKeyHintLabelSize = (int)(keyHeight * mKeyHintLabelRatio); + } + + private static final int selectTextSizeFromDimensionOrRatio(final int keyHeight, + final int dimens, final float ratio, final int defaultDimens) { + if (ResourceUtils.isValidDimensionPixelSize(dimens)) { + return dimens; + } + if (ResourceUtils.isValidFraction(ratio)) { + return (int)(keyHeight * ratio); + } + return defaultDimens; + } + + private static final int selectTextSizeFromKeyboardOrView(final int keyHeight, + final float ratioFromKeyboard, final float ratioFromView) { + final float ratio = ResourceUtils.isValidFraction(ratioFromKeyboard) + ? ratioFromKeyboard : ratioFromView; + return (int)(keyHeight * ratio); + } + + public void blendAlpha(final Paint paint) { + final int color = paint.getColor(); + paint.setARGB((paint.getAlpha() * mAnimAlpha) / Constants.Color.ALPHA_OPAQUE, + Color.red(color), Color.green(color), Color.blue(color)); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java new file mode 100644 index 000000000..a3a38507f --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java @@ -0,0 +1,106 @@ +/* + * 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; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; + +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.ResourceUtils; + +public class KeyPreviewDrawParams { + // XML attributes. + public final Drawable mPreviewBackground; + public final Drawable mPreviewLeftBackground; + public final Drawable mPreviewRightBackground; + public final int mPreviewTextColor; + public final int mPreviewOffset; + public final int mPreviewHeight; + public final int mLingerTimeout; + + private final float mPreviewTextRatio; + + // The graphical geometry of the key preview. + // <-width-> + // +-------+ ^ + // | | | + // |preview| height (visible) + // | | | + // + + ^ v + // \ / |offset + // +-\ /-+ v + // | +-+ | + // |parent | + // | key| + // +-------+ + // The background of a {@link TextView} being used for a key preview may have invisible + // paddings. To align the more keys keyboard panel's visible part with the visible part of + // the background, we need to record the width and height of key preview that don't include + // invisible paddings. + public int mPreviewVisibleWidth; + public int mPreviewVisibleHeight; + // The key preview may have an arbitrary offset and its background that may have a bottom + // padding. To align the more keys keyboard and the key preview we also need to record the + // offset between the top edge of parent key and the bottom of the visible part of key + // preview background. + public int mPreviewVisibleOffset; + + public Typeface mKeyTypeface; + public int mPreviewTextSize; + public int mKeyLetterSize; + public final int[] mCoordinates = new int[2]; + + private static final int PREVIEW_ALPHA = 240; + + public KeyPreviewDrawParams(final TypedArray keyboardViewAttr, final TypedArray keyAttr) { + mPreviewBackground = keyboardViewAttr.getDrawable( + R.styleable.KeyboardView_keyPreviewBackground); + mPreviewLeftBackground = keyboardViewAttr.getDrawable( + R.styleable.KeyboardView_keyPreviewLeftBackground); + mPreviewRightBackground = keyboardViewAttr.getDrawable( + R.styleable.KeyboardView_keyPreviewRightBackground); + setAlpha(mPreviewBackground, PREVIEW_ALPHA); + setAlpha(mPreviewLeftBackground, PREVIEW_ALPHA); + setAlpha(mPreviewRightBackground, PREVIEW_ALPHA); + mPreviewOffset = keyboardViewAttr.getDimensionPixelOffset( + R.styleable.KeyboardView_keyPreviewOffset, 0); + mPreviewHeight = keyboardViewAttr.getDimensionPixelSize( + R.styleable.KeyboardView_keyPreviewHeight, 80); + mLingerTimeout = keyboardViewAttr.getInt( + R.styleable.KeyboardView_keyPreviewLingerTimeout, 0); + + mPreviewTextRatio = ResourceUtils.getFraction(keyAttr, + R.styleable.Keyboard_Key_keyPreviewTextRatio); + mPreviewTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyPreviewTextColor, 0); + } + + public void updateParams(final Keyboard keyboard, final KeyDrawParams keyDrawParams) { + final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; + if (ResourceUtils.isValidFraction(mPreviewTextRatio)) { + mPreviewTextSize = (int)(keyHeight * mPreviewTextRatio); + } + mKeyLetterSize = keyDrawParams.mKeyLetterSize; + mKeyTypeface = keyDrawParams.mKeyTypeface; + } + + private static void setAlpha(final Drawable drawable, final int alpha) { + if (drawable == null) return; + drawable.setAlpha(alpha); + } +} 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 <T> ArrayList<T> arrayAsList(T[] array, int start, int end) { + private static <T> ArrayList<T> 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 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<String, KeyStyle> mStyles = CollectionUtils.newHashMap(); + private final HashMap<String, KeyStyle> mStyles = CollectionUtils.newHashMap(); - final KeyboardTextsSet mTextsSet; + private final KeyboardTextsSet mTextsSet; private final KeyStyle mEmptyKeyStyle; private static final String EMPTY_STYLE_NAME = "<empty>"; - 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; - } - - protected String[] parseStringArray(TypedArray a, int index) { - if (a.hasValue(index)) { - return KeySpecParser.parseCsvString(a.getString(index), mTextsSet); - } - return null; + private static class EmptyKeyStyle extends KeyStyle { + EmptyKeyStyle(final KeyboardTextsSet textsSet) { + super(textsSet); } - } - 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<String, KeyStyle> mStyles; private final String mParentStyleName; private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray(); - public DeclaredKeyStyle(String parentStyleName) { + public DeclaredKeyStyle(final String parentStyleName, final KeyboardTextsSet textsSet, + final HashMap<String, KeyStyle> 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..3d1045f88 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java @@ -0,0 +1,826 @@ +/* + * 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: + * <pre> + * <!-- 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> + * </pre> + * The XML file which is included in other file must have <merge> as root element, + * such as: + * <pre> + * <!-- xml/other_keys.xml --> + * <merge> + * <Key key_attributes* /> + * ... + * </merge> + * </pre> + * and + * <pre> + * <!-- xml/other_rows.xml --> + * <merge> + * <Row row_attributes*> + * <Key key_attributes* /> + * </Row> + * ... + * </merge> + * </pre> + * You can also use switch-case-default tags to select Rows and Keys. + * <pre> + * <switch> + * <case case_attribute*> + * <!-- Any valid tags at switch position --> + * </case> + * ... + * <default> + * <!-- Any valid tags at switch position --> + * </default> + * </switch> + * </pre> + * You can declare Key style and specify styles within Key tags. + * <pre> + * <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" ... /> + * </pre> + */ + +public class KeyboardBuilder<KP extends KeyboardParams> { + 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<KP> 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); + 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 (keyAttr.hasValue(R.styleable.Keyboard_Key_keyTypeface)) { + params.mKeyTypeface = Typeface.defaultFromStyle(keyAttr.getInt( + R.styleable.Keyboard_Key_keyTypeface, Typeface.NORMAL)); + } + params.mKeyLetterRatio = ResourceUtils.getFraction(keyAttr, + R.styleable.Keyboard_Key_keyLetterSize); + params.mKeyLetterSize = ResourceUtils.getDimensionPixelSize(keyAttr, + R.styleable.Keyboard_Key_keyLetterSize); + params.mKeyHintLetterRatio = ResourceUtils.getFraction(keyAttr, + R.styleable.Keyboard_Key_keyHintLetterRatio); + params.mKeyShiftedLetterHintRatio = ResourceUtils.getFraction(keyAttr, + R.styleable.Keyboard_Key_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<Void> job = new RunInLocale<Void>() { + @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 { + 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("</%s>", 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("</%s>", 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("</%s> 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 <merge> 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("</%s>", 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 <case> does not have "index" attribute, that means this <case> 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 <case> does not have "index" attribute, that means this <case> 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 <case> does not have "index" attribute, that means this <case> 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 <case> does not have "index" attribute, that means this <case> 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<Key> mKeys = CollectionUtils.newHashSet(); + public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList(); + public final ArrayList<Key> 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 <Row> 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<Key, Key> 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/ResourceUtils.java b/java/src/com/android/inputmethod/latin/ResourceUtils.java index e01ac3dd4..5021ad384 100644 --- a/java/src/com/android/inputmethod/latin/ResourceUtils.java +++ b/java/src/com/android/inputmethod/latin/ResourceUtils.java @@ -17,11 +17,16 @@ package com.android.inputmethod.latin; import android.content.res.Resources; +import android.content.res.TypedArray; import android.os.Build; +import android.util.TypedValue; import java.util.HashMap; public final class ResourceUtils { + public static final float UNDEFINED_RATIO = -1.0f; + public static final int UNDEFINED_DIMENSION = -1; + private ResourceUtils() { // This utility class is not publicly instantiable. } @@ -45,4 +50,79 @@ public final class ResourceUtils { } return sDeviceOverrideValueMap.get(key); } + + public static boolean isValidFraction(final float fraction) { + return fraction >= 0.0f; + } + + // {@link Resources#getDimensionPixelSize(int)} returns at least one pixel size. + public static boolean isValidDimensionPixelSize(final int dimension) { + return dimension > 0; + } + + // {@link Resources#getDimensionPixelOffset(int)} may return zero pixel offset. + public static boolean isValidDimensionPixelOffset(final int dimension) { + return dimension >= 0; + } + + public static float getFraction(final TypedArray a, final int index, final float defValue) { + final TypedValue value = a.peekValue(index); + if (value == null || !isFractionValue(value)) { + return defValue; + } + return a.getFraction(index, 1, 1, defValue); + } + + public static float getFraction(final TypedArray a, final int index) { + return getFraction(a, index, UNDEFINED_RATIO); + } + + public static int getDimensionPixelSize(final TypedArray a, final int index) { + final TypedValue value = a.peekValue(index); + if (value == null || !isDimensionValue(value)) { + return ResourceUtils.UNDEFINED_DIMENSION; + } + return a.getDimensionPixelSize(index, ResourceUtils.UNDEFINED_DIMENSION); + } + + public static float getDimensionOrFraction(TypedArray a, int index, int base, + float defValue) { + final TypedValue value = a.peekValue(index); + if (value == null) { + return defValue; + } + if (isFractionValue(value)) { + return a.getFraction(index, base, base, defValue); + } else if (isDimensionValue(value)) { + return a.getDimension(index, defValue); + } + return defValue; + } + + public static int getEnumValue(TypedArray a, int index, int defValue) { + final TypedValue value = a.peekValue(index); + if (value == null) { + return defValue; + } + if (isIntegerValue(value)) { + return a.getInt(index, defValue); + } + return defValue; + } + + public static boolean isFractionValue(TypedValue v) { + return v.type == TypedValue.TYPE_FRACTION; + } + + public static boolean isDimensionValue(TypedValue v) { + return v.type == TypedValue.TYPE_DIMENSION; + } + + public static boolean isIntegerValue(TypedValue v) { + return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT; + } + + public static boolean isStringValue(TypedValue v) { + return v.type == TypedValue.TYPE_STRING; + } } diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java index 161b94ca0..d4f7cab5c 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java @@ -188,6 +188,54 @@ public class BinaryDictInputOutput { // suspicion that a bug might be causing an infinite loop. private static final int MAX_PASSES = 24; + private interface FusionDictionaryBufferInterface { + public int readUnsignedByte(); + public int readUnsignedShort(); + public int readUnsignedInt24(); + public int readInt(); + public int position(); + public void position(int newPosition); + } + + private static final class ByteBufferWrapper implements FusionDictionaryBufferInterface { + private ByteBuffer buffer; + ByteBufferWrapper(final ByteBuffer buffer) { + this.buffer = buffer; + } + + @Override + public int readUnsignedByte() { + return ((int)buffer.get()) & 0xFF; + } + + @Override + public int readUnsignedShort() { + return ((int)buffer.getShort()) & 0xFFFF; + } + + @Override + public int readUnsignedInt24() { + final int retval = readUnsignedByte(); + return (retval << 16) + readUnsignedShort(); + } + + @Override + public int readInt() { + return buffer.getInt(); + } + + @Override + public int position() { + return buffer.position(); + } + + @Override + public void position(int newPos) { + buffer.position(newPos); + return; + } + } + /** * A class grouping utility function for our specific character encoding. */ @@ -310,9 +358,9 @@ public class BinaryDictInputOutput { } /** - * Reads a string from a ByteBuffer. This is the converse of the above method. + * Reads a string from a buffer. This is the converse of the above method. */ - private static String readString(final ByteBuffer buffer) { + private static String readString(final FusionDictionaryBufferInterface buffer) { final StringBuilder s = new StringBuilder(); int character = readChar(buffer); while (character != INVALID_CHARACTER) { @@ -323,19 +371,19 @@ public class BinaryDictInputOutput { } /** - * Reads a character from the ByteBuffer. + * Reads a character from the buffer. * * This follows the character format documented earlier in this source file. * * @param buffer the buffer, positioned over an encoded character. * @return the character code. */ - private static int readChar(final ByteBuffer buffer) { - int character = readUnsignedByte(buffer); + private static int readChar(final FusionDictionaryBufferInterface buffer) { + int character = buffer.readUnsignedByte(); if (!fitsOnOneByte(character)) { if (GROUP_CHARACTERS_TERMINATOR == character) return INVALID_CHARACTER; character <<= 16; - character += readUnsignedShort(buffer); + character += buffer.readUnsignedShort(); } return character; } @@ -1093,10 +1141,10 @@ public class BinaryDictInputOutput { // readDictionaryBinary is the public entry point for them. static final int[] characterBuffer = new int[MAX_WORD_LENGTH]; - private static CharGroupInfo readCharGroup(final ByteBuffer buffer, + private static CharGroupInfo readCharGroup(final FusionDictionaryBufferInterface buffer, final int originalGroupAddress) { int addressPointer = originalGroupAddress; - final int flags = readUnsignedByte(buffer); + final int flags = buffer.readUnsignedByte(); ++addressPointer; final int characters[]; if (0 != (flags & FLAG_HAS_MULTIPLE_CHARS)) { @@ -1117,22 +1165,22 @@ public class BinaryDictInputOutput { final int frequency; if (0 != (FLAG_IS_TERMINAL & flags)) { ++addressPointer; - frequency = readUnsignedByte(buffer); + frequency = buffer.readUnsignedByte(); } else { frequency = CharGroup.NOT_A_TERMINAL; } int childrenAddress = addressPointer; switch (flags & MASK_GROUP_ADDRESS_TYPE) { case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE: - childrenAddress += readUnsignedByte(buffer); + childrenAddress += buffer.readUnsignedByte(); addressPointer += 1; break; case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES: - childrenAddress += readUnsignedShort(buffer); + childrenAddress += buffer.readUnsignedShort(); addressPointer += 2; break; case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES: - childrenAddress += readUnsignedInt24(buffer); + childrenAddress += buffer.readUnsignedInt24(); addressPointer += 3; break; case FLAG_GROUP_ADDRESS_TYPE_NOADDRESS: @@ -1144,9 +1192,9 @@ public class BinaryDictInputOutput { if (0 != (flags & FLAG_HAS_SHORTCUT_TARGETS)) { final int pointerBefore = buffer.position(); shortcutTargets = new ArrayList<WeightedString>(); - buffer.getShort(); // Skip the size + buffer.readUnsignedShort(); // Skip the size while (true) { - final int targetFlags = readUnsignedByte(buffer); + final int targetFlags = buffer.readUnsignedByte(); final String word = CharEncoding.readString(buffer); shortcutTargets.add(new WeightedString(word, targetFlags & FLAG_ATTRIBUTE_FREQUENCY)); @@ -1158,22 +1206,22 @@ public class BinaryDictInputOutput { if (0 != (flags & FLAG_HAS_BIGRAMS)) { bigrams = new ArrayList<PendingAttribute>(); while (true) { - final int bigramFlags = readUnsignedByte(buffer); + final int bigramFlags = buffer.readUnsignedByte(); ++addressPointer; final int sign = 0 == (bigramFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1; int bigramAddress = addressPointer; switch (bigramFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) { case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE: - bigramAddress += sign * readUnsignedByte(buffer); + bigramAddress += sign * buffer.readUnsignedByte(); addressPointer += 1; break; case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES: - bigramAddress += sign * readUnsignedShort(buffer); + bigramAddress += sign * buffer.readUnsignedShort(); addressPointer += 2; break; case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES: - final int offset = (readUnsignedByte(buffer) << 16) - + readUnsignedShort(buffer); + final int offset = (buffer.readUnsignedByte() << 16) + + buffer.readUnsignedShort(); bigramAddress += sign * offset; addressPointer += 3; break; @@ -1192,13 +1240,13 @@ public class BinaryDictInputOutput { /** * Reads and returns the char group count out of a buffer and forwards the pointer. */ - private static int readCharGroupCount(final ByteBuffer buffer) { - final int msb = readUnsignedByte(buffer); + private static int readCharGroupCount(final FusionDictionaryBufferInterface buffer) { + final int msb = buffer.readUnsignedByte(); if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) { return msb; } else { return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8) - + readUnsignedByte(buffer); + + buffer.readUnsignedByte(); } } @@ -1215,8 +1263,8 @@ public class BinaryDictInputOutput { * @param address the address to seek. * @return the word, as a string. */ - private static String getWordAtAddress(final ByteBuffer buffer, final int headerSize, - final int address) { + private static String getWordAtAddress(final FusionDictionaryBufferInterface buffer, + final int headerSize, final int address) { final String cachedString = wordCache.get(address); if (null != cachedString) return cachedString; final int originalPointer = buffer.position(); @@ -1241,7 +1289,7 @@ public class BinaryDictInputOutput { builder.append(new String(last.mCharacters, 0, last.mCharacters.length)); buffer.position(last.mChildrenAddress + headerSize); groupOffset = last.mChildrenAddress + 1; - i = readUnsignedByte(buffer); + i = buffer.readUnsignedByte(); last = null; continue; } @@ -1251,7 +1299,7 @@ public class BinaryDictInputOutput { builder.append(new String(last.mCharacters, 0, last.mCharacters.length)); buffer.position(last.mChildrenAddress + headerSize); groupOffset = last.mChildrenAddress + 1; - i = readUnsignedByte(buffer); + i = buffer.readUnsignedByte(); last = null; continue; } @@ -1262,10 +1310,10 @@ public class BinaryDictInputOutput { } /** - * Reads a single node from a binary file. + * Reads a single node from a buffer. * - * This methods reads the file at the current position of its file pointer. A node is - * fully expected to start at the current position. + * This methods reads the file at the current position. A node is fully expected to start at + * the current position. * This will recursively read other nodes into the structure, populating the reverse * maps on the fly and using them to keep track of already read nodes. * @@ -1275,7 +1323,7 @@ public class BinaryDictInputOutput { * @param reverseGroupMap a mapping from addresses to already read character groups. * @return the read node with all his children already read. */ - private static Node readNode(final ByteBuffer buffer, final int headerSize, + private static Node readNode(final FusionDictionaryBufferInterface buffer, final int headerSize, final Map<Integer, Node> reverseNodeMap, final Map<Integer, CharGroup> reverseGroupMap) throws IOException { final int nodeOrigin = buffer.position() - headerSize; @@ -1283,7 +1331,7 @@ public class BinaryDictInputOutput { final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>(); int groupOffset = nodeOrigin + getGroupCountSize(count); for (int i = count; i > 0; --i) { - CharGroupInfo info =readCharGroup(buffer, groupOffset); + CharGroupInfo info = readCharGroup(buffer, groupOffset); ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets; ArrayList<WeightedString> bigrams = null; if (null != info.mBigrams) { @@ -1323,69 +1371,101 @@ public class BinaryDictInputOutput { * Helper function to get the binary format version from the header. * @throws IOException */ - private static int getFormatVersion(final ByteBuffer buffer) throws IOException { - final int magic_v1 = readUnsignedShort(buffer); - if (VERSION_1_MAGIC_NUMBER == magic_v1) return readUnsignedByte(buffer); - final int magic_v2 = (magic_v1 << 16) + readUnsignedShort(buffer); - if (VERSION_2_MAGIC_NUMBER == magic_v2) return readUnsignedShort(buffer); + private static int getFormatVersion(final FusionDictionaryBufferInterface buffer) + throws IOException { + final int magic_v1 = buffer.readUnsignedShort(); + if (VERSION_1_MAGIC_NUMBER == magic_v1) return buffer.readUnsignedByte(); + final int magic_v2 = (magic_v1 << 16) + buffer.readUnsignedShort(); + if (VERSION_2_MAGIC_NUMBER == magic_v2) return buffer.readUnsignedShort(); return NOT_A_VERSION_NUMBER; } /** - * Reads options from a file and populate a map with their contents. + * Helper function to get and validate the binary format version. + * @throws UnsupportedFormatException + * @throws IOException + */ + private static int checkFormatVersion(final FusionDictionaryBufferInterface buffer) + throws IOException, UnsupportedFormatException { + final int version = getFormatVersion(buffer); + if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION) { + throw new UnsupportedFormatException("This file has version " + version + + ", but this implementation does not support versions above " + + MAXIMUM_SUPPORTED_VERSION); + } + return version; + } + + /** + * Reads a header from a buffer. + * @throws IOException + * @throws UnsupportedFormatException + */ + private static int readHeader(final FusionDictionaryBufferInterface buffer, + final HashMap<String, String> options, + final int version) + throws IOException, UnsupportedFormatException { + + final int headerSize; + if (version < FIRST_VERSION_WITH_HEADER_SIZE) { + headerSize = buffer.position(); + } else { + headerSize = buffer.readInt(); + populateOptions(buffer, headerSize, options); + buffer.position(headerSize); + } + + if (headerSize < 0) { + throw new UnsupportedFormatException("header size can't be negative."); + } + + return headerSize; + } + + /** + * Reads options from a buffer and populate a map with their contents. * - * The file is read at the current file pointer, so the caller must take care the pointer + * The buffer is read at the current position, so the caller must take care the pointer * is in the right place before calling this. */ - public static void populateOptions(final ByteBuffer buffer, final int headerSize, - final HashMap<String, String> options) { + public static void populateOptions(final FusionDictionaryBufferInterface buffer, + final int headerSize, final HashMap<String, String> options) { while (buffer.position() < headerSize) { final String key = CharEncoding.readString(buffer); final String value = CharEncoding.readString(buffer); options.put(key, value); } } + // TODO: remove this method. + public static void populateOptions(final ByteBuffer buffer, final int headerSize, + final HashMap<String, String> options) { + populateOptions(new ByteBufferWrapper(buffer), headerSize, options); + } /** - * Reads a byte buffer and returns the memory representation of the dictionary. + * Reads a buffer and returns the memory representation of the dictionary. * - * This high-level method takes a binary file and reads its contents, populating a + * This high-level method takes a buffer and reads its contents, populating a * FusionDictionary structure. The optional dict argument is an existing dictionary to - * which words from the file should be added. If it is null, a new dictionary is created. + * which words from the buffer should be added. If it is null, a new dictionary is created. * * @param buffer the buffer to read. * @param dict an optional dictionary to add words to, or null. * @return the created (or merged) dictionary. */ - public static FusionDictionary readDictionaryBinary(final ByteBuffer buffer, - final FusionDictionary dict) throws IOException, UnsupportedFormatException { - // Check file version - final int version = getFormatVersion(buffer); - if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION) { - throw new UnsupportedFormatException("This file has version " + version - + ", but this implementation does not support versions above " - + MAXIMUM_SUPPORTED_VERSION); - } + public static FusionDictionary readDictionaryBinary( + final FusionDictionaryBufferInterface buffer, final FusionDictionary dict) + throws IOException, UnsupportedFormatException { // clear cache wordCache.clear(); - // Read options - final int optionsFlags = readUnsignedShort(buffer); + // Read header + final int version = checkFormatVersion(buffer); + final int optionsFlags = buffer.readUnsignedShort(); - final int headerSize; final HashMap<String, String> options = new HashMap<String, String>(); - if (version < FIRST_VERSION_WITH_HEADER_SIZE) { - headerSize = buffer.position(); - } else { - headerSize = buffer.getInt(); - populateOptions(buffer, headerSize, options); - buffer.position(headerSize); - } - - if (headerSize < 0) { - throw new UnsupportedFormatException("header size can't be negative."); - } + final int headerSize = readHeader(buffer, options, version); Map<Integer, Node> reverseNodeMapping = new TreeMap<Integer, Node>(); Map<Integer, CharGroup> reverseGroupMapping = new TreeMap<Integer, CharGroup>(); @@ -1413,26 +1493,10 @@ public class BinaryDictInputOutput { return newDict; } - /** - * Helper function to read one byte from ByteBuffer. - */ - private static int readUnsignedByte(final ByteBuffer buffer) { - return ((int)buffer.get()) & 0xFF; - } - - /** - * Helper function to read two byte from ByteBuffer. - */ - private static int readUnsignedShort(final ByteBuffer buffer) { - return ((int)buffer.getShort()) & 0xFFFF; - } - - /** - * Helper function to read three byte from ByteBuffer. - */ - private static int readUnsignedInt24(final ByteBuffer buffer) { - final int value = readUnsignedByte(buffer) << 16; - return value + readUnsignedShort(buffer); + // TODO: remove this method. + public static FusionDictionary readDictionaryBinary(final ByteBuffer buffer, + final FusionDictionary dict) throws IOException, UnsupportedFormatException { + return readDictionaryBinary(new ByteBufferWrapper(buffer), dict); } /** @@ -1450,7 +1514,7 @@ public class BinaryDictInputOutput { inStream = new FileInputStream(file); final ByteBuffer buffer = inStream.getChannel().map( FileChannel.MapMode.READ_ONLY, 0, file.length()); - final int version = getFormatVersion(buffer); + final int version = getFormatVersion(new ByteBufferWrapper(buffer)); return (version >= MINIMUM_SUPPORTED_VERSION && version <= MAXIMUM_SUPPORTED_VERSION); } catch (FileNotFoundException e) { return false; 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<Builder.MoreSuggestionsParam> { - private final MoreSuggestionsView mPaneView; - private SuggestedWords mSuggestions; - private int mFromPos; - private int mToPos; + 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 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++; + 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; } - mColumnOrders[pos] = pos - rowStartPos; - mRowNumbers[pos] = row; - pos++; + mNumColumnsInRow[row] = pos - rowStartPos; + rowStartPos = pos; + row++; } - mNumColumnsInRow[row] = pos - rowStartPos; - mNumRows = row + 1; - mBaseWidth = mOccupiedWidth = Math.max( - minWidth, calcurateMaxRowWidth(fromPos, pos)); - mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap; - return pos - fromPos; + 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 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(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)); + 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++; } - return maxRowWidth; + 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]]; - } + private static final int[][] COLUMN_ORDER_TO_NUMBER = { + { 0, }, + { 1, 0, }, + { 2, 0, 1}, + }; - 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 getNumColumnInRow(final int pos) { + return mNumColumnsInRow[mRowNumbers[pos]]; + } - public int getX(int pos) { - final int columnNumber = getColumnNumber(pos); - return columnNumber * (getWidth(pos) + mDividerWidth); - } + 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 getY(int pos) { - final int row = mRowNumbers[pos]; - return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding; - } + public int getX(final int pos) { + final int columnNumber = getColumnNumber(pos); + return columnNumber * (getWidth(pos) + mDividerWidth); + } - public int getWidth(int pos) { - final int numColumnInRow = getNumColumnInRow(pos); - return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow; - } + public int getY(final int pos) { + final int row = mRowNumbers[pos]; + return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding; + } - 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); + public int getWidth(final int pos) { + final int numColumnInRow = getNumColumnInRow(pos); + return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow; + } - final int numColumnInRow = mNumColumnsInRow[row]; - final int column = getColumnNumber(pos); - if (column == 0) - key.markAsLeftEdge(this); - if (column == numColumnInRow - 1) - key.markAsRightEdge(this); - } + 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 Builder(MoreSuggestionsView paneView) { + public static class Builder extends KeyboardBuilder<MoreSuggestionsParam> { + private final MoreSuggestionsView mPaneView; + private SuggestedWords mSuggestions; + private int mFromPos; + private int mToPos; + + 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/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index 03263d274..9e8ab81b0 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -61,6 +61,7 @@ import com.android.inputmethod.latin.AutoCorrection; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.ResourceUtils; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.Utils; @@ -196,15 +197,15 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle); mSuggestionStripOption = a.getInt( R.styleable.SuggestionStripView_suggestionStripOption, 0); - final float alphaValidTypedWord = getFraction(a, + final float alphaValidTypedWord = ResourceUtils.getFraction(a, R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f); - final float alphaTypedWord = getFraction(a, + final float alphaTypedWord = ResourceUtils.getFraction(a, R.styleable.SuggestionStripView_alphaTypedWord, 1.0f); - final float alphaAutoCorrect = getFraction(a, + final float alphaAutoCorrect = ResourceUtils.getFraction(a, R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f); - final float alphaSuggested = getFraction(a, + final float alphaSuggested = ResourceUtils.getFraction(a, R.styleable.SuggestionStripView_alphaSuggested, 1.0f); - mAlphaObsoleted = getFraction(a, + mAlphaObsoleted = ResourceUtils.getFraction(a, R.styleable.SuggestionStripView_alphaSuggested, 1.0f); mColorValidTypedWord = applyAlpha(a.getColor( R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord); @@ -217,13 +218,13 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen mSuggestionsCountInStrip = a.getInt( R.styleable.SuggestionStripView_suggestionsCountInStrip, DEFAULT_SUGGESTIONS_COUNT_IN_STRIP); - mCenterSuggestionWeight = getFraction(a, + mCenterSuggestionWeight = ResourceUtils.getFraction(a, R.styleable.SuggestionStripView_centerSuggestionPercentile, DEFAULT_CENTER_SUGGESTION_PERCENTILE); mMaxMoreSuggestionsRow = a.getInt( R.styleable.SuggestionStripView_maxMoreSuggestionsRow, DEFAULT_MAX_MORE_SUGGESTIONS_ROW); - mMinMoreSuggestionsWidth = getFraction(a, + mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a, R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f); a.recycle(); @@ -278,10 +279,6 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen return new BitmapDrawable(res, buffer); } - static float getFraction(final TypedArray a, final int index, final float defValue) { - return a.getFraction(index, 1, 1, defValue); - } - private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) { final CharSequence word = suggestedWords.getWord(pos); final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect(); diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java index 71a6d6a78..cd9ff85f8 100644 --- a/java/src/com/android/inputmethod/research/ResearchLog.java +++ b/java/src/com/android/inputmethod/research/ResearchLog.java @@ -257,7 +257,7 @@ public class ResearchLog { for (Key keyboardKey : keyboardKeys) { mJsonWriter.beginObject(); mJsonWriter.name("code").value(keyboardKey.mCode); - mJsonWriter.name("altCode").value(keyboardKey.mAltCode); + mJsonWriter.name("altCode").value(keyboardKey.getAltCode()); mJsonWriter.name("x").value(keyboardKey.mX); mJsonWriter.name("y").value(keyboardKey.mY); mJsonWriter.name("w").value(keyboardKey.mWidth); diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index 918fcf5a1..9bb81a034 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -1045,7 +1045,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final int y, final boolean ignoreModifierKey, final boolean altersCode, final int code) { if (key != null) { - CharSequence outputText = key.mOutputText; + String outputText = key.getOutputText(); final Object[] values = { Keyboard.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null : scrubDigitsFromString(outputText.toString()), |