diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionary.java b/java/src/com/android/inputmethod/latin/ContactsDictionary.java index 8a7dfb839..83bc9046b 100644 --- a/java/src/com/android/inputmethod/latin/ContactsDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsDictionary.java @@ -149,7 +149,8 @@ public class ContactsDictionary extends ExpandableDictionary { // capitalization of i. final int wordLen = word.length(); if (wordLen < maxWordLength && wordLen > 1) { - super.addWord(word, FREQUENCY_FOR_CONTACTS); + super.addWord(word, null /* shortcut */, + FREQUENCY_FOR_CONTACTS); if (!TextUtils.isEmpty(prevWord)) { super.setBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM); diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index a405aa409..1ec678f7f 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -23,11 +23,6 @@ import com.android.inputmethod.keyboard.ProximityInfo; * strokes. */ public abstract class Dictionary { - /** - * Whether or not to replicate the typed word in the suggested list, even if it's valid. - */ - protected static final boolean INCLUDE_TYPED_WORD_IF_VALID = false; - /** * The weight to give to a word if it's length is the same as the number of typed characters. */ diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index fe21ebe87..7a740b3f1 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -22,6 +22,7 @@ import com.android.inputmethod.keyboard.KeyDetector; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.ProximityInfo; +import java.util.ArrayList; import java.util.LinkedList; /** @@ -53,6 +54,8 @@ public class ExpandableDictionary extends Dictionary { boolean mTerminal; Node mParent; NodeArray mChildren; + ArrayList mShortcutTargets; + boolean mShortcutOnly; LinkedList mNGrams; // Supports ngram } @@ -150,15 +153,15 @@ public class ExpandableDictionary extends Dictionary { return BinaryDictionary.MAX_WORD_LENGTH; } - public void addWord(String word, int frequency) { + public void addWord(final String word, final String shortcutTarget, final int frequency) { if (word.length() >= BinaryDictionary.MAX_WORD_LENGTH) { return; } - addWordRec(mRoots, word, 0, frequency, null); + addWordRec(mRoots, word, 0, shortcutTarget, frequency, null); } private void addWordRec(NodeArray children, final String word, final int depth, - final int frequency, Node parentNode) { + final String shortcutTarget, final int frequency, Node parentNode) { final int wordLength = word.length(); if (wordLength <= depth) return; final char c = word.charAt(depth); @@ -172,15 +175,25 @@ public class ExpandableDictionary extends Dictionary { break; } } + final boolean isShortcutOnly = (null != shortcutTarget); if (childNode == null) { childNode = new Node(); childNode.mCode = c; childNode.mParent = parentNode; + childNode.mShortcutOnly = isShortcutOnly; children.add(childNode); } if (wordLength == depth + 1) { // Terminate this word childNode.mTerminal = true; + if (isShortcutOnly) { + if (null == childNode.mShortcutTargets) { + childNode.mShortcutTargets = new ArrayList(); + } + childNode.mShortcutTargets.add(shortcutTarget.toCharArray()); + } else { + childNode.mShortcutOnly = false; + } childNode.mFrequency = Math.max(frequency, childNode.mFrequency); if (childNode.mFrequency > 255) childNode.mFrequency = 255; return; @@ -188,7 +201,7 @@ public class ExpandableDictionary extends Dictionary { if (childNode.mChildren == null) { childNode.mChildren = new NodeArray(); } - addWordRec(childNode.mChildren, word, depth + 1, frequency, childNode); + addWordRec(childNode.mChildren, word, depth + 1, shortcutTarget, frequency, childNode); } @Override @@ -239,7 +252,13 @@ public class ExpandableDictionary extends Dictionary { if (mRequiresReload) startDictionaryLoadingTaskLocked(); if (mUpdatingDictionary) return false; } - return getWordFrequency(word) > -1; + final Node node = searchNode(mRoots, word, 0, word.length()); + // If node is null, we didn't find the word, so it's not valid. + // If node.mShortcutOnly is true, then it exists as a shortcut but not as a word, + // so that means it's not a valid word. + // If node.mShortcutOnly is false, then it exists as a word (it may also exist as + // a shortcut, but this does not matter), so it's a valid word. + return (node == null) ? false : !node.mShortcutOnly; } /** @@ -247,7 +266,7 @@ public class ExpandableDictionary extends Dictionary { */ protected int getWordFrequency(CharSequence word) { // Case-sensitive search - Node node = searchNode(mRoots, word, 0, word.length()); + final Node node = searchNode(mRoots, word, 0, word.length()); return (node == null) ? -1 : node.mFrequency; } @@ -261,6 +280,35 @@ public class ExpandableDictionary extends Dictionary { } } + /** + * Helper method to add a word and its shortcuts. + * + * @param node the terminal node + * @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 + * @return whether there is still space for more words. {@see Dictionary.WordCallback#addWord}. + */ + private boolean addWordAndShortcutsFromNode(final Node node, final char[] word, final int depth, + final int finalFreq, final WordCallback callback) { + if (finalFreq > 0 && !node.mShortcutOnly) { + if (!callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId, Dictionary.UNIGRAM)) { + 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; + } + } + } + return true; + } + /** * Recursively traverse the tree for words that match the input. Input consists of * a list of arrays. Each item in the list is one input character position. An input @@ -313,8 +361,8 @@ public class ExpandableDictionary extends Dictionary { } else { finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength); } - if (!callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId, - Dictionary.UNIGRAM)) { + if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq, callback)) { + // No space left in the queue, bail out return; } } @@ -344,18 +392,18 @@ public class ExpandableDictionary extends Dictionary { if (codeSize == inputIndex + 1) { if (terminal) { - if (INCLUDE_TYPED_WORD_IF_VALID - || !same(word, depth + 1, codes.getTypedWord())) { - final int finalFreq; - if (skipPos < 0) { - finalFreq = freq * snr * addedAttenuation - * FULL_WORD_SCORE_MULTIPLIER; - } else { - finalFreq = computeSkippedWordFinalFreq(freq, - snr * addedAttenuation, mInputLength); - } - callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId, - Dictionary.UNIGRAM); + final int finalFreq; + if (skipPos < 0) { + finalFreq = freq * snr * addedAttenuation + * FULL_WORD_SCORE_MULTIPLIER; + } else { + finalFreq = computeSkippedWordFinalFreq(freq, + snr * addedAttenuation, mInputLength); + } + if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq, + callback)) { + // No space left in the queue, bail out + return; } } if (children != null) { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 8eea373a4..28dcb1e89 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1130,7 +1130,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public boolean addWordToDictionary(String word) { - mUserDictionary.addWord(word, 128); + mUserDictionary.addWordToUserDictionary(word, 128); // Suggestion strip should be updated after the operation of adding word to the // user dictionary mHandler.postUpdateSuggestions(); diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserDictionary.java index 50e8b249e..b78be89b8 100644 --- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserDictionary.java +++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserDictionary.java @@ -42,6 +42,6 @@ public class SynchronouslyLoadedUserDictionary extends UserDictionary { @Override public synchronized boolean isValidWord(CharSequence word) { blockingReloadDictionaryIfRequired(); - return getWordFrequency(word) > -1; + return super.isValidWord(word); } } diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java index 6beeaace9..218bac72a 100644 --- a/java/src/com/android/inputmethod/latin/UserDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserDictionary.java @@ -31,8 +31,11 @@ import java.util.Arrays; public class UserDictionary extends ExpandableDictionary { + // 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, }; @@ -149,15 +152,18 @@ public class UserDictionary extends ExpandableDictionary { } /** - * Adds a word to the dictionary and makes it persistent. + * Adds a word to the user dictionary and makes it persistent. + * + * This will call upon the system interface to do the actual work through the intent + * readied by the system to this effect. + * * @param word the word to add. If the word is capitalized, then the dictionary will * recognize it as a capitalized word when searched. * @param frequency the frequency of occurrence of the word. A frequency of 255 is considered * the highest. * @TODO use a higher or float range for frequency */ - @Override - public synchronized void addWord(final String word, final int frequency) { + public synchronized void addWordToUserDictionary(final String word, final int frequency) { // Force load the dictionary here synchronously if (getRequiresReload()) loadDictionaryAsync(); // TODO: do something for the UI. With the following, any sufficiently long word will @@ -191,14 +197,19 @@ public class UserDictionary extends ExpandableDictionary { final int maxWordLength = getMaxWordLength(); if (cursor.moveToFirst()) { final int indexWord = cursor.getColumnIndex(Words.WORD); + final int indexShortcut = cursor.getColumnIndex(SHORTCUT); 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); // Safeguard against adding really long words. Stack may overflow due // to recursion if (word.length() < maxWordLength) { - super.addWord(word, frequency); + super.addWord(word, null, frequency); + } + if (null != shortcut && shortcut.length() < maxWordLength) { + super.addWord(shortcut, word, frequency); } cursor.moveToNext(); } diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java index 9191aa953..e13602e50 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java @@ -176,7 +176,7 @@ public class UserHistoryDictionary extends ExpandableDictionary { * The second word may not be null (a NullPointerException would be thrown). */ public int addToUserHistory(final String word1, String word2) { - super.addWord(word2, FREQUENCY_FOR_TYPED); + super.addWord(word2, null /* shortcut */, FREQUENCY_FOR_TYPED); // Do not insert a word as a bigram of itself if (word2.equals(word1)) { return 0; @@ -246,7 +246,7 @@ public class UserHistoryDictionary extends ExpandableDictionary { // Safeguard against adding really long words. Stack may overflow due // to recursive lookup if (null == word1) { - super.addWord(word2, frequency); + super.addWord(word2, null /* shortcut */, frequency); } else if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) { super.setBigram(word1, word2, frequency); diff --git a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java index bb3ba8651..a0de2f970 100644 --- a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java +++ b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java @@ -66,7 +66,7 @@ public class WhitelistDictionary extends ExpandableDictionary { if (before != null && after != null) { mWhitelistWords.put( before.toLowerCase(), new Pair(score, after)); - addWord(after, score); + addWord(after, null /* shortcut */, score); } } } catch (NumberFormatException e) {