diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
20 files changed, 792 insertions, 496 deletions
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index b92283c5b..c884e7b1f 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -24,13 +24,14 @@ import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.personalization.DynamicPersonalizationDictionaryWriter; +import com.android.inputmethod.latin.utils.AsyncResultHolder; import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.PrioritizedSerialExecutor; import java.io.File; import java.util.ArrayList; -import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Abstract base class for an expandable dictionary that can be created and updated dynamically @@ -48,19 +49,27 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { /** Whether to print debug output to log */ private static boolean DEBUG = false; + // TODO: Remove and enable dynamic update in native code. + /** Whether to call binary dictionary dynamically updating methods. */ + private static boolean ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE = false; + + private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100; + /** * The maximum length of a word in this dictionary. */ protected static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH; /** - * A static map of locks, each of which controls access to a single binary dictionary file. They - * ensure that only one instance can update the same dictionary at the same time. The key for - * this map is the filename and the value is the shared dictionary controller associated with - * that filename. + * A static map of time recorders, each of which records the time of accesses to a single binary + * dictionary file. The key for this map is the filename and the value is the shared dictionary + * time recorder associated with that filename. */ - private static final HashMap<String, DictionaryController> sSharedDictionaryControllers = - CollectionUtils.newHashMap(); + private static volatile ConcurrentHashMap<String, DictionaryTimeRecorder> + sFilenameDictionaryTimeRecorderMap = CollectionUtils.newConcurrentHashMap(); + + private static volatile ConcurrentHashMap<String, PrioritizedSerialExecutor> + sFilenameExecutorMap = CollectionUtils.newConcurrentHashMap(); /** The application context. */ protected final Context mContext; @@ -71,30 +80,34 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { */ private BinaryDictionary mBinaryDictionary; + // TODO: Remove and handle dictionaries in native code. /** The in-memory dictionary used to generate the binary dictionary. */ - private AbstractDictionaryWriter mDictionaryWriter; + protected AbstractDictionaryWriter mDictionaryWriter; /** * The name of this dictionary, used as the filename for storing the binary dictionary. Multiple * dictionary instances with the same filename is supported, with access controlled by - * DictionaryController. + * DictionaryTimeRecorder. */ private final String mFilename; /** Whether to support dynamically updating the dictionary */ private final boolean mIsUpdatable; - /** Controls access to the shared binary dictionary file across multiple instances. */ - private final DictionaryController mSharedDictionaryController; + // TODO: remove, once dynamic operations is serialized + /** Records access to the shared binary dictionary file across multiple instances. */ + private final DictionaryTimeRecorder mFilenameDictionaryTimeRecorder; - /** Controls access to the local binary dictionary for this instance. */ - private final DictionaryController mLocalDictionaryController = new DictionaryController(); + // TODO: remove, once dynamic operations is serialized + /** Records access to the local binary dictionary for this instance. */ + private final DictionaryTimeRecorder mPerInstanceDictionaryTimeRecorder = + new DictionaryTimeRecorder(); /* A extension for a binary dictionary file. */ public static final String DICT_FILE_EXTENSION = ".dict"; - private final AtomicReference<AsyncWriteBinaryDictionaryTask> mWaitingTask = - new AtomicReference<AsyncWriteBinaryDictionaryTask>(); + private final AtomicReference<Runnable> mUnfinishedFlushingTask = + new AtomicReference<Runnable>(); /** * Abstract method for loading the unigrams and bigrams of a given dictionary in a background @@ -110,16 +123,32 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { protected abstract boolean hasContentChanged(); /** - * Gets the shared dictionary controller for the given filename. + * Gets the dictionary time recorder for the given filename. */ - private static synchronized DictionaryController getSharedDictionaryController( + private static DictionaryTimeRecorder getDictionaryTimeRecorder( String filename) { - DictionaryController controller = sSharedDictionaryControllers.get(filename); - if (controller == null) { - controller = new DictionaryController(); - sSharedDictionaryControllers.put(filename, controller); + DictionaryTimeRecorder recorder = sFilenameDictionaryTimeRecorderMap.get(filename); + if (recorder == null) { + synchronized(sFilenameDictionaryTimeRecorderMap) { + recorder = new DictionaryTimeRecorder(); + sFilenameDictionaryTimeRecorderMap.put(filename, recorder); + } + } + return recorder; + } + + /** + * Gets the executor for the given filename. + */ + private static PrioritizedSerialExecutor getExecutor(final String filename) { + PrioritizedSerialExecutor executor = sFilenameExecutorMap.get(filename); + if (executor == null) { + synchronized(sFilenameExecutorMap) { + executor = new PrioritizedSerialExecutor(); + sFilenameExecutorMap.put(filename, executor); + } } - return controller; + return executor; } private static AbstractDictionaryWriter getDictionaryWriter(final Context context, @@ -148,7 +177,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { mContext = context; mIsUpdatable = isUpdatable; mBinaryDictionary = null; - mSharedDictionaryController = getSharedDictionaryController(filename); + mFilenameDictionaryTimeRecorder = getDictionaryTimeRecorder(filename); // Currently, only dynamic personalization dictionary is updatable. mDictionaryWriter = getDictionaryWriter(context, dictType, isUpdatable); } @@ -162,35 +191,38 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { */ @Override public void close() { - closeBinaryDictionary(); - mLocalDictionaryController.writeLock().lock(); - try { - mDictionaryWriter.close(); - } finally { - mLocalDictionaryController.writeLock().unlock(); - } + getExecutor(mFilename).execute(new Runnable() { + @Override + public void run() { + if (mBinaryDictionary!= null) { + mBinaryDictionary.close(); + mBinaryDictionary = null; + } + mDictionaryWriter.close(); + } + }); } protected void closeBinaryDictionary() { // Ensure that no other threads are accessing the local binary dictionary. - mLocalDictionaryController.writeLock().lock(); - try { - if (mBinaryDictionary != null) { - mBinaryDictionary.close(); - mBinaryDictionary = null; + getExecutor(mFilename).execute(new Runnable() { + @Override + public void run() { + if (mBinaryDictionary != null) { + mBinaryDictionary.close(); + mBinaryDictionary = null; + } } - } finally { - mLocalDictionaryController.writeLock().unlock(); - } + }); } protected void clear() { - mLocalDictionaryController.writeLock().lock(); - try { - mDictionaryWriter.clear(); - } finally { - mLocalDictionaryController.writeLock().unlock(); - } + getExecutor(mFilename).execute(new Runnable() { + @Override + public void run() { + mDictionaryWriter.clear(); + } + }); } /** @@ -219,14 +251,17 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mFilename); return; } - // TODO: Use a queue to reflect what needs to be reflected. - if (mLocalDictionaryController.writeLock().tryLock()) { - try { + + getExecutor(mFilename).execute(new Runnable() { + @Override + public void run() { + if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) { + mBinaryDictionary.addUnigramWord(word, frequency); + } + // TODO: Remove. mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord); - } finally { - mLocalDictionaryController.writeLock().unlock(); } - } + }); } /** @@ -239,15 +274,18 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { + mFilename); return; } - // TODO: Use a queue to reflect what needs to be reflected. - if (mLocalDictionaryController.writeLock().tryLock()) { - try { + + getExecutor(mFilename).execute(new Runnable() { + @Override + public void run() { + if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) { + mBinaryDictionary.addBigramWords(word0, word1, frequency); + } + // TODO: Remove. mDictionaryWriter.addBigramWords(word0, word1, frequency, isValid, 0 /* lastTouchedTime */); - } finally { - mLocalDictionaryController.writeLock().unlock(); } - } + }); } /** @@ -259,24 +297,30 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { + mFilename); return; } - // TODO: Use a queue to reflect what needs to be reflected. - if (mLocalDictionaryController.writeLock().tryLock()) { - try { + + getExecutor(mFilename).execute(new Runnable() { + @Override + public void run() { + if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) { + mBinaryDictionary.removeBigramWords(word0, word1); + } + // TODO: Remove. mDictionaryWriter.removeBigramWords(word0, word1); - } finally { - mLocalDictionaryController.writeLock().unlock(); } - } + }); } @Override public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, final String prevWord, final ProximityInfo proximityInfo, final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) { - asyncReloadDictionaryIfRequired(); - // Write lock because getSuggestions in native updates session status. - if (mLocalDictionaryController.writeLock().tryLock()) { - try { + reloadDictionaryIfRequired(); + final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); + final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder = + new AsyncResultHolder<ArrayList<SuggestedWordInfo>>(); + getExecutor(mFilename).executePrioritized(new Runnable() { + @Override + public void run() { final ArrayList<SuggestedWordInfo> inMemDictSuggestion = mDictionaryWriter.getSuggestions(composer, prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions); @@ -286,38 +330,37 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions); if (inMemDictSuggestion == null) { - return binarySuggestion; + holder.set(binarySuggestion); } else if (binarySuggestion == null) { - return inMemDictSuggestion; + holder.set(inMemDictSuggestion); } else { binarySuggestion.addAll(inMemDictSuggestion); - return binarySuggestion; + holder.set(binarySuggestion); } } else { - return inMemDictSuggestion; + holder.set(inMemDictSuggestion); } - } finally { - mLocalDictionaryController.writeLock().unlock(); } - } - return null; + }); + + return holder.get(null, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS); } @Override public boolean isValidWord(final String word) { - asyncReloadDictionaryIfRequired(); + reloadDictionaryIfRequired(); return isValidWordInner(word); } protected boolean isValidWordInner(final String word) { - if (mLocalDictionaryController.readLock().tryLock()) { - try { - return isValidWordLocked(word); - } finally { - mLocalDictionaryController.readLock().unlock(); + final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>(); + getExecutor(mFilename).executePrioritized(new Runnable() { + @Override + public void run() { + holder.set(isValidWordLocked(word)); } - } - return false; + }); + return holder.get(false, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS); } protected boolean isValidWordLocked(final String word) { @@ -335,8 +378,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { * dictionary exists, this method will generate one. */ protected void loadDictionary() { - mLocalDictionaryController.mLastUpdateRequestTime = SystemClock.uptimeMillis(); - asyncReloadDictionaryIfRequired(); + mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = SystemClock.uptimeMillis(); + reloadDictionaryIfRequired(); } /** @@ -346,8 +389,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private void loadBinaryDictionary() { if (DEBUG) { Log.d(TAG, "Loading binary dictionary: " + mFilename + " request=" - + mSharedDictionaryController.mLastUpdateRequestTime + " update=" - + mSharedDictionaryController.mLastUpdateTime); + + mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime + " update=" + + mFilenameDictionaryTimeRecorder.mLastUpdateTime); } final File file = new File(mContext.getFilesDir(), mFilename); @@ -358,20 +401,18 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0, length, true /* useFullEditDistance */, null, mDictType, mIsUpdatable); - if (mBinaryDictionary != null) { - // Ensure all threads accessing the current dictionary have finished before swapping in - // the new one. - final BinaryDictionary oldBinaryDictionary = mBinaryDictionary; - mLocalDictionaryController.writeLock().lock(); - try { + // Ensure all threads accessing the current dictionary have finished before swapping in + // the new one. + final BinaryDictionary oldBinaryDictionary = mBinaryDictionary; + getExecutor(mFilename).executePrioritized(new Runnable() { + @Override + public void run() { mBinaryDictionary = newBinaryDictionary; - } finally { - mLocalDictionaryController.writeLock().unlock(); + if (oldBinaryDictionary != null) { + oldBinaryDictionary.close(); + } } - oldBinaryDictionary.close(); - } else { - mBinaryDictionary = newBinaryDictionary; - } + }); } /** @@ -386,8 +427,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private void writeBinaryDictionary() { if (DEBUG) { Log.d(TAG, "Generating binary dictionary: " + mFilename + " request=" - + mSharedDictionaryController.mLastUpdateRequestTime + " update=" - + mSharedDictionaryController.mLastUpdateTime); + + mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime + " update=" + + mFilenameDictionaryTimeRecorder.mLastUpdateTime); } if (needsToReloadBeforeWriting()) { mDictionaryWriter.clear(); @@ -405,54 +446,42 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { */ protected void setRequiresReload(final boolean requiresRebuild) { final long time = SystemClock.uptimeMillis(); - mLocalDictionaryController.mLastUpdateRequestTime = time; - mSharedDictionaryController.mLastUpdateRequestTime = time; + mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = time; + mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime = time; if (DEBUG) { Log.d(TAG, "Reload request: " + mFilename + ": request=" + time + " update=" - + mSharedDictionaryController.mLastUpdateTime); - } - } - - /** - * Reloads the dictionary if required. Reload will occur asynchronously in a separate thread. - */ - public void asyncReloadDictionaryIfRequired() { - if (!isReloadRequired()) return; - if (DEBUG) { - Log.d(TAG, "Starting AsyncReloadDictionaryTask: " + mFilename); + + mFilenameDictionaryTimeRecorder.mLastUpdateTime); } - new AsyncReloadDictionaryTask().start(); } /** * Reloads the dictionary if required. */ - public final void syncReloadDictionaryIfRequired() { + public final void reloadDictionaryIfRequired() { if (!isReloadRequired()) return; - syncReloadDictionaryInternal(); + reloadDictionary(); } /** * Returns whether a dictionary reload is required. */ private boolean isReloadRequired() { - return mBinaryDictionary == null || mLocalDictionaryController.isOutOfDate(); + return mBinaryDictionary == null || mPerInstanceDictionaryTimeRecorder.isOutOfDate(); } /** * Reloads the dictionary. Access is controlled on a per dictionary file basis and supports * concurrent calls from multiple instances that share the same dictionary file. */ - private final void syncReloadDictionaryInternal() { + private final void reloadDictionary() { // Ensure that only one thread attempts to read or write to the shared binary dictionary // file at the same time. - mLocalDictionaryController.writeLock().lock(); - try { - mSharedDictionaryController.writeLock().lock(); - try { + getExecutor(mFilename).execute(new Runnable() { + @Override + public void run() { final long time = SystemClock.uptimeMillis(); final boolean dictionaryFileExists = dictionaryFileExists(); - if (mSharedDictionaryController.isOutOfDate() || !dictionaryFileExists) { + if (mFilenameDictionaryTimeRecorder.isOutOfDate() || !dictionaryFileExists) { // If the shared dictionary file does not exist or is out of date, the first // instance that acquires the lock will generate a new one. if (hasContentChanged() || !dictionaryFileExists) { @@ -460,34 +489,31 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { // rebuild the binary dictionary. Empty dictionaries are supported (in the // case where loadDictionaryAsync() adds nothing) in order to provide a // uniform framework. - mSharedDictionaryController.mLastUpdateTime = time; + mFilenameDictionaryTimeRecorder.mLastUpdateTime = time; writeBinaryDictionary(); loadBinaryDictionary(); } else { // If not, the reload request was unnecessary so revert // LastUpdateRequestTime to LastUpdateTime. - mSharedDictionaryController.mLastUpdateRequestTime = - mSharedDictionaryController.mLastUpdateTime; + mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime = + mFilenameDictionaryTimeRecorder.mLastUpdateTime; } - } else if (mBinaryDictionary == null || mLocalDictionaryController.mLastUpdateTime - < mSharedDictionaryController.mLastUpdateTime) { + } else if (mBinaryDictionary == null || + mPerInstanceDictionaryTimeRecorder.mLastUpdateTime + < mFilenameDictionaryTimeRecorder.mLastUpdateTime) { // Otherwise, if the local dictionary is older than the shared dictionary, load // the shared dictionary. loadBinaryDictionary(); } if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) { // Binary dictionary is not valid. Regenerate the dictionary file. - mSharedDictionaryController.mLastUpdateTime = time; + mFilenameDictionaryTimeRecorder.mLastUpdateTime = time; writeBinaryDictionary(); loadBinaryDictionary(); } - mLocalDictionaryController.mLastUpdateTime = time; - } finally { - mSharedDictionaryController.writeLock().unlock(); + mPerInstanceDictionaryTimeRecorder.mLastUpdateTime = time; } - } finally { - mLocalDictionaryController.writeLock().unlock(); - } + }); } // TODO: cache the file's existence so that we avoid doing a disk access each time. @@ -497,83 +523,36 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } /** - * Thread class for asynchronously reloading and rewriting the binary dictionary. - */ - private class AsyncReloadDictionaryTask extends Thread { - @Override - public void run() { - syncReloadDictionaryInternal(); - } - } - - /** * Load the dictionary to memory. */ protected void asyncLoadDictionaryToMemory() { - new AsyncLoadDictionaryToMemoryTask().start(); - } - - /** - * Thread class for asynchronously loading dictionary to memory. - */ - private class AsyncLoadDictionaryToMemoryTask extends Thread { - @Override - public void run() { - mLocalDictionaryController.writeLock().lock(); - try { - mSharedDictionaryController.readLock().lock(); - try { - loadDictionaryAsync(); - } finally { - mSharedDictionaryController.readLock().unlock(); - } - } finally { - mLocalDictionaryController.writeLock().unlock(); + getExecutor(mFilename).executePrioritized(new Runnable() { + @Override + public void run() { + loadDictionaryAsync(); } - } + }); } /** * Generate binary dictionary using DictionaryWriter. */ protected void asyncWriteBinaryDictionary() { - final AsyncWriteBinaryDictionaryTask newTask = new AsyncWriteBinaryDictionaryTask(); - newTask.start(); - final AsyncWriteBinaryDictionaryTask oldTask = mWaitingTask.getAndSet(newTask); - if (oldTask != null) { - oldTask.interrupt(); - } - } - - /** - * Thread class for asynchronously writing the binary dictionary. - */ - private class AsyncWriteBinaryDictionaryTask extends Thread { - @Override - public void run() { - mSharedDictionaryController.writeLock().lock(); - try { - mLocalDictionaryController.writeLock().lock(); - try { - if (isInterrupted()) { - return; - } - writeBinaryDictionary(); - } finally { - mLocalDictionaryController.writeLock().unlock(); - } - } finally { - mSharedDictionaryController.writeLock().unlock(); + final Runnable newTask = new Runnable() { + @Override + public void run() { + writeBinaryDictionary(); } - } + }; + final Runnable oldTask = mUnfinishedFlushingTask.getAndSet(newTask); + getExecutor(mFilename).replaceAndExecute(oldTask, newTask); } /** - * Lock for controlling access to a given binary dictionary and for tracking whether the - * dictionary is out of date. Can be shared across multiple dictionary instances that access the - * same filename. + * Time recorder for tracking whether the dictionary is out of date. + * Can be shared across multiple dictionary instances that access the same filename. */ - private static class DictionaryController extends ReentrantReadWriteLock { + private static class DictionaryTimeRecorder { private volatile long mLastUpdateTime = 0; private volatile long mLastUpdateRequestTime = 0; @@ -588,12 +567,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { @UsedForTesting protected void addWordDynamicallyForTests(final String word, final String shortcutTarget, final int frequency, final boolean isNotAWord) { - mLocalDictionaryController.writeLock().lock(); - try { - addWordDynamically(word, shortcutTarget, frequency, isNotAWord); - } finally { - mLocalDictionaryController.writeLock().unlock(); - } + getExecutor(mFilename).executePrioritized(new Runnable() { + @Override + public void run() { + addWordDynamically(word, shortcutTarget, frequency, isNotAWord); + } + }); } /** @@ -602,12 +581,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { @UsedForTesting protected void addBigramDynamicallyForTests(final String word0, final String word1, final int frequency, final boolean isValid) { - mLocalDictionaryController.writeLock().lock(); - try { - addBigramDynamically(word0, word1, frequency, isValid); - } finally { - mLocalDictionaryController.writeLock().unlock(); - } + getExecutor(mFilename).executePrioritized(new Runnable() { + @Override + public void run() { + addBigramDynamically(word0, word1, frequency, isValid); + } + }); } /** @@ -615,11 +594,27 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { */ @UsedForTesting protected void removeBigramDynamicallyForTests(final String word0, final String word1) { - mLocalDictionaryController.writeLock().lock(); - try { - removeBigramDynamically(word0, word1); - } finally { - mLocalDictionaryController.writeLock().unlock(); - } + getExecutor(mFilename).executePrioritized(new Runnable() { + @Override + public void run() { + removeBigramDynamically(word0, word1); + } + }); + } + + // TODO: Implement native binary methods once the dynamic dictionary implementation is done. + @UsedForTesting + public boolean isInDictionaryForTests(final String word) { + final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>(); + getExecutor(mFilename).executePrioritized(new Runnable() { + @Override + public void run() { + if (mDictType == Dictionary.TYPE_USER_HISTORY) { + holder.set(((DynamicPersonalizationDictionaryWriter) mDictionaryWriter) + .isInDictionaryForTests(word)); + } + } + }); + return holder.get(false, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS); } } diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index 342dcfc5e..ba7d1a2b0 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -265,10 +265,10 @@ public class ExpandableDictionary extends Dictionary { return (node == null) ? false : !node.mShortcutOnly; } - public boolean removeBigram(final String word1, final String word2) { + public boolean removeBigram(final String word0, final String word1) { // Refer to addOrSetBigram() about word1.toLowerCase() - final Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null); - final Node secondWord = searchWord(mRoots, word2, 0, null); + final Node firstWord = searchWord(mRoots, word0.toLowerCase(), 0, null); + final Node secondWord = searchWord(mRoots, word1, 0, null); LinkedList<NextWord> bigrams = firstWord.mNGrams; NextWord bigramNode = null; if (bigrams == null || bigrams.size() == 0) { @@ -297,10 +297,10 @@ public class ExpandableDictionary extends Dictionary { return (node == null) ? -1 : node.mFrequency; } - public NextWord getBigramWord(final String word1, final String word2) { - // Refer to addOrSetBigram() about word1.toLowerCase() - final Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null); - final Node secondWord = searchWord(mRoots, word2, 0, null); + public NextWord getBigramWord(final String word0, final String word1) { + // Refer to addOrSetBigram() about word0.toLowerCase() + final Node firstWord = searchWord(mRoots, word0.toLowerCase(), 0, null); + final Node secondWord = searchWord(mRoots, word1, 0, null); LinkedList<NextWord> bigrams = firstWord.mNGrams; if (bigrams == null || bigrams.size() == 0) { return null; @@ -473,37 +473,41 @@ public class ExpandableDictionary extends Dictionary { } } - public int setBigramAndGetFrequency(final String word1, final String word2, + public int setBigramAndGetFrequency(final String word0, final String word1, final int frequency) { - return setBigramAndGetFrequency(word1, word2, frequency, null /* unused */); + return setBigramAndGetFrequency(word0, word1, frequency, null /* unused */); } - public int setBigramAndGetFrequency(final String word1, final String word2, + public int setBigramAndGetFrequency(final String word0, final String word1, final ForgettingCurveParams fcp) { - return setBigramAndGetFrequency(word1, word2, 0 /* unused */, fcp); + return setBigramAndGetFrequency(word0, word1, 0 /* unused */, fcp); } /** * Adds bigrams to the in-memory trie structure that is being used to retrieve any word - * @param word1 the first word of this bigram - * @param word2 the second word of this bigram + * @param word0 the first word of this bigram + * @param word1 the second word of this bigram * @param frequency frequency for this bigram * @param fcp an instance of ForgettingCurveParams to use for decay policy * @return returns the final bigram frequency */ - private int setBigramAndGetFrequency(final String word1, final String word2, + private int setBigramAndGetFrequency(final String word0, final String word1, final int frequency, final ForgettingCurveParams fcp) { + if (TextUtils.isEmpty(word0)) { + Log.e(TAG, "Invalid bigram previous word: " + word0); + return frequency; + } // We don't want results to be different according to case of the looked up left hand side // word. We do want however to return the correct case for the right hand side. // So we want to squash the case of the left hand side, and preserve that of the right // hand side word. - final String word1Lower = word1.toLowerCase(); - if (TextUtils.isEmpty(word1Lower) || TextUtils.isEmpty(word2)) { - Log.e(TAG, "Invalid bigram pair: " + word1 + ", " + word1Lower + ", " + word2); + final String word0Lower = word0.toLowerCase(); + if (TextUtils.isEmpty(word0Lower) || TextUtils.isEmpty(word1)) { + Log.e(TAG, "Invalid bigram pair: " + word0 + ", " + word0Lower + ", " + word1); return frequency; } - final Node firstWord = searchWord(mRoots, word1Lower, 0, null); - final Node secondWord = searchWord(mRoots, word2, 0, null); + final Node firstWord = searchWord(mRoots, word0Lower, 0, null); + final Node secondWord = searchWord(mRoots, word1, 0, null); LinkedList<NextWord> bigrams = firstWord.mNGrams; if (bigrams == null || bigrams.size() == 0) { firstWord.mNGrams = CollectionUtils.newLinkedList(); diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 6be9ded5c..6c83ac7ed 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -86,6 +86,7 @@ import com.android.inputmethod.latin.settings.SettingsActivity; import com.android.inputmethod.latin.settings.SettingsValues; import com.android.inputmethod.latin.suggestions.SuggestionStripView; import com.android.inputmethod.latin.utils.ApplicationUtils; +import com.android.inputmethod.latin.utils.AsyncResultHolder; import com.android.inputmethod.latin.utils.AutoCorrectionUtils; import com.android.inputmethod.latin.utils.CapsModeUtils; import com.android.inputmethod.latin.utils.CollectionUtils; @@ -107,8 +108,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Locale; import java.util.TreeSet; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; /** * Input method implementation for Qwerty'ish keyboard. @@ -2218,7 +2217,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mKeyboardSwitcher.updateShiftState(); } - // Returns true if we did an autocorrection, false otherwise. + // Returns true if we do an autocorrection, false otherwise. private boolean handleSeparator(final int primaryCode, final int x, final int y, final int spaceState) { boolean didAutoCorrect = false; @@ -2385,31 +2384,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } - final CountDownLatch latch = new CountDownLatch(1); - final SuggestedWords[] suggestedWordsArray = new SuggestedWords[1]; + final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<SuggestedWords>(); getSuggestedWordsOrOlderSuggestionsAsync(Suggest.SESSION_TYPING, new OnGetSuggestedWordsCallback() { @Override public void onGetSuggestedWords(final SuggestedWords suggestedWords) { - suggestedWordsArray[0] = suggestedWords; - latch.countDown(); + holder.set(suggestedWords); } } ); - // TODO: Quit blocking the main thread. - try { - // Wait for the result of getSuggestedWords - // We set the time out to avoid ANR. - latch.await(GET_SUGGESTED_WORDS_TIMEOUT, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - // TODO: Cancel all pending "getSuggestedWords" tasks when it failed. We may want to add - // "onGetSuggestionFailed" to "OnGetSuggestedWordsCallback". - Log.e(TAG, "InterruptedException while waiting for getSuggestedWords.", e); - return; - } - if (suggestedWordsArray[0] != null) { - showSuggestionStrip(suggestedWordsArray[0]); + // This line may cause the current thread to wait. + final SuggestedWords suggestedWords = holder.get(null, GET_SUGGESTED_WORDS_TIMEOUT); + if (suggestedWords != null) { + showSuggestionStrip(suggestedWords); } } @@ -2489,11 +2477,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen false /* isPrediction */); } - private void showSuggestionStrip(final SuggestedWords suggestedWords) { - if (suggestedWords.isEmpty()) { - clearSuggestionStrip(); - return; - } + private void setAutoCorrection(final SuggestedWords suggestedWords) { + if (suggestedWords.isEmpty()) return; final String autoCorrection; if (suggestedWords.mWillAutoCorrect) { autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION); @@ -2501,13 +2486,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD); } mWordComposer.setAutoCorrection(autoCorrection); + } + + private void showSuggestionStrip(final SuggestedWords suggestedWords) { + if (suggestedWords.isEmpty()) { + clearSuggestionStrip(); + return; + } + setAutoCorrection(suggestedWords); final boolean isAutoCorrection = suggestedWords.willAutoCorrect(); setSuggestedWords(suggestedWords, isAutoCorrection); setAutoCorrectionIndicator(isAutoCorrection); setSuggestionStripShown(isSuggestionsStripVisible()); } - private void commitCurrentAutoCorrection(final String separatorString) { + private void commitCurrentAutoCorrection(final String separator) { // Complete any pending suggestions query first if (mHandler.hasPendingUpdateSuggestions()) { updateSuggestionStrip(); @@ -2523,16 +2516,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } if (mSettings.isInternal()) { LatinImeLoggerUtils.onAutoCorrection( - typedWord, autoCorrection, separatorString, mWordComposer); + typedWord, autoCorrection, separator, mWordComposer); } if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { final SuggestedWords suggestedWords = mSuggestedWords; ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection, - separatorString, mWordComposer.isBatchMode(), suggestedWords); + separator, mWordComposer.isBatchMode(), suggestedWords); } mExpectingUpdateSelection = true; commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD, - separatorString); + separator); if (!typedWord.equals(autoCorrection)) { // This will make the correction flash for a short while as a visual clue // to the user that auto-correction happened. It has no other effect; in particular diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java index 67ef538ac..3213c92c7 100644 --- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java @@ -35,14 +35,14 @@ public final class SynchronouslyLoadedContactsBinaryDictionary extends ContactsB public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes, final String prevWordForBigrams, final ProximityInfo proximityInfo, final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) { - syncReloadDictionaryIfRequired(); + reloadDictionaryIfRequired(); return super.getSuggestions(codes, prevWordForBigrams, proximityInfo, blockOffensiveWords, additionalFeaturesOptions); } @Override public synchronized boolean isValidWord(final String word) { - syncReloadDictionaryIfRequired(); + reloadDictionaryIfRequired(); return isValidWordInner(word); } diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java index bea522320..6405b5e46 100644 --- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java @@ -38,14 +38,14 @@ public final class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDic public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes, final String prevWordForBigrams, final ProximityInfo proximityInfo, final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) { - syncReloadDictionaryIfRequired(); + reloadDictionaryIfRequired(); return super.getSuggestions(codes, prevWordForBigrams, proximityInfo, blockOffensiveWords, additionalFeaturesOptions); } @Override public synchronized boolean isValidWord(final String word) { - syncReloadDictionaryIfRequired(); + reloadDictionaryIfRequired(); return isValidWordInner(word); } } diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java index 79f5ad8bd..5a213415a 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java @@ -27,7 +27,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Iterator; /** * Encodes binary files for a FusionDictionary. @@ -432,7 +431,7 @@ public class BinaryDictEncoderUtils { * @param formatOptions file format options. * @return the same array it was passed. The nodes have been updated for address and size. */ - private static ArrayList<PtNodeArray> computeAddresses(final FusionDictionary dict, + /* package */ static ArrayList<PtNodeArray> computeAddresses(final FusionDictionary dict, final ArrayList<PtNodeArray> flatNodes, final FormatOptions formatOptions) { // First get the worst possible sizes and offsets for (final PtNodeArray n : flatNodes) calculatePtNodeArrayMaximumSize(n, formatOptions); @@ -484,7 +483,7 @@ public class BinaryDictEncoderUtils { * * @param arrays the list of node arrays to check */ - private static void checkFlatPtNodeArrayList(final ArrayList<PtNodeArray> arrays) { + /* package */ static void checkFlatPtNodeArrayList(final ArrayList<PtNodeArray> arrays) { int offset = 0; int index = 0; for (final PtNodeArray ptNodeArray : arrays) { @@ -501,52 +500,53 @@ public class BinaryDictEncoderUtils { } /** - * Helper method to write a variable-size address to a file. + * Helper method to write a children position to a file. * * @param buffer the buffer to write to. * @param index the index in the buffer to write the address to. - * @param address the address to write. + * @param position the position to write. * @return the size in bytes the address actually took. */ - private static int writeVariableAddress(final byte[] buffer, int index, final int address) { - switch (getByteSize(address)) { + /* package */ static int writeChildrenPosition(final byte[] buffer, int index, + final int position) { + switch (getByteSize(position)) { case 1: - buffer[index++] = (byte)address; + buffer[index++] = (byte)position; return 1; case 2: - buffer[index++] = (byte)(0xFF & (address >> 8)); - buffer[index++] = (byte)(0xFF & address); + buffer[index++] = (byte)(0xFF & (position >> 8)); + buffer[index++] = (byte)(0xFF & position); return 2; case 3: - buffer[index++] = (byte)(0xFF & (address >> 16)); - buffer[index++] = (byte)(0xFF & (address >> 8)); - buffer[index++] = (byte)(0xFF & address); + buffer[index++] = (byte)(0xFF & (position >> 16)); + buffer[index++] = (byte)(0xFF & (position >> 8)); + buffer[index++] = (byte)(0xFF & position); return 3; case 0: return 0; default: - throw new RuntimeException("Address " + address + " has a strange size"); + throw new RuntimeException("Position " + position + " has a strange size"); } } /** - * Helper method to write a variable-size signed address to a file. + * Helper method to write a signed children position to a file. * * @param buffer the buffer to write to. * @param index the index in the buffer to write the address to. - * @param address the address to write. + * @param position the position to write. * @return the size in bytes the address actually took. */ - private static int writeVariableSignedAddress(final byte[] buffer, int index, - final int address) { - if (!BinaryDictIOUtils.hasChildrenAddress(address)) { + /* package */ static int writeSignedChildrenPosition(final byte[] buffer, int index, + final int position) { + if (!BinaryDictIOUtils.hasChildrenAddress(position)) { buffer[index] = buffer[index + 1] = buffer[index + 2] = 0; } else { - final int absAddress = Math.abs(address); + final int absPosition = Math.abs(position); buffer[index++] = - (byte)((address < 0 ? FormatSpec.MSB8 : 0) | (0xFF & (absAddress >> 16))); - buffer[index++] = (byte)(0xFF & (absAddress >> 8)); - buffer[index++] = (byte)(0xFF & absAddress); + (byte)((position < 0 ? FormatSpec.MSB8 : 0) | (0xFF & (absPosition >> 16))); + buffer[index++] = (byte)(0xFF & (absPosition >> 8)); + buffer[index++] = (byte)(0xFF & absPosition); } return 3; } @@ -598,7 +598,7 @@ public class BinaryDictEncoderUtils { return flags; } - private static byte makePtNodeFlags(final PtNode node, final int ptNodeAddress, + /* package */ static byte makePtNodeFlags(final PtNode node, final int ptNodeAddress, final int childrenOffset, final FormatOptions formatOptions) { return (byte) makePtNodeFlags(node.mChars.length > 1, node.mFrequency >= 0, getByteSize(childrenOffset), @@ -616,7 +616,7 @@ public class BinaryDictEncoderUtils { * @param word the second bigram, for debugging purposes * @return the flags */ - private static final int makeBigramFlags(final boolean more, final int offset, + /* package */ static final int makeBigramFlags(final boolean more, final int offset, int bigramFrequency, final int unigramFrequency, final String word) { int bigramFlags = (more ? FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT : 0) + (offset < 0 ? FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE : 0); @@ -702,7 +702,7 @@ public class BinaryDictEncoderUtils { + (frequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY); } - private static final int writeParentAddress(final byte[] buffer, final int index, + /* package */ static final int writeParentAddress(final byte[] buffer, final int index, final int address, final FormatOptions formatOptions) { if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) { if (address == FormatSpec.NO_PARENT_ADDRESS) { @@ -721,137 +721,69 @@ public class BinaryDictEncoderUtils { } } + /* package */ static final int getChildrenPosition(final PtNode ptNode, + final FormatOptions formatOptions) { + int positionOfChildrenPosField = ptNode.mCachedAddressAfterUpdate + + getNodeHeaderSize(ptNode, formatOptions); + if (ptNode.mFrequency >= 0) { + positionOfChildrenPosField += FormatSpec.PTNODE_FREQUENCY_SIZE; + } + return null == ptNode.mChildren ? FormatSpec.NO_CHILDREN_ADDRESS + : ptNode.mChildren.mCachedAddressAfterUpdate - positionOfChildrenPosField; + } + /** * Write a PtNodeArray to memory. The PtNodeArray is expected to have its final position cached. * * @param dict the dictionary the node array is a part of (for relative offsets). - * @param buffer the memory buffer to write to. + * @param dictEncoder the dictionary encoder. * @param ptNodeArray the node array to write. * @param formatOptions file format options. - * @return the address of the END of the node. */ @SuppressWarnings("unused") - private static int writePlacedNode(final FusionDictionary dict, byte[] buffer, - final PtNodeArray ptNodeArray, final FormatOptions formatOptions) { + /* package */ static void writePlacedNode(final FusionDictionary dict, + final DictEncoder dictEncoder, final PtNodeArray ptNodeArray, + final FormatOptions formatOptions) { // TODO: Make the code in common with BinaryDictIOUtils#writePtNode - int index = ptNodeArray.mCachedAddressAfterUpdate; + dictEncoder.setPosition(ptNodeArray.mCachedAddressAfterUpdate); final int ptNodeCount = ptNodeArray.mData.size(); - final int countSize = getPtNodeCountSize(ptNodeArray); - final int parentAddress = ptNodeArray.mCachedParentAddress; - if (1 == countSize) { - buffer[index++] = (byte)ptNodeCount; - } else if (2 == countSize) { - // We need to signal 2-byte size by setting the top bit of the MSB to 1, so - // we | 0x80 to do this. - buffer[index++] = (byte)((ptNodeCount >> 8) | 0x80); - buffer[index++] = (byte)(ptNodeCount & 0xFF); - } else { - throw new RuntimeException("Strange size from getGroupCountSize : " + countSize); - } - int ptNodeAddress = index; + dictEncoder.writePtNodeCount(ptNodeCount); + final int parentPosition = + (ptNodeArray.mCachedParentAddress == FormatSpec.NO_PARENT_ADDRESS) + ? FormatSpec.NO_PARENT_ADDRESS + : ptNodeArray.mCachedParentAddress + ptNodeArray.mCachedAddressAfterUpdate; for (int i = 0; i < ptNodeCount; ++i) { final PtNode ptNode = ptNodeArray.mData.get(i); - if (index != ptNode.mCachedAddressAfterUpdate) { + if (dictEncoder.getPosition() != ptNode.mCachedAddressAfterUpdate) { throw new RuntimeException("Bug: write index is not the same as the cached address " - + "of the node : " + index + " <> " + ptNode.mCachedAddressAfterUpdate); + + "of the node : " + dictEncoder.getPosition() + " <> " + + ptNode.mCachedAddressAfterUpdate); } - ptNodeAddress += getNodeHeaderSize(ptNode, formatOptions); // Sanity checks. if (DBG && ptNode.mFrequency > FormatSpec.MAX_TERMINAL_FREQUENCY) { throw new RuntimeException("A node has a frequency > " + FormatSpec.MAX_TERMINAL_FREQUENCY + " : " + ptNode.mFrequency); } - if (ptNode.mFrequency >= 0) ptNodeAddress += FormatSpec.PTNODE_FREQUENCY_SIZE; - final int childrenOffset = null == ptNode.mChildren - ? FormatSpec.NO_CHILDREN_ADDRESS - : ptNode.mChildren.mCachedAddressAfterUpdate - ptNodeAddress; - buffer[index++] = - makePtNodeFlags(ptNode, ptNodeAddress, childrenOffset, formatOptions); - - if (parentAddress == FormatSpec.NO_PARENT_ADDRESS) { - index = writeParentAddress(buffer, index, parentAddress, formatOptions); - } else { - index = writeParentAddress(buffer, index, parentAddress - + (ptNodeArray.mCachedAddressAfterUpdate - - ptNode.mCachedAddressAfterUpdate), - formatOptions); - } - - index = CharEncoding.writeCharArray(ptNode.mChars, buffer, index); - if (ptNode.hasSeveralChars()) { - buffer[index++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR; - } - if (ptNode.mFrequency >= 0) { - buffer[index++] = (byte) ptNode.mFrequency; - } - - final int shift; - if (formatOptions.mSupportsDynamicUpdate) { - shift = writeVariableSignedAddress(buffer, index, childrenOffset); - } else { - shift = writeVariableAddress(buffer, index, childrenOffset); - } - index += shift; - ptNodeAddress += shift; - - // Write shortcuts - if (null != ptNode.mShortcutTargets && !ptNode.mShortcutTargets.isEmpty()) { - final int indexOfShortcutByteSize = index; - index += FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE; - ptNodeAddress += FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE; - final Iterator<WeightedString> shortcutIterator = - ptNode.mShortcutTargets.iterator(); - while (shortcutIterator.hasNext()) { - final WeightedString target = shortcutIterator.next(); - ++ptNodeAddress; - int shortcutFlags = makeShortcutFlags(shortcutIterator.hasNext(), - target.mFrequency); - buffer[index++] = (byte)shortcutFlags; - final int shortcutShift = CharEncoding.writeString(buffer, index, target.mWord); - index += shortcutShift; - ptNodeAddress += shortcutShift; - } - final int shortcutByteSize = index - indexOfShortcutByteSize; - if (shortcutByteSize > 0xFFFF) { - throw new RuntimeException("Shortcut list too large"); - } - buffer[indexOfShortcutByteSize] = (byte)(shortcutByteSize >> 8); - buffer[indexOfShortcutByteSize + 1] = (byte)(shortcutByteSize & 0xFF); - } - // Write bigrams - if (null != ptNode.mBigrams) { - final Iterator<WeightedString> bigramIterator = ptNode.mBigrams.iterator(); - while (bigramIterator.hasNext()) { - final WeightedString bigram = bigramIterator.next(); - final PtNode target = - FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord); - final int addressOfBigram = target.mCachedAddressAfterUpdate; - final int unigramFrequencyForThisWord = target.mFrequency; - ++ptNodeAddress; - final int offset = addressOfBigram - ptNodeAddress; - int bigramFlags = makeBigramFlags(bigramIterator.hasNext(), offset, - bigram.mFrequency, unigramFrequencyForThisWord, bigram.mWord); - buffer[index++] = (byte)bigramFlags; - final int bigramShift = writeVariableAddress(buffer, index, Math.abs(offset)); - index += bigramShift; - ptNodeAddress += bigramShift; - } - } + dictEncoder.writePtNodeFlags(ptNode, parentPosition, formatOptions); + dictEncoder.writeParentPosition(parentPosition, ptNode, formatOptions); + dictEncoder.writeCharacters(ptNode.mChars, ptNode.hasSeveralChars()); + dictEncoder.writeFrequency(ptNode.mFrequency); + dictEncoder.writeChildrenPosition(ptNode, formatOptions); + dictEncoder.writeShortcuts(ptNode.mShortcutTargets); + dictEncoder.writeBigrams(ptNode.mBigrams, dict); } if (formatOptions.mSupportsDynamicUpdate) { - buffer[index] = buffer[index + 1] = buffer[index + 2] - = FormatSpec.NO_FORWARD_LINK_ADDRESS; - index += FormatSpec.FORWARD_LINK_ADDRESS_SIZE; + dictEncoder.writeForwardLinkAddress(FormatSpec.NO_FORWARD_LINK_ADDRESS); } - if (index != ptNodeArray.mCachedAddressAfterUpdate + ptNodeArray.mCachedSize) { - throw new RuntimeException( - "Not the same size : written " + (index - ptNodeArray.mCachedAddressAfterUpdate) + if (dictEncoder.getPosition() != ptNodeArray.mCachedAddressAfterUpdate + + ptNodeArray.mCachedSize) { + throw new RuntimeException("Not the same size : written " + + (dictEncoder.getPosition() - ptNodeArray.mCachedAddressAfterUpdate) + " bytes from a node that should have " + ptNodeArray.mCachedSize + " bytes"); } - return index; } /** @@ -862,7 +794,7 @@ public class BinaryDictEncoderUtils { * * @param ptNodeArrays the list of PtNode arrays. */ - private static void showStatistics(ArrayList<PtNodeArray> ptNodeArrays) { + /* package */ static void showStatistics(ArrayList<PtNodeArray> ptNodeArrays) { int firstTerminalAddress = Integer.MAX_VALUE; int lastTerminalAddress = Integer.MIN_VALUE; int size = 0; @@ -912,23 +844,15 @@ public class BinaryDictEncoderUtils { } /** - * Dumps a FusionDictionary to a file. + * Writes a file header to an output stream. * - * @param destination the stream to write the binary data to. + * @param destination the stream to write the file header to. * @param dict the dictionary to write. * @param formatOptions file format options. */ - /* package */ static void writeDictionaryBinary(final OutputStream destination, + /* package */ static void writeDictionaryHeader(final OutputStream destination, final FusionDictionary dict, final FormatOptions formatOptions) - throws IOException, UnsupportedFormatException { - - // Addresses are limited to 3 bytes, but since addresses can be relative to each node - // array, the structure itself is not limited to 16MB. However, if it is over 16MB deciding - // the order of the PtNode arrays becomes a quite complicated problem, because though the - // dictionary itself does not have a size limit, each node array must still be within 16MB - // of all its children and parents. As long as this is ensured, the dictionary file may - // grow to any size. - + throws IOException, UnsupportedFormatException { final int version = formatOptions.mVersion; if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION || version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) { @@ -975,33 +899,5 @@ public class BinaryDictEncoderUtils { destination.write(bytes); headerBuffer.close(); - - // Leave the choice of the optimal node order to the flattenTree function. - MakedictLog.i("Flattening the tree..."); - ArrayList<PtNodeArray> flatNodes = flattenTree(dict.mRootNodeArray); - - MakedictLog.i("Computing addresses..."); - computeAddresses(dict, flatNodes, formatOptions); - MakedictLog.i("Checking PtNode array..."); - if (DBG) checkFlatPtNodeArrayList(flatNodes); - - // Create a buffer that matches the final dictionary size. - final PtNodeArray lastNodeArray = flatNodes.get(flatNodes.size() - 1); - final int bufferSize = lastNodeArray.mCachedAddressAfterUpdate + lastNodeArray.mCachedSize; - final byte[] buffer = new byte[bufferSize]; - int index = 0; - - MakedictLog.i("Writing file..."); - int dataEndOffset = 0; - for (PtNodeArray nodeArray : flatNodes) { - dataEndOffset = writePlacedNode(dict, buffer, nodeArray, formatOptions); - } - - if (DBG) showStatistics(flatNodes); - - destination.write(buffer, 0, dataEndOffset); - - destination.close(); - MakedictLog.i("Done"); } } diff --git a/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java index 89c982e7b..d1589a30e 100644 --- a/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java @@ -17,8 +17,11 @@ package com.android.inputmethod.latin.makedict; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; +import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; +import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; import java.io.IOException; +import java.util.ArrayList; /** * An interface of binary dictionary encoder. @@ -26,4 +29,32 @@ import java.io.IOException; public interface DictEncoder { public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions) throws IOException, UnsupportedFormatException; + + public void setPosition(final int position); + public int getPosition(); + public void writePtNodeCount(final int ptNodeCount); + public void writePtNodeFlags(final PtNode ptNode, final int parentAddress, + final FormatOptions formatOptions); + public void writeParentPosition(final int parentPosition, final PtNode ptNode, + final FormatOptions formatOptions); + public void writeCharacters(final int[] characters, final boolean hasSeveralChars); + public void writeFrequency(final int frequency); + public void writeChildrenPosition(final PtNode ptNode, final FormatOptions formatOptions); + + /** + * Write a shortcut attributes list to memory. + * + * @param shortcuts the shortcut attributes list. + */ + public void writeShortcuts(final ArrayList<WeightedString> shortcuts); + + /** + * Write a bigram attributes list to memory. + * + * @param bigrams the bigram attributes list. + * @param dict the dictionary the node array is a part of (for relative offsets). + */ + public void writeBigrams(final ArrayList<WeightedString> bigrams, final FusionDictionary dict); + + public void writeForwardLinkAddress(final int forwardLinkAddress); } diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java index b8ef57696..bf35f6a8a 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java +++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java @@ -112,8 +112,10 @@ public final class FormatSpec { * e | 1 byte = bbbbbbbb match * n | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte) * t | otherwise => (bbbbbbbb << 16) + (next byte << 8) + next byte - * a | - * ddress + * a | This address is relative to the head of the PtNode. + * d | If the node doesn't have a parent, this field is set to 0. + * d | + * ress * * c | IF FLAG_HAS_MULTIPLE_CHARS * h | char, char, char, char n * (1 or 3 bytes) : use PtNodeInfo for i/o helpers @@ -132,17 +134,18 @@ public final class FormatSpec { * i | 1 byte = bbbbbbbb match * l | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte) * d | otherwise => (bbbbbbbb<<16) + (next byte << 8) + next byte - * r | ELSIF 00 = FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS == addressType - * e | // nothing - * n | ELSIF 01 = FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE == addressType - * A | children address, 1 byte - * d | ELSIF 10 = FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES == addressType - * d | children address, 2 bytes - * r | ELSE // 11 = FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES = addressType - * e | children address, 3 bytes - * s | END - * s - * ress + * r | if this node doesn't have children, this field is set to 0. + * e | (see BinaryDictEncoderUtils#writeVariableSignedAddress) + * n | ELSIF 00 = FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS == addressType + * a | // nothing + * d | ELSIF 01 = FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE == addressType + * d | children address, 1 byte + * r | ELSIF 10 = FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES == addressType + * e | children address, 2 bytes + * s | ELSE // 11 = FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES = addressType + * s | children address, 3 bytes + * | END + * | This address is relative to the position of this field. * * | IF FLAG_IS_TERMINAL && FLAG_HAS_SHORTCUT_TARGETS * | shortcut string list diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java index e81fd4514..3f26ff378 100644 --- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java @@ -16,13 +16,19 @@ package com.android.inputmethod.latin.makedict; +import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; +import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; +import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray; +import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; /** * An implementation of DictEncoder for version 3 binary dictionary. @@ -31,10 +37,13 @@ public class Ver3DictEncoder implements DictEncoder { private final File mDictFile; private OutputStream mOutStream; + private byte[] mBuffer; + private int mPosition; public Ver3DictEncoder(final File dictFile) { mDictFile = dictFile; mOutStream = null; + mBuffer = null; } // This constructor is used only by BinaryDictOffdeviceUtilsTests. @@ -57,12 +66,172 @@ public class Ver3DictEncoder implements DictEncoder { } @Override - public void writeDictionary(FusionDictionary dict, FormatOptions formatOptions) + public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions) throws IOException, UnsupportedFormatException { + if (formatOptions.mVersion > 3) { + throw new UnsupportedFormatException( + "The given format options has wrong version number : " + + formatOptions.mVersion); + } + if (mOutStream == null) { openStream(); } - BinaryDictEncoderUtils.writeDictionaryBinary(mOutStream, dict, formatOptions); + BinaryDictEncoderUtils.writeDictionaryHeader(mOutStream, dict, formatOptions); + + // Addresses are limited to 3 bytes, but since addresses can be relative to each node + // array, the structure itself is not limited to 16MB. However, if it is over 16MB deciding + // the order of the PtNode arrays becomes a quite complicated problem, because though the + // dictionary itself does not have a size limit, each node array must still be within 16MB + // of all its children and parents. As long as this is ensured, the dictionary file may + // grow to any size. + + // Leave the choice of the optimal node order to the flattenTree function. + MakedictLog.i("Flattening the tree..."); + ArrayList<PtNodeArray> flatNodes = BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray); + + MakedictLog.i("Computing addresses..."); + BinaryDictEncoderUtils.computeAddresses(dict, flatNodes, formatOptions); + MakedictLog.i("Checking PtNode array..."); + if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes); + + // Create a buffer that matches the final dictionary size. + final PtNodeArray lastNodeArray = flatNodes.get(flatNodes.size() - 1); + final int bufferSize = lastNodeArray.mCachedAddressAfterUpdate + lastNodeArray.mCachedSize; + mBuffer = new byte[bufferSize]; + + MakedictLog.i("Writing file..."); + + for (PtNodeArray nodeArray : flatNodes) { + BinaryDictEncoderUtils.writePlacedNode(dict, this, nodeArray, formatOptions); + } + if (MakedictLog.DBG) BinaryDictEncoderUtils.showStatistics(flatNodes); + mOutStream.write(mBuffer, 0, mPosition); + + MakedictLog.i("Done"); close(); } + + @Override + public void setPosition(final int position) { + if (mBuffer == null || position < 0 || position >= mBuffer.length) return; + mPosition = position; + } + + @Override + public int getPosition() { + return mPosition; + } + + @Override + public void writePtNodeCount(final int ptNodeCount) { + final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount); + if (1 == countSize) { + mBuffer[mPosition++] = (byte) ptNodeCount; + } else if (2 == countSize) { + mBuffer[mPosition++] = (byte) ((ptNodeCount >> 8) & 0xFF); + mBuffer[mPosition++] = (byte) (ptNodeCount & 0xFF); + } else { + throw new RuntimeException("Strange size from getGroupCountSize : " + countSize); + } + } + + @Override + public void writePtNodeFlags(final PtNode ptNode, final int parentAddress, + final FormatOptions formatOptions) { + final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions); + mBuffer[mPosition++] = BinaryDictEncoderUtils.makePtNodeFlags(ptNode, mPosition, + childrenPos, formatOptions); + } + + @Override + public void writeParentPosition(final int parentPosition, final PtNode ptNode, + final FormatOptions formatOptions) { + if (parentPosition == FormatSpec.NO_PARENT_ADDRESS) { + mPosition = BinaryDictEncoderUtils.writeParentAddress(mBuffer, mPosition, + parentPosition, formatOptions); + } else { + mPosition = BinaryDictEncoderUtils.writeParentAddress(mBuffer, mPosition, + parentPosition - ptNode.mCachedAddressAfterUpdate, formatOptions); + } + } + + @Override + public void writeCharacters(final int[] codePoints, final boolean hasSeveralChars) { + mPosition = CharEncoding.writeCharArray(codePoints, mBuffer, mPosition); + if (hasSeveralChars) { + mBuffer[mPosition++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR; + } + } + + @Override + public void writeFrequency(final int frequency) { + if (frequency >= 0) { + mBuffer[mPosition++] = (byte) frequency; + } + } + + @Override + public void writeChildrenPosition(final PtNode ptNode, final FormatOptions formatOptions) { + final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions); + if (formatOptions.mSupportsDynamicUpdate) { + mPosition += BinaryDictEncoderUtils.writeSignedChildrenPosition(mBuffer, mPosition, + childrenPos); + } else { + mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition, + childrenPos); + } + } + + @Override + public void writeShortcuts(final ArrayList<WeightedString> shortcuts) { + if (null == shortcuts || shortcuts.isEmpty()) return; + + final int indexOfShortcutByteSize = mPosition; + mPosition += FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE; + final Iterator<WeightedString> shortcutIterator = shortcuts.iterator(); + while (shortcutIterator.hasNext()) { + final WeightedString target = shortcutIterator.next(); + final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags( + shortcutIterator.hasNext(), + target.mFrequency); + mBuffer[mPosition++] = (byte)shortcutFlags; + final int shortcutShift = CharEncoding.writeString(mBuffer, mPosition, target.mWord); + mPosition += shortcutShift; + } + final int shortcutByteSize = mPosition - indexOfShortcutByteSize; + if (shortcutByteSize > 0xFFFF) { + throw new RuntimeException("Shortcut list too large"); + } + mBuffer[indexOfShortcutByteSize] = (byte)((shortcutByteSize >> 8) & 0xFF); + mBuffer[indexOfShortcutByteSize + 1] = (byte)(shortcutByteSize & 0xFF); + } + + @Override + public void writeBigrams(final ArrayList<WeightedString> bigrams, final FusionDictionary dict) { + if (bigrams == null) return; + + final Iterator<WeightedString> bigramIterator = bigrams.iterator(); + while (bigramIterator.hasNext()) { + final WeightedString bigram = bigramIterator.next(); + final PtNode target = + FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord); + final int addressOfBigram = target.mCachedAddressAfterUpdate; + final int unigramFrequencyForThisWord = target.mFrequency; + final int offset = addressOfBigram + - (mPosition + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE); + int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(bigramIterator.hasNext(), + offset, bigram.mFrequency, unigramFrequencyForThisWord, bigram.mWord); + mBuffer[mPosition++] = (byte) bigramFlags; + mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition, + Math.abs(offset)); + } + } + + @Override + public void writeForwardLinkAddress(final int forwardLinkAddress) { + mBuffer[mPosition++] = (byte) ((forwardLinkAddress >> 16) & 0xFF); + mBuffer[mPosition++] = (byte) ((forwardLinkAddress >> 8) & 0xFF); + mBuffer[mPosition++] = (byte) (forwardLinkAddress & 0xFF); + } } diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java index d44660623..e43e74d87 100644 --- a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java +++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java @@ -18,6 +18,7 @@ package com.android.inputmethod.latin.personalization; import android.content.Context; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.AbstractDictionaryWriter; import com.android.inputmethod.latin.ExpandableDictionary; @@ -156,4 +157,10 @@ public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWr public boolean isValidWord(final String word) { return mExpandableDictionary.isValidWord(word); } + + @UsedForTesting + public boolean isInDictionaryForTests(final String word) { + // TODO: Use native method to determine whether the word is in dictionary or not + return mBigramList.containsKey(word); + } } diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java index a08145b33..5b1d0647b 100644 --- a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java +++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java @@ -69,7 +69,7 @@ public abstract class DynamicPredictionDictionaryBase extends ExpandableBinaryDi mPrefs = sp; if (mLocale != null && mLocale.length() > 1) { asyncLoadDictionaryToMemory(); - asyncReloadDictionaryIfRequired(); + reloadDictionaryIfRequired(); } } @@ -196,12 +196,6 @@ public abstract class DynamicPredictionDictionaryBase extends ExpandableBinaryDi return mLocale; } - @UsedForTesting - /* package for test */ void forceAddWordForTest( - final String word0, final String word1, final boolean isValid) { - addToPersonalizationPredictionDictionary(word0, word1, isValid); - } - public void registerUpdateSession(PersonalizationDictionaryUpdateSession session) { session.setPredictionDictionary(this); mSessions.add(session); diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java index c8deaf90d..5f702ee3f 100644 --- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java @@ -52,7 +52,7 @@ public class PersonalizationHelper { if (DEBUG) { Log.w(TAG, "Use cached UserHistoryPredictionDictionary for " + locale); } - dict.asyncReloadDictionaryIfRequired(); + dict.reloadDictionaryIfRequired(); return dict; } } diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java index 6c2c9e26e..4c1803bdf 100644 --- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java +++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java @@ -97,6 +97,10 @@ public final class UserHistoryDictionaryBigramList { return mBigramMap.isEmpty(); } + public boolean containsKey(String word) { + return mBigramMap.containsKey(word); + } + public Set<String> keySet() { return mBigramMap.keySet(); } diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java index fd83865ba..8732a59f8 100644 --- a/java/src/com/android/inputmethod/latin/settings/Settings.java +++ b/java/src/com/android/inputmethod/latin/settings/Settings.java @@ -44,7 +44,9 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_VIBRATE_ON = "vibrate_on"; public static final String PREF_SOUND_ON = "sound_on"; public static final String PREF_POPUP_ON = "popup_on"; - public static final String PREF_VOICE_MODE = "voice_mode"; + // PREF_VOICE_MODE_OBSOLETE is obsolete. Use PREF_VOICE_INPUT_KEY instead. + public static final String PREF_VOICE_MODE_OBSOLETE = "voice_mode"; + public static final String PREF_VOICE_INPUT_KEY = "pref_voice_input_key"; public static final String PREF_CORRECTION_SETTINGS = "correction_settings"; public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary"; public static final String PREF_CONFIGURE_DICTIONARIES_KEY = "configure_dictionaries_key"; diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java index 1677e1828..cb7dda655 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java @@ -61,7 +61,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS || Build.VERSION.SDK_INT <= 18 /* Build.VERSION.JELLY_BEAN_MR2 */; - private ListPreference mVoicePreference; + private CheckBoxPreference mVoiceInputKeyPreference; private ListPreference mShowCorrectionSuggestionsPreference; private ListPreference mAutoCorrectionThresholdPreference; private ListPreference mKeyPreviewPopupDismissDelay; @@ -107,7 +107,8 @@ public final class SettingsFragment extends InputMethodSettingsFragment SubtypeLocaleUtils.init(context); AudioAndHapticFeedbackManager.init(context); - mVoicePreference = (ListPreference) findPreference(Settings.PREF_VOICE_MODE); + mVoiceInputKeyPreference = + (CheckBoxPreference) findPreference(Settings.PREF_VOICE_INPUT_KEY); mShowCorrectionSuggestionsPreference = (ListPreference) findPreference(Settings.PREF_SHOW_SUGGESTIONS_SETTING); final SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); @@ -166,7 +167,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment final boolean showVoiceKeyOption = res.getBoolean( R.bool.config_enable_show_voice_key_option); if (!showVoiceKeyOption) { - generalSettings.removePreference(mVoicePreference); + generalSettings.removePreference(mVoiceInputKeyPreference); } final PreferenceGroup advancedSettings = @@ -243,10 +244,8 @@ public final class SettingsFragment extends InputMethodSettingsFragment public void onResume() { super.onResume(); final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled(); - if (isShortcutImeEnabled) { - updateVoiceModeSummary(); - } else { - getPreferenceScreen().removePreference(mVoicePreference); + if (!isShortcutImeEnabled) { + getPreferenceScreen().removePreference(mVoiceInputKeyPreference); } final SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); final CheckBoxPreference showSetupWizardIcon = @@ -287,7 +286,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity()); } ensureConsistencyOfAutoCorrectionSettings(); - updateVoiceModeSummary(); updateShowCorrectionSuggestionsSummary(); updateKeyPreviewPopupDelaySummary(); refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources()); @@ -330,12 +328,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment lp.setSummary(entries[lp.findIndexOfValue(lp.getValue())]); } - private void updateVoiceModeSummary() { - mVoicePreference.setSummary( - getResources().getStringArray(R.array.voice_input_modes_summary) - [mVoicePreference.findIndexOfValue(mVoicePreference.getValue())]); - } - private void refreshEnablingsOfKeypressSoundAndVibrationSettings( final SharedPreferences sp, final Resources res) { setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS, diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java index 32730d23f..072bb8731 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java @@ -64,7 +64,7 @@ public final class SettingsValues { public final boolean mVibrateOn; public final boolean mSoundOn; public final boolean mKeyPreviewPopupOn; - private final String mVoiceMode; + private final boolean mShowsVoiceInputKey; public final boolean mIncludesOtherImesInLanguageSwitchList; public final boolean mShowsLanguageSwitchKey; public final boolean mUseContactsDict; @@ -90,8 +90,6 @@ public final class SettingsValues { public final float mAutoCorrectionThreshold; public final boolean mCorrectionEnabled; public final int mSuggestionVisibility; - private final boolean mVoiceKeyEnabled; - private final boolean mVoiceKeyOnMain; public final boolean mBoostPersonalizationDictionaryForDebug; public final boolean mUseOnlyPersonalizationDictionaryForDebug; @@ -137,9 +135,7 @@ public final class SettingsValues { mKeyPreviewPopupOn = Settings.readKeyPreviewPopupEnabled(prefs, res); mSlidingKeyInputPreviewEnabled = prefs.getBoolean( Settings.PREF_SLIDING_KEY_INPUT_PREVIEW, true); - final String voiceModeMain = res.getString(R.string.voice_mode_main); - final String voiceModeOff = res.getString(R.string.voice_mode_off); - mVoiceMode = prefs.getString(Settings.PREF_VOICE_MODE, voiceModeMain); + mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res); final String autoCorrectionThresholdRawValue = prefs.getString( Settings.PREF_AUTO_CORRECTION_THRESHOLD, res.getString(R.string.auto_correction_threshold_mode_index_modest)); @@ -159,8 +155,6 @@ public final class SettingsValues { mKeyPreviewPopupDismissDelay = Settings.readKeyPreviewPopupDismissDelay(prefs, res); mAutoCorrectionThreshold = readAutoCorrectionThreshold(res, autoCorrectionThresholdRawValue); - mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff); - mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain); mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res); mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true); mGestureFloatingPreviewTextEnabled = prefs.getBoolean( @@ -201,7 +195,7 @@ public final class SettingsValues { mSoundOn = true; mKeyPreviewPopupOn = true; mSlidingKeyInputPreviewEnabled = true; - mVoiceMode = "0"; + mShowsVoiceInputKey = true; mIncludesOtherImesInLanguageSwitchList = false; mShowsLanguageSwitchKey = true; mUseContactsDict = true; @@ -214,8 +208,6 @@ public final class SettingsValues { mKeypressSoundVolume = 1; mKeyPreviewPopupDismissDelay = 70; mAutoCorrectionThreshold = 1; - mVoiceKeyEnabled = true; - mVoiceKeyOnMain = true; mGestureInputEnabled = true; mGestureTrailEnabled = true; mGestureFloatingPreviewTextEnabled = true; @@ -274,14 +266,10 @@ public final class SettingsValues { public boolean isVoiceKeyEnabled(final EditorInfo editorInfo) { final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled(); final int inputType = (editorInfo != null) ? editorInfo.inputType : 0; - return shortcutImeEnabled && mVoiceKeyEnabled + return shortcutImeEnabled && mShowsVoiceInputKey && !InputTypeUtils.isPasswordInputType(inputType); } - public boolean isVoiceKeyOnMain() { - return mVoiceKeyOnMain; - } - public boolean isLanguageSwitchKeyEnabled() { if (!mShowsLanguageSwitchKey) { return false; @@ -372,4 +360,18 @@ public final class SettingsValues { } return autoCorrectionThreshold; } + + private static boolean needsToShowVoiceInputKey(SharedPreferences prefs, Resources res) { + final String voiceModeMain = res.getString(R.string.voice_mode_main); + final String voiceMode = prefs.getString(Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain); + final boolean showsVoiceInputKey = voiceMode == null || voiceMode.equals(voiceModeMain); + if (!showsVoiceInputKey) { + // Migrate settings from PREF_VOICE_MODE_OBSOLETE to PREF_VOICE_INPUT_KEY + // Set voiceModeMain as a value of obsolete voice mode settings. + prefs.edit().putString(Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain).apply(); + // Disable voice input key. + prefs.edit().putBoolean(Settings.PREF_VOICE_INPUT_KEY, false).apply(); + } + return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, true); + } } diff --git a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java new file mode 100644 index 000000000..c2e97a36f --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.utils; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * This class is a holder of a result of asynchronous computation. + * + * @param <E> the type of the result. + */ +public class AsyncResultHolder<E> { + + private final Object mLock = new Object(); + + private E mResult; + private final CountDownLatch mLatch; + + public AsyncResultHolder() { + mLatch = new CountDownLatch(1); + } + + /** + * Sets the result value to this holder. + * + * @param result the value which is set. + */ + public void set(final E result) { + synchronized(mLock) { + if (mLatch.getCount() > 0) { + mResult = result; + mLatch.countDown(); + } + } + } + + /** + * Gets the result value held in this holder. + * Causes the current thread to wait unless the value is set or the specified time is elapsed. + * + * @param defaultValue the default value. + * @param timeOut the time to wait. + * @return if the result is set until the time limit then the result, otherwise defaultValue. + */ + public E get(final E defaultValue, final long timeOut) { + try { + if(mLatch.await(timeOut, TimeUnit.MILLISECONDS)) { + return mResult; + } else { + return defaultValue; + } + } catch (InterruptedException e) { + return defaultValue; + } + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java b/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java index c4ead0ad1..ac654fa65 100644 --- a/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java @@ -65,12 +65,12 @@ public final class DebugLogUtils { /** * Get the stack trace contained in an exception as a human-readable string. - * @param e the exception + * @param t the throwable * @return the human-readable stack trace */ - public static String getStackTrace(final Exception e) { + public static String getStackTrace(final Throwable t) { final StringBuilder sb = new StringBuilder(); - final StackTraceElement[] frames = e.getStackTrace(); + final StackTraceElement[] frames = t.getStackTrace(); for (int j = 0; j < frames.length; ++j) { sb.append(frames[j].toString() + "\n"); } diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java new file mode 100644 index 000000000..3c1db6529 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.utils; + +import java.util.ArrayDeque; +import java.util.Queue; + +/** + * An object that executes submitted tasks using a thread. + */ +public class PrioritizedSerialExecutor { + public static final String TAG = PrioritizedSerialExecutor.class.getSimpleName(); + + private final Object mLock = new Object(); + + // The default value of capacities of task queues. + private static final int TASK_QUEUE_CAPACITY = 1000; + private final Queue<Runnable> mTasks; + private final Queue<Runnable> mPrioritizedTasks; + + // The task which is running now. + private Runnable mActive; + + public PrioritizedSerialExecutor() { + mTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY); + mPrioritizedTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY); + } + + /** + * Clears all queued tasks. + */ + public void clearAllTasks() { + synchronized(mLock) { + mTasks.clear(); + mPrioritizedTasks.clear(); + } + } + + /** + * Enqueues the given task into the task queue. + * @param r the enqueued task + */ + public void execute(final Runnable r) { + synchronized(mLock) { + mTasks.offer(r); + if (mActive == null) { + scheduleNext(); + } + } + } + + /** + * Enqueues the given task into the prioritized task queue. + * @param r the enqueued task + */ + public void executePrioritized(final Runnable r) { + synchronized(mLock) { + mPrioritizedTasks.offer(r); + if (mActive == null) { + scheduleNext(); + } + } + } + + private boolean fetchNextTasks() { + synchronized(mLock) { + mActive = mPrioritizedTasks.poll(); + if (mActive == null) { + mActive = mTasks.poll(); + } + return mActive != null; + } + } + + private void scheduleNext() { + synchronized(mLock) { + if (!fetchNextTasks()) { + return; + } + new Thread(new Runnable() { + @Override + public void run() { + try { + do { + synchronized(mLock) { + if (mActive != null) { + mActive.run(); + } + } + } while (fetchNextTasks()); + } finally { + scheduleNext(); + } + } + }).start(); + } + } + + public void remove(final Runnable r) { + synchronized(mLock) { + mTasks.remove(r); + mPrioritizedTasks.remove(r); + } + } + + public void replaceAndExecute(final Runnable oldTask, final Runnable newTask) { + synchronized(mLock) { + if (oldTask != null) remove(oldTask); + execute(newTask); + } + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java index 99788f6f2..05f3061a8 100644 --- a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map.Entry; import java.util.TreeMap; +import java.util.concurrent.TimeUnit; /** * Reads and writes Binary files for a UserHistoryDictionary. @@ -43,6 +44,9 @@ import java.util.TreeMap; public final class UserHistoryDictIOUtils { private static final String TAG = UserHistoryDictIOUtils.class.getSimpleName(); private static final boolean DEBUG = false; + private static final String USES_FORGETTING_CURVE_KEY = "USES_FORGETTING_CURVE"; + private static final String USES_FORGETTING_CURVE_VALUE = "1"; + private static final String LAST_UPDATED_TIME_KEY = "date"; public interface OnAddWordListener { public void setUnigram(final String word, final String shortcutTarget, final int frequency); @@ -61,6 +65,9 @@ public final class UserHistoryDictIOUtils { final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams, final FormatOptions formatOptions) { final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams); + fusionDict.addOptionAttribute(USES_FORGETTING_CURVE_KEY, USES_FORGETTING_CURVE_VALUE); + fusionDict.addOptionAttribute(LAST_UPDATED_TIME_KEY, + String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()))); try { dictEncoder.writeDictionary(fusionDict, formatOptions); Log.d(TAG, "end writing"); |