Loading java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +0 −3 Original line number Original line Diff line number Diff line Loading @@ -87,9 +87,6 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { mLocale = locale; mLocale = locale; mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale); mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale); registerObserver(context); registerObserver(context); // Load the current binary dictionary from internal storage. If no binary dictionary exists, // reloadDictionaryIfRequired will start a new thread to generate one asynchronously. reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); } } Loading java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +130 −123 Original line number Original line Diff line number Diff line Loading @@ -26,7 +26,6 @@ import com.android.inputmethod.latin.makedict.FormatSpec; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.makedict.WordProperty; import com.android.inputmethod.latin.makedict.WordProperty; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.utils.AsyncResultHolder; import com.android.inputmethod.latin.utils.CombinedFormatUtils; import com.android.inputmethod.latin.utils.CombinedFormatUtils; import com.android.inputmethod.latin.utils.ExecutorUtils; import com.android.inputmethod.latin.utils.ExecutorUtils; import com.android.inputmethod.latin.utils.FileUtils; import com.android.inputmethod.latin.utils.FileUtils; Loading @@ -40,6 +39,8 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** /** * Abstract base class for an expandable dictionary that can be created and updated dynamically * Abstract base class for an expandable dictionary that can be created and updated dynamically Loading @@ -59,7 +60,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private static final boolean DBG_STRESS_TEST = false; private static final boolean DBG_STRESS_TEST = false; private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100; private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100; private static final int TIMEOUT_FOR_READ_OPS_FOR_TESTS_IN_MILLISECONDS = 10000; private static final int DEFAULT_MAX_UNIGRAM_COUNT = 10000; private static final int DEFAULT_MAX_UNIGRAM_COUNT = 10000; private static final int DEFAULT_MAX_BIGRAM_COUNT = 10000; private static final int DEFAULT_MAX_BIGRAM_COUNT = 10000; Loading Loading @@ -93,9 +93,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { /** Dictionary file */ /** Dictionary file */ private final File mDictFile; private final File mDictFile; private final AtomicBoolean mProcessingLargeTask; /** Indicates whether a task for reloading the dictionary has been scheduled. */ private final AtomicBoolean mIsReloading; /** Indicates whether the current dictionary needs to be reloaded. */ private boolean mNeedsToReload; private boolean mNeedsToReload; private final ReentrantReadWriteLock mLock; /* A extension for a binary dictionary file. */ /* A extension for a binary dictionary file. */ protected static final String DICT_FILE_EXTENSION = ".dict"; protected static final String DICT_FILE_EXTENSION = ".dict"; Loading Loading @@ -144,8 +148,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { mLocale = locale; mLocale = locale; mDictFile = getDictFile(context, dictName, dictFile); mDictFile = getDictFile(context, dictName, dictFile); mBinaryDictionary = null; mBinaryDictionary = null; mProcessingLargeTask = new AtomicBoolean(); mIsReloading = new AtomicBoolean(); mNeedsToReload = false; mNeedsToReload = false; mLock = new ReentrantReadWriteLock(); } } public static File getDictFile(final Context context, final String dictName, public static File getDictFile(final Context context, final String dictName, Loading @@ -159,12 +164,30 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return dictFile != null ? dictFile.getName() : name + "." + locale.toString(); return dictFile != null ? dictFile.getName() : name + "." + locale.toString(); } } private void asyncExecuteTaskWithWriteLock(final Runnable task) { asyncExecuteTaskWithLock(mLock.writeLock(), task); } private void asyncExecuteTaskWithLock(final Lock lock, final Runnable task) { ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { lock.lock(); try { task.run(); } finally { lock.unlock(); } } }); } /** /** * Closes and cleans up the binary dictionary. * Closes and cleans up the binary dictionary. */ */ @Override @Override public void close() { public void close() { ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { if (mBinaryDictionary != null) { if (mBinaryDictionary != null) { Loading @@ -188,6 +211,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return attributeMap; return attributeMap; } } private void removeBinaryDictionary() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override public void run() { removeBinaryDictionaryLocked(); } }); } private void removeBinaryDictionaryLocked() { private void removeBinaryDictionaryLocked() { if (mBinaryDictionary != null) { if (mBinaryDictionary != null) { mBinaryDictionary.close(); mBinaryDictionary.close(); Loading @@ -211,7 +243,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } } public void clear() { public void clear() { ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { removeBinaryDictionaryLocked(); removeBinaryDictionaryLocked(); Loading @@ -224,13 +256,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { * Check whether GC is needed and run GC if required. * Check whether GC is needed and run GC if required. */ */ protected void runGCIfRequired(final boolean mindsBlockByGC) { protected void runGCIfRequired(final boolean mindsBlockByGC) { ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { if (mBinaryDictionary == null) { if (mBinaryDictionary == null) { return; return; } } runGCAfterAllPrioritizedTasksIfRequiredLocked(mindsBlockByGC); runGCIfRequiredLocked(mindsBlockByGC); } } }); }); } } Loading @@ -241,25 +273,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } } } } private void runGCAfterAllPrioritizedTasksIfRequiredLocked(final boolean mindsBlockByGC) { // needsToRunGC() have to be called with lock. if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) { if (setProcessingLargeTaskIfNot()) { // Run GC after currently existing time sensitive operations. ExecutorUtils.getExecutor(mDictName).executePrioritized(new Runnable() { @Override public void run() { try { mBinaryDictionary.flushWithGC(); } finally { mProcessingLargeTask.set(false); } } }); } } } /** /** * Dynamically adds a word unigram to the dictionary. May overwrite an existing entry. * Dynamically adds a word unigram to the dictionary. May overwrite an existing entry. */ */ Loading @@ -267,13 +280,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord, final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord, final boolean isBlacklisted, final int timestamp) { final boolean isBlacklisted, final int timestamp) { reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { if (mBinaryDictionary == null) { if (mBinaryDictionary == null) { return; return; } } runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */); runGCIfRequiredLocked(true /* mindsBlockByGC */); addWordDynamicallyLocked(word, frequency, shortcutTarget, shortcutFreq, addWordDynamicallyLocked(word, frequency, shortcutTarget, shortcutFreq, isNotAWord, isBlacklisted, timestamp); isNotAWord, isBlacklisted, timestamp); } } Loading @@ -293,13 +306,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { public void addBigramDynamically(final String word0, final String word1, public void addBigramDynamically(final String word0, final String word1, final int frequency, final int timestamp) { final int frequency, final int timestamp) { reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { if (mBinaryDictionary == null) { if (mBinaryDictionary == null) { return; return; } } runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */); runGCIfRequiredLocked(true /* mindsBlockByGC */); addBigramDynamicallyLocked(word0, word1, frequency, timestamp); addBigramDynamicallyLocked(word0, word1, frequency, timestamp); } } }); }); Loading @@ -315,13 +328,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { */ */ public void removeBigramDynamically(final String word0, final String word1) { public void removeBigramDynamically(final String word0, final String word1) { reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { if (mBinaryDictionary == null) { if (mBinaryDictionary == null) { return; return; } } runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */); runGCIfRequiredLocked(true /* mindsBlockByGC */); mBinaryDictionary.removeBigramWords(word0, word1); mBinaryDictionary.removeBigramWords(word0, word1); } } }); }); Loading @@ -338,14 +351,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { final ArrayList<LanguageModelParam> languageModelParams, final ArrayList<LanguageModelParam> languageModelParams, final AddMultipleDictionaryEntriesCallback callback) { final AddMultipleDictionaryEntriesCallback callback) { reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { try { if (mBinaryDictionary == null) { if (mBinaryDictionary == null) { return; return; } } final boolean locked = setProcessingLargeTaskIfNot(); try { mBinaryDictionary.addMultipleDictionaryEntries( mBinaryDictionary.addMultipleDictionaryEntries( languageModelParams.toArray( languageModelParams.toArray( new LanguageModelParam[languageModelParams.size()])); new LanguageModelParam[languageModelParams.size()])); Loading @@ -353,9 +365,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { if (callback != null) { if (callback != null) { callback.onFinished(); callback.onFinished(); } } if (locked) { mProcessingLargeTask.set(false); } } } } } }); }); Loading @@ -367,29 +376,33 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, final int sessionId, final float[] inOutLanguageWeight) { final int sessionId, final float[] inOutLanguageWeight) { reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); if (processingLargeTask()) { boolean lockAcquired = false; return null; try { } lockAcquired = mLock.readLock().tryLock( final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder = TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS, TimeUnit.MILLISECONDS); new AsyncResultHolder<ArrayList<SuggestedWordInfo>>(); if (lockAcquired) { ExecutorUtils.getExecutor(mDictName).executePrioritized(new Runnable() { @Override public void run() { if (mBinaryDictionary == null) { if (mBinaryDictionary == null) { holder.set(null); return null; return; } } final ArrayList<SuggestedWordInfo> binarySuggestion = final ArrayList<SuggestedWordInfo> suggestions = mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord, mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions, proximityInfo, blockOffensiveWords, additionalFeaturesOptions, sessionId, inOutLanguageWeight); sessionId, inOutLanguageWeight); holder.set(binarySuggestion); if (mBinaryDictionary.isCorrupted()) { if (mBinaryDictionary.isCorrupted()) { removeBinaryDictionaryLocked(); Log.i(TAG, "Dictionary (" + mDictName +") is corrupted. " + "Remove and regenerate it."); removeBinaryDictionary(); } } return suggestions; } } }); } catch (final InterruptedException e) { return holder.get(null, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS); Log.e(TAG, "Interrupted tryLock() in getSuggestionsWithSessionId().", e); } finally { if (lockAcquired) { mLock.readLock().unlock(); } } return null; } } @Override @Override Loading @@ -404,17 +417,24 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { @Override @Override public boolean isValidWord(final String word) { public boolean isValidWord(final String word) { reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); if (processingLargeTask()) { boolean lockAcquired = false; try { lockAcquired = mLock.readLock().tryLock( TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS, TimeUnit.MILLISECONDS); if (lockAcquired) { if (mBinaryDictionary == null) { return false; return false; } } final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>(); return isValidWordLocked(word); ExecutorUtils.getExecutor(mDictName).executePrioritized(new Runnable() { } @Override } catch (final InterruptedException e) { public void run() { Log.e(TAG, "Interrupted tryLock() in isValidWord().", e); holder.set(isValidWordLocked(word)); } finally { if (lockAcquired) { mLock.readLock().unlock(); } } }); } return holder.get(false, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS); return false; } } protected boolean isValidWordLocked(final String word) { protected boolean isValidWordLocked(final String word) { Loading Loading @@ -484,13 +504,14 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } } /** /** * Reloads the dictionary if required. * Load the current binary dictionary from internal storage. If the dictionary file doesn't * exists or needs to be regenerated, the new dictionary file will be asynchronously generated. * However, the dictionary itself is accessible even before the new dictionary file is actually * generated. It may return a null result for getSuggestions() in that case by design. */ */ public final void reloadDictionaryIfRequired() { public final void reloadDictionaryIfRequired() { if (!isReloadRequired()) return; if (!isReloadRequired()) return; if (setProcessingLargeTaskIfNot()) { asyncReloadDictionary(); reloadDictionary(); } } } /** /** Loading @@ -500,38 +521,24 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return mBinaryDictionary == null || mNeedsToReload; return mBinaryDictionary == null || mNeedsToReload; } } private boolean processingLargeTask() { return mProcessingLargeTask.get(); } // Returns whether the dictionary is being used for a large task. If true, we should not use // this dictionary for latency sensitive operations. private boolean setProcessingLargeTaskIfNot() { return mProcessingLargeTask.compareAndSet(false /* expect */ , true /* update */); } /** /** * Reloads the dictionary. Access is controlled on a per dictionary file basis and supports * Reloads the dictionary. Access is controlled on a per dictionary file basis. * concurrent calls from multiple instances that share the same dictionary file. */ */ private final void reloadDictionary() { private final void asyncReloadDictionary() { // Ensure that only one thread attempts to read or write to the shared binary dictionary if (mIsReloading.compareAndSet(false, true)) { // file at the same time. mNeedsToReload = false; ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { try { try { if (!dictionaryFileExists() || haveContentsChanged()) { if (!dictionaryFileExists() || haveContentsChanged()) { // If the shared dictionary file does not exist or is out of date and // If the shared dictionary file does not exist or contents have been // contents have been updated, the first instance that acquires the lock // updated, generate a new one. // will generate a new one createNewDictionaryLocked(); createNewDictionaryLocked(); } else if (mBinaryDictionary == null) { } else if (mBinaryDictionary == null) { // Otherwise, if the local dictionary is older than the shared dictionary, // Otherwise, load the existing dictionary. // load the shared dictionary. loadBinaryDictionaryLocked(); loadBinaryDictionaryLocked(); } } mNeedsToReload = false; if (mBinaryDictionary != null && !(isValidDictionaryLocked() if (mBinaryDictionary != null && !(isValidDictionaryLocked() // TODO: remove the check below // TODO: remove the check below && matchesExpectedBinaryDictFormatVersionForThisType( && matchesExpectedBinaryDictFormatVersionForThisType( Loading @@ -542,11 +549,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { createNewDictionaryLocked(); createNewDictionaryLocked(); } } } finally { } finally { mProcessingLargeTask.set(false); mIsReloading.set(false); } } } } }); }); } } } // TODO: cache the file's existence so that we avoid doing a disk access each time. // TODO: cache the file's existence so that we avoid doing a disk access each time. private boolean dictionaryFileExists() { private boolean dictionaryFileExists() { Loading @@ -557,7 +565,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { * Flush binary dictionary to dictionary file. * Flush binary dictionary to dictionary file. */ */ public void asyncFlushBinaryDictionary() { public void asyncFlushBinaryDictionary() { ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { flushDictionaryLocked(); flushDictionaryLocked(); Loading @@ -568,16 +576,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { // TODO: Implement BinaryDictionary.isInDictionary(). // TODO: Implement BinaryDictionary.isInDictionary(). @UsedForTesting @UsedForTesting public boolean isInUnderlyingBinaryDictionaryForTests(final String word) { public boolean isInUnderlyingBinaryDictionaryForTests(final String word) { final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>(); mLock.readLock().lock(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { try { @Override if (mBinaryDictionary != null && mDictType == Dictionary.TYPE_USER_HISTORY) { public void run() { return mBinaryDictionary.isValidWord(word); if (mDictType == Dictionary.TYPE_USER_HISTORY) { holder.set(mBinaryDictionary.isValidWord(word)); } } return false; } finally { mLock.readLock().unlock(); } } }); return holder.get(false, TIMEOUT_FOR_READ_OPS_FOR_TESTS_IN_MILLISECONDS); } } @UsedForTesting @UsedForTesting Loading @@ -599,7 +606,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { @UsedForTesting @UsedForTesting public void dumpAllWordsForDebug() { public void dumpAllWordsForDebug() { reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() { @Override @Override public void run() { public void run() { Log.d(TAG, "Dump dictionary: " + mDictName); Log.d(TAG, "Dump dictionary: " + mDictName); Loading tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java +1 −0 Original line number Original line Diff line number Diff line Loading @@ -264,6 +264,7 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { for (final String word : words) { for (final String word : words) { UserHistoryDictionary.addToDictionary(dict, prevWord, word, true, mCurrentTime); UserHistoryDictionary.addToDictionary(dict, prevWord, word, true, mCurrentTime); prevWord = word; prevWord = word; dict.waitAllTasksForTests(); assertTrue(dict.isInUnderlyingBinaryDictionaryForTests(word)); assertTrue(dict.isInUnderlyingBinaryDictionaryForTests(word)); } } forcePassingShortTime(); forcePassingShortTime(); Loading Loading
java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +0 −3 Original line number Original line Diff line number Diff line Loading @@ -87,9 +87,6 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { mLocale = locale; mLocale = locale; mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale); mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale); registerObserver(context); registerObserver(context); // Load the current binary dictionary from internal storage. If no binary dictionary exists, // reloadDictionaryIfRequired will start a new thread to generate one asynchronously. reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); } } Loading
java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +130 −123 Original line number Original line Diff line number Diff line Loading @@ -26,7 +26,6 @@ import com.android.inputmethod.latin.makedict.FormatSpec; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.makedict.WordProperty; import com.android.inputmethod.latin.makedict.WordProperty; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.utils.AsyncResultHolder; import com.android.inputmethod.latin.utils.CombinedFormatUtils; import com.android.inputmethod.latin.utils.CombinedFormatUtils; import com.android.inputmethod.latin.utils.ExecutorUtils; import com.android.inputmethod.latin.utils.ExecutorUtils; import com.android.inputmethod.latin.utils.FileUtils; import com.android.inputmethod.latin.utils.FileUtils; Loading @@ -40,6 +39,8 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** /** * Abstract base class for an expandable dictionary that can be created and updated dynamically * Abstract base class for an expandable dictionary that can be created and updated dynamically Loading @@ -59,7 +60,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private static final boolean DBG_STRESS_TEST = false; private static final boolean DBG_STRESS_TEST = false; private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100; private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100; private static final int TIMEOUT_FOR_READ_OPS_FOR_TESTS_IN_MILLISECONDS = 10000; private static final int DEFAULT_MAX_UNIGRAM_COUNT = 10000; private static final int DEFAULT_MAX_UNIGRAM_COUNT = 10000; private static final int DEFAULT_MAX_BIGRAM_COUNT = 10000; private static final int DEFAULT_MAX_BIGRAM_COUNT = 10000; Loading Loading @@ -93,9 +93,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { /** Dictionary file */ /** Dictionary file */ private final File mDictFile; private final File mDictFile; private final AtomicBoolean mProcessingLargeTask; /** Indicates whether a task for reloading the dictionary has been scheduled. */ private final AtomicBoolean mIsReloading; /** Indicates whether the current dictionary needs to be reloaded. */ private boolean mNeedsToReload; private boolean mNeedsToReload; private final ReentrantReadWriteLock mLock; /* A extension for a binary dictionary file. */ /* A extension for a binary dictionary file. */ protected static final String DICT_FILE_EXTENSION = ".dict"; protected static final String DICT_FILE_EXTENSION = ".dict"; Loading Loading @@ -144,8 +148,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { mLocale = locale; mLocale = locale; mDictFile = getDictFile(context, dictName, dictFile); mDictFile = getDictFile(context, dictName, dictFile); mBinaryDictionary = null; mBinaryDictionary = null; mProcessingLargeTask = new AtomicBoolean(); mIsReloading = new AtomicBoolean(); mNeedsToReload = false; mNeedsToReload = false; mLock = new ReentrantReadWriteLock(); } } public static File getDictFile(final Context context, final String dictName, public static File getDictFile(final Context context, final String dictName, Loading @@ -159,12 +164,30 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return dictFile != null ? dictFile.getName() : name + "." + locale.toString(); return dictFile != null ? dictFile.getName() : name + "." + locale.toString(); } } private void asyncExecuteTaskWithWriteLock(final Runnable task) { asyncExecuteTaskWithLock(mLock.writeLock(), task); } private void asyncExecuteTaskWithLock(final Lock lock, final Runnable task) { ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { lock.lock(); try { task.run(); } finally { lock.unlock(); } } }); } /** /** * Closes and cleans up the binary dictionary. * Closes and cleans up the binary dictionary. */ */ @Override @Override public void close() { public void close() { ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { if (mBinaryDictionary != null) { if (mBinaryDictionary != null) { Loading @@ -188,6 +211,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return attributeMap; return attributeMap; } } private void removeBinaryDictionary() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override public void run() { removeBinaryDictionaryLocked(); } }); } private void removeBinaryDictionaryLocked() { private void removeBinaryDictionaryLocked() { if (mBinaryDictionary != null) { if (mBinaryDictionary != null) { mBinaryDictionary.close(); mBinaryDictionary.close(); Loading @@ -211,7 +243,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } } public void clear() { public void clear() { ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { removeBinaryDictionaryLocked(); removeBinaryDictionaryLocked(); Loading @@ -224,13 +256,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { * Check whether GC is needed and run GC if required. * Check whether GC is needed and run GC if required. */ */ protected void runGCIfRequired(final boolean mindsBlockByGC) { protected void runGCIfRequired(final boolean mindsBlockByGC) { ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { if (mBinaryDictionary == null) { if (mBinaryDictionary == null) { return; return; } } runGCAfterAllPrioritizedTasksIfRequiredLocked(mindsBlockByGC); runGCIfRequiredLocked(mindsBlockByGC); } } }); }); } } Loading @@ -241,25 +273,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } } } } private void runGCAfterAllPrioritizedTasksIfRequiredLocked(final boolean mindsBlockByGC) { // needsToRunGC() have to be called with lock. if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) { if (setProcessingLargeTaskIfNot()) { // Run GC after currently existing time sensitive operations. ExecutorUtils.getExecutor(mDictName).executePrioritized(new Runnable() { @Override public void run() { try { mBinaryDictionary.flushWithGC(); } finally { mProcessingLargeTask.set(false); } } }); } } } /** /** * Dynamically adds a word unigram to the dictionary. May overwrite an existing entry. * Dynamically adds a word unigram to the dictionary. May overwrite an existing entry. */ */ Loading @@ -267,13 +280,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord, final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord, final boolean isBlacklisted, final int timestamp) { final boolean isBlacklisted, final int timestamp) { reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { if (mBinaryDictionary == null) { if (mBinaryDictionary == null) { return; return; } } runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */); runGCIfRequiredLocked(true /* mindsBlockByGC */); addWordDynamicallyLocked(word, frequency, shortcutTarget, shortcutFreq, addWordDynamicallyLocked(word, frequency, shortcutTarget, shortcutFreq, isNotAWord, isBlacklisted, timestamp); isNotAWord, isBlacklisted, timestamp); } } Loading @@ -293,13 +306,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { public void addBigramDynamically(final String word0, final String word1, public void addBigramDynamically(final String word0, final String word1, final int frequency, final int timestamp) { final int frequency, final int timestamp) { reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { if (mBinaryDictionary == null) { if (mBinaryDictionary == null) { return; return; } } runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */); runGCIfRequiredLocked(true /* mindsBlockByGC */); addBigramDynamicallyLocked(word0, word1, frequency, timestamp); addBigramDynamicallyLocked(word0, word1, frequency, timestamp); } } }); }); Loading @@ -315,13 +328,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { */ */ public void removeBigramDynamically(final String word0, final String word1) { public void removeBigramDynamically(final String word0, final String word1) { reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { if (mBinaryDictionary == null) { if (mBinaryDictionary == null) { return; return; } } runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */); runGCIfRequiredLocked(true /* mindsBlockByGC */); mBinaryDictionary.removeBigramWords(word0, word1); mBinaryDictionary.removeBigramWords(word0, word1); } } }); }); Loading @@ -338,14 +351,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { final ArrayList<LanguageModelParam> languageModelParams, final ArrayList<LanguageModelParam> languageModelParams, final AddMultipleDictionaryEntriesCallback callback) { final AddMultipleDictionaryEntriesCallback callback) { reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { try { if (mBinaryDictionary == null) { if (mBinaryDictionary == null) { return; return; } } final boolean locked = setProcessingLargeTaskIfNot(); try { mBinaryDictionary.addMultipleDictionaryEntries( mBinaryDictionary.addMultipleDictionaryEntries( languageModelParams.toArray( languageModelParams.toArray( new LanguageModelParam[languageModelParams.size()])); new LanguageModelParam[languageModelParams.size()])); Loading @@ -353,9 +365,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { if (callback != null) { if (callback != null) { callback.onFinished(); callback.onFinished(); } } if (locked) { mProcessingLargeTask.set(false); } } } } } }); }); Loading @@ -367,29 +376,33 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, final int sessionId, final float[] inOutLanguageWeight) { final int sessionId, final float[] inOutLanguageWeight) { reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); if (processingLargeTask()) { boolean lockAcquired = false; return null; try { } lockAcquired = mLock.readLock().tryLock( final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder = TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS, TimeUnit.MILLISECONDS); new AsyncResultHolder<ArrayList<SuggestedWordInfo>>(); if (lockAcquired) { ExecutorUtils.getExecutor(mDictName).executePrioritized(new Runnable() { @Override public void run() { if (mBinaryDictionary == null) { if (mBinaryDictionary == null) { holder.set(null); return null; return; } } final ArrayList<SuggestedWordInfo> binarySuggestion = final ArrayList<SuggestedWordInfo> suggestions = mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord, mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions, proximityInfo, blockOffensiveWords, additionalFeaturesOptions, sessionId, inOutLanguageWeight); sessionId, inOutLanguageWeight); holder.set(binarySuggestion); if (mBinaryDictionary.isCorrupted()) { if (mBinaryDictionary.isCorrupted()) { removeBinaryDictionaryLocked(); Log.i(TAG, "Dictionary (" + mDictName +") is corrupted. " + "Remove and regenerate it."); removeBinaryDictionary(); } } return suggestions; } } }); } catch (final InterruptedException e) { return holder.get(null, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS); Log.e(TAG, "Interrupted tryLock() in getSuggestionsWithSessionId().", e); } finally { if (lockAcquired) { mLock.readLock().unlock(); } } return null; } } @Override @Override Loading @@ -404,17 +417,24 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { @Override @Override public boolean isValidWord(final String word) { public boolean isValidWord(final String word) { reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); if (processingLargeTask()) { boolean lockAcquired = false; try { lockAcquired = mLock.readLock().tryLock( TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS, TimeUnit.MILLISECONDS); if (lockAcquired) { if (mBinaryDictionary == null) { return false; return false; } } final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>(); return isValidWordLocked(word); ExecutorUtils.getExecutor(mDictName).executePrioritized(new Runnable() { } @Override } catch (final InterruptedException e) { public void run() { Log.e(TAG, "Interrupted tryLock() in isValidWord().", e); holder.set(isValidWordLocked(word)); } finally { if (lockAcquired) { mLock.readLock().unlock(); } } }); } return holder.get(false, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS); return false; } } protected boolean isValidWordLocked(final String word) { protected boolean isValidWordLocked(final String word) { Loading Loading @@ -484,13 +504,14 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } } /** /** * Reloads the dictionary if required. * Load the current binary dictionary from internal storage. If the dictionary file doesn't * exists or needs to be regenerated, the new dictionary file will be asynchronously generated. * However, the dictionary itself is accessible even before the new dictionary file is actually * generated. It may return a null result for getSuggestions() in that case by design. */ */ public final void reloadDictionaryIfRequired() { public final void reloadDictionaryIfRequired() { if (!isReloadRequired()) return; if (!isReloadRequired()) return; if (setProcessingLargeTaskIfNot()) { asyncReloadDictionary(); reloadDictionary(); } } } /** /** Loading @@ -500,38 +521,24 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return mBinaryDictionary == null || mNeedsToReload; return mBinaryDictionary == null || mNeedsToReload; } } private boolean processingLargeTask() { return mProcessingLargeTask.get(); } // Returns whether the dictionary is being used for a large task. If true, we should not use // this dictionary for latency sensitive operations. private boolean setProcessingLargeTaskIfNot() { return mProcessingLargeTask.compareAndSet(false /* expect */ , true /* update */); } /** /** * Reloads the dictionary. Access is controlled on a per dictionary file basis and supports * Reloads the dictionary. Access is controlled on a per dictionary file basis. * concurrent calls from multiple instances that share the same dictionary file. */ */ private final void reloadDictionary() { private final void asyncReloadDictionary() { // Ensure that only one thread attempts to read or write to the shared binary dictionary if (mIsReloading.compareAndSet(false, true)) { // file at the same time. mNeedsToReload = false; ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { try { try { if (!dictionaryFileExists() || haveContentsChanged()) { if (!dictionaryFileExists() || haveContentsChanged()) { // If the shared dictionary file does not exist or is out of date and // If the shared dictionary file does not exist or contents have been // contents have been updated, the first instance that acquires the lock // updated, generate a new one. // will generate a new one createNewDictionaryLocked(); createNewDictionaryLocked(); } else if (mBinaryDictionary == null) { } else if (mBinaryDictionary == null) { // Otherwise, if the local dictionary is older than the shared dictionary, // Otherwise, load the existing dictionary. // load the shared dictionary. loadBinaryDictionaryLocked(); loadBinaryDictionaryLocked(); } } mNeedsToReload = false; if (mBinaryDictionary != null && !(isValidDictionaryLocked() if (mBinaryDictionary != null && !(isValidDictionaryLocked() // TODO: remove the check below // TODO: remove the check below && matchesExpectedBinaryDictFormatVersionForThisType( && matchesExpectedBinaryDictFormatVersionForThisType( Loading @@ -542,11 +549,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { createNewDictionaryLocked(); createNewDictionaryLocked(); } } } finally { } finally { mProcessingLargeTask.set(false); mIsReloading.set(false); } } } } }); }); } } } // TODO: cache the file's existence so that we avoid doing a disk access each time. // TODO: cache the file's existence so that we avoid doing a disk access each time. private boolean dictionaryFileExists() { private boolean dictionaryFileExists() { Loading @@ -557,7 +565,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { * Flush binary dictionary to dictionary file. * Flush binary dictionary to dictionary file. */ */ public void asyncFlushBinaryDictionary() { public void asyncFlushBinaryDictionary() { ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithWriteLock(new Runnable() { @Override @Override public void run() { public void run() { flushDictionaryLocked(); flushDictionaryLocked(); Loading @@ -568,16 +576,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { // TODO: Implement BinaryDictionary.isInDictionary(). // TODO: Implement BinaryDictionary.isInDictionary(). @UsedForTesting @UsedForTesting public boolean isInUnderlyingBinaryDictionaryForTests(final String word) { public boolean isInUnderlyingBinaryDictionaryForTests(final String word) { final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>(); mLock.readLock().lock(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { try { @Override if (mBinaryDictionary != null && mDictType == Dictionary.TYPE_USER_HISTORY) { public void run() { return mBinaryDictionary.isValidWord(word); if (mDictType == Dictionary.TYPE_USER_HISTORY) { holder.set(mBinaryDictionary.isValidWord(word)); } } return false; } finally { mLock.readLock().unlock(); } } }); return holder.get(false, TIMEOUT_FOR_READ_OPS_FOR_TESTS_IN_MILLISECONDS); } } @UsedForTesting @UsedForTesting Loading @@ -599,7 +606,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { @UsedForTesting @UsedForTesting public void dumpAllWordsForDebug() { public void dumpAllWordsForDebug() { reloadDictionaryIfRequired(); reloadDictionaryIfRequired(); ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() { @Override @Override public void run() { public void run() { Log.d(TAG, "Dump dictionary: " + mDictName); Log.d(TAG, "Dump dictionary: " + mDictName); Loading
tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java +1 −0 Original line number Original line Diff line number Diff line Loading @@ -264,6 +264,7 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { for (final String word : words) { for (final String word : words) { UserHistoryDictionary.addToDictionary(dict, prevWord, word, true, mCurrentTime); UserHistoryDictionary.addToDictionary(dict, prevWord, word, true, mCurrentTime); prevWord = word; prevWord = word; dict.waitAllTasksForTests(); assertTrue(dict.isInUnderlyingBinaryDictionaryForTests(word)); assertTrue(dict.isInUnderlyingBinaryDictionaryForTests(word)); } } forcePassingShortTime(); forcePassingShortTime(); Loading