diff options
29 files changed, 972 insertions, 924 deletions
diff --git a/java/proguard.flags b/java/proguard.flags index 7ce6f41b5..44416ecba 100644 --- a/java/proguard.flags +++ b/java/proguard.flags @@ -30,3 +30,7 @@ -keep class com.android.inputmethod.latin.SettingsActivity { *; } + +-keep class com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder$MiniKeyboardLayoutParams { + <init>(...); +} diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml index 253581836..0bfe9ac65 100644 --- a/java/res/values/strings.xml +++ b/java/res/values/strings.xml @@ -109,17 +109,17 @@ <!-- Indicates that a word has been added to the dictionary --> <string name="added_word"><xliff:g id="word">%s</xliff:g> : Saved</string> - <!-- Label for soft enter key when it performs GO action. Must be short to fit on key! --> + <!-- Label for soft enter key when it performs GO action. Must be short to fit on key! [CHAR LIMIT=5] --> <string name="label_go_key">Go</string> - <!-- Label for soft enter key when it performs NEXT action. Must be short to fit on key! --> + <!-- Label for soft enter key when it performs NEXT action. Must be short to fit on key! [CHAR LIMIT=5] --> <string name="label_next_key">Next</string> - <!-- Label for soft enter key when it performs DONE action. Must be short to fit on key! --> + <!-- Label for soft enter key when it performs DONE action. Must be short to fit on key! [CHAR LIMIT=5] --> <string name="label_done_key">Done</string> - <!-- Label for soft enter key when it performs SEND action. Must be short to fit on key! --> + <!-- Label for soft enter key when it performs SEND action. Must be short to fit on key! [CHAR LIMIT=5] --> <string name="label_send_key">Send</string> - <!-- Label for "switch to alphabetic" key. Must be short to fit on key! --> + <!-- Label for "switch to alphabetic" key. Must be short to fit on key! [CHAR LIMIT=3] --> <string name="label_to_alpha_key">ABC</string> - <!-- Label for Shift modifier key of symbol keyboard. Must be short to fit on key! --> + <!-- Label for Shift modifier key of symbol keyboard. Must be short to fit on key! [CHAR LIMIT=5] --> <string name="label_more_key">More</string> <!-- Label for "Pause" key of phone number keyboard. Must be short to fit on key! [CHAR LIMIT=5] --> <string name="label_pause_key">Pause</string> diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index 5b34dd5db..8bc7e43b4 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -27,6 +27,7 @@ import android.util.Xml; import com.android.inputmethod.keyboard.internal.KeyStyles; import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; +import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.keyboard.internal.KeyboardParser; import com.android.inputmethod.keyboard.internal.KeyboardParser.ParseException; import com.android.inputmethod.keyboard.internal.PopupCharactersParser; @@ -73,7 +74,9 @@ public class Key { /** Height of the key, not including the gap */ public final int mHeight; /** The horizontal gap around this key */ - public final int mGap; + 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; @@ -102,9 +105,6 @@ public class Key { /** Whether this key repeats itself when held down */ public final boolean mRepeatable; - /** The Keyboard that this key belongs to */ - private final Keyboard mKeyboard; - /** The current pressed state of this key */ private boolean mPressed; /** If this is a sticky key, is its highlight on? */ @@ -191,13 +191,13 @@ public class Key { /** * This constructor is being used only for key in popup mini keyboard. */ - public Key(Resources res, Keyboard keyboard, CharSequence popupCharacter, int x, int y, + public Key(Resources res, KeyboardParams params, CharSequence popupCharacter, int x, int y, int width, int height, int edgeFlags) { - mKeyboard = keyboard; - mHeight = height - keyboard.getVerticalGap(); - mGap = keyboard.getHorizontalGap(); + mHeight = height - params.mVerticalGap; + mHorizontalGap = params.mHorizontalGap; + mVerticalGap = params.mVerticalGap; mVisualInsetsLeft = mVisualInsetsRight = 0; - mWidth = width - mGap; + mWidth = width - mHorizontalGap; mEdgeFlags = edgeFlags; mHintLabel = null; mLabelOption = 0; @@ -210,10 +210,10 @@ public class Key { mLabel = PopupCharactersParser.getLabel(popupSpecification); mOutputText = PopupCharactersParser.getOutputText(popupSpecification); final int code = PopupCharactersParser.getCode(res, popupSpecification); - mCode = keyboard.isRtlKeyboard() ? getRtlParenthesisCode(code) : code; - mIcon = keyboard.mIconsSet.getIcon(PopupCharactersParser.getIconId(popupSpecification)); + mCode = params.mIsRtlKeyboard ? getRtlParenthesisCode(code) : code; + mIcon = params.mIconsSet.getIcon(PopupCharactersParser.getIconId(popupSpecification)); // Horizontal gap is divided equally to both sides of the key. - mX = x + mGap / 2; + mX = x + mHorizontalGap / 2; mY = y; } @@ -221,16 +221,15 @@ public class Key { * Create a key with the given top-left coordinate and extract its attributes from the XML * parser. * @param res resources associated with the caller's context - * @param row the row that this key belongs to. The row must already be attached to - * a {@link Keyboard}. + * @param params the keyboard building parameters. + * @param row the row that this key belongs to. * @param x the x coordinate of the top-left * @param y the y coordinate of the top-left * @param parser the XML parser containing the attributes for this key * @param keyStyles active key styles set */ - public Key(Resources res, Row row, int x, int y, XmlResourceParser parser, - KeyStyles keyStyles) { - mKeyboard = row.getKeyboard(); + public Key(Resources res, KeyboardParams params, Row row, int x, int y, + XmlResourceParser parser, KeyStyles keyStyles) { final TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard); @@ -238,13 +237,14 @@ public class Key { try { mHeight = KeyboardParser.getDimensionOrFraction(keyboardAttr, R.styleable.Keyboard_rowHeight, - mKeyboard.getKeyboardHeight(), row.mDefaultHeight) - row.mVerticalGap; - mGap = KeyboardParser.getDimensionOrFraction(keyboardAttr, + params.mHeight, row.mRowHeight) - params.mVerticalGap; + mHorizontalGap = KeyboardParser.getDimensionOrFraction(keyboardAttr, R.styleable.Keyboard_horizontalGap, - mKeyboard.getDisplayWidth(), row.mDefaultHorizontalGap); + params.mWidth, params.mHorizontalGap); + mVerticalGap = params.mVerticalGap; keyWidth = KeyboardParser.getDimensionOrFraction(keyboardAttr, R.styleable.Keyboard_keyWidth, - mKeyboard.getDisplayWidth(), row.mDefaultWidth); + params.mWidth, row.mDefaultKeyWidth); } finally { keyboardAttr.recycle(); } @@ -262,7 +262,7 @@ public class Key { style = keyStyles.getEmptyKeyStyle(); } - final int keyboardWidth = mKeyboard.getDisplayWidth(); + final int keyboardWidth = params.mOccupiedWidth; int keyXPos = KeyboardParser.getDimensionOrFraction(keyAttr, R.styleable.Keyboard_Key_keyXPos, keyboardWidth, x); if (keyXPos < 0) { @@ -287,19 +287,19 @@ public class Key { } // Horizontal gap is divided equally to both sides of the key. - mX = keyXPos + mGap / 2; + mX = keyXPos + mHorizontalGap / 2; mY = y; - mWidth = keyWidth - mGap; + mWidth = keyWidth - mHorizontalGap; CharSequence[] popupCharacters = style.getTextArray( keyAttr, R.styleable.Keyboard_Key_popupCharacters); - if (mKeyboard.mId.mPasswordInput) { + if (params.mId.mPasswordInput) { popupCharacters = PopupCharactersParser.filterOut( res, popupCharacters, PopupCharactersParser.NON_ASCII_FILTER); } // In Arabic symbol layouts, we'd like to keep digits in popup characters regardless of // config_digit_popup_characters_enabled. - if (mKeyboard.mId.isAlphabetKeyboard() && !res.getBoolean( + if (params.mId.isAlphabetKeyboard() && !res.getBoolean( R.bool.config_digit_popup_characters_enabled)) { mPopupCharacters = PopupCharactersParser.filterOut( res, popupCharacters, PopupCharactersParser.DIGIT_FILTER); @@ -308,7 +308,7 @@ public class Key { } mMaxPopupColumn = style.getInt(keyboardAttr, R.styleable.Keyboard_Key_maxPopupKeyboardColumn, - mKeyboard.getMaxPopupKeyboardColumn()); + params.mMaxPopupColumn); mRepeatable = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable, false); mFunctional = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isFunctional, false); @@ -316,7 +316,7 @@ public class Key { mEnabled = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_enabled, true); mEdgeFlags = 0; - final KeyboardIconsSet iconsSet = mKeyboard.mIconsSet; + final KeyboardIconsSet iconsSet = params.mIconsSet; mVisualInsetsLeft = KeyboardParser.getDimensionOrFraction(keyAttr, R.styleable.Keyboard_Key_visualInsetsLeft, keyboardWidth, 0); mVisualInsetsRight = KeyboardParser.getDimensionOrFraction(keyAttr, @@ -324,17 +324,14 @@ public class Key { mPreviewIcon = iconsSet.getIcon(style.getInt( keyAttr, R.styleable.Keyboard_Key_keyIconPreview, KeyboardIconsSet.ICON_UNDEFINED)); - Keyboard.setDefaultBounds(mPreviewIcon); mIcon = iconsSet.getIcon(style.getInt( keyAttr, R.styleable.Keyboard_Key_keyIcon, KeyboardIconsSet.ICON_UNDEFINED)); - Keyboard.setDefaultBounds(mIcon); final int shiftedIconId = style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIconShifted, KeyboardIconsSet.ICON_UNDEFINED); if (shiftedIconId != KeyboardIconsSet.ICON_UNDEFINED) { final Drawable shiftedIcon = iconsSet.getIcon(shiftedIconId); - Keyboard.setDefaultBounds(shiftedIcon); - mKeyboard.addShiftedIcon(this, shiftedIcon); + params.addShiftedIcon(this, shiftedIcon); } mHintLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyHintLabel); @@ -347,15 +344,12 @@ public class Key { Keyboard.CODE_UNSPECIFIED); if (code == Keyboard.CODE_UNSPECIFIED && !TextUtils.isEmpty(mLabel)) { final int firstChar = mLabel.charAt(0); - mCode = mKeyboard.isRtlKeyboard() ? getRtlParenthesisCode(firstChar) : firstChar; + mCode = params.mIsRtlKeyboard ? getRtlParenthesisCode(firstChar) : firstChar; } else if (code != Keyboard.CODE_UNSPECIFIED) { mCode = code; } else { mCode = Keyboard.CODE_DUMMY; } - if (mCode == Keyboard.CODE_SHIFT) { - mKeyboard.addShiftKey(this); - } } finally { keyAttr.recycle(); } @@ -365,10 +359,6 @@ public class Key { mEdgeFlags |= flags; } - public CharSequence getCaseAdjustedLabel() { - return mKeyboard.adjustLabelCase(mLabel); - } - public Typeface selectTypeface(Typeface defaultTypeface) { // TODO: Handle "bold" here too? if ((mLabelOption & LABEL_OPTION_FONT_NORMAL) != 0) { @@ -460,10 +450,10 @@ public class Key { * assume that all points between the key and the edge are considered to be on the key. */ public boolean isOnKey(int x, int y) { - final int left = mX - mGap / 2; - final int right = left + mWidth + mGap; + final int left = mX - mHorizontalGap / 2; + final int right = left + mWidth + mHorizontalGap; final int top = mY; - final int bottom = top + mHeight + mKeyboard.getVerticalGap(); + final int bottom = top + mHeight + mVerticalGap; final int flags = mEdgeFlags; if (flags == 0) { return x >= left && x <= right && y >= top && y <= bottom; diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java index 6d25025c5..53d46a344 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java +++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java @@ -57,7 +57,7 @@ public class KeyDetector { mCorrectionX = (int)correctionX; mCorrectionY = (int)correctionY; mKeyboard = keyboard; - final int threshold = keyboard.getMostCommonKeyWidth(); + final int threshold = keyboard.mMostCommonKeyWidth; mProximityThresholdSquare = threshold * threshold; } diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index 8840c79da..809c949df 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -16,24 +16,17 @@ package com.android.inputmethod.keyboard; -import android.content.Context; -import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.text.TextUtils; -import android.util.Log; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; -import com.android.inputmethod.keyboard.internal.KeyboardParser; +import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.keyboard.internal.KeyboardShiftState; -import com.android.inputmethod.latin.R; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; /** * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard @@ -54,8 +47,6 @@ import java.util.List; * </pre> */ public class Keyboard { - private static final String TAG = Keyboard.class.getSimpleName(); - public static final int EDGE_LEFT = 0x01; public static final int EDGE_RIGHT = 0x02; public static final int EDGE_TOP = 0x04; @@ -93,212 +84,80 @@ public class Keyboard { // Code value representing the code is not specified. public static final int CODE_UNSPECIFIED = -99; - /** Horizontal gap default for all rows */ - private int mDefaultHorizontalGap; + public final KeyboardId mId; + + /** Total height of the keyboard, including the padding and keys */ + public final int mOccupiedHeight; + /** Total width of the keyboard, including the padding and keys */ + public final int mOccupiedWidth; - /** Default key width */ - private int mDefaultWidth; + public final int mHeight; + public final int mWidth; - /** Default key height */ - private int mDefaultHeight; + /** Default row height */ + public final int mDefaultRowHeight; /** Default gap between rows */ - private int mDefaultVerticalGap; + public final int mVerticalGap; + + public final int mMostCommonKeyWidth; /** Popup keyboard template */ - private int mPopupKeyboardResId; + public final int mPopupKeyboardResId; /** Maximum column for popup keyboard */ - private int mMaxPopupColumn; + public final int mMaxPopupColumn; /** True if Right-To-Left keyboard */ - private boolean mIsRtlKeyboard; - - /** List of shift keys in this keyboard and its icons and state */ - private final List<Key> mShiftKeys = new ArrayList<Key>(); - private final HashMap<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>(); - private final HashMap<Key, Drawable> mUnshiftedIcons = new HashMap<Key, Drawable>(); - private final HashSet<Key> mShiftLockKeys = new HashSet<Key>(); - private final KeyboardShiftState mShiftState = new KeyboardShiftState(); - - /** Total height of the keyboard, including the padding and keys */ - private int mTotalHeight; - - /** - * Total width (minimum width) of the keyboard, including left side gaps and keys, but not any - * gaps on the right side. - */ - private int mMinWidth; - - /** List of keys in this keyboard */ - private final List<Key> mKeys = new ArrayList<Key>(); - - /** Width of the screen available to fit the keyboard */ - private final int mDisplayWidth; - - /** Height of the screen */ - private final int mDisplayHeight; - - /** Height of keyboard */ - private int mKeyboardHeight; + public final boolean mIsRtlKeyboard; - private int mMostCommonKeyWidth = 0; + /** List of keys and icons in this keyboard */ + public final List<Key> mKeys; + public final List<Key> mShiftKeys; + public final Set<Key> mShiftLockKeys; + public final Map<Key, Drawable> mShiftedIcons; + public final Map<Key, Drawable> mUnshiftedIcons; + public final KeyboardIconsSet mIconsSet; - public final KeyboardId mId; - - public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet(); - - // Variables for pre-computing nearest keys. - - // TODO: Change GRID_WIDTH and GRID_HEIGHT to private. - public final int GRID_WIDTH; - public final int GRID_HEIGHT; + private final KeyboardShiftState mShiftState = new KeyboardShiftState(); private final ProximityInfo mProximityInfo; - /** - * Creates a keyboard from the given xml key layout file. - * @param context the application or service context - * @param xmlLayoutResId the resource file that contains the keyboard layout and keys. - * @param id keyboard identifier - * @param width keyboard width - */ + public Keyboard(KeyboardParams params) { + mId = params.mId; + mOccupiedHeight = params.mOccupiedHeight; + mOccupiedWidth = params.mOccupiedWidth; + mHeight = params.mHeight; + mWidth = params.mWidth; + mMostCommonKeyWidth = params.mMostCommonKeyWidth; + mIsRtlKeyboard = params.mIsRtlKeyboard; + mPopupKeyboardResId = params.mPopupKeyboardResId; + mMaxPopupColumn = params.mMaxPopupColumn; + + mDefaultRowHeight = params.mDefaultRowHeight; + mVerticalGap = params.mVerticalGap; + + mKeys = Collections.unmodifiableList(params.mKeys); + mShiftKeys = Collections.unmodifiableList(params.mShiftKeys); + mShiftLockKeys = Collections.unmodifiableSet(params.mShiftLockKeys); + mShiftedIcons = Collections.unmodifiableMap(params.mShiftedIcons); + mUnshiftedIcons = Collections.unmodifiableMap(params.mUnshiftedIcons); + mIconsSet = params.mIconsSet; - public Keyboard(Context context, int xmlLayoutResId, KeyboardId id, int width) { - final Resources res = context.getResources(); - GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width); - GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height); - - final int horizontalEdgesPadding = (int)res.getDimension( - R.dimen.keyboard_horizontal_edges_padding); - mDisplayWidth = width - horizontalEdgesPadding * 2; - // TODO: Adjust the height by referring to the height of area available for drawing as well. - mDisplayHeight = res.getDisplayMetrics().heightPixels; - - mDefaultHorizontalGap = 0; - setKeyWidth(mDisplayWidth / 10); - mDefaultVerticalGap = 0; - mDefaultHeight = mDefaultWidth; - mId = id; - loadKeyboard(context, xmlLayoutResId); mProximityInfo = new ProximityInfo( - GRID_WIDTH, GRID_HEIGHT, getMinWidth(), getHeight(), getKeyWidth(), mKeys); + params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight, + mMostCommonKeyWidth, mKeys); } public int getProximityInfo() { return mProximityInfo.getNativeProximityInfo(); } + // TODO: Access mKeys directly public List<Key> getKeys() { return mKeys; } - public int getHorizontalGap() { - return mDefaultHorizontalGap; - } - - public void setHorizontalGap(int gap) { - mDefaultHorizontalGap = gap; - } - - public int getVerticalGap() { - return mDefaultVerticalGap; - } - - public void setVerticalGap(int gap) { - mDefaultVerticalGap = gap; - } - - public int getRowHeight() { - return mDefaultHeight; - } - - public void setRowHeight(int height) { - mDefaultHeight = height; - } - - public int getKeyWidth() { - return mDefaultWidth; - } - - public void setKeyWidth(int width) { - mDefaultWidth = width; - } - - /** - * Returns the total height of the keyboard - * @return the total height of the keyboard - */ - public int getHeight() { - return mTotalHeight; - } - - public void setHeight(int height) { - mTotalHeight = height; - } - - public int getMinWidth() { - return mMinWidth; - } - - public void setMinWidth(int minWidth) { - mMinWidth = minWidth; - } - - public int getDisplayHeight() { - return mDisplayHeight; - } - - public int getDisplayWidth() { - return mDisplayWidth; - } - - public int getKeyboardHeight() { - return mKeyboardHeight; - } - - public void setKeyboardHeight(int height) { - mKeyboardHeight = height; - } - - public boolean isRtlKeyboard() { - return mIsRtlKeyboard; - } - - public void setRtlKeyboard(boolean isRtl) { - mIsRtlKeyboard = isRtl; - } - - public int getPopupKeyboardResId() { - return mPopupKeyboardResId; - } - - public void setPopupKeyboardResId(int resId) { - mPopupKeyboardResId = resId; - } - - public int getMaxPopupKeyboardColumn() { - return mMaxPopupColumn; - } - - public void setMaxPopupKeyboardColumn(int column) { - mMaxPopupColumn = column; - } - - public void addShiftKey(Key key) { - if (key == null) return; - mShiftKeys.add(key); - if (key.mSticky) { - mShiftLockKeys.add(key); - } - } - - public void addShiftedIcon(Key key, Drawable icon) { - if (key == null) return; - mUnshiftedIcons.put(key, key.getIcon()); - mShiftedIcons.put(key, icon); - } - public boolean hasShiftLockKey() { return !mShiftLockKeys.isEmpty(); } @@ -389,52 +248,4 @@ public class Keyboard { public int[] getNearestKeys(int x, int y) { return mProximityInfo.getNearestKeys(x, y); } - - /** - * Compute the most common key width in order to use it as proximity key detection threshold. - * - * @return The most common key width in the keyboard - */ - public int getMostCommonKeyWidth() { - if (mMostCommonKeyWidth == 0) { - final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>(); - int maxCount = 0; - int mostCommonWidth = 0; - for (final Key key : mKeys) { - final Integer width = key.mWidth + key.mGap; - Integer count = histogram.get(width); - if (count == null) - count = 0; - histogram.put(width, ++count); - if (count > maxCount) { - maxCount = count; - mostCommonWidth = width; - } - } - mMostCommonKeyWidth = mostCommonWidth; - } - return mMostCommonKeyWidth; - } - - private void loadKeyboard(Context context, int xmlLayoutResId) { - try { - KeyboardParser parser = new KeyboardParser(this, context); - parser.parseKeyboard(xmlLayoutResId); - // mMinWidth is the width of this keyboard which is maximum width of row. - mMinWidth = parser.getMaxRowWidth(); - mTotalHeight = parser.getTotalHeight(); - } catch (XmlPullParserException e) { - Log.w(TAG, "keyboard XML parse error: " + e); - throw new IllegalArgumentException(e); - } catch (IOException e) { - Log.w(TAG, "keyboard XML parse error: " + e); - throw new RuntimeException(e); - } - } - - public static void setDefaultBounds(Drawable drawable) { - if (drawable != null) - drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), - drawable.getIntrinsicHeight()); - } } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index 9299c6c58..d0a2f864c 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -42,8 +42,6 @@ public class KeyboardId { public static final int F2KEY_MODE_SHORTCUT_IME = 2; public static final int F2KEY_MODE_SHORTCUT_IME_OR_SETTINGS = 3; - private static final int MINI_KEYBOARD_ID_MARKER = -1; - public final Locale mLocale; public final int mOrientation; public final int mWidth; @@ -110,9 +108,9 @@ public class KeyboardId { }); } - public KeyboardId cloneAsMiniKeyboard() { - return new KeyboardId("mini popup keyboard", MINI_KEYBOARD_ID_MARKER, mLocale, mOrientation, - mWidth, mMode, mAttribute, false, F2KEY_MODE_NONE, false, false, false); + public KeyboardId cloneWithNewXml(String xmlName, int xmlId) { + return new KeyboardId(xmlName, xmlId, mLocale, mOrientation, mWidth, mMode, mAttribute, + false, F2KEY_MODE_NONE, false, false, false); } public KeyboardId cloneWithNewGeometry(int orientation, int width) { @@ -127,10 +125,6 @@ public class KeyboardId { return mXmlId; } - public boolean isMiniKeyboard() { - return mXmlId == MINI_KEYBOARD_ID_MARKER; - } - public boolean isAlphabetKeyboard() { return mXmlId == R.xml.kbd_qwerty; } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 17fdd0cc4..f45e81090 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -29,7 +29,6 @@ import android.view.View; import android.view.inputmethod.EditorInfo; import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; -import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.keyboard.internal.ModifierKeyState; import com.android.inputmethod.keyboard.internal.ShiftKeyState; import com.android.inputmethod.latin.LatinIME; @@ -270,14 +269,17 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha if (keyboard == null) { final Locale savedLocale = Utils.setSystemLocale( mResources, mSubtypeSwitcher.getInputLocale()); - - keyboard = new LatinKeyboard(mThemeContext, id, id.mWidth); + try { + keyboard = new LatinKeyboard.Builder(mThemeContext).load(id).build(); + } finally { + Utils.setSystemLocale(mResources, savedLocale); + } mKeyboardCache.put(id, new SoftReference<LatinKeyboard>(keyboard)); - if (DEBUG_CACHE) + + if (DEBUG_CACHE) { Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": " + ((ref == null) ? "LOAD" : "GCed") + " id=" + id); - - Utils.setSystemLocale(mResources, savedLocale); + } } else if (DEBUG_CACHE) { Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": HIT id=" + id); } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index 4086a8e77..0fb510948 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -357,7 +357,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { mDirtyRect.set(0, 0, getWidth(), getHeight()); mBufferNeedsUpdate = true; invalidateAllKeys(); - final int keyHeight = keyboard.getRowHeight() - keyboard.getVerticalGap(); + final int keyHeight = keyboard.mDefaultRowHeight - keyboard.mVerticalGap; mKeyDrawParams.updateKeyHeight(keyHeight); mKeyPreviewDrawParams.updateKeyHeight(keyHeight); } @@ -396,7 +396,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mKeyboard != null) { // The main keyboard expands to the display width. - final int height = mKeyboard.getKeyboardHeight() + getPaddingTop() + getPaddingBottom(); + final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom(); setMeasuredDimension(widthMeasureSpec, height); } else { super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -443,7 +443,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { + getPaddingLeft(); final int keyDrawY = mInvalidatedKey.mY + getPaddingTop(); canvas.translate(keyDrawX, keyDrawY); - onBufferDrawKey(mInvalidatedKey, canvas, mPaint, params, isManualTemporaryUpperCase); + onBufferDrawKey(mInvalidatedKey, mKeyboard, canvas, mPaint, params, + isManualTemporaryUpperCase); canvas.translate(-keyDrawX, -keyDrawY); } else { // Draw all keys. @@ -451,7 +452,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft(); final int keyDrawY = key.mY + getPaddingTop(); canvas.translate(keyDrawX, keyDrawY); - onBufferDrawKey(key, canvas, mPaint, params, isManualTemporaryUpperCase); + onBufferDrawKey(key, mKeyboard, canvas, mPaint, params, isManualTemporaryUpperCase); canvas.translate(-keyDrawX, -keyDrawY); } } @@ -470,8 +471,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { return false; } - private static void onBufferDrawKey(final Key key, final Canvas canvas, Paint paint, - KeyDrawParams params, boolean isManualTemporaryUpperCase) { + private static void onBufferDrawKey(final Key key, final Keyboard keyboard, final Canvas canvas, + Paint paint, KeyDrawParams params, boolean isManualTemporaryUpperCase) { final boolean debugShowAlign = LatinImeLogger.sVISUALDEBUG; // Draw key background. final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight @@ -507,7 +508,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { float positionX = centerX; if (key.mLabel != null) { // Switch the character to uppercase if shift is pressed - final CharSequence label = key.getCaseAdjustedLabel(); + final CharSequence label = keyboard.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, @@ -798,7 +799,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mPreviewTextSize); previewText.setTypeface(params.mKeyTextStyle); } - previewText.setText(key.getCaseAdjustedLabel()); + previewText.setText(mKeyboard.adjustLabelCase(key.mLabel)); } else { final Drawable previewIcon = key.getPreviewIcon(); previewText.setCompoundDrawables(null, null, null, diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java index 3c27129ec..9a13608fd 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java @@ -31,13 +31,14 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.text.TextUtils; +import com.android.inputmethod.keyboard.internal.KeyboardParams; +import com.android.inputmethod.keyboard.internal.KeyboardParser; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SubtypeSwitcher; import java.lang.ref.SoftReference; import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Locale; // TODO: We should remove this class @@ -73,32 +74,16 @@ public class LatinKeyboard extends Keyboard { private static final String SMALL_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "small"; private static final String MEDIUM_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "medium"; - public LatinKeyboard(Context context, KeyboardId id, int width) { - super(context, id.getXmlId(), id, width); + private LatinKeyboard(Context context, LatinKeyboardParams params) { + super(params); mRes = context.getResources(); mTheme = context.getTheme(); - final List<Key> keys = getKeys(); - int spaceKeyIndex = -1; - int shortcutKeyIndex = -1; - final int keyCount = keys.size(); - for (int index = 0; index < keyCount; index++) { - // For now, assuming there are up to one space key and one shortcut key respectively. - switch (keys.get(index).mCode) { - case CODE_SPACE: - spaceKeyIndex = index; - break; - case CODE_SHORTCUT: - shortcutKeyIndex = index; - break; - } - } - // The index of space key is available only after Keyboard constructor has finished. - mSpaceKey = (spaceKeyIndex >= 0) ? keys.get(spaceKeyIndex) : null; + mSpaceKey = params.mSpaceKey; mSpaceIcon = (mSpaceKey != null) ? mSpaceKey.getIcon() : null; - mShortcutKey = (shortcutKeyIndex >= 0) ? keys.get(shortcutKeyIndex) : null; + mShortcutKey = params.mShortcutKey; mEnabledShortcutIcon = (mShortcutKey != null) ? mShortcutKey.getIcon() : null; final TypedArray a = context.obtainStyledAttributes( @@ -114,6 +99,42 @@ public class LatinKeyboard extends Keyboard { a.recycle(); } + private static class LatinKeyboardParams extends KeyboardParams { + public Key mSpaceKey = null; + public Key mShortcutKey = null; + + @Override + public void onAddKey(Key key) { + super.onAddKey(key); + + switch (key.mCode) { + case Keyboard.CODE_SPACE: + mSpaceKey = key; + break; + case Keyboard.CODE_SHORTCUT: + mShortcutKey = key; + break; + } + } + } + + public static class Builder extends KeyboardParser<LatinKeyboardParams> { + public Builder(Context context) { + super(context, new LatinKeyboardParams()); + } + + @Override + public Builder load(KeyboardId id) { + super.load(id); + return this; + } + + @Override + public LatinKeyboard build() { + return new LatinKeyboard(mContext, mParams); + } + } + public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboardView view) { mSpacebarTextFadeFactor = fadeFactor; updateSpacebarForLocale(false); @@ -294,8 +315,8 @@ public class LatinKeyboard extends Keyboard { @Override public int[] getNearestKeys(int x, int y) { // Avoid dead pixels at edges of the keyboard - return super.getNearestKeys(Math.max(0, Math.min(x, getMinWidth() - 1)), - Math.max(0, Math.min(y, getHeight() - 1))); + return super.getNearestKeys(Math.max(0, Math.min(x, mOccupiedWidth - 1)), + Math.max(0, Math.min(y, mOccupiedHeight - 1))); } public static int getTextSizeFromTheme(Theme theme, int style, int defValue) { diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java index b397ca757..abf28c73c 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java @@ -289,7 +289,7 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke super.setKeyboard(keyboard); mKeyDetector.setKeyboard( keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection); - mKeyDetector.setProximityThreshold(keyboard.getMostCommonKeyWidth()); + mKeyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth); PointerTracker.setKeyDetector(mKeyDetector); mPopupPanelCache.clear(); } @@ -360,7 +360,7 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke (PopupMiniKeyboardView)container.findViewById(R.id.mini_keyboard_view); final Keyboard parentKeyboard = getKeyboard(); final Keyboard miniKeyboard = new MiniKeyboardBuilder( - this, parentKeyboard.getPopupKeyboardResId(), parentKey, parentKeyboard).build(); + this, parentKeyboard.mPopupKeyboardResId, parentKey, parentKeyboard).build(); miniKeyboardView.setKeyboard(miniKeyboard); container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java index b78fd94ea..04096778b 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java @@ -69,7 +69,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { public void setKeyboard(Keyboard newKeyboard) { super.setKeyboard(newKeyboard); // One-seventh of the keyboard width seems like a reasonable threshold - final int jumpThreshold = newKeyboard.getMinWidth() / 7; + final int jumpThreshold = newKeyboard.mOccupiedWidth / 7; mJumpThresholdSquare = jumpThreshold * jumpThreshold; } diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java index 95e32755e..7f5339de5 100644 --- a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java @@ -16,30 +16,14 @@ package com.android.inputmethod.keyboard; -import android.content.Context; +import com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder.MiniKeyboardLayoutParams; public class MiniKeyboard extends Keyboard { - private int mDefaultKeyCoordX; + private final int mDefaultKeyCoordX; - public MiniKeyboard(Context context, int xmlLayoutResId, Keyboard parentKeyboard) { - super(context, xmlLayoutResId, parentKeyboard.mId.cloneAsMiniKeyboard(), - parentKeyboard.getMinWidth()); - // HACK: Current mini keyboard design totally relies on the 9-patch padding about horizontal - // and vertical key spacing. To keep the visual of mini keyboard as is, these hacks are - // needed to keep having the same horizontal and vertical key spacing. - setHorizontalGap(0); - setVerticalGap(parentKeyboard.getVerticalGap() / 2); - - // TODO: When we have correctly padded key background 9-patch drawables for mini keyboard, - // revert the above hacks and uncomment the following lines. - //setHorizontalGap(parentKeyboard.getHorizontalGap()); - //setVerticalGap(parentKeyboard.getVerticalGap()); - - setRtlKeyboard(parentKeyboard.isRtlKeyboard()); - } - - public void setDefaultCoordX(int pos) { - mDefaultKeyCoordX = pos; + public MiniKeyboard(MiniKeyboardLayoutParams params) { + super(params); + mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2; } public int getDefaultCoordX() { diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index b25754de4..3d8a9cfc6 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -286,7 +286,7 @@ public class PointerTracker { mKeyDetector = keyDetector; mKeyboard = keyDetector.getKeyboard(); mKeys = mKeyboard.getKeys(); - final int keyQuarterWidth = mKeyboard.getKeyWidth() / 4; + final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4; mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth; } diff --git a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java index 2741ee80b..dfaaa707c 100644 --- a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java @@ -108,8 +108,8 @@ public class PopupMiniKeyboardView extends KeyboardView implements PopupPanel { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final Keyboard keyboard = getKeyboard(); if (keyboard != null) { - final int width = keyboard.getMinWidth() + getPaddingLeft() + getPaddingRight(); - final int height = keyboard.getKeyboardHeight() + getPaddingTop() + getPaddingBottom(); + final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight(); + final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom(); setMeasuredDimension(width, height); } else { super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -170,9 +170,9 @@ public class PopupMiniKeyboardView extends KeyboardView implements PopupPanel { final int miniKeyboardLeft = pointX - miniKeyboard.getDefaultCoordX() + parentKeyboardView.getPaddingLeft(); final int x = Math.max(0, Math.min(miniKeyboardLeft, - parentKeyboardView.getWidth() - miniKeyboard.getMinWidth())) + parentKeyboardView.getWidth() - miniKeyboard.mOccupiedWidth)) - container.getPaddingLeft() + mCoordinates[0]; - final int y = pointY - parentKeyboard.getVerticalGap() + final int y = pointY - parentKeyboard.mVerticalGap - (container.getMeasuredHeight() - container.getPaddingBottom()) + parentKeyboardView.getPaddingTop() + mCoordinates[1]; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java index 02c261bcd..ed4608bf6 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java @@ -21,7 +21,6 @@ import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.util.Log; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.R; public class KeyboardIconsSet { @@ -51,7 +50,7 @@ public class KeyboardIconsSet { private final Drawable mIcons[] = new Drawable[ICON_LAST + 1]; - private static final int getIconId(int attrIndex) { + private static final int getIconId(final int attrIndex) { switch (attrIndex) { case R.styleable.Keyboard_iconShiftKey: return ICON_SHIFT_KEY; @@ -86,16 +85,14 @@ public class KeyboardIconsSet { } } - public void loadIcons(TypedArray keyboardAttrs) { + public void loadIcons(final TypedArray keyboardAttrs) { final int count = keyboardAttrs.getIndexCount(); for (int i = 0; i < count; i++) { final int attrIndex = keyboardAttrs.getIndex(i); final int iconId = getIconId(attrIndex); if (iconId != ICON_UNDEFINED) { try { - final Drawable icon = keyboardAttrs.getDrawable(attrIndex); - Keyboard.setDefaultBounds(icon); - mIcons[iconId] = icon; + mIcons[iconId] = setDefaultBounds(keyboardAttrs.getDrawable(attrIndex)); } catch (Resources.NotFoundException e) { Log.w(TAG, "Drawable resource for icon #" + iconId + " not found"); } @@ -103,11 +100,18 @@ public class KeyboardIconsSet { } } - public Drawable getIcon(int iconId) { + public Drawable getIcon(final int iconId) { if (iconId == ICON_UNDEFINED) return null; if (iconId < 0 || iconId >= mIcons.length) throw new IllegalArgumentException("icon id is out of range: " + iconId); return mIcons[iconId]; } + + private static Drawable setDefaultBounds(final Drawable icon) { + if (icon != null) { + icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); + } + return icon; + } } 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..4ccaa72d2 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2011 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.drawable.Drawable; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardId; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class KeyboardParams { + public KeyboardId mId; + + public int mOccupiedHeight; + public int mOccupiedWidth; + + public int mHeight; + public int mWidth; + + public int mTopPadding; + public int mBottomPadding; + public int mHorizontalEdgesPadding; + public int mHorizontalCenterPadding; + + public int mDefaultRowHeight; + public int mDefaultKeyWidth; + public int mHorizontalGap; + public int mVerticalGap; + + public boolean mIsRtlKeyboard; + public int mPopupKeyboardResId; + public int mMaxPopupColumn; + + public int GRID_WIDTH; + public int GRID_HEIGHT; + + public final List<Key> mKeys = new ArrayList<Key>(); + public final List<Key> mShiftKeys = new ArrayList<Key>(); + public final Set<Key> mShiftLockKeys = new HashSet<Key>(); + public final Map<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>(); + public final Map<Key, Drawable> mUnshiftedIcons = new HashMap<Key, Drawable>(); + public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet(); + + public int mMostCommonKeyWidth = 0; + + public void onAddKey(Key key) { + mKeys.add(key); + updateHistogram(key); + if (key.mCode == Keyboard.CODE_SHIFT) { + mShiftKeys.add(key); + if (key.mSticky) { + mShiftLockKeys.add(key); + } + } + } + + public void addShiftedIcon(Key key, Drawable icon) { + mUnshiftedIcons.put(key, key.getIcon()); + mShiftedIcons.put(key, icon); + } + + private int mMaxCount = 0; + private final Map<Integer, Integer> mHistogram = new HashMap<Integer, Integer>(); + + private void updateHistogram(Key key) { + final Integer width = key.mWidth + key.mHorizontalGap; + final int count = (mHistogram.containsKey(width) ? mHistogram.get(width) : 0) + 1; + mHistogram.put(width, count); + if (count > mMaxCount) { + mMaxCount = count; + mMostCommonKeyWidth = width; + } + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java index f6f46750c..42e290f8c 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.util.Xml; @@ -107,7 +108,7 @@ import java.util.List; * </pre> */ -public class KeyboardParser { +public class KeyboardParser<KP extends KeyboardParams> { private static final String TAG = KeyboardParser.class.getSimpleName(); private static final boolean DEBUG = false; @@ -123,40 +124,52 @@ public class KeyboardParser { private static final String TAG_DEFAULT = "default"; public static final String TAG_KEY_STYLE = "key-style"; - private final Keyboard mKeyboard; - private final Context mContext; - private final Resources mResources; + protected final KP mParams; + protected final Context mContext; + protected final Resources mResources; + private final DisplayMetrics mDisplayMetrics; - private int mKeyboardTopPadding; - private int mKeyboardBottomPadding; - private int mHorizontalEdgesPadding; private int mCurrentX = 0; private int mCurrentY = 0; - private int mMaxRowWidth = 0; - private int mTotalHeight = 0; private Row mCurrentRow = null; private boolean mLeftEdge; private Key mRightEdgeKey = null; private final KeyStyles mKeyStyles = new KeyStyles(); - public KeyboardParser(Keyboard keyboard, Context context) { - mKeyboard = keyboard; + public KeyboardParser(Context context, KP params) { mContext = context; final Resources res = context.getResources(); mResources = res; - mHorizontalEdgesPadding = (int)res.getDimension(R.dimen.keyboard_horizontal_edges_padding); + mDisplayMetrics = res.getDisplayMetrics(); + + mParams = params; + mParams.mHorizontalEdgesPadding = (int)res.getDimension( + R.dimen.keyboard_horizontal_edges_padding); + + mParams.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width); + mParams.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height); } - public int getMaxRowWidth() { - return mMaxRowWidth; + public KeyboardParser<KP> load(KeyboardId id) { + mParams.mId = id; + try { + parseKeyboard(id.getXmlId()); + } catch (XmlPullParserException e) { + Log.w(TAG, "keyboard XML parse error: " + e); + throw new IllegalArgumentException(e); + } catch (IOException e) { + Log.w(TAG, "keyboard XML parse error: " + e); + throw new RuntimeException(e); + } + return this; } - public int getTotalHeight() { - return mTotalHeight; + public Keyboard build() { + return new Keyboard(mParams); } - public void parseKeyboard(int resId) throws XmlPullParserException, IOException { - if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_KEYBOARD, mKeyboard.mId)); + private void parseKeyboard(int resId) throws XmlPullParserException, IOException { + if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_KEYBOARD, mParams.mId)); final XmlResourceParser parser = mResources.getXml(resId); int event; while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { @@ -165,7 +178,7 @@ public class KeyboardParser { if (TAG_KEYBOARD.equals(tag)) { parseKeyboardAttributes(parser); startKeyboard(); - parseKeyboardContent(parser, mKeyboard.getKeys()); + parseKeyboardContent(parser, false); break; } else { throw new IllegalStartTag(parser, TAG_KEYBOARD); @@ -196,15 +209,14 @@ public class KeyboardParser { } private void parseKeyboardAttributes(XmlResourceParser parser) { - final Keyboard keyboard = mKeyboard; - final int displayWidth = keyboard.getDisplayWidth(); + 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 = keyboard.getDisplayHeight(); + final int displayHeight = mDisplayMetrics.heightPixels; final int keyboardHeight = (int)keyboardAttr.getDimension( R.styleable.Keyboard_keyboardHeight, displayHeight / 2); final int maxKeyboardHeight = getDimensionOrFraction(keyboardAttr, @@ -219,61 +231,65 @@ public class KeyboardParser { } // Keyboard height will not exceed maxKeyboardHeight and will not be less than // minKeyboardHeight. - final int height = Math.max( + mParams.mOccupiedHeight = Math.max( Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight); - - keyboard.setKeyboardHeight(height); - keyboard.setRtlKeyboard(keyboardAttr.getBoolean( - R.styleable.Keyboard_isRtlKeyboard, false)); - keyboard.setKeyWidth(getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_keyWidth, displayWidth, displayWidth / 10)); - keyboard.setRowHeight(getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_rowHeight, height, 50)); - keyboard.setHorizontalGap(getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_horizontalGap, displayWidth, 0)); - keyboard.setVerticalGap(getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_verticalGap, height, 0)); - keyboard.setPopupKeyboardResId(keyboardAttr.getResourceId( - R.styleable.Keyboard_popupKeyboardTemplate, 0)); - keyboard.setMaxPopupKeyboardColumn(keyAttr.getInt( - R.styleable.Keyboard_Key_maxPopupKeyboardColumn, 5)); - - mKeyboard.mIconsSet.loadIcons(keyboardAttr); - mKeyboardTopPadding = getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_keyboardTopPadding, height, 0); - mKeyboardBottomPadding = getDimensionOrFraction(keyboardAttr, - R.styleable.Keyboard_keyboardBottomPadding, height, 0); + mParams.mOccupiedWidth = mParams.mId.mWidth; + mParams.mTopPadding = getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_keyboardTopPadding, mParams.mOccupiedHeight, 0); + mParams.mBottomPadding = getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_keyboardBottomPadding, mParams.mOccupiedHeight, 0); + + final int height = mParams.mOccupiedHeight; + final int width = mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding * 2 + - mParams.mHorizontalCenterPadding; + mParams.mHeight = height; + mParams.mWidth = width; + mParams.mDefaultKeyWidth = getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_keyWidth, width, width / 10); + mParams.mDefaultRowHeight = getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_rowHeight, height, height / 4); + mParams.mHorizontalGap = getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_horizontalGap, width, 0); + mParams.mVerticalGap = getDimensionOrFraction(keyboardAttr, + R.styleable.Keyboard_verticalGap, height, 0); + + mParams.mIsRtlKeyboard = keyboardAttr.getBoolean(R.styleable.Keyboard_isRtlKeyboard, false); + mParams.mPopupKeyboardResId = keyboardAttr.getResourceId( + R.styleable.Keyboard_popupKeyboardTemplate, 0); + mParams.mMaxPopupColumn = keyAttr.getInt(R.styleable.Keyboard_Key_maxPopupKeyboardColumn, 5); + + mParams.mIconsSet.loadIcons(keyboardAttr); } finally { keyAttr.recycle(); keyboardAttr.recycle(); } } - private void parseKeyboardContent(XmlResourceParser parser, List<Key> keys) + private void parseKeyboardContent(XmlResourceParser 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 = new Row(mResources, mKeyboard, parser); + Row row = parseRowAttributes(parser); if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_ROW)); - if (keys != null) + if (!skip) startRow(row); - parseRowContent(parser, row, keys); + parseRowContent(parser, row, skip); } else if (TAG_INCLUDE.equals(tag)) { - parseIncludeKeyboardContent(parser, keys); + parseIncludeKeyboardContent(parser, skip); } else if (TAG_SWITCH.equals(tag)) { - parseSwitchKeyboardContent(parser, keys); + parseSwitchKeyboardContent(parser, skip); } else if (TAG_KEY_STYLE.equals(tag)) { - parseKeyStyle(parser, keys); + parseKeyStyle(parser, skip); } else { throw new IllegalStartTag(parser, TAG_ROW); } } else if (event == XmlPullParser.END_TAG) { final String tag = parser.getName(); if (TAG_KEYBOARD.equals(tag)) { - endKeyboard(mKeyboard.getVerticalGap()); + endKeyboard(); break; } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) || TAG_MERGE.equals(tag)) { @@ -288,22 +304,36 @@ public class KeyboardParser { } } - private void parseRowContent(XmlResourceParser parser, Row row, List<Key> keys) + private Row parseRowAttributes(XmlResourceParser parser) { + final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), + R.styleable.Keyboard); + try { + if (a.hasValue(R.styleable.Keyboard_horizontalGap)) + throw new IllegalAttribute(parser, "horizontalGap"); + if (a.hasValue(R.styleable.Keyboard_verticalGap)) + throw new IllegalAttribute(parser, "verticalGap"); + return new Row(mResources, mParams, parser); + } finally { + a.recycle(); + } + } + + private void parseRowContent(XmlResourceParser 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, keys); + parseKey(parser, row, skip); } else if (TAG_SPACER.equals(tag)) { - parseSpacer(parser, row, keys); + parseSpacer(parser, row, skip); } else if (TAG_INCLUDE.equals(tag)) { - parseIncludeRowContent(parser, row, keys); + parseIncludeRowContent(parser, row, skip); } else if (TAG_SWITCH.equals(tag)) { - parseSwitchRowContent(parser, row, keys); + parseSwitchRowContent(parser, row, skip); } else if (TAG_KEY_STYLE.equals(tag)) { - parseKeyStyle(parser, keys); + parseKeyStyle(parser, skip); } else { throw new IllegalStartTag(parser, TAG_KEY); } @@ -311,7 +341,7 @@ public class KeyboardParser { final String tag = parser.getName(); if (TAG_ROW.equals(tag)) { if (DEBUG) Log.d(TAG, String.format("</%s>", TAG_ROW)); - if (keys != null) + if (!skip) endRow(); break; } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag) @@ -327,24 +357,24 @@ public class KeyboardParser { } } - private void parseKey(XmlResourceParser parser, Row row, List<Key> keys) + private void parseKey(XmlResourceParser parser, Row row, boolean skip) throws XmlPullParserException, IOException { - if (keys == null) { + if (skip) { checkEndTag(TAG_KEY, parser); } else { - Key key = new Key(mResources, row, mCurrentX, mCurrentY, parser, mKeyStyles); + Key key = new Key(mResources, mParams, row, mCurrentX, mCurrentY, parser, mKeyStyles); if (DEBUG) Log.d(TAG, String.format("<%s%s keyLabel=%s code=%d popupCharacters=%s />", TAG_KEY, (key.isEnabled() ? "" : " disabled"), key.mLabel, key.mCode, Arrays.toString(key.mPopupCharacters))); checkEndTag(TAG_KEY, parser); - keys.add(key); + mParams.onAddKey(key); endKey(key); } } - private void parseSpacer(XmlResourceParser parser, Row row, List<Key> keys) + private void parseSpacer(XmlResourceParser parser, Row row, boolean skip) throws XmlPullParserException, IOException { - if (keys == null) { + if (skip) { checkEndTag(TAG_SPACER, parser); } else { if (DEBUG) Log.d(TAG, String.format("<%s />", TAG_SPACER)); @@ -352,9 +382,9 @@ public class KeyboardParser { R.styleable.Keyboard); if (keyboardAttr.hasValue(R.styleable.Keyboard_horizontalGap)) throw new IllegalAttribute(parser, "horizontalGap"); - final int keyboardWidth = mKeyboard.getDisplayWidth(); + final int keyboardWidth = mParams.mWidth; final int keyWidth = getDimensionOrFraction(keyboardAttr, R.styleable.Keyboard_keyWidth, - keyboardWidth, row.mDefaultWidth); + keyboardWidth, row.mDefaultKeyWidth); keyboardAttr.recycle(); final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), @@ -371,19 +401,19 @@ public class KeyboardParser { } } - private void parseIncludeKeyboardContent(XmlResourceParser parser, List<Key> keys) + private void parseIncludeKeyboardContent(XmlResourceParser parser, boolean skip) throws XmlPullParserException, IOException { - parseIncludeInternal(parser, null, keys); + parseIncludeInternal(parser, null, skip); } - private void parseIncludeRowContent(XmlResourceParser parser, Row row, List<Key> keys) + private void parseIncludeRowContent(XmlResourceParser parser, Row row, boolean skip) throws XmlPullParserException, IOException { - parseIncludeInternal(parser, row, keys); + parseIncludeInternal(parser, row, skip); } - private void parseIncludeInternal(XmlResourceParser parser, Row row, List<Key> keys) + private void parseIncludeInternal(XmlResourceParser parser, Row row, boolean skip) throws XmlPullParserException, IOException { - if (keys == null) { + if (skip) { checkEndTag(TAG_INCLUDE, parser); } else { final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), @@ -397,11 +427,11 @@ public class KeyboardParser { throw new ParseException("No keyboardLayout attribute in <include/>", parser); if (DEBUG) Log.d(TAG, String.format("<%s keyboardLayout=%s />", TAG_INCLUDE, mResources.getResourceEntryName(keyboardLayout))); - parseMerge(mResources.getLayout(keyboardLayout), row, keys); + parseMerge(mResources.getLayout(keyboardLayout), row, skip); } } - private void parseMerge(XmlResourceParser parser, Row row, List<Key> keys) + private void parseMerge(XmlResourceParser parser, Row row, boolean skip) throws XmlPullParserException, IOException { int event; while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { @@ -409,9 +439,9 @@ public class KeyboardParser { final String tag = parser.getName(); if (TAG_MERGE.equals(tag)) { if (row == null) { - parseKeyboardContent(parser, keys); + parseKeyboardContent(parser, skip); } else { - parseRowContent(parser, row, keys); + parseRowContent(parser, row, skip); } break; } else { @@ -422,28 +452,28 @@ public class KeyboardParser { } } - private void parseSwitchKeyboardContent(XmlResourceParser parser, List<Key> keys) + private void parseSwitchKeyboardContent(XmlResourceParser parser, boolean skip) throws XmlPullParserException, IOException { - parseSwitchInternal(parser, null, keys); + parseSwitchInternal(parser, null, skip); } - private void parseSwitchRowContent(XmlResourceParser parser, Row row, List<Key> keys) + private void parseSwitchRowContent(XmlResourceParser parser, Row row, boolean skip) throws XmlPullParserException, IOException { - parseSwitchInternal(parser, row, keys); + parseSwitchInternal(parser, row, skip); } - private void parseSwitchInternal(XmlResourceParser parser, Row row, List<Key> keys) + private void parseSwitchInternal(XmlResourceParser parser, Row row, boolean skip) throws XmlPullParserException, IOException { - if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_SWITCH, mKeyboard.mId)); + if (DEBUG) Log.d(TAG, String.format("<%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 ? null : keys); + selected |= parseCase(parser, row, selected ? true : skip); } else if (TAG_DEFAULT.equals(tag)) { - selected |= parseDefault(parser, row, selected ? null : keys); + selected |= parseDefault(parser, row, selected ? true : skip); } else { throw new IllegalStartTag(parser, TAG_KEY); } @@ -459,21 +489,21 @@ public class KeyboardParser { } } - private boolean parseCase(XmlResourceParser parser, Row row, List<Key> keys) + private boolean parseCase(XmlResourceParser parser, Row row, boolean skip) throws XmlPullParserException, IOException { final boolean selected = parseCaseCondition(parser); if (row == null) { // Processing Rows. - parseKeyboardContent(parser, selected ? keys : null); + parseKeyboardContent(parser, selected ? skip : true); } else { // Processing Keys. - parseRowContent(parser, row, selected ? keys : null); + parseRowContent(parser, row, selected ? skip : true); } return selected; } private boolean parseCaseCondition(XmlResourceParser parser) { - final KeyboardId id = mKeyboard.mId; + final KeyboardId id = mParams.mId; if (id == null) return true; @@ -582,18 +612,18 @@ public class KeyboardParser { return false; } - private boolean parseDefault(XmlResourceParser parser, Row row, List<Key> keys) + private boolean parseDefault(XmlResourceParser parser, Row row, boolean skip) throws XmlPullParserException, IOException { if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_DEFAULT)); if (row == null) { - parseKeyboardContent(parser, keys); + parseKeyboardContent(parser, skip); } else { - parseRowContent(parser, row, keys); + parseRowContent(parser, row, skip); } return true; } - private void parseKeyStyle(XmlResourceParser parser, List<Key> keys) { + private void parseKeyStyle(XmlResourceParser parser, boolean skip) { TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard_KeyStyle); TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser), @@ -602,7 +632,7 @@ public class KeyboardParser { if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) throw new ParseException("<" + TAG_KEY_STYLE + "/> needs styleName attribute", parser); - if (keys != null) + if (!skip) mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser); } finally { keyStyleAttr.recycle(); @@ -618,12 +648,12 @@ public class KeyboardParser { } private void startKeyboard() { - mCurrentY += mKeyboardTopPadding; + mCurrentY += mParams.mTopPadding; } private void startRow(Row row) { mCurrentX = 0; - setSpacer(mCurrentX, mHorizontalEdgesPadding); + setSpacer(mCurrentX, mParams.mHorizontalEdgesPadding); mCurrentRow = row; mLeftEdge = true; mRightEdgeKey = null; @@ -636,15 +666,13 @@ public class KeyboardParser { mRightEdgeKey.addEdgeFlags(Keyboard.EDGE_RIGHT); mRightEdgeKey = null; } - setSpacer(mCurrentX, mHorizontalEdgesPadding); - if (mCurrentX > mMaxRowWidth) - mMaxRowWidth = mCurrentX; - mCurrentY += mCurrentRow.mDefaultHeight; + setSpacer(mCurrentX, mParams.mHorizontalEdgesPadding); + mCurrentY += mCurrentRow.mRowHeight; mCurrentRow = null; } private void endKey(Key key) { - mCurrentX = key.mX - key.mGap / 2 + key.mWidth + key.mGap; + mCurrentX = key.mX - key.mHorizontalGap / 2 + key.mWidth + key.mHorizontalGap; if (mLeftEdge) { key.addEdgeFlags(Keyboard.EDGE_LEFT); mLeftEdge = false; @@ -652,9 +680,7 @@ public class KeyboardParser { mRightEdgeKey = key; } - private void endKeyboard(int defaultVerticalGap) { - mCurrentY += mKeyboardBottomPadding; - mTotalHeight = mCurrentY - defaultVerticalGap; + private void endKeyboard() { } private void setSpacer(int keyXPos, int width) { diff --git a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java index 965c679ea..bad7a1772 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java +++ b/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java @@ -16,8 +16,6 @@ package com.android.inputmethod.keyboard.internal; -import android.content.Context; -import android.content.res.Resources; import android.graphics.Paint; import android.graphics.Rect; @@ -27,26 +25,30 @@ import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.MiniKeyboard; import com.android.inputmethod.latin.R; -import java.util.List; - -public class MiniKeyboardBuilder { - private final Resources mRes; - private final MiniKeyboard mKeyboard; +public class MiniKeyboardBuilder extends + KeyboardParser<MiniKeyboardBuilder.MiniKeyboardLayoutParams> { private final CharSequence[] mPopupCharacters; - private final MiniKeyboardLayoutParams mParams; - /* package */ static class MiniKeyboardLayoutParams { - public final int mKeyWidth; - public final int mRowHeight; - /* package */ final int mTopRowAdjustment; - public final int mNumRows; - public final int mNumColumns; - public final int mLeftKeys; - public final int mRightKeys; // includes default key. - public int mTopPadding; + public static class MiniKeyboardLayoutParams extends KeyboardParams { + /* package */ int mTopRowAdjustment; + public int mNumRows; + public int mNumColumns; + public int mLeftKeys; + public int mRightKeys; // includes default key. + + public MiniKeyboardLayoutParams() { + super(); + } + + /* package for test */ MiniKeyboardLayoutParams(int numKeys, int maxColumns, int keyWidth, + int rowHeight, int coordXInParent, int parentKeyboardWidth) { + super(); + setParameters( + numKeys, maxColumns, keyWidth, rowHeight, coordXInParent, parentKeyboardWidth); + } /** - * The object holding mini keyboard layout parameters. + * Set keyboard parameters of mini keyboard. * * @param numKeys number of keys in this mini keyboard. * @param maxColumns number of maximum columns of this mini keyboard. @@ -54,15 +56,15 @@ public class MiniKeyboardBuilder { * @param rowHeight mini keyboard row height in pixel, including vertical gap. * @param coordXInParent coordinate x of the popup key in parent keyboard. * @param parentKeyboardWidth parent keyboard width in pixel. - * parent keyboard. */ - public MiniKeyboardLayoutParams(int numKeys, int maxColumns, int keyWidth, int rowHeight, + public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight, int coordXInParent, int parentKeyboardWidth) { - if (parentKeyboardWidth / keyWidth < maxColumns) + if (parentKeyboardWidth / keyWidth < maxColumns) { throw new IllegalArgumentException("Keyboard is too small to hold mini keyboard: " + parentKeyboardWidth + " " + keyWidth + " " + maxColumns); - mKeyWidth = keyWidth; - mRowHeight = rowHeight; + } + mDefaultKeyWidth = keyWidth; + mDefaultRowHeight = rowHeight; final int numRows = (numKeys + maxColumns - 1) / maxColumns; mNumRows = numRows; @@ -108,6 +110,9 @@ public class MiniKeyboardBuilder { } else { mTopRowAdjustment = -1; } + + mWidth = mOccupiedWidth = mNumColumns * mDefaultKeyWidth; + mHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap; } // Return key position according to column count (0 is default). @@ -160,19 +165,19 @@ public class MiniKeyboardBuilder { } public int getDefaultKeyCoordX() { - return mLeftKeys * mKeyWidth; + return mLeftKeys * mDefaultKeyWidth; } public int getX(int n, int row) { - final int x = getColumnPos(n) * mKeyWidth + getDefaultKeyCoordX(); + final int x = getColumnPos(n) * mDefaultKeyWidth + getDefaultKeyCoordX(); if (isTopRow(row)) { - return x + mTopRowAdjustment * (mKeyWidth / 2); + return x + mTopRowAdjustment * (mDefaultKeyWidth / 2); } return x; } public int getY(int row) { - return (mNumRows - 1 - row) * mRowHeight + mTopPadding; + return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; } public int getRowFlags(int row) { @@ -185,42 +190,32 @@ public class MiniKeyboardBuilder { private boolean isTopRow(int rowCount) { return rowCount == mNumRows - 1; } - - public void setTopPadding (int topPadding) { - mTopPadding = topPadding; - } - - public int getKeyboardHeight() { - return mNumRows * mRowHeight + mTopPadding; - } - - public int getKeyboardWidth() { - return mNumColumns * mKeyWidth; - } } - public MiniKeyboardBuilder(KeyboardView view, int layoutTemplateResId, Key parentKey, + public MiniKeyboardBuilder(KeyboardView view, int xmlId, Key parentKey, Keyboard parentKeyboard) { - final Context context = view.getContext(); - mRes = context.getResources(); - final MiniKeyboard keyboard = new MiniKeyboard( - context, layoutTemplateResId, parentKeyboard); - mKeyboard = keyboard; + super(view.getContext(), new MiniKeyboardLayoutParams()); + load(parentKeyboard.mId.cloneWithNewXml(mResources.getResourceEntryName(xmlId), xmlId)); + + // HACK: Current mini keyboard design totally relies on the 9-patch padding about horizontal + // and vertical key spacing. To keep the visual of mini keyboard as is, these hacks are + // needed to keep having the same horizontal and vertical key spacing. + mParams.mHorizontalGap = 0; + mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2; + // TODO: When we have correctly padded key background 9-patch drawables for mini keyboard, + // revert the above hacks and uncomment the following lines. + //mParams.mHorizontalGap = parentKeyboard.mHorizontalGap; + //mParams.mVerticalGap = parentKeyboard.mVerticalGap; + + mParams.mIsRtlKeyboard = parentKeyboard.mIsRtlKeyboard; mPopupCharacters = parentKey.mPopupCharacters; - final int keyWidth = getMaxKeyWidth(view, mPopupCharacters, keyboard.getKeyWidth()); - final MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams( + final int keyWidth = getMaxKeyWidth(view, mPopupCharacters, mParams.mDefaultKeyWidth); + mParams.setParameters( mPopupCharacters.length, parentKey.mMaxPopupColumn, - keyWidth, parentKeyboard.getRowHeight(), - parentKey.mX + (parentKey.mWidth + parentKey.mGap) / 2 - keyWidth / 2, + keyWidth, parentKeyboard.mDefaultRowHeight, + parentKey.mX + (mParams.mDefaultKeyWidth - keyWidth) / 2, view.getMeasuredWidth()); - params.setTopPadding(keyboard.getVerticalGap()); - mParams = params; - - keyboard.setRowHeight(params.mRowHeight); - keyboard.setKeyboardHeight(params.getKeyboardHeight()); - keyboard.setMinWidth(params.getKeyboardWidth()); - keyboard.setDefaultCoordX(params.getDefaultKeyCoordX() + params.mKeyWidth / 2); } private static int getMaxKeyWidth(KeyboardView view, CharSequence[] popupCharacters, @@ -249,17 +244,16 @@ public class MiniKeyboardBuilder { return Math.max(minKeyWidth, maxWidth + horizontalPadding); } + @Override public MiniKeyboard build() { - final MiniKeyboard keyboard = mKeyboard; - final List<Key> keys = keyboard.getKeys(); final MiniKeyboardLayoutParams params = mParams; for (int n = 0; n < mPopupCharacters.length; n++) { final CharSequence label = mPopupCharacters[n]; final int row = n / params.mNumColumns; - final Key key = new Key(mRes, keyboard, label, params.getX(n, row), params.getY(row), - params.mKeyWidth, params.mRowHeight, params.getRowFlags(row)); - keys.add(key); + final Key key = new Key(mResources, params, label, params.getX(n, row), params.getY(row), + params.mDefaultKeyWidth, params.mDefaultRowHeight, params.getRowFlags(row)); + params.onAddKey(key); } - return keyboard; + return new MiniKeyboard(params); } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/Row.java b/java/src/com/android/inputmethod/keyboard/internal/Row.java index b34d6d06f..9299cc261 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/Row.java +++ b/java/src/com/android/inputmethod/keyboard/internal/Row.java @@ -31,34 +31,19 @@ import com.android.inputmethod.latin.R; */ public class Row { /** Default width of a key in this row. */ - public final int mDefaultWidth; + public final int mDefaultKeyWidth; /** Default height of a key in this row. */ - public final int mDefaultHeight; - /** Default horizontal gap between keys in this row. */ - public final int mDefaultHorizontalGap; - /** Vertical gap following this row. */ - public final int mVerticalGap; + public final int mRowHeight; - private final Keyboard mKeyboard; - - public Row(Resources res, Keyboard keyboard, XmlResourceParser parser) { - this.mKeyboard = keyboard; - final int keyboardWidth = keyboard.getDisplayWidth(); - final int keyboardHeight = keyboard.getKeyboardHeight(); + public Row(Resources res, KeyboardParams params, XmlResourceParser parser) { + final int keyboardWidth = params.mWidth; + final int keyboardHeight = params.mHeight; TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.Keyboard); - mDefaultWidth = KeyboardParser.getDimensionOrFraction(a, - R.styleable.Keyboard_keyWidth, keyboardWidth, keyboard.getKeyWidth()); - mDefaultHeight = KeyboardParser.getDimensionOrFraction(a, - R.styleable.Keyboard_rowHeight, keyboardHeight, keyboard.getRowHeight()); - mDefaultHorizontalGap = KeyboardParser.getDimensionOrFraction(a, - R.styleable.Keyboard_horizontalGap, keyboardWidth, keyboard.getHorizontalGap()); - mVerticalGap = KeyboardParser.getDimensionOrFraction(a, - R.styleable.Keyboard_verticalGap, keyboardHeight, keyboard.getVerticalGap()); + mDefaultKeyWidth = KeyboardParser.getDimensionOrFraction(a, + R.styleable.Keyboard_keyWidth, keyboardWidth, params.mDefaultKeyWidth); + mRowHeight = KeyboardParser.getDimensionOrFraction(a, + R.styleable.Keyboard_rowHeight, keyboardHeight, params.mDefaultRowHeight); a.recycle(); } - - public Keyboard getKeyboard() { - return mKeyboard; - } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index bdcbfbcb1..b2af6f9ee 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1685,7 +1685,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar final int rawPrimaryCode = suggestion.charAt(0); // Maybe apply the "bidi mirrored" conversions for parentheses final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard(); - final int primaryCode = keyboard.isRtlKeyboard() + final int primaryCode = keyboard.mIsRtlKeyboard ? Key.getRtlParenthesisCode(rawPrimaryCode) : rawPrimaryCode; final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : ""; diff --git a/native/src/bigram_dictionary.cpp b/native/src/bigram_dictionary.cpp index 6ed4d0982..c340c6c1a 100644 --- a/native/src/bigram_dictionary.cpp +++ b/native/src/bigram_dictionary.cpp @@ -21,13 +21,14 @@ #include "bigram_dictionary.h" #include "dictionary.h" +#include "binary_format.h" namespace latinime { BigramDictionary::BigramDictionary(const unsigned char *dict, int maxWordLength, int maxAlternatives, const bool isLatestDictVersion, const bool hasBigram, Dictionary *parentDictionary) - : DICT(dict), MAX_WORD_LENGTH(maxWordLength), + : DICT(dict + NEW_DICTIONARY_HEADER_SIZE), MAX_WORD_LENGTH(maxWordLength), MAX_ALTERNATIVES(maxAlternatives), IS_LATEST_DICT_VERSION(isLatestDictVersion), HAS_BIGRAM(hasBigram), mParentDictionary(parentDictionary) { if (DEBUG_DICT) { @@ -82,169 +83,64 @@ bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequ return false; } -int BigramDictionary::getBigramAddress(int *pos, bool advance) { - int address = 0; - - address += (DICT[*pos] & 0x3F) << 16; - address += (DICT[*pos + 1] & 0xFF) << 8; - address += (DICT[*pos + 2] & 0xFF); - - if (advance) { - *pos += 3; - } - - return address; -} - -int BigramDictionary::getBigramFreq(int *pos) { - int freq = DICT[(*pos)++] & FLAG_BIGRAM_FREQ; - - return freq; -} - - +/* Parameters : + * prevWord: the word before, the one for which we need to look up bigrams. + * prevWordLength: its length. + * codes: what user typed, in the same format as for UnigramDictionary::getSuggestions. + * codesSize: the size of the codes array. + * bigramChars: an array for output, at the same format as outwords for getSuggestions. + * bigramFreq: an array to output frequencies. + * maxWordLength: the maximum size of a word. + * maxBigrams: the maximum number of bigrams fitting in the bigramChars array. + * maxAlteratives: unused. + * This method returns the number of bigrams this word has, for backward compatibility. + * Note: this is not the number of bigrams output in the array, which is the number of + * bigrams this word has WHOSE first letter also matches the letter the user typed. + * TODO: this may not be a sensible thing to do. It makes sense when the bigrams are + * used to match the first letter of the second word, but once the user has typed more + * and the bigrams are used to boost unigram result scores, it makes little sense to + * reduce their scope to the ones that match the first letter. + */ int BigramDictionary::getBigrams(unsigned short *prevWord, int prevWordLength, int *codes, int codesSize, unsigned short *bigramChars, int *bigramFreq, int maxWordLength, int maxBigrams, int maxAlternatives) { + // TODO: remove unused arguments, and refrain from storing stuff in members of this class + // TODO: have "in" arguments before "out" ones, and make out args explicit in the name mBigramFreq = bigramFreq; mBigramChars = bigramChars; mInputCodes = codes; - mInputLength = codesSize; mMaxBigrams = maxBigrams; - if (HAS_BIGRAM && IS_LATEST_DICT_VERSION) { - int pos = mParentDictionary->getBigramPosition(prevWord, prevWordLength); - if (DEBUG_DICT) { - LOGI("Pos -> %d", pos); - } - if (pos < 0) { - return 0; - } - - int bigramCount = 0; - int bigramExist = (DICT[pos] & FLAG_BIGRAM_READ); - if (bigramExist > 0) { - int nextBigramExist = 1; - while (nextBigramExist > 0 && bigramCount < maxBigrams) { - int bigramAddress = getBigramAddress(&pos, true); - int frequency = (FLAG_BIGRAM_FREQ & DICT[pos]); - // search for all bigrams and store them - searchForTerminalNode(bigramAddress, frequency); - nextBigramExist = (DICT[pos++] & FLAG_BIGRAM_CONTINUED); - bigramCount++; - } - } + const uint8_t* const root = DICT; + int pos = BinaryFormat::getTerminalPosition(root, prevWord, prevWordLength); - return bigramCount; + if (NOT_VALID_WORD == pos) return 0; + const int flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos); + if (0 == (flags & UnigramDictionary::FLAG_HAS_BIGRAMS)) return 0; + if (0 == (flags & UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS)) { + BinaryFormat::getCharCodeAndForwardPointer(root, &pos); + } else { + pos = BinaryFormat::skipOtherCharacters(root, pos); } - return 0; -} - -void BigramDictionary::searchForTerminalNode(int addressLookingFor, int frequency) { - // track word with such address and store it in an array - unsigned short word[MAX_WORD_LENGTH]; - - int pos; - int followDownBranchAddress = DICTIONARY_HEADER_SIZE; - bool found = false; - char followingChar = ' '; - int depth = -1; - - while(!found) { - bool followDownAddressSearchStop = false; - bool firstAddress = true; - bool haveToSearchAll = true; - - if (depth < MAX_WORD_LENGTH && depth >= 0) { - word[depth] = (unsigned short) followingChar; - } - pos = followDownBranchAddress; // pos start at count - int count = DICT[pos] & 0xFF; - if (DEBUG_DICT) { - LOGI("count - %d",count); - } - pos++; - for (int i = 0; i < count; i++) { - // pos at data - pos++; - // pos now at flag - if (!getFirstBitOfByte(&pos)) { // non-terminal - if (!followDownAddressSearchStop) { - int addr = getBigramAddress(&pos, false); - if (addr > addressLookingFor) { - followDownAddressSearchStop = true; - if (firstAddress) { - firstAddress = false; - haveToSearchAll = true; - } else if (!haveToSearchAll) { - break; - } - } else { - followDownBranchAddress = addr; - followingChar = (char)(0xFF & DICT[pos-1]); - if (firstAddress) { - firstAddress = false; - haveToSearchAll = false; - } - } - } - pos += 3; - } else if (getFirstBitOfByte(&pos)) { // terminal - if (addressLookingFor == (pos-1)) { // found !! - depth++; - word[depth] = (0xFF & DICT[pos-1]); - found = true; - break; - } - if (getSecondBitOfByte(&pos)) { // address + freq (4 byte) - if (!followDownAddressSearchStop) { - int addr = getBigramAddress(&pos, false); - if (addr > addressLookingFor) { - followDownAddressSearchStop = true; - if (firstAddress) { - firstAddress = false; - haveToSearchAll = true; - } else if (!haveToSearchAll) { - break; - } - } else { - followDownBranchAddress = addr; - followingChar = (char)(0xFF & DICT[pos-1]); - if (firstAddress) { - firstAddress = false; - haveToSearchAll = true; - } - } - } - pos += 4; - } else { // freq only (2 byte) - pos += 2; - } - - // skipping bigram - int bigramExist = (DICT[pos] & FLAG_BIGRAM_READ); - if (bigramExist > 0) { - int nextBigramExist = 1; - while (nextBigramExist > 0) { - pos += 3; - nextBigramExist = (DICT[pos++] & FLAG_BIGRAM_CONTINUED); - } - } else { - pos++; - } - } + pos = BinaryFormat::skipChildrenPosition(flags, pos); + pos = BinaryFormat::skipFrequency(flags, pos); + int bigramFlags; + int bigramCount = 0; + do { + bigramFlags = BinaryFormat::getFlagsAndForwardPointer(root, &pos); + uint16_t bigramBuffer[MAX_WORD_LENGTH]; + const int bigramPos = BinaryFormat::getAttributeAddressAndForwardPointer(root, bigramFlags, + &pos); + const int length = BinaryFormat::getWordAtAddress(root, bigramPos, MAX_WORD_LENGTH, + bigramBuffer); + + if (checkFirstCharacter(bigramBuffer)) { + const int frequency = UnigramDictionary::MASK_ATTRIBUTE_FREQUENCY & bigramFlags; + addWordBigram(bigramBuffer, length, frequency); } - depth++; - if (followDownBranchAddress == 0) { - if (DEBUG_DICT) { - LOGI("ERROR!!! Cannot find bigram!!"); - } - break; - } - } - if (checkFirstCharacter(word)) { - addWordBigram(word, depth, frequency); - } + ++bigramCount; + } while (0 != (UnigramDictionary::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags)); + return bigramCount; } bool BigramDictionary::checkFirstCharacter(unsigned short *word) { diff --git a/native/src/binary_format.h b/native/src/binary_format.h index a946b1ee3..6f65088db 100644 --- a/native/src/binary_format.h +++ b/native/src/binary_format.h @@ -50,6 +50,8 @@ public: int *pos); static int getTerminalPosition(const uint8_t* const root, const uint16_t* const inWord, const int length); + static int getWordAtAddress(const uint8_t* const root, const int address, const int maxDepth, + uint16_t* outWord); }; inline int BinaryFormat::detectFormat(const uint8_t* const dict) { @@ -290,6 +292,151 @@ inline int BinaryFormat::getTerminalPosition(const uint8_t* const root, } } +// This function searches for a terminal in the dictionary by its address. +// Due to the fact that words are ordered in the dictionary in a strict breadth-first order, +// it is possible to check for this with advantageous complexity. For each node, we search +// for groups with children and compare the children address with the address we look for. +// When we shoot the address we look for, it means the word we look for is in the children +// of the previous group. The only tricky part is the fact that if we arrive at the end of a +// node with the last group's children address still less than what we are searching for, we +// must descend the last group's children (for example, if the word we are searching for starts +// with a z, it's the last group of the root node, so all children addresses will be smaller +// than the address we look for, and we have to descend the z node). +/* Parameters : + * root: the dictionary buffer + * address: the byte position of the last chargroup of the word we are searching for (this is + * what is stored as the "bigram address" in each bigram) + * outword: an array to write the found word, with MAX_WORD_LENGTH size. + * Return value : the length of the word, of 0 if the word was not found. + */ +inline int BinaryFormat::getWordAtAddress(const uint8_t* const root, const int address, + const int maxDepth, uint16_t* outWord) { + int pos = 0; + int wordPos = 0; + + // One iteration of the outer loop iterates through nodes. As stated above, we will only + // traverse nodes that are actually a part of the terminal we are searching, so each time + // we enter this loop we are one depth level further than last time. + // The only reason we count nodes is because we want to reduce the probability of infinite + // looping in case there is a bug. Since we know there is an upper bound to the depth we are + // supposed to traverse, it does not hurt to count iterations. + for (int loopCount = maxDepth; loopCount > 0; --loopCount) { + int lastCandidateGroupPos = 0; + // Let's loop through char groups in this node searching for either the terminal + // or one of its ascendants. + for (int charGroupCount = getGroupCountAndForwardPointer(root, &pos); charGroupCount > 0; + --charGroupCount) { + const int startPos = pos; + const uint8_t flags = getFlagsAndForwardPointer(root, &pos); + const int32_t character = getCharCodeAndForwardPointer(root, &pos); + if (address == startPos) { + // We found the address. Copy the rest of the word in the buffer and return + // the length. + outWord[wordPos] = character; + if (UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS & flags) { + int32_t nextChar = getCharCodeAndForwardPointer(root, &pos); + // We count chars in order to avoid infinite loops if the file is broken or + // if there is some other bug + int charCount = maxDepth; + while (-1 != nextChar && --charCount > 0) { + outWord[++wordPos] = nextChar; + nextChar = getCharCodeAndForwardPointer(root, &pos); + } + } + return ++wordPos; + } + // We need to skip past this char group, so skip any remaining chars after the + // first and possibly the frequency. + if (UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS & flags) { + pos = skipOtherCharacters(root, pos); + } + pos = skipFrequency(flags, pos); + + // The fact that this group has children is very important. Since we already know + // that this group does not match, if it has no children we know it is irrelevant + // to what we are searching for. + const bool hasChildren = (UnigramDictionary::FLAG_GROUP_ADDRESS_TYPE_NOADDRESS != + (UnigramDictionary::MASK_GROUP_ADDRESS_TYPE & flags)); + // We will write in `found' whether we have passed the children address we are + // searching for. For example if we search for "beer", the children of b are less + // than the address we are searching for and the children of c are greater. When we + // come here for c, we realize this is too big, and that we should descend b. + bool found; + if (hasChildren) { + // Here comes the tricky part. First, read the children position. + const int childrenPos = readChildrenPosition(root, flags, pos); + if (childrenPos > address) { + // If the children pos is greater than address, it means the previous chargroup, + // which address is stored in lastCandidateGroupPos, was the right one. + found = true; + } else if (1 >= charGroupCount) { + // However if we are on the LAST group of this node, and we have NOT shot the + // address we should descend THIS node. So we trick the lastCandidateGroupPos + // so that we will descend this node, not the previous one. + lastCandidateGroupPos = startPos; + found = true; + } else { + // Else, we should continue looking. + found = false; + } + } else { + // Even if we don't have children here, we could still be on the last group of this + // node. If this is the case, we should descend the last group that had children, + // and their address is already in lastCandidateGroup. + found = (1 >= charGroupCount); + } + + if (found) { + // Okay, we found the group we should descend. Its address is in + // the lastCandidateGroupPos variable, so we just re-read it. + if (0 != lastCandidateGroupPos) { + const uint8_t lastFlags = + getFlagsAndForwardPointer(root, &lastCandidateGroupPos); + const int32_t lastChar = + getCharCodeAndForwardPointer(root, &lastCandidateGroupPos); + // We copy all the characters in this group to the buffer + outWord[wordPos] = lastChar; + if (UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS & lastFlags) { + int32_t nextChar = + getCharCodeAndForwardPointer(root, &lastCandidateGroupPos); + int charCount = maxDepth; + while (-1 != nextChar && --charCount > 0) { + outWord[++wordPos] = nextChar; + nextChar = getCharCodeAndForwardPointer(root, &lastCandidateGroupPos); + } + } + ++wordPos; + // Now we only need to branch to the children address. Skip the frequency if + // it's there, read pos, and break to resume the search at pos. + lastCandidateGroupPos = skipFrequency(lastFlags, lastCandidateGroupPos); + pos = readChildrenPosition(root, lastFlags, lastCandidateGroupPos); + break; + } else { + // Here is a little tricky part: we come here if we found out that all children + // addresses in this group are bigger than the address we are searching for. + // Should we conclude the word is not in the dictionary? No! It could still be + // one of the remaining chargroups in this node, so we have to keep looking in + // this node until we find it (or we realize it's not there either, in which + // case it's actually not in the dictionary). Pass the end of this group, ready + // to start the next one. + pos = skipChildrenPosAndAttributes(root, flags, pos); + } + } else { + // If we did not find it, we should record the last children address for the next + // iteration. + if (hasChildren) lastCandidateGroupPos = startPos; + // Now skip the end of this group (children pos and the attributes if any) so that + // our pos is after the end of this char group, at the start of the next one. + pos = skipChildrenPosAndAttributes(root, flags, pos); + } + + } + } + // If we have looked through all the chargroups and found no match, the address is + // not the address of a terminal in this dictionary. + return 0; +} + } // namespace latinime #endif // LATINIME_BINARY_FORMAT_H diff --git a/native/src/correction_state.cpp b/native/src/correction_state.cpp index aa5efce40..fba947ed4 100644 --- a/native/src/correction_state.cpp +++ b/native/src/correction_state.cpp @@ -21,18 +21,26 @@ #define LOG_TAG "LatinIME: correction_state.cpp" #include "correction_state.h" +#include "proximity_info.h" namespace latinime { -CorrectionState::CorrectionState() { +CorrectionState::CorrectionState(const int typedLetterMultiplier, const int fullWordMultiplier) + : TYPED_LETTER_MULTIPLIER(typedLetterMultiplier), FULL_WORD_MULTIPLIER(fullWordMultiplier) { } -void CorrectionState::setCorrectionParams(const ProximityInfo *pi, const int inputLength, - const int skipPos, const int excessivePos, const int transposedPos) { +void CorrectionState::initCorrectionState(const ProximityInfo *pi, const int inputLength) { mProximityInfo = pi; + mInputLength = inputLength; +} + +void CorrectionState::setCorrectionParams(const int skipPos, const int excessivePos, + const int transposedPos, const int spaceProximityPos, const int missingSpacePos) { mSkipPos = skipPos; mExcessivePos = excessivePos; mTransposedPos = transposedPos; + mSpaceProximityPos = spaceProximityPos; + mMissingSpacePos = missingSpacePos; } void CorrectionState::checkState() { @@ -46,7 +54,203 @@ void CorrectionState::checkState() { } } +int CorrectionState::getFreqForSplitTwoWords(const int firstFreq, const int secondFreq) { + return CorrectionState::RankingAlgorithm::calcFreqForSplitTwoWords(firstFreq, secondFreq, this); +} + +int CorrectionState::getFinalFreq(const int inputIndex, const int depth, const int matchWeight, + const int freq, const bool sameLength) { + return CorrectionState::RankingAlgorithm::calculateFinalFreq(inputIndex, depth, matchWeight, + freq, sameLength, this); +} + CorrectionState::~CorrectionState() { } +///////////////////////// +// 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 { + 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; + } + } +} + +////////////////////// +// RankingAlgorithm // +////////////////////// + +int CorrectionState::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const int depth, + const int matchCount, const int freq, const bool sameLength, + const CorrectionState* correctionState) { + const int skipPos = correctionState->getSkipPos(); + const int excessivePos = correctionState->getExcessivePos(); + const int transposedPos = correctionState->getTransposedPos(); + const int inputLength = correctionState->mInputLength; + const int typedLetterMultiplier = correctionState->TYPED_LETTER_MULTIPLIER; + const int fullWordMultiplier = correctionState->FULL_WORD_MULTIPLIER; + const ProximityInfo *proximityInfo = correctionState->mProximityInfo; + const int matchWeight = powerIntCapped(typedLetterMultiplier, matchCount); + + // TODO: Demote by edit distance + int finalFreq = freq * matchWeight; + if (skipPos >= 0) { + if (inputLength >= 2) { + const int demotionRate = WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE + * (10 * inputLength - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X) + / (10 * inputLength + - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10); + if (DEBUG_DICT_FULL) { + LOGI("Demotion rate for missing character is %d.", demotionRate); + } + multiplyRate(demotionRate, &finalFreq); + } else { + finalFreq = 0; + } + } + if (transposedPos >= 0) multiplyRate( + WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE, &finalFreq); + if (excessivePos >= 0) { + multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq); + if (!proximityInfo->existsAdjacentProximityChars(inputIndex)) { + // If an excessive character is not adjacent to the left char or the right char, + // we will demote this word. + multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE, &finalFreq); + } + } + int lengthFreq = typedLetterMultiplier; + multiplyIntCapped(powerIntCapped(typedLetterMultiplier, depth), &lengthFreq); + if (lengthFreq == matchWeight) { + // Full exact match + if (depth > 1) { + if (DEBUG_DICT) { + LOGI("Found full matched word."); + } + multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq); + } + if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0) { + finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq); + } + } else if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0 && depth > 0) { + // A word with proximity corrections + if (DEBUG_DICT) { + LOGI("Found one proximity correction."); + } + multiplyIntCapped(typedLetterMultiplier, &finalFreq); + multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq); + } + if (DEBUG_DICT) { + LOGI("calc: %d, %d", depth, sameLength); + } + if (sameLength) multiplyIntCapped(fullWordMultiplier, &finalFreq); + return finalFreq; +} + +int CorrectionState::RankingAlgorithm::calcFreqForSplitTwoWords( + const int firstFreq, const int secondFreq, const CorrectionState* correctionState) { + const int spaceProximityPos = correctionState->mSpaceProximityPos; + const int missingSpacePos = correctionState->mMissingSpacePos; + if (DEBUG_DICT) { + int inputCount = 0; + if (spaceProximityPos >= 0) ++inputCount; + if (missingSpacePos >= 0) ++inputCount; + assert(inputCount <= 1); + } + const bool isSpaceProximity = spaceProximityPos >= 0; + const int inputLength = correctionState->mInputLength; + const int firstWordLength = isSpaceProximity ? spaceProximityPos : missingSpacePos; + const int secondWordLength = isSpaceProximity + ? (inputLength - spaceProximityPos - 1) + : (inputLength - missingSpacePos); + const int typedLetterMultiplier = correctionState->TYPED_LETTER_MULTIPLIER; + + if (firstWordLength == 0 || secondWordLength == 0) { + return 0; + } + const int firstDemotionRate = 100 - 100 / (firstWordLength + 1); + int tempFirstFreq = firstFreq; + multiplyRate(firstDemotionRate, &tempFirstFreq); + + const int secondDemotionRate = 100 - 100 / (secondWordLength + 1); + int tempSecondFreq = secondFreq; + multiplyRate(secondDemotionRate, &tempSecondFreq); + + const int totalLength = firstWordLength + secondWordLength; + + // Promote pairFreq with multiplying by 2, because the word length is the same as the typed + // length. + int totalFreq = tempFirstFreq + tempSecondFreq; + + // This is a workaround to try offsetting the not-enough-demotion which will be done in + // calcNormalizedScore in Utils.java. + // In calcNormalizedScore the score will be demoted by (1 - 1 / length) + // but we demoted only (1 - 1 / (length + 1)) so we will additionally adjust freq by + // (1 - 1 / length) / (1 - 1 / (length + 1)) = (1 - 1 / (length * length)) + const int normalizedScoreNotEnoughDemotionAdjustment = 100 - 100 / (totalLength * totalLength); + multiplyRate(normalizedScoreNotEnoughDemotionAdjustment, &totalFreq); + + // At this moment, totalFreq is calculated by the following formula: + // (firstFreq * (1 - 1 / (firstWordLength + 1)) + secondFreq * (1 - 1 / (secondWordLength + 1))) + // * (1 - 1 / totalLength) / (1 - 1 / (totalLength + 1)) + + multiplyIntCapped(powerIntCapped(typedLetterMultiplier, totalLength), &totalFreq); + + // This is another workaround to offset the demotion which will be done in + // calcNormalizedScore in Utils.java. + // In calcNormalizedScore the score will be demoted by (1 - 1 / length) so we have to promote + // the same amount because we already have adjusted the synthetic freq of this "missing or + // mistyped space" suggestion candidate above in this method. + const int normalizedScoreDemotionRateOffset = (100 + 100 / totalLength); + multiplyRate(normalizedScoreDemotionRateOffset, &totalFreq); + + if (isSpaceProximity) { + // A word pair with one space proximity correction + if (DEBUG_DICT) { + LOGI("Found a word pair with space proximity correction."); + } + multiplyIntCapped(typedLetterMultiplier, &totalFreq); + multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &totalFreq); + } + + multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &totalFreq); + return totalFreq; +} + } // namespace latinime diff --git a/native/src/correction_state.h b/native/src/correction_state.h index 5b7392590..e03b2a17c 100644 --- a/native/src/correction_state.h +++ b/native/src/correction_state.h @@ -26,10 +26,12 @@ namespace latinime { class ProximityInfo; class CorrectionState { + public: - CorrectionState(); - void setCorrectionParams(const ProximityInfo *pi, const int inputLength, const int skipPos, - const int excessivePos, const int transposedPos); + CorrectionState(const int typedLetterMultiplier, const int fullWordMultiplier); + void initCorrectionState(const ProximityInfo *pi, const int inputLength); + void setCorrectionParams(const int skipPos, const int excessivePos, const int transposedPos, + const int spaceProximityPos, const int missingSpacePos); void checkState(); virtual ~CorrectionState(); int getSkipPos() const { @@ -41,12 +43,36 @@ public: int getTransposedPos() const { return mTransposedPos; } + int getSpaceProximityPos() const { + return mSpaceProximityPos; + } + int getMissingSpacePos() const { + return mMissingSpacePos; + } + int getFreqForSplitTwoWords(const int firstFreq, const int secondFreq); + int getFinalFreq(const int inputIndex, const int depth, const int matchWeight, const int freq, + const bool sameLength); + private: + + const int TYPED_LETTER_MULTIPLIER; + const int FULL_WORD_MULTIPLIER; const ProximityInfo *mProximityInfo; int mInputLength; int mSkipPos; int mExcessivePos; int mTransposedPos; + int mSpaceProximityPos; + int mMissingSpacePos; + + class RankingAlgorithm { + public: + static int calculateFinalFreq(const int inputIndex, const int depth, + const int matchCount, const int freq, const bool sameLength, + const CorrectionState* correctionState); + static int calcFreqForSplitTwoWords(const int firstFreq, const int secondFreq, + const CorrectionState* correctionState); + }; }; } // namespace latinime #endif // LATINIME_CORRECTION_INFO_H diff --git a/native/src/dictionary.cpp b/native/src/dictionary.cpp index 9e32ee80f..a49769bdb 100644 --- a/native/src/dictionary.cpp +++ b/native/src/dictionary.cpp @@ -57,12 +57,4 @@ bool Dictionary::isValidWord(unsigned short *word, int length) { return mUnigramDictionary->isValidWord(word, length); } -int Dictionary::getBigramPosition(unsigned short *word, int length) { - if (IS_LATEST_DICT_VERSION) { - return mUnigramDictionary->getBigramPosition(DICTIONARY_HEADER_SIZE, word, 0, length); - } else { - return mUnigramDictionary->getBigramPosition(0, word, 0, length); - } -} - } // namespace latinime diff --git a/native/src/dictionary.h b/native/src/dictionary.h index 73e03d8fd..d5de0083a 100644 --- a/native/src/dictionary.h +++ b/native/src/dictionary.h @@ -64,8 +64,6 @@ public: const int pos, unsigned short *c, int *childrenPosition, bool *terminal, int *freq); static inline unsigned short toBaseLowerCase(unsigned short c); - // TODO: delete this - int getBigramPosition(unsigned short *word, int length); private: bool hasBigram(); diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp index f0bb384fb..eb28538f1 100644 --- a/native/src/unigram_dictionary.cpp +++ b/native/src/unigram_dictionary.cpp @@ -48,7 +48,7 @@ UnigramDictionary::UnigramDictionary(const uint8_t* const streamStart, int typed if (DEBUG_DICT) { LOGI("UnigramDictionary - constructor"); } - mCorrectionState = new CorrectionState(); + mCorrectionState = new CorrectionState(typedLetterMultiplier, fullWordMultiplier); } UnigramDictionary::~UnigramDictionary() { @@ -187,6 +187,7 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, PROF_START(0); initSuggestions( proximityInfo, xcoordinates, ycoordinates, codes, codesSize, outWords, frequencies); + mCorrectionState->initCorrectionState(mProximityInfo, mInputLength); if (DEBUG_DICT) assert(codesSize == mInputLength); const int MAX_DEPTH = min(mInputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH); @@ -242,7 +243,7 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, if (DEBUG_DICT) { LOGI("--- Suggest missing space characters %d", i); } - getMissingSpaceWords(mInputLength, i); + getMissingSpaceWords(mInputLength, i, mCorrectionState); } } PROF_END(5); @@ -261,7 +262,7 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, i, x, y, proximityInfo->hasSpaceProximity(x, y)); } if (proximityInfo->hasSpaceProximity(x, y)) { - getMistypedSpaceWords(mInputLength, i); + getMistypedSpaceWords(mInputLength, i, mCorrectionState); } } } @@ -355,8 +356,8 @@ void UnigramDictionary::getSuggestionCandidates(const int skipPos, assert(excessivePos < mInputLength); assert(missingPos < mInputLength); } - mCorrectionState->setCorrectionParams(mProximityInfo, mInputLength, skipPos, excessivePos, - transposedPos); + mCorrectionState->setCorrectionParams(skipPos, excessivePos, transposedPos, + -1 /* spaceProximityPos */, -1 /* missingSpacePos */); int rootPosition = ROOT_POS; // Get the number of children of root, then increment the position int childCount = Dictionary::getCount(DICT_ROOT, &rootPosition); @@ -364,7 +365,7 @@ void UnigramDictionary::getSuggestionCandidates(const int skipPos, mStackChildCount[0] = childCount; mStackTraverseAll[0] = (mInputLength <= 0); - mStackNodeFreq[0] = 1; + mStackMatchCount[0] = 0; mStackInputIndex[0] = 0; mStackDiffs[0] = 0; mStackSiblingPos[0] = rootPosition; @@ -375,7 +376,7 @@ void UnigramDictionary::getSuggestionCandidates(const int skipPos, if (mStackChildCount[depth] > 0) { --mStackChildCount[depth]; bool traverseAllNodes = mStackTraverseAll[depth]; - int matchWeight = mStackNodeFreq[depth]; + int matchCount = mStackMatchCount[depth]; int inputIndex = mStackInputIndex[depth]; int diffs = mStackDiffs[depth]; int siblingPos = mStackSiblingPos[depth]; @@ -384,9 +385,9 @@ void UnigramDictionary::getSuggestionCandidates(const int skipPos, // depth will never be greater than maxDepth because in that case, // needsToTraverseChildrenNodes should be false const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos, outputIndex, - maxDepth, traverseAllNodes, matchWeight, inputIndex, diffs, + maxDepth, traverseAllNodes, matchCount, inputIndex, diffs, nextLetters, nextLettersSize, mCorrectionState, &childCount, - &firstChildPos, &traverseAllNodes, &matchWeight, &inputIndex, &diffs, + &firstChildPos, &traverseAllNodes, &matchCount, &inputIndex, &diffs, &siblingPos, &outputIndex); // Update next sibling pos mStackSiblingPos[depth] = siblingPos; @@ -395,7 +396,7 @@ void UnigramDictionary::getSuggestionCandidates(const int skipPos, ++depth; mStackChildCount[depth] = childCount; mStackTraverseAll[depth] = traverseAllNodes; - mStackNodeFreq[depth] = matchWeight; + mStackMatchCount[depth] = matchCount; mStackInputIndex[depth] = inputIndex; mStackDiffs[depth] = diffs; mStackSiblingPos[depth] = firstChildPos; @@ -408,11 +409,6 @@ void UnigramDictionary::getSuggestionCandidates(const int skipPos, } } -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; @@ -427,153 +423,18 @@ inline static void multiplyIntCapped(const int multiplier, int *base) { } } -inline static int powerIntCapped(const int base, const int n) { - 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; - } +void UnigramDictionary::getMissingSpaceWords( + const int inputLength, const int missingSpacePos, CorrectionState *correctionState) { + correctionState->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */, + -1 /* transposedPos */, -1 /* spaceProximityPos */, missingSpacePos); + getSplitTwoWordsSuggestion(inputLength, correctionState); } -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 calcFreqForSplitTwoWords( - const int typedLetterMultiplier, const int firstWordLength, const int secondWordLength, - const int firstFreq, const int secondFreq, const bool isSpaceProximity) { - if (firstWordLength == 0 || secondWordLength == 0) { - return 0; - } - const int firstDemotionRate = 100 - 100 / (firstWordLength + 1); - int tempFirstFreq = firstFreq; - multiplyRate(firstDemotionRate, &tempFirstFreq); - - const int secondDemotionRate = 100 - 100 / (secondWordLength + 1); - int tempSecondFreq = secondFreq; - multiplyRate(secondDemotionRate, &tempSecondFreq); - - const int totalLength = firstWordLength + secondWordLength; - - // Promote pairFreq with multiplying by 2, because the word length is the same as the typed - // length. - int totalFreq = tempFirstFreq + tempSecondFreq; - - // This is a workaround to try offsetting the not-enough-demotion which will be done in - // calcNormalizedScore in Utils.java. - // In calcNormalizedScore the score will be demoted by (1 - 1 / length) - // but we demoted only (1 - 1 / (length + 1)) so we will additionally adjust freq by - // (1 - 1 / length) / (1 - 1 / (length + 1)) = (1 - 1 / (length * length)) - const int normalizedScoreNotEnoughDemotionAdjustment = 100 - 100 / (totalLength * totalLength); - multiplyRate(normalizedScoreNotEnoughDemotionAdjustment, &totalFreq); - - // At this moment, totalFreq is calculated by the following formula: - // (firstFreq * (1 - 1 / (firstWordLength + 1)) + secondFreq * (1 - 1 / (secondWordLength + 1))) - // * (1 - 1 / totalLength) / (1 - 1 / (totalLength + 1)) - - multiplyIntCapped(powerIntCapped(typedLetterMultiplier, totalLength), &totalFreq); - - // This is another workaround to offset the demotion which will be done in - // calcNormalizedScore in Utils.java. - // In calcNormalizedScore the score will be demoted by (1 - 1 / length) so we have to promote - // the same amount because we already have adjusted the synthetic freq of this "missing or - // mistyped space" suggestion candidate above in this method. - const int normalizedScoreDemotionRateOffset = (100 + 100 / totalLength); - multiplyRate(normalizedScoreDemotionRateOffset, &totalFreq); - - if (isSpaceProximity) { - // A word pair with one space proximity correction - if (DEBUG_DICT) { - LOGI("Found a word pair with space proximity correction."); - } - multiplyIntCapped(typedLetterMultiplier, &totalFreq); - multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &totalFreq); - } - - multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &totalFreq); - return totalFreq; -} - -bool UnigramDictionary::getMissingSpaceWords(const int inputLength, const int missingSpacePos) { - return getSplitTwoWordsSuggestion( - inputLength, 0, missingSpacePos, missingSpacePos, inputLength - missingSpacePos, false); -} - -bool UnigramDictionary::getMistypedSpaceWords(const int inputLength, const int spaceProximityPos) { - return getSplitTwoWordsSuggestion( - inputLength, 0, spaceProximityPos, spaceProximityPos + 1, - inputLength - spaceProximityPos - 1, true); -} - -inline int UnigramDictionary::calculateFinalFreq(const int inputIndex, const int depth, - const int matchWeight, const int freq, const bool sameLength, - CorrectionState *correctionState) const { - const int skipPos = correctionState->getSkipPos(); - const int excessivePos = correctionState->getExcessivePos(); - const int transposedPos = correctionState->getTransposedPos(); - - // TODO: Demote by edit distance - int finalFreq = freq * matchWeight; - if (skipPos >= 0) { - if (mInputLength >= 2) { - const int demotionRate = WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE - * (10 * mInputLength - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X) - / (10 * mInputLength - - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10); - if (DEBUG_DICT_FULL) { - LOGI("Demotion rate for missing character is %d.", demotionRate); - } - multiplyRate(demotionRate, &finalFreq); - } else { - finalFreq = 0; - } - } - if (transposedPos >= 0) multiplyRate( - WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE, &finalFreq); - if (excessivePos >= 0) { - multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq); - if (!mProximityInfo->existsAdjacentProximityChars(inputIndex)) { - // If an excessive character is not adjacent to the left char or the right char, - // we will demote this word. - multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE, &finalFreq); - } - } - int lengthFreq = TYPED_LETTER_MULTIPLIER; - multiplyIntCapped(powerIntCapped(TYPED_LETTER_MULTIPLIER, depth), &lengthFreq); - if (lengthFreq == matchWeight) { - // Full exact match - if (depth > 1) { - if (DEBUG_DICT) { - LOGI("Found full matched word."); - } - multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq); - } - if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0) { - finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq); - } - } else if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0 && depth > 0) { - // A word with proximity corrections - if (DEBUG_DICT) { - LOGI("Found one proximity correction."); - } - multiplyIntCapped(TYPED_LETTER_MULTIPLIER, &finalFreq); - multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq); - } - if (DEBUG_DICT) { - LOGI("calc: %d, %d", depth, sameLength); - } - if (sameLength) multiplyIntCapped(FULL_WORD_MULTIPLIER, &finalFreq); - return finalFreq; +void UnigramDictionary::getMistypedSpaceWords( + const int inputLength, const int spaceProximityPos, CorrectionState *correctionState) { + correctionState->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */, + -1 /* transposedPos */, spaceProximityPos, -1 /* missingSpacePos */); + getSplitTwoWordsSuggestion(inputLength, correctionState); } inline bool UnigramDictionary::needsToSkipCurrentNode(const unsigned short c, @@ -586,7 +447,7 @@ inline bool UnigramDictionary::needsToSkipCurrentNode(const unsigned short c, inline void UnigramDictionary::onTerminal(unsigned short int* word, const int depth, const uint8_t* const root, const uint8_t flags, const int pos, - const int inputIndex, const int matchWeight, const int freq, const bool sameLength, + const int inputIndex, const int matchCount, const int freq, const bool sameLength, int* nextLetters, const int nextLettersSize, CorrectionState *correctionState) { const int skipPos = correctionState->getSkipPos(); @@ -594,8 +455,8 @@ inline void UnigramDictionary::onTerminal(unsigned short int* word, const int de if (isSameAsTyped) return; if (depth >= MIN_SUGGEST_DEPTH) { - const int finalFreq = calculateFinalFreq(inputIndex, depth, matchWeight, - freq, sameLength, correctionState); + const int finalFreq = correctionState->getFinalFreq(inputIndex, depth, matchCount, + freq, sameLength); if (!isSameAsTyped) addWord(word, depth + 1, finalFreq); } @@ -605,13 +466,29 @@ inline void UnigramDictionary::onTerminal(unsigned short int* word, const int de } } -bool UnigramDictionary::getSplitTwoWordsSuggestion(const int inputLength, - const int firstWordStartPos, const int firstWordLength, const int secondWordStartPos, - const int secondWordLength, const bool isSpaceProximity) { - if (inputLength >= MAX_WORD_LENGTH) return false; +void UnigramDictionary::getSplitTwoWordsSuggestion( + const int inputLength, CorrectionState* correctionState) { + const int spaceProximityPos = correctionState->getSpaceProximityPos(); + const int missingSpacePos = correctionState->getMissingSpacePos(); + if (DEBUG_DICT) { + int inputCount = 0; + if (spaceProximityPos >= 0) ++inputCount; + if (missingSpacePos >= 0) ++inputCount; + assert(inputCount <= 1); + } + const bool isSpaceProximity = spaceProximityPos >= 0; + const int firstWordStartPos = 0; + const int secondWordStartPos = isSpaceProximity ? (spaceProximityPos + 1) : missingSpacePos; + const int firstWordLength = isSpaceProximity ? spaceProximityPos : missingSpacePos; + const int secondWordLength = isSpaceProximity + ? (inputLength - spaceProximityPos - 1) + : (inputLength - missingSpacePos); + + if (inputLength >= MAX_WORD_LENGTH) return; if (0 >= firstWordLength || 0 >= secondWordLength || firstWordStartPos >= secondWordStartPos || firstWordStartPos < 0 || secondWordStartPos + secondWordLength > inputLength) - return false; + return; + const int newWordLength = firstWordLength + secondWordLength + 1; // Allocating variable length array on stack unsigned short word[newWordLength]; @@ -619,7 +496,7 @@ bool UnigramDictionary::getSplitTwoWordsSuggestion(const int inputLength, if (DEBUG_DICT) { LOGI("First freq: %d", firstFreq); } - if (firstFreq <= 0) return false; + if (firstFreq <= 0) return; for (int i = 0; i < firstWordLength; ++i) { word[i] = mWord[i]; @@ -629,21 +506,19 @@ bool UnigramDictionary::getSplitTwoWordsSuggestion(const int inputLength, if (DEBUG_DICT) { LOGI("Second freq: %d", secondFreq); } - if (secondFreq <= 0) return false; + if (secondFreq <= 0) return; word[firstWordLength] = SPACE; for (int i = (firstWordLength + 1); i < newWordLength; ++i) { word[i] = mWord[i - firstWordLength - 1]; } - int pairFreq = calcFreqForSplitTwoWords(TYPED_LETTER_MULTIPLIER, firstWordLength, - secondWordLength, firstFreq, secondFreq, isSpaceProximity); + const int pairFreq = mCorrectionState->getFreqForSplitTwoWords(firstFreq, secondFreq); if (DEBUG_DICT) { - LOGI("Split two words: %d, %d, %d, %d, %d", firstFreq, secondFreq, pairFreq, inputLength, - TYPED_LETTER_MULTIPLIER); + LOGI("Split two words: %d, %d, %d, %d", firstFreq, secondFreq, pairFreq, inputLength); } addWord(word, newWordLength, pairFreq); - return true; + return; } // Wrapper for getMostFrequentWordLikeInner, which matches it to the previous @@ -803,7 +678,7 @@ int UnigramDictionary::getBigramPosition(int pos, unsigned short *word, int offs // the current node in nextSiblingPosition. Thus, the caller must keep count of the nodes at any // given level, as output into newCount when traversing this level's parent. inline bool UnigramDictionary::processCurrentNode(const int initialPos, const int initialDepth, - const int maxDepth, const bool initialTraverseAllNodes, int matchWeight, int inputIndex, + const int maxDepth, const bool initialTraverseAllNodes, int matchCount, int inputIndex, const int initialDiffs, int *nextLetters, const int nextLettersSize, CorrectionState *correctionState, int *newCount, int *newChildrenPosition, bool *newTraverseAllNodes, int *newMatchRate, int *newInputIndex, int *newDiffs, @@ -868,7 +743,7 @@ inline bool UnigramDictionary::processCurrentNode(const int initialPos, const in // The frequency should be here, because we come here only if this is actually // a terminal node, and we are on its last char. const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos); - onTerminal(mWord, depth, DICT_ROOT, flags, pos, inputIndex, matchWeight, + onTerminal(mWord, depth, DICT_ROOT, flags, pos, inputIndex, matchCount, freq, false, nextLetters, nextLettersSize, mCorrectionState); } if (!hasChildren) { @@ -913,13 +788,13 @@ inline bool UnigramDictionary::processCurrentNode(const int initialPos, const in // If inputIndex is greater than mInputLength, that means there is no // proximity chars. So, we don't need to check proximity. if (ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR == matchedProximityCharId) { - multiplyIntCapped(TYPED_LETTER_MULTIPLIER, &matchWeight); + ++matchCount; } const bool isSameAsUserTypedLength = mInputLength == inputIndex + 1 || (excessivePos == mInputLength - 1 && inputIndex == mInputLength - 2); if (isSameAsUserTypedLength && isTerminal) { const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos); - onTerminal(mWord, depth, DICT_ROOT, flags, pos, inputIndex, matchWeight, + onTerminal(mWord, depth, DICT_ROOT, flags, pos, inputIndex, matchCount, freq, true, nextLetters, nextLettersSize, mCorrectionState); } // This character matched the typed character (enough to traverse the node at least) @@ -975,7 +850,7 @@ inline bool UnigramDictionary::processCurrentNode(const int initialPos, const in // All the output values that are purely computation by this function are held in local // variables. Output them to the caller. *newTraverseAllNodes = traverseAllNodes; - *newMatchRate = matchWeight; + *newMatchRate = matchCount; *newDiffs = diffs; *newInputIndex = inputIndex; *newOutputIndex = depth; diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h index 41e381860..f18ed6841 100644 --- a/native/src/unigram_dictionary.h +++ b/native/src/unigram_dictionary.h @@ -74,6 +74,7 @@ public: virtual ~UnigramDictionary(); private: + void getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const int codesSize, unsigned short *outWords, int *frequencies); @@ -89,13 +90,11 @@ private: const int transposedPos, int *nextLetters, const int nextLettersSize, const int maxDepth); bool addWord(unsigned short *word, int length, int frequency); - bool getSplitTwoWordsSuggestion(const int inputLength, - const int firstWordStartPos, const int firstWordLength, - const int secondWordStartPos, const int secondWordLength, const bool isSpaceProximity); - bool getMissingSpaceWords(const int inputLength, const int missingSpacePos); - bool getMistypedSpaceWords(const int inputLength, const int spaceProximityPos); - int calculateFinalFreq(const int inputIndex, const int depth, const int snr, - const int freq, const bool sameLength, CorrectionState *correctionState) const; + void getSplitTwoWordsSuggestion(const int inputLength, CorrectionState *correctionState); + void getMissingSpaceWords( + const int inputLength, const int missingSpacePos, CorrectionState *correctionState); + void getMistypedSpaceWords( + const int inputLength, const int spaceProximityPos, CorrectionState *correctionState); void onTerminal(unsigned short int* word, const int depth, const uint8_t* const root, const uint8_t flags, const int pos, const int inputIndex, const int matchWeight, const int freq, const bool sameLength, @@ -145,7 +144,7 @@ private: int mStackChildCount[MAX_WORD_LENGTH_INTERNAL]; bool mStackTraverseAll[MAX_WORD_LENGTH_INTERNAL]; - int mStackNodeFreq[MAX_WORD_LENGTH_INTERNAL]; + int mStackMatchCount[MAX_WORD_LENGTH_INTERNAL]; int mStackInputIndex[MAX_WORD_LENGTH_INTERNAL]; int mStackDiffs[MAX_WORD_LENGTH_INTERNAL]; int mStackSiblingPos[MAX_WORD_LENGTH_INTERNAL]; diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java index 07d0e5d75..f9ea1805a 100644 --- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java +++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java @@ -36,7 +36,7 @@ public class SuggestHelper { // Use null as the locale for Suggest so as to force it to use the internal dictionary // (and not try to find a dictionary provider for a specified locale) mSuggest = new Suggest(context, dictionaryId, null); - mKeyboard = new LatinKeyboard(context, keyboardId, keyboardId.mWidth); + mKeyboard = new LatinKeyboard.Builder(context).load(keyboardId).build(); mKeyDetector = new KeyDetector(0); init(); } @@ -44,7 +44,7 @@ public class SuggestHelper { protected SuggestHelper(Context context, File dictionaryPath, long startOffset, long length, KeyboardId keyboardId) { mSuggest = new Suggest(context, dictionaryPath, startOffset, length, null); - mKeyboard = new LatinKeyboard(context, keyboardId, keyboardId.mWidth); + mKeyboard = new LatinKeyboard.Builder(context).load(keyboardId).build(); mKeyDetector = new KeyDetector(0); init(); } @@ -54,7 +54,7 @@ public class SuggestHelper { mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL); mKeyDetector.setKeyboard(mKeyboard, 0, 0); mKeyDetector.setProximityCorrectionEnabled(true); - mKeyDetector.setProximityThreshold(mKeyboard.getMostCommonKeyWidth()); + mKeyDetector.setProximityThreshold(mKeyboard.mMostCommonKeyWidth); } public void setCorrectionMode(int correctionMode) { |