Read and use user dictionary shortcuts.
Bug: 4646172 Change-Id: I51002c73d5bad1a698110c5cda02253348be8eed
This commit is contained in:
parent
a9aeb6f3cc
commit
19ad9bf145
8 changed files with 90 additions and 35 deletions
|
@ -149,7 +149,8 @@ public class ContactsDictionary extends ExpandableDictionary {
|
||||||
// capitalization of i.
|
// capitalization of i.
|
||||||
final int wordLen = word.length();
|
final int wordLen = word.length();
|
||||||
if (wordLen < maxWordLength && wordLen > 1) {
|
if (wordLen < maxWordLength && wordLen > 1) {
|
||||||
super.addWord(word, FREQUENCY_FOR_CONTACTS);
|
super.addWord(word, null /* shortcut */,
|
||||||
|
FREQUENCY_FOR_CONTACTS);
|
||||||
if (!TextUtils.isEmpty(prevWord)) {
|
if (!TextUtils.isEmpty(prevWord)) {
|
||||||
super.setBigram(prevWord, word,
|
super.setBigram(prevWord, word,
|
||||||
FREQUENCY_FOR_CONTACTS_BIGRAM);
|
FREQUENCY_FOR_CONTACTS_BIGRAM);
|
||||||
|
|
|
@ -23,11 +23,6 @@ import com.android.inputmethod.keyboard.ProximityInfo;
|
||||||
* strokes.
|
* strokes.
|
||||||
*/
|
*/
|
||||||
public abstract class Dictionary {
|
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.
|
* The weight to give to a word if it's length is the same as the number of typed characters.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -22,6 +22,7 @@ import com.android.inputmethod.keyboard.KeyDetector;
|
||||||
import com.android.inputmethod.keyboard.Keyboard;
|
import com.android.inputmethod.keyboard.Keyboard;
|
||||||
import com.android.inputmethod.keyboard.ProximityInfo;
|
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,6 +54,8 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
boolean mTerminal;
|
boolean mTerminal;
|
||||||
Node mParent;
|
Node mParent;
|
||||||
NodeArray mChildren;
|
NodeArray mChildren;
|
||||||
|
ArrayList<char[]> mShortcutTargets;
|
||||||
|
boolean mShortcutOnly;
|
||||||
LinkedList<NextWord> mNGrams; // Supports ngram
|
LinkedList<NextWord> mNGrams; // Supports ngram
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,15 +153,15 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
return BinaryDictionary.MAX_WORD_LENGTH;
|
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) {
|
if (word.length() >= BinaryDictionary.MAX_WORD_LENGTH) {
|
||||||
return;
|
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,
|
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();
|
final int wordLength = word.length();
|
||||||
if (wordLength <= depth) return;
|
if (wordLength <= depth) return;
|
||||||
final char c = word.charAt(depth);
|
final char c = word.charAt(depth);
|
||||||
|
@ -172,15 +175,25 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
final boolean isShortcutOnly = (null != shortcutTarget);
|
||||||
if (childNode == null) {
|
if (childNode == null) {
|
||||||
childNode = new Node();
|
childNode = new Node();
|
||||||
childNode.mCode = c;
|
childNode.mCode = c;
|
||||||
childNode.mParent = parentNode;
|
childNode.mParent = parentNode;
|
||||||
|
childNode.mShortcutOnly = isShortcutOnly;
|
||||||
children.add(childNode);
|
children.add(childNode);
|
||||||
}
|
}
|
||||||
if (wordLength == depth + 1) {
|
if (wordLength == depth + 1) {
|
||||||
// Terminate this word
|
// Terminate this word
|
||||||
childNode.mTerminal = true;
|
childNode.mTerminal = true;
|
||||||
|
if (isShortcutOnly) {
|
||||||
|
if (null == childNode.mShortcutTargets) {
|
||||||
|
childNode.mShortcutTargets = new ArrayList<char[]>();
|
||||||
|
}
|
||||||
|
childNode.mShortcutTargets.add(shortcutTarget.toCharArray());
|
||||||
|
} else {
|
||||||
|
childNode.mShortcutOnly = false;
|
||||||
|
}
|
||||||
childNode.mFrequency = Math.max(frequency, childNode.mFrequency);
|
childNode.mFrequency = Math.max(frequency, childNode.mFrequency);
|
||||||
if (childNode.mFrequency > 255) childNode.mFrequency = 255;
|
if (childNode.mFrequency > 255) childNode.mFrequency = 255;
|
||||||
return;
|
return;
|
||||||
|
@ -188,7 +201,7 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
if (childNode.mChildren == null) {
|
if (childNode.mChildren == null) {
|
||||||
childNode.mChildren = new NodeArray();
|
childNode.mChildren = new NodeArray();
|
||||||
}
|
}
|
||||||
addWordRec(childNode.mChildren, word, depth + 1, frequency, childNode);
|
addWordRec(childNode.mChildren, word, depth + 1, shortcutTarget, frequency, childNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -239,7 +252,13 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
if (mRequiresReload) startDictionaryLoadingTaskLocked();
|
if (mRequiresReload) startDictionaryLoadingTaskLocked();
|
||||||
if (mUpdatingDictionary) return false;
|
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) {
|
protected int getWordFrequency(CharSequence word) {
|
||||||
// Case-sensitive search
|
// 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;
|
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
|
* 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
|
* 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 {
|
} else {
|
||||||
finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength);
|
finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength);
|
||||||
}
|
}
|
||||||
if (!callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
|
if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq, callback)) {
|
||||||
Dictionary.UNIGRAM)) {
|
// No space left in the queue, bail out
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -344,18 +392,18 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
|
|
||||||
if (codeSize == inputIndex + 1) {
|
if (codeSize == inputIndex + 1) {
|
||||||
if (terminal) {
|
if (terminal) {
|
||||||
if (INCLUDE_TYPED_WORD_IF_VALID
|
final int finalFreq;
|
||||||
|| !same(word, depth + 1, codes.getTypedWord())) {
|
if (skipPos < 0) {
|
||||||
final int finalFreq;
|
finalFreq = freq * snr * addedAttenuation
|
||||||
if (skipPos < 0) {
|
* FULL_WORD_SCORE_MULTIPLIER;
|
||||||
finalFreq = freq * snr * addedAttenuation
|
} else {
|
||||||
* FULL_WORD_SCORE_MULTIPLIER;
|
finalFreq = computeSkippedWordFinalFreq(freq,
|
||||||
} else {
|
snr * addedAttenuation, mInputLength);
|
||||||
finalFreq = computeSkippedWordFinalFreq(freq,
|
}
|
||||||
snr * addedAttenuation, mInputLength);
|
if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq,
|
||||||
}
|
callback)) {
|
||||||
callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
|
// No space left in the queue, bail out
|
||||||
Dictionary.UNIGRAM);
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (children != null) {
|
if (children != null) {
|
||||||
|
|
|
@ -1120,7 +1120,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addWordToDictionary(String word) {
|
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
|
// Suggestion strip should be updated after the operation of adding word to the
|
||||||
// user dictionary
|
// user dictionary
|
||||||
mHandler.postUpdateSuggestions();
|
mHandler.postUpdateSuggestions();
|
||||||
|
|
|
@ -42,6 +42,6 @@ public class SynchronouslyLoadedUserDictionary extends UserDictionary {
|
||||||
@Override
|
@Override
|
||||||
public synchronized boolean isValidWord(CharSequence word) {
|
public synchronized boolean isValidWord(CharSequence word) {
|
||||||
blockingReloadDictionaryIfRequired();
|
blockingReloadDictionaryIfRequired();
|
||||||
return getWordFrequency(word) > -1;
|
return super.isValidWord(word);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,11 @@ import java.util.Arrays;
|
||||||
|
|
||||||
public class UserDictionary extends ExpandableDictionary {
|
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 = {
|
private static final String[] PROJECTION_QUERY = {
|
||||||
Words.WORD,
|
Words.WORD,
|
||||||
|
SHORTCUT,
|
||||||
Words.FREQUENCY,
|
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
|
* @param word the word to add. If the word is capitalized, then the dictionary will
|
||||||
* recognize it as a capitalized word when searched.
|
* recognize it as a capitalized word when searched.
|
||||||
* @param frequency the frequency of occurrence of the word. A frequency of 255 is considered
|
* @param frequency the frequency of occurrence of the word. A frequency of 255 is considered
|
||||||
* the highest.
|
* the highest.
|
||||||
* @TODO use a higher or float range for frequency
|
* @TODO use a higher or float range for frequency
|
||||||
*/
|
*/
|
||||||
@Override
|
public synchronized void addWordToUserDictionary(final String word, final int frequency) {
|
||||||
public synchronized void addWord(final String word, final int frequency) {
|
|
||||||
// Force load the dictionary here synchronously
|
// Force load the dictionary here synchronously
|
||||||
if (getRequiresReload()) loadDictionaryAsync();
|
if (getRequiresReload()) loadDictionaryAsync();
|
||||||
// TODO: do something for the UI. With the following, any sufficiently long word will
|
// 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();
|
final int maxWordLength = getMaxWordLength();
|
||||||
if (cursor.moveToFirst()) {
|
if (cursor.moveToFirst()) {
|
||||||
final int indexWord = cursor.getColumnIndex(Words.WORD);
|
final int indexWord = cursor.getColumnIndex(Words.WORD);
|
||||||
|
final int indexShortcut = cursor.getColumnIndex(SHORTCUT);
|
||||||
final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY);
|
final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY);
|
||||||
while (!cursor.isAfterLast()) {
|
while (!cursor.isAfterLast()) {
|
||||||
String word = cursor.getString(indexWord);
|
String word = cursor.getString(indexWord);
|
||||||
|
String shortcut = cursor.getString(indexShortcut);
|
||||||
int frequency = cursor.getInt(indexFrequency);
|
int frequency = cursor.getInt(indexFrequency);
|
||||||
// Safeguard against adding really long words. Stack may overflow due
|
// Safeguard against adding really long words. Stack may overflow due
|
||||||
// to recursion
|
// to recursion
|
||||||
if (word.length() < maxWordLength) {
|
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();
|
cursor.moveToNext();
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,7 +176,7 @@ public class UserHistoryDictionary extends ExpandableDictionary {
|
||||||
* The second word may not be null (a NullPointerException would be thrown).
|
* The second word may not be null (a NullPointerException would be thrown).
|
||||||
*/
|
*/
|
||||||
public int addToUserHistory(final String word1, String word2) {
|
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
|
// Do not insert a word as a bigram of itself
|
||||||
if (word2.equals(word1)) {
|
if (word2.equals(word1)) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -246,7 +246,7 @@ public class UserHistoryDictionary extends ExpandableDictionary {
|
||||||
// Safeguard against adding really long words. Stack may overflow due
|
// Safeguard against adding really long words. Stack may overflow due
|
||||||
// to recursive lookup
|
// to recursive lookup
|
||||||
if (null == word1) {
|
if (null == word1) {
|
||||||
super.addWord(word2, frequency);
|
super.addWord(word2, null /* shortcut */, frequency);
|
||||||
} else if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH
|
} else if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH
|
||||||
&& word2.length() < BinaryDictionary.MAX_WORD_LENGTH) {
|
&& word2.length() < BinaryDictionary.MAX_WORD_LENGTH) {
|
||||||
super.setBigram(word1, word2, frequency);
|
super.setBigram(word1, word2, frequency);
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class WhitelistDictionary extends ExpandableDictionary {
|
||||||
if (before != null && after != null) {
|
if (before != null && after != null) {
|
||||||
mWhitelistWords.put(
|
mWhitelistWords.put(
|
||||||
before.toLowerCase(), new Pair<Integer, String>(score, after));
|
before.toLowerCase(), new Pair<Integer, String>(score, after));
|
||||||
addWord(after, score);
|
addWord(after, null /* shortcut */, score);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
|
|
Loading…
Reference in a new issue