diff options
Diffstat (limited to 'java/src/com/android')
20 files changed, 340 insertions, 322 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java index 383298de9..e3739eb3c 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java @@ -127,6 +127,8 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke private static final int MSG_TYPING_STATE_EXPIRED = 4; private final KeyTimerParams mParams; + // TODO: Suppress layout changes in key repeat mode + // TODO: Remove this variable. private boolean mInKeyRepeat; public KeyTimerHandler(LatinKeyboardView outerInstance, KeyTimerParams params) { @@ -140,8 +142,11 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke final PointerTracker tracker = (PointerTracker) msg.obj; switch (msg.what) { case MSG_REPEAT_KEY: - tracker.onRegisterKey(tracker.getKey()); - startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval); + final Key currentKey = tracker.getKey(); + if (currentKey != null && currentKey.mCode == msg.arg1) { + tracker.onRegisterKey(currentKey); + startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval); + } break; case MSG_LONGPRESS_KEY: if (tracker != null) { @@ -158,12 +163,14 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke } private void startKeyRepeatTimer(PointerTracker tracker, long delay) { - sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, tracker), delay); + final Key key = tracker.getKey(); + if (key == null) return; + mInKeyRepeat = true; + sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay); } @Override public void startKeyRepeatTimer(PointerTracker tracker) { - mInKeyRepeat = true; startKeyRepeatTimer(tracker, mParams.mKeyRepeatStartTimeout); } @@ -451,8 +458,8 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke */ @Override public void setKeyboard(Keyboard keyboard) { - // Remove any pending messages, except dismissing preview - mKeyTimerHandler.cancelKeyTimers(); + // Remove any pending messages, except dismissing preview and key repeat. + mKeyTimerHandler.cancelLongPressTimer(); super.setKeyboard(keyboard); mKeyDetector.setKeyboard( keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection); diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 34e428e82..8aac8d80c 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -148,6 +148,7 @@ public class PointerTracker { // true if this pointer has been long-pressed and is showing a more keys panel. private boolean mIsShowingMoreKeysPanel; + // TODO: Remove this variable. // true if this pointer is repeatable key private boolean mIsRepeatableKey; @@ -319,6 +320,13 @@ public class PointerTracker { private void setKeyDetectorInner(KeyDetector keyDetector) { mKeyDetector = keyDetector; mKeyboard = keyDetector.getKeyboard(); + final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); + if (newKey != mCurrentKey) { + if (mDrawingProxy != null) { + setReleasedKeyGraphics(mCurrentKey); + } + mCurrentKey = newKey; + } final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4; mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth; } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index d0613bd72..f44e6328b 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -20,7 +20,9 @@ import android.content.Context; import android.text.TextUtils; import com.android.inputmethod.keyboard.ProximityInfo; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import java.util.ArrayList; import java.util.Arrays; import java.util.Locale; @@ -40,6 +42,7 @@ public class BinaryDictionary extends Dictionary { */ public static final int MAX_WORD_LENGTH = 48; public static final int MAX_WORDS = 18; + public static final int MAX_SPACES = 16; private static final String TAG = "BinaryDictionary"; private static final int MAX_BIGRAMS = 60; @@ -51,6 +54,7 @@ public class BinaryDictionary extends Dictionary { private final int[] mInputCodes = new int[MAX_WORD_LENGTH]; private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS]; private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS]; + private final int[] mSpaceIndices = new int[MAX_SPACES]; private final int[] mScores = new int[MAX_WORDS]; private final int[] mBigramScores = new int[MAX_BIGRAMS]; @@ -65,14 +69,12 @@ public class BinaryDictionary extends Dictionary { * @param offset the offset of the dictionary data within the file. * @param length the length of the binary data. * @param useFullEditDistance whether to use the full edit distance in suggestions + * @param dicTypeId the dictionary type id of the dictionary */ public BinaryDictionary(final Context context, final String filename, final long offset, final long length, - final boolean useFullEditDistance, final Locale locale) { - // Note: at the moment a binary dictionary is always of the "main" type. - // Initializing this here will help transitioning out of the scheme where - // the Suggest class knows everything about every single dictionary. - mDicTypeId = Suggest.DIC_MAIN; + final boolean useFullEditDistance, final Locale locale, final int dicTypeId) { + mDicTypeId = dicTypeId; mUseFullEditDistance = useFullEditDistance; loadDictionary(filename, offset, length); } @@ -87,8 +89,10 @@ public class BinaryDictionary extends Dictionary { private native int getFrequencyNative(long dict, int[] word, int wordLength); private native boolean isValidBigramNative(long dict, int[] word1, int[] word2); private native int getSuggestionsNative(long dict, long proximityInfo, int[] xCoordinates, - int[] yCoordinates, int[] inputCodes, int codesSize, int[] prevWordForBigrams, - boolean useFullEditDistance, char[] outputChars, int[] scores); + int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodes, int codesSize, + int commitPoint, boolean isGesture, int dicTypeId, + int[] prevWordCodePointArray, boolean useFullEditDistance, char[] outputChars, + int[] scores, int[] outputIndices); private native int getBigramsNative(long dict, int[] prevWord, int prevWordLength, int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores, int maxWordLength, int maxBigrams); @@ -103,9 +107,9 @@ public class BinaryDictionary extends Dictionary { } @Override - public void getBigrams(final WordComposer codes, final CharSequence previousWord, - final WordCallback callback) { - if (mNativeDict == 0) return; + public ArrayList<SuggestedWordInfo> getBigrams(final WordComposer codes, + final CharSequence previousWord) { + if (mNativeDict == 0) return null; int[] codePoints = StringUtils.toCodePointArray(previousWord.toString()); Arrays.fill(mOutputChars_bigrams, (char) 0); @@ -123,6 +127,7 @@ public class BinaryDictionary extends Dictionary { count = MAX_BIGRAMS; } + final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>(); for (int j = 0; j < count; ++j) { if (codesSize > 0 && mBigramScores[j] < 1) break; final int start = j * MAX_WORD_LENGTH; @@ -131,19 +136,22 @@ public class BinaryDictionary extends Dictionary { ++len; } if (len > 0) { - callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j], - mDicTypeId, Dictionary.BIGRAM); + suggestions.add(new SuggestedWordInfo( + new String(mOutputChars_bigrams, start, len), + mBigramScores[j], SuggestedWordInfo.KIND_CORRECTION)); } } + return suggestions; } // proximityInfo and/or prevWordForBigrams may not be null. @Override - public void getWords(final WordComposer codes, final CharSequence prevWordForBigrams, - final WordCallback callback, final ProximityInfo proximityInfo) { + public ArrayList<SuggestedWordInfo> getWords(final WordComposer codes, + final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { final int count = getSuggestions(codes, prevWordForBigrams, proximityInfo, mOutputChars, - mScores); + mScores, mSpaceIndices); + final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>(); for (int j = 0; j < count; ++j) { if (mScores[j] < 1) break; final int start = j * MAX_WORD_LENGTH; @@ -152,10 +160,13 @@ public class BinaryDictionary extends Dictionary { ++len; } if (len > 0) { - callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId, - Dictionary.UNIGRAM); + // TODO: actually get the kind from native code + suggestions.add(new SuggestedWordInfo( + new String(mOutputChars, start, len), + mScores[j], SuggestedWordInfo.KIND_CORRECTION)); } } + return suggestions; } /* package for test */ boolean isValidDictionary() { @@ -165,7 +176,7 @@ public class BinaryDictionary extends Dictionary { // proximityInfo may not be null. /* package for test */ int getSuggestions(final WordComposer codes, final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo, - char[] outputChars, int[] scores) { + char[] outputChars, int[] scores, int[] spaceIndices) { if (!isValidDictionary()) return -1; final int codesSize = codes.size(); @@ -179,14 +190,21 @@ public class BinaryDictionary extends Dictionary { Arrays.fill(outputChars, (char) 0); Arrays.fill(scores, 0); - final int[] prevWordCodePointArray = null == prevWordForBigrams + // TODO: toLowerCase in the native code + final int[] prevWordCodePointArray = (null == prevWordForBigrams) ? null : StringUtils.toCodePointArray(prevWordForBigrams.toString()); - // TODO: pass the previous word to native code - return getSuggestionsNative( - mNativeDict, proximityInfo.getNativeProximityInfo(), - codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize, - prevWordCodePointArray, mUseFullEditDistance, outputChars, scores); + int[] emptyArray = new int[codesSize]; + Arrays.fill(emptyArray, 0); + + //final int commitPoint = codes.getCommitPoint(); + //codes.clearCommitPoint(); + + return getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(), + codes.getXCoordinates(), codes.getYCoordinates(), emptyArray, emptyArray, mInputCodes, + codesSize, 0 /* unused */, false, mDicTypeId, + prevWordCodePointArray, mUseFullEditDistance, + outputChars, scores, spaceIndices); } public static float calcNormalizedScore(String before, String after, int score) { diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java index 10e511eaf..2a02603ca 100644 --- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java @@ -120,12 +120,6 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { } } - @Override - public void getBigrams(final WordComposer codes, final CharSequence previousWord, - final WordCallback callback) { - super.getBigrams(codes, previousWord, callback); - } - private boolean useFirstLastBigramsForLocale(Locale locale) { // TODO: Add firstname/lastname bigram rules for other languages. if (locale != null && locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) { diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 9c3d46e70..99a04da72 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -17,6 +17,9 @@ package com.android.inputmethod.latin; import com.android.inputmethod.keyboard.ProximityInfo; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; + +import java.util.ArrayList; /** * Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key @@ -32,51 +35,26 @@ public abstract class Dictionary { public static final int BIGRAM = 1; public static final int NOT_A_PROBABILITY = -1; - /** - * Interface to be implemented by classes requesting words to be fetched from the dictionary. - * @see #getWords(WordComposer, CharSequence, WordCallback, ProximityInfo) - */ - public interface WordCallback { - /** - * Adds a word to a list of suggestions. The word is expected to be ordered based on - * the provided score. - * @param word the character array containing the word - * @param wordOffset starting offset of the word in the character array - * @param wordLength length of valid characters in the character array - * @param score the score of occurrence. This is normalized between 1 and 255, but - * can exceed those limits - * @param dicTypeId of the dictionary where word was from - * @param dataType tells type of this data, either UNIGRAM or BIGRAM - * @return true if the word was added, false if no more words are required - */ - boolean addWord(char[] word, int wordOffset, int wordLength, int score, int dicTypeId, - int dataType); - } /** * Searches for words in the dictionary that match the characters in the composer. Matched - * words are added through the callback object. - * @param composer the key sequence to match + * words are returned as an ArrayList. + * @param composer the key sequence to match with coordinate info, as a WordComposer * @param prevWordForBigrams the previous word, or null if none - * @param callback the callback object to send matched words to as possible candidates * @param proximityInfo the object for key proximity. May be ignored by some implementations. - * @see WordCallback#addWord(char[], int, int, int, int, int) + * @return the list of suggestions */ - abstract public void getWords(final WordComposer composer, - final CharSequence prevWordForBigrams, final WordCallback callback, - final ProximityInfo proximityInfo); + abstract public ArrayList<SuggestedWordInfo> getWords(final WordComposer composer, + final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo); /** - * Searches for pairs in the bigram dictionary that matches the previous word and all the - * possible words following are added through the callback object. + * Searches for pairs in the bigram dictionary that matches the previous word. * @param composer the key sequence to match * @param previousWord the word before - * @param callback the callback object to send possible word following previous word + * @return the list of suggestions */ - public void getBigrams(final WordComposer composer, final CharSequence previousWord, - final WordCallback callback) { - // empty base implementation - } + public abstract ArrayList<SuggestedWordInfo> getBigrams(final WordComposer composer, + final CharSequence previousWord); /** * Checks if the given word occurs in the dictionary diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java index 26c2e637e..169e70745 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java +++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java @@ -17,9 +17,11 @@ package com.android.inputmethod.latin; import com.android.inputmethod.keyboard.ProximityInfo; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import android.util.Log; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.concurrent.CopyOnWriteArrayList; @@ -50,17 +52,41 @@ public class DictionaryCollection extends Dictionary { } @Override - public void getWords(final WordComposer composer, final CharSequence prevWordForBigrams, - final WordCallback callback, final ProximityInfo proximityInfo) { - for (final Dictionary dict : mDictionaries) - dict.getWords(composer, prevWordForBigrams, callback, proximityInfo); + public ArrayList<SuggestedWordInfo> getWords(final WordComposer composer, + final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { + 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).getWords(composer, + prevWordForBigrams, proximityInfo); + if (null == suggestions) suggestions = new ArrayList<SuggestedWordInfo>(); + final int length = dictionaries.size(); + for (int i = 0; i < length; ++ i) { + final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getWords(composer, + prevWordForBigrams, proximityInfo); + if (null != sugg) suggestions.addAll(sugg); + } + return suggestions; } @Override - public void getBigrams(final WordComposer composer, final CharSequence previousWord, - final WordCallback callback) { - for (final Dictionary dict : mDictionaries) - dict.getBigrams(composer, previousWord, callback); + public ArrayList<SuggestedWordInfo> getBigrams(final WordComposer composer, + final CharSequence previousWord) { + 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).getBigrams(composer, + previousWord); + if (null == suggestions) suggestions = new ArrayList<SuggestedWordInfo>(); + final int length = dictionaries.size(); + for (int i = 0; i < length; ++ i) { + final ArrayList<SuggestedWordInfo> sugg = + dictionaries.get(i).getBigrams(composer, previousWord); + if (null != sugg) suggestions.addAll(sugg); + } + return suggestions; } @Override diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java index a22d73af7..6d77c4dd2 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java @@ -59,7 +59,7 @@ public class DictionaryFactory { for (final AssetFileAddress f : assetFileList) { final BinaryDictionary binaryDictionary = new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, - useFullEditDistance, locale); + useFullEditDistance, locale, Suggest.DIC_MAIN); if (binaryDictionary.isValidDictionary()) { dictList.add(binaryDictionary); } @@ -112,7 +112,7 @@ public class DictionaryFactory { return null; } return new BinaryDictionary(context, sourceDir, afd.getStartOffset(), afd.getLength(), - false /* useFullEditDistance */, locale); + false /* useFullEditDistance */, locale, Suggest.DIC_MAIN); } catch (android.content.res.Resources.NotFoundException e) { Log.e(TAG, "Could not find the resource"); return null; @@ -140,7 +140,7 @@ public class DictionaryFactory { long startOffset, long length, final boolean useFullEditDistance, Locale locale) { if (dictionary.isFile()) { return new BinaryDictionary(context, dictionary.getAbsolutePath(), startOffset, length, - useFullEditDistance, locale); + useFullEditDistance, locale, Suggest.DIC_MAIN); } else { Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath()); return null; diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index c65404cbc..c076fa0f9 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -19,6 +19,7 @@ import android.os.SystemClock; import android.util.Log; import com.android.inputmethod.keyboard.ProximityInfo; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.makedict.BinaryDictInputOutput; import com.android.inputmethod.latin.makedict.FusionDictionary; import com.android.inputmethod.latin.makedict.FusionDictionary.Node; @@ -194,46 +195,47 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } @Override - public void getWords(final WordComposer codes, final CharSequence prevWordForBigrams, - final WordCallback callback, final ProximityInfo proximityInfo) { + public ArrayList<SuggestedWordInfo> getWords(final WordComposer codes, + final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { asyncReloadDictionaryIfRequired(); - getWordsInner(codes, prevWordForBigrams, callback, proximityInfo); + return getWordsInner(codes, prevWordForBigrams, proximityInfo); } - protected final void getWordsInner(final WordComposer codes, - final CharSequence prevWordForBigrams, final WordCallback callback, - final ProximityInfo proximityInfo) { + protected final ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes, + final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { // Ensure that there are no concurrent calls to getWords. If there are, do nothing and // return. if (mLocalDictionaryController.tryLock()) { try { if (mBinaryDictionary != null) { - mBinaryDictionary.getWords(codes, prevWordForBigrams, callback, proximityInfo); + return mBinaryDictionary.getWords(codes, prevWordForBigrams, proximityInfo); } } finally { mLocalDictionaryController.unlock(); } } + return null; } @Override - public void getBigrams(final WordComposer codes, final CharSequence previousWord, - final WordCallback callback) { + public ArrayList<SuggestedWordInfo> getBigrams(final WordComposer codes, + final CharSequence previousWord) { asyncReloadDictionaryIfRequired(); - getBigramsInner(codes, previousWord, callback); + return getBigramsInner(codes, previousWord); } - protected void getBigramsInner(final WordComposer codes, final CharSequence previousWord, - final WordCallback callback) { + protected ArrayList<SuggestedWordInfo> getBigramsInner(final WordComposer codes, + final CharSequence previousWord) { if (mLocalDictionaryController.tryLock()) { try { if (mBinaryDictionary != null) { - mBinaryDictionary.getBigrams(codes, previousWord, callback); + return mBinaryDictionary.getBigrams(codes, previousWord); } } finally { mLocalDictionaryController.unlock(); } } + return null; } @Override @@ -306,7 +308,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { // Build the new binary dictionary final BinaryDictionary newBinaryDictionary = new BinaryDictionary(mContext, filename, 0, length, true /* useFullEditDistance */, - null); + null, mDicTypeId); if (mBinaryDictionary != null) { // Ensure all threads accessing the current dictionary have finished before swapping in diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index 4a5471c85..f19d77be2 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -21,6 +21,7 @@ import android.content.Context; import com.android.inputmethod.keyboard.KeyDetector; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.ProximityInfo; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams; import java.util.ArrayList; @@ -247,23 +248,25 @@ public class ExpandableDictionary extends Dictionary { } @Override - public void getWords(final WordComposer codes, final CharSequence prevWordForBigrams, - final WordCallback callback, final ProximityInfo proximityInfo) { + public ArrayList<SuggestedWordInfo> getWords(final WordComposer codes, + final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { synchronized (mUpdatingLock) { // If we need to update, start off a background task if (mRequiresReload) startDictionaryLoadingTaskLocked(); // Currently updating contacts, don't return any results. - if (mUpdatingDictionary) return; + if (mUpdatingDictionary) return null; } if (codes.size() >= BinaryDictionary.MAX_WORD_LENGTH) { - return; + return null; } - getWordsInner(codes, prevWordForBigrams, callback, proximityInfo); + final ArrayList<SuggestedWordInfo> suggestions = + getWordsInner(codes, prevWordForBigrams, proximityInfo); + return suggestions; } - protected final void getWordsInner(final WordComposer codes, - final CharSequence prevWordForBigrams, final WordCallback callback, - final ProximityInfo proximityInfo) { + protected final ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes, + final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { + final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>(); mInputLength = codes.size(); if (mCodes.length < mInputLength) mCodes = new int[mInputLength][]; final int[] xCoordinates = codes.getXCoordinates(); @@ -281,10 +284,11 @@ public class ExpandableDictionary extends Dictionary { proximityInfo.fillArrayWithNearestKeyCodes(x, y, codes.getCodeAt(i), mCodes[i]); } mMaxDepth = mInputLength * 3; - getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, -1, callback); + getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, -1, suggestions); for (int i = 0; i < mInputLength; i++) { - getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, i, callback); + getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, i, suggestions); } + return suggestions; } @Override @@ -368,24 +372,27 @@ public class ExpandableDictionary extends Dictionary { * @param word the word to insert, as an array of code points * @param depth the depth of the node in the tree * @param finalFreq the frequency for this word + * @param suggestions the suggestion collection to add the suggestions to * @return whether there is still space for more words. - * @see Dictionary.WordCallback#addWord(char[], int, int, int, int, int) */ private boolean addWordAndShortcutsFromNode(final Node node, final char[] word, final int depth, - final int finalFreq, final WordCallback callback) { + final int finalFreq, final ArrayList<SuggestedWordInfo> suggestions) { if (finalFreq > 0 && !node.mShortcutOnly) { - if (!callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId, Dictionary.UNIGRAM)) { - return false; - } + // Use KIND_CORRECTION always. This dictionary does not really have a notion of + // COMPLETION against CORRECTION; we could artificially add one by looking at + // the respective size of the typed word and the suggestion if it matters sometime + // in the future. + suggestions.add(new SuggestedWordInfo(new String(word, 0, depth + 1), finalFreq, + SuggestedWordInfo.KIND_CORRECTION)); + if (suggestions.size() >= Suggest.MAX_SUGGESTIONS) return false; } if (null != node.mShortcutTargets) { final int length = node.mShortcutTargets.size(); for (int shortcutIndex = 0; shortcutIndex < length; ++shortcutIndex) { final char[] shortcut = node.mShortcutTargets.get(shortcutIndex); - if (!callback.addWord(shortcut, 0, shortcut.length, finalFreq, mDicTypeId, - Dictionary.UNIGRAM)) { - return false; - } + suggestions.add(new SuggestedWordInfo(new String(shortcut, 0, shortcut.length), + finalFreq, SuggestedWordInfo.KIND_SHORTCUT)); + if (suggestions.size() > Suggest.MAX_SUGGESTIONS) return false; } } return true; @@ -408,12 +415,12 @@ public class ExpandableDictionary extends Dictionary { * case we skip over some punctuations such as apostrophe in the traversal. That is, if you type * "wouldve", it could be matching "would've", so the depth will be one more than the * inputIndex - * @param callback the callback class for adding a word + * @param suggestions the list in which to add suggestions */ // TODO: Share this routine with the native code for BinaryDictionary protected void getWordsRec(NodeArray roots, final WordComposer codes, final char[] word, final int depth, final boolean completion, int snr, int inputIndex, int skipPos, - WordCallback callback) { + final ArrayList<SuggestedWordInfo> suggestions) { final int count = roots.mLength; final int codeSize = mInputLength; // Optimization: Prune out words that are too long compared to how much was typed. @@ -443,14 +450,14 @@ public class ExpandableDictionary extends Dictionary { } else { finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength); } - if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq, callback)) { + if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq, suggestions)) { // No space left in the queue, bail out return; } } if (children != null) { getWordsRec(children, codes, word, depth + 1, true, snr, inputIndex, - skipPos, callback); + skipPos, suggestions); } } else if ((c == Keyboard.CODE_SINGLE_QUOTE && currentChars[0] != Keyboard.CODE_SINGLE_QUOTE) || depth == skipPos) { @@ -458,7 +465,7 @@ public class ExpandableDictionary extends Dictionary { word[depth] = c; if (children != null) { getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex, - skipPos, callback); + skipPos, suggestions); } } else { // Don't use alternatives if we're looking for missing characters @@ -483,7 +490,7 @@ public class ExpandableDictionary extends Dictionary { snr * addedAttenuation, mInputLength); } if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq, - callback)) { + suggestions)) { // No space left in the queue, bail out return; } @@ -491,12 +498,12 @@ public class ExpandableDictionary extends Dictionary { if (children != null) { getWordsRec(children, codes, word, depth + 1, true, snr * addedAttenuation, inputIndex + 1, - skipPos, callback); + skipPos, suggestions); } } else if (children != null) { getWordsRec(children, codes, word, depth + 1, false, snr * addedAttenuation, inputIndex + 1, - skipPos, callback); + skipPos, suggestions); } } } @@ -593,22 +600,25 @@ public class ExpandableDictionary extends Dictionary { } private void runBigramReverseLookUp(final CharSequence previousWord, - final WordCallback callback) { + final ArrayList<SuggestedWordInfo> suggestions) { // Search for the lowercase version of the word only, because that's where bigrams // store their sons. Node prevWord = searchNode(mRoots, previousWord.toString().toLowerCase(), 0, previousWord.length()); if (prevWord != null && prevWord.mNGrams != null) { - reverseLookUp(prevWord.mNGrams, callback); + reverseLookUp(prevWord.mNGrams, suggestions); } } @Override - public void getBigrams(final WordComposer codes, final CharSequence previousWord, - final WordCallback callback) { + public ArrayList<SuggestedWordInfo> getBigrams(final WordComposer codes, + final CharSequence previousWord) { if (!reloadDictionaryIfRequired()) { - runBigramReverseLookUp(previousWord, callback); + final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>(); + runBigramReverseLookUp(previousWord, suggestions); + return suggestions; } + return null; } /** @@ -635,11 +645,12 @@ public class ExpandableDictionary extends Dictionary { /** * reverseLookUp retrieves the full word given a list of terminal nodes and adds those words - * through callback. + * to the suggestions list passed as an argument. * @param terminalNodes list of terminal nodes we want to add + * @param suggestions the suggestion collection to add the word to */ private void reverseLookUp(LinkedList<NextWord> terminalNodes, - final WordCallback callback) { + final ArrayList<SuggestedWordInfo> suggestions) { Node node; int freq; for (NextWord nextWord : terminalNodes) { @@ -653,8 +664,9 @@ public class ExpandableDictionary extends Dictionary { } while (node != null); if (freq >= 0) { - callback.addWord(mLookedUpString, index, BinaryDictionary.MAX_WORD_LENGTH - index, - freq, mDicTypeId, Dictionary.BIGRAM); + suggestions.add(new SuggestedWordInfo(new String(mLookedUpString, index, + BinaryDictionary.MAX_WORD_LENGTH - index), + freq, SuggestedWordInfo.KIND_CORRECTION)); } } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 8a5fc495e..25b8fd566 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1710,7 +1710,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // getSuggestedWords handles gracefully a null value of prevWord final SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer, prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(), - mCurrentSettings.mCorrectionEnabled); + mCurrentSettings.mCorrectionEnabled, false); // Basically, we update the suggestion strip only when suggestion count > 1. However, // there is an exception: We update the suggestion strip whenever typed word's length @@ -1922,7 +1922,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (mCurrentSettings.mCorrectionEnabled) { final CharSequence prevWord = mConnection.getThisWord(mCurrentSettings.mWordSeparators); if (!TextUtils.isEmpty(prevWord)) { - suggestedWords = mSuggest.getBigramPredictions(prevWord); + suggestedWords = mSuggest.getSuggestedWords(mWordComposer, + prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(), + mCurrentSettings.mCorrectionEnabled, true); } else { suggestedWords = null; } diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 0c19bed05..40d327ebb 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -340,13 +340,6 @@ public class RichInputConnection { * Returns the word before the cursor if the cursor is at the end of a word, null otherwise */ public CharSequence getWordBeforeCursorIfAtEndOfWord(final SettingsValues settings) { - // Bail out if the cursor is not at the end of a word (cursor must be preceded by - // non-whitespace, non-separator, non-start-of-text) - // Example ("|" is the cursor here) : <SOL>"|a" " |a" " | " all get rejected here. - final CharSequence textBeforeCursor = getTextBeforeCursor(1, 0); - if (TextUtils.isEmpty(textBeforeCursor) - || settings.isWordSeparator(textBeforeCursor.charAt(0))) return null; - // Bail out if the cursor is in the middle of a word (cursor must be followed by whitespace, // separator or end of line/text) // Example: "test|"<EOL> "te|st" get rejected here @@ -363,6 +356,15 @@ public class RichInputConnection { word = word.subSequence(1, word.length()); } if (TextUtils.isEmpty(word)) return null; + // Find the last code point of the string + final int lastCodePoint = Character.codePointBefore(word, word.length()); + // If for some reason the text field contains non-unicode binary data, or if the + // charsequence is exactly one char long and the contents is a low surrogate, return null. + if (!Character.isDefined(lastCodePoint)) return null; + // Bail out if the cursor is not at the end of a word (cursor must be preceded by + // non-whitespace, non-separator, non-start-of-text) + // Example ("|" is the cursor here) : <SOL>"|a" " |a" " | " all get rejected here. + if (settings.isWordSeparator(lastCodePoint)) return null; final char firstChar = word.charAt(0); // we just tested that word is not empty if (word.length() == 1 && !Character.isLetter(firstChar)) return null; diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 4c67b4957..4c89a6e91 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -143,19 +143,39 @@ public class Settings extends InputMethodSettingsFragment generalSettings.removePreference(mVoicePreference); } + final PreferenceGroup advancedSettings = + (PreferenceGroup) findPreference(PREF_ADVANCED_SETTINGS); if (!VibratorUtils.getInstance(context).hasVibrator()) { - final PreferenceGroup advancedSettings = - (PreferenceGroup) findPreference(PREF_ADVANCED_SETTINGS); generalSettings.removePreference(findPreference(PREF_VIBRATE_ON)); if (null != advancedSettings) { // Theoretically advancedSettings cannot be null advancedSettings.removePreference(findPreference(PREF_VIBRATION_DURATION_SETTINGS)); } } - final boolean showPopupOption = res.getBoolean( + final boolean showKeyPreviewPopupOption = res.getBoolean( R.bool.config_enable_show_popup_on_keypress_option); - if (!showPopupOption) { + mKeyPreviewPopupDismissDelay = + (ListPreference) findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY); + if (!showKeyPreviewPopupOption) { generalSettings.removePreference(findPreference(PREF_POPUP_ON)); + if (null != advancedSettings) { // Theoretically advancedSettings cannot be null + advancedSettings.removePreference(mKeyPreviewPopupDismissDelay); + } + } else { + final String[] entries = new String[] { + res.getString(R.string.key_preview_popup_dismiss_no_delay), + res.getString(R.string.key_preview_popup_dismiss_default_delay), + }; + final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger( + R.integer.config_key_preview_linger_timeout)); + mKeyPreviewPopupDismissDelay.setEntries(entries); + mKeyPreviewPopupDismissDelay.setEntryValues( + new String[] { "0", popupDismissDelayDefaultValue }); + if (null == mKeyPreviewPopupDismissDelay.getValue()) { + mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue); + } + mKeyPreviewPopupDismissDelay.setEnabled( + SettingsValues.isKeyPreviewPopupEnabled(prefs, res)); } final CheckBoxPreference includeOtherImesInLanguageSwitchList = @@ -163,23 +183,6 @@ public class Settings extends InputMethodSettingsFragment includeOtherImesInLanguageSwitchList.setEnabled( !SettingsValues.isLanguageSwitchKeySupressed(prefs)); - mKeyPreviewPopupDismissDelay = - (ListPreference)findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY); - final String[] entries = new String[] { - res.getString(R.string.key_preview_popup_dismiss_no_delay), - res.getString(R.string.key_preview_popup_dismiss_default_delay), - }; - final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger( - R.integer.config_key_preview_linger_timeout)); - mKeyPreviewPopupDismissDelay.setEntries(entries); - mKeyPreviewPopupDismissDelay.setEntryValues( - new String[] { "0", popupDismissDelayDefaultValue }); - if (null == mKeyPreviewPopupDismissDelay.getValue()) { - mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue); - } - mKeyPreviewPopupDismissDelay.setEnabled( - SettingsValues.isKeyPreviewPopupEnabled(prefs, res)); - final PreferenceScreen dictionaryLink = (PreferenceScreen) findPreference(PREF_CONFIGURE_DICTIONARIES_KEY); final Intent intent = dictionaryLink.getIntent(); @@ -305,13 +308,15 @@ public class Settings extends InputMethodSettingsFragment private void updateKeyPreviewPopupDelaySummary() { final ListPreference lp = mKeyPreviewPopupDismissDelay; - lp.setSummary(lp.getEntries()[lp.findIndexOfValue(lp.getValue())]); + final CharSequence[] entries = lp.getEntries(); + if (entries == null || entries.length <= 0) return; + lp.setSummary(entries[lp.findIndexOfValue(lp.getValue())]); } private void updateVoiceModeSummary() { mVoicePreference.setSummary( getResources().getStringArray(R.array.voice_input_modes_summary) - [mVoicePreference.findIndexOfValue(mVoicePreference.getValue())]); + [mVoicePreference.findIndexOfValue(mVoicePreference.getValue())]); } private void refreshEnablingsOfKeypressSoundAndVibrationSettings( diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java index a43b90525..6e7d985d6 100644 --- a/java/src/com/android/inputmethod/latin/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/StringUtils.java @@ -184,6 +184,9 @@ public class StringUtils { final char[] characters = string.toCharArray(); final int length = characters.length; final int[] codePoints = new int[Character.codePointCount(characters, 0, length)]; + if (length <= 0) { + return new int[0]; + } int codePoint = Character.codePointAt(characters, 0); int dsti = 0; for (int srci = Character.charCount(codePoint); diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 892245402..03ed9d98c 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -26,6 +26,7 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import java.io.File; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; @@ -34,7 +35,7 @@ import java.util.concurrent.ConcurrentHashMap; * This class loads a dictionary and provides a list of suggestions for a given sequence of * characters. This includes corrections and completions. */ -public class Suggest implements Dictionary.WordCallback { +public class Suggest { public static final String TAG = Suggest.class.getSimpleName(); public static final int APPROX_MAX_WORD_LENGTH = 32; @@ -58,30 +59,34 @@ public class Suggest implements Dictionary.WordCallback { public static final String DICT_KEY_CONTACTS = "contacts"; // User dictionary, the system-managed one. public static final String DICT_KEY_USER = "user"; - // User history dictionary for the unigram map, internal to LatinIME - public static final String DICT_KEY_USER_HISTORY_UNIGRAM = "history_unigram"; - // User history dictionary for the bigram map, internal to LatinIME - public static final String DICT_KEY_USER_HISTORY_BIGRAM = "history_bigram"; + // User history dictionary internal to LatinIME + public static final String DICT_KEY_USER_HISTORY = "history"; public static final String DICT_KEY_WHITELIST ="whitelist"; + // TODO: remove this map. This only serves as backward compatibility with a feature + // that has never been used and has been broken for a while. + private static final HashMap<String, Integer> sDictKeyToDictIndex + = new HashMap<String, Integer>(); + static { + sDictKeyToDictIndex.put(DICT_KEY_MAIN, DIC_MAIN); + sDictKeyToDictIndex.put(DICT_KEY_USER, DIC_USER); + sDictKeyToDictIndex.put(DICT_KEY_USER_HISTORY, DIC_USER_HISTORY); + sDictKeyToDictIndex.put(DICT_KEY_CONTACTS, DIC_CONTACTS); + sDictKeyToDictIndex.put(DICT_KEY_WHITELIST, DIC_WHITELIST); + } private static final boolean DBG = LatinImeLogger.sDBG; private Dictionary mMainDictionary; private ContactsBinaryDictionary mContactsDict; private WhitelistDictionary mWhiteListDictionary; - private final ConcurrentHashMap<String, Dictionary> mUnigramDictionaries = - new ConcurrentHashMap<String, Dictionary>(); - private final ConcurrentHashMap<String, Dictionary> mBigramDictionaries = + private final ConcurrentHashMap<String, Dictionary> mDictionaries = new ConcurrentHashMap<String, Dictionary>(); public static final int MAX_SUGGESTIONS = 18; - private static final int PREF_MAX_BIGRAMS = 60; - private float mAutoCorrectionThreshold; private ArrayList<SuggestedWordInfo> mSuggestions = new ArrayList<SuggestedWordInfo>(); - private ArrayList<SuggestedWordInfo> mBigramSuggestions = new ArrayList<SuggestedWordInfo>(); private CharSequence mConsideredWord; // TODO: Remove these member variables by passing more context to addWord() callback method @@ -100,14 +105,13 @@ public class Suggest implements Dictionary.WordCallback { final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary, startOffset, length /* useFullEditDistance */, false, locale); mMainDictionary = mainDict; - addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, mainDict); - addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, mainDict); + addOrReplaceDictionary(mDictionaries, DICT_KEY_MAIN, mainDict); initWhitelistAndAutocorrectAndPool(context, locale); } private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) { mWhiteListDictionary = new WhitelistDictionary(context, locale); - addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary); + addOrReplaceDictionary(mDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary); } private void initAsynchronously(final Context context, final Locale locale) { @@ -136,8 +140,7 @@ public class Suggest implements Dictionary.WordCallback { public void run() { final DictionaryCollection newMainDict = DictionaryFactory.createMainDictionaryFromManager(context, locale); - addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, newMainDict); - addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, newMainDict); + addOrReplaceDictionary(mDictionaries, DICT_KEY_MAIN, newMainDict); mMainDictionary = newMainDict; } }.start(); @@ -158,7 +161,7 @@ public class Suggest implements Dictionary.WordCallback { } public ConcurrentHashMap<String, Dictionary> getUnigramDictionaries() { - return mUnigramDictionaries; + return mDictionaries; } public static int getApproxMaxWordLength() { @@ -170,7 +173,7 @@ public class Suggest implements Dictionary.WordCallback { * before the main dictionary, if set. This refers to the system-managed user dictionary. */ public void setUserDictionary(UserBinaryDictionary userDictionary) { - addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER, userDictionary); + addOrReplaceDictionary(mDictionaries, DICT_KEY_USER, userDictionary); } /** @@ -180,15 +183,11 @@ public class Suggest implements Dictionary.WordCallback { */ public void setContactsDictionary(ContactsBinaryDictionary contactsDictionary) { mContactsDict = contactsDictionary; - addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary); - addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary); + addOrReplaceDictionary(mDictionaries, DICT_KEY_CONTACTS, contactsDictionary); } public void setUserHistoryDictionary(UserHistoryDictionary userHistoryDictionary) { - addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER_HISTORY_UNIGRAM, - userHistoryDictionary); - addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_USER_HISTORY_BIGRAM, - userHistoryDictionary); + addOrReplaceDictionary(mDictionaries, DICT_KEY_USER_HISTORY, userHistoryDictionary); } public void setAutoCorrectionThreshold(float threshold) { @@ -212,50 +211,14 @@ public class Suggest implements Dictionary.WordCallback { return sb; } - protected void addBigramToSuggestions(SuggestedWordInfo bigram) { - mSuggestions.add(bigram); - } - - private static final WordComposer sEmptyWordComposer = new WordComposer(); - public SuggestedWords getBigramPredictions(CharSequence prevWordForBigram) { - LatinImeLogger.onStartSuggestion(prevWordForBigram); - mIsFirstCharCapitalized = false; - mIsAllUpperCase = false; - mTrailingSingleQuotesCount = 0; - mSuggestions = new ArrayList<SuggestedWordInfo>(MAX_SUGGESTIONS); - - // Treating USER_TYPED as UNIGRAM suggestion for logging now. - LatinImeLogger.onAddSuggestedWord("", Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM); - mConsideredWord = ""; - - mBigramSuggestions = new ArrayList<SuggestedWordInfo>(PREF_MAX_BIGRAMS); - - getAllBigrams(prevWordForBigram, sEmptyWordComposer); - - // Nothing entered: return all bigrams for the previous word - int insertCount = Math.min(mBigramSuggestions.size(), MAX_SUGGESTIONS); - for (int i = 0; i < insertCount; ++i) { - addBigramToSuggestions(mBigramSuggestions.get(i)); - } - - SuggestedWordInfo.removeDups(mSuggestions); - - return new SuggestedWords(mSuggestions, - false /* typedWordValid */, - false /* hasAutoCorrectionCandidate */, - false /* allowsToBeAutoCorrected */, - false /* isPunctuationSuggestions */, - false /* isObsoleteSuggestions */, - true /* isPrediction */); - } - // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder public SuggestedWords getSuggestedWords( final WordComposer wordComposer, CharSequence prevWordForBigram, - final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) { + final ProximityInfo proximityInfo, final boolean isCorrectionEnabled, + final boolean isPrediction) { LatinImeLogger.onStartSuggestion(prevWordForBigram); - mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); - mIsAllUpperCase = wordComposer.isAllUpperCase(); + mIsFirstCharCapitalized = !isPrediction && wordComposer.isFirstCharCapitalized(); + mIsAllUpperCase = !isPrediction && wordComposer.isAllUpperCase(); mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount(); mSuggestions = new ArrayList<SuggestedWordInfo>(MAX_SUGGESTIONS); @@ -269,37 +232,29 @@ public class Suggest implements Dictionary.WordCallback { if (wordComposer.size() <= 1 && isCorrectionEnabled) { // At first character typed, search only the bigrams - mBigramSuggestions = new ArrayList<SuggestedWordInfo>(PREF_MAX_BIGRAMS); - if (!TextUtils.isEmpty(prevWordForBigram)) { - getAllBigrams(prevWordForBigram, wordComposer); - if (TextUtils.isEmpty(consideredWord)) { - // Nothing entered: return all bigrams for the previous word - int insertCount = Math.min(mBigramSuggestions.size(), MAX_SUGGESTIONS); - for (int i = 0; i < insertCount; ++i) { - addBigramToSuggestions(mBigramSuggestions.get(i)); - } - } else { - // Word entered: return only bigrams that match the first char of the typed word - final char currentChar = consideredWord.charAt(0); + final CharSequence lowerPrevWord; + if (StringUtils.hasUpperCase(prevWordForBigram)) { // TODO: Must pay attention to locale when changing case. - // TODO: Use codepoint instead of char - final char currentCharUpper = Character.toUpperCase(currentChar); - int count = 0; - final int bigramSuggestionSize = mBigramSuggestions.size(); - for (int i = 0; i < bigramSuggestionSize; i++) { - final SuggestedWordInfo bigramSuggestion = mBigramSuggestions.get(i); - final char bigramSuggestionFirstChar = - (char)bigramSuggestion.codePointAt(0); - if (bigramSuggestionFirstChar == currentChar - || bigramSuggestionFirstChar == currentCharUpper) { - addBigramToSuggestions(bigramSuggestion); - if (++count > MAX_SUGGESTIONS) break; - } + lowerPrevWord = prevWordForBigram.toString().toLowerCase(); + } else { + lowerPrevWord = null; + } + for (final String key : mDictionaries.keySet()) { + final int dicTypeId = sDictKeyToDictIndex.get(key); + final Dictionary dictionary = mDictionaries.get(key); + final ArrayList<SuggestedWordInfo> suggestions = + dictionary.getBigrams(wordComposer, prevWordForBigram); + if (null != lowerPrevWord) { + suggestions.addAll(dictionary.getBigrams(wordComposer, lowerPrevWord)); + } + for (final SuggestedWordInfo suggestion : suggestions) { + final String suggestionStr = suggestion.mWord.toString(); + addWord(suggestionStr.toCharArray(), null, 0, suggestionStr.length(), + suggestion.mScore, dicTypeId, Dictionary.BIGRAM, mSuggestions); } } } - } else if (wordComposer.size() > 1) { final WordComposer wordComposerForLookup; if (mTrailingSingleQuotesCount > 0) { @@ -311,12 +266,19 @@ public class Suggest implements Dictionary.WordCallback { wordComposerForLookup = wordComposer; } // At second character typed, search the unigrams (scores being affected by bigrams) - for (final String key : mUnigramDictionaries.keySet()) { + for (final String key : mDictionaries.keySet()) { // Skip UserUnigramDictionary and WhitelistDictionary to lookup - if (key.equals(DICT_KEY_USER_HISTORY_UNIGRAM) || key.equals(DICT_KEY_WHITELIST)) + if (key.equals(DICT_KEY_USER_HISTORY) || key.equals(DICT_KEY_WHITELIST)) continue; - final Dictionary dictionary = mUnigramDictionaries.get(key); - dictionary.getWords(wordComposerForLookup, prevWordForBigram, this, proximityInfo); + final int dicTypeId = sDictKeyToDictIndex.get(key); + final Dictionary dictionary = mDictionaries.get(key); + final ArrayList<SuggestedWordInfo> suggestions = dictionary.getWords( + wordComposerForLookup, prevWordForBigram, proximityInfo); + for (final SuggestedWordInfo suggestion : suggestions) { + final String suggestionStr = suggestion.mWord.toString(); + addWord(suggestionStr.toCharArray(), null, 0, suggestionStr.length(), + suggestion.mScore, dicTypeId, Dictionary.UNIGRAM, mSuggestions); + } } } @@ -326,7 +288,7 @@ public class Suggest implements Dictionary.WordCallback { final boolean hasAutoCorrection; if (isCorrectionEnabled) { final CharSequence autoCorrection = - AutoCorrection.computeAutoCorrectionWord(mUnigramDictionaries, wordComposer, + AutoCorrection.computeAutoCorrectionWord(mDictionaries, wordComposer, mSuggestions, consideredWord, mAutoCorrectionThreshold, whitelistedWord); hasAutoCorrection = (null != autoCorrection); @@ -348,12 +310,14 @@ public class Suggest implements Dictionary.WordCallback { } } - mSuggestions.add(0, new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE, - SuggestedWordInfo.KIND_TYPED)); + if (!isPrediction) { + mSuggestions.add(0, new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE, + SuggestedWordInfo.KIND_TYPED)); + } SuggestedWordInfo.removeDups(mSuggestions); final ArrayList<SuggestedWordInfo> suggestionsList; - if (DBG) { + if (DBG && !mSuggestions.isEmpty()) { suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, mSuggestions); } else { suggestionsList = mSuggestions; @@ -386,29 +350,12 @@ public class Suggest implements Dictionary.WordCallback { autoCorrectionAvailable = false; } return new SuggestedWords(suggestionsList, - !allowsToBeAutoCorrected /* typedWordValid */, - autoCorrectionAvailable /* hasAutoCorrectionCandidate */, - allowsToBeAutoCorrected /* allowsToBeAutoCorrected */, + !isPrediction && !allowsToBeAutoCorrected /* typedWordValid */, + !isPrediction && autoCorrectionAvailable /* hasAutoCorrectionCandidate */, + !isPrediction && allowsToBeAutoCorrected /* allowsToBeAutoCorrected */, false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */, - false /* isPrediction */); - } - - /** - * Adds all bigram predictions for prevWord. Also checks the lower case version of prevWord if - * it contains any upper case characters. - */ - private void getAllBigrams(final CharSequence prevWord, final WordComposer wordComposer) { - if (StringUtils.hasUpperCase(prevWord)) { - // TODO: Must pay attention to locale when changing case. - final CharSequence lowerPrevWord = prevWord.toString().toLowerCase(); - for (final Dictionary dictionary : mBigramDictionaries.values()) { - dictionary.getBigrams(wordComposer, lowerPrevWord, this); - } - } - for (final Dictionary dictionary : mBigramDictionaries.values()) { - dictionary.getBigrams(wordComposer, prevWord, this); - } + isPrediction); } private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo( @@ -438,19 +385,11 @@ public class Suggest implements Dictionary.WordCallback { } // TODO: Use codepoint instead of char - @Override - public boolean addWord(final char[] word, final int offset, final int length, int score, - final int dicTypeId, final int dataType) { + public boolean addWord(final char[] word, int[] indices, final int offset, final int length, + int score, final int dicTypeId, final int dataType, + final ArrayList<SuggestedWordInfo> suggestions) { int dataTypeForLog = dataType; - final ArrayList<SuggestedWordInfo> suggestions; - final int prefMaxSuggestions; - if (dataType == Dictionary.BIGRAM) { - suggestions = mBigramSuggestions; - prefMaxSuggestions = PREF_MAX_BIGRAMS; - } else { - suggestions = mSuggestions; - prefMaxSuggestions = MAX_SUGGESTIONS; - } + final int prefMaxSuggestions = MAX_SUGGESTIONS; int pos = 0; @@ -513,8 +452,7 @@ public class Suggest implements Dictionary.WordCallback { public void close() { final HashSet<Dictionary> dictionaries = new HashSet<Dictionary>(); - dictionaries.addAll(mUnigramDictionaries.values()); - dictionaries.addAll(mBigramDictionaries.values()); + dictionaries.addAll(mDictionaries.values()); for (final Dictionary dictionary : dictionaries) { dictionary.close(); } diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index 1ed91fe71..45ac9ff53 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -129,10 +129,11 @@ public class SuggestedWords { public static final int KIND_BLACKLIST = 4; // Blacklisted word public static final int KIND_HARDCODED = 5; // Hardcoded suggestion, e.g. punctuation public static final int KIND_APP_DEFINED = 6; // Suggested by the application + public static final int KIND_SHORTCUT = 7; // A shortcut private final String mWordStr; public final CharSequence mWord; public final int mScore; - public final int mKind; + public final int mKind; // one of the KIND_* constants above public final int mCodePointCount; private String mDebugString = ""; diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java index 673b54500..74f27e3cc 100644 --- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java @@ -19,7 +19,9 @@ package com.android.inputmethod.latin; import android.content.Context; import com.android.inputmethod.keyboard.ProximityInfo; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import java.util.ArrayList; import java.util.Locale; public class SynchronouslyLoadedContactsBinaryDictionary extends ContactsBinaryDictionary { @@ -30,11 +32,10 @@ public class SynchronouslyLoadedContactsBinaryDictionary extends ContactsBinaryD } @Override - public synchronized void getWords(final WordComposer codes, - final CharSequence prevWordForBigrams, final WordCallback callback, - final ProximityInfo proximityInfo) { + public synchronized ArrayList<SuggestedWordInfo> getWords(final WordComposer codes, + final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { syncReloadDictionaryIfRequired(); - getWordsInner(codes, prevWordForBigrams, callback, proximityInfo); + return getWordsInner(codes, prevWordForBigrams, proximityInfo); } @Override diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java index 1606a34e0..5b2a6edec 100644 --- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java @@ -19,6 +19,9 @@ package com.android.inputmethod.latin; import android.content.Context; import com.android.inputmethod.keyboard.ProximityInfo; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; + +import java.util.ArrayList; public class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDictionary { @@ -32,11 +35,10 @@ public class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDictionar } @Override - public synchronized void getWords(final WordComposer codes, - final CharSequence prevWordForBigrams, final WordCallback callback, - final ProximityInfo proximityInfo) { + public synchronized ArrayList<SuggestedWordInfo> getWords(final WordComposer codes, + final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { syncReloadDictionaryIfRequired(); - getWordsInner(codes, prevWordForBigrams, callback, proximityInfo); + return getWordsInner(codes, prevWordForBigrams, proximityInfo); } @Override diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java index 6fa1a25a1..5bcdb57b5 100644 --- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java @@ -36,11 +36,22 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { // TODO: use Words.SHORTCUT when it's public in the SDK final static String SHORTCUT = "shortcut"; - private static final String[] PROJECTION_QUERY = { - Words.WORD, - SHORTCUT, - Words.FREQUENCY, - }; + private static final String[] PROJECTION_QUERY; + static { + // 16 is JellyBean, but we want this to compile against ICS. + if (android.os.Build.VERSION.SDK_INT >= 16) { + PROJECTION_QUERY = new String[] { + Words.WORD, + SHORTCUT, + Words.FREQUENCY, + }; + } else { + PROJECTION_QUERY = new String[] { + Words.WORD, + Words.FREQUENCY, + }; + } + } private static final String NAME = "userunigram"; @@ -136,7 +147,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { requestArguments = localeElements; } final Cursor cursor = mContext.getContentResolver().query( - Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null); + Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null); try { addWords(cursor); } finally { @@ -182,16 +193,18 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { } private void addWords(Cursor cursor) { + // 16 is JellyBean, but we want this to compile against ICS. + final boolean hasShortcutColumn = android.os.Build.VERSION.SDK_INT >= 16; clearFusionDictionary(); if (cursor == null) return; if (cursor.moveToFirst()) { final int indexWord = cursor.getColumnIndex(Words.WORD); - final int indexShortcut = cursor.getColumnIndex(SHORTCUT); + final int indexShortcut = hasShortcutColumn ? cursor.getColumnIndex(SHORTCUT) : 0; final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY); while (!cursor.isAfterLast()) { - String word = cursor.getString(indexWord); - String shortcut = cursor.getString(indexShortcut); - int frequency = cursor.getInt(indexFrequency); + final String word = cursor.getString(indexWord); + final String shortcut = hasShortcutColumn ? cursor.getString(indexShortcut) : null; + final int frequency = cursor.getInt(indexFrequency); // Safeguard against adding really long words. if (word.length() < MAX_WORD_LENGTH) { super.addWord(word, null, frequency); diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index f2d21ab9b..8f71de0e7 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -44,6 +44,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.nio.channels.FileChannel; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 7fffc31c0..5f4d66091 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -32,12 +32,12 @@ import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.BinaryDictionary; import com.android.inputmethod.latin.ContactsBinaryDictionary; import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.Dictionary.WordCallback; import com.android.inputmethod.latin.DictionaryCollection; import com.android.inputmethod.latin.DictionaryFactory; import com.android.inputmethod.latin.LocaleUtils; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.StringUtils; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary; import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary; import com.android.inputmethod.latin.UserBinaryDictionary; @@ -203,7 +203,8 @@ public class AndroidSpellCheckerService extends SpellCheckerService EMPTY_STRING_ARRAY); } - private static class SuggestionsGatherer implements WordCallback { + // TODO: remove this class and replace it by storage local to the session. + private static class SuggestionsGatherer { public static class Result { public final String[] mSuggestions; public final boolean mHasRecommendedSuggestions; @@ -237,9 +238,8 @@ public class AndroidSpellCheckerService extends SpellCheckerService mScores = new int[mMaxLength]; } - @Override - synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score, - int dicTypeId, int dataType) { + synchronized public boolean addWord(char[] word, int[] spaceIndices, int wordOffset, + int wordLength, int score) { final int positionIndex = Arrays.binarySearch(mScores, 0, mLength, score); // binarySearch returns the index if the element exists, and -<insertion index> - 1 // if it doesn't. See documentation for binarySearch. @@ -780,8 +780,13 @@ public class AndroidSpellCheckerService extends SpellCheckerService try { dictInfo = mDictionaryPool.takeOrGetNull(); if (null == dictInfo) return getNotInDictEmptySuggestions(); - dictInfo.mDictionary.getWords(composer, prevWord, suggestionsGatherer, - dictInfo.mProximityInfo); + final ArrayList<SuggestedWordInfo> suggestions = dictInfo.mDictionary.getWords( + composer, prevWord, dictInfo.mProximityInfo); + for (final SuggestedWordInfo suggestion : suggestions) { + final String suggestionStr = suggestion.mWord.toString(); + suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0, + suggestionStr.length(), suggestion.mScore); + } isInDict = dictInfo.mDictionary.isValidWord(text); if (!isInDict && CAPITALIZE_NONE != capitalizeType) { // We want to test the word again if it's all caps or first caps only. |