diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
32 files changed, 631 insertions, 260 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 2e108756e..9bca0bf35 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -186,9 +186,10 @@ public final class BinaryDictionary extends Dictionary { long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions, int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray, - int[] outputSuggestionCount, int[] outputCodePoints, int[] outputScores, - int[] outputIndices, int[] outputTypes, int[] outputAutoCommitFirstWordConfidence, - float[] inOutLanguageWeight); + int prevWordCount, int[] outputSuggestionCount, int[] outputCodePoints, + int[] outputScores, int[] outputIndices, int[] outputTypes, + int[] outputAutoCommitFirstWordConfidence, + float[] inOutWeightOfLangModelVsSpatialModel); private static native boolean addUnigramEntryNative(long dict, int[] word, int probability, int[] shortcutTarget, int shortcutProbability, boolean isBeginningOfSentence, boolean isNotAWord, boolean isBlacklisted, int timestamp); @@ -256,7 +257,8 @@ public final class BinaryDictionary extends Dictionary { public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo, final SettingsValuesForSuggestion settingsValuesForSuggestion, - final int sessionId, final float[] inOutLanguageWeight) { + final int sessionId, final float weightForLocale, + final float[] inOutWeightOfLangModelVsSpatialModel) { if (!isValidDictionary()) { return null; } @@ -284,10 +286,12 @@ public final class BinaryDictionary extends Dictionary { settingsValuesForSuggestion.mSpaceAwareGestureEnabled); session.mNativeSuggestOptions.setAdditionalFeaturesOptions( settingsValuesForSuggestion.mAdditionalFeaturesSettingValues); - if (inOutLanguageWeight != null) { - session.mInputOutputLanguageWeight[0] = inOutLanguageWeight[0]; + if (inOutWeightOfLangModelVsSpatialModel != null) { + session.mInputOutputWeightOfLangModelVsSpatialModel[0] = + inOutWeightOfLangModelVsSpatialModel[0]; } else { - session.mInputOutputLanguageWeight[0] = Dictionary.NOT_A_LANGUAGE_WEIGHT; + session.mInputOutputWeightOfLangModelVsSpatialModel[0] = + Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL; } // TOOD: Pass multiple previous words information for n-gram. getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(), @@ -295,12 +299,14 @@ public final class BinaryDictionary extends Dictionary { inputPointers.getYCoordinates(), inputPointers.getTimes(), inputPointers.getPointerIds(), session.mInputCodePoints, inputSize, session.mNativeSuggestOptions.getOptions(), session.mPrevWordCodePointArrays, - session.mIsBeginningOfSentenceArray, session.mOutputSuggestionCount, - session.mOutputCodePoints, session.mOutputScores, session.mSpaceIndices, - session.mOutputTypes, session.mOutputAutoCommitFirstWordConfidence, - session.mInputOutputLanguageWeight); - if (inOutLanguageWeight != null) { - inOutLanguageWeight[0] = session.mInputOutputLanguageWeight[0]; + session.mIsBeginningOfSentenceArray, prevWordsInfo.getPrevWordCount(), + session.mOutputSuggestionCount, session.mOutputCodePoints, session.mOutputScores, + session.mSpaceIndices, session.mOutputTypes, + session.mOutputAutoCommitFirstWordConfidence, + session.mInputOutputWeightOfLangModelVsSpatialModel); + if (inOutWeightOfLangModelVsSpatialModel != null) { + inOutWeightOfLangModelVsSpatialModel[0] = + session.mInputOutputWeightOfLangModelVsSpatialModel[0]; } final int count = session.mOutputSuggestionCount[0]; final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>(); @@ -314,7 +320,8 @@ public final class BinaryDictionary extends Dictionary { if (len > 0) { suggestions.add(new SuggestedWordInfo( new String(session.mOutputCodePoints, start, len), - session.mOutputScores[j], session.mOutputTypes[j], this /* sourceDict */, + (int)(session.mOutputScores[j] * weightForLocale), session.mOutputTypes[j], + this /* sourceDict */, session.mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */, session.mOutputAutoCommitFirstWordConfidence[0])); } @@ -358,9 +365,8 @@ public final class BinaryDictionary extends Dictionary { if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) { return NOT_A_PROBABILITY; } - final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][]; - final boolean[] isBeginningOfSentenceArray = - new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; + final int[][] prevWordCodePointArrays = new int[prevWordsInfo.getPrevWordCount()][]; + final boolean[] isBeginningOfSentenceArray = new boolean[prevWordsInfo.getPrevWordCount()]; prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray); final int[] wordCodePoints = StringUtils.toCodePointArray(word); return getNgramProbabilityNative(mNativeDict, prevWordCodePointArrays, @@ -455,9 +461,8 @@ public final class BinaryDictionary extends Dictionary { if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) { return false; } - final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][]; - final boolean[] isBeginningOfSentenceArray = - new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; + final int[][] prevWordCodePointArrays = new int[prevWordsInfo.getPrevWordCount()][]; + final boolean[] isBeginningOfSentenceArray = new boolean[prevWordsInfo.getPrevWordCount()]; prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray); final int[] wordCodePoints = StringUtils.toCodePointArray(word); if (!addNgramEntryNative(mNativeDict, prevWordCodePointArrays, @@ -473,9 +478,8 @@ public final class BinaryDictionary extends Dictionary { if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) { return false; } - final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][]; - final boolean[] isBeginningOfSentenceArray = - new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; + final int[][] prevWordCodePointArrays = new int[prevWordsInfo.getPrevWordCount()][]; + final boolean[] isBeginningOfSentenceArray = new boolean[prevWordsInfo.getPrevWordCount()]; prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray); final int[] wordCodePoints = StringUtils.toCodePointArray(word); if (!removeNgramEntryNative(mNativeDict, prevWordCodePointArrays, @@ -486,6 +490,7 @@ public final class BinaryDictionary extends Dictionary { return true; } + @UsedForTesting public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) { if (!isValidDictionary()) return; int processedParamCount = 0; diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java index b341f623e..2751c1250 100644 --- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java +++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java @@ -40,7 +40,7 @@ public final class DicTraverseSession { public final int[] mOutputTypes = new int[MAX_RESULTS]; // Only one result is ever used public final int[] mOutputAutoCommitFirstWordConfidence = new int[1]; - public final float[] mInputOutputLanguageWeight = new float[1]; + public final float[] mInputOutputWeightOfLangModelVsSpatialModel = new float[1]; public final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions(); diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 2f79c7662..b58a52b41 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -22,6 +22,8 @@ import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; import java.util.ArrayList; import java.util.Locale; +import java.util.Arrays; +import java.util.HashSet; /** * Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key @@ -29,7 +31,7 @@ import java.util.Locale; */ public abstract class Dictionary { public static final int NOT_A_PROBABILITY = -1; - public static final float NOT_A_LANGUAGE_WEIGHT = -1.0f; + public static final float NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL = -1.0f; // The following types do not actually come from real dictionary instances, so we create // corresponding instances. @@ -65,6 +67,14 @@ public abstract class Dictionary { // The locale for this dictionary. May be null if unknown (phony dictionary for example). public final Locale mLocale; + /** + * Set out of the dictionary types listed above that are based on data specific to the user, + * e.g., the user's contacts. + */ + private static final HashSet<String> sUserSpecificDictionaryTypes = + new HashSet(Arrays.asList(new String[] { TYPE_USER_TYPED, TYPE_USER, TYPE_CONTACTS, + TYPE_USER_HISTORY, TYPE_PERSONALIZATION, TYPE_CONTEXTUAL })); + public Dictionary(final String dictType, final Locale locale) { mDictType = dictType; mLocale = locale; @@ -78,15 +88,18 @@ public abstract class Dictionary { * @param proximityInfo the object for key proximity. May be ignored by some implementations. * @param settingsValuesForSuggestion the settings values used for the suggestion. * @param sessionId the session id. - * @param inOutLanguageWeight the language weight used for generating suggestions. - * inOutLanguageWeight is a float array that has only one element. This can be updated when the - * different language weight is used. + * @param weightForLocale the weight given to this locale, to multiply the output scores for + * multilingual input. + * @param inOutWeightOfLangModelVsSpatialModel the weight of the language model as a ratio of + * the spatial model, used for generating suggestions. inOutWeightOfLangModelVsSpatialModel is + * a float array that has only one element. This can be updated when a different value is used. * @return the list of suggestions (possibly null if none) */ abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo, final SettingsValuesForSuggestion settingsValuesForSuggestion, - final int sessionId, final float[] inOutLanguageWeight); + final int sessionId, final float weightForLocale, + final float[] inOutWeightOfLangModelVsSpatialModel); /** * Checks if the given word has to be treated as a valid word. Please note that some @@ -159,6 +172,14 @@ public abstract class Dictionary { } /** + * Whether this dictionary is based on data specific to the user, e.g., the user's contacts. + * @return Whether this dictionary is specific to the user. + */ + public boolean isUserSpecific() { + return sUserSpecificDictionaryTypes.contains(mDictType); + } + + /** * Not a true dictionary. A placeholder used to indicate suggestions that don't come from any * real dictionary. */ @@ -172,7 +193,8 @@ public abstract class Dictionary { public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo, final SettingsValuesForSuggestion settingsValuesForSuggestion, - final int sessionId, final float[] inOutLanguageWeight) { + final int sessionId, final float weightForLocale, + final float[] inOutWeightOfLangModelVsSpatialModel) { return null; } diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java index ca5e93714..b26b37817 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java +++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java @@ -62,20 +62,21 @@ public final class DictionaryCollection extends Dictionary { public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo, final SettingsValuesForSuggestion settingsValuesForSuggestion, - final int sessionId, final float[] inOutLanguageWeight) { + final int sessionId, final float weightForLocale, + final float[] inOutWeightOfLangModelVsSpatialModel) { final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries; if (dictionaries.isEmpty()) return null; // To avoid creating unnecessary objects, we get the list out of the first // dictionary and add the rest to it if not null, hence the get(0) ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer, prevWordsInfo, proximityInfo, settingsValuesForSuggestion, sessionId, - inOutLanguageWeight); + weightForLocale, inOutWeightOfLangModelVsSpatialModel); if (null == suggestions) suggestions = new ArrayList<>(); final int length = dictionaries.size(); for (int i = 1; i < length; ++ i) { final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer, prevWordsInfo, proximityInfo, settingsValuesForSuggestion, sessionId, - inOutLanguageWeight); + weightForLocale, inOutWeightOfLangModelVsSpatialModel); if (null != sugg) suggestions.addAll(sugg); } return suggestions; diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java index eced45ea5..aa15bd6bf 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java @@ -104,6 +104,7 @@ public class DictionaryFacilitator { private static class DictionaryGroup { public final Locale mLocale; private Dictionary mMainDict; + public float mWeightForLocale = 1.0f; public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap = new ConcurrentHashMap<>(); @@ -595,16 +596,19 @@ public class DictionaryFacilitator { final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo, final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) { final DictionaryGroup[] dictionaryGroups = mDictionaryGroups; - final SuggestionResults suggestionResults = - new SuggestionResults(SuggestedWords.MAX_SUGGESTIONS); - final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT }; + final SuggestionResults suggestionResults = new SuggestionResults( + SuggestedWords.MAX_SUGGESTIONS, + prevWordsInfo.mPrevWordsInfo[0].mIsBeginningOfSentence); + final float[] weightOfLangModelVsSpatialModel = + new float[] { Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL }; for (final DictionaryGroup dictionaryGroup : dictionaryGroups) { for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { final Dictionary dictionary = dictionaryGroup.getDict(dictType); if (null == dictionary) continue; final ArrayList<SuggestedWordInfo> dictionarySuggestions = dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo, - settingsValuesForSuggestion, sessionId, languageWeight); + settingsValuesForSuggestion, sessionId, + dictionaryGroup.mWeightForLocale, weightOfLangModelVsSpatialModel); if (null == dictionarySuggestions) continue; suggestionResults.addAll(dictionarySuggestions); if (null != suggestionResults.mRawSuggestions) { @@ -706,6 +710,7 @@ public class DictionaryFacilitator { getLocale(), personalizationDataChunk, spacingAndPunctuations, callback); } + @UsedForTesting public void addPhraseToContextualDictionary(final String[] phrase, final int probability, final int bigramProbabilityForWords, final int bigramProbabilityForPhrases) { // TODO: we're inserting the phrase into the dictionary for the active language. Rethink diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index 671ba6714..ad967c133 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -435,7 +435,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo, final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId, - final float[] inOutLanguageWeight) { + final float weightForLocale, final float[] inOutWeightOfLangModelVsSpatialModel) { reloadDictionaryIfRequired(); boolean lockAcquired = false; try { @@ -447,7 +447,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } final ArrayList<SuggestedWordInfo> suggestions = mBinaryDictionary.getSuggestions(composer, prevWordsInfo, proximityInfo, - settingsValuesForSuggestion, sessionId, inOutLanguageWeight); + settingsValuesForSuggestion, sessionId, weightForLocale, + inOutWeightOfLangModelVsSpatialModel); if (mBinaryDictionary.isCorrupted()) { Log.i(TAG, "Dictionary (" + mDictName +") is corrupted. " + "Remove and regenerate it."); diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java index 50a6e48e3..ffd363b5d 100644 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ b/java/src/com/android/inputmethod/latin/InputAttributes.java @@ -112,7 +112,7 @@ public final class InputAttributes { mShouldInsertSpacesAutomatically = InputTypeUtils.isAutoSpaceFriendlyType(inputType); final boolean noMicrophone = mIsPasswordField - || InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS == variation + || InputTypeUtils.isEmailVariation(variation) || InputType.TYPE_TEXT_VARIATION_URI == variation || hasNoMicrophoneKeyOption(); mShouldShowVoiceInputKey = !noMicrophone; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 475782042..69fe6de9a 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -189,9 +189,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private static final int MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED = 6; private static final int MSG_RESET_CACHES = 7; private static final int MSG_WAIT_FOR_DICTIONARY_LOAD = 8; - private static final int MSG_SHOW_COMMIT_INDICATOR = 9; // Update this when adding new messages - private static final int MSG_LAST = MSG_SHOW_COMMIT_INDICATOR; + private static final int MSG_LAST = MSG_WAIT_FOR_DICTIONARY_LOAD; private static final int ARG1_NOT_GESTURE_INPUT = 0; private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; @@ -202,7 +201,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private int mDelayInMillisecondsToUpdateSuggestions; private int mDelayInMillisecondsToUpdateShiftState; - private int mDelayInMillisecondsToShowCommitIndicator; public UIHandler(final LatinIME ownerInstance) { super(ownerInstance); @@ -218,8 +216,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen R.integer.config_delay_in_milliseconds_to_update_suggestions); mDelayInMillisecondsToUpdateShiftState = res.getInteger( R.integer.config_delay_in_milliseconds_to_update_shift_state); - mDelayInMillisecondsToShowCommitIndicator = res.getInteger( - R.integer.text_decorator_delay_in_milliseconds_to_show_commit_indicator); } @Override @@ -277,14 +273,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen latinIme.getCurrentRecapitalizeState()); } break; - case MSG_SHOW_COMMIT_INDICATOR: - // Protocol of MSG_SET_COMMIT_INDICATOR_ENABLED: - // - what: MSG_SHOW_COMMIT_INDICATOR - // - arg1: not used. - // - arg2: not used. - // - obj: the Runnable object to be called back. - ((Runnable) msg.obj).run(); - break; case MSG_WAIT_FOR_DICTIONARY_LOAD: Log.i(TAG, "Timeout waiting for dictionary load"); break; @@ -385,19 +373,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget(); } - /** - * Posts a delayed task to show the commit indicator. - * - * <p>Only one task can exist in the queue. When this method is called, any prior task that - * has not yet fired will be canceled.</p> - * @param task the runnable object that will be fired when the delayed task is dispatched. - */ - public void postShowCommitIndicatorTask(final Runnable task) { - removeMessages(MSG_SHOW_COMMIT_INDICATOR); - sendMessageDelayed(obtainMessage(MSG_SHOW_COMMIT_INDICATOR, task), - mDelayInMillisecondsToShowCommitIndicator); - } - // Working variables for the following methods. private boolean mIsOrientationChanging; private boolean mPendingSuccessiveImsCallback; @@ -1516,19 +1491,23 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final boolean isEmptyApplicationSpecifiedCompletions = currentSettingsValues.isApplicationSpecifiedCompletionsOn() && suggestedWords.isEmpty(); - final boolean noSuggestionsToShow = (SuggestedWords.EMPTY == suggestedWords) + final boolean noSuggestionsFromDictionaries = (SuggestedWords.EMPTY == suggestedWords) || suggestedWords.isPunctuationSuggestions() || isEmptyApplicationSpecifiedCompletions; - if (shouldShowImportantNotice && noSuggestionsToShow) { + final boolean isBeginningOfSentencePrediction = (suggestedWords.mInputStyle + == SuggestedWords.INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION); + final boolean noSuggestionsToOverrideImportantNotice = noSuggestionsFromDictionaries + || isBeginningOfSentencePrediction; + if (shouldShowImportantNotice && noSuggestionsToOverrideImportantNotice) { if (mSuggestionStripView.maybeShowImportantNoticeTitle()) { return; } } if (currentSettingsValues.isSuggestionsEnabledPerUserSettings() - // We should clear suggestions if there is no suggestion to show. - || noSuggestionsToShow - || currentSettingsValues.isApplicationSpecifiedCompletionsOn()) { + || currentSettingsValues.isApplicationSpecifiedCompletionsOn() + // We should clear the contextual strip if there is no suggestion from dictionaries. + || noSuggestionsFromDictionaries) { mSuggestionStripView.setSuggestions(suggestedWords, SubtypeLocaleUtils.isRtlLanguage(mSubtypeSwitcher.getCurrentSubtype())); } diff --git a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java index db877ab7a..76d4f57da 100644 --- a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java +++ b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java @@ -86,33 +86,30 @@ public class PrevWordsInfo { // For simplicity of implementation, elements may also be EMPTY_WORD_INFO transiently after the // WordComposer was reset and before starting a new composing word, but we should never be // calling getSuggetions* in this situation. - public WordInfo[] mPrevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; + public final WordInfo[] mPrevWordsInfo; // Construct from the previous word information. public PrevWordsInfo(final WordInfo prevWordInfo) { - mPrevWordsInfo[0] = prevWordInfo; + mPrevWordsInfo = new WordInfo[] { prevWordInfo }; } // Construct from WordInfo array. n-th element represents (n+1)-th previous word's information. public PrevWordsInfo(final WordInfo[] prevWordsInfo) { - for (int i = 0; i < Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM; i++) { - mPrevWordsInfo[i] = - (prevWordsInfo.length > i) ? prevWordsInfo[i] : WordInfo.EMPTY_WORD_INFO; - } + mPrevWordsInfo = prevWordsInfo; } // Create next prevWordsInfo using current prevWordsInfo. public PrevWordsInfo getNextPrevWordsInfo(final WordInfo wordInfo) { - final WordInfo[] prevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; + final int nextPrevWordCount = Math.min(Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM, + mPrevWordsInfo.length + 1); + final WordInfo[] prevWordsInfo = new WordInfo[nextPrevWordCount]; prevWordsInfo[0] = wordInfo; - for (int i = 1; i < prevWordsInfo.length; i++) { - prevWordsInfo[i] = mPrevWordsInfo[i - 1]; - } + System.arraycopy(mPrevWordsInfo, 0, prevWordsInfo, 1, prevWordsInfo.length - 1); return new PrevWordsInfo(prevWordsInfo); } public boolean isValid() { - return mPrevWordsInfo[0].isValid(); + return mPrevWordsInfo.length > 0 && mPrevWordsInfo[0].isValid(); } public void outputToArray(final int[][] codePointArrays, @@ -129,9 +126,14 @@ public class PrevWordsInfo { } } + public int getPrevWordCount() { + return mPrevWordsInfo.length; + } + @Override public int hashCode() { - return Arrays.hashCode(mPrevWordsInfo); + // Just for having equals(). + return mPrevWordsInfo[0].hashCode(); } @Override @@ -139,7 +141,23 @@ public class PrevWordsInfo { if (this == o) return true; if (!(o instanceof PrevWordsInfo)) return false; final PrevWordsInfo prevWordsInfo = (PrevWordsInfo)o; - return Arrays.equals(mPrevWordsInfo, prevWordsInfo.mPrevWordsInfo); + + final int minLength = Math.min(mPrevWordsInfo.length, prevWordsInfo.mPrevWordsInfo.length); + for (int i = 0; i < minLength; i++) { + if (!mPrevWordsInfo[i].equals(prevWordsInfo.mPrevWordsInfo[i])) { + return false; + } + } + final WordInfo[] longerWordsInfo = + (mPrevWordsInfo.length > prevWordsInfo.mPrevWordsInfo.length) ? + mPrevWordsInfo : prevWordsInfo.mPrevWordsInfo; + for (int i = minLength; i < longerWordsInfo.length; i++) { + if (longerWordsInfo[i] != null + && !WordInfo.EMPTY_WORD_INFO.equals(longerWordsInfo[i])) { + return false; + } + } + return true; } @Override @@ -150,7 +168,11 @@ public class PrevWordsInfo { builder.append("PrevWord["); builder.append(i); builder.append("]: "); - if (wordInfo == null || !wordInfo.isValid()) { + if (wordInfo == null) { + builder.append("null. "); + continue; + } + if (!wordInfo.isValid()) { builder.append("Empty. "); continue; } diff --git a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java index ecf25c28b..827367bb4 100644 --- a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java @@ -53,11 +53,13 @@ public final class ReadOnlyBinaryDictionary extends Dictionary { public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo, final SettingsValuesForSuggestion settingsValuesForSuggestion, - final int sessionId, final float[] inOutLanguageWeight) { + final int sessionId, final float weightForLocale, + final float[] inOutWeightOfLangModelVsSpatialModel) { if (mLock.readLock().tryLock()) { try { return mBinaryDictionary.getSuggestions(composer, prevWordsInfo, proximityInfo, - settingsValuesForSuggestion, sessionId, inOutLanguageWeight); + settingsValuesForSuggestion, sessionId, weightForLocale, + inOutWeightOfLangModelVsSpatialModel); } finally { mLock.readLock().unlock(); } diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index dc00ecc8f..d672430a1 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -252,7 +252,7 @@ public final class RichInputConnection { * See {@link InputConnection#commitText(CharSequence, int)}. */ public void commitText(final CharSequence text, final int newCursorPosition) { - commitTextWithBackgroundColor(text, newCursorPosition, Color.TRANSPARENT); + commitTextWithBackgroundColor(text, newCursorPosition, Color.TRANSPARENT, text.length()); } /** @@ -265,9 +265,11 @@ public final class RichInputConnection { * the background color. Note that this method specifies {@link BackgroundColorSpan} with * {@link Spanned#SPAN_COMPOSING} flag, meaning that the background color persists until * {@link #finishComposingText()} is called. + * @param coloredTextLength the length of text, in Java chars, which should be rendered with + * the given background color. */ public void commitTextWithBackgroundColor(final CharSequence text, final int newCursorPosition, - final int color) { + final int color, final int coloredTextLength) { if (DEBUG_BATCH_NESTING) checkBatchEdit(); if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); mCommittedTextBeforeComposingText.append(text); @@ -285,7 +287,8 @@ public final class RichInputConnection { mTempObjectForCommitText.clear(); mTempObjectForCommitText.append(text); final BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(color); - mTempObjectForCommitText.setSpan(backgroundColorSpan, 0, text.length(), + final int spanLength = Math.min(coloredTextLength, text.length()); + mTempObjectForCommitText.setSpan(backgroundColorSpan, 0, spanLength, Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mIC.commitText(mTempObjectForCommitText, newCursorPosition); mLastCommittedTextHasBackgroundColor = true; diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 9bf017578..1ecc995b2 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -188,8 +188,14 @@ public final class Suggest { suggestionsList = suggestionsContainer; } - final int inputStyle = resultsArePredictions ? SuggestedWords.INPUT_STYLE_PREDICTION : - inputStyleIfNotPrediction; + final int inputStyle; + if (resultsArePredictions) { + inputStyle = suggestionResults.mIsBeginningOfSentence + ? SuggestedWords.INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION + : SuggestedWords.INPUT_STYLE_PREDICTION; + } else { + inputStyle = inputStyleIfNotPrediction; + } callback.onGetSuggestedWords(new SuggestedWords(suggestionsList, suggestionResults.mRawSuggestions, // TODO: this first argument is lying. If this is a whitelisted word which is an @@ -235,7 +241,7 @@ public final class Suggest { SuggestedWordInfo.removeDups(null /* typedWord */, suggestionsContainer); // For some reason some suggestions with MIN_VALUE are making their way here. - // TODO: Find a more robust way to detect distractors. + // TODO: Find a more robust way to detect distracters. for (int i = suggestionsContainer.size() - 1; i >= 0; --i) { if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) { suggestionsContainer.remove(i); @@ -244,6 +250,8 @@ public final class Suggest { // In the batch input mode, the most relevant suggested word should act as a "typed word" // (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false). + // Note that because this method is never used to get predictions, there is no need to + // modify inputType such in getSuggestedWordsForNonBatchInput. callback.onGetSuggestedWords(new SuggestedWords(suggestionsContainer, suggestionResults.mRawSuggestions, true /* typedWordValid */, diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index dcfaa3f6d..3eefafc1f 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -39,6 +39,7 @@ public class SuggestedWords { public static final int INPUT_STYLE_APPLICATION_SPECIFIED = 4; public static final int INPUT_STYLE_RECORRECTION = 5; public static final int INPUT_STYLE_PREDICTION = 6; + public static final int INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION = 7; // The maximum number of suggestions available. public static final int MAX_SUGGESTIONS = 18; @@ -80,10 +81,9 @@ public class SuggestedWords { final int inputStyle, final int sequenceNumber) { this(suggestedWordInfoList, rawSuggestions, - (suggestedWordInfoList.isEmpty() || INPUT_STYLE_PREDICTION == inputStyle) ? null + (suggestedWordInfoList.isEmpty() || isPrediction(inputStyle)) ? null : suggestedWordInfoList.get(INDEX_OF_TYPED_WORD).mWord, - typedWordValid, willAutoCorrect, isObsoleteSuggestions, inputStyle, - sequenceNumber); + typedWordValid, willAutoCorrect, isObsoleteSuggestions, inputStyle, sequenceNumber); } public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList, @@ -180,6 +180,7 @@ public class SuggestedWords { return "SuggestedWords:" + " mTypedWordValid=" + mTypedWordValid + " mWillAutoCorrect=" + mWillAutoCorrect + + " mInputStyle=" + mInputStyle + " words=" + Arrays.toString(mSuggestedWordInfoList.toArray()); } @@ -386,8 +387,13 @@ public class SuggestedWords { } } + private static boolean isPrediction(final int inputStyle) { + return INPUT_STYLE_PREDICTION == inputStyle + || INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION == inputStyle; + } + public boolean isPrediction() { - return INPUT_STYLE_PREDICTION == mInputStyle; + return isPrediction(mInputStyle); } // SuggestedWords is an immutable object, as much as possible. We must not just remove diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index 32d1fe372..567aa07f1 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -49,6 +49,7 @@ public final class WordComposer { private final ArrayList<Event> mEvents; private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH); private String mAutoCorrection; + private String mAutoCorrectionDictionaryType; private boolean mIsResumed; private boolean mIsBatchMode; // A memory of the last rejected batch mode suggestion, if any. This goes like this: the user @@ -418,8 +419,9 @@ public final class WordComposer { /** * Sets the auto-correction for this word. */ - public void setAutoCorrection(final String correction) { + public void setAutoCorrection(final String correction, String dictType) { mAutoCorrection = correction; + mAutoCorrectionDictionaryType = dictType; } /** @@ -430,6 +432,13 @@ public final class WordComposer { } /** + * @return the auto-correction dictionary type or null if none. + */ + public String getAutoCorrectionDictionaryTypeOrNull() { + return mAutoCorrectionDictionaryType; + } + + /** * @return whether we started composing this word by resuming suggestion on an existing string */ public boolean isResumed() { diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index c5e60d677..8eccd5cee 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -28,6 +28,7 @@ import android.util.Log; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.EditorInfo; import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper; @@ -91,12 +92,8 @@ public final class InputLogic { private final TextDecorator mTextDecorator = new TextDecorator(new TextDecorator.Listener() { @Override - public void onClickComposingTextToCommit(SuggestedWordInfo wordInfo) { - mLatinIME.pickSuggestionManually(wordInfo); - } - @Override - public void onClickComposingTextToAddToDictionary(SuggestedWordInfo wordInfo) { - mLatinIME.addWordToUserDictionary(wordInfo.mWord); + public void onClickComposingTextToAddToDictionary(final String word) { + mLatinIME.addWordToUserDictionary(word); mLatinIME.dismissAddToDictionaryHint(); } }); @@ -171,6 +168,7 @@ public final class InputLogic { mConnection.requestCursorUpdates(true /* enableMonitor */, true /* requestImmediateCallback */); } + mTextDecorator.reset(); } } @@ -207,6 +205,8 @@ public final class InputLogic { public void finishInput() { if (mWordComposer.isComposingWord()) { mConnection.finishComposingText(); + StatsUtils.onWordCommitUserTyped( + mWordComposer.getTypedWord(), mWordComposer.isBatchMode()); } resetComposingState(true /* alsoResetLastComposedWord */); mInputLogicHandler.reset(); @@ -253,6 +253,7 @@ public final class InputLogic { promotePhantomSpace(settingsValues); } mConnection.commitText(text, 1); + StatsUtils.onWordCommitUserTyped(mEnteredText, mWordComposer.isBatchMode()); mConnection.endBatchEdit(); // Space state must be updated before calling updateShiftState mSpaceState = SpaceState.NONE; @@ -334,17 +335,8 @@ public final class InputLogic { } final boolean shouldShowAddToDictionaryHint = shouldShowAddToDictionaryHint(suggestionInfo); - final boolean shouldShowAddToDictionaryIndicator = - shouldShowAddToDictionaryHint && settingsValues.mShouldShowUiToAcceptTypedWord; - final int backgroundColor; - if (shouldShowAddToDictionaryIndicator) { - backgroundColor = settingsValues.mTextHighlightColorForAddToDictionaryIndicator; - } else { - backgroundColor = Color.TRANSPARENT; - } - commitChosenWordWithBackgroundColor(settingsValues, suggestion, - LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR, - backgroundColor); + commitChosenWord(settingsValues, suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK, + LastComposedWord.NOT_A_SEPARATOR); mConnection.endBatchEdit(); // Don't allow cancellation of manual pick mLastComposedWord.deactivate(); @@ -359,11 +351,10 @@ public final class InputLogic { // That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE. handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE); } - if (shouldShowAddToDictionaryIndicator) { - mTextDecorator.showAddToDictionaryIndicator(suggestionInfo); - } StatsUtils.onPickSuggestionManually(mSuggestedWords, suggestionInfo); + StatsUtils.onWordCommitSuggestionPickedManually( + suggestionInfo.mWord, mWordComposer.isBatchMode()); return inputTransaction; } @@ -433,6 +424,9 @@ public final class InputLogic { mRecapitalizeStatus.enable(); // We moved the cursor and need to invalidate the indicator right now. mTextDecorator.reset(); + // Remaining background color that was used for the add-to-dictionary indicator should be + // removed. + mConnection.removeBackgroundColorFromHighlightedTextIfNecessary(); // We moved the cursor. If we are touching a word, we need to resume suggestion. mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */, true /* shouldDelay */); @@ -511,7 +505,9 @@ public final class InputLogic { handler.cancelUpdateSuggestionStrip(); ++mAutoCommitSequenceNumber; mConnection.beginBatchEdit(); - if (mWordComposer.isComposingWord()) { + if (!mWordComposer.isComposingWord()) { + mConnection.removeBackgroundColorFromHighlightedTextIfNecessary(); + } else { if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { // If we are in the middle of a recorrection, we need to commit the recorrection // first so that we can insert the batch input at the current cursor position. @@ -582,6 +578,7 @@ public final class InputLogic { batchPointers.shift(candidate.mIndexOfTouchPointOfSecondWord); promotePhantomSpace(settingsValues); mConnection.commitText(commitParts[0], 0); + StatsUtils.onWordCommitUserTyped(commitParts[0], mWordComposer.isBatchMode()); mSpaceState = SpaceState.PHANTOM; keyboardSwitcher.requestUpdatingShiftState( getCurrentAutoCapsState(settingsValues), getCurrentRecapitalizeState()); @@ -612,53 +609,24 @@ public final class InputLogic { final SettingsValues settingsValues, final LatinIME.UIHandler handler) { if (SuggestedWords.EMPTY != suggestedWords) { final String autoCorrection; + final String dictType; if (suggestedWords.mWillAutoCorrect) { - autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION); + SuggestedWordInfo info = suggestedWords.getInfo( + SuggestedWords.INDEX_OF_AUTO_CORRECTION); + autoCorrection = info.mWord; + dictType = info.mSourceDict.mDictType; } else { // We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD) // because it may differ from mWordComposer.mTypedWord. autoCorrection = suggestedWords.mTypedWord; + dictType = Dictionary.TYPE_USER_TYPED; } - mWordComposer.setAutoCorrection(autoCorrection); + // TODO: Use the SuggestedWordInfo to set the auto correction when + // user typed word is available via SuggestedWordInfo. + mWordComposer.setAutoCorrection(autoCorrection, dictType); } mSuggestedWords = suggestedWords; final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect; - if (shouldShowCommitIndicator(suggestedWords, settingsValues)) { - // typedWordInfo is never null here. - final int textBackgroundColor = settingsValues.mTextHighlightColorForCommitIndicator; - final SuggestedWordInfo typedWordInfo = suggestedWords.getTypedWordInfoOrNull(); - handler.postShowCommitIndicatorTask(new Runnable() { - @Override - public void run() { - // TODO: This needs to be refactored to ensure that mWordComposer is accessed - // only from the UI thread. - if (!mWordComposer.isComposingWord()) { - mTextDecorator.reset(); - return; - } - final SuggestedWordInfo currentTypedWordInfo = - mSuggestedWords.getTypedWordInfoOrNull(); - if (currentTypedWordInfo == null) { - mTextDecorator.reset(); - return; - } - if (!currentTypedWordInfo.equals(typedWordInfo)) { - // Suggested word has been changed. This task is obsolete. - mTextDecorator.reset(); - return; - } - // TODO: As with the above TODO comment, this operation must be performed only - // on the UI thread too. Needs to be refactored. - setComposingTextInternalWithBackgroundColor(typedWordInfo.mWord, - 1 /* newCursorPosition */, textBackgroundColor); - mTextDecorator.showCommitIndicator(typedWordInfo); - } - }); - } else { - // Note: It is OK to not cancel previous postShowCommitIndicatorTask() here. Having a - // cancellation mechanism could improve performance a bit though. - mTextDecorator.reset(); - } // Put a blue underline to a word in TextView which will be auto-corrected. if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator @@ -836,13 +804,14 @@ public final class InputLogic { final InputTransaction inputTransaction, // TODO: remove this argument final LatinIME.UIHandler handler) { - // In case the "add to dictionary" hint was still displayed. - // TODO: Do we really need to check if we have composing text here? - if (!mWordComposer.isComposingWord() && - mSuggestionStripViewAccessor.isShowingAddToDictionaryHint()) { - mSuggestionStripViewAccessor.dismissAddToDictionaryHint(); + if (!mWordComposer.isComposingWord()) { mConnection.removeBackgroundColorFromHighlightedTextIfNecessary(); - mTextDecorator.reset(); + // In case the "add to dictionary" hint was still displayed. + // TODO: Do we really need to check if we have composing text here? + if (mSuggestionStripViewAccessor.isShowingAddToDictionaryHint()) { + mSuggestionStripViewAccessor.dismissAddToDictionaryHint(); + mTextDecorator.reset(); + } } final int codePoint = event.mCodePoint; @@ -1101,8 +1070,10 @@ public final class InputLogic { inputTransaction.setRequiresUpdateSuggestions(); } else { if (mLastComposedWord.canRevertCommit()) { - revertCommit(inputTransaction); + final String lastComposedWord = mLastComposedWord.mTypedWord; + revertCommit(inputTransaction, inputTransaction.mSettingsValues); StatsUtils.onRevertAutoCorrect(); + StatsUtils.onWordCommitUserTyped(lastComposedWord, mWordComposer.isBatchMode()); return; } if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) { @@ -1602,14 +1573,19 @@ public final class InputLogic { * This is triggered upon pressing backspace just after a commit with auto-correction. * * @param inputTransaction The transaction in progress. + * @param settingsValues the current values of the settings. */ - private void revertCommit(final InputTransaction inputTransaction) { + private void revertCommit(final InputTransaction inputTransaction, + final SettingsValues settingsValues) { final CharSequence originallyTypedWord = mLastComposedWord.mTypedWord; + final String originallyTypedWordString = + originallyTypedWord != null ? originallyTypedWord.toString() : ""; final CharSequence committedWord = mLastComposedWord.mCommittedWord; final String committedWordString = committedWord.toString(); final int cancelLength = committedWord.length(); + final String separatorString = mLastComposedWord.mSeparatorString; // We want java chars, not codepoints for the following. - final int separatorLength = mLastComposedWord.mSeparatorString.length(); + final int separatorLength = separatorString.length(); // TODO: should we check our saved separator against the actual contents of the text view? final int deleteLength = cancelLength + separatorLength; if (DebugFlags.DEBUG_ENABLED) { @@ -1628,7 +1604,7 @@ public final class InputLogic { if (!TextUtils.isEmpty(committedWord)) { mDictionaryFacilitator.removeWordFromPersonalizedDicts(committedWordString); } - final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString; + final String stringToCommit = originallyTypedWord + separatorString; final SpannableString textToCommit = new SpannableString(stringToCommit); if (committedWord instanceof SpannableString) { final SpannableString committedWordWithSuggestionSpans = (SpannableString)committedWord; @@ -1665,23 +1641,53 @@ public final class InputLogic { suggestions.toArray(new String[suggestions.size()]), 0 /* flags */), 0 /* start */, lastCharIndex /* end */, 0 /* flags */); } + + final boolean shouldShowAddToDictionaryForTypedWord = + shouldShowAddToDictionaryForTypedWord(mLastComposedWord, settingsValues); + if (inputTransaction.mSettingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) { // For languages with spaces, we revert to the typed string, but the cursor is still // after the separator so we don't resume suggestions. If the user wants to correct // the word, they have to press backspace again. - mConnection.commitText(textToCommit, 1); + if (shouldShowAddToDictionaryForTypedWord) { + mConnection.commitTextWithBackgroundColor(textToCommit, 1, + settingsValues.mTextHighlightColorForAddToDictionaryIndicator, + originallyTypedWordString.length()); + } else { + mConnection.commitText(textToCommit, 1); + } } else { // For languages without spaces, we revert the typed string but the cursor is flush // with the typed word, so we need to resume suggestions right away. final int[] codePoints = StringUtils.toCodePointArray(stringToCommit); mWordComposer.setComposingWord(codePoints, mLatinIME.getCoordinatesForCurrentKeyboard(codePoints)); - setComposingTextInternal(textToCommit, 1); + if (shouldShowAddToDictionaryForTypedWord) { + setComposingTextInternalWithBackgroundColor(textToCommit, 1, + settingsValues.mTextHighlightColorForAddToDictionaryIndicator, + originallyTypedWordString.length()); + } else { + setComposingTextInternal(textToCommit, 1); + } } // Don't restart suggestion yet. We'll restart if the user deletes the separator. mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; - // We have a separator between the word and the cursor: we should show predictions. - inputTransaction.setRequiresUpdateSuggestions(); + + if (shouldShowAddToDictionaryForTypedWord) { + // Due to the API limitation as of L, we cannot reliably retrieve the reverted text + // when the separator causes line breaking. Until this API limitation is addressed in + // the framework, show the indicator only when the separator doesn't contain + // line-breaking characters. + if (!StringUtils.hasLineBreakCharacter(separatorString)) { + mTextDecorator.showAddToDictionaryIndicator(originallyTypedWordString, + mConnection.getExpectedSelectionStart(), + mConnection.getExpectedSelectionEnd()); + } + mSuggestionStripViewAccessor.showAddToDictionaryHint(originallyTypedWordString); + } else { + // We have a separator between the word and the cursor: we should show predictions. + inputTransaction.setRequiresUpdateSuggestions(); + } } /** @@ -2003,6 +2009,8 @@ public final class InputLogic { final int indexOfLastSpace = batchInputText.lastIndexOf(Constants.CODE_SPACE) + 1; if (0 != indexOfLastSpace) { mConnection.commitText(batchInputText.substring(0, indexOfLastSpace), 1); + StatsUtils.onWordCommitUserTyped( + batchInputText.substring(0, indexOfLastSpace), mWordComposer.isBatchMode()); final SuggestedWords suggestedWordsForLastWordOfPhraseGesture = suggestedWords.getSuggestedWordsForLastWordOfPhraseGesture(); mLatinIME.showSuggestionStrip(suggestedWordsForLastWordOfPhraseGesture); @@ -2041,8 +2049,10 @@ public final class InputLogic { if (!mWordComposer.isComposingWord()) return; final String typedWord = mWordComposer.getTypedWord(); if (typedWord.length() > 0) { + final boolean isBatchMode = mWordComposer.isBatchMode(); commitChosenWord(settingsValues, typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, separatorString); + StatsUtils.onWordCommitUserTyped(typedWord, isBatchMode); } } @@ -2088,6 +2098,7 @@ public final class InputLogic { throw new RuntimeException("We have an auto-correction but the typed word " + "is empty? Impossible! I must commit suicide."); } + final boolean isBatchMode = mWordComposer.isBatchMode(); commitChosenWord(settingsValues, autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD, separator); if (!typedWord.equals(autoCorrection)) { @@ -2100,14 +2111,17 @@ public final class InputLogic { mConnection.commitCorrection(new CorrectionInfo( mConnection.getExpectedSelectionEnd() - autoCorrection.length(), typedWord, autoCorrection)); + StatsUtils.onAutoCorrection(typedWord, autoCorrection, isBatchMode, + mWordComposer.getAutoCorrectionDictionaryTypeOrNull()); + StatsUtils.onWordCommitAutoCorrect(autoCorrection, isBatchMode); + } else { + StatsUtils.onWordCommitUserTyped(autoCorrection, isBatchMode); } } } /** - * Commits the chosen word to the text field and saves it for later retrieval. This is a - * synonym of {@code commitChosenWordWithBackgroundColor(settingsValues, chosenWord, - * commitType, separatorString, Color.TRANSPARENT}. + * Commits the chosen word to the text field and saves it for later retrieval. * * @param settingsValues the current values of the settings. * @param chosenWord the word we want to commit. @@ -2116,23 +2130,6 @@ public final class InputLogic { */ private void commitChosenWord(final SettingsValues settingsValues, final String chosenWord, final int commitType, final String separatorString) { - commitChosenWordWithBackgroundColor(settingsValues, chosenWord, commitType, separatorString, - Color.TRANSPARENT); - } - - /** - * Commits the chosen word to the text field and saves it for later retrieval. - * - * @param settingsValues the current values of the settings. - * @param chosenWord the word we want to commit. - * @param commitType the type of the commit, as one of LastComposedWord.COMMIT_TYPE_* - * @param separatorString the separator that's causing the commit, or NOT_A_SEPARATOR if none. - * @param backgroundColor the background color to be specified with the committed text. Pass - * {@link Color#TRANSPARENT} to not specify the background color. - */ - private void commitChosenWordWithBackgroundColor(final SettingsValues settingsValues, - final String chosenWord, final int commitType, final String separatorString, - final int backgroundColor) { final SuggestedWords suggestedWords = mSuggestedWords; final CharSequence chosenWordWithSuggestions = SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord, @@ -2142,7 +2139,7 @@ public final class InputLogic { // information from the 1st previous word. final PrevWordsInfo prevWordsInfo = mConnection.getPrevWordsInfoFromNthPreviousWord( settingsValues.mSpacingAndPunctuations, mWordComposer.isComposingWord() ? 2 : 1); - mConnection.commitTextWithBackgroundColor(chosenWordWithSuggestions, 1, backgroundColor); + mConnection.commitText(chosenWordWithSuggestions, 1); // Add the word to the user history dictionary performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWordsInfo); // TODO: figure out here if this is an auto-correct or if the best word is actually @@ -2226,7 +2223,7 @@ public final class InputLogic { private void setComposingTextInternal(final CharSequence newComposingText, final int newCursorPosition) { setComposingTextInternalWithBackgroundColor(newComposingText, newCursorPosition, - Color.TRANSPARENT); + Color.TRANSPARENT, newComposingText.length()); } /** @@ -2242,9 +2239,11 @@ public final class InputLogic { * @param newCursorPosition the new cursor position * @param backgroundColor the background color to be set to the composing text. Set * {@link Color#TRANSPARENT} to disable the background color. + * @param coloredTextLength the length of text, in Java chars, which should be rendered with + * the given background color. */ private void setComposingTextInternalWithBackgroundColor(final CharSequence newComposingText, - final int newCursorPosition, final int backgroundColor) { + final int newCursorPosition, final int backgroundColor, final int coloredTextLength) { final CharSequence composingTextToBeSet; if (backgroundColor == Color.TRANSPARENT) { composingTextToBeSet = newComposingText; @@ -2252,7 +2251,8 @@ public final class InputLogic { final SpannableString spannable = new SpannableString(newComposingText); final BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(backgroundColor); - spannable.setSpan(backgroundColorSpan, 0, spannable.length(), + final int spanLength = Math.min(coloredTextLength, spannable.length()); + spannable.setSpan(backgroundColorSpan, 0, spanLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); composingTextToBeSet = spannable; } @@ -2274,7 +2274,8 @@ public final class InputLogic { } /** - * Must be called from {@link InputMethodService#onUpdateCursorAnchorInfo} is called. + * Must be called from {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} is + * called. * @param info The wrapper object with which we can access cursor/anchor info. */ public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) { @@ -2298,12 +2299,12 @@ public final class InputLogic { } /** - * Returns whether the commit indicator should be shown or not. - * @param suggestedWords the suggested word that is being displayed. + * Returns whether the add to dictionary indicator should be shown or not. + * @param lastComposedWord the last composed word information. * @param settingsValues the current settings value. * @return {@code true} if the commit indicator should be shown. */ - private boolean shouldShowCommitIndicator(final SuggestedWords suggestedWords, + private boolean shouldShowAddToDictionaryForTypedWord(final LastComposedWord lastComposedWord, final SettingsValues settingsValues) { if (!mConnection.isCursorAnchorInfoMonitorEnabled()) { // We cannot help in this case because we are heavily relying on this new API. @@ -2312,24 +2313,16 @@ public final class InputLogic { if (!settingsValues.mShouldShowUiToAcceptTypedWord) { return false; } - final SuggestedWordInfo typedWordInfo = suggestedWords.getTypedWordInfoOrNull(); - if (typedWordInfo == null) { + if (TextUtils.isEmpty(lastComposedWord.mTypedWord)) { return false; } - if (suggestedWords.mInputStyle != SuggestedWords.INPUT_STYLE_TYPING){ + if (TextUtils.equals(lastComposedWord.mTypedWord, lastComposedWord.mCommittedWord)) { return false; } - if (settingsValues.mShowCommitIndicatorOnlyForAutoCorrection - && !suggestedWords.mWillAutoCorrect) { + if (!mDictionaryFacilitator.isUserDictionaryEnabled()) { return false; } - // TODO: Calling shouldShowAddToDictionaryHint(typedWordInfo) multiple times should be fine - // in terms of performance, but we can do better. One idea is to make SuggestedWords include - // a boolean that tells whether the word is a dictionary word or not. - if (settingsValues.mShowCommitIndicatorOnlyForOutOfVocabulary - && !shouldShowAddToDictionaryHint(typedWordInfo)) { - return false; - } - return true; + return !mDictionaryFacilitator.isValidWord(lastComposedWord.mTypedWord, + true /* ignoreCase */); } } diff --git a/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java new file mode 100644 index 000000000..06ab1e2d2 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.settings; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; +import android.text.TextUtils; +import android.widget.ListView; +import android.widget.Toast; + +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.SubtypeSwitcher; +import com.android.inputmethod.latin.define.ProductionFlags; +import com.android.inputmethod.latin.utils.LoginAccountUtils; + +import javax.annotation.Nullable; + +/** + * "Accounts & Privacy" settings sub screen. + * + * This settings sub screen handles the following preferences: + * <li> Account selection/management for IME + * <li> TODO: Sync preferences + * <li> TODO: Privacy preferences + */ +public final class AccountsSettingsFragment extends SubScreenFragment { + static final String PREF_ACCCOUNT_SWITCHER = "account_switcher"; + + private final DialogInterface.OnClickListener mAccountSelectedListener = + new AccountSelectedListener(); + private final DialogInterface.OnClickListener mAccountSignedOutListener = + new AccountSignedOutListener(); + + @Override + public void onCreate(final Bundle icicle) { + super.onCreate(icicle); + addPreferencesFromResource(R.xml.prefs_screen_accounts); + + final Resources res = getResources(); + final Context context = getActivity(); + + // When we are called from the Settings application but we are not already running, some + // singleton and utility classes may not have been initialized. We have to call + // initialization method of these classes here. See {@link LatinIME#onCreate()}. + SubtypeSwitcher.init(context); + + if (ProductionFlags.IS_METRICS_LOGGING_SUPPORTED) { + final Preference enableMetricsLogging = + findPreference(Settings.PREF_ENABLE_METRICS_LOGGING); + if (enableMetricsLogging != null) { + final String enableMetricsLoggingTitle = res.getString( + R.string.enable_metrics_logging, getApplicationName()); + enableMetricsLogging.setTitle(enableMetricsLoggingTitle); + } + } else { + removePreference(Settings.PREF_ENABLE_METRICS_LOGGING); + } + } + + @Override + public void onResume() { + super.onResume(); + refreshAccountSelection(); + } + + @Override + public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { + // TODO: Look at the preference that changed before refreshing the view. + refreshAccountSelection(); + } + + private void refreshAccountSelection() { + final String currentAccount = getCurrentlySelectedAccount(); + final Preference accountSwitcher = findPreference(PREF_ACCCOUNT_SWITCHER); + if (currentAccount == null) { + // No account is currently selected. + accountSwitcher.setSummary(getString(R.string.no_accounts_selected)); + } else { + // Set the currently selected account. + accountSwitcher.setSummary(getString(R.string.account_selected, currentAccount)); + } + final Context context = getActivity(); + final String[] accountsForLogin = LoginAccountUtils.getAccountsForLogin(context); + accountSwitcher.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + if (accountsForLogin.length == 0) { + // TODO: Handle account addition. + Toast.makeText(getActivity(), + getString(R.string.account_select_cancel), Toast.LENGTH_SHORT).show(); + } else { + createAccountPicker(accountsForLogin, currentAccount).show(); + } + return true; + } + }); + + // TODO: Depending on the account selection, enable/disable preferences that + // depend on an account. + } + + @Nullable + private String getCurrentlySelectedAccount() { + return getSharedPreferences().getString(Settings.PREF_ACCOUNT_NAME, null); + } + + /** + * Creates an account picker dialog showing the given accounts in a list and selecting + * the selected account by default. + * The list of accounts must not be null/empty. + * + * Package-private for testing. + */ + AlertDialog createAccountPicker(final String[] accounts, + final String selectedAccount) { + if (accounts == null || accounts.length == 0) { + throw new IllegalArgumentException("List of accounts must not be empty"); + } + + // See if the currently selected account is in the list. + // If it is, the entry is selected, and a sign-out button is provided. + // If it isn't, select the 0th account by default which will get picked up + // if the user presses OK. + int index = 0; + boolean isSignedIn = false; + for (int i = 0; i < accounts.length; i++) { + if (TextUtils.equals(accounts[i], selectedAccount)) { + index = i; + isSignedIn = true; + break; + } + } + final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.account_select_title) + .setSingleChoiceItems(accounts, index, null) + .setPositiveButton(R.string.account_select_ok, mAccountSelectedListener) + .setNegativeButton(R.string.account_select_cancel, null); + if (isSignedIn) { + builder.setNeutralButton(R.string.account_select_sign_out, mAccountSignedOutListener); + } + return builder.create(); + } + + /** + * Listener for an account being selected from the picker. + * Persists the account to shared preferences. + */ + class AccountSelectedListener implements DialogInterface.OnClickListener { + @Override + public void onClick(DialogInterface dialog, int which) { + final ListView lv = ((AlertDialog)dialog).getListView(); + final Object selectedItem = lv.getItemAtPosition(lv.getCheckedItemPosition()); + getSharedPreferences() + .edit() + .putString(Settings.PREF_ACCOUNT_NAME, (String) selectedItem) + .apply(); + } + } + + /** + * Listener for sign-out being initiated from from the picker. + * Removed the account from shared preferences. + */ + class AccountSignedOutListener implements DialogInterface.OnClickListener { + @Override + public void onClick(DialogInterface dialog, int which) { + getSharedPreferences() + .edit() + .remove(Settings.PREF_ACCOUNT_NAME) + .apply(); + } + } +} diff --git a/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java index 00f2c73dd..a6cb55db1 100644 --- a/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java @@ -93,14 +93,16 @@ public final class AdvancedSettingsFragment extends SubScreenFragment { removePreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON); } + // If metrics logging isn't supported, or account sign in is enabled + // don't show the logging preference. + // TODO: Eventually when we enable account sign in by default, + // we'll remove logging preference from here. if (ProductionFlags.IS_METRICS_LOGGING_SUPPORTED) { final Preference enableMetricsLogging = findPreference(Settings.PREF_ENABLE_METRICS_LOGGING); if (enableMetricsLogging != null) { - final int applicationLabelRes = context.getApplicationInfo().labelRes; - final String applicationName = res.getString(applicationLabelRes); final String enableMetricsLoggingTitle = res.getString( - R.string.enable_metrics_logging, applicationName); + R.string.enable_metrics_logging, getApplicationName()); enableMetricsLogging.setTitle(enableMetricsLoggingTitle); } } else { diff --git a/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java index f5e4d33a2..a9884ba13 100644 --- a/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/AppearanceSettingsFragment.java @@ -19,6 +19,7 @@ package com.android.inputmethod.latin.settings; import android.os.Bundle; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.define.ProductionFlags; /** @@ -29,6 +30,10 @@ public final class AppearanceSettingsFragment extends SubScreenFragment { public void onCreate(final Bundle icicle) { super.onCreate(icicle); addPreferencesFromResource(R.xml.prefs_screen_appearance); + if (!ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED + || !Settings.getInstance().getCurrent().isTablet()) { + removePreference(Settings.PREF_ENABLE_SPLIT_KEYBOARD); + } } @Override @@ -38,4 +43,4 @@ public final class AppearanceSettingsFragment extends SubScreenFragment { findPreference(Settings.PREF_CUSTOM_INPUT_STYLES)); ThemeSettingsFragment.updateKeyboardThemeSummary(findPreference(Settings.SCREEN_THEME)); } -} +}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java index 0de2d8831..a171fc330 100644 --- a/java/src/com/android/inputmethod/latin/settings/Settings.java +++ b/java/src/com/android/inputmethod/latin/settings/Settings.java @@ -43,6 +43,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang private static final String TAG = Settings.class.getSimpleName(); // Settings screens public static final String SCREEN_PREFERENCES = "screen_preferences"; + public static final String SCREEN_ACCOUNTS = "screen_accounts"; public static final String SCREEN_APPEARANCE = "screen_appearance"; public static final String SCREEN_THEME = "screen_theme"; public static final String SCREEN_MULTILINGUAL = "screen_multilingual"; @@ -83,6 +84,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang "pref_include_other_imes_in_language_switch_list"; public static final String PREF_KEYBOARD_THEME = "pref_keyboard_theme"; public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles"; + public static final String PREF_ENABLE_SPLIT_KEYBOARD = "pref_split_keyboard"; // TODO: consolidate key preview dismiss delay with the key preview animation parameters. public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY = "pref_key_preview_popup_dismiss_delay"; @@ -103,6 +105,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_KEY_IS_INTERNAL = "pref_key_is_internal"; public static final String PREF_ENABLE_METRICS_LOGGING = "pref_enable_metrics_logging"; + public static final String PREF_ACCOUNT_NAME = "pref_account_name"; // This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead. // This is being used only for the backward compatibility. diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java index 4fc17387f..8c4801798 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java @@ -25,6 +25,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.define.ProductionFlags; import com.android.inputmethod.latin.utils.ApplicationUtils; import com.android.inputmethod.latin.utils.FeedbackUtils; import com.android.inputmethodcommon.InputMethodSettingsFragment; @@ -51,6 +52,10 @@ public final class SettingsFragment extends InputMethodSettingsFragment { final Preference multilingualOptions = findPreference(Settings.SCREEN_MULTILINGUAL); preferenceScreen.removePreference(multilingualOptions); } + if (!ProductionFlags.ENABLE_ACCOUNT_SIGN_IN) { + final Preference accountsPreference = findPreference(Settings.SCREEN_ACCOUNTS); + preferenceScreen.removePreference(accountsPreference); + } } @Override diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java index 270b22a4a..3339ab57f 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java @@ -25,6 +25,7 @@ import android.util.Log; import android.view.inputmethod.EditorInfo; import com.android.inputmethod.compat.AppWorkaroundsUtils; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodManager; @@ -79,6 +80,9 @@ public class SettingsValues { public final int mKeyLongpressTimeout; public final boolean mEnableMetricsLogging; public final boolean mShouldShowUiToAcceptTypedWord; + // Use split layout for keyboard. + public final boolean mIsSplitKeyboardEnabled; + public final int mScreenMetrics; // From the input box public final InputAttributes mInputAttributes; @@ -98,10 +102,7 @@ public class SettingsValues { new int[AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE]; // TextDecorator - public final int mTextHighlightColorForCommitIndicator; public final int mTextHighlightColorForAddToDictionaryIndicator; - public final boolean mShowCommitIndicatorOnlyForAutoCorrection; - public final boolean mShowCommitIndicatorOnlyForOutOfVocabulary; // Debug settings public final boolean mIsInternal; @@ -157,6 +158,9 @@ public class SettingsValues { mDoubleSpacePeriodTimeout = res.getInteger(R.integer.config_double_space_period_timeout); mHasHardwareKeyboard = Settings.readHasHardwareKeyboard(res.getConfiguration()); mEnableMetricsLogging = prefs.getBoolean(Settings.PREF_ENABLE_METRICS_LOGGING, true); + mIsSplitKeyboardEnabled = prefs.getBoolean(Settings.PREF_ENABLE_SPLIT_KEYBOARD, false); + mScreenMetrics = res.getInteger(R.integer.config_screen_metrics); + mShouldShowUiToAcceptTypedWord = Settings.HAS_UI_TO_ACCEPT_TYPED_WORD && prefs.getBoolean(DebugSettings.PREF_SHOW_UI_TO_ACCEPT_TYPED_WORD, true); // Compute other readable settings @@ -176,12 +180,6 @@ public class SettingsValues { mSuggestionsEnabledPerUserSettings = readSuggestionsEnabled(prefs); AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray( prefs, mAdditionalFeaturesSettingValues); - mShowCommitIndicatorOnlyForAutoCorrection = res.getBoolean( - R.bool.text_decorator_only_for_auto_correction); - mShowCommitIndicatorOnlyForOutOfVocabulary = res.getBoolean( - R.bool.text_decorator_only_for_out_of_vocabulary); - mTextHighlightColorForCommitIndicator = res.getColor( - R.color.text_decorator_commit_indicator_text_highlight_color); mTextHighlightColorForAddToDictionaryIndicator = res.getColor( R.color.text_decorator_add_to_dictionary_indicator_text_highlight_color); mIsInternal = Settings.isInternal(prefs); @@ -225,6 +223,11 @@ public class SettingsValues { return mEnableMetricsLogging; } + public boolean isTablet() { + return mScreenMetrics == Constants.SCREEN_METRICS_SMALL_TABLET + || mScreenMetrics == Constants.SCREEN_METRICS_LARGE_TABLET; + } + public boolean isApplicationSpecifiedCompletionsOn() { return mInputAttributes.mApplicationSpecifiedCompletionOn; } @@ -431,12 +434,6 @@ public class SettingsValues { sb.append("" + (null == awu ? "null" : awu.toString())); sb.append("\n mAdditionalFeaturesSettingValues = "); sb.append("" + Arrays.toString(mAdditionalFeaturesSettingValues)); - sb.append("\n mShowCommitIndicatorOnlyForAutoCorrection = "); - sb.append("" + mShowCommitIndicatorOnlyForAutoCorrection); - sb.append("\n mShowCommitIndicatorOnlyForOutOfVocabulary = "); - sb.append("" + mShowCommitIndicatorOnlyForOutOfVocabulary); - sb.append("\n mTextHighlightColorForCommitIndicator = "); - sb.append("" + mTextHighlightColorForCommitIndicator); sb.append("\n mTextHighlightColorForAddToDictionaryIndicator = "); sb.append("" + mTextHighlightColorForAddToDictionaryIndicator); sb.append("\n mIsInternal = "); diff --git a/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java b/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java index ca5b395ce..240f8f89b 100644 --- a/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/SubScreenFragment.java @@ -20,6 +20,7 @@ import android.app.backup.BackupManager; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.content.res.Resources; import android.os.Bundle; import android.preference.ListPreference; import android.preference.Preference; @@ -79,6 +80,16 @@ abstract class SubScreenFragment extends PreferenceFragment return getPreferenceManager().getSharedPreferences(); } + /** + * Gets the application name to display on the UI. + */ + final String getApplicationName() { + final Context context = getActivity(); + final Resources res = getResources(); + final int applicationLabelRes = context.getApplicationInfo().labelRes; + return res.getString(applicationLabelRes); + } + @Override public void addPreferencesFromResource(final int preferencesResId) { super.addPreferencesFromResource(preferencesResId); diff --git a/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java b/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java new file mode 100644 index 000000000..254bc6567 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.settings; + +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.Intent; +import android.os.Bundle; + +/** + * Test activity to use when testing preference fragments. <br/> + * Usage: <br/> + * Create an ActivityInstrumentationTestCase2 for this activity + * and call setIntent() with an intent that specifies the fragment to load in the activity. + * The fragment can then be obtained from this activity and used for testing/verification. + */ +public final class TestFragmentActivity extends Activity { + /** + * The fragment name that should be loaded when starting this activity. + * This must be specified when starting this activity, as this activity is only + * meant to test fragments from instrumentation tests. + */ + public static final String EXTRA_SHOW_FRAGMENT = "show_fragment"; + + public Fragment mFragment; + + @Override + protected void onCreate(final Bundle savedState) { + super.onCreate(savedState); + final Intent intent = getIntent(); + final String fragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT); + if (fragmentName == null) { + throw new IllegalArgumentException("No fragment name specified for testing"); + } + + mFragment = Fragment.instantiate(this, fragmentName); + FragmentManager fragmentManager = getFragmentManager(); + fragmentManager.beginTransaction().add(mFragment, fragmentName).commit(); + } +} diff --git a/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java index 5a3fc3600..29289aed2 100644 --- a/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/ThemeSettingsFragment.java @@ -17,7 +17,6 @@ package com.android.inputmethod.latin.settings; import android.content.Context; -import android.content.SharedPreferences; import android.content.res.Resources; import android.os.Bundle; import android.preference.Preference; @@ -32,12 +31,12 @@ import com.android.inputmethod.latin.settings.RadioButtonPreference.OnRadioButto */ public final class ThemeSettingsFragment extends SubScreenFragment implements OnRadioButtonClickedListener { - private String mSelectedThemeId; + private int mSelectedThemeId; static class KeyboardThemePreference extends RadioButtonPreference { - final String mThemeId; + final int mThemeId; - KeyboardThemePreference(final Context context, final String name, final String id) { + KeyboardThemePreference(final Context context, final String name, final int id) { super(context); setTitle(name); mThemeId = id; @@ -45,14 +44,13 @@ public final class ThemeSettingsFragment extends SubScreenFragment } static void updateKeyboardThemeSummary(final Preference pref) { - final Resources res = pref.getContext().getResources(); - final SharedPreferences prefs = pref.getSharedPreferences(); - final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(prefs); - final String keyboardThemeId = String.valueOf(keyboardTheme.mThemeId); + final Context context = pref.getContext(); + final Resources res = context.getResources(); + final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(context); final String[] keyboardThemeNames = res.getStringArray(R.array.keyboard_theme_names); - final String[] keyboardThemeIds = res.getStringArray(R.array.keyboard_theme_ids); + final int[] keyboardThemeIds = res.getIntArray(R.array.keyboard_theme_ids); for (int index = 0; index < keyboardThemeNames.length; index++) { - if (keyboardThemeId.equals(keyboardThemeIds[index])) { + if (keyboardTheme.mThemeId == keyboardThemeIds[index]) { pref.setSummary(keyboardThemeNames[index]); return; } @@ -64,18 +62,18 @@ public final class ThemeSettingsFragment extends SubScreenFragment super.onCreate(icicle); addPreferencesFromResource(R.xml.prefs_screen_theme); final PreferenceScreen screen = getPreferenceScreen(); + final Context context = getActivity(); final Resources res = getResources(); final String[] keyboardThemeNames = res.getStringArray(R.array.keyboard_theme_names); - final String[] keyboardThemeIds = res.getStringArray(R.array.keyboard_theme_ids); + final int[] keyboardThemeIds = res.getIntArray(R.array.keyboard_theme_ids); for (int index = 0; index < keyboardThemeNames.length; index++) { final KeyboardThemePreference pref = new KeyboardThemePreference( - getActivity(), keyboardThemeNames[index], keyboardThemeIds[index]); + context, keyboardThemeNames[index], keyboardThemeIds[index]); screen.addPreference(pref); pref.setOnRadioButtonClickedListener(this); } - final SharedPreferences prefs = getSharedPreferences(); - final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(prefs); - mSelectedThemeId = String.valueOf(keyboardTheme.mThemeId); + final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(context); + mSelectedThemeId = keyboardTheme.mThemeId; } @Override @@ -106,7 +104,7 @@ public final class ThemeSettingsFragment extends SubScreenFragment final Preference preference = screen.getPreference(index); if (preference instanceof KeyboardThemePreference) { final KeyboardThemePreference pref = (KeyboardThemePreference)preference; - final boolean selected = mSelectedThemeId.equals(pref.mThemeId); + final boolean selected = (mSelectedThemeId == pref.mThemeId); pref.setSelected(selected); } } diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java index 197908032..249478785 100644 --- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java @@ -23,6 +23,7 @@ import android.content.res.Resources; import android.text.TextUtils; import android.util.Log; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.AssetFileAddress; import com.android.inputmethod.latin.BinaryDictionaryGetter; import com.android.inputmethod.latin.Constants; @@ -382,6 +383,7 @@ public class DictionaryInfoUtils { return dictList; } + @UsedForTesting public static boolean looksValidForDictionaryInsertion(final CharSequence text, final SpacingAndPunctuations spacingAndPunctuations) { if (TextUtils.isEmpty(text)) return false; diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java index 94c62429e..6fd241ee9 100644 --- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java +++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java @@ -21,6 +21,7 @@ import java.util.Locale; import android.view.inputmethod.InputMethodSubtype; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.PrevWordsInfo; public interface DistracterFilter { @@ -36,6 +37,7 @@ public interface DistracterFilter { public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo, final String testedWord, final Locale locale); + @UsedForTesting public int getWordHandlingType(final PrevWordsInfo prevWordsInfo, final String testedWord, final Locale locale); diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java index 1db525502..f8a845304 100644 --- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java +++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java @@ -64,9 +64,9 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr private final Object mLock = new Object(); // If the score of the top suggestion exceeds this value, the tested word (e.g., - // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distractor to + // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distracter to // words in dictionary. The greater the threshold is, the less likely the tested word would - // become a distractor, which means the tested word will be more likely to be added to + // become a distracter, which means the tested word will be more likely to be added to // the dictionary. private static final float DISTRACTER_WORD_SCORE_THRESHOLD = 0.4f; @@ -196,7 +196,7 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr } final boolean Word = dictionaryFacilitator.isValidWord(testedWord, false /* ignoreCase */); if (Word) { - // Valid word is not a distractor. + // Valid word is not a distracter. if (DEBUG) { Log.d(TAG, "isDistracter: false (valid word)"); } @@ -257,12 +257,12 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr return false; } final SuggestedWordInfo firstSuggestion = suggestionResults.first(); - final boolean isDistractor = suggestionExceedsDistracterThreshold( + final boolean isDistracter = suggestionExceedsDistracterThreshold( firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD); if (DEBUG) { - Log.d(TAG, "isDistracter: " + isDistractor); + Log.d(TAG, "isDistracter: " + isDistracter); } - return isDistractor; + return isDistracter; } private static boolean suggestionExceedsDistracterThreshold(final SuggestedWordInfo suggestion, diff --git a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java index c2167a76b..ae2de44c7 100644 --- a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java @@ -18,6 +18,7 @@ package com.android.inputmethod.latin.utils; import com.android.inputmethod.dictionarypack.DictionarySettingsFragment; import com.android.inputmethod.latin.about.AboutPreferences; +import com.android.inputmethod.latin.settings.AccountsSettingsFragment; import com.android.inputmethod.latin.settings.AdvancedSettingsFragment; import com.android.inputmethod.latin.settings.AppearanceSettingsFragment; import com.android.inputmethod.latin.settings.CorrectionSettingsFragment; @@ -42,6 +43,7 @@ public class FragmentUtils { sLatinImeFragments.add(DictionarySettingsFragment.class.getName()); sLatinImeFragments.add(AboutPreferences.class.getName()); sLatinImeFragments.add(PreferencesSettingsFragment.class.getName()); + sLatinImeFragments.add(AccountsSettingsFragment.class.getName()); sLatinImeFragments.add(AppearanceSettingsFragment.class.getName()); sLatinImeFragments.add(ThemeSettingsFragment.class.getName()); sLatinImeFragments.add(MultiLingualSettingsFragment.class.getName()); diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java index 05d124764..7955541aa 100644 --- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java +++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java @@ -18,6 +18,7 @@ package com.android.inputmethod.latin.utils; import android.util.Log; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.DictionaryFacilitator; import com.android.inputmethod.latin.PrevWordsInfo; @@ -58,12 +59,14 @@ public final class LanguageModelParam { public final int mTimestamp; // Constructor for unigram. TODO: support shortcuts + @UsedForTesting public LanguageModelParam(final CharSequence word, final int unigramProbability, final int timestamp) { this(null /* word0 */, word, unigramProbability, Dictionary.NOT_A_PROBABILITY, timestamp); } // Constructor for unigram and bigram. + @UsedForTesting public LanguageModelParam(final CharSequence word0, final CharSequence word1, final int unigramProbability, final int bigramProbability, final int timestamp) { diff --git a/java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java index 3cd63612c..5720d9388 100644 --- a/java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin.utils; +import java.util.Arrays; import java.util.regex.Pattern; import com.android.inputmethod.latin.Constants; @@ -56,6 +57,7 @@ public final class PrevWordsInfoUtils { if (prev == null) return PrevWordsInfo.EMPTY_PREV_WORDS_INFO; final String[] w = SPACE_REGEX.split(prev); final WordInfo[] prevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; + Arrays.fill(prevWordsInfo, WordInfo.EMPTY_WORD_INFO); for (int i = 0; i < prevWordsInfo.length; i++) { final int focusedWordIndex = w.length - n - i; // Referring to the word after the focused word. @@ -66,7 +68,6 @@ public final class PrevWordsInfoUtils { if (spacingAndPunctuations.isWordConnector(firstChar)) { // The word following the focused word is starting with a word connector. // TODO: Return meaningful context for this case. - prevWordsInfo[i] = WordInfo.EMPTY_WORD_INFO; break; } } @@ -93,7 +94,6 @@ public final class PrevWordsInfoUtils { // TODO: Return meaningful context for this case. if (spacingAndPunctuations.isWordSeparator(lastChar) || spacingAndPunctuations.isWordConnector(lastChar)) { - prevWordsInfo[i] = WordInfo.EMPTY_WORD_INFO; break; } prevWordsInfo[i] = new WordInfo(focusedWord); diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java index 55557de9d..bbcef990d 100644 --- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java @@ -37,6 +37,14 @@ public final class StringUtils { private static final String EMPTY_STRING = ""; + private static final char CHAR_LINE_FEED = 0X000A; + private static final char CHAR_VERTICAL_TAB = 0X000B; + private static final char CHAR_FORM_FEED = 0X000C; + private static final char CHAR_CARRIAGE_RETURN = 0X000D; + private static final char CHAR_NEXT_LINE = 0X0085; + private static final char CHAR_LINE_SEPARATOR = 0X2028; + private static final char CHAR_PARAGRAPH_SEPARATOR = 0X2029; + private StringUtils() { // This utility class is not publicly instantiable. } @@ -594,4 +602,30 @@ public final class StringUtils { return sb + "]"; } } + + /** + * Returns whether the last composed word contains line-breaking character (e.g. CR or LF). + * @param text the text to be examined. + * @return {@code true} if the last composed word contains line-breaking separator. + */ + @UsedForTesting + public static boolean hasLineBreakCharacter(final String text) { + if (TextUtils.isEmpty(text)) { + return false; + } + for (int i = text.length() - 1; i >= 0; --i) { + final char c = text.charAt(i); + switch (c) { + case CHAR_LINE_FEED: + case CHAR_VERTICAL_TAB: + case CHAR_FORM_FEED: + case CHAR_CARRIAGE_RETURN: + case CHAR_NEXT_LINE: + case CHAR_LINE_SEPARATOR: + case CHAR_PARAGRAPH_SEPARATOR: + return true; + } + } + return false; + } } diff --git a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java index eaa5743d4..d6f644228 100644 --- a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java +++ b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java @@ -22,7 +22,6 @@ import com.android.inputmethod.latin.define.ProductionFlags; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; -import java.util.Locale; import java.util.TreeSet; /** @@ -31,14 +30,17 @@ import java.util.TreeSet; */ public final class SuggestionResults extends TreeSet<SuggestedWordInfo> { public final ArrayList<SuggestedWordInfo> mRawSuggestions; + // TODO: Instead of a boolean , we may want to include the context of this suggestion results, + // such as {@link PrevWordsInfo}. + public final boolean mIsBeginningOfSentence; private final int mCapacity; - public SuggestionResults(final int capacity) { - this(sSuggestedWordInfoComparator, capacity); + public SuggestionResults(final int capacity, final boolean isBeginningOfSentence) { + this(sSuggestedWordInfoComparator, capacity, isBeginningOfSentence); } - public SuggestionResults(final Comparator<SuggestedWordInfo> comparator, - final int capacity) { + private SuggestionResults(final Comparator<SuggestedWordInfo> comparator, + final int capacity, final boolean isBeginningOfSentence) { super(comparator); mCapacity = capacity; if (ProductionFlags.INCLUDE_RAW_SUGGESTIONS) { @@ -46,6 +48,7 @@ public final class SuggestionResults extends TreeSet<SuggestedWordInfo> { } else { mRawSuggestions = null; } + mIsBeginningOfSentence = isBeginningOfSentence; } @Override |