diff options
39 files changed, 819 insertions, 406 deletions
diff --git a/java/res/drawable-hdpi/sym_keyboard_info_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_info_holo_dark.png Binary files differnew file mode 100644 index 000000000..f52f5af7a --- /dev/null +++ b/java/res/drawable-hdpi/sym_keyboard_info_holo_dark.png diff --git a/java/res/drawable-hdpi/sym_keyboard_more_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_more_holo_dark.png Binary files differnew file mode 100644 index 000000000..72e2b297d --- /dev/null +++ b/java/res/drawable-hdpi/sym_keyboard_more_holo_dark.png diff --git a/java/res/drawable-mdpi/sym_keyboard_info_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_info_holo_dark.png Binary files differnew file mode 100644 index 000000000..fa4e2390f --- /dev/null +++ b/java/res/drawable-mdpi/sym_keyboard_info_holo_dark.png diff --git a/java/res/drawable-mdpi/sym_keyboard_more_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_more_holo_dark.png Binary files differnew file mode 100644 index 000000000..58e27a2df --- /dev/null +++ b/java/res/drawable-mdpi/sym_keyboard_more_holo_dark.png diff --git a/java/res/drawable-xhdpi/sym_keyboard_info_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_info_holo_dark.png Binary files differnew file mode 100644 index 000000000..513892fb9 --- /dev/null +++ b/java/res/drawable-xhdpi/sym_keyboard_info_holo_dark.png diff --git a/java/res/drawable-xhdpi/sym_keyboard_more_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_more_holo_dark.png Binary files differnew file mode 100644 index 000000000..07e9bee38 --- /dev/null +++ b/java/res/drawable-xhdpi/sym_keyboard_more_holo_dark.png diff --git a/java/res/drawable-xxhdpi/sym_keyboard_info_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_info_holo_dark.png Binary files differnew file mode 100644 index 000000000..b214d0eca --- /dev/null +++ b/java/res/drawable-xxhdpi/sym_keyboard_info_holo_dark.png diff --git a/java/res/drawable-xxhdpi/sym_keyboard_more_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_more_holo_dark.png Binary files differnew file mode 100644 index 000000000..702da2720 --- /dev/null +++ b/java/res/drawable-xxhdpi/sym_keyboard_more_holo_dark.png diff --git a/java/res/layout/suggestions_strip.xml b/java/res/layout/suggestions_strip.xml index 9c83542a6..9ffd231de 100644 --- a/java/res/layout/suggestions_strip.xml +++ b/java/res/layout/suggestions_strip.xml @@ -45,4 +45,17 @@ android:textAlignment="viewStart" style="?attr/suggestionWordStyle" /> </LinearLayout> + <LinearLayout + android:id="@+id/important_notice_strip" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <TextView + android:id="@+id/important_notice_title" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1.0" + android:textSize="14sp" + style="?attr/suggestionWordStyle" /> + </LinearLayout> </merge> diff --git a/java/res/values/strings-config-important-notice.xml b/java/res/values/strings-config-important-notice.xml index 8ee0b99c3..da206a37c 100644 --- a/java/res/values/strings-config-important-notice.xml +++ b/java/res/values/strings-config-important-notice.xml @@ -20,5 +20,8 @@ <resources> <integer name="config_important_notice_version">0</integer> - <!-- TODO: Add important notice title and description string resources. --> + <!-- The title of the important notice displayed on the suggestion strip. --> + <string name="important_notice_title"></string> + <!-- The contents of the important notice. --> + <string name="important_notice_contents"></string> </resources> diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index 5fd70ea63..f7eec6b43 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -284,19 +284,19 @@ public class Key implements Comparable<Key> { int moreKeysColumn = style.getInt(keyAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMoreKeysKeyboardColumn); int value; - if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) { + if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) { moreKeysColumn = value & MORE_KEYS_COLUMN_MASK; } - if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) { + if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) { moreKeysColumn = MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER | (value & MORE_KEYS_COLUMN_MASK); } - if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) { + if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) { moreKeysColumn |= MORE_KEYS_FLAGS_HAS_LABELS; } - if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) { + if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) { moreKeysColumn |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS; } - if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) { + if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) { moreKeysColumn |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY; } mMoreKeysColumnAndFlags = moreKeysColumn; @@ -308,7 +308,7 @@ public class Key implements Comparable<Key> { additionalMoreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_additionalMoreKeys); } - moreKeys = KeySpecParser.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys); + moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys); if (moreKeys != null) { actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS; mMoreKeys = new MoreKeySpec[moreKeys.length]; diff --git a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java index 4ccecb2f0..dce7fc57e 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java @@ -17,6 +17,7 @@ package com.android.inputmethod.keyboard.internal; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.utils.StringUtils; import android.text.TextUtils; @@ -29,15 +30,16 @@ import android.text.TextUtils; * marker. An output text may consist of multiple code points separated by comma. * The format of the codesArray element should be: * <pre> - * codePointInHex[,codePoint2InHex]*(|outputTextCodePointInHex[,outputTextCodePoint2InHex]*)? + * label1[,label2]*(|outputText1[,outputText2]*(|minSupportSdkVersion)?)? * </pre> */ // TODO: Write unit tests for this class. public final class CodesArrayParser { // Constants for parsing. - private static final char COMMA = ','; - private static final String VERTICAL_BAR_STRING = "\\|"; - private static final String COMMA_STRING = ","; + private static final char COMMA = Constants.CODE_COMMA; + private static final String COMMA_REGEX = StringUtils.newSingleCodePointString(COMMA); + private static final String VERTICAL_BAR_REGEX = // "\\|" + new String(new char[] { Constants.CODE_BACKSLASH, Constants.CODE_VERTICAL_BAR }); private static final int BASE_HEX = 16; private CodesArrayParser() { @@ -45,7 +47,7 @@ public final class CodesArrayParser { } private static String getLabelSpec(final String codesArraySpec) { - final String[] strs = codesArraySpec.split(VERTICAL_BAR_STRING, -1); + final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1); if (strs.length <= 1) { return codesArraySpec; } @@ -55,7 +57,7 @@ public final class CodesArrayParser { public static String parseLabel(final String codesArraySpec) { final String labelSpec = getLabelSpec(codesArraySpec); final StringBuilder sb = new StringBuilder(); - for (final String codeInHex : labelSpec.split(COMMA_STRING)) { + for (final String codeInHex : labelSpec.split(COMMA_REGEX)) { final int codePoint = Integer.parseInt(codeInHex, BASE_HEX); sb.appendCodePoint(codePoint); } @@ -63,17 +65,15 @@ public final class CodesArrayParser { } private static String getCodeSpec(final String codesArraySpec) { - final String[] strs = codesArraySpec.split(VERTICAL_BAR_STRING, -1); + final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1); if (strs.length <= 1) { return codesArraySpec; } return TextUtils.isEmpty(strs[1]) ? strs[0] : strs[1]; } - // codesArraySpec consists of: - // <label>|<code0>,<code1>,...|<minSupportSdkVersion> public static int getMinSupportSdkVersion(final String codesArraySpec) { - final String[] strs = codesArraySpec.split(VERTICAL_BAR_STRING, -1); + final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1); if (strs.length <= 2) { return 0; } @@ -98,7 +98,7 @@ public final class CodesArrayParser { return null; } final StringBuilder sb = new StringBuilder(); - for (final String codeInHex : codeSpec.split(COMMA_STRING)) { + for (final String codeInHex : codeSpec.split(COMMA_REGEX)) { final int codePoint = Integer.parseInt(codeInHex, BASE_HEX); sb.appendCodePoint(codePoint); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java index 5c7c6e39d..be7396520 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java @@ -17,6 +17,7 @@ package com.android.inputmethod.keyboard.internal; import android.content.Context; +import android.os.Handler; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; @@ -35,16 +36,19 @@ import com.android.inputmethod.latin.R; // TODO: Implement key popup preview. public final class EmojiPageKeyboardView extends KeyboardView implements GestureDetector.OnGestureListener { + private static final long KEY_PRESS_DELAY_TIME = 250; // msec + private static final long KEY_RELEASE_DELAY_TIME = 30; // msec + public interface OnKeyEventListener { public void onPressKey(Key key); public void onReleaseKey(Key key); } private static final OnKeyEventListener EMPTY_LISTENER = new OnKeyEventListener() { - @Override - public void onPressKey(final Key key) {} - @Override - public void onReleaseKey(final Key key) {} + @Override + public void onPressKey(final Key key) {} + @Override + public void onReleaseKey(final Key key) {} }; private OnKeyEventListener mListener = EMPTY_LISTENER; @@ -60,6 +64,7 @@ public final class EmojiPageKeyboardView extends KeyboardView implements super(context, attrs, defStyle); mGestureDetector = new GestureDetector(context, this); mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */); + mHandler = new Handler(); } public void setOnKeyEventListener(final OnKeyEventListener listener) { @@ -92,6 +97,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements // {@link GestureEnabler#OnGestureListener} methods. private Key mCurrentKey; + private Runnable mPendingKeyDown; + private final Handler mHandler; private Key getKey(final MotionEvent e) { final int index = e.getActionIndex(); @@ -101,6 +108,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements } public void releaseCurrentKey() { + mHandler.removeCallbacks(mPendingKeyDown); + mPendingKeyDown = null; final Key currentKey = mCurrentKey; if (currentKey == null) { return; @@ -118,9 +127,17 @@ public final class EmojiPageKeyboardView extends KeyboardView implements if (key == null) { return false; } - key.onPressed(); - invalidateKey(key); - mListener.onPressKey(key); + // Do not trigger key-down effect right now in case this is actually a fling action. + mPendingKeyDown = new Runnable() { + @Override + public void run() { + mPendingKeyDown = null; + key.onPressed(); + invalidateKey(key); + mListener.onPressKey(key); + } + }; + mHandler.postDelayed(mPendingKeyDown, KEY_PRESS_DELAY_TIME); return false; } @@ -132,13 +149,28 @@ public final class EmojiPageKeyboardView extends KeyboardView implements @Override public boolean onSingleTapUp(final MotionEvent e) { final Key key = getKey(e); + final Runnable pendingKeyDown = mPendingKeyDown; + final Key currentKey = mCurrentKey; releaseCurrentKey(); if (key == null) { return false; } - key.onReleased(); - invalidateKey(key); - mListener.onReleaseKey(key); + if (key == currentKey && pendingKeyDown != null) { + pendingKeyDown.run(); + // Trigger key-release event a little later so that a user can see visual feedback. + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + key.onReleased(); + invalidateKey(key); + mListener.onReleaseKey(key); + } + }, KEY_RELEASE_DELAY_TIME); + } else { + key.onReleased(); + invalidateKey(key); + mListener.onReleaseKey(key); + } return true; } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java index 07c5cae8a..c51941d32 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java @@ -19,106 +19,46 @@ package com.android.inputmethod.keyboard.internal; import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT; import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; -import android.text.TextUtils; - -import com.android.inputmethod.latin.LatinImeLogger; -import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.utils.StringUtils; -import java.util.ArrayList; -import java.util.Arrays; - /** - * The string parser of more keys specification. - * The specification is comma separated texts each of which represents one "more key". - * The specification might have label or string resource reference in it. These references are - * expanded before parsing comma. - * - Label reference should be a string representation of label (!text/label_name) - * - String resource reference should be a string representation of resource (!text/resource_name) - * Each "more key" specification is one of the following: - * - Label optionally followed by keyOutputText or code (keyLabel|keyOutputText). - * - Icon followed by keyOutputText or code (!icon/icon_name|!code/code_name) - * - Icon should be a string representation of icon (!icon/icon_name). - * - Code should be a code point presented by hexadecimal string prefixed with "0x", or a string - * representation of code (!code/code_name). + * The string parser of the key specification. + * + * Each key specification is one of the following: + * - Label optionally followed by keyOutputText (keyLabel|keyOutputText). + * - Label optionally followed by code point (keyLabel|!code/code_name). + * - Icon followed by keyOutputText (!icon/icon_name|keyOutputText). + * - Icon followed by code point (!icon/icon_name|!code/code_name). + * Label and keyOutputText are one of the following: + * - Literal string. + * - Label reference represented by (!text/label_name), see {@link KeyboardTextsSet}. + * - String resource reference represented by (!text/resource_name), see {@link KeyboardTextsSet}. + * Icon is represented by (!icon/icon_name), see {@link KeyboardIconsSet}. + * Code is one of the following: + * - Code point presented by hexadecimal string prefixed with "0x" + * - Code reference represented by (!code/code_name), see {@link KeyboardCodesSet}. * Special character, comma ',' backslash '\', and bar '|' can be escaped by '\' character. - * Note that the '\' is also parsed by XML parser and CSV parser as well. - * See {@link KeyboardIconsSet} about icon_name. + * Note that the '\' is also parsed by XML parser and {@link MoreKeySpec#splitKeySpecs(String)} + * as well. */ public final class KeySpecParser { - private static final boolean DEBUG = LatinImeLogger.sDBG; - // Constants for parsing. - private static final char COMMA = ','; - private static final char BACKSLASH = '\\'; - private static final char VERTICAL_BAR = '|'; + private static final char BACKSLASH = Constants.CODE_BACKSLASH; + private static final char VERTICAL_BAR = Constants.CODE_VERTICAL_BAR; private static final String PREFIX_HEX = "0x"; - private static final String ADDITIONAL_MORE_KEY_MARKER = "%"; private KeySpecParser() { // Intentional empty constructor for utility class. } - /** - * Split the text containing multiple key specifications separated by commas into an array of - * key specifications. - * A key specification can contain a character escaped by the backslash character, including a - * comma character. - * Note that an empty key specification will be eliminated from the result array. - * - * @param text the text containing multiple key specifications. - * @return an array of key specification text. Null if the specified <code>text</code> is empty - * or has no key specifications. - */ - public static String[] splitKeySpecs(final String text) { - if (TextUtils.isEmpty(text)) { - return null; - } - final int size = text.length(); - // Optimization for one-letter key specification. - if (size == 1) { - return text.charAt(0) == COMMA ? null : new String[] { text }; - } - - ArrayList<String> list = null; - int start = 0; - // The characters in question in this loop are COMMA and BACKSLASH. These characters never - // match any high or low surrogate character. So it is OK to iterate through with char - // index. - for (int pos = 0; pos < size; pos++) { - final char c = text.charAt(pos); - if (c == COMMA) { - // Skip empty entry. - if (pos - start > 0) { - if (list == null) { - list = CollectionUtils.newArrayList(); - } - list.add(text.substring(start, pos)); - } - // Skip comma - start = pos + 1; - } else if (c == BACKSLASH) { - // Skip escape character and escaped character. - pos++; - } - } - final String remain = (size - start > 0) ? text.substring(start) : null; - if (list == null) { - return remain != null ? new String[] { remain } : null; - } - if (remain != null) { - list.add(remain); - } - return list.toArray(new String[list.size()]); - } - - private static boolean hasIcon(final String moreKeySpec) { - return moreKeySpec.startsWith(KeyboardIconsSet.PREFIX_ICON); + private static boolean hasIcon(final String keySpec) { + return keySpec.startsWith(KeyboardIconsSet.PREFIX_ICON); } - private static boolean hasCode(final String moreKeySpec) { - final int end = indexOfLabelEnd(moreKeySpec, 0); - if (end > 0 && end + 1 < moreKeySpec.length() && moreKeySpec.startsWith( + private static boolean hasCode(final String keySpec) { + final int end = indexOfLabelEnd(keySpec, 0); + if (end > 0 && end + 1 < keySpec.length() && keySpec.startsWith( KeyboardCodesSet.PREFIX_CODE, end + 1)) { return true; } @@ -144,17 +84,17 @@ public final class KeySpecParser { return sb.toString(); } - private static int indexOfLabelEnd(final String moreKeySpec, final int start) { - if (moreKeySpec.indexOf(BACKSLASH, start) < 0) { - final int end = moreKeySpec.indexOf(VERTICAL_BAR, start); + private static int indexOfLabelEnd(final String keySpec, final int start) { + if (keySpec.indexOf(BACKSLASH, start) < 0) { + final int end = keySpec.indexOf(VERTICAL_BAR, start); if (end == 0) { - throw new KeySpecParserError(VERTICAL_BAR + " at " + start + ": " + moreKeySpec); + throw new KeySpecParserError(VERTICAL_BAR + " at " + start + ": " + keySpec); } return end; } - final int length = moreKeySpec.length(); + final int length = keySpec.length(); for (int pos = start; pos < length; pos++) { - final char c = moreKeySpec.charAt(pos); + final char c = keySpec.charAt(pos); if (c == BACKSLASH && pos + 1 < length) { // Skip escape char pos++; @@ -165,63 +105,63 @@ public final class KeySpecParser { return -1; } - public static String getLabel(final String moreKeySpec) { - if (hasIcon(moreKeySpec)) { + public static String getLabel(final String keySpec) { + if (hasIcon(keySpec)) { return null; } - final int end = indexOfLabelEnd(moreKeySpec, 0); - final String label = (end > 0) ? parseEscape(moreKeySpec.substring(0, end)) - : parseEscape(moreKeySpec); - if (TextUtils.isEmpty(label)) { - throw new KeySpecParserError("Empty label: " + moreKeySpec); + final int end = indexOfLabelEnd(keySpec, 0); + final String label = (end > 0) ? parseEscape(keySpec.substring(0, end)) + : parseEscape(keySpec); + if (label.isEmpty()) { + throw new KeySpecParserError("Empty label: " + keySpec); } return label; } - private static String getOutputTextInternal(final String moreKeySpec) { - final int end = indexOfLabelEnd(moreKeySpec, 0); + private static String getOutputTextInternal(final String keySpec) { + final int end = indexOfLabelEnd(keySpec, 0); if (end <= 0) { return null; } - if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) { - throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec); + if (indexOfLabelEnd(keySpec, end + 1) >= 0) { + throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + keySpec); } - return parseEscape(moreKeySpec.substring(end + /* VERTICAL_BAR */1)); + return parseEscape(keySpec.substring(end + /* VERTICAL_BAR */1)); } - static String getOutputText(final String moreKeySpec) { - if (hasCode(moreKeySpec)) { + public static String getOutputText(final String keySpec) { + if (hasCode(keySpec)) { return null; } - final String outputText = getOutputTextInternal(moreKeySpec); + final String outputText = getOutputTextInternal(keySpec); if (outputText != null) { if (StringUtils.codePointCount(outputText) == 1) { // If output text is one code point, it should be treated as a code. // See {@link #getCode(Resources, String)}. return null; } - if (!TextUtils.isEmpty(outputText)) { - return outputText; + if (outputText.isEmpty()) { + throw new KeySpecParserError("Empty outputText: " + keySpec); } - throw new KeySpecParserError("Empty outputText: " + moreKeySpec); + return outputText; } - final String label = getLabel(moreKeySpec); + final String label = getLabel(keySpec); if (label == null) { - throw new KeySpecParserError("Empty label: " + moreKeySpec); + throw new KeySpecParserError("Empty label: " + keySpec); } // Code is automatically generated for one letter label. See {@link getCode()}. return (StringUtils.codePointCount(label) == 1) ? null : label; } - static int getCode(final String moreKeySpec, final KeyboardCodesSet codesSet) { - if (hasCode(moreKeySpec)) { - final int end = indexOfLabelEnd(moreKeySpec, 0); - if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) { - throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec); + public static int getCode(final String keySpec, final KeyboardCodesSet codesSet) { + if (hasCode(keySpec)) { + final int end = indexOfLabelEnd(keySpec, 0); + if (indexOfLabelEnd(keySpec, end + 1) >= 0) { + throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + keySpec); } - return parseCode(moreKeySpec.substring(end + 1), codesSet, CODE_UNSPECIFIED); + return parseCode(keySpec.substring(end + 1), codesSet, CODE_UNSPECIFIED); } - final String outputText = getOutputTextInternal(moreKeySpec); + final String outputText = getOutputTextInternal(keySpec); if (outputText != null) { // If output text is one code point, it should be treated as a code. // See {@link #getOutputText(String)}. @@ -230,7 +170,7 @@ public final class KeySpecParser { } return CODE_OUTPUT_TEXT; } - final String label = getLabel(moreKeySpec); + final String label = getLabel(keySpec); // Code is automatically generated for one letter label. if (StringUtils.codePointCount(label) == 1) { return label.codePointAt(0); @@ -250,154 +190,22 @@ public final class KeySpecParser { } } - public static int getIconId(final String moreKeySpec) { - if (moreKeySpec != null && hasIcon(moreKeySpec)) { - final int end = moreKeySpec.indexOf( + public static int getIconId(final String keySpec) { + if (keySpec != null && hasIcon(keySpec)) { + final int end = keySpec.indexOf( VERTICAL_BAR, KeyboardIconsSet.PREFIX_ICON.length()); final String name = (end < 0) - ? moreKeySpec.substring(KeyboardIconsSet.PREFIX_ICON.length()) - : moreKeySpec.substring(KeyboardIconsSet.PREFIX_ICON.length(), end); + ? keySpec.substring(KeyboardIconsSet.PREFIX_ICON.length()) + : keySpec.substring(KeyboardIconsSet.PREFIX_ICON.length(), end); return KeyboardIconsSet.getIconId(name); } return KeyboardIconsSet.ICON_UNDEFINED; } - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - - private static String[] filterOutEmptyString(final String[] array) { - if (array == null) { - return EMPTY_STRING_ARRAY; - } - ArrayList<String> out = null; - for (int i = 0; i < array.length; i++) { - final String entry = array[i]; - if (TextUtils.isEmpty(entry)) { - if (out == null) { - out = CollectionUtils.arrayAsList(array, 0, i); - } - } else if (out != null) { - out.add(entry); - } - } - if (out == null) { - return array; - } - return out.toArray(new String[out.size()]); - } - - public static String[] insertAdditionalMoreKeys(final String[] moreKeySpecs, - final String[] additionalMoreKeySpecs) { - final String[] moreKeys = filterOutEmptyString(moreKeySpecs); - final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs); - final int moreKeysCount = moreKeys.length; - final int additionalCount = additionalMoreKeys.length; - ArrayList<String> out = null; - int additionalIndex = 0; - for (int moreKeyIndex = 0; moreKeyIndex < moreKeysCount; moreKeyIndex++) { - final String moreKeySpec = moreKeys[moreKeyIndex]; - if (moreKeySpec.equals(ADDITIONAL_MORE_KEY_MARKER)) { - if (additionalIndex < additionalCount) { - // Replace '%' marker with additional more key specification. - final String additionalMoreKey = additionalMoreKeys[additionalIndex]; - if (out != null) { - out.add(additionalMoreKey); - } else { - moreKeys[moreKeyIndex] = additionalMoreKey; - } - additionalIndex++; - } else { - // Filter out excessive '%' marker. - if (out == null) { - out = CollectionUtils.arrayAsList(moreKeys, 0, moreKeyIndex); - } - } - } else { - if (out != null) { - out.add(moreKeySpec); - } - } - } - if (additionalCount > 0 && additionalIndex == 0) { - // No '%' marker is found in more keys. - // Insert all additional more keys to the head of more keys. - if (DEBUG && out != null) { - throw new RuntimeException("Internal logic error:" - + " moreKeys=" + Arrays.toString(moreKeys) - + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys)); - } - out = CollectionUtils.arrayAsList(additionalMoreKeys, additionalIndex, additionalCount); - for (int i = 0; i < moreKeysCount; i++) { - out.add(moreKeys[i]); - } - } else if (additionalIndex < additionalCount) { - // The number of '%' markers are less than additional more keys. - // Append remained additional more keys to the tail of more keys. - if (DEBUG && out != null) { - throw new RuntimeException("Internal logic error:" - + " moreKeys=" + Arrays.toString(moreKeys) - + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys)); - } - out = CollectionUtils.arrayAsList(moreKeys, 0, moreKeysCount); - for (int i = additionalIndex; i < additionalCount; i++) { - out.add(additionalMoreKeys[additionalIndex]); - } - } - if (out == null && moreKeysCount > 0) { - return moreKeys; - } else if (out != null && out.size() > 0) { - return out.toArray(new String[out.size()]); - } else { - return null; - } - } - @SuppressWarnings("serial") public static final class KeySpecParserError extends RuntimeException { public KeySpecParserError(final String message) { super(message); } } - - public static int getIntValue(final String[] moreKeys, final String key, - final int defaultValue) { - if (moreKeys == null) { - return defaultValue; - } - final int keyLen = key.length(); - boolean foundValue = false; - int value = defaultValue; - for (int i = 0; i < moreKeys.length; i++) { - final String moreKeySpec = moreKeys[i]; - if (moreKeySpec == null || !moreKeySpec.startsWith(key)) { - continue; - } - moreKeys[i] = null; - try { - if (!foundValue) { - value = Integer.parseInt(moreKeySpec.substring(keyLen)); - foundValue = true; - } - } catch (NumberFormatException e) { - throw new RuntimeException( - "integer should follow after " + key + ": " + moreKeySpec); - } - } - return value; - } - - public static boolean getBooleanValue(final String[] moreKeys, final String key) { - if (moreKeys == null) { - return false; - } - boolean value = false; - for (int i = 0; i < moreKeys.length; i++) { - final String moreKeySpec = moreKeys[i]; - if (moreKeySpec == null || !moreKeySpec.equals(key)) { - continue; - } - moreKeys[i] = null; - value = true; - } - return value; - } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java index a2242b841..7941ddd41 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java @@ -40,7 +40,7 @@ public abstract class KeyStyle { protected String[] parseStringArray(final TypedArray a, final int index) { if (a.hasValue(index)) { final String text = mTextsSet.resolveTextReference(a.getString(index)); - return KeySpecParser.splitKeySpecs(text); + return MoreKeySpec.splitKeySpecs(text); } return null; } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java index 96a5b089f..f8c098893 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java @@ -21,6 +21,7 @@ import android.content.res.Resources; import android.text.TextUtils; import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.HashMap; @@ -47,7 +48,7 @@ import java.util.HashMap; */ public final class KeyboardTextsSet { public static final String PREFIX_TEXT = "!text/"; - private static final char BACKSLASH = '\\'; + private static final char BACKSLASH = Constants.CODE_BACKSLASH; private static final int MAX_STRING_REFERENCE_INDIRECTION = 10; // Language to texts map. diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java index dd58e1ff4..d3bc0c2b2 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java +++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java @@ -19,10 +19,25 @@ package com.android.inputmethod.keyboard.internal; import android.text.TextUtils; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.LatinImeLogger; +import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.StringUtils; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Locale; +/** + * The more key specification object. The more keys are an array of {@link MoreKeySpec}. + * + * The more keys specification is comma separated "key specification" each of which represents one + * "more key". + * The key specification might have label or string resource reference in it. These references are + * expanded before parsing comma. + * Special character, comma ',' backslash '\' can be escaped by '\' character. + * Note that the '\' is also parsed by XML parser and {@link MoreKeySpec#splitKeySpecs(String)} + * as well. + */ public final class MoreKeySpec { public final int mCode; public final String mLabel; @@ -83,4 +98,196 @@ public final class MoreKeySpec { return label + "|" + output; } } + + private static final boolean DEBUG = LatinImeLogger.sDBG; + // Constants for parsing. + private static final char COMMA = Constants.CODE_COMMA; + private static final char BACKSLASH = Constants.CODE_BACKSLASH; + private static final String ADDITIONAL_MORE_KEY_MARKER = + StringUtils.newSingleCodePointString(Constants.CODE_PERCENT); + + /** + * Split the text containing multiple key specifications separated by commas into an array of + * key specifications. + * A key specification can contain a character escaped by the backslash character, including a + * comma character. + * Note that an empty key specification will be eliminated from the result array. + * + * @param text the text containing multiple key specifications. + * @return an array of key specification text. Null if the specified <code>text</code> is empty + * or has no key specifications. + */ + public static String[] splitKeySpecs(final String text) { + if (TextUtils.isEmpty(text)) { + return null; + } + final int size = text.length(); + // Optimization for one-letter key specification. + if (size == 1) { + return text.charAt(0) == COMMA ? null : new String[] { text }; + } + + ArrayList<String> list = null; + int start = 0; + // The characters in question in this loop are COMMA and BACKSLASH. These characters never + // match any high or low surrogate character. So it is OK to iterate through with char + // index. + for (int pos = 0; pos < size; pos++) { + final char c = text.charAt(pos); + if (c == COMMA) { + // Skip empty entry. + if (pos - start > 0) { + if (list == null) { + list = CollectionUtils.newArrayList(); + } + list.add(text.substring(start, pos)); + } + // Skip comma + start = pos + 1; + } else if (c == BACKSLASH) { + // Skip escape character and escaped character. + pos++; + } + } + final String remain = (size - start > 0) ? text.substring(start) : null; + if (list == null) { + return remain != null ? new String[] { remain } : null; + } + if (remain != null) { + list.add(remain); + } + return list.toArray(new String[list.size()]); + } + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + private static String[] filterOutEmptyString(final String[] array) { + if (array == null) { + return EMPTY_STRING_ARRAY; + } + ArrayList<String> out = null; + for (int i = 0; i < array.length; i++) { + final String entry = array[i]; + if (TextUtils.isEmpty(entry)) { + if (out == null) { + out = CollectionUtils.arrayAsList(array, 0, i); + } + } else if (out != null) { + out.add(entry); + } + } + if (out == null) { + return array; + } + return out.toArray(new String[out.size()]); + } + + public static String[] insertAdditionalMoreKeys(final String[] moreKeySpecs, + final String[] additionalMoreKeySpecs) { + final String[] moreKeys = filterOutEmptyString(moreKeySpecs); + final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs); + final int moreKeysCount = moreKeys.length; + final int additionalCount = additionalMoreKeys.length; + ArrayList<String> out = null; + int additionalIndex = 0; + for (int moreKeyIndex = 0; moreKeyIndex < moreKeysCount; moreKeyIndex++) { + final String moreKeySpec = moreKeys[moreKeyIndex]; + if (moreKeySpec.equals(ADDITIONAL_MORE_KEY_MARKER)) { + if (additionalIndex < additionalCount) { + // Replace '%' marker with additional more key specification. + final String additionalMoreKey = additionalMoreKeys[additionalIndex]; + if (out != null) { + out.add(additionalMoreKey); + } else { + moreKeys[moreKeyIndex] = additionalMoreKey; + } + additionalIndex++; + } else { + // Filter out excessive '%' marker. + if (out == null) { + out = CollectionUtils.arrayAsList(moreKeys, 0, moreKeyIndex); + } + } + } else { + if (out != null) { + out.add(moreKeySpec); + } + } + } + if (additionalCount > 0 && additionalIndex == 0) { + // No '%' marker is found in more keys. + // Insert all additional more keys to the head of more keys. + if (DEBUG && out != null) { + throw new RuntimeException("Internal logic error:" + + " moreKeys=" + Arrays.toString(moreKeys) + + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys)); + } + out = CollectionUtils.arrayAsList(additionalMoreKeys, additionalIndex, additionalCount); + for (int i = 0; i < moreKeysCount; i++) { + out.add(moreKeys[i]); + } + } else if (additionalIndex < additionalCount) { + // The number of '%' markers are less than additional more keys. + // Append remained additional more keys to the tail of more keys. + if (DEBUG && out != null) { + throw new RuntimeException("Internal logic error:" + + " moreKeys=" + Arrays.toString(moreKeys) + + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys)); + } + out = CollectionUtils.arrayAsList(moreKeys, 0, moreKeysCount); + for (int i = additionalIndex; i < additionalCount; i++) { + out.add(additionalMoreKeys[additionalIndex]); + } + } + if (out == null && moreKeysCount > 0) { + return moreKeys; + } else if (out != null && out.size() > 0) { + return out.toArray(new String[out.size()]); + } else { + return null; + } + } + + public static int getIntValue(final String[] moreKeys, final String key, + final int defaultValue) { + if (moreKeys == null) { + return defaultValue; + } + final int keyLen = key.length(); + boolean foundValue = false; + int value = defaultValue; + for (int i = 0; i < moreKeys.length; i++) { + final String moreKeySpec = moreKeys[i]; + if (moreKeySpec == null || !moreKeySpec.startsWith(key)) { + continue; + } + moreKeys[i] = null; + try { + if (!foundValue) { + value = Integer.parseInt(moreKeySpec.substring(keyLen)); + foundValue = true; + } + } catch (NumberFormatException e) { + throw new RuntimeException( + "integer should follow after " + key + ": " + moreKeySpec); + } + } + return value; + } + + public static boolean getBooleanValue(final String[] moreKeys, final String key) { + if (moreKeys == null) { + return false; + } + boolean value = false; + for (int i = 0; i < moreKeys.length; i++) { + final String moreKeySpec = moreKeys[i]; + if (moreKeySpec == null || !moreKeySpec.equals(key)) { + continue; + } + moreKeys[i] = null; + value = true; + } + return value; + } } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 95823dac5..00eb57c9f 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -22,6 +22,7 @@ import android.util.SparseArray; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.makedict.Word; import com.android.inputmethod.latin.settings.NativeSuggestOptions; import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.JniUtils; @@ -68,11 +69,12 @@ public final class BinaryDictionary extends Dictionary { private static final int FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX = 2; private static final int FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX = 3; - // Format to get unigram historical info from native side via getWordPropertyNative(). - private static final int FORMAT_WORD_PROPERTY_OUTPUT_HISTORICAL_INFO_COUNT = 3; - private static final int FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX = 0; - private static final int FORMAT_WORD_PROPERTY_LEVEL_INDEX = 1; - private static final int FORMAT_WORD_PROPERTY_COUNT_INDEX = 2; + // Format to get probability and historical info from native side via getWordPropertyNative(). + public static final int FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT = 4; + public static final int FORMAT_WORD_PROPERTY_PROBABILITY_INDEX = 0; + public static final int FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX = 1; + public static final int FORMAT_WORD_PROPERTY_LEVEL_INDEX = 2; + public static final int FORMAT_WORD_PROPERTY_COUNT_INDEX = 3; private long mNativeDict; private final Locale mLocale; @@ -144,9 +146,10 @@ public final class BinaryDictionary extends Dictionary { private static native int getProbabilityNative(long dict, int[] word); private static native int getBigramProbabilityNative(long dict, int[] word0, int[] word1); private static native void getWordPropertyNative(long dict, int[] word, - int[] outCodePoints, boolean[] outFlags, int[] outProbability, - int[] outHistoricalInfo, ArrayList<int[]> outShortcutTargets, - ArrayList<Integer> outShortcutProbabilities); + int[] outCodePoints, boolean[] outFlags, int[] outProbabilityInfo, + ArrayList<int[]> outBigramTargets, ArrayList<int[]> outBigramProbabilityInfo, + ArrayList<int[]> outShortcutTargets, ArrayList<Integer> outShortcutProbabilities); + private static native int getNextWordNative(long dict, int token, int[] outCodePoints); private static native int getSuggestionsNative(long dict, long proximityInfo, long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodePoints, int inputSize, int commitPoint, @@ -313,22 +316,49 @@ public final class BinaryDictionary extends Dictionary { final int[] codePoints = StringUtils.toCodePointArray(word); final int[] outCodePoints = new int[MAX_WORD_LENGTH]; final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT]; - final int[] outProbability = new int[1]; - final int[] outHistoricalInfo = - new int[FORMAT_WORD_PROPERTY_OUTPUT_HISTORICAL_INFO_COUNT]; + final int[] outProbabilityInfo = + new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT]; + final ArrayList<int[]> outBigramTargets = CollectionUtils.newArrayList(); + final ArrayList<int[]> outBigramProbabilityInfo = CollectionUtils.newArrayList(); final ArrayList<int[]> outShortcutTargets = CollectionUtils.newArrayList(); final ArrayList<Integer> outShortcutProbabilities = CollectionUtils.newArrayList(); - getWordPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbability, - outHistoricalInfo, outShortcutTargets, outShortcutProbabilities); + getWordPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbabilityInfo, + outBigramTargets, outBigramProbabilityInfo, outShortcutTargets, + outShortcutProbabilities); return new WordProperty(codePoints, outFlags[FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX], outFlags[FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX], outFlags[FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX], - outFlags[FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX], outProbability[0], - outHistoricalInfo[FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX], - outHistoricalInfo[FORMAT_WORD_PROPERTY_LEVEL_INDEX], - outHistoricalInfo[FORMAT_WORD_PROPERTY_COUNT_INDEX], - outShortcutTargets, outShortcutProbabilities); + outFlags[FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX], outProbabilityInfo, + outBigramTargets, outBigramProbabilityInfo, outShortcutTargets, + outShortcutProbabilities); + } + + public static class GetNextWordPropertyResult { + public WordProperty mWordProperty; + public int mNextToken; + + public GetNextWordPropertyResult(final WordProperty wordPreperty, final int nextToken) { + mWordProperty = wordPreperty; + mNextToken = nextToken; + } + } + + /** + * Method to iterate all words in the dictionary for makedict. + * If token is 0, this method newly starts iterating the dictionary. + */ + @UsedForTesting + public GetNextWordPropertyResult getNextWordProperty(final int token) { + final int[] codePoints = new int[MAX_WORD_LENGTH]; + final int nextToken = getNextWordNative(mNativeDict, token, codePoints); + int len = 0; + // codePoints is null-terminated if its length is shorter than the array length. + while (len < MAX_WORD_LENGTH && codePoints[len] != 0) { + ++len; + } + final String word = new String(mOutputCodePoints, 0, len); + return new GetNextWordPropertyResult(getWordProperty(word), nextToken); } // Add a unigram entry to binary dictionary with unigram attributes in native code. @@ -379,7 +409,6 @@ public final class BinaryDictionary extends Dictionary { return; } } - } private void reopen() { diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java index 0b396b1de..d6ac71fe2 100644 --- a/java/src/com/android/inputmethod/latin/Constants.java +++ b/java/src/com/android/inputmethod/latin/Constants.java @@ -191,6 +191,8 @@ public final class Constants { public static final int CODE_QUESTION_MARK = '?'; public static final int CODE_EXCLAMATION_MARK = '!'; public static final int CODE_SLASH = '/'; + public static final int CODE_BACKSLASH = '\\'; + public static final int CODE_VERTICAL_BAR = '|'; public static final int CODE_COMMERCIAL_AT = '@'; public static final int CODE_PLUS = '+'; public static final int CODE_PERCENT = '%'; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 8f4b2d67e..2c7ef70a3 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1157,6 +1157,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mInputLogic.mSuggest.mDictionaryFacilitator.addWordToUserDictionary(wordToEdit); } + // Callback for the {@link SuggestionStripView}, to call when the important notice strip is + // pressed. + @Override + public void showImportantNoticeContents() { + // TODO: Show dialog to display important notice contents. + } + public void displaySettingsDialog() { if (isShowingOptionDialog()) return; showSubtypeSelectorAndSettings(); @@ -1401,10 +1408,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // because it may differ from mWordComposer.mTypedWord. autoCorrection = sourceSuggestedWords.mTypedWord; } - if (SuggestedWords.EMPTY != suggestedWords) { + if (SuggestedWords.EMPTY == suggestedWords) { + setNeutralSuggestionStrip(); + } else { mInputLogic.mWordComposer.setAutoCorrection(autoCorrection); + setSuggestedWords(suggestedWords, isSuggestionsStripVisible()); } - setSuggestedWords(suggestedWords, isSuggestionsStripVisible()); // Cache the auto-correction in accessibility code so we can speak it if the user // touches a key that will insert it. AccessibilityUtils.getInstance().setAutoCorrection(suggestedWords, diff --git a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java index 8ba32ff76..70cb2b285 100644 --- a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java +++ b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java @@ -19,6 +19,7 @@ package com.android.inputmethod.latin.settings; import android.content.res.Resources; import com.android.inputmethod.keyboard.internal.KeySpecParser; +import com.android.inputmethod.keyboard.internal.MoreKeySpec; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.R; @@ -55,7 +56,7 @@ public final class SpacingAndPunctuations { res.getString(R.string.symbols_word_connectors)); mSortedWordSeparators = StringUtils.toSortedCodePointArray( res.getString(R.string.symbols_word_separators)); - final String[] suggestPuncsSpec = KeySpecParser.splitKeySpecs(res.getString( + final String[] suggestPuncsSpec = MoreKeySpec.splitKeySpecs(res.getString( R.string.suggested_punctuations)); mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec); mSentenceSeparator = res.getInteger(R.integer.sentence_separator); diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java index 5ed42ab00..da084e1e9 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java @@ -28,7 +28,6 @@ import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.support.v4.view.GravityCompat; import android.support.v4.view.ViewCompat; import android.text.Spannable; import android.text.SpannableString; @@ -45,6 +44,7 @@ import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.inputmethod.compat.TextViewCompatUtils; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SuggestedWords; @@ -492,7 +492,24 @@ final class SuggestionStripLayoutHelper { hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); } - private static void setLayoutWeight(final View v, final float weight, final int height) { + public void layoutImportantNotice(final View importantNoticeStrip, final int stripWidth) { + final Resources res = importantNoticeStrip.getResources(); + final Drawable infoIcon = res.getDrawable(R.drawable.sym_keyboard_info_holo_dark); + final Drawable moreIcon = res.getDrawable(R.drawable.sym_keyboard_more_holo_dark); + final int width = stripWidth - infoIcon.getIntrinsicWidth() - moreIcon.getIntrinsicWidth(); + final TextView titleView = (TextView)importantNoticeStrip.findViewById( + R.id.important_notice_title); + titleView.setTextColor(mColorAutoCorrect); + TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds( + titleView, infoIcon, null, moreIcon, null); + final CharSequence importantNoticeTitle = res.getText(R.string.important_notice_title); + titleView.setTextScaleX(1.0f); // Reset textScaleX. + final float titleScaleX = getTextScaleX(importantNoticeTitle, width, titleView.getPaint()); + titleView.setText(importantNoticeTitle); + titleView.setTextScaleX(titleScaleX); + } + + static void setLayoutWeight(final View v, final float weight, final int height) { final ViewGroup.LayoutParams lp = v.getLayoutParams(); if (lp instanceof LinearLayout.LayoutParams) { final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp; diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index 22804fce1..fe95d6781 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -54,6 +54,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick OnLongClickListener { public interface Listener { public void addWordToUserDictionary(String word); + public void showImportantNoticeContents(); public void pickSuggestionManually(int index, SuggestedWordInfo word); } @@ -62,6 +63,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick private final ViewGroup mSuggestionsStrip; private final ViewGroup mAddToDictionaryStrip; + private final View mImportantNoticeStrip; MainKeyboardView mMainKeyboardView; private final View mMoreSuggestionsContainer; @@ -81,10 +83,13 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick private static class StripVisibilityGroup { private final View mSuggestionsStrip; private final View mAddToDictionaryStrip; + private final View mImportantNoticeStrip; - public StripVisibilityGroup(final View suggestionsStrip, final View addToDictionaryStrip) { + public StripVisibilityGroup(final View suggestionsStrip, final View addToDictionaryStrip, + final View importantNoticeStrip) { mSuggestionsStrip = suggestionsStrip; mAddToDictionaryStrip = addToDictionaryStrip; + mImportantNoticeStrip = importantNoticeStrip; showSuggestionsStrip(); } @@ -93,16 +98,25 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick : ViewCompat.LAYOUT_DIRECTION_LTR; ViewCompat.setLayoutDirection(mSuggestionsStrip, layoutDirection); ViewCompat.setLayoutDirection(mAddToDictionaryStrip, layoutDirection); + ViewCompat.setLayoutDirection(mImportantNoticeStrip, layoutDirection); } public void showSuggestionsStrip() { mSuggestionsStrip.setVisibility(VISIBLE); mAddToDictionaryStrip.setVisibility(INVISIBLE); + mImportantNoticeStrip.setVisibility(INVISIBLE); } public void showAddToDictionaryStrip() { mSuggestionsStrip.setVisibility(INVISIBLE); mAddToDictionaryStrip.setVisibility(VISIBLE); + mImportantNoticeStrip.setVisibility(INVISIBLE); + } + + public void showImportantNoticeStrip() { + mSuggestionsStrip.setVisibility(INVISIBLE); + mAddToDictionaryStrip.setVisibility(INVISIBLE); + mImportantNoticeStrip.setVisibility(VISIBLE); } public boolean isShowingAddToDictionaryStrip() { @@ -128,7 +142,9 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip); mAddToDictionaryStrip = (ViewGroup)findViewById(R.id.add_to_dictionary_strip); - mStripVisibilityGroup = new StripVisibilityGroup(mSuggestionsStrip, mAddToDictionaryStrip); + mImportantNoticeStrip = findViewById(R.id.important_notice_strip); + mStripVisibilityGroup = new StripVisibilityGroup(mSuggestionsStrip, mAddToDictionaryStrip, + mImportantNoticeStrip); for (int pos = 0; pos < SuggestedWords.MAX_SUGGESTIONS; pos++) { final TextView word = new TextView(context, null, R.attr.suggestionWordStyle); @@ -176,6 +192,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords); } + mStripVisibilityGroup.showSuggestionsStrip(); } public int setMoreSuggestionsHeight(final int remainingHeight) { @@ -203,6 +220,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return false; } + public void showImportantNoticeTitle() { + mLayoutHelper.layoutImportantNotice(mImportantNoticeStrip, getWidth()); + mStripVisibilityGroup.showImportantNoticeStrip(); + mImportantNoticeStrip.setOnClickListener(this); + } + public void clear() { mSuggestionsStrip.removeAllViews(); removeAllDebugInfoViews(); @@ -360,6 +383,10 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick @Override public void onClick(final View view) { + if (view == mImportantNoticeStrip) { + mListener.showImportantNoticeContents(); + return; + } final Object tag = view.getTag(); // {@link String} tag is set at {@link #showAddToDictionaryHint(String,CharSequence)}. if (tag instanceof String) { diff --git a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java index 36b927eea..b18a1d83b 100644 --- a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java @@ -17,6 +17,7 @@ package com.android.inputmethod.latin.utils; import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.Constants; import java.util.ArrayList; @@ -57,9 +58,9 @@ public final class CsvUtils { // Note that none of these characters match high or low surrogate characters, so we need not // take care of matching by code point. - private static final char COMMA = ','; - private static final char SPACE = ' '; - private static final char QUOTE = '"'; + private static final char COMMA = Constants.CODE_COMMA; + private static final char SPACE = Constants.CODE_SPACE; + private static final char QUOTE = Constants.CODE_DOUBLE_QUOTE; @SuppressWarnings("serial") public static class CsvParseException extends RuntimeException { diff --git a/java/src/com/android/inputmethod/latin/utils/WordProperty.java b/java/src/com/android/inputmethod/latin/utils/WordProperty.java index d6c0f900a..ba9b114b0 100644 --- a/java/src/com/android/inputmethod/latin/utils/WordProperty.java +++ b/java/src/com/android/inputmethod/latin/utils/WordProperty.java @@ -32,15 +32,30 @@ public class WordProperty { public final boolean mIsBlacklisted; public final boolean mHasBigrams; public final boolean mHasShortcuts; - public final int mProbability; - // mTimestamp, mLevel and mCount are historical info. These values are depend on the - // implementation in native code; thus, we must not use them and have any assumptions about - // them except for tests. - public final int mTimestamp; - public final int mLevel; - public final int mCount; + public final ProbabilityInfo mProbabilityInfo; + public final ArrayList<WeightedString> mBigramTargets = CollectionUtils.newArrayList(); + public final ArrayList<ProbabilityInfo> mBigramProbabilityInfo = CollectionUtils.newArrayList(); public final ArrayList<WeightedString> mShortcutTargets = CollectionUtils.newArrayList(); + // TODO: Use this kind of Probability class for dictionary read/write code under the makedict + // package. + public static final class ProbabilityInfo { + public final int mProbability; + // wTimestamp, mLevel and mCount are historical info. These values are depend on the + // implementation in native code; thus, we must not use them and have any assumptions about + // them except for tests. + public final int mTimestamp; + public final int mLevel; + public final int mCount; + + public ProbabilityInfo(final int[] probabilityInfo) { + mProbability = probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_PROBABILITY_INDEX]; + mTimestamp = probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX]; + mLevel = probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_LEVEL_INDEX]; + mCount = probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_COUNT_INDEX]; + } + } + private static int getCodePointCount(final int[] codePoints) { for (int i = 0; i < codePoints.length; i++) { if (codePoints[i] == 0) { @@ -53,18 +68,29 @@ public class WordProperty { // This represents invalid word when the probability is BinaryDictionary.NOT_A_PROBABILITY. public WordProperty(final int[] codePoints, final boolean isNotAWord, final boolean isBlacklisted, final boolean hasBigram, - final boolean hasShortcuts, final int probability, final int timestamp, - final int level, final int count, final ArrayList<int[]> shortcutTargets, + final boolean hasShortcuts, final int[] probabilityInfo, + final ArrayList<int[]> bigramTargets, final ArrayList<int[]> bigramProbabilityInfo, + final ArrayList<int[]> shortcutTargets, final ArrayList<Integer> shortcutProbabilities) { mCodePoints = new String(codePoints, 0 /* offset */, getCodePointCount(codePoints)); mIsNotAWord = isNotAWord; mIsBlacklisted = isBlacklisted; mHasBigrams = hasBigram; mHasShortcuts = hasShortcuts; - mProbability = probability; - mTimestamp = timestamp; - mLevel = level; - mCount = count; + mProbabilityInfo = new ProbabilityInfo(probabilityInfo); + + final int bigramTargetCount = bigramTargets.size(); + for (int i = 0; i < bigramTargetCount; i++) { + final int[] bigramTargetCodePointArray = bigramTargets.get(i); + final String bigramTargetString = new String(bigramTargetCodePointArray, + 0 /* offset */, getCodePointCount(bigramTargetCodePointArray)); + final ProbabilityInfo bigramProbability = + new ProbabilityInfo(bigramProbabilityInfo.get(i)); + mBigramTargets.add( + new WeightedString(bigramTargetString, bigramProbability.mProbability)); + mBigramProbabilityInfo.add(bigramProbability); + } + final int shortcutTargetCount = shortcutTargets.size(); for (int i = 0; i < shortcutTargetCount; i++) { final int[] shortcutTargetCodePointArray = shortcutTargets.get(i); @@ -77,6 +103,6 @@ public class WordProperty { @UsedForTesting public boolean isValid() { - return mProbability != BinaryDictionary.NOT_A_PROBABILITY; + return mProbabilityInfo.mProbability != BinaryDictionary.NOT_A_PROBABILITY; } }
\ No newline at end of file diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp index 16a3fe825..c919ebd91 100644 --- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp +++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp @@ -260,18 +260,39 @@ static jint latinime_BinaryDictionary_getBigramProbability(JNIEnv *env, jclass c word1Length); } +// Method to iterate all words in the dictionary for makedict. +// If token is 0, this method newly starts iterating the dictionary. This method returns 0 when +// the dictionary does not have a next word. +static jint latinime_BinaryDictionary_getNextWord(JNIEnv *env, jclass clazz, + jlong dict, jint token, jintArray outCodePoints) { + Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict); + if (!dictionary) return 0; + const jsize outCodePointsLength = env->GetArrayLength(outCodePoints); + if (outCodePointsLength != MAX_WORD_LENGTH) { + AKLOGE("Invalid outCodePointsLength: %d", outCodePointsLength); + ASSERT(false); + return 0; + } + int wordCodePoints[outCodePointsLength]; + memset(wordCodePoints, 0, sizeof(wordCodePoints)); + const int nextToken = dictionary->getNextWordAndNextToken(token, wordCodePoints); + env->SetIntArrayRegion(outCodePoints, 0, outCodePointsLength, wordCodePoints); + return nextToken; +} + static void latinime_BinaryDictionary_getWordProperty(JNIEnv *env, jclass clazz, jlong dict, jintArray word, jintArray outCodePoints, jbooleanArray outFlags, - jintArray outProbability, jintArray outHistoricalInfo, jobject outShortcutTargets, - jobject outShortcutProbabilities) { + jintArray outProbabilityInfo, jobject outBigramTargets, jobject outBigramProbabilityInfo, + jobject outShortcutTargets, jobject outShortcutProbabilities) { Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict); if (!dictionary) return; const jsize wordLength = env->GetArrayLength(word); int wordCodePoints[wordLength]; env->GetIntArrayRegion(word, 0, wordLength, wordCodePoints); const WordProperty wordProperty = dictionary->getWordProperty(wordCodePoints, wordLength); - wordProperty.outputProperties(env, outCodePoints, outFlags, outProbability, - outHistoricalInfo, outShortcutTargets, outShortcutProbabilities); + wordProperty.outputProperties(env, outCodePoints, outFlags, outProbabilityInfo, + outBigramTargets, outBigramProbabilityInfo, outShortcutTargets, + outShortcutProbabilities); } static jfloat latinime_BinaryDictionary_calcNormalizedScore(JNIEnv *env, jclass clazz, @@ -521,10 +542,16 @@ static const JNINativeMethod sMethods[] = { }, { const_cast<char *>("getWordPropertyNative"), - const_cast<char *>("(J[I[I[Z[I[ILjava/util/ArrayList;Ljava/util/ArrayList;)V"), + const_cast<char *>("(J[I[I[Z[ILjava/util/ArrayList;Ljava/util/ArrayList;" + "Ljava/util/ArrayList;Ljava/util/ArrayList;)V"), reinterpret_cast<void *>(latinime_BinaryDictionary_getWordProperty) }, { + const_cast<char *>("getNextWordNative"), + const_cast<char *>("(JI[I)I"), + reinterpret_cast<void *>(latinime_BinaryDictionary_getNextWord) + }, + { const_cast<char *>("calcNormalizedScoreNative"), const_cast<char *>("([I[II)F"), reinterpret_cast<void *>(latinime_BinaryDictionary_calcNormalizedScore) diff --git a/native/jni/src/suggest/core/dictionary/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp index 16b1a56b1..9b71eff7a 100644 --- a/native/jni/src/suggest/core/dictionary/dictionary.cpp +++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp @@ -150,6 +150,12 @@ const WordProperty Dictionary::getWordProperty(const int *const codePoints, codePoints, codePointCount); } +int Dictionary::getNextWordAndNextToken(const int token, int *const outCodePoints) { + TimeKeeper::setCurrentTime(); + return mDictionaryStructureWithBufferPolicy.get()->getNextWordAndNextToken( + token, outCodePoints); +} + void Dictionary::logDictionaryInfo(JNIEnv *const env) const { int dictionaryIdCodePointBuffer[HEADER_ATTRIBUTE_BUFFER_SIZE]; int versionStringCodePointBuffer[HEADER_ATTRIBUTE_BUFFER_SIZE]; diff --git a/native/jni/src/suggest/core/dictionary/dictionary.h b/native/jni/src/suggest/core/dictionary/dictionary.h index 4a468f3df..0a413cb52 100644 --- a/native/jni/src/suggest/core/dictionary/dictionary.h +++ b/native/jni/src/suggest/core/dictionary/dictionary.h @@ -96,6 +96,11 @@ class Dictionary { const WordProperty getWordProperty(const int *const codePoints, const int codePointCount); + // Method to iterate all words in the dictionary. + // The returned token has to be used to get the next word. If token is 0, this method newly + // starts iterating the dictionary. + int getNextWordAndNextToken(const int token, int *const outCodePoints); + const DictionaryStructureWithBufferPolicy *getDictionaryStructurePolicy() const { return mDictionaryStructureWithBufferPolicy.get(); } diff --git a/native/jni/src/suggest/core/dictionary/word_property.cpp b/native/jni/src/suggest/core/dictionary/word_property.cpp index ed32bde8b..288e6b05e 100644 --- a/native/jni/src/suggest/core/dictionary/word_property.cpp +++ b/native/jni/src/suggest/core/dictionary/word_property.cpp @@ -19,29 +19,53 @@ namespace latinime { void WordProperty::outputProperties(JNIEnv *const env, jintArray outCodePoints, - jbooleanArray outFlags, jintArray outProbability, jintArray outHistoricalInfo, - jobject outShortcutTargets, jobject outShortcutProbabilities) const { + jbooleanArray outFlags, jintArray outProbabilityInfo, jobject outBigramTargets, + jobject outBigramProbabilities, jobject outShortcutTargets, + jobject outShortcutProbabilities) const { env->SetIntArrayRegion(outCodePoints, 0 /* start */, mCodePoints.size(), &mCodePoints[0]); jboolean flags[] = {mIsNotAWord, mIsBlacklisted, mHasBigrams, mHasShortcuts}; env->SetBooleanArrayRegion(outFlags, 0 /* start */, NELEMS(flags), flags); - env->SetIntArrayRegion(outProbability, 0 /* start */, 1 /* len */, &mProbability); - int historicalInfo[] = {mTimestamp, mLevel, mCount}; - env->SetIntArrayRegion(outHistoricalInfo, 0 /* start */, NELEMS(historicalInfo), - historicalInfo); + int probabilityInfo[] = {mProbability, mTimestamp, mLevel, mCount}; + env->SetIntArrayRegion(outProbabilityInfo, 0 /* start */, NELEMS(probabilityInfo), + probabilityInfo); jclass integerClass = env->FindClass("java/lang/Integer"); jmethodID intToIntegerConstructorId = env->GetMethodID(integerClass, "<init>", "(I)V"); jclass arrayListClass = env->FindClass("java/util/ArrayList"); jmethodID addMethodId = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z"); - const int shortcutTargetCount = mShortcutTargets.size(); + + // Output bigrams. + const int bigramCount = mBigrams.size(); + for (int i = 0; i < bigramCount; ++i) { + const BigramProperty *const bigramProperty = &mBigrams[i]; + const std::vector<int> *const word1CodePoints = bigramProperty->getTargetCodePoints(); + jintArray bigramWord1CodePointArray = env->NewIntArray(word1CodePoints->size()); + env->SetIntArrayRegion(bigramWord1CodePointArray, 0 /* start */, + word1CodePoints->size(), &word1CodePoints->at(0)); + env->CallVoidMethod(outBigramTargets, addMethodId, bigramWord1CodePointArray); + env->DeleteLocalRef(bigramWord1CodePointArray); + + int bigramProbabilityInfo[] = {bigramProperty->getProbability(), + bigramProperty->getTimestamp(), bigramProperty->getLevel(), + bigramProperty->getCount()}; + jintArray bigramProbabilityInfoArray = env->NewIntArray(NELEMS(bigramProbabilityInfo)); + env->SetIntArrayRegion(bigramProbabilityInfoArray, 0 /* start */, + NELEMS(bigramProbabilityInfo), bigramProbabilityInfo); + env->CallVoidMethod(outBigramProbabilities, addMethodId, bigramProbabilityInfoArray); + env->DeleteLocalRef(bigramProbabilityInfoArray); + } + + // Output shortcuts. + const int shortcutTargetCount = mShortcuts.size(); for (int i = 0; i < shortcutTargetCount; ++i) { - jintArray shortcutTargetCodePointArray = env->NewIntArray(mShortcutTargets[i].size()); + const std::vector<int> *const targetCodePoints = mShortcuts[i].getTargetCodePoints(); + jintArray shortcutTargetCodePointArray = env->NewIntArray(targetCodePoints->size()); env->SetIntArrayRegion(shortcutTargetCodePointArray, 0 /* start */, - mShortcutTargets[i].size(), &mShortcutTargets[i][0]); + targetCodePoints->size(), &targetCodePoints->at(0)); env->CallVoidMethod(outShortcutTargets, addMethodId, shortcutTargetCodePointArray); env->DeleteLocalRef(shortcutTargetCodePointArray); jobject integerProbability = env->NewObject(integerClass, intToIntegerConstructorId, - mShortcutProbabilities[i]); + mShortcuts[i].getProbability()); env->CallVoidMethod(outShortcutProbabilities, addMethodId, integerProbability); env->DeleteLocalRef(integerProbability); } diff --git a/native/jni/src/suggest/core/dictionary/word_property.h b/native/jni/src/suggest/core/dictionary/word_property.h index dcac8536a..40b1a91a4 100644 --- a/native/jni/src/suggest/core/dictionary/word_property.h +++ b/native/jni/src/suggest/core/dictionary/word_property.h @@ -28,27 +28,78 @@ namespace latinime { // This class is used for returning information belonging to a word to java side. class WordProperty { public: - // TODO: Add bigram information. + class BigramProperty { + public: + BigramProperty(const std::vector<int> *const targetCodePoints, + const int probability, const int timestamp, const int level, const int count) + : mTargetCodePoints(*targetCodePoints), mProbability(probability), + mTimestamp(timestamp), mLevel(level), mCount(count) {} + + const std::vector<int> *getTargetCodePoints() const { + return &mTargetCodePoints; + } + + int getProbability() const { + return mProbability; + } + + int getTimestamp() const { + return mTimestamp; + } + + int getLevel() const { + return mLevel; + } + + int getCount() const { + return mCount; + } + + private: + std::vector<int> mTargetCodePoints; + int mProbability; + int mTimestamp; + int mLevel; + int mCount; + }; + + class ShortcutProperty { + public: + ShortcutProperty(const std::vector<int> *const targetCodePoints, const int probability) + : mTargetCodePoints(*targetCodePoints), mProbability(probability) {} + + const std::vector<int> *getTargetCodePoints() const { + return &mTargetCodePoints; + } + + int getProbability() const { + return mProbability; + } + + private: + std::vector<int> mTargetCodePoints; + int mProbability; + }; + // Invalid word. WordProperty() : mCodePoints(), mIsNotAWord(false), mIsBlacklisted(false), mHasBigrams(false), mHasShortcuts(false), mProbability(NOT_A_PROBABILITY), - mTimestamp(0), mLevel(0), mCount(0), mShortcutTargets(), mShortcutProbabilities() {} + mTimestamp(0), mLevel(0), mCount(0), mBigrams(), mShortcuts() {} WordProperty(const std::vector<int> *const codePoints, const bool isNotAWord, const bool isBlacklisted, const bool hasBigrams, const bool hasShortcuts, const int probability, const int timestamp, - const int level, const int count, - const std::vector<std::vector<int> > *const shortcutTargets, - const std::vector<int> *const shortcutProbabilities) + const int level, const int count, const std::vector<BigramProperty> *const bigrams, + const std::vector<ShortcutProperty> *const shortcuts) : mCodePoints(*codePoints), mIsNotAWord(isNotAWord), mIsBlacklisted(isBlacklisted), mHasBigrams(hasBigrams), mHasShortcuts(hasShortcuts), mProbability(probability), - mTimestamp(timestamp), mLevel(level), mCount(count), - mShortcutTargets(*shortcutTargets), mShortcutProbabilities(*shortcutProbabilities) {} + mTimestamp(timestamp), mLevel(level), mCount(count), mBigrams(*bigrams), + mShortcuts(*shortcuts) {} void outputProperties(JNIEnv *const env, jintArray outCodePoints, jbooleanArray outFlags, - jintArray outProbability, jintArray outHistoricalInfo, jobject outShortcutTargets, - jobject outShortcutProbabilities) const; + jintArray outProbabilityInfo, jobject outBigramTargets, jobject outBigramProbabilities, + jobject outShortcutTargets, jobject outShortcutProbabilities) const; private: DISALLOW_ASSIGNMENT_OPERATOR(WordProperty); @@ -63,9 +114,8 @@ class WordProperty { int mTimestamp; int mLevel; int mCount; - // Shortcut - std::vector<std::vector<int> > mShortcutTargets; - std::vector<int> mShortcutProbabilities; + std::vector<BigramProperty> mBigrams; + std::vector<ShortcutProperty> mShortcuts; }; } // namespace latinime #endif // LATINIME_WORD_PROPERTY_H diff --git a/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h b/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h index b878984f1..784419586 100644 --- a/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h +++ b/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h @@ -95,6 +95,11 @@ class DictionaryStructureWithBufferPolicy { virtual const WordProperty getWordProperty(const int *const codePonts, const int codePointCount) const = 0; + // Method to iterate all words in the dictionary. + // The returned token has to be used to get the next word. If token is 0, this method newly + // starts iterating the dictionary. + virtual int getNextWordAndNextToken(const int token, int *const outCodePoints) = 0; + protected: DictionaryStructureWithBufferPolicy() {} diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h index 67d615e86..319c81569 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h @@ -129,6 +129,11 @@ class PatriciaTriePolicy : public DictionaryStructureWithBufferPolicy { return WordProperty(); } + int getNextWordAndNextToken(const int token, int *const outCodePoints) { + // getNextWordAndNextToken is not supported. + return 0; + } + private: DISALLOW_IMPLICIT_CONSTRUCTORS(PatriciaTriePolicy); diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp index 0b067e127..1c420e070 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp @@ -332,9 +332,44 @@ const WordProperty Ver4PatriciaTriePolicy::getWordProperty(const int *const code mBuffers.get()->getProbabilityDictContent()->getProbabilityEntry( ptNodeParams.getTerminalId()); const HistoricalInfo *const historicalInfo = probabilityEntry.getHistoricalInfo(); + // Fetch bigram information. + std::vector<WordProperty::BigramProperty> bigrams; + const int bigramListPos = getBigramsPositionOfPtNode(ptNodePos); + if (bigramListPos != NOT_A_DICT_POS) { + int bigramWord1CodePoints[MAX_WORD_LENGTH]; + const BigramDictContent *const bigramDictContent = mBuffers.get()->getBigramDictContent(); + const TerminalPositionLookupTable *const terminalPositionLookupTable = + mBuffers.get()->getTerminalPositionLookupTable(); + bool hasNext = true; + int readingPos = bigramListPos; + while (hasNext) { + const BigramEntry bigramEntry = + bigramDictContent->getBigramEntryAndAdvancePosition(&readingPos); + hasNext = bigramEntry.hasNext(); + const int word1TerminalId = bigramEntry.getTargetTerminalId(); + const int word1TerminalPtNodePos = + terminalPositionLookupTable->getTerminalPtNodePosition(word1TerminalId); + if (word1TerminalPtNodePos == NOT_A_DICT_POS) { + continue; + } + // Word (unigram) probability + int word1Probability = NOT_A_PROBABILITY; + const int codePointCount = getCodePointsAndProbabilityAndReturnCodePointCount( + word1TerminalPtNodePos, MAX_WORD_LENGTH, bigramWord1CodePoints, + &word1Probability); + std::vector<int> word1(bigramWord1CodePoints, + bigramWord1CodePoints + codePointCount); + const HistoricalInfo *const historicalInfo = bigramEntry.getHistoricalInfo(); + const int probability = bigramEntry.hasHistoricalInfo() ? + ForgettingCurveUtils::decodeProbability(bigramEntry.getHistoricalInfo()) : + bigramEntry.getProbability(); + bigrams.push_back(WordProperty::BigramProperty(&word1, probability, + historicalInfo->getTimeStamp(), historicalInfo->getLevel(), + historicalInfo->getCount())); + } + } // Fetch shortcut information. - std::vector<std::vector<int> > shortcutTargets; - std::vector<int> shortcutProbabilities; + std::vector<WordProperty::ShortcutProperty> shortcuts; int shortcutPos = getShortcutPositionOfPtNode(ptNodePos); if (shortcutPos != NOT_A_DICT_POS) { int shortcutTarget[MAX_WORD_LENGTH]; @@ -347,15 +382,20 @@ const WordProperty Ver4PatriciaTriePolicy::getWordProperty(const int *const code shortcutDictContent->getShortcutEntryAndAdvancePosition(MAX_WORD_LENGTH, shortcutTarget, &shortcutTargetLength, &shortcutProbability, &hasNext, &shortcutPos); std::vector<int> target(shortcutTarget, shortcutTarget + shortcutTargetLength); - shortcutTargets.push_back(target); - shortcutProbabilities.push_back(shortcutProbability); + shortcuts.push_back(WordProperty::ShortcutProperty(&target, shortcutProbability)); } } return WordProperty(&codePointVector, ptNodeParams.isNotAWord(), ptNodeParams.isBlacklisted(), ptNodeParams.hasBigrams(), ptNodeParams.hasShortcutTargets(), ptNodeParams.getProbability(), historicalInfo->getTimeStamp(), historicalInfo->getLevel(), - historicalInfo->getCount(), &shortcutTargets, &shortcutProbabilities); + historicalInfo->getCount(), &bigrams, &shortcuts); +} + +int Ver4PatriciaTriePolicy::getNextWordAndNextToken(const int token, + int *const outCodePoints) { + // TODO: Implement. + return 0; } } // namespace latinime diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h index a43bd0eca..1bcd4ceea 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h @@ -109,6 +109,8 @@ class Ver4PatriciaTriePolicy : public DictionaryStructureWithBufferPolicy { const WordProperty getWordProperty(const int *const codePoints, const int codePointCount) const; + int getNextWordAndNextToken(const int token, int *const outCodePoints); + private: DISALLOW_IMPLICIT_CONSTRUCTORS(Ver4PatriciaTriePolicy); diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecSplitTests.java index 7b7f1758c..5f301a839 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecSplitTests.java @@ -31,7 +31,7 @@ import java.util.Arrays; import java.util.Locale; @MediumTest -public class KeySpecParserSplitTests extends InstrumentationTestCase { +public class MoreKeySpecSplitTests extends InstrumentationTestCase { private static final Locale TEST_LOCALE = Locale.ENGLISH; final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); @@ -93,7 +93,7 @@ public class KeySpecParserSplitTests extends InstrumentationTestCase { private void assertTextArray(final String message, final String value, final String ... expectedArray) { final String resolvedActual = mTextsSet.resolveTextReference(value); - final String[] actual = KeySpecParser.splitKeySpecs(resolvedActual); + final String[] actual = MoreKeySpec.splitKeySpecs(resolvedActual); final String[] expected = (expectedArray.length == 0) ? null : expectedArray; assertArrayEquals(message, expected, actual); } diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecTests.java index 0388435eb..538ba2ccf 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecTests.java @@ -32,7 +32,7 @@ import java.util.Arrays; import java.util.Locale; @SmallTest -public class KeySpecParserTests extends AndroidTestCase { +public class MoreKeySpecTests extends AndroidTestCase { private final static Locale TEST_LOCALE = Locale.ENGLISH; final KeyboardCodesSet mCodesSet = new KeyboardCodesSet(); final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); @@ -71,8 +71,9 @@ public class KeySpecParserTests extends AndroidTestCase { mSettingsIconId = KeySpecParser.getIconId(ICON_SETTINGS); } - private void assertParser(String message, String moreKeySpec, String expectedLabel, - String expectedOutputText, int expectedIcon, int expectedCode) { + private void assertParser(final String message, final String moreKeySpec, + final String expectedLabel, final String expectedOutputText, final int expectedIcon, + final int expectedCode) { final String labelResolved = mTextsSet.resolveTextReference(moreKeySpec); final MoreKeySpec spec = new MoreKeySpec(labelResolved, false /* needsToUpperCase */, Locale.US, mCodesSet); @@ -86,8 +87,9 @@ public class KeySpecParserTests extends AndroidTestCase { Constants.printableCode(spec.mCode)); } - private void assertParserError(String message, String moreKeySpec, String expectedLabel, - String expectedOutputText, int expectedIcon, int expectedCode) { + private void assertParserError(final String message, final String moreKeySpec, + final String expectedLabel, final String expectedOutputText, final int expectedIcon, + final int expectedCode) { try { assertParser(message, moreKeySpec, expectedLabel, expectedOutputText, expectedIcon, expectedCode); @@ -339,7 +341,8 @@ public class KeySpecParserTests extends AndroidTestCase { null, null, mSettingsIconId, mCodeSettings); } - private static void assertArrayEquals(String message, Object[] expected, Object[] actual) { + private static void assertArrayEquals(final String message, final Object[] expected, + final Object[] actual) { if (expected == actual) { return; } @@ -357,10 +360,9 @@ public class KeySpecParserTests extends AndroidTestCase { } } - private static void assertInsertAdditionalMoreKeys(String message, String[] moreKeys, - String[] additionalMoreKeys, String[] expected) { - final String[] actual = - KeySpecParser.insertAdditionalMoreKeys( moreKeys, additionalMoreKeys); + private static void assertInsertAdditionalMoreKeys(final String message, + final String[] moreKeys, final String[] additionalMoreKeys, final String[] expected) { + final String[] actual = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys); assertArrayEquals(message, expected, actual); } @@ -584,10 +586,10 @@ public class KeySpecParserTests extends AndroidTestCase { private static final String AUTO_COLUMN_ORDER = "!autoColumnOrder!"; private static final String FIXED_COLUMN_ORDER = "!fixedColumnOrder!"; - private static void assertGetBooleanValue(String message, String key, String[] moreKeys, - String[] expected, boolean expectedValue) { + private static void assertGetBooleanValue(final String message, final String key, + final String[] moreKeys, final String[] expected, final boolean expectedValue) { final String[] actual = Arrays.copyOf(moreKeys, moreKeys.length); - final boolean actualValue = KeySpecParser.getBooleanValue(actual, key); + final boolean actualValue = MoreKeySpec.getBooleanValue(actual, key); assertEquals(message + " [value]", expectedValue, actualValue); assertArrayEquals(message, expected, actual); } @@ -622,10 +624,11 @@ public class KeySpecParserTests extends AndroidTestCase { "a", null, "b", NEEDS_DIVIDER, "!HASLABEL!", "d" }, true); } - private static void assertGetIntValue(String message, String key, int defaultValue, - String[] moreKeys, String[] expected, int expectedValue) { + private static void assertGetIntValue(final String message, final String key, + final int defaultValue, final String[] moreKeys, final String[] expected, + final int expectedValue) { final String[] actual = Arrays.copyOf(moreKeys, moreKeys.length); - final int actualValue = KeySpecParser.getIntValue(actual, key, defaultValue); + final int actualValue = MoreKeySpec.getIntValue(actual, key, defaultValue); assertEquals(message + " [value]", expectedValue, actualValue); assertArrayEquals(message, expected, actual); } diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java index 844fcbbd9..e39b46f94 100644 --- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java +++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java @@ -871,14 +871,15 @@ public class BinaryDictionaryTests extends AndroidTestCase { } } - public void testGetUnigramProperties() { - testGetUnigramProperties(FormatSpec.VERSION4); + public void testGetWordProperties() { + testGetWordProperties(FormatSpec.VERSION4); } - private void testGetUnigramProperties(final int formatVersion) { + private void testGetWordProperties(final int formatVersion) { final long seed = System.currentTimeMillis(); final Random random = new Random(seed); - final int ITERATION_COUNT = 1000; + final int UNIGRAM_COUNT = 1000; + final int BIGRAM_COUNT = 1000; final int codePointSetSize = 20; final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random); @@ -892,10 +893,16 @@ public class BinaryDictionaryTests extends AndroidTestCase { 0 /* offset */, dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); - final WordProperty invalidUnigramProperty = binaryDictionary.getWordProperty("dummyWord"); - assertFalse(invalidUnigramProperty.isValid()); + final WordProperty invalidWordProperty = binaryDictionary.getWordProperty("dummyWord"); + assertFalse(invalidWordProperty.isValid()); + + final ArrayList<String> words = new ArrayList<String>(); + final HashMap<String, Integer> wordProbabilities = new HashMap<String, Integer>(); + final HashMap<String, HashSet<String>> bigrams = new HashMap<String, HashSet<String>>(); + final HashMap<Pair<String, String>, Integer> bigramProbabilities = + new HashMap<Pair<String, String>, Integer>(); - for (int i = 0; i < ITERATION_COUNT; i++) { + for (int i = 0; i < UNIGRAM_COUNT; i++) { final String word = CodePointUtils.generateWord(random, codePointSet); final int unigramProbability = random.nextInt(0xFF); final boolean isNotAWord = random.nextBoolean(); @@ -904,6 +911,11 @@ public class BinaryDictionaryTests extends AndroidTestCase { binaryDictionary.addUnigramWord(word, unigramProbability, null /* shortcutTarget */, BinaryDictionary.NOT_A_PROBABILITY, isNotAWord, isBlacklisted, BinaryDictionary.NOT_A_VALID_TIMESTAMP); + if (binaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) { + binaryDictionary.flushWithGC(); + } + words.add(word); + wordProbabilities.put(word, unigramProbability); final WordProperty unigramProperty = binaryDictionary.getWordProperty(word); assertEquals(word, unigramProperty.mCodePoints); assertTrue(unigramProperty.isValid()); @@ -911,9 +923,52 @@ public class BinaryDictionaryTests extends AndroidTestCase { assertEquals(isBlacklisted, unigramProperty.mIsBlacklisted); assertEquals(false, unigramProperty.mHasBigrams); assertEquals(false, unigramProperty.mHasShortcuts); - assertEquals(unigramProbability, unigramProperty.mProbability); + assertEquals(unigramProbability, unigramProperty.mProbabilityInfo.mProbability); assertTrue(unigramProperty.mShortcutTargets.isEmpty()); } + + for (int i = 0; i < BIGRAM_COUNT; i++) { + final int word0Index = random.nextInt(wordProbabilities.size()); + final int word1Index = random.nextInt(wordProbabilities.size()); + if (word0Index == word1Index) { + continue; + } + final String word0 = words.get(word0Index); + final String word1 = words.get(word1Index); + final int bigramProbability = random.nextInt(0xF); + binaryDictionary.addBigramWords(word0, word1, bigramProbability, + BinaryDictionary.NOT_A_VALID_TIMESTAMP); + if (binaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) { + binaryDictionary.flushWithGC(); + } + if (!bigrams.containsKey(word0)) { + final HashSet<String> bigramWord1s = new HashSet<String>(); + bigrams.put(word0, bigramWord1s); + } + bigrams.get(word0).add(word1); + bigramProbabilities.put(new Pair<String, String>(word0, word1), bigramProbability); + } + + for (int i = 0; i < words.size(); i++) { + final String word0 = words.get(i); + if (!bigrams.containsKey(word0)) { + continue; + } + final HashSet<String> bigramWord1s = bigrams.get(word0); + final WordProperty unigramProperty = binaryDictionary.getWordProperty(word0); + assertEquals(bigramWord1s.size(), unigramProperty.mBigramTargets.size()); + assertEquals(unigramProperty.mBigramTargets.size(), + unigramProperty.mBigramProbabilityInfo.size()); + for (int j = 0; j < unigramProperty.mBigramTargets.size(); j++) { + final String word1 = unigramProperty.mBigramTargets.get(j).mWord; + assertTrue(bigramWord1s.contains(word1)); + final int probability = unigramProperty.mBigramTargets.get(j).mFrequency; + assertEquals((int)bigramProbabilities.get(new Pair<String, String>(word0, word1)), + probability); + assertEquals(unigramProperty.mBigramProbabilityInfo.get(j).mProbability, + probability); + } + } } public void testAddShortcuts() { @@ -936,28 +991,28 @@ public class BinaryDictionaryTests extends AndroidTestCase { binaryDictionary.addUnigramWord("aaa", unigramProbability, "zzz", shortcutProbability, false /* isNotAWord */, false /* isBlacklisted */, 0 /* timestamp */); - WordProperty unigramProperty = binaryDictionary.getWordProperty("aaa"); - assertEquals(1, unigramProperty.mShortcutTargets.size()); - assertEquals("zzz", unigramProperty.mShortcutTargets.get(0).mWord); - assertEquals(shortcutProbability, unigramProperty.mShortcutTargets.get(0).mFrequency); + WordProperty wordProperty = binaryDictionary.getWordProperty("aaa"); + assertEquals(1, wordProperty.mShortcutTargets.size()); + assertEquals("zzz", wordProperty.mShortcutTargets.get(0).mWord); + assertEquals(shortcutProbability, wordProperty.mShortcutTargets.get(0).mFrequency); final int updatedShortcutProbability = 2; binaryDictionary.addUnigramWord("aaa", unigramProbability, "zzz", updatedShortcutProbability, false /* isNotAWord */, false /* isBlacklisted */, 0 /* timestamp */); - unigramProperty = binaryDictionary.getWordProperty("aaa"); - assertEquals(1, unigramProperty.mShortcutTargets.size()); - assertEquals("zzz", unigramProperty.mShortcutTargets.get(0).mWord); + wordProperty = binaryDictionary.getWordProperty("aaa"); + assertEquals(1, wordProperty.mShortcutTargets.size()); + assertEquals("zzz", wordProperty.mShortcutTargets.get(0).mWord); assertEquals(updatedShortcutProbability, - unigramProperty.mShortcutTargets.get(0).mFrequency); + wordProperty.mShortcutTargets.get(0).mFrequency); binaryDictionary.addUnigramWord("aaa", unigramProbability, "yyy", shortcutProbability, false /* isNotAWord */, false /* isBlacklisted */, 0 /* timestamp */); final HashMap<String, Integer> shortcutTargets = new HashMap<String, Integer>(); shortcutTargets.put("zzz", updatedShortcutProbability); shortcutTargets.put("yyy", shortcutProbability); - unigramProperty = binaryDictionary.getWordProperty("aaa"); - assertEquals(2, unigramProperty.mShortcutTargets.size()); - for (WeightedString shortcutTarget : unigramProperty.mShortcutTargets) { + wordProperty = binaryDictionary.getWordProperty("aaa"); + assertEquals(2, wordProperty.mShortcutTargets.size()); + for (WeightedString shortcutTarget : wordProperty.mShortcutTargets) { assertTrue(shortcutTargets.containsKey(shortcutTarget.mWord)); assertEquals((int)shortcutTargets.get(shortcutTarget.mWord), shortcutTarget.mFrequency); shortcutTargets.remove(shortcutTarget.mWord); @@ -965,9 +1020,9 @@ public class BinaryDictionaryTests extends AndroidTestCase { shortcutTargets.put("zzz", updatedShortcutProbability); shortcutTargets.put("yyy", shortcutProbability); binaryDictionary.flushWithGC(); - unigramProperty = binaryDictionary.getWordProperty("aaa"); - assertEquals(2, unigramProperty.mShortcutTargets.size()); - for (WeightedString shortcutTarget : unigramProperty.mShortcutTargets) { + wordProperty = binaryDictionary.getWordProperty("aaa"); + assertEquals(2, wordProperty.mShortcutTargets.size()); + for (WeightedString shortcutTarget : wordProperty.mShortcutTargets) { assertTrue(shortcutTargets.containsKey(shortcutTarget.mWord)); assertEquals((int)shortcutTargets.get(shortcutTarget.mWord), shortcutTarget.mFrequency); shortcutTargets.remove(shortcutTarget.mWord); @@ -1034,14 +1089,15 @@ public class BinaryDictionaryTests extends AndroidTestCase { } for (final String word : words) { - final WordProperty unigramProperty = binaryDictionary.getWordProperty(word); - assertEquals((int)unigramProbabilities.get(word), unigramProperty.mProbability); + final WordProperty wordProperty = binaryDictionary.getWordProperty(word); + assertEquals((int)unigramProbabilities.get(word), + wordProperty.mProbabilityInfo.mProbability); if (!shortcutTargets.containsKey(word)) { // The word does not have shortcut targets. continue; } - assertEquals(shortcutTargets.get(word).size(), unigramProperty.mShortcutTargets.size()); - for (final WeightedString shortcutTarget : unigramProperty.mShortcutTargets) { + assertEquals(shortcutTargets.get(word).size(), wordProperty.mShortcutTargets.size()); + for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) { final String targetCodePonts = shortcutTarget.mWord; assertEquals((int)shortcutTargets.get(word).get(targetCodePonts), shortcutTarget.mFrequency); diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java index 8a1ac5233..715db2f9b 100644 --- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java +++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java @@ -108,6 +108,19 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase { } } + @Override + protected void setUp() throws Exception { + super.setUp(); + BinaryDictionary.setCurrentTimeForTest(0); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + // Quit test mode. + BinaryDictionary.setCurrentTimeForTest(-1); + } + private void generateWords(final int number, final Random random) { final int[] codePointSet = CodePointUtils.generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE, random); diff --git a/tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl b/tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl index 92529925d..db1dde39f 100644 --- a/tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl +++ b/tools/make-keyboard-text/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl @@ -21,6 +21,7 @@ import android.content.res.Resources; import android.text.TextUtils; import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.HashMap; @@ -47,7 +48,7 @@ import java.util.HashMap; */ public final class KeyboardTextsSet { public static final String PREFIX_TEXT = "!text/"; - private static final char BACKSLASH = '\\'; + private static final char BACKSLASH = Constants.CODE_BACKSLASH; private static final int MAX_STRING_REFERENCE_INDIRECTION = 10; // Language to texts map. |