diff options
24 files changed, 561 insertions, 375 deletions
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml index 0ae7fa2ab..70fc7f86e 100644 --- a/java/res/values/attrs.xml +++ b/java/res/values/attrs.xml @@ -242,6 +242,9 @@ <flag name="withIconLeft" value="0x1000" /> <flag name="withIconRight" value="0x2000" /> <flag name="autoXScale" value="0x4000" /> + <!-- If true, character case of code, altCode, moreKeys, keyOutputText, keyLabel, + or keyHintLabel will never be subject to change. --> + <flag name="preserveCase" value="0x8000" /> </attr> <!-- The icon to display on the key instead of the label. --> <attr name="keyIcon" format="enum"> @@ -294,6 +297,19 @@ </declare-styleable> <declare-styleable name="Keyboard_Case"> + <!-- This should be aligned with KeyboardSet_Element's elementName. --> + <attr name="keyboardSetElement" format="enum|string"> + <enum name="alphabet" value="0" /> + <enum name="alphabetManualShifted" value="1" /> + <enum name="alphabetAutomaticShifted" value="2" /> + <enum name="alphabetShiftLocked" value="3" /> + <enum name="alphabetShiftLockShifted" value="4" /> + <enum name="symbols" value="5" /> + <enum name="symbolsShifted" value="6" /> + <enum name="phone" value="7" /> + <enum name="phoneShifted" value="8" /> + <enum name="number" value="9" /> + </attr> <!-- This should be aligned with KeyboardId.MODE_* --> <attr name="mode" format="enum|string"> <enum name="text" value="0" /> @@ -356,10 +372,8 @@ <enum name="phoneShifted" value="8" /> <enum name="number" value="9" /> </attr> - <attr name="elementKeyboard" format="reference|enum"> - <!-- This should be aligned with KeyboardSet.ELEMENT_KEYBOARD_* --> - <enum name="autoGenerateFromAlphabet" value="1"/> - </attr> + <attr name="elementKeyboard" format="reference"/> + <attr name="elementAutoGenerate" format="boolean" /> <!-- TODO: Add setShifted and setShiftLocked attribute. --> </declare-styleable> </resources> diff --git a/java/res/xml/keyboard_set.xml b/java/res/xml/keyboard_set.xml index ebdd9902e..27ef316fe 100644 --- a/java/res/xml/keyboard_set.xml +++ b/java/res/xml/keyboard_set.xml @@ -23,7 +23,24 @@ latin:keyboardLocale="en_GB,en_US"> <Element latin:elementName="alphabet" - latin:elementKeyboard="@xml/kbd_qwerty" /> + latin:elementKeyboard="@xml/kbd_qwerty" + latin:elementAutoGenerate="true" /> + <Element + latin:elementName="alphabetManualShifted" + latin:elementKeyboard="@xml/kbd_qwerty" + latin:elementAutoGenerate="true" /> + <Element + latin:elementName="alphabetAutomaticShifted" + latin:elementKeyboard="@xml/kbd_qwerty" + latin:elementAutoGenerate="true" /> + <Element + latin:elementName="alphabetShiftLocked" + latin:elementKeyboard="@xml/kbd_qwerty" + latin:elementAutoGenerate="true" /> + <Element + latin:elementName="alphabetShiftLockShifted" + latin:elementKeyboard="@xml/kbd_qwerty" + latin:elementAutoGenerate="true" /> <Element latin:elementName="symbols" latin:elementKeyboard="@xml/kbd_symbols" /> diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index 3a9423f4b..9c495fd5f 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -52,9 +52,9 @@ public class Key { public final int mAltCode; /** Label to display */ - public final CharSequence mLabel; + public final String mLabel; /** Hint label to display on the key in conjunction with the label */ - public final CharSequence mHintLabel; + public final String mHintLabel; /** Flags of the label */ private final int mLabelFlags; private static final int LABEL_FLAGS_ALIGN_LEFT = 0x01; @@ -71,14 +71,16 @@ public class Key { private static final int LABEL_FLAGS_WITH_ICON_LEFT = 0x1000; private static final int LABEL_FLAGS_WITH_ICON_RIGHT = 0x2000; private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000; + private static final int LABEL_FLAGS_PRESERVE_CASE = 0x8000; - // TODO: These icon references could be int (icon attribute id) /** Icon to display instead of a label. Icon takes precedence over a label */ + private final int mIconAttrId; + // TODO: Remove this variable. private Drawable mIcon; /** Icon for disabled state */ - private Drawable mDisabledIcon; + private final int mDisabledIconAttrId; /** Preview version of the icon, for the preview popup */ - public final Drawable mPreviewIcon; + public final int mPreviewIconAttrId; /** Width of the key, not including the gap */ public final int mWidth; @@ -187,7 +189,7 @@ public class Key { /** * This constructor is being used only for key in popup suggestions pane. */ - public Key(Keyboard.Params params, CharSequence label, CharSequence hintLabel, Drawable icon, + public Key(Keyboard.Params params, String label, String hintLabel, Drawable icon, int code, CharSequence outputText, int x, int y, int width, int height) { mHeight = height - params.mVerticalGap; mHorizontalGap = params.mHorizontalGap; @@ -204,9 +206,10 @@ public class Key { mOutputText = outputText; mCode = code; mAltCode = Keyboard.CODE_UNSPECIFIED; + mIconAttrId = KeyboardIconsSet.ATTR_UNDEFINED; mIcon = icon; - mDisabledIcon = null; - mPreviewIcon = null; + mDisabledIconAttrId = KeyboardIconsSet.ATTR_UNDEFINED; + mPreviewIconAttrId = KeyboardIconsSet.ATTR_UNDEFINED; // Horizontal gap is divided equally to both sides of the key. mX = x + mHorizontalGap / 2; mY = y; @@ -260,19 +263,6 @@ public class Key { // Update row to have current x coordinate. row.setXPos(keyXPos + keyWidth); - final String[] moreKeys = style.getTextArray(keyAttr, - R.styleable.Keyboard_Key_moreKeys); - // In Arabic symbol layouts, we'd like to keep digits in more keys regardless of - // config_digit_more_keys_enabled. - if (params.mId.isAlphabetKeyboard() - && !res.getBoolean(R.bool.config_digit_more_keys_enabled)) { - mMoreKeys = MoreKeySpecParser.filterOut(res, moreKeys, MoreKeySpecParser.DIGIT_FILTER); - } else { - mMoreKeys = moreKeys; - } - mMaxMoreKeysColumn = style.getInt(keyAttr, - R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMiniKeyboardColumn); - mBackgroundType = style.getInt(keyAttr, R.styleable.Keyboard_Key_backgroundType, BACKGROUND_TYPE_NORMAL); mActionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags, 0); @@ -282,24 +272,47 @@ public class Key { R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0); mVisualInsetsRight = (int) Keyboard.Builder.getDimensionOrFraction(keyAttr, R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0); - final int previewIconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr, + mPreviewIconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIconPreview, KeyboardIconsSet.ICON_UNDEFINED)); - mPreviewIcon = iconsSet.getIconByAttrId(previewIconAttrId); - final int iconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr, + mIconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIcon, KeyboardIconsSet.ICON_UNDEFINED)); - mIcon = iconsSet.getIconByAttrId(iconAttrId); - final int disabledIconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr, + mIcon = iconsSet.getIconByAttrId(mIconAttrId); + mDisabledIconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIconDisabled, KeyboardIconsSet.ICON_UNDEFINED)); - mDisabledIcon = iconsSet.getIconByAttrId(disabledIconAttrId); - mHintLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyHintLabel); - mLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyLabel); mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags, 0); - mOutputText = style.getText(keyAttr, R.styleable.Keyboard_Key_keyOutputText); + final boolean preserveCase = (mLabelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0; + + final String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys); + if (moreKeys != null) { + for (int i = 0; i < moreKeys.length; i++) { + moreKeys[i] = adjustCaseOfStringForKeyboardId( + moreKeys[i], preserveCase, params.mId); + } + } + // TODO: Add new key label flag to control this. + // In Arabic symbol layouts, we'd like to keep digits in more keys regardless of + // config_digit_more_keys_enabled. + if (params.mId.isAlphabetKeyboard() + && !res.getBoolean(R.bool.config_digit_more_keys_enabled)) { + mMoreKeys = MoreKeySpecParser.filterOut(res, moreKeys, MoreKeySpecParser.DIGIT_FILTER); + } else { + mMoreKeys = moreKeys; + } + mMaxMoreKeysColumn = style.getInt(keyAttr, + R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMiniKeyboardColumn); + + mLabel = adjustCaseOfStringForKeyboardId(style.getString( + keyAttr, R.styleable.Keyboard_Key_keyLabel), preserveCase, params.mId); + mHintLabel = adjustCaseOfStringForKeyboardId(style.getString( + keyAttr, R.styleable.Keyboard_Key_keyHintLabel), preserveCase, params.mId); + mOutputText = adjustCaseOfStringForKeyboardId(style.getString( + keyAttr, R.styleable.Keyboard_Key_keyOutputText), preserveCase, params.mId); // Choose the first letter of the label as primary code if not // specified. - final int code = style.getInt(keyAttr, R.styleable.Keyboard_Key_code, - Keyboard.CODE_UNSPECIFIED); + final int code = adjustCaseOfCodeForKeyboardId(style.getInt( + keyAttr, R.styleable.Keyboard_Key_code, Keyboard.CODE_UNSPECIFIED), preserveCase, + params.mId); if (code == Keyboard.CODE_UNSPECIFIED && mOutputText == null && !TextUtils.isEmpty(mLabel)) { if (mLabel.length() != 1) { @@ -312,13 +325,36 @@ public class Key { } else { mCode = code; } - mAltCode = style.getInt(keyAttr, - R.styleable.Keyboard_Key_altCode, Keyboard.CODE_UNSPECIFIED); + mAltCode = adjustCaseOfCodeForKeyboardId(style.getInt(keyAttr, + R.styleable.Keyboard_Key_altCode, Keyboard.CODE_UNSPECIFIED), preserveCase, + params.mId); mHashCode = hashCode(this); keyAttr.recycle(); } + private static int adjustCaseOfCodeForKeyboardId(int code, boolean preserveCase, + KeyboardId id) { + if (!Keyboard.isLetterCode(code) || preserveCase) return code; + final String text = new String(new int[] { code } , 0, 1); + final String casedText = adjustCaseOfStringForKeyboardId(text, preserveCase, id); + return casedText.codePointAt(0); + } + + private static String adjustCaseOfStringForKeyboardId(String text, boolean preserveCase, + KeyboardId id) { + if (text == null || preserveCase) return text; + switch (id.mElementId) { + case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: + case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: + return text.toUpperCase(id.mLocale); + default: + return text; + } + } + private static int hashCode(Key key) { return Arrays.hashCode(new Object[] { key.mX, @@ -328,13 +364,15 @@ public class Key { key.mCode, key.mLabel, key.mHintLabel, + key.mIconAttrId, // Key can be distinguishable without the following members. // key.mAltCode, // key.mOutputText, // key.mActionFlags, // key.mLabelFlags, // key.mIcon, - // key.mPreviewIcon, + // key.mDisabledIconAttrId, + // key.mPreviewIconAttrId, // key.mBackgroundType, // key.mHorizontalGap, // key.mVerticalGap, @@ -471,8 +509,9 @@ public class Key { return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0; } - public Drawable getIcon() { - return mEnabled ? mIcon : mDisabledIcon; + // TODO: Get rid of this method. + public Drawable getIcon(KeyboardIconsSet iconSet) { + return mEnabled ? mIcon : iconSet.getIconByAttrId(mDisabledIconAttrId); } // TODO: Get rid of this method. diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index c548f1145..abe5b62ee 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -245,10 +245,10 @@ public class Keyboard { } // TODO: Remove this method. - public CharSequence adjustLabelCase(CharSequence label) { + public String adjustLabelCase(String label) { if (mId.isAlphabetKeyboard() && isShiftedOrShiftLocked() && !TextUtils.isEmpty(label) && label.length() < 3 && Character.isLowerCase(label.charAt(0))) { - return label.toString().toUpperCase(mId.mLocale); + return label.toUpperCase(mId.mLocale); } return label; } @@ -293,6 +293,8 @@ public class Keyboard { public final Set<Key> mShiftLockKeys = new HashSet<Key>(); public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet(); + public KeyboardSet.KeysCache mKeysCache; + public int mMostCommonKeyHeight = 0; public int mMostCommonKeyWidth = 0; @@ -361,7 +363,8 @@ public class Keyboard { clearHistogram(); } - public void onAddKey(Key key) { + public void onAddKey(Key newKey) { + final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey; mKeys.add(key); updateHistogram(key); if (key.mCode == Keyboard.CODE_SHIFT) { @@ -688,6 +691,10 @@ public class Keyboard { params.mTouchPositionCorrection.load(data); } + public void setAutoGenerate(KeyboardSet.KeysCache keysCache) { + mParams.mKeysCache = keysCache; + } + public Builder<KP> load(int xmlId, KeyboardId id) { mParams.mId = id; final XmlResourceParser parser = mResources.getXml(xmlId); @@ -1034,6 +1041,9 @@ public class Keyboard { final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard_Case); try { + final boolean keyboardSetElementMatched = matchTypedValue(a, + R.styleable.Keyboard_Case_keyboardSetElement, id.mElementId, + KeyboardId.elementIdToName(id.mElementId)); final boolean modeMatched = matchTypedValue(a, R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode)); final boolean navigateActionMatched = matchBoolean(a, @@ -1062,13 +1072,15 @@ public class Keyboard { R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage()); final boolean countryCodeMatched = matchString(a, R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry()); - final boolean selected = modeMatched && navigateActionMatched - && passwordInputMatched && hasSettingsKeyMatched && f2KeyModeMatched - && clobberSettingsKeyMatched && shortcutKeyEnabledMatched - && hasShortcutKeyMatched && imeActionMatched && localeCodeMatched - && languageCodeMatched && countryCodeMatched; - - if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s%s%s%s%s%s> %s", TAG_CASE, + final boolean selected = keyboardSetElementMatched && modeMatched + && navigateActionMatched && passwordInputMatched && hasSettingsKeyMatched + && f2KeyModeMatched && clobberSettingsKeyMatched + && shortcutKeyEnabledMatched && hasShortcutKeyMatched && imeActionMatched + && localeCodeMatched && languageCodeMatched && countryCodeMatched; + + if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s> %s", TAG_CASE, + textAttr(a.getString(R.styleable.Keyboard_Case_keyboardSetElement), + "keyboardSetElement"), textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"), booleanAttr(a, R.styleable.Keyboard_Case_navigateAction, "navigateAction"), booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, "passwordInput"), diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index edce4c6e8..8db8c9460 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -58,7 +58,7 @@ public class KeyboardId { public final int mOrientation; public final int mWidth; public final int mMode; - public final int mElement; + public final int mElementId; private final int mInputType; private final int mImeOptions; private final boolean mSettingsKeyEnabled; @@ -68,14 +68,14 @@ public class KeyboardId { private final int mHashCode; - public KeyboardId(int element, Locale locale, int orientation, int width, int mode, + public KeyboardId(int elementId, Locale locale, int orientation, int width, int mode, int inputType, int imeOptions, boolean settingsKeyEnabled, boolean clobberSettingsKey, boolean shortcutKeyEnabled, boolean hasShortcutKey) { this.mLocale = locale; this.mOrientation = orientation; this.mWidth = width; this.mMode = mode; - this.mElement = element; + this.mElementId = elementId; this.mInputType = inputType; this.mImeOptions = imeOptions; this.mSettingsKeyEnabled = settingsKeyEnabled; @@ -89,7 +89,7 @@ public class KeyboardId { private static int hashCode(KeyboardId id) { return Arrays.hashCode(new Object[] { id.mOrientation, - id.mElement, + id.mElementId, id.mMode, id.mWidth, id.navigateAction(), @@ -107,7 +107,7 @@ public class KeyboardId { if (other == this) return true; return other.mOrientation == this.mOrientation - && other.mElement == this.mElement + && other.mElementId == this.mElementId && other.mMode == this.mMode && other.mWidth == this.mWidth && other.navigateAction() == this.navigateAction() @@ -121,19 +121,19 @@ public class KeyboardId { } public boolean isAlphabetKeyboard() { - return mElement < ELEMENT_SYMBOLS; + return mElementId < ELEMENT_SYMBOLS; } public boolean isSymbolsKeyboard() { - return mElement == ELEMENT_SYMBOLS || mElement == ELEMENT_SYMBOLS_SHIFTED; + return mElementId == ELEMENT_SYMBOLS || mElementId == ELEMENT_SYMBOLS_SHIFTED; } public boolean isPhoneKeyboard() { - return mElement == ELEMENT_PHONE || mElement == ELEMENT_PHONE_SHIFTED; + return mElementId == ELEMENT_PHONE || mElementId == ELEMENT_PHONE_SHIFTED; } public boolean isPhoneShiftKeyboard() { - return mElement == ELEMENT_PHONE_SHIFTED; + return mElementId == ELEMENT_PHONE_SHIFTED; } public boolean navigateAction() { @@ -188,7 +188,7 @@ public class KeyboardId { @Override public String toString() { return String.format("[%s %s %s%d %s %s %s%s%s%s%s%s%s]", - elementToString(mElement), + elementIdToName(mElementId), mLocale, (mOrientation == 1 ? "port" : "land"), mWidth, modeName(mMode), @@ -211,8 +211,8 @@ public class KeyboardId { && TextUtils.equals(a.privateImeOptions, b.privateImeOptions); } - public static String elementToString(int element) { - switch (element) { + public static String elementIdToName(int elementId) { + switch (elementId) { case ELEMENT_ALPHABET: return "alphabet"; case ELEMENT_ALPHABET_MANUAL_SHIFTED: return "alphabetManualShifted"; case ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: return "alphabetAutomaticShifted"; diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java index f4602b8fd..cacb8a324 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java @@ -22,7 +22,6 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.util.Log; -import android.util.TypedValue; import android.util.Xml; import android.view.inputmethod.EditorInfo; @@ -41,6 +40,7 @@ import java.io.IOException; import java.lang.ref.SoftReference; import java.util.HashMap; import java.util.Locale; +import java.util.Map; /** * This class represents a set of keyboards. Each of them represents a different keyboard @@ -55,10 +55,38 @@ public class KeyboardSet { private static final String TAG_KEYBOARD_SET = TAG; private static final String TAG_ELEMENT = "Element"; - private static final int ELEMENT_KEYBOARD_AUTO_GENERATE_FROM_ALPHABET = 1; - private final Context mContext; private final Params mParams; + private final KeysCache mKeysCache = new KeysCache(); + + public static class KeysCache { + private final Map<Key, Key> mMap; + + public KeysCache() { + mMap = new HashMap<Key, Key>(); + } + + 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; + } + } + + static class KeyboardElement { + final int mElementId; + final int mLayoutId; + final boolean mAutoGenerate; + KeyboardElement(int elementId, int layoutId, boolean autoGenerate) { + mElementId = elementId; + mLayoutId = layoutId; + mAutoGenerate = autoGenerate; + } + } static class Params { int mMode; @@ -72,7 +100,8 @@ public class KeyboardSet { Locale mLocale; int mOrientation; int mWidth; - final HashMap<Integer, Integer> mElementKeyboards = new HashMap<Integer, Integer>(); + final Map<Integer, KeyboardElement> mElementKeyboards = + new HashMap<Integer, KeyboardElement>(); Params() {} } @@ -89,15 +118,15 @@ public class KeyboardSet { } public Keyboard getMainKeyboard() { - return getKeyboard(false, false); + return getKeyboard(false, false, false); } public Keyboard getSymbolsKeyboard() { - return getKeyboard(true, false); + return getKeyboard(true, false, false); } public Keyboard getSymbolsShiftedKeyboard() { - final Keyboard keyboard = getKeyboard(true, true); + final Keyboard keyboard = getKeyboard(true, false, true); // TODO: Remove this logic once we introduce initial keyboard shift state attribute. // Symbol shift keyboard may have a shift key that has a caps lock style indicator (a.k.a. // sticky shift key). To show or dismiss the indicator, we need to call setShiftLocked() @@ -106,22 +135,23 @@ public class KeyboardSet { return keyboard; } - private Keyboard getKeyboard(boolean isSymbols, boolean isShift) { - final int element = KeyboardSet.getElement(mParams.mMode, isSymbols, isShift); - // TODO: If xmlId is ELEMENT_KEYBOARD_AUTO_GENERATE_FROM_ALPHABET, auto generate the - // keyboard based on base main alphabet keyboard considering element. - final int xmlId = mParams.mElementKeyboards.get(element); - final KeyboardId id = KeyboardSet.getKeyboardId(element, isSymbols, mParams); - final Keyboard keyboard = getKeyboard(mContext, xmlId, id); + private Keyboard getKeyboard(boolean isSymbols, boolean isShiftLock, boolean isShift) { + final int elementId = KeyboardSet.getElementId( + mParams.mMode, isSymbols, isShiftLock, isShift); + final KeyboardElement keyboardElement = mParams.mElementKeyboards.get(elementId); + // TODO: If keyboardElement.mAutoGenerate is true, the keyboard will be auto generated + // based on keyboardElement.mKayoutId Keyboard XML definition. + final KeyboardId id = KeyboardSet.getKeyboardId(elementId, isSymbols, mParams); + final Keyboard keyboard = getKeyboard(mContext, keyboardElement, id); return keyboard; } public KeyboardId getMainKeyboardId() { - final int element = KeyboardSet.getElement(mParams.mMode, false, false); - return KeyboardSet.getKeyboardId(element, false, mParams); + final int elementId = KeyboardSet.getElementId(mParams.mMode, false, false, false); + return KeyboardSet.getKeyboardId(elementId, false, mParams); } - private Keyboard getKeyboard(Context context, int xmlId, KeyboardId id) { + private Keyboard getKeyboard(Context context, KeyboardElement element, KeyboardId id) { final Resources res = context.getResources(); final SoftReference<Keyboard> ref = sKeyboardCache.get(id); Keyboard keyboard = (ref == null) ? null : ref.get(); @@ -130,7 +160,10 @@ public class KeyboardSet { try { final Keyboard.Builder<Keyboard.Params> builder = new Keyboard.Builder<Keyboard.Params>(context, new Keyboard.Params()); - builder.load(xmlId, id); + if (element.mAutoGenerate) { + builder.setAutoGenerate(mKeysCache); + } + builder.load(element.mLayoutId, id); builder.setTouchPositionCorrectionEnabled(mParams.mTouchPositionCorrectionEnabled); keyboard = builder.build(); } finally { @@ -152,7 +185,8 @@ public class KeyboardSet { return keyboard; } - private static int getElement(int mode, boolean isSymbols, boolean isShift) { + private static int getElementId(int mode, boolean isSymbols, boolean isShiftLock, + boolean isShift) { switch (mode) { case KeyboardId.MODE_PHONE: return (isSymbols && isShift) @@ -164,14 +198,15 @@ public class KeyboardSet { return isShift ? KeyboardId.ELEMENT_SYMBOLS_SHIFTED : KeyboardId.ELEMENT_SYMBOLS; } + // TODO: Consult isShiftLock and isShift to determine the element. return KeyboardId.ELEMENT_ALPHABET; } } - private static KeyboardId getKeyboardId(int element, boolean isSymbols, Params params) { + private static KeyboardId getKeyboardId(int elementId, boolean isSymbols, Params params) { final boolean hasShortcutKey = params.mVoiceKeyEnabled && (isSymbols != params.mVoiceKeyOnMain); - return new KeyboardId(element, params.mLocale, params.mOrientation, params.mWidth, + return new KeyboardId(elementId, params.mLocale, params.mOrientation, params.mWidth, params.mMode, params.mInputType, params.mImeOptions, params.mSettingsKeyEnabled, params.mNoSettingsKey, params.mVoiceKeyEnabled, hasShortcutKey); } @@ -240,7 +275,9 @@ public class KeyboardSet { try { parseKeyboardSet(mResources, R.xml.keyboard_set); } catch (Exception e) { - // + throw new RuntimeException(e.getMessage() + " in " + + mResources.getResourceName(R.xml.keyboard_set) + + " of locale " + mParams.mLocale); } finally { LocaleUtils.setSystemLocale(mResources, savedLocale); } @@ -304,15 +341,12 @@ public class KeyboardSet { final int elementName = a.getInt( R.styleable.KeyboardSet_Element_elementName, 0); - final int index = R.styleable.KeyboardSet_Element_elementKeyboard; - final TypedValue v = a.peekValue(index); - final int elementKeyboard; - if (v.type == TypedValue.TYPE_REFERENCE) { - elementKeyboard = a.getResourceId(index, 0); - } else { - elementKeyboard = a.getInt(index, 0); - } - mParams.mElementKeyboards.put(elementName, elementKeyboard); + final int elementKeyboard = a.getResourceId( + R.styleable.KeyboardSet_Element_elementKeyboard, 0); + final boolean elementAutoGenerate = a.getBoolean( + R.styleable.KeyboardSet_Element_elementAutoGenerate, false); + mParams.mElementKeyboards.put(elementName, new KeyboardElement( + elementName, elementKeyboard, elementAutoGenerate)); } finally { a.recycle(); } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index abc220e34..afcf51059 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -39,6 +39,7 @@ import android.widget.RelativeLayout; import android.widget.TextView; import com.android.inputmethod.compat.FrameLayoutCompatUtils; +import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; @@ -373,7 +374,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } // Read fraction value in TypedArray as float. - private static float getRatio(TypedArray a, int index) { + /* package */ static float getRatio(TypedArray a, int index) { return a.getFraction(index, 1000, 1000, 1) / 1000.0f; } @@ -519,7 +520,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } // Draw key background. - /* package */ void onDrawKeyBackground(Key key, Canvas canvas, KeyDrawParams params) { + 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 bgHeight = key.mHeight + params.mPadding.top + params.mPadding.bottom; @@ -541,8 +542,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } // Draw key top visuals. - /* package */ void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, - KeyDrawParams params) { + protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) { final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight; final int keyHeight = key.mHeight; final float centerX = keyWidth * 0.5f; @@ -553,11 +553,11 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } // Draw key label. - final Drawable icon = key.getIcon(); + final Drawable icon = key.getIcon(mKeyboard.mIconsSet); float positionX = centerX; if (key.mLabel != null) { // Switch the character to uppercase if shift is pressed - final CharSequence label = mKeyboard.adjustLabelCase(key.mLabel); + final String label = mKeyboard.adjustLabelCase(key.mLabel); // For characters, use large font. For labels like "Done", use smaller font. paint.setTypeface(key.selectTypeface(params.mKeyTextStyle)); final int labelSize = key.selectTextSize(params.mKeyLetterSize, @@ -639,7 +639,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // Draw hint label. if (key.mHintLabel != null) { - final CharSequence hint = key.mHintLabel; + final String hint = key.mHintLabel; final int hintColor; final int hintSize; if (key.hasHintLabel()) { @@ -718,7 +718,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } // Draw popup hint "..." at the bottom right corner of the key. - /* package */ void drawKeyPopupHint(Key key, Canvas canvas, Paint paint, KeyDrawParams params) { + protected void drawKeyPopupHint(Key key, Canvas canvas, Paint paint, KeyDrawParams params) { final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight; final int keyHeight = key.mHeight; @@ -737,8 +737,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } } - private static final Rect sTextBounds = new Rect(); - private static int getCharGeometryCacheKey(char reference, Paint paint) { final int labelSize = (int)paint.getTextSize(); final Typeface face = paint.getTypeface(); @@ -754,42 +752,45 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } } - private static float getCharHeight(char[] character, Paint paint) { + // Working variable for the following methods. + private final Rect mTextBounds = new Rect(); + + private float getCharHeight(char[] character, Paint paint) { final Integer key = getCharGeometryCacheKey(character[0], paint); final Float cachedValue = sTextHeightCache.get(key); if (cachedValue != null) return cachedValue; - paint.getTextBounds(character, 0, 1, sTextBounds); - final float height = sTextBounds.height(); + paint.getTextBounds(character, 0, 1, mTextBounds); + final float height = mTextBounds.height(); sTextHeightCache.put(key, height); return height; } - private static float getCharWidth(char[] character, Paint paint) { + private float getCharWidth(char[] character, Paint paint) { final Integer key = getCharGeometryCacheKey(character[0], paint); final Float cachedValue = sTextWidthCache.get(key); if (cachedValue != null) return cachedValue; - paint.getTextBounds(character, 0, 1, sTextBounds); - final float width = sTextBounds.width(); + paint.getTextBounds(character, 0, 1, mTextBounds); + final float width = mTextBounds.width(); sTextWidthCache.put(key, width); return width; } - private static float getLabelWidth(CharSequence label, Paint paint) { - paint.getTextBounds(label.toString(), 0, label.length(), sTextBounds); - return sTextBounds.width(); + protected float getLabelWidth(CharSequence label, Paint paint) { + paint.getTextBounds(label.toString(), 0, label.length(), mTextBounds); + return mTextBounds.width(); } - public float getDefaultLabelWidth(CharSequence label, Paint paint) { + public float getDefaultLabelWidth(String label, Paint paint) { paint.setTextSize(mKeyDrawParams.mKeyLabelSize); paint.setTypeface(mKeyDrawParams.mKeyTextStyle); return getLabelWidth(label, paint); } - private static void drawIcon(Canvas canvas, Drawable icon, int x, int y, int width, + protected static void drawIcon(Canvas canvas, Drawable icon, int x, int y, int width, int height) { canvas.translate(x, y); icon.setBounds(0, 0, width, height); @@ -898,9 +899,10 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } previewText.setText(mKeyboard.adjustLabelCase(key.mLabel)); } else { - final Drawable previewIcon = key.mPreviewIcon; + final Drawable previewIcon = mKeyboard.mIconsSet.getIconByAttrId( + key.mPreviewIconAttrId); previewText.setCompoundDrawables(null, null, null, - previewIcon != null ? previewIcon : key.getIcon()); + previewIcon != null ? previewIcon : key.getIcon(mKeyboard.mIconsSet)); previewText.setText(null); } previewText.setBackgroundDrawable(params.mPreviewBackground); diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java index aa0f9751d..f5b282df3 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java @@ -20,14 +20,11 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.TypedArray; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; +import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Message; import android.text.TextUtils; @@ -54,8 +51,6 @@ import com.android.inputmethod.latin.StaticInnerHandlerWrapper; import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils; -import java.util.Arrays; -import java.util.HashMap; import java.util.Locale; import java.util.WeakHashMap; @@ -72,38 +67,33 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true; - /* Space key and its icons, drawables and colors. */ + // TODO: Kill process when the usability study mode was changed. + private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy; + + /** Listener for {@link KeyboardActionListener}. */ + private KeyboardActionListener mKeyboardActionListener; + + /* Space key and its icons */ private Key mSpaceKey; private Drawable mSpaceIcon; - private final boolean mIsSpacebarTriggeringPopupByLongPress; - private static final int SPACE_LED_LENGTH_PERCENT = 80; - private final boolean mAutoCorrectionSpacebarLedEnabled; - private final Drawable mAutoCorrectionSpacebarLedIcon; + // Stuff to draw language name on spacebar. + private boolean mNeedsToDisplayLanguage; + private Locale mSpacebarLocale; + private float mSpacebarTextFadeFactor = 0.0f; private final float mSpacebarTextRatio; private float mSpacebarTextSize; private final int mSpacebarTextColor; private final int mSpacebarTextShadowColor; - private final HashMap<Integer, BitmapDrawable> mSpacebarDrawableCache = - new HashMap<Integer, BitmapDrawable>(); - - private boolean mAutoCorrectionSpacebarLedOn; - private boolean mNeedsToDisplayLanguage; - private Locale mSpacebarLocale; - private float mSpacebarTextFadeFactor = 0.0f; - // Height in space key the language name will be drawn. (proportional to space key height) - public static final float SPACEBAR_LANGUAGE_BASELINE = 0.6f; + private static final float SPACEBAR_LANGUAGE_BASELINE = 0.6f; // If the full language name needs to be smaller than this value to be drawn on space key, // its short language name will be used instead. private static final float MINIMUM_SCALE_OF_LANGUAGE_NAME = 0.8f; - - private final SuddenJumpingTouchEventHandler mTouchScreenRegulator; - - // Timing constants - private final int mKeyRepeatInterval; - - // TODO: Kill process when the usability study mode was changed. - private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy; + // Stuff to draw auto correction LED on spacebar. + private boolean mAutoCorrectionSpacebarLedOn; + private final boolean mAutoCorrectionSpacebarLedEnabled; + private final Drawable mAutoCorrectionSpacebarLedIcon; + private static final int SPACE_LED_LENGTH_PERCENT = 80; // Mini keyboard private PopupWindow mMoreKeysWindow; @@ -111,17 +101,16 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke private int mMoreKeysPanelPointerTrackerId; private final WeakHashMap<Key, MoreKeysPanel> mMoreKeysPanelCache = new WeakHashMap<Key, MoreKeysPanel>(); + private final boolean mConfigShowMiniKeyboardAtTouchedPoint; - /** Listener for {@link KeyboardActionListener}. */ - private KeyboardActionListener mKeyboardActionListener; + private final boolean mIsSpacebarTriggeringPopupByLongPress; + private final SuddenJumpingTouchEventHandler mTouchScreenRegulator; + protected KeyDetector mKeyDetector; private boolean mHasDistinctMultitouch; private int mOldPointerCount = 1; private Key mOldKey; - private final boolean mConfigShowMiniKeyboardAtTouchedPoint; - protected KeyDetector mKeyDetector; - // To detect double tap. protected GestureDetector mGestureDetector; @@ -134,10 +123,14 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke private static final int MSG_IGNORE_DOUBLE_TAP = 3; private static final int MSG_KEY_TYPED = 4; + private final int mKeyRepeatInterval; private boolean mInKeyRepeat; public KeyTimerHandler(LatinKeyboardView outerInstance) { super(outerInstance); + // TODO: This should be the attribute of LatinKeyboardView. + final Resources res = outerInstance.getContext().getResources(); + mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval); } @Override @@ -147,7 +140,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke switch (msg.what) { case MSG_REPEAT_KEY: tracker.onRepeatKey(tracker.getKey()); - startKeyRepeatTimer(keyboardView.mKeyRepeatInterval, tracker); + startKeyRepeatTimer(mKeyRepeatInterval, tracker); break; case MSG_LONGPRESS_KEY: keyboardView.openMiniKeyboardIfRequired(tracker.getKey(), tracker); @@ -213,7 +206,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke } } - private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener { + class DoubleTapListener extends GestureDetector.SimpleOnGestureListener { private boolean mProcessingShiftDoubleTapEvent = false; @Override @@ -222,7 +215,8 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke if (ENABLE_CAPSLOCK_BY_DOUBLETAP && keyboard.mId.isAlphabetKeyboard()) { final int pointerIndex = firstDown.getActionIndex(); final int id = firstDown.getPointerId(pointerIndex); - final PointerTracker tracker = getPointerTracker(id); + final PointerTracker tracker = PointerTracker.getPointerTracker( + id, LatinKeyboardView.this); final Key key = tracker.getKeyOn((int)firstDown.getX(), (int)firstDown.getY()); // If the first down event is on shift key. if (key != null && key.isShift()) { @@ -241,7 +235,8 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke final MotionEvent secondDown = secondTap; final int pointerIndex = secondDown.getActionIndex(); final int id = secondDown.getPointerId(pointerIndex); - final PointerTracker tracker = getPointerTracker(id); + final PointerTracker tracker = PointerTracker.getPointerTracker( + id, LatinKeyboardView.this); final Key key = tracker.getKeyOn((int)secondDown.getX(), (int)secondDown.getY()); // If the second down event is also on shift key. if (key != null && key.isShift()) { @@ -268,8 +263,10 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this); final Resources res = getResources(); + // TODO: This should be the attribute of LatinKeyboardView. mConfigShowMiniKeyboardAtTouchedPoint = res.getBoolean( R.bool.config_show_mini_keyboard_at_touched_point); + // TODO: This should be the attribute of LatinKeyboardView. final float keyHysteresisDistance = res.getDimension(R.dimen.key_hysteresis_distance); mKeyDetector = new KeyDetector(keyHysteresisDistance); @@ -280,10 +277,10 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke mHasDistinctMultitouch = context.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); - mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval); PointerTracker.init(mHasDistinctMultitouch, getContext()); + // TODO: This should be the attribute of LatinKeyboardView. final int longPressSpaceKeyTimeout = res.getInteger(R.integer.config_long_press_space_key_timeout); mIsSpacebarTriggeringPopupByLongPress = (longPressSpaceKeyTimeout > 0); @@ -360,7 +357,6 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; mSpacebarTextSize = keyHeight * mSpacebarTextRatio; mSpacebarLocale = keyboard.mId.mLocale; - clearSpacebarDrawableCache(); } /** @@ -522,10 +518,6 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke return true; } - private PointerTracker getPointerTracker(final int id) { - return PointerTracker.getPointerTracker(id, this); - } - public boolean isInSlidingKeyInput() { if (mMoreKeysPanel != null) { return true; @@ -609,7 +601,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke } if (mKeyTimerHandler.isInKeyRepeat()) { - final PointerTracker tracker = getPointerTracker(id); + final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); // Key repeating timer will be canceled if 2 or more keys are in action, and current // event (UP or DOWN) is non-modifier key. if (pointerCount > 1 && !tracker.isModifier()) { @@ -623,7 +615,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke // multi-touch panel. if (nonDistinctMultitouch) { // Use only main (id=0) pointer tracker. - PointerTracker tracker = getPointerTracker(0); + final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); if (pointerCount == 1 && oldPointerCount == 2) { // Multi-touch to single touch transition. // Send a down event for the latest pointer if the key is different from the @@ -652,7 +644,8 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke if (action == MotionEvent.ACTION_MOVE) { for (int i = 0; i < pointerCount; i++) { - final PointerTracker tracker = getPointerTracker(me.getPointerId(i)); + final PointerTracker tracker = PointerTracker.getPointerTracker( + me.getPointerId(i), this); final int px, py; if (mMoreKeysPanel != null && tracker.mPointerId == mMoreKeysPanelPointerTrackerId) { @@ -669,7 +662,8 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke } } } else { - getPointerTracker(id).processMotionEvent(action, x, y, eventTime, this); + final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); + tracker.processMotionEvent(action, x, y, eventTime, this); } return true; @@ -739,7 +733,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke */ public boolean dispatchHoverEvent(MotionEvent event) { if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { - final PointerTracker tracker = getPointerTracker(0); + final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker); } @@ -759,44 +753,30 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke public void updateSpacebar(float fadeFactor, boolean needsToDisplayLanguage) { mSpacebarTextFadeFactor = fadeFactor; mNeedsToDisplayLanguage = needsToDisplayLanguage; - updateSpacebarIcon(); invalidateKey(mSpaceKey); } public void updateAutoCorrectionState(boolean isAutoCorrection) { if (!mAutoCorrectionSpacebarLedEnabled) return; mAutoCorrectionSpacebarLedOn = isAutoCorrection; - updateSpacebarIcon(); invalidateKey(mSpaceKey); } @Override - /* package */ void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, - KeyDrawParams params) { + protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) { super.onDrawKeyTopVisuals(key, canvas, paint, params); if (key.mCode == Keyboard.CODE_SPACE) { + drawSpacebar(key, canvas, paint); + // Whether space key needs to show the "..." popup hint for special purposes if (mIsSpacebarTriggeringPopupByLongPress && Utils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) { - super.drawKeyPopupHint(key, canvas, paint, params); + drawKeyPopupHint(key, canvas, paint, params); } } } - // TODO: Get rid of this method and draw spacebar locale and auto correction spacebar LED - // in onDrawKeyTopVisuals. - private void updateSpacebarIcon() { - if (mSpaceKey == null) return; - if (mNeedsToDisplayLanguage) { - mSpaceKey.setIcon(getSpaceDrawable(mSpacebarLocale)); - } else if (mAutoCorrectionSpacebarLedOn) { - mSpaceKey.setIcon(getSpaceDrawable(null)); - } else { - mSpaceKey.setIcon(mSpaceIcon); - } - } - private static int getSpacebarTextColor(int color, float fadeFactor) { final int newColor = Color.argb((int)(Color.alpha(color) * fadeFactor), Color.red(color), Color.green(color), Color.blue(color)); @@ -804,24 +784,23 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke } // Compute width of text with specified text size using paint. - private static int getTextWidth(Paint paint, String text, float textSize, Rect bounds) { + private int getTextWidth(Paint paint, String text, float textSize) { paint.setTextSize(textSize); - paint.getTextBounds(text, 0, text.length(), bounds); - return bounds.width(); + return (int)getLabelWidth(text, paint); } // Layout locale language name on spacebar. - private static String layoutSpacebar(Paint paint, Locale locale, int width, + private String layoutLanguageOnSpacebar(Paint paint, Locale locale, int width, float origTextSize) { - final Rect bounds = new Rect(); - + paint.setTextAlign(Align.CENTER); + paint.setTypeface(Typeface.DEFAULT); // Estimate appropriate language name text size to fit in maxTextWidth. String language = Utils.getFullDisplayName(locale, true); - int textWidth = getTextWidth(paint, language, origTextSize, bounds); + int textWidth = getTextWidth(paint, language, origTextSize); // Assuming text width and text size are proportional to each other. float textSize = origTextSize * Math.min(width / textWidth, 1.0f); // allow variable text size - textWidth = getTextWidth(paint, language, textSize, bounds); + textWidth = getTextWidth(paint, language, textSize); // If text size goes too small or text does not fit, use middle or short name final boolean useMiddleName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME) || (textWidth > width); @@ -829,7 +808,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke final boolean useShortName; if (useMiddleName) { language = Utils.getMiddleDisplayLanguage(locale); - textWidth = getTextWidth(paint, language, origTextSize, bounds); + textWidth = getTextWidth(paint, language, origTextSize); textSize = origTextSize * Math.min(width / textWidth, 1.0f); useShortName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME) || (textWidth > width); @@ -839,7 +818,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke if (useShortName) { language = Utils.getShortDisplayLanguage(locale); - textWidth = getTextWidth(paint, language, origTextSize, bounds); + textWidth = getTextWidth(paint, language, origTextSize); textSize = origTextSize * Math.min(width / textWidth, 1.0f); } paint.setTextSize(textSize); @@ -847,50 +826,14 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke return language; } - private Integer getSpaceDrawableKey(Locale locale) { - return Arrays.hashCode(new Object[] { - locale, - mAutoCorrectionSpacebarLedOn, - mSpacebarTextFadeFactor - }); - } - - private void clearSpacebarDrawableCache() { - for (final BitmapDrawable drawable : mSpacebarDrawableCache.values()) { - final Bitmap bitmap = drawable.getBitmap(); - bitmap.recycle(); - } - mSpacebarDrawableCache.clear(); - } - - private BitmapDrawable getSpaceDrawable(Locale locale) { - final Integer hashCode = getSpaceDrawableKey(locale); - final BitmapDrawable cached = mSpacebarDrawableCache.get(hashCode); - if (cached != null) { - return cached; - } - final BitmapDrawable drawable = new BitmapDrawable(getResources(), drawSpacebar( - locale, mAutoCorrectionSpacebarLedOn, mSpacebarTextFadeFactor)); - mSpacebarDrawableCache.put(hashCode, drawable); - return drawable; - } - - private Bitmap drawSpacebar(Locale inputLocale, boolean isAutoCorrection, - float textFadeFactor) { - final int width = mSpaceKey.mWidth; - final int height = mSpaceIcon != null ? mSpaceIcon.getIntrinsicHeight() : mSpaceKey.mHeight; - final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(buffer); - canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + private void drawSpacebar(Key key, Canvas canvas, Paint paint) { + final int width = key.mWidth; + final int height = mSpaceIcon != null ? mSpaceIcon.getIntrinsicHeight() : key.mHeight; // If application locales are explicitly selected. - if (inputLocale != null) { - final Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setTextAlign(Align.CENTER); - - final String language = layoutSpacebar(paint, inputLocale, width, mSpacebarTextSize); - + if (mNeedsToDisplayLanguage) { + final String language = layoutLanguageOnSpacebar(paint, mSpacebarLocale, width, + mSpacebarTextSize); // Draw language text with shadow // In case there is no space icon, we will place the language text at the center of // spacebar. @@ -898,28 +841,25 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke final float textHeight = -paint.ascent() + descent; final float baseline = (mSpaceIcon != null) ? height * SPACEBAR_LANGUAGE_BASELINE : height / 2 + textHeight / 2; - paint.setColor(getSpacebarTextColor(mSpacebarTextShadowColor, textFadeFactor)); + paint.setColor(getSpacebarTextColor(mSpacebarTextShadowColor, mSpacebarTextFadeFactor)); canvas.drawText(language, width / 2, baseline - descent - 1, paint); - paint.setColor(getSpacebarTextColor(mSpacebarTextColor, textFadeFactor)); + paint.setColor(getSpacebarTextColor(mSpacebarTextColor, mSpacebarTextFadeFactor)); canvas.drawText(language, width / 2, baseline - descent, paint); } // Draw the spacebar icon at the bottom - if (isAutoCorrection) { + if (mAutoCorrectionSpacebarLedOn) { final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100; final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight(); int x = (width - iconWidth) / 2; int y = height - iconHeight; - mAutoCorrectionSpacebarLedIcon.setBounds(x, y, x + iconWidth, y + iconHeight); - mAutoCorrectionSpacebarLedIcon.draw(canvas); + drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight); } else if (mSpaceIcon != null) { final int iconWidth = mSpaceIcon.getIntrinsicWidth(); final int iconHeight = mSpaceIcon.getIntrinsicHeight(); int x = (width - iconWidth) / 2; int y = height - iconHeight; - mSpaceIcon.setBounds(x, y, x + iconWidth, y + iconHeight); - mSpaceIcon.draw(canvas); + drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight); } - return buffer; } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java index faea38941..b7215ec1b 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java @@ -40,8 +40,8 @@ public class KeyStyles { private static final KeyStyle EMPTY_KEY_STYLE = new EmptyKeyStyle(); public interface KeyStyle { - public String[] getTextArray(TypedArray a, int index); - public CharSequence getText(TypedArray a, int index); + public String[] getStringArray(TypedArray a, int index); + public String getString(TypedArray a, int index); public int getInt(TypedArray a, int index, int defaultValue); public int getFlag(TypedArray a, int index, int defaultValue); } @@ -52,13 +52,13 @@ public class KeyStyles { } @Override - public String[] getTextArray(TypedArray a, int index) { - return parseTextArray(a, index); + public String[] getStringArray(TypedArray a, int index) { + return parseStringArray(a, index); } @Override - public CharSequence getText(TypedArray a, int index) { - return a.getText(index); + public String getString(TypedArray a, int index) { + return a.getString(index); } @Override @@ -71,16 +71,15 @@ public class KeyStyles { return a.getInt(index, defaultValue); } - protected static String[] parseTextArray(TypedArray a, int index) { + protected static String[] parseStringArray(TypedArray a, int index) { if (!a.hasValue(index)) return null; - final CharSequence text = a.getText(index); - return parseCsvText(text.toString(), a.getResources(), R.string.english_ime_name); + return parseCsvString(a.getString(index), a.getResources(), R.string.english_ime_name); } } /* package for test */ - static String[] parseCsvText(String rawText, Resources res, int packageNameResId) { + static String[] parseCsvString(String rawText, Resources res, int packageNameResId) { final String text = Utils.resolveStringResource(rawText, res, packageNameResId); final int size = text.length(); if (size == 0) { @@ -139,15 +138,15 @@ public class KeyStyles { private final HashMap<Integer, Object> mAttributes = new HashMap<Integer, Object>(); @Override - public String[] getTextArray(TypedArray a, int index) { + public String[] getStringArray(TypedArray a, int index) { return a.hasValue(index) - ? super.getTextArray(a, index) : (String[])mAttributes.get(index); + ? super.getStringArray(a, index) : (String[])mAttributes.get(index); } @Override - public CharSequence getText(TypedArray a, int index) { + public String getString(TypedArray a, int index) { return a.hasValue(index) - ? super.getText(a, index) : (CharSequence)mAttributes.get(index); + ? super.getString(a, index) : (String)mAttributes.get(index); } @Override @@ -170,10 +169,10 @@ public class KeyStyles { // TODO: Currently not all Key attributes can be declared as style. readInt(keyAttr, R.styleable.Keyboard_Key_code); readInt(keyAttr, R.styleable.Keyboard_Key_altCode); - readText(keyAttr, R.styleable.Keyboard_Key_keyLabel); - readText(keyAttr, R.styleable.Keyboard_Key_keyOutputText); - readText(keyAttr, R.styleable.Keyboard_Key_keyHintLabel); - readTextArray(keyAttr, R.styleable.Keyboard_Key_moreKeys); + readString(keyAttr, R.styleable.Keyboard_Key_keyLabel); + readString(keyAttr, R.styleable.Keyboard_Key_keyOutputText); + readString(keyAttr, R.styleable.Keyboard_Key_keyHintLabel); + readStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys); readFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags); readInt(keyAttr, R.styleable.Keyboard_Key_keyIcon); readInt(keyAttr, R.styleable.Keyboard_Key_keyIconDisabled); @@ -183,9 +182,9 @@ public class KeyStyles { readFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags); } - private void readText(TypedArray a, int index) { + private void readString(TypedArray a, int index) { if (a.hasValue(index)) - mAttributes.put(index, a.getText(index)); + mAttributes.put(index, a.getString(index)); } private void readInt(TypedArray a, int index) { @@ -199,8 +198,8 @@ public class KeyStyles { mAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0)); } - private void readTextArray(TypedArray a, int index) { - final CharSequence[] value = parseTextArray(a, index); + private void readStringArray(TypedArray a, int index) { + final String[] value = parseStringArray(a, index); if (value != null) mAttributes.put(index, value); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java index 09ecbcaa0..bec6ae1cc 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java @@ -31,7 +31,7 @@ public class KeyboardIconsSet { private static final String TAG = KeyboardIconsSet.class.getSimpleName(); public static final int ICON_UNDEFINED = 0; - private static final int ATTR_UNDEFINED = 0; + public static final int ATTR_UNDEFINED = 0; private final Map<Integer, Drawable> mIcons = new HashMap<Integer, Drawable>(); diff --git a/java/src/com/android/inputmethod/latin/ComposingStateManager.java b/java/src/com/android/inputmethod/latin/ComposingStateManager.java index 8811f2023..27f509a29 100644 --- a/java/src/com/android/inputmethod/latin/ComposingStateManager.java +++ b/java/src/com/android/inputmethod/latin/ComposingStateManager.java @@ -53,6 +53,13 @@ public class ComposingStateManager { } } + public synchronized boolean isComposing() { + // TODO: use the composing flag in WordComposer instead of maintaining it + // here separately. Even better, do away with this class and manage the auto + // correction indicator in the same place as the suggestions. + return mIsComposing; + } + public synchronized boolean isAutoCorrectionIndicatorOn() { return mAutoCorrectionIndicatorOn; } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 31cbc4ee3..d59497d6a 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1702,10 +1702,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (DEBUG) { Log.d(TAG, "Flip the indicator. " + oldAutoCorrectionIndicator + " -> " + newAutoCorrectionIndicator); - if (newAutoCorrectionIndicator + if (mComposingStateManager.isComposing() && newAutoCorrectionIndicator != mComposingStateManager.isAutoCorrectionIndicatorOn()) { - throw new RuntimeException("Couldn't flip the indicator! We are not " - + "composing a word right now."); + throw new RuntimeException("Couldn't flip the indicator!"); } } final CharSequence textWithUnderline = diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java index 2bc2cfdf6..db3544987 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java @@ -22,7 +22,8 @@ import com.android.inputmethod.keyboard.ProximityInfo; import java.util.TreeMap; public class SpellCheckerProximityInfo { - final private static int NUL = KeyDetector.NOT_A_CODE; + /* public for test */ + final public static int NUL = KeyDetector.NOT_A_CODE; // This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside // native code - this value is passed at creation of the binary object and reused diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java index 3d26d972d..f42b8e681 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java @@ -72,7 +72,7 @@ public class MoreSuggestions extends Keyboard { int pos = fromPos, rowStartPos = fromPos; final int size = Math.min(suggestions.size(), SuggestionsView.MAX_SUGGESTIONS); while (pos < size) { - final CharSequence word = suggestions.getWord(pos); + final String word = suggestions.getWord(pos).toString(); // TODO: Should take care of text x-scaling. mWidths[pos] = (int)view.getDefaultLabelWidth(word, paint) + padding; final int numColumn = pos - rowStartPos + 1; diff --git a/native/src/correction.cpp b/native/src/correction.cpp index 63dd283c8..d7d05edc2 100644 --- a/native/src/correction.cpp +++ b/native/src/correction.cpp @@ -269,7 +269,7 @@ bool Correction::needsToPrune() const { // TODO: use edit distance here return mOutputIndex - 1 >= mMaxDepth || mProximityCount > mMaxEditDistance // Allow one char longer word for missing character - || (!mDoAutoCompletion && (mOutputIndex + 1 >= mInputLength)); + || (!mDoAutoCompletion && (mOutputIndex > mInputLength)); } void Correction::addCharToCurrentWord(const int32_t c) { @@ -555,55 +555,6 @@ Correction::CorrectionType Correction::processCharAndCalcState( Correction::~Correction() { } -///////////////////////// -// static inline utils // -///////////////////////// - -static const int TWO_31ST_DIV_255 = S_INT_MAX / 255; -static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) { - return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX); -} - -static const int TWO_31ST_DIV_2 = S_INT_MAX / 2; -inline static void multiplyIntCapped(const int multiplier, int *base) { - const int temp = *base; - if (temp != S_INT_MAX) { - // Branch if multiplier == 2 for the optimization - if (multiplier == 2) { - *base = TWO_31ST_DIV_2 >= temp ? temp << 1 : S_INT_MAX; - } else { - // TODO: This overflow check gives a wrong answer when, for example, - // temp = 2^16 + 1 and multiplier = 2^17 + 1. - // Fix this behavior. - const int tempRetval = temp * multiplier; - *base = tempRetval >= temp ? tempRetval : S_INT_MAX; - } - } -} - -inline static int powerIntCapped(const int base, const int n) { - if (n <= 0) return 1; - if (base == 2) { - return n < 31 ? 1 << n : S_INT_MAX; - } else { - int ret = base; - for (int i = 1; i < n; ++i) multiplyIntCapped(base, &ret); - return ret; - } -} - -inline static void multiplyRate(const int rate, int *freq) { - if (*freq != S_INT_MAX) { - if (*freq > 1000000) { - *freq /= 100; - multiplyIntCapped(rate, freq); - } else { - multiplyIntCapped(rate, freq); - *freq /= 100; - } - } -} - inline static int getQuoteCount(const unsigned short* word, const int length) { int quoteCount = 0; for (int i = 0; i < length; ++i) { @@ -939,7 +890,11 @@ int Correction::RankingAlgorithm::calcFreqForSplitTwoWords( multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &totalFreq); } - multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &totalFreq); + if (isSpaceProximity) { + multiplyRate(WORDS_WITH_MISTYPED_SPACE_DEMOTION_RATE, &totalFreq); + } else { + multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &totalFreq); + } if (capitalizedWordDemotion) { multiplyRate(TWO_WORDS_CAPITALIZED_DEMOTION_RATE, &totalFreq); diff --git a/native/src/correction.h b/native/src/correction.h index 0715551d0..b00c8e120 100644 --- a/native/src/correction.h +++ b/native/src/correction.h @@ -36,6 +36,55 @@ class Correction { NOT_ON_TERMINAL } CorrectionType; + ///////////////////////// + // static inline utils // + ///////////////////////// + + static const int TWO_31ST_DIV_255 = S_INT_MAX / 255; + static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) { + return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX); + } + + static const int TWO_31ST_DIV_2 = S_INT_MAX / 2; + inline static void multiplyIntCapped(const int multiplier, int *base) { + const int temp = *base; + if (temp != S_INT_MAX) { + // Branch if multiplier == 2 for the optimization + if (multiplier == 2) { + *base = TWO_31ST_DIV_2 >= temp ? temp << 1 : S_INT_MAX; + } else { + // TODO: This overflow check gives a wrong answer when, for example, + // temp = 2^16 + 1 and multiplier = 2^17 + 1. + // Fix this behavior. + const int tempRetval = temp * multiplier; + *base = tempRetval >= temp ? tempRetval : S_INT_MAX; + } + } + } + + inline static int powerIntCapped(const int base, const int n) { + if (n <= 0) return 1; + if (base == 2) { + return n < 31 ? 1 << n : S_INT_MAX; + } else { + int ret = base; + for (int i = 1; i < n; ++i) multiplyIntCapped(base, &ret); + return ret; + } + } + + inline static void multiplyRate(const int rate, int *freq) { + if (*freq != S_INT_MAX) { + if (*freq > 1000000) { + *freq /= 100; + multiplyIntCapped(rate, freq); + } else { + multiplyIntCapped(rate, freq); + *freq /= 100; + } + } + } + Correction(const int typedLetterMultiplier, const int fullWordMultiplier); void initCorrection( const ProximityInfo *pi, const int inputLength, const int maxWordLength); diff --git a/native/src/defines.h b/native/src/defines.h index 119a7d779..9c2d08777 100644 --- a/native/src/defines.h +++ b/native/src/defines.h @@ -169,6 +169,7 @@ static void prof_out(void) { #define NOT_VALID_WORD -99 #define NOT_A_CHARACTER -1 #define NOT_A_DISTANCE -1 +#define NOT_A_COORDINATE -1 #define EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO -2 #define PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO -3 #define NOT_A_INDEX -1 @@ -188,6 +189,7 @@ static void prof_out(void) { #define WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE 80 #define WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X 12 #define WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE 58 +#define WORDS_WITH_MISTYPED_SPACE_DEMOTION_RATE 50 #define WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE 75 #define WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE 75 #define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 70 @@ -221,6 +223,9 @@ static void prof_out(void) { #define MAX_DEPTH_MULTIPLIER 3 +#define FIRST_WORD_INDEX 1 +#define SECOND_WORD_INDEX 2 + // TODO: Reduce this constant if possible; check the maximum number of umlauts in the same German // word in the dictionary #define DEFAULT_MAX_UMLAUT_SEARCH_DEPTH 5 diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp index b91957c77..e0e938099 100644 --- a/native/src/proximity_info.cpp +++ b/native/src/proximity_info.cpp @@ -165,6 +165,9 @@ float ProximityInfo::calculateNormalizedSquaredDistance( if (!hasSweetSpotData(keyIndex)) { return NOT_A_DISTANCE_FLOAT; } + if (NOT_A_COORDINATE == mInputXCoordinates[inputIndex]) { + return NOT_A_DISTANCE_FLOAT; + } const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(keyIndex, inputIndex); const float squaredRadius = square(mSweetSpotRadii[keyIndex]); return squaredDistance / squaredRadius; diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp index e998ee486..a7eb4e10d 100644 --- a/native/src/unigram_dictionary.cpp +++ b/native/src/unigram_dictionary.cpp @@ -159,19 +159,26 @@ int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, } PROF_START(20); + if (DEBUG_DICT) { + double ns = queuePool->getMasterQueue()->getHighestNormalizedScore( + proximityInfo->getPrimaryInputWord(), codesSize, 0, 0, 0); + ns += 0; + AKLOGI("Max normalized score = %f", ns); + } const int suggestedWordsCount = queuePool->getMasterQueue()->outputSuggestions(frequencies, outWords); if (DEBUG_DICT) { + double ns = queuePool->getMasterQueue()->getHighestNormalizedScore( + proximityInfo->getPrimaryInputWord(), codesSize, 0, 0, 0); + ns += 0; AKLOGI("Returning %d words", suggestedWordsCount); /// Print the returned words for (int j = 0; j < suggestedWordsCount; ++j) { -#ifdef FLAG_DBG short unsigned int* w = outWords + j * MAX_WORD_LENGTH; char s[MAX_WORD_LENGTH]; for (int i = 0; i <= MAX_WORD_LENGTH; i++) s[i] = w[i]; AKLOGI("%s %i", s, frequencies[j]); -#endif } } PROF_END(20); @@ -205,6 +212,13 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, PROF_START(4); // Note: This line is intentionally left blank + bool hasAutoCorrectionCandidate = false; + WordsPriorityQueue* masterQueue = queuePool->getMasterQueue(); + if (masterQueue->size() > 0) { + double nsForMaster = masterQueue->getHighestNormalizedScore( + proximityInfo->getPrimaryInputWord(), inputLength, 0, 0, 0); + hasAutoCorrectionCandidate = (nsForMaster > START_TWO_WORDS_CORRECTION_THRESHOLD); + } PROF_END(4); PROF_START(5); @@ -216,7 +230,8 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, AKLOGI("--- Suggest missing space characters %d", i); } getMissingSpaceWords(proximityInfo, xcoordinates, ycoordinates, codes, - useFullEditDistance, inputLength, i, correction, queuePool); + useFullEditDistance, inputLength, i, correction, queuePool, + hasAutoCorrectionCandidate); } } PROF_END(5); @@ -236,7 +251,8 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, } if (proximityInfo->hasSpaceProximity(x, y)) { getMistypedSpaceWords(proximityInfo, xcoordinates, ycoordinates, codes, - useFullEditDistance, inputLength, i, correction, queuePool); + useFullEditDistance, inputLength, i, correction, queuePool, + hasAutoCorrectionCandidate); } } } @@ -281,12 +297,12 @@ void UnigramDictionary::getOneWordSuggestions(ProximityInfo *proximityInfo, WordsPriorityQueuePool *queuePool) { initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, correction); getSuggestionCandidates(useFullEditDistance, inputLength, correction, queuePool, - true /* doAutoCompletion */, DEFAULT_MAX_ERRORS); + true /* doAutoCompletion */, DEFAULT_MAX_ERRORS, FIRST_WORD_INDEX); } void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance, const int inputLength, Correction *correction, WordsPriorityQueuePool *queuePool, - const bool doAutoCompletion, const int maxErrors) { + const bool doAutoCompletion, const int maxErrors, const int currentWordIndex) { // TODO: Remove setCorrectionParams correction->setCorrectionParams(0, 0, 0, -1 /* spaceProximityPos */, -1 /* missingSpacePos */, useFullEditDistance, @@ -305,7 +321,8 @@ void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance, int firstChildPos; const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos, - correction, &childCount, &firstChildPos, &siblingPos, queuePool); + correction, &childCount, &firstChildPos, &siblingPos, queuePool, + currentWordIndex); // Update next sibling pos correction->setTreeSiblingPos(outputIndex, siblingPos); @@ -323,31 +340,32 @@ void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance, void UnigramDictionary::getMissingSpaceWords(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const bool useFullEditDistance, const int inputLength, const int missingSpacePos, Correction *correction, - WordsPriorityQueuePool* queuePool) { + WordsPriorityQueuePool* queuePool, const bool hasAutoCorrectionCandidate) { getSplitTwoWordsSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, useFullEditDistance, inputLength, missingSpacePos, -1/* spaceProximityPos */, - correction, queuePool); + correction, queuePool, hasAutoCorrectionCandidate); } void UnigramDictionary::getMistypedSpaceWords(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const bool useFullEditDistance, const int inputLength, const int spaceProximityPos, Correction *correction, - WordsPriorityQueuePool* queuePool) { + WordsPriorityQueuePool* queuePool, const bool hasAutoCorrectionCandidate) { getSplitTwoWordsSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, useFullEditDistance, inputLength, -1 /* missingSpacePos */, spaceProximityPos, - correction, queuePool); + correction, queuePool, hasAutoCorrectionCandidate); } inline void UnigramDictionary::onTerminal(const int freq, const TerminalAttributes& terminalAttributes, Correction *correction, - WordsPriorityQueuePool *queuePool, const bool addToMasterQueue) { + WordsPriorityQueuePool *queuePool, const bool addToMasterQueue, + const int currentWordIndex) { const int inputIndex = correction->getInputIndex(); const bool addToSubQueue = inputIndex < SUB_QUEUE_MAX_COUNT; int wordLength; unsigned short* wordPointer; - if (addToMasterQueue) { + if ((currentWordIndex == 1) && addToMasterQueue) { WordsPriorityQueue *masterQueue = queuePool->getMasterQueue(); const int finalFreq = correction->getFinalFreq(freq, &wordPointer, &wordLength); if (finalFreq != NOT_A_FREQUENCY) { @@ -376,9 +394,14 @@ inline void UnigramDictionary::onTerminal(const int freq, // We only allow two words + other error correction for words with SUB_QUEUE_MIN_WORD_LENGTH // or more length. if (inputIndex >= SUB_QUEUE_MIN_WORD_LENGTH && addToSubQueue) { - // TODO: Check the validity of "inputIndex == wordLength" - //if (addToSubQueue && inputIndex == wordLength) { - WordsPriorityQueue *subQueue = queuePool->getSubQueue1(inputIndex); + WordsPriorityQueue *subQueue; + if (currentWordIndex == 1) { + subQueue = queuePool->getSubQueue1(inputIndex); + } else if (currentWordIndex == 2) { + subQueue = queuePool->getSubQueue2(inputIndex); + } else { + return; + } const int finalFreq = correction->getFinalFreqForSubQueue(freq, &wordPointer, &wordLength, inputIndex); addWord(wordPointer, wordLength, finalFreq, subQueue); @@ -388,17 +411,21 @@ inline void UnigramDictionary::onTerminal(const int freq, void UnigramDictionary::getSplitTwoWordsSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const bool useFullEditDistance, const int inputLength, const int missingSpacePos, - const int spaceProximityPos, Correction *correction, WordsPriorityQueuePool* queuePool) { + const int spaceProximityPos, Correction *correction, WordsPriorityQueuePool* queuePool, + const bool hasAutoCorrectionCandidate) { if (inputLength >= MAX_WORD_LENGTH) return; if (DEBUG_DICT) { int inputCount = 0; if (spaceProximityPos >= 0) ++inputCount; if (missingSpacePos >= 0) ++inputCount; assert(inputCount <= 1); + // MAX_PROXIMITY_CHARS_SIZE in ProximityInfo.java should be 16 + assert(MAX_PROXIMITY_CHARS == 16); } + initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, + inputLength, correction); WordsPriorityQueue *masterQueue = queuePool->getMasterQueue(); - const bool isSpaceProximity = spaceProximityPos >= 0; // First word @@ -411,26 +438,22 @@ void UnigramDictionary::getSplitTwoWordsSuggestions(ProximityInfo *proximityInfo if (firstFreq > 0) { firstOutputWordLength = firstInputWordLength; firstOutputWord = mWord; - } else { - if (masterQueue->size() > 0) { - double nsForMaster = masterQueue->getHighestNormalizedScore( - proximityInfo->getPrimaryInputWord(), inputLength, 0, 0, 0); - if (nsForMaster > START_TWO_WORDS_CORRECTION_THRESHOLD) { - // Do nothing if the highest suggestion exceeds the threshold. - return; - } - } + } else if (!hasAutoCorrectionCandidate) { WordsPriorityQueue* firstWordQueue = queuePool->getSubQueue1(firstInputWordLength); - if (firstWordQueue->size() < 1) { + if (!firstWordQueue || firstWordQueue->size() < 1) { return; } int score = 0; const double ns = firstWordQueue->getHighestNormalizedScore( proximityInfo->getPrimaryInputWord(), firstInputWordLength, &firstOutputWord, &score, &firstOutputWordLength); + if (DEBUG_DICT) { + AKLOGI("NS1 = %f, Score = %d", ns, score); + } // Two words correction won't be done if the score of the first word doesn't exceed the // threshold. - if (ns < TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD) { + if (ns < TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD + || firstOutputWordLength < SUB_QUEUE_MIN_WORD_LENGTH) { return; } firstFreq = score >> (firstOutputWordLength @@ -456,14 +479,6 @@ void UnigramDictionary::getSplitTwoWordsSuggestions(ProximityInfo *proximityInfo outputWord[firstOutputWordLength] = SPACE; outputWordLength = firstOutputWordLength + 1; - //const int outputWordLength = firstOutputWordLength + secondWordLength + 1; - // Space proximity preparation - //WordsPriorityQueue *subQueue = queuePool->getSubQueue1(); - //initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, firstOutputWordLength, - //subQueue, correction); - //getSuggestionCandidates(useFullEditDistance, firstOutputWordLength, correction, subQueue, - //false, MAX_ERRORS_FOR_TWO_WORDS); - // Second word const int secondInputWordLength = isSpaceProximity ? (inputLength - spaceProximityPos - 1) @@ -478,9 +493,42 @@ void UnigramDictionary::getSplitTwoWordsSuggestions(ProximityInfo *proximityInfo if (secondFreq > 0) { secondOutputWordLength = secondInputWordLength; secondOutputWord = mWord; + } else if (!hasAutoCorrectionCandidate) { + const int offset = secondInputWordStartPos; + initSuggestions(proximityInfo, &xcoordinates[offset], &ycoordinates[offset], + codes + offset * MAX_PROXIMITY_CHARS, secondInputWordLength, correction); + queuePool->clearSubQueue2(); + getSuggestionCandidates(useFullEditDistance, secondInputWordLength, correction, + queuePool, false, MAX_ERRORS_FOR_TWO_WORDS, SECOND_WORD_INDEX); + if (DEBUG_DICT) { + AKLOGI("Dump second word candidates %d", secondInputWordLength); + for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) { + queuePool->getSubQueue2(i)->dumpTopWord(); + } + } + WordsPriorityQueue* secondWordQueue = queuePool->getSubQueue2(secondInputWordLength); + if (!secondWordQueue || secondWordQueue->size() < 1) { + return; + } + int score = 0; + const double ns = secondWordQueue->getHighestNormalizedScore( + proximityInfo->getPrimaryInputWord(), secondInputWordLength, + &secondOutputWord, &score, &secondOutputWordLength); + if (DEBUG_DICT) { + AKLOGI("NS2 = %f, Score = %d", ns, score); + } + // Two words correction won't be done if the score of the first word doesn't exceed the + // threshold. + if (ns < TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD + || secondOutputWordLength < SUB_QUEUE_MIN_WORD_LENGTH) { + return; + } + secondFreq = score >> (secondOutputWordLength + + TWO_WORDS_PLUS_OTHER_ERROR_CORRECTION_DEMOTION_DIVIDER); } if (DEBUG_DICT) { + DUMP_WORD(secondOutputWord, secondOutputWordLength); AKLOGI("Second freq: %d", secondFreq); } @@ -742,7 +790,8 @@ int UnigramDictionary::getBigramPosition(int pos, unsigned short *word, int offs // given level, as output into newCount when traversing this level's parent. inline bool UnigramDictionary::processCurrentNode(const int initialPos, Correction *correction, int *newCount, - int *newChildrenPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool) { + int *newChildrenPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool, + const int currentWordIndex) { if (DEBUG_DICT) { correction->checkState(); } @@ -823,7 +872,8 @@ inline bool UnigramDictionary::processCurrentNode(const int initialPos, const int childrenAddressPos = BinaryFormat::skipFrequency(flags, pos); const int attributesPos = BinaryFormat::skipChildrenPosition(flags, childrenAddressPos); TerminalAttributes terminalAttributes(DICT_ROOT, flags, attributesPos); - onTerminal(freq, terminalAttributes, correction, queuePool, needsToInvokeOnTerminal); + onTerminal(freq, terminalAttributes, correction, queuePool, needsToInvokeOnTerminal, + currentWordIndex); // If there are more chars in this node, then this virtual node has children. // If we are on the last char, this virtual node has children if this node has. diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h index b950971bb..461e73586 100644 --- a/native/src/unigram_dictionary.h +++ b/native/src/unigram_dictionary.h @@ -99,11 +99,13 @@ class UnigramDictionary { const int inputLength, Correction *correction, WordsPriorityQueuePool* queuePool); void getSuggestionCandidates( const bool useFullEditDistance, const int inputLength, Correction *correction, - WordsPriorityQueuePool* queuePool, const bool doAutoCompletion, const int maxErrors); + WordsPriorityQueuePool* queuePool, const bool doAutoCompletion, const int maxErrors, + const int currentWordIndex); void getSplitTwoWordsSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const bool useFullEditDistance, const int inputLength, const int spaceProximityPos, - const int missingSpacePos, Correction *correction, WordsPriorityQueuePool* queuePool); + const int missingSpacePos, Correction *correction, WordsPriorityQueuePool* queuePool, + const bool hasAutoCorrectionCandidate); void getSplitTwoWordsSuggestionsOld(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const bool useFullEditDistance, const int inputLength, const int spaceProximityPos, @@ -111,18 +113,20 @@ class UnigramDictionary { void getMissingSpaceWords(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const bool useFullEditDistance, const int inputLength, const int missingSpacePos, Correction *correction, - WordsPriorityQueuePool* queuePool); + WordsPriorityQueuePool* queuePool, const bool hasAutoCorrectionCandidate); void getMistypedSpaceWords(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const bool useFullEditDistance, const int inputLength, const int spaceProximityPos, Correction *correction, - WordsPriorityQueuePool* queuePool); + WordsPriorityQueuePool* queuePool, const bool hasAutoCorrectionCandidate); void onTerminal(const int freq, const TerminalAttributes& terminalAttributes, - Correction *correction, WordsPriorityQueuePool *queuePool, const bool addToMasterQueue); + Correction *correction, WordsPriorityQueuePool *queuePool, const bool addToMasterQueue, + const int currentWordIndex); bool needsToSkipCurrentNode(const unsigned short c, const int inputIndex, const int skipPos, const int depth); // Process a node by considering proximity, missing and excessive character bool processCurrentNode(const int initialPos, Correction *correction, int *newCount, - int *newChildPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool); + int *newChildPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool, + const int currentWordIndex); int getMostFrequentWordLike(const int startInputIndex, const int inputLength, ProximityInfo *proximityInfo, unsigned short *word); int getMostFrequentWordLikeInner(const uint16_t* const inWord, const int length, diff --git a/native/src/words_priority_queue.h b/native/src/words_priority_queue.h index c85f2b9b3..e8cd983b1 100644 --- a/native/src/words_priority_queue.h +++ b/native/src/words_priority_queue.h @@ -137,7 +137,7 @@ class WordsPriorityQueue { if (size() <= 0) { return; } - DUMP_WORD(mSuggestions.top()->mWord, mSuggestions.top()->mWordLength); + DUMP_WORD(mHighestSuggestedWord->mWord, mHighestSuggestedWord->mWordLength); } double getHighestNormalizedScore(const unsigned short* before, const int beforeLength, diff --git a/native/src/words_priority_queue_pool.h b/native/src/words_priority_queue_pool.h index 5fa254852..599b89711 100644 --- a/native/src/words_priority_queue_pool.h +++ b/native/src/words_priority_queue_pool.h @@ -45,15 +45,21 @@ class WordsPriorityQueuePool { // TODO: Come up with more generic pool WordsPriorityQueue* getSubQueue1(const int id) { - if (DEBUG_WORDS_PRIORITY_QUEUE) { - assert(id >= 0 && id < SUB_QUEUE_MAX_COUNT); + if (id < 0 || id >= SUB_QUEUE_MAX_COUNT) { + if (DEBUG_WORDS_PRIORITY_QUEUE) { + assert(false); + } + return 0; } return mSubQueues1[id]; } WordsPriorityQueue* getSubQueue2(const int id) { - if (DEBUG_WORDS_PRIORITY_QUEUE) { - assert(id >= 0 && id < SUB_QUEUE_MAX_COUNT); + if (id < 0 || id >= SUB_QUEUE_MAX_COUNT) { + if (DEBUG_WORDS_PRIORITY_QUEUE) { + assert(false); + } + return 0; } return mSubQueues2[id]; } @@ -66,6 +72,18 @@ class WordsPriorityQueuePool { } } + inline void clearSubQueue1() { + for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) { + mSubQueues1[i]->clear(); + } + } + + inline void clearSubQueue2() { + for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) { + mSubQueues2[i]->clear(); + } + } + void dumpSubQueue1TopSuggestions() { AKLOGI("DUMP SUBQUEUE1 TOP SUGGESTIONS"); for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) { diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java index 29881d91c..2ae8027af 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyStylesTests.java @@ -39,7 +39,7 @@ public class KeyStylesTests extends AndroidTestCase { } private void assertTextArray(String message, String value, String ... expected) { - final String actual[] = KeyStyles.parseCsvText(value, mTestResources, + final String actual[] = KeyStyles.parseCsvString(value, mTestResources, R.string.empty_string); if (expected.length == 0) { assertNull(message, actual); diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java index 0d5e42b81..59ca22df4 100644 --- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java +++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java @@ -34,6 +34,11 @@ import android.widget.TextView; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; +import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService; // for proximity info +import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo; + +import java.util.Arrays; +import java.util.HashMap; public class InputLogicTests extends ServiceTestCase<LatinIME> { @@ -42,9 +47,29 @@ public class InputLogicTests extends ServiceTestCase<LatinIME> { private LatinIME mLatinIME; private TextView mTextView; private InputConnection mInputConnection; + private HashMap<Integer, int[]> mProximity; public InputLogicTests() { super(LatinIME.class); + mProximity = createProximity(); + } + + private static HashMap<Integer, int[]> createProximity() { + final HashMap<Integer, int[]> proximity = new HashMap<Integer, int[]>(); + final int[] testProximity = SpellCheckerProximityInfo.getProximityForScript( + AndroidSpellCheckerService.SCRIPT_LATIN); + final int ROW_SIZE = SpellCheckerProximityInfo.ROW_SIZE; + final int NUL = SpellCheckerProximityInfo.NUL; + for (int row = 0; row * ROW_SIZE < testProximity.length; ++row) { + final int rowBase = row * ROW_SIZE; + int column; + for (column = 1; NUL != testProximity[rowBase + column]; ++column) { + // Do nothing, just search for a NUL element + } + proximity.put(testProximity[row * ROW_SIZE], + Arrays.copyOfRange(testProximity, rowBase, rowBase + column)); + } + return proximity; } // returns the previous setting value @@ -73,7 +98,9 @@ public class InputLogicTests extends ServiceTestCase<LatinIME> { mLatinIME.onCreate(); setDebugMode(previousDebugSetting); final EditorInfo ei = new EditorInfo(); + ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; final InputConnection ic = mTextView.onCreateInputConnection(ei); + ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; final LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); final ViewGroup vg = new FrameLayout(getContext()); @@ -95,7 +122,11 @@ public class InputLogicTests extends ServiceTestCase<LatinIME> { // to keep these tests as pinpoint as possible and avoid bringing it too many dependencies, // but keep them in mind if something breaks. Commenting them out as is should work. //mLatinIME.onPressKey(codePoint); - mLatinIME.onCodeInput(codePoint, new int[] { codePoint }, + int[] proximityKeys = mProximity.get(codePoint); + if (null == proximityKeys) { + proximityKeys = new int[] { codePoint }; + } + mLatinIME.onCodeInput(codePoint, proximityKeys, KeyboardActionListener.NOT_A_TOUCH_COORDINATE, KeyboardActionListener.NOT_A_TOUCH_COORDINATE); //mLatinIME.onReleaseKey(codePoint, false); @@ -139,4 +170,11 @@ public class InputLogicTests extends ServiceTestCase<LatinIME> { type(Keyboard.CODE_DELETE); assertEquals("delete selection", EXPECTED_RESULT, mTextView.getText().toString()); } + + public void testAutoCorrect() { + final String STRING_TO_TYPE = "tgis "; + final String EXPECTED_RESULT = "this "; + type(STRING_TO_TYPE); + assertEquals("simple auto-correct", EXPECTED_RESULT, mTextView.getText().toString()); + } } |