public void getSuggestedWords( final WordComposer wordComposer, final String prevWordForBigram, final ProximityInfo proximityInfo, final boolean blockOffensiveWords, final boolean isCorrectionEnabled, final int[] additionalFeaturesOptions, final int sessionId, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { LatinImeLogger.onStartSuggestion(prevWordForBigram); if (wordComposer.isBatchMode()) { getSuggestedWordsForBatchInput( wordComposer, prevWordForBigram, proximityInfo, blockOffensiveWords, additionalFeaturesOptions, sessionId, sequenceNumber, callback); } else { getSuggestedWordsForTypingInput( wordComposer, prevWordForBigram, proximityInfo, blockOffensiveWords, isCorrectionEnabled, additionalFeaturesOptions, sequenceNumber, callback); } }
public static void backspace() { if (sState == State.ACCEPTED_DEFAULT) { sState = State.UNDO_COMMIT; sAutoSuggestUndoneCount++; LatinImeLogger.logOnAutoSuggestionCanceled(); } else if (sState == State.UNDO_COMMIT) { sState = State.IN_WORD; } sBackspaceCount++; displayState(); }
public static void acceptedDefault(CharSequence typedWord, CharSequence actualWord) { if (typedWord == null) return; if (!typedWord.equals(actualWord)) { sAutoSuggestCount++; } sTypedChars += typedWord.length(); sActualChars += actualWord.length(); sState = State.ACCEPTED_DEFAULT; LatinImeLogger.logOnAutoSuggestion(typedWord.toString(), actualWord.toString()); displayState(); }
public boolean addWord( final char[] word, final int offset, final int length, int freq, final int dicTypeId, final Dictionary.DataType dataType) { Dictionary.DataType dataTypeForLog = dataType; ArrayList<CharSequence> suggestions; int[] priorities; int prefMaxSuggestions; if (dataType == Dictionary.DataType.BIGRAM) { suggestions = mBigramSuggestions; priorities = mBigramPriorities; prefMaxSuggestions = PREF_MAX_BIGRAMS; } else { suggestions = mSuggestions; priorities = mPriorities; prefMaxSuggestions = mPrefMaxSuggestions; } int pos = 0; // Check if it's the same word, only caps are different if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) { pos = 0; } else { if (dataType == Dictionary.DataType.UNIGRAM) { // Check if the word was already added before (by bigram data) int bigramSuggestion = searchBigramSuggestion(word, offset, length); if (bigramSuggestion >= 0) { dataTypeForLog = Dictionary.DataType.BIGRAM; // turn freq from bigram into multiplier specified above double multiplier = (((double) mBigramPriorities[bigramSuggestion]) / MAXIMUM_BIGRAM_FREQUENCY) * (BIGRAM_MULTIPLIER_MAX - BIGRAM_MULTIPLIER_MIN) + BIGRAM_MULTIPLIER_MIN; /* Log.d(TAG,"bigram num: " + bigramSuggestion + " wordB: " + mBigramSuggestions.get(bigramSuggestion).toString() + " currentPriority: " + freq + " bigramPriority: " + mBigramPriorities[bigramSuggestion] + " multiplier: " + multiplier); */ freq = (int) Math.round((freq * multiplier)); } } // Check the last one's priority and bail if (priorities[prefMaxSuggestions - 1] >= freq) return true; while (pos < prefMaxSuggestions) { if (priorities[pos] < freq || (priorities[pos] == freq && length < suggestions.get(pos).length())) { break; } pos++; } } if (pos >= prefMaxSuggestions) { return true; } System.arraycopy(priorities, pos, priorities, pos + 1, prefMaxSuggestions - pos - 1); priorities[pos] = freq; int poolSize = mStringPool.size(); StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) : new StringBuilder(getApproxMaxWordLength()); sb.setLength(0); if (mIsAllUpperCase) { sb.append(new String(word, offset, length).toUpperCase()); } else if (mIsFirstCharCapitalized) { sb.append(Character.toUpperCase(word[offset])); if (length > 1) { sb.append(word, offset + 1, length - 1); } } else { sb.append(word, offset, length); } suggestions.add(pos, sb); if (suggestions.size() > prefMaxSuggestions) { CharSequence garbage = suggestions.remove(prefMaxSuggestions); if (garbage instanceof StringBuilder) { mStringPool.add(garbage); } } else { LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog); } return true; }
/** * Returns a list of words that match the list of character codes passed in. This list will be * overwritten the next time this function is called. * * @param view a view for retrieving the context for AutoText * @param wordComposer contains what is currently being typed * @param prevWordForBigram previous word (used only for bigram) * @return list of suggestions. */ public List<CharSequence> getSuggestions( View view, WordComposer wordComposer, boolean includeTypedWordIfValid, CharSequence prevWordForBigram) { LatinImeLogger.onStartSuggestion(prevWordForBigram); mHaveCorrection = false; mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); mIsAllUpperCase = wordComposer.isAllUpperCase(); collectGarbage(mSuggestions, mPrefMaxSuggestions); Arrays.fill(mPriorities, 0); Arrays.fill(mNextLettersFrequencies, 0); // Save a lowercase version of the original word mOriginalWord = wordComposer.getTypedWord(); if (mOriginalWord != null) { final String mOriginalWordString = mOriginalWord.toString(); mOriginalWord = mOriginalWordString; mLowerOriginalWord = mOriginalWordString.toLowerCase(); // Treating USER_TYPED as UNIGRAM suggestion for logging now. LatinImeLogger.onAddSuggestedWord( mOriginalWordString, Suggest.DIC_USER_TYPED, Dictionary.DataType.UNIGRAM); } else { mLowerOriginalWord = ""; } if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM || mCorrectionMode == CORRECTION_BASIC)) { // At first character typed, search only the bigrams Arrays.fill(mBigramPriorities, 0); collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS); if (!TextUtils.isEmpty(prevWordForBigram)) { CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase(); if (mMainDict.isValidWord(lowerPrevWord)) { prevWordForBigram = lowerPrevWord; } if (mUserBigramDictionary != null) { mUserBigramDictionary.getBigrams( wordComposer, prevWordForBigram, this, mNextLettersFrequencies); } if (mContactsDictionary != null) { mContactsDictionary.getBigrams( wordComposer, prevWordForBigram, this, mNextLettersFrequencies); } if (mMainDict != null) { mMainDict.getBigrams(wordComposer, prevWordForBigram, this, mNextLettersFrequencies); } char currentChar = wordComposer.getTypedWord().charAt(0); char currentCharUpper = Character.toUpperCase(currentChar); int count = 0; int bigramSuggestionSize = mBigramSuggestions.size(); for (int i = 0; i < bigramSuggestionSize; i++) { if (mBigramSuggestions.get(i).charAt(0) == currentChar || mBigramSuggestions.get(i).charAt(0) == currentCharUpper) { int poolSize = mStringPool.size(); StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) : new StringBuilder(getApproxMaxWordLength()); sb.setLength(0); sb.append(mBigramSuggestions.get(i)); mSuggestions.add(count++, sb); if (count > mPrefMaxSuggestions) break; } } } } else if (wordComposer.size() > 1) { // At second character typed, search the unigrams (scores being affected by bigrams) if (mUserDictionary != null || mContactsDictionary != null) { if (mUserDictionary != null) { mUserDictionary.getWords(wordComposer, this, mNextLettersFrequencies); } if (mContactsDictionary != null) { mContactsDictionary.getWords(wordComposer, this, mNextLettersFrequencies); } if (mSuggestions.size() > 0 && isValidWord(mOriginalWord) && (mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM)) { mHaveCorrection = true; } } mMainDict.getWords(wordComposer, this, mNextLettersFrequencies); if ((mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM) && mSuggestions.size() > 0) { mHaveCorrection = true; } } if (mOriginalWord != null) { mSuggestions.add(0, mOriginalWord.toString()); } // Check if the first suggestion has a minimum number of characters in common if (wordComposer.size() > 1 && mSuggestions.size() > 1 && (mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM)) { if (!haveSufficientCommonality(mLowerOriginalWord, mSuggestions.get(1))) { mHaveCorrection = false; } } if (mAutoTextEnabled) { int i = 0; int max = 6; // Don't autotext the suggestions from the dictionaries if (mCorrectionMode == CORRECTION_BASIC) max = 1; while (i < mSuggestions.size() && i < max) { String suggestedWord = mSuggestions.get(i).toString().toLowerCase(); CharSequence autoText = AutoText.get(suggestedWord, 0, suggestedWord.length(), view); // Is there an AutoText correction? boolean canAdd = autoText != null; // Is that correction already the current prediction (or original word)? canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i)); // Is that correction already the next predicted word? if (canAdd && i + 1 < mSuggestions.size() && mCorrectionMode != CORRECTION_BASIC) { canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i + 1)); } if (canAdd) { mHaveCorrection = true; mSuggestions.add(i + 1, autoText); i++; } i++; } } removeDupes(); return mSuggestions; }
// Retrieves suggestions for the batch input // and calls the callback function with the suggestions. private void getSuggestedWordsForBatchInput( final WordComposer wordComposer, final String prevWordForBigram, final ProximityInfo proximityInfo, final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, final int sessionId, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, MAX_SUGGESTIONS); // At second character typed, search the unigrams (scores being affected by bigrams) for (final String key : mDictionaries.keySet()) { final Dictionary dictionary = mDictionaries.get(key); suggestionsSet.addAll( dictionary.getSuggestionsWithSessionId( wordComposer, prevWordForBigram, proximityInfo, blockOffensiveWords, additionalFeaturesOptions, sessionId)); } for (SuggestedWordInfo wordInfo : suggestionsSet) { LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType); } final ArrayList<SuggestedWordInfo> suggestionsContainer = CollectionUtils.newArrayList(suggestionsSet); final int suggestionsCount = suggestionsContainer.size(); final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock(); final boolean isAllUpperCase = wordComposer.isAllUpperCase(); if (isFirstCharCapitalized || isAllUpperCase) { for (int i = 0; i < suggestionsCount; ++i) { final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo( wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized, 0 /* trailingSingleQuotesCount */); suggestionsContainer.set(i, transformedWordInfo); } } if (suggestionsContainer.size() > 1 && TextUtils.equals( suggestionsContainer.get(0).mWord, wordComposer.getRejectedBatchModeSuggestion())) { final SuggestedWordInfo rejected = suggestionsContainer.remove(0); suggestionsContainer.add(1, rejected); } SuggestedWordInfo.removeDups(suggestionsContainer); // For some reason some suggestions with MIN_VALUE are making their way here. // TODO: Find a more robust way to detect distractors. for (int i = suggestionsContainer.size() - 1; i >= 0; --i) { if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) { suggestionsContainer.remove(i); } } // 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). callback.onGetSuggestedWords( new SuggestedWords( suggestionsContainer, true /* typedWordValid */, false /* willAutoCorrect */, false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */, false /* isPrediction */, sequenceNumber)); }
// Retrieves suggestions for the typing input // and calls the callback function with the suggestions. private void getSuggestedWordsForTypingInput( final WordComposer wordComposer, final String prevWordForBigram, final ProximityInfo proximityInfo, final boolean blockOffensiveWords, final boolean isCorrectionEnabled, final int[] additionalFeaturesOptions, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount(); final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, MAX_SUGGESTIONS); final String typedWord = wordComposer.getTypedWord(); final String consideredWord = trailingSingleQuotesCount > 0 ? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount) : typedWord; LatinImeLogger.onAddSuggestedWord(typedWord, Dictionary.TYPE_USER_TYPED); final WordComposer wordComposerForLookup; if (trailingSingleQuotesCount > 0) { wordComposerForLookup = new WordComposer(wordComposer); for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) { wordComposerForLookup.deleteLast(); } } else { wordComposerForLookup = wordComposer; } for (final String key : mDictionaries.keySet()) { final Dictionary dictionary = mDictionaries.get(key); suggestionsSet.addAll( dictionary.getSuggestions( wordComposerForLookup, prevWordForBigram, proximityInfo, blockOffensiveWords, additionalFeaturesOptions)); } final String whitelistedWord; if (suggestionsSet.isEmpty()) { whitelistedWord = null; } else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) { whitelistedWord = null; } else { whitelistedWord = suggestionsSet.first().mWord; } // The word can be auto-corrected if it has a whitelist entry that is not itself, // or if it's a 2+ characters non-word (i.e. it's not in the dictionary). final boolean allowsToBeAutoCorrected = (null != whitelistedWord && !whitelistedWord.equals(consideredWord)) || (consideredWord.length() > 1 && !AutoCorrectionUtils.isValidWord( this, consideredWord, wordComposer.isFirstCharCapitalized())); final boolean hasAutoCorrection; // TODO: using isCorrectionEnabled here is not very good. It's probably useless, because // any attempt to do auto-correction is already shielded with a test for this flag; at the // same time, it feels wrong that the SuggestedWord object includes information about // the current settings. It may also be useful to know, when the setting is off, whether // the word *would* have been auto-corrected. if (!isCorrectionEnabled || !allowsToBeAutoCorrected || !wordComposer.isComposingWord() || suggestionsSet.isEmpty() || wordComposer.hasDigits() || wordComposer.isMostlyCaps() || wordComposer.isResumed() || !hasMainDictionary() || SuggestedWordInfo.KIND_SHORTCUT == suggestionsSet.first().mKind) { // If we don't have a main dictionary, we never want to auto-correct. The reason for // this is, the user may have a contact whose name happens to match a valid word in // their language, and it will unexpectedly auto-correct. For example, if the user // types in English with no dictionary and has a "Will" in their contact list, "will" // would always auto-correct to "Will" which is unwanted. Hence, no main dict => no // auto-correct. // Also, shortcuts should never auto-correct unless they are whitelist entries. // TODO: we may want to have shortcut-only entries auto-correct in the future. hasAutoCorrection = false; } else { hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold( suggestionsSet.first(), consideredWord, mAutoCorrectionThreshold); } final ArrayList<SuggestedWordInfo> suggestionsContainer = CollectionUtils.newArrayList(suggestionsSet); final int suggestionsCount = suggestionsContainer.size(); final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); final boolean isAllUpperCase = wordComposer.isAllUpperCase(); if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) { for (int i = 0; i < suggestionsCount; ++i) { final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo( wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized, trailingSingleQuotesCount); suggestionsContainer.set(i, transformedWordInfo); } } for (int i = 0; i < suggestionsCount; ++i) { final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), wordInfo.mSourceDict.mDictType); } if (!TextUtils.isEmpty(typedWord)) { suggestionsContainer.add( 0, new SuggestedWordInfo( typedWord, SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED, SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */)); } SuggestedWordInfo.removeDups(suggestionsContainer); final ArrayList<SuggestedWordInfo> suggestionsList; if (DBG && !suggestionsContainer.isEmpty()) { suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, suggestionsContainer); } else { suggestionsList = suggestionsContainer; } callback.onGetSuggestedWords( new SuggestedWords( suggestionsList, // TODO: this first argument is lying. If this is a whitelisted word which is an // actual word, it says typedWordValid = false, which looks wrong. We should either // rename the attribute or change the value. !allowsToBeAutoCorrected /* typedWordValid */, hasAutoCorrection, /* willAutoCorrect */ false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */, !wordComposer.isComposingWord() /* isPrediction */, sequenceNumber)); }