aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java126
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java3
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java7
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java6
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java110
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java135
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java4
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java2
-rw-r--r--native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp12
-rw-r--r--native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp6
-rw-r--r--native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h13
-rw-r--r--native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp19
-rw-r--r--native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h6
-rw-r--r--native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp8
-rw-r--r--native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h2
-rw-r--r--native/jni/src/utils/char_utils.cpp2
-rw-r--r--native/jni/src/utils/char_utils.h9
-rw-r--r--tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java14
-rw-r--r--tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTestsBase.java (renamed from tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserBase.java)10
-rw-r--r--tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecTests.java13
-rw-r--r--tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java6
-rw-r--r--tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java4
22 files changed, 332 insertions, 185 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index c51941d32..2925a4b76 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -56,10 +56,9 @@ public final class KeySpecParser {
return keySpec.startsWith(KeyboardIconsSet.PREFIX_ICON);
}
- 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)) {
+ private static boolean hasCode(final String keySpec, final int labelEnd) {
+ if (labelEnd > 0 && labelEnd + 1 < keySpec.length()
+ && keySpec.startsWith(KeyboardCodesSet.PREFIX_CODE, labelEnd + 1)) {
return true;
}
return false;
@@ -84,16 +83,20 @@ public final class KeySpecParser {
return sb.toString();
}
- 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 + ": " + keySpec);
+ private static int indexOfLabelEnd(final String keySpec) {
+ final int length = keySpec.length();
+ if (keySpec.indexOf(BACKSLASH) < 0) {
+ final int labelEnd = keySpec.indexOf(VERTICAL_BAR);
+ if (labelEnd == 0) {
+ if (length == 1) {
+ // Treat a sole vertical bar as a special case of key label.
+ return -1;
+ }
+ throw new KeySpecParserError("Empty label");
}
- return end;
+ return labelEnd;
}
- final int length = keySpec.length();
- for (int pos = start; pos < length; pos++) {
+ for (int pos = 0; pos < length; pos++) {
final char c = keySpec.charAt(pos);
if (c == BACKSLASH && pos + 1 < length) {
// Skip escape char
@@ -105,35 +108,55 @@ public final class KeySpecParser {
return -1;
}
+ private static String getBeforeLabelEnd(final String keySpec, final int labelEnd) {
+ return (labelEnd < 0) ? keySpec : keySpec.substring(0, labelEnd);
+ }
+
+ private static String getAfterLabelEnd(final String keySpec, final int labelEnd) {
+ return keySpec.substring(labelEnd + /* VERTICAL_BAR */1);
+ }
+
+ private static void checkDoubleLabelEnd(final String keySpec, final int labelEnd) {
+ if (indexOfLabelEnd(getAfterLabelEnd(keySpec, labelEnd)) < 0) {
+ return;
+ }
+ throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + keySpec);
+ }
+
public static String getLabel(final String keySpec) {
+ if (keySpec == null) {
+ // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
+ return null;
+ }
if (hasIcon(keySpec)) {
return null;
}
- final int end = indexOfLabelEnd(keySpec, 0);
- final String label = (end > 0) ? parseEscape(keySpec.substring(0, end))
- : parseEscape(keySpec);
+ final int labelEnd = indexOfLabelEnd(keySpec);
+ final String label = parseEscape(getBeforeLabelEnd(keySpec, labelEnd));
if (label.isEmpty()) {
throw new KeySpecParserError("Empty label: " + keySpec);
}
return label;
}
- private static String getOutputTextInternal(final String keySpec) {
- final int end = indexOfLabelEnd(keySpec, 0);
- if (end <= 0) {
+ private static String getOutputTextInternal(final String keySpec, final int labelEnd) {
+ if (labelEnd <= 0) {
return null;
}
- if (indexOfLabelEnd(keySpec, end + 1) >= 0) {
- throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + keySpec);
- }
- return parseEscape(keySpec.substring(end + /* VERTICAL_BAR */1));
+ checkDoubleLabelEnd(keySpec, labelEnd);
+ return parseEscape(getAfterLabelEnd(keySpec, labelEnd));
}
public static String getOutputText(final String keySpec) {
- if (hasCode(keySpec)) {
+ if (keySpec == null) {
+ // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
return null;
}
- final String outputText = getOutputTextInternal(keySpec);
+ final int labelEnd = indexOfLabelEnd(keySpec);
+ if (hasCode(keySpec, labelEnd)) {
+ return null;
+ }
+ final String outputText = getOutputTextInternal(keySpec, labelEnd);
if (outputText != null) {
if (StringUtils.codePointCount(outputText) == 1) {
// If output text is one code point, it should be treated as a code.
@@ -154,14 +177,16 @@ public final class KeySpecParser {
}
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(keySpec.substring(end + 1), codesSet, CODE_UNSPECIFIED);
+ if (keySpec == null) {
+ // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
+ return CODE_UNSPECIFIED;
}
- final String outputText = getOutputTextInternal(keySpec);
+ final int labelEnd = indexOfLabelEnd(keySpec);
+ if (hasCode(keySpec, labelEnd)) {
+ checkDoubleLabelEnd(keySpec, labelEnd);
+ return parseCode(getAfterLabelEnd(keySpec, labelEnd), codesSet, CODE_UNSPECIFIED);
+ }
+ final String outputText = getOutputTextInternal(keySpec, labelEnd);
if (outputText != null) {
// If output text is one code point, it should be treated as a code.
// See {@link #getOutputText(String)}.
@@ -171,35 +196,40 @@ public final class KeySpecParser {
return CODE_OUTPUT_TEXT;
}
final String label = getLabel(keySpec);
- // Code is automatically generated for one letter label.
- if (StringUtils.codePointCount(label) == 1) {
- return label.codePointAt(0);
+ if (label == null) {
+ throw new KeySpecParserError("Empty label: " + keySpec);
}
- return CODE_OUTPUT_TEXT;
+ // Code is automatically generated for one letter label.
+ return (StringUtils.codePointCount(label) == 1) ? label.codePointAt(0) : CODE_OUTPUT_TEXT;
}
+ // TODO: Make this method private once Key.code attribute is removed.
public static int parseCode(final String text, final KeyboardCodesSet codesSet,
final int defCode) {
- if (text == null) return defCode;
+ if (text == null) {
+ return defCode;
+ }
if (text.startsWith(KeyboardCodesSet.PREFIX_CODE)) {
return codesSet.getCode(text.substring(KeyboardCodesSet.PREFIX_CODE.length()));
- } else if (text.startsWith(PREFIX_HEX)) {
+ }
+ if (text.startsWith(PREFIX_HEX)) {
return Integer.parseInt(text.substring(PREFIX_HEX.length()), 16);
- } else {
- return Integer.parseInt(text);
}
+ return Integer.parseInt(text);
}
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)
- ? keySpec.substring(KeyboardIconsSet.PREFIX_ICON.length())
- : keySpec.substring(KeyboardIconsSet.PREFIX_ICON.length(), end);
- return KeyboardIconsSet.getIconId(name);
- }
- return KeyboardIconsSet.ICON_UNDEFINED;
+ if (keySpec == null) {
+ // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
+ return KeyboardIconsSet.ICON_UNDEFINED;
+ }
+ if (!hasIcon(keySpec)) {
+ return KeyboardIconsSet.ICON_UNDEFINED;
+ }
+ final int labelEnd = indexOfLabelEnd(keySpec);
+ final String iconName = getBeforeLabelEnd(keySpec, labelEnd)
+ .substring(KeyboardIconsSet.PREFIX_ICON.length());
+ return KeyboardIconsSet.getIconId(iconName);
}
@SuppressWarnings("serial")
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
index d3bc0c2b2..0551e9e98 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
@@ -46,6 +46,9 @@ public final class MoreKeySpec {
public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, final Locale locale,
final KeyboardCodesSet codesSet) {
+ if (TextUtils.isEmpty(moreKeySpec)) {
+ throw new KeySpecParser.KeySpecParserError("Empty more key spec");
+ }
mLabel = StringUtils.toUpperCaseOfStringForLocale(
KeySpecParser.getLabel(moreKeySpec), needsToUpperCase, locale);
final int code = StringUtils.toUpperCaseOfCodeForLocale(
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 80a27e23f..b20bcd1f9 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -139,7 +139,7 @@ public final class BinaryDictionary extends Dictionary {
}
private static native boolean createEmptyDictFileNative(String filePath, long dictVersion,
- String[] attributeKeyStringArray, String[] attributeValueStringArray);
+ String locale, String[] attributeKeyStringArray, String[] attributeValueStringArray);
private static native long openNative(String sourceDir, long dictOffset, long dictSize,
boolean isUpdatable);
private static native void getHeaderInfoNative(long dict, int[] outHeaderSize,
@@ -179,7 +179,7 @@ public final class BinaryDictionary extends Dictionary {
private static native String getPropertyNative(long dict, String query);
public static boolean createEmptyDictFile(final String filePath, final long dictVersion,
- final Map<String, String> attributeMap) {
+ final Locale locale, final Map<String, String> attributeMap) {
final String[] keyArray = new String[attributeMap.size()];
final String[] valueArray = new String[attributeMap.size()];
int index = 0;
@@ -188,7 +188,8 @@ public final class BinaryDictionary extends Dictionary {
valueArray[index] = attributeMap.get(key);
index++;
}
- return createEmptyDictFileNative(filePath, dictVersion, keyArray, valueArray);
+ return createEmptyDictFileNative(filePath, dictVersion, locale.toString(), keyArray,
+ valueArray);
}
// TODO: Move native dict into session
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index f0dc7720d..565d6a1f9 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -289,10 +289,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
Log.e(TAG, "Can't remove a file: " + file.getName());
}
BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
- DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
+ DICTIONARY_FORMAT_VERSION, mLocale, getHeaderAttributeMap());
mBinaryDictionary = new BinaryDictionary(
file.getAbsolutePath(), 0 /* offset */, file.length(),
- true /* useFullEditDistance */, null, mDictType, mIsUpdatable);
+ true /* useFullEditDistance */, mLocale, mDictType, mIsUpdatable);
} else {
mDictionaryWriter.clear();
}
@@ -594,7 +594,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
Log.e(TAG, "Can't remove a file: " + file.getName());
}
BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
- DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
+ DICTIONARY_FORMAT_VERSION, mLocale, getHeaderAttributeMap());
} else {
if (mBinaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
mBinaryDictionary.flushWithGC();
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 8d0f4128e..e55c08dae 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -67,7 +67,6 @@ import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.inputlogic.InputLogic;
-import com.android.inputmethod.latin.inputlogic.SpaceState;
import com.android.inputmethod.latin.personalization.DictionaryDecayBroadcastReciever;
import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegistrar;
import com.android.inputmethod.latin.personalization.PersonalizationHelper;
@@ -83,7 +82,6 @@ import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
import com.android.inputmethod.latin.utils.IntentUtils;
import com.android.inputmethod.latin.utils.JniUtils;
-import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import com.android.inputmethod.research.ResearchLogger;
@@ -117,13 +115,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final String SCHEME_PACKAGE = "package";
private final Settings mSettings;
- private final InputLogic mInputLogic = new InputLogic(this);
+ private final InputLogic mInputLogic = new InputLogic(this /* LatinIME */,
+ this /* SuggestionStripViewAccessor */);
private View mExtractArea;
private View mKeyPreviewBackingView;
private SuggestionStripView mSuggestionStripView;
- private CompletionInfo[] mApplicationSpecifiedCompletions;
+ // TODO[IL]: remove this member completely.
+ public CompletionInfo[] mApplicationSpecifiedCompletions;
private RichInputMethodManager mRichImm;
@UsedForTesting final KeyboardSwitcher mKeyboardSwitcher;
@@ -1306,12 +1306,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Nothing to do so far.
}
- // TODO[IL]: Move this to InputLogic and make it private
- @Override
+ // TODO: remove this, read this directly from mInputLogic or something in the tests
+ @UsedForTesting
public boolean isShowingPunctuationList() {
- if (mInputLogic.mSuggestedWords == null) return false;
- return mSettings.getCurrent().mSpacingAndPunctuations.mSuggestPuncList
- == mInputLogic.mSuggestedWords;
+ return mInputLogic.isShowingPunctuationList(mSettings.getCurrent());
}
// TODO[IL]: Define a clear interface for this
@@ -1456,94 +1454,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// interface
@Override
public void pickSuggestionManually(final int index, final SuggestedWordInfo suggestionInfo) {
- final SuggestedWords suggestedWords = mInputLogic.mSuggestedWords;
- final String suggestion = suggestionInfo.mWord;
- // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
- if (suggestion.length() == 1 && isShowingPunctuationList()) {
- // Word separators are suggested before the user inputs something.
- // So, LatinImeLogger logs "" as a user's input.
- LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
- // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
- final int primaryCode = suggestion.charAt(0);
- onCodeInput(primaryCode,
- Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
- false /* isBatchMode */, suggestedWords.mIsPrediction);
- }
- return;
- }
-
- mInputLogic.mConnection.beginBatchEdit();
- final SettingsValues currentSettings = mSettings.getCurrent();
- if (SpaceState.PHANTOM == mInputLogic.mSpaceState && suggestion.length() > 0
- // In the batch input mode, a manually picked suggested word should just replace
- // the current batch input text and there is no need for a phantom space.
- && !mInputLogic.mWordComposer.isBatchMode()) {
- final int firstChar = Character.codePointAt(suggestion, 0);
- if (!currentSettings.isWordSeparator(firstChar)
- || currentSettings.isUsuallyPrecededBySpace(firstChar)) {
- mInputLogic.promotePhantomSpace(currentSettings);
- }
- }
-
- if (currentSettings.isApplicationSpecifiedCompletionsOn()
- && mApplicationSpecifiedCompletions != null
- && index >= 0 && index < mApplicationSpecifiedCompletions.length) {
- mInputLogic.mSuggestedWords = SuggestedWords.EMPTY;
- if (mSuggestionStripView != null) {
- mSuggestionStripView.clear();
- }
- mKeyboardSwitcher.updateShiftState();
- mInputLogic.resetComposingState(true /* alsoResetLastComposedWord */);
- final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
- mInputLogic.mConnection.commitCompletion(completionInfo);
- mInputLogic.mConnection.endBatchEdit();
- return;
- }
-
- // We need to log before we commit, because the word composer will store away the user
- // typed word.
- final String replacedWord = mInputLogic.mWordComposer.getTypedWord();
- LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
- mInputLogic.commitChosenWord(currentSettings, suggestion,
- LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
- mInputLogic.mWordComposer.isBatchMode(), suggestionInfo.mScore,
- suggestionInfo.mKind, suggestionInfo.mSourceDict.mDictType);
- }
- mInputLogic.mConnection.endBatchEdit();
- // Don't allow cancellation of manual pick
- mInputLogic.mLastComposedWord.deactivate();
- // Space state must be updated before calling updateShiftState
- mInputLogic.mSpaceState = SpaceState.PHANTOM;
- mKeyboardSwitcher.updateShiftState();
+ mInputLogic.onPickSuggestionManually(mSettings.getCurrent(), index, suggestionInfo,
+ mHandler, mKeyboardSwitcher);
+ }
- // We should show the "Touch again to save" hint if the user pressed the first entry
- // AND it's in none of our current dictionaries (main, user or otherwise).
- // Please note that if mSuggest is null, it means that everything is off: suggestion
- // and correction, so we shouldn't try to show the hint
- final Suggest suggest = mInputLogic.mSuggest;
- final boolean showingAddToDictionaryHint =
- (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
- || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
- && suggest != null
- // If the suggestion is not in the dictionary, the hint should be shown.
- && !suggest.mDictionaryFacilitator.isValidWord(suggestion,
- true /* ignoreCase */);
-
- if (currentSettings.mIsInternal) {
- LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
- Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
- }
- if (showingAddToDictionaryHint
- && suggest.mDictionaryFacilitator.isUserDictionaryEnabled()) {
- mSuggestionStripView.showAddToDictionaryHint(suggestion);
- } else {
- // If we're not showing the "Touch again to save", then update the suggestion strip.
- mHandler.postUpdateSuggestionStrip();
- }
+ @Override
+ public void showAddToDictionaryHint(final String word) {
+ if (null == mSuggestionStripView) return;
+ mSuggestionStripView.showAddToDictionaryHint(word);
}
// TODO[IL]: Define a clean interface for this
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index a994a43af..3ecf5f0fb 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -23,6 +23,7 @@ import android.text.style.SuggestionSpan;
import android.util.Log;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
@@ -44,6 +45,7 @@ import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
import com.android.inputmethod.latin.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.InputTypeUtils;
@@ -65,6 +67,7 @@ public final class InputLogic {
// TODO : Remove this member when we can.
private final LatinIME mLatinIME;
+ private final SuggestionStripViewAccessor mSuggestionStripViewAccessor;
// Never null.
private InputLogicHandler mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
@@ -94,8 +97,10 @@ public final class InputLogic {
// Find a way to remove it for readability.
public boolean mIsAutoCorrectionIndicatorOn;
- public InputLogic(final LatinIME latinIME) {
+ public InputLogic(final LatinIME latinIME,
+ final SuggestionStripViewAccessor suggestionStripViewAccessor) {
mLatinIME = latinIME;
+ mSuggestionStripViewAccessor = suggestionStripViewAccessor;
mWordComposer = new WordComposer();
mEventInterpreter = new EventInterpreter(latinIME);
mConnection = new RichInputConnection(latinIME);
@@ -179,6 +184,108 @@ public final class InputLogic {
}
/**
+ * A suggestion was picked from the suggestion strip.
+ * @param settingsValues the current values of the settings.
+ * @param index the index of the suggestion.
+ * @param suggestionInfo the suggestion info.
+ */
+ // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
+ // interface
+ public void onPickSuggestionManually(final SettingsValues settingsValues,
+ final int index, final SuggestedWordInfo suggestionInfo,
+ // TODO: remove these two arguments
+ final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher) {
+ final SuggestedWords suggestedWords = mSuggestedWords;
+ final String suggestion = suggestionInfo.mWord;
+ // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
+ if (suggestion.length() == 1 && isShowingPunctuationList(settingsValues)) {
+ // Word separators are suggested before the user inputs something.
+ // So, LatinImeLogger logs "" as a user's input.
+ LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
+ // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
+ final int primaryCode = suggestion.charAt(0);
+ onCodeInput(primaryCode,
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
+ settingsValues, handler, keyboardSwitcher);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
+ false /* isBatchMode */, suggestedWords.mIsPrediction);
+ }
+ return;
+ }
+
+ mConnection.beginBatchEdit();
+ if (SpaceState.PHANTOM == mSpaceState && suggestion.length() > 0
+ // In the batch input mode, a manually picked suggested word should just replace
+ // the current batch input text and there is no need for a phantom space.
+ && !mWordComposer.isBatchMode()) {
+ final int firstChar = Character.codePointAt(suggestion, 0);
+ if (!settingsValues.isWordSeparator(firstChar)
+ || settingsValues.isUsuallyPrecededBySpace(firstChar)) {
+ promotePhantomSpace(settingsValues);
+ }
+ }
+
+ // TODO: stop relying on mApplicationSpecifiedCompletions. The SuggestionInfo object
+ // should contain a reference to the CompletionInfo instead.
+ if (settingsValues.isApplicationSpecifiedCompletionsOn()
+ && mLatinIME.mApplicationSpecifiedCompletions != null
+ && index >= 0 && index < mLatinIME.mApplicationSpecifiedCompletions.length) {
+ mSuggestedWords = SuggestedWords.EMPTY;
+ mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
+ keyboardSwitcher.updateShiftState();
+ resetComposingState(true /* alsoResetLastComposedWord */);
+ final CompletionInfo completionInfo = mLatinIME.mApplicationSpecifiedCompletions[index];
+ mConnection.commitCompletion(completionInfo);
+ mConnection.endBatchEdit();
+ return;
+ }
+
+ // We need to log before we commit, because the word composer will store away the user
+ // typed word.
+ final String replacedWord = mWordComposer.getTypedWord();
+ LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
+ commitChosenWord(settingsValues, suggestion,
+ LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
+ mWordComposer.isBatchMode(), suggestionInfo.mScore,
+ suggestionInfo.mKind, suggestionInfo.mSourceDict.mDictType);
+ }
+ mConnection.endBatchEdit();
+ // Don't allow cancellation of manual pick
+ mLastComposedWord.deactivate();
+ // Space state must be updated before calling updateShiftState
+ mSpaceState = SpaceState.PHANTOM;
+ keyboardSwitcher.updateShiftState();
+
+ // We should show the "Touch again to save" hint if the user pressed the first entry
+ // AND it's in none of our current dictionaries (main, user or otherwise).
+ // Please note that if mSuggest is null, it means that everything is off: suggestion
+ // and correction, so we shouldn't try to show the hint
+ final Suggest suggest = mSuggest;
+ final boolean showingAddToDictionaryHint =
+ (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
+ || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
+ && suggest != null
+ // If the suggestion is not in the dictionary, the hint should be shown.
+ && !suggest.mDictionaryFacilitator.isValidWord(suggestion,
+ true /* ignoreCase */);
+
+ if (settingsValues.mIsInternal) {
+ LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+ }
+ if (showingAddToDictionaryHint
+ && suggest.mDictionaryFacilitator.isUserDictionaryEnabled()) {
+ mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion);
+ } else {
+ // If we're not showing the "Touch again to save", then update the suggestion strip.
+ handler.postUpdateSuggestionStrip();
+ }
+ }
+
+ /**
* Consider an update to the cursor position. Evaluate whether this update has happened as
* part of normal typing or whether it was an explicit cursor move by the user. In any case,
* do the necessary adjustments.
@@ -638,7 +745,7 @@ public final class InputLogic {
mSpaceState = SpaceState.WEAK;
}
// In case the "add to dictionary" hint was still displayed.
- mLatinIME.dismissAddToDictionaryHint();
+ mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
}
handler.postUpdateSuggestionStrip();
if (settingsValues.mIsInternal) {
@@ -714,7 +821,7 @@ public final class InputLogic {
if (maybeDoubleSpacePeriod(settingsValues, handler)) {
keyboardSwitcher.updateShiftState();
mSpaceState = SpaceState.DOUBLE;
- } else if (!mLatinIME.isShowingPunctuationList()) {
+ } else if (!isShowingPunctuationList(settingsValues)) {
mSpaceState = SpaceState.WEAK;
}
}
@@ -745,7 +852,7 @@ public final class InputLogic {
// Set punctuation right away. onUpdateSelection will fire but tests whether it is
// already displayed or not, so it's okay.
- mLatinIME.setNeutralSuggestionStrip();
+ mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
}
keyboardSwitcher.updateShiftState();
@@ -1098,7 +1205,7 @@ public final class InputLogic {
}
if (!mWordComposer.isComposingWord() && !settingsValues.mBigramPredictionEnabled) {
- mLatinIME.setNeutralSuggestionStrip();
+ mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
return;
}
@@ -1120,7 +1227,7 @@ public final class InputLogic {
final SuggestedWords suggestedWords = holder.get(null,
Constants.GET_SUGGESTED_WORDS_TIMEOUT);
if (suggestedWords != null) {
- mLatinIME.showSuggestionStrip(suggestedWords);
+ mSuggestionStripViewAccessor.showSuggestionStrip(suggestedWords);
}
}
@@ -1326,6 +1433,15 @@ public final class InputLogic {
}
/**
+ * Find out if the punctuation list is shown in the suggestion strip.
+ * @return whether the current suggestions are the punctuation list.
+ */
+ // TODO: make this private. It's used through LatinIME for tests.
+ public boolean isShowingPunctuationList(final SettingsValues settingsValues) {
+ return settingsValues.mSpacingAndPunctuations.mSuggestPuncList == mSuggestedWords;
+ }
+
+ /**
* Factor in auto-caps and manual caps and compute the current caps mode.
* @param settingsValues the current settings values.
* @param keyboardShiftMode the current shift mode of the keyboard. See
@@ -1482,7 +1598,7 @@ public final class InputLogic {
final int newSelStart, final int newSelEnd) {
final boolean shouldFinishComposition = mWordComposer.isComposingWord();
resetComposingState(true /* alsoResetLastComposedWord */);
- mLatinIME.setNeutralSuggestionStrip();
+ mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd,
shouldFinishComposition);
}
@@ -1723,9 +1839,8 @@ public final class InputLogic {
// the segment of text starting at the supplied index and running for the length
// of the auto-correction flash. At this moment, the "typedWord" argument is
// ignored by TextView.
- mConnection.commitCorrection(
- new CorrectionInfo(
- mConnection.getExpectedSelectionEnd() - typedWord.length(),
+ mConnection.commitCorrection(new CorrectionInfo(
+ mConnection.getExpectedSelectionEnd() - autoCorrection.length(),
typedWord, autoCorrection));
}
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
index 160775d63..4b0efbad0 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
@@ -55,7 +55,9 @@ public class Ver4DictEncoder implements DictEncoder {
throw new UnsupportedFormatException("Given path is not a directory.");
}
if (!BinaryDictionary.createEmptyDictFile(mDictPlacedDir.getAbsolutePath(),
- FormatSpec.VERSION4, dict.mOptions.mAttributes)) {
+ FormatSpec.VERSION4, LocaleUtils.constructLocaleFromString(
+ dict.mOptions.mAttributes.get(DictionaryHeader.DICTIONARY_LOCALE_KEY)),
+ dict.mOptions.mAttributes)) {
throw new IOException("Cannot create dictionary file : "
+ mDictPlacedDir.getAbsolutePath());
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java
index 91bf73d30..60f1c7a4e 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java
@@ -23,9 +23,9 @@ import com.android.inputmethod.latin.SuggestedWords;
*/
public interface SuggestionStripViewAccessor {
public boolean hasSuggestionStripView();
+ public void showAddToDictionaryHint(final String word);
public boolean isShowingAddToDictionaryHint();
public void dismissAddToDictionaryHint();
- public boolean isShowingPunctuationList();
public void setNeutralSuggestionStrip();
public void showSuggestionStrip(final SuggestedWords suggestedWords);
}
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 4372cbeb5..f5c3ee63c 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -29,6 +29,7 @@
#include "suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h"
#include "suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h"
#include "utils/autocorrection_threshold_utils.h"
+#include "utils/char_utils.h"
#include "utils/time_keeper.h"
namespace latinime {
@@ -37,13 +38,15 @@ class ProximityInfo;
// TODO: Move to makedict.
static jboolean latinime_BinaryDictionary_createEmptyDictFile(JNIEnv *env, jclass clazz,
- jstring filePath, jlong dictVersion, jobjectArray attributeKeyStringArray,
+ jstring filePath, jlong dictVersion, jstring locale, jobjectArray attributeKeyStringArray,
jobjectArray attributeValueStringArray) {
const jsize filePathUtf8Length = env->GetStringUTFLength(filePath);
char filePathChars[filePathUtf8Length + 1];
env->GetStringUTFRegion(filePath, 0, env->GetStringLength(filePath), filePathChars);
filePathChars[filePathUtf8Length] = '\0';
-
+ jsize localeLength = env->GetStringLength(locale);
+ jchar localeCodePoints[localeLength];
+ env->GetStringRegion(locale, 0, localeLength, localeCodePoints);
const int keyCount = env->GetArrayLength(attributeKeyStringArray);
const int valueCount = env->GetArrayLength(attributeValueStringArray);
if (keyCount != valueCount) {
@@ -73,7 +76,7 @@ static jboolean latinime_BinaryDictionary_createEmptyDictFile(JNIEnv *env, jclas
}
return DictFileWritingUtils::createEmptyDictFile(filePathChars, static_cast<int>(dictVersion),
- &attributeMap);
+ CharUtils::convertShortArrayToIntVector(localeCodePoints, localeLength), &attributeMap);
}
static jlong latinime_BinaryDictionary_open(JNIEnv *env, jclass clazz, jstring sourceDir,
@@ -503,7 +506,8 @@ static int latinime_BinaryDictionary_setCurrentTimeForTest(JNIEnv *env, jclass c
static const JNINativeMethod sMethods[] = {
{
const_cast<char *>("createEmptyDictFileNative"),
- const_cast<char *>("(Ljava/lang/String;J[Ljava/lang/String;[Ljava/lang/String;)Z"),
+ const_cast<char *>(
+ "(Ljava/lang/String;JLjava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)Z"),
reinterpret_cast<void *>(latinime_BinaryDictionary_createEmptyDictFile)
},
{
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
index 7504524f0..b5b5ed740 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
@@ -32,6 +32,7 @@ const char *const HeaderPolicy::EXTENDED_REGION_SIZE_KEY = "EXTENDED_REGION_SIZE
// Historical info is information that is needed to support decaying such as timestamp, level and
// count.
const char *const HeaderPolicy::HAS_HISTORICAL_INFO_KEY = "HAS_HISTORICAL_INFO";
+const char *const HeaderPolicy::LOCALE_KEY = "locale"; // match Java declaration
const int HeaderPolicy::DEFAULT_MULTIPLE_WORDS_DEMOTION_RATE = 100;
const float HeaderPolicy::MULTIPLE_WORD_COST_MULTIPLIER_SCALE = 100.0f;
@@ -59,6 +60,10 @@ void HeaderPolicy::readHeaderValueOrQuestionMark(const char *const key, int *out
outValue[terminalIndex] = '\0';
}
+const std::vector<int> HeaderPolicy::readLocale() const {
+ return HeaderReadWriteUtils::readCodePointVectorAttributeValue(&mAttributeMap, LOCALE_KEY);
+}
+
float HeaderPolicy::readMultipleWordCostMultiplier() const {
const int demotionRate = HeaderReadWriteUtils::readIntAttributeValue(&mAttributeMap,
MULTIPLE_WORDS_DEMOTION_RATE_KEY, DEFAULT_MULTIPLE_WORDS_DEMOTION_RATE);
@@ -116,6 +121,7 @@ void HeaderPolicy::fillInHeader(const bool updatesLastDecayedTime, const int uni
// Set the current time as the generation time.
HeaderReadWriteUtils::setIntAttribute(outAttributeMap, DATE_KEY,
TimeKeeper::peekCurrentTime());
+ HeaderReadWriteUtils::setCodePointVectorAttribute(outAttributeMap, LOCALE_KEY, mLocale);
if (updatesLastDecayedTime) {
// Set current time as the last updated time.
HeaderReadWriteUtils::setIntAttribute(outAttributeMap, LAST_DECAYED_TIME_KEY,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
index 1320c6560..a05e00c39 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
@@ -23,6 +23,7 @@
#include "suggest/core/policy/dictionary_header_structure_policy.h"
#include "suggest/policyimpl/dictionary/header/header_read_write_utils.h"
#include "suggest/policyimpl/dictionary/utils/format_utils.h"
+#include "utils/char_utils.h"
#include "utils/time_keeper.h"
namespace latinime {
@@ -35,6 +36,7 @@ class HeaderPolicy : public DictionaryHeaderStructurePolicy {
mDictionaryFlags(HeaderReadWriteUtils::getFlags(dictBuf)),
mSize(HeaderReadWriteUtils::getHeaderSize(dictBuf)),
mAttributeMap(createAttributeMapAndReadAllAttributes(dictBuf)),
+ mLocale(readLocale()),
mMultiWordCostMultiplier(readMultipleWordCostMultiplier()),
mRequiresGermanUmlautProcessing(readRequiresGermanUmlautProcessing()),
mIsDecayingDict(HeaderReadWriteUtils::readBoolAttributeValue(&mAttributeMap,
@@ -54,10 +56,11 @@ class HeaderPolicy : public DictionaryHeaderStructurePolicy {
// Constructs header information using an attribute map.
HeaderPolicy(const FormatUtils::FORMAT_VERSION dictFormatVersion,
+ const std::vector<int> locale,
const HeaderReadWriteUtils::AttributeMap *const attributeMap)
: mDictFormatVersion(dictFormatVersion),
mDictionaryFlags(HeaderReadWriteUtils::createAndGetDictionaryFlagsUsingAttributeMap(
- attributeMap)), mSize(0), mAttributeMap(*attributeMap),
+ attributeMap)), mSize(0), mAttributeMap(*attributeMap), mLocale(locale),
mMultiWordCostMultiplier(readMultipleWordCostMultiplier()),
mRequiresGermanUmlautProcessing(readRequiresGermanUmlautProcessing()),
mIsDecayingDict(HeaderReadWriteUtils::readBoolAttributeValue(&mAttributeMap,
@@ -68,12 +71,13 @@ class HeaderPolicy : public DictionaryHeaderStructurePolicy {
DATE_KEY, TimeKeeper::peekCurrentTime() /* defaultValue */)),
mUnigramCount(0), mBigramCount(0), mExtendedRegionSize(0),
mHasHistoricalInfoOfWords(HeaderReadWriteUtils::readBoolAttributeValue(
- &mAttributeMap, HAS_HISTORICAL_INFO_KEY, false /* defaultValue */)) {}
+ &mAttributeMap, HAS_HISTORICAL_INFO_KEY, false /* defaultValue */)) {
+ }
// Temporary dummy header.
HeaderPolicy()
: mDictFormatVersion(FormatUtils::UNKNOWN_VERSION), mDictionaryFlags(0), mSize(0),
- mAttributeMap(), mMultiWordCostMultiplier(0.0f),
+ mAttributeMap(), mLocale(CharUtils::EMPTY_STRING), mMultiWordCostMultiplier(0.0f),
mRequiresGermanUmlautProcessing(false), mIsDecayingDict(false),
mDate(0), mLastDecayedTime(0), mUnigramCount(0), mBigramCount(0),
mExtendedRegionSize(0), mHasHistoricalInfoOfWords(false) {}
@@ -174,6 +178,7 @@ class HeaderPolicy : public DictionaryHeaderStructurePolicy {
static const char *const BIGRAM_COUNT_KEY;
static const char *const EXTENDED_REGION_SIZE_KEY;
static const char *const HAS_HISTORICAL_INFO_KEY;
+ static const char *const LOCALE_KEY;
static const int DEFAULT_MULTIPLE_WORDS_DEMOTION_RATE;
static const float MULTIPLE_WORD_COST_MULTIPLIER_SCALE;
@@ -181,6 +186,7 @@ class HeaderPolicy : public DictionaryHeaderStructurePolicy {
const HeaderReadWriteUtils::DictionaryFlags mDictionaryFlags;
const int mSize;
HeaderReadWriteUtils::AttributeMap mAttributeMap;
+ const std::vector<int> mLocale;
const float mMultiWordCostMultiplier;
const bool mRequiresGermanUmlautProcessing;
const bool mIsDecayingDict;
@@ -191,6 +197,7 @@ class HeaderPolicy : public DictionaryHeaderStructurePolicy {
const int mExtendedRegionSize;
const bool mHasHistoricalInfoOfWords;
+ const std::vector<int> readLocale() const;
float readMultipleWordCostMultiplier() const;
bool readRequiresGermanUmlautProcessing() const;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
index 6b4598642..850b0d87f 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
@@ -130,6 +130,13 @@ const HeaderReadWriteUtils::DictionaryFlags HeaderReadWriteUtils::NO_FLAGS = 0;
return true;
}
+/* static */ void HeaderReadWriteUtils::setCodePointVectorAttribute(
+ AttributeMap *const headerAttributes, const char *const key, const std::vector<int> value) {
+ AttributeMap::key_type keyVector;
+ insertCharactersIntoVector(key, &keyVector);
+ (*headerAttributes)[keyVector] = value;
+}
+
/* static */ void HeaderReadWriteUtils::setBoolAttribute(AttributeMap *const headerAttributes,
const char *const key, const bool value) {
setIntAttribute(headerAttributes, key, value ? 1 : 0);
@@ -151,6 +158,18 @@ const HeaderReadWriteUtils::DictionaryFlags HeaderReadWriteUtils::NO_FLAGS = 0;
(*headerAttributes)[*key] = valueVector;
}
+/* static */ const std::vector<int> HeaderReadWriteUtils::readCodePointVectorAttributeValue(
+ const AttributeMap *const headerAttributes, const char *const key) {
+ AttributeMap::key_type keyVector;
+ insertCharactersIntoVector(key, &keyVector);
+ AttributeMap::const_iterator it = headerAttributes->find(keyVector);
+ if (it == headerAttributes->end()) {
+ return std::vector<int>();
+ } else {
+ return it->second;
+ }
+}
+
/* static */ bool HeaderReadWriteUtils::readBoolAttributeValue(
const AttributeMap *const headerAttributes, const char *const key,
const bool defaultValue) {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
index fc24bbdd5..3433c0494 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
@@ -63,12 +63,18 @@ class HeaderReadWriteUtils {
/**
* Methods for header attributes.
*/
+ static void setCodePointVectorAttribute(AttributeMap *const headerAttributes,
+ const char *const key, const std::vector<int> value);
+
static void setBoolAttribute(AttributeMap *const headerAttributes,
const char *const key, const bool value);
static void setIntAttribute(AttributeMap *const headerAttributes,
const char *const key, const int value);
+ static const std::vector<int> readCodePointVectorAttributeValue(
+ const AttributeMap *const headerAttributes, const char *const key);
+
static bool readBoolAttributeValue(const AttributeMap *const headerAttributes,
const char *const key, const bool defaultValue);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
index 84403c807..335ea0de0 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
@@ -31,11 +31,12 @@ namespace latinime {
const char *const DictFileWritingUtils::TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE = ".tmp";
/* static */ bool DictFileWritingUtils::createEmptyDictFile(const char *const filePath,
- const int dictVersion, const HeaderReadWriteUtils::AttributeMap *const attributeMap) {
+ const int dictVersion, const std::vector<int> localeAsCodePointVector,
+ const HeaderReadWriteUtils::AttributeMap *const attributeMap) {
TimeKeeper::setCurrentTime();
switch (dictVersion) {
case FormatUtils::VERSION_4:
- return createEmptyV4DictFile(filePath, attributeMap);
+ return createEmptyV4DictFile(filePath, localeAsCodePointVector, attributeMap);
default:
AKLOGE("Cannot create dictionary %s because format version %d is not supported.",
filePath, dictVersion);
@@ -44,8 +45,9 @@ const char *const DictFileWritingUtils::TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE =
}
/* static */ bool DictFileWritingUtils::createEmptyV4DictFile(const char *const dirPath,
+ const std::vector<int> localeAsCodePointVector,
const HeaderReadWriteUtils::AttributeMap *const attributeMap) {
- HeaderPolicy headerPolicy(FormatUtils::VERSION_4, attributeMap);
+ HeaderPolicy headerPolicy(FormatUtils::VERSION_4, localeAsCodePointVector, attributeMap);
Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers =
Ver4DictBuffers::createVer4DictBuffers(&headerPolicy);
headerPolicy.fillInAndWriteHeaderToBuffer(true /* updatesLastDecayedTime */,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h
index bdf9fd63c..c2ecff45e 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h
@@ -31,6 +31,7 @@ class DictFileWritingUtils {
static const char *const TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE;
static bool createEmptyDictFile(const char *const filePath, const int dictVersion,
+ const std::vector<int> localeAsCodePointVector,
const HeaderReadWriteUtils::AttributeMap *const attributeMap);
static bool flushAllHeaderAndBodyToFile(const char *const filePath,
@@ -44,6 +45,7 @@ class DictFileWritingUtils {
DISALLOW_IMPLICIT_CONSTRUCTORS(DictFileWritingUtils);
static bool createEmptyV4DictFile(const char *const filePath,
+ const std::vector<int> localeAsCodePointVector,
const HeaderReadWriteUtils::AttributeMap *const attributeMap);
static bool flushBufferToFile(const char *const filePath,
diff --git a/native/jni/src/utils/char_utils.cpp b/native/jni/src/utils/char_utils.cpp
index 0e7039610..d41fc8924 100644
--- a/native/jni/src/utils/char_utils.cpp
+++ b/native/jni/src/utils/char_utils.cpp
@@ -1273,4 +1273,6 @@ static int compare_pair_capital(const void *a, const void *b) {
/* U+04F0 */ 0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04F6, 0x04F7,
/* U+04F8 */ 0x042B, 0x044B, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF,
};
+
+/* static */ const std::vector<int> CharUtils::EMPTY_STRING(1 /* size */, '\0' /* value */);
} // namespace latinime
diff --git a/native/jni/src/utils/char_utils.h b/native/jni/src/utils/char_utils.h
index 41663c81a..98b8966df 100644
--- a/native/jni/src/utils/char_utils.h
+++ b/native/jni/src/utils/char_utils.h
@@ -18,6 +18,7 @@
#define LATINIME_CHAR_UTILS_H
#include <cctype>
+#include <vector>
#include "defines.h"
@@ -85,7 +86,15 @@ class CharUtils {
return spaceCount;
}
+ static AK_FORCE_INLINE std::vector<int> convertShortArrayToIntVector(
+ const unsigned short *const source, const int length) {
+ std::vector<int> destination;
+ destination.insert(destination.end(), source, source + length);
+ return destination; // Copies the vector
+ }
+
static unsigned short latin_tolower(const unsigned short c);
+ static const std::vector<int> EMPTY_STRING;
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(CharUtils);
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
index e29181c71..9e43bd4d2 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
@@ -16,12 +16,15 @@
package com.android.inputmethod.keyboard.internal;
+import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED;
+import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;
+
import android.test.suitebuilder.annotation.SmallTest;
import com.android.inputmethod.latin.Constants;
@SmallTest
-public final class KeySpecParserTests extends KeySpecParserBase {
+public final class KeySpecParserTests extends KeySpecParserTestsBase {
@Override
protected void assertParser(final String message, final String keySpec,
final String expectedLabel, final String expectedOutputText, final int expectedIcon,
@@ -40,4 +43,13 @@ public final class KeySpecParserTests extends KeySpecParserBase {
Constants.printableCode(expectedCode),
Constants.printableCode(actualCode));
}
+
+ // TODO: Remove this method.
+ // These should throw {@link KeySpecParserError} when Key.keyLabel attribute become mandatory.
+ public void testEmptySpec() {
+ assertParser("Null spec", null,
+ null, null, ICON_UNDEFINED, CODE_UNSPECIFIED);
+ assertParser("Empty spec", "",
+ null, null, ICON_UNDEFINED, CODE_UNSPECIFIED);
+ }
}
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserBase.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTestsBase.java
index aecef23e8..cb640b3f8 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTestsBase.java
@@ -30,7 +30,7 @@ import com.android.inputmethod.latin.utils.RunInLocale;
import java.util.Locale;
-abstract class KeySpecParserBase extends AndroidTestCase {
+abstract class KeySpecParserTestsBase extends AndroidTestCase {
private final static Locale TEST_LOCALE = Locale.ENGLISH;
protected final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
protected final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
@@ -101,7 +101,9 @@ abstract class KeySpecParserBase extends AndroidTestCase {
"a", null, ICON_UNDEFINED, 'a');
assertParser("Single surrogate", SURROGATE_PAIR1,
SURROGATE_PAIR1, null, ICON_UNDEFINED, SURROGATE_CODE1);
- assertParser("Single escaped bar", "\\|",
+ assertParser("Sole vertical bar", "|",
+ "|", null, ICON_UNDEFINED, '|');
+ assertParser("Single escaped vertical bar", "\\|",
"|", null, ICON_UNDEFINED, '|');
assertParser("Single escaped escape", "\\\\",
"\\", null, ICON_UNDEFINED, '\\');
@@ -251,8 +253,6 @@ abstract class KeySpecParserBase extends AndroidTestCase {
}
public void testFormatError() {
- assertParserError("Empty spec", "", null,
- null, ICON_UNDEFINED, CODE_UNSPECIFIED);
assertParserError("Empty label with outputText", "|a",
null, "a", ICON_UNDEFINED, CODE_UNSPECIFIED);
assertParserError("Empty label with code", "|" + CODE_SETTINGS,
@@ -261,8 +261,6 @@ abstract class KeySpecParserBase extends AndroidTestCase {
"a", null, ICON_UNDEFINED, CODE_UNSPECIFIED);
assertParserError("Empty outputText with icon", ICON_SETTINGS + "|",
null, null, mSettingsIconId, CODE_UNSPECIFIED);
- assertParserError("Empty icon and code", "|",
- null, null, ICON_UNDEFINED, CODE_UNSPECIFIED);
assertParserError("Icon without code", ICON_SETTINGS,
null, null, mSettingsIconId, CODE_UNSPECIFIED);
assertParserError("Non existing icon", ICON_NON_EXISTING + "|abc",
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecTests.java b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecTests.java
index 213e2d483..ea25bcf37 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecTests.java
@@ -16,6 +16,9 @@
package com.android.inputmethod.keyboard.internal;
+import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED;
+import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;
+
import android.test.suitebuilder.annotation.SmallTest;
import com.android.inputmethod.latin.Constants;
@@ -24,7 +27,7 @@ import java.util.Arrays;
import java.util.Locale;
@SmallTest
-public final class MoreKeySpecTests extends KeySpecParserBase {
+public final class MoreKeySpecTests extends KeySpecParserTestsBase {
@Override
protected void assertParser(final String message, final String moreKeySpec,
final String expectedLabel, final String expectedOutputText, final int expectedIconId,
@@ -42,6 +45,14 @@ public final class MoreKeySpecTests extends KeySpecParserBase {
Constants.printableCode(spec.mCode));
}
+ // TODO: Move this method to {@link KeySpecParserBase}.
+ public void testEmptySpec() {
+ assertParserError("Null spec", null,
+ null, null, ICON_UNDEFINED, CODE_UNSPECIFIED);
+ assertParserError("Empty spec", "",
+ null, null, ICON_UNDEFINED, CODE_UNSPECIFIED);
+ }
+
private static void assertArrayEquals(final String message, final Object[] expected,
final Object[] actual) {
if (expected == actual) {
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
index c41bbd768..7d664c825 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
@@ -28,6 +28,7 @@ import com.android.inputmethod.latin.makedict.FusionDictionary;
import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.utils.FileUtils;
+import com.android.inputmethod.latin.utils.LocaleUtils;
import java.io.File;
import java.io.IOException;
@@ -104,15 +105,14 @@ public class BinaryDictionaryDecayingTests extends AndroidTestCase {
FileUtils.deleteRecursively(file);
Map<String, String> attributeMap = new HashMap<String, String>();
attributeMap.put(DictionaryHeader.DICTIONARY_ID_KEY, dictId);
- attributeMap.put(DictionaryHeader.DICTIONARY_LOCALE_KEY, dictId);
attributeMap.put(DictionaryHeader.DICTIONARY_VERSION_KEY,
String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
attributeMap.put(DictionaryHeader.USES_FORGETTING_CURVE_KEY,
DictionaryHeader.ATTRIBUTE_VALUE_TRUE);
attributeMap.put(DictionaryHeader.HAS_HISTORICAL_INFO_KEY,
DictionaryHeader.ATTRIBUTE_VALUE_TRUE);
- if (BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
- FormatSpec.VERSION4, attributeMap)) {
+ if (BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(), FormatSpec.VERSION4,
+ LocaleUtils.constructLocaleFromString(TEST_LOCALE), attributeMap)) {
return file;
} else {
throw new IOException("Empty dictionary " + file.getAbsolutePath()
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
index bab86e546..e21975db6 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -69,8 +69,8 @@ public class BinaryDictionaryTests extends AndroidTestCase {
file.delete();
file.mkdir();
Map<String, String> attributeMap = new HashMap<String, String>();
- if (BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
- FormatSpec.VERSION4, attributeMap)) {
+ if (BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(), FormatSpec.VERSION4,
+ Locale.ENGLISH, attributeMap)) {
return file;
} else {
throw new IOException("Empty dictionary " + file.getAbsolutePath()