aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--java/proguard.flags4
-rw-r--r--java/res/values/strings.xml12
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java76
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyDetector.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java291
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java12
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java14
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java17
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboard.java67
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/MiniKeyboard.java26
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java8
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java18
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java95
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java236
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java118
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/Row.java33
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java2
-rw-r--r--native/src/bigram_dictionary.cpp202
-rw-r--r--native/src/binary_format.h147
-rw-r--r--native/src/correction_state.cpp210
-rw-r--r--native/src/correction_state.h32
-rw-r--r--native/src/dictionary.cpp8
-rw-r--r--native/src/dictionary.h2
-rw-r--r--native/src/unigram_dictionary.cpp235
-rw-r--r--native/src/unigram_dictionary.h15
-rw-r--r--tests/src/com/android/inputmethod/latin/SuggestHelper.java6
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) {