Read and use user dictionary shortcuts.

Bug: 4646172

Change-Id: I51002c73d5bad1a698110c5cda02253348be8eed
main
Jean Chalard 2012-05-11 16:33:01 +09:00
parent a9aeb6f3cc
commit 19ad9bf145
8 changed files with 90 additions and 35 deletions

View File

@ -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);

View File

@ -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.
*/ */

View File

@ -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,8 +392,6 @@ public class ExpandableDictionary extends Dictionary {
if (codeSize == inputIndex + 1) { if (codeSize == inputIndex + 1) {
if (terminal) { if (terminal) {
if (INCLUDE_TYPED_WORD_IF_VALID
|| !same(word, depth + 1, codes.getTypedWord())) {
final int finalFreq; final int finalFreq;
if (skipPos < 0) { if (skipPos < 0) {
finalFreq = freq * snr * addedAttenuation finalFreq = freq * snr * addedAttenuation
@ -354,8 +400,10 @@ public class ExpandableDictionary extends Dictionary {
finalFreq = computeSkippedWordFinalFreq(freq, finalFreq = computeSkippedWordFinalFreq(freq,
snr * addedAttenuation, mInputLength); snr * addedAttenuation, mInputLength);
} }
callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId, if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq,
Dictionary.UNIGRAM); callback)) {
// No space left in the queue, bail out
return;
} }
} }
if (children != null) { if (children != null) {

View File

@ -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();

View File

@ -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);
} }
} }

View File

@ -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();
} }

View File

@ -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);

View File

@ -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) {