diff options
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() |