aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java443
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java173
-rw-r--r--java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java4
-rw-r--r--java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java4
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java6
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java2
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java71
-rw-r--r--java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java126
9 files changed, 464 insertions, 367 deletions
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 740b86d86..c884e7b1f 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -24,14 +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.personalization.DynamicPredictionDictionaryBase;
+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
@@ -49,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;
@@ -72,13 +80,14 @@ 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. */
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;
@@ -86,18 +95,19 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private final boolean mIsUpdatable;
// TODO: remove, once dynamic operations is serialized
- /** Controls access to the shared binary dictionary file across multiple instances. */
- private final DictionaryController mSharedDictionaryController;
+ /** Records access to the shared binary dictionary file across multiple instances. */
+ private final DictionaryTimeRecorder mFilenameDictionaryTimeRecorder;
// TODO: remove, once dynamic operations is serialized
- /** Controls access to the local binary dictionary for this instance. */
- private final DictionaryController mLocalDictionaryController = new DictionaryController();
+ /** 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
@@ -113,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,
@@ -151,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);
}
@@ -165,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();
+ }
+ });
}
/**
@@ -222,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();
}
- }
+ });
}
/**
@@ -242,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();
}
- }
+ });
}
/**
@@ -262,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);
@@ -289,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) {
@@ -338,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();
}
/**
@@ -349,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);
@@ -361,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;
- }
+ });
}
/**
@@ -389,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();
@@ -408,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);
+ + mFilenameDictionaryTimeRecorder.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);
- }
- 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.
- mSharedDictionaryController.writeLock().lock();
- try {
- mLocalDictionaryController.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) {
@@ -463,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 {
- mLocalDictionaryController.writeLock().unlock();
+ mPerInstanceDictionaryTimeRecorder.mLastUpdateTime = time;
}
- } finally {
- mSharedDictionaryController.writeLock().unlock();
- }
+ });
}
// TODO: cache the file's existence so that we avoid doing a disk access each time.
@@ -500,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() {
- mSharedDictionaryController.readLock().lock();
- try {
- mLocalDictionaryController.writeLock().lock();
- try {
- loadDictionaryAsync();
- } finally {
- mLocalDictionaryController.writeLock().unlock();
- }
- } finally {
- mSharedDictionaryController.readLock().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;
@@ -591,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);
+ }
+ });
}
/**
@@ -605,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);
+ }
+ });
}
/**
@@ -618,42 +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) {
- mLocalDictionaryController.writeLock().lock();
- try {
- if (mDictType == Dictionary.TYPE_USER_HISTORY) {
- return ((DynamicPersonalizationDictionaryWriter) mDictionaryWriter)
- .isInDictionaryForTests(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));
+ }
}
- } finally {
- mLocalDictionaryController.writeLock().unlock();
- }
- return false;
- }
-
- // TODO: Remove and use addToPersonalizationPredictionDictionary instead!!!!!!!!!!!!!!!!
- @UsedForTesting
- public void forceAddWordForTest(
- final String word0, final String word1, final boolean isValid) {
- mLocalDictionaryController.writeLock().lock();
- try {
- mDictionaryWriter.addUnigramWord(word1, null /* the "shortcut" parameter is null */,
- DynamicPredictionDictionaryBase.FREQUENCY_FOR_TYPED, false /* isNotAWord */);
- mDictionaryWriter.addBigramWords(word0, word1,
- DynamicPredictionDictionaryBase.FREQUENCY_FOR_TYPED, isValid,
- 0 /* lastTouchedTime */);
- } finally {
- mLocalDictionaryController.writeLock().unlock();
- }
+ });
+ return holder.get(false, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
}
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 3d29c5a0b..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.
@@ -1668,8 +1667,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return didAutoCorrect;
}
- // Called from the end of onTextInput
- private void completeOnTextInput(final String rawText) {
+ // Called from PointerTracker through the KeyboardActionListener interface
+ @Override
+ public void onTextInput(final String rawText) {
+ mConnection.beginBatchEdit();
+ if (mWordComposer.isComposingWord()) {
+ commitCurrentAutoCorrection(rawText);
+ } else {
+ resetComposingState(true /* alsoResetLastComposedWord */);
+ }
mHandler.postUpdateSuggestionStrip();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS
&& ResearchLogger.RESEARCH_KEY_OUTPUT_TEXT.equals(rawText)) {
@@ -1692,44 +1698,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mEnteredText = text;
}
- // Called from PointerTracker through the KeyboardActionListener interface
- @Override
- public void onTextInput(final String rawText) {
- mConnection.beginBatchEdit();
- boolean isReturningAsynchronously = false;
- if (mWordComposer.isComposingWord()) {
- commitCurrentAutoCorrection(rawText, new Runnable() {
- @Override
- public void run() {
- completeOnTextInput(rawText);
- }
- });
- isReturningAsynchronously = true;
- } else {
- resetComposingState(true /* alsoResetLastComposedWord */);
- }
- if (!isReturningAsynchronously) {
- completeOnTextInput(rawText);
- }
- }
-
- private void completeOnStartBatchInput(final SettingsValues settingsValues) {
- final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
- if (Character.isLetterOrDigit(codePointBeforeCursor)
- || settingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) {
- mSpaceState = SPACE_STATE_PHANTOM;
- }
- mConnection.endBatchEdit();
- mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
- }
-
@Override
public void onStartBatchInput() {
mInputUpdater.onStartBatchInput();
mHandler.cancelUpdateSuggestionStrip();
mConnection.beginBatchEdit();
final SettingsValues settingsValues = mSettings.getCurrent();
- boolean isReturningAsynchronously = false;
if (mWordComposer.isComposingWord()) {
if (settingsValues.mIsInternal) {
if (mWordComposer.isBatchMode()) {
@@ -1751,21 +1725,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// tapping probably is that the word you intend to type is not in the dictionary,
// so we do not attempt to correct, on the assumption that if that was a dictionary
// word, the user would probably have gestured instead.
- commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR, new Runnable() {
- @Override
- public void run() {
- completeOnStartBatchInput(settingsValues);
- }
- });
- isReturningAsynchronously = true;
+ commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
} else {
commitTyped(LastComposedWord.NOT_A_SEPARATOR);
}
mExpectingUpdateSelection = true;
}
- if (!isReturningAsynchronously) {
- completeOnStartBatchInput(settingsValues);
+ final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
+ if (Character.isLetterOrDigit(codePointBeforeCursor)
+ || settingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) {
+ mSpaceState = SPACE_STATE_PHANTOM;
}
+ mConnection.endBatchEdit();
+ mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
}
private static final class InputUpdater implements Handler.Callback {
@@ -2245,9 +2217,30 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mKeyboardSwitcher.updateShiftState();
}
- private void completeHandleSeparator(final int primaryCode, final int x, final int y,
- final int spaceState, final SettingsValues currentSettings,
- final boolean shouldAvoidSendingCode) {
+ // 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;
+ final SettingsValues currentSettings = mSettings.getCurrent();
+ // We avoid sending spaces in languages without spaces if we were composing.
+ final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == primaryCode
+ && !currentSettings.mCurrentLanguageHasSpaces && mWordComposer.isComposingWord();
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can insert the separator at the current cursor position.
+ resetEntireInputState(mLastSelectionStart);
+ }
+ if (mWordComposer.isComposingWord()) { // May have changed since we stored wasComposing
+ if (currentSettings.mCorrectionEnabled) {
+ final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
+ : new String(new int[] { primaryCode }, 0, 1);
+ commitCurrentAutoCorrection(separator);
+ didAutoCorrect = true;
+ } else {
+ commitTyped(new String(new int[]{primaryCode}, 0, 1));
+ }
+ }
+
final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
Constants.SUGGESTION_STRIP_COORDINATE == x);
@@ -2302,44 +2295,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
mKeyboardSwitcher.updateShiftState();
- }
-
- // 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 doesAutoCorrect = false;
- final SettingsValues currentSettings = mSettings.getCurrent();
- // We avoid sending spaces in languages without spaces if we were composing.
- final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == primaryCode
- && !currentSettings.mCurrentLanguageHasSpaces && mWordComposer.isComposingWord();
- if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
- // If we are in the middle of a recorrection, we need to commit the recorrection
- // first so that we can insert the separator at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
- }
- boolean isReturningAsynchronously = false;
- if (mWordComposer.isComposingWord()) { // May have changed since we stored wasComposing
- if (currentSettings.mCorrectionEnabled) {
- final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
- : new String(new int[] { primaryCode }, 0, 1);
- commitCurrentAutoCorrection(separator, new Runnable() {
- @Override
- public void run() {
- completeHandleSeparator(primaryCode, x, y, spaceState, currentSettings,
- shouldAvoidSendingCode);
- }
- });
- doesAutoCorrect = true;
- isReturningAsynchronously = true;
- } else {
- commitTyped(new String(new int[]{primaryCode}, 0, 1));
- }
- }
- if (!isReturningAsynchronously) {
- completeHandleSeparator(primaryCode, x, y, spaceState, currentSettings,
- shouldAvoidSendingCode);
- }
- return doesAutoCorrect;
+ return didAutoCorrect;
}
private CharSequence getTextWithUnderline(final String text) {
@@ -2428,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);
}
}
@@ -2555,7 +2500,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
setSuggestionStripShown(isSuggestionsStripVisible());
}
- private void completeCommitCurrentAutoCorrection(final String separator) {
+ private void commitCurrentAutoCorrection(final String separator) {
+ // Complete any pending suggestions query first
+ if (mHandler.hasPendingUpdateSuggestions()) {
+ updateSuggestionStrip();
+ }
final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull();
final String typedWord = mWordComposer.getTypedWord();
final String autoCorrection = (typedAutoCorrection != null)
@@ -2591,22 +2540,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- private void commitCurrentAutoCorrection(final String separator, final Runnable callback) {
- getSuggestedWordsOrOlderSuggestionsAsync(Suggest.SESSION_TYPING,
- new OnGetSuggestedWordsCallback() {
- @Override
- public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
- if (suggestedWords != null) {
- setAutoCorrection(suggestedWords);
- }
- completeCommitCurrentAutoCorrection(separator);
- if (callback != null) {
- callback.run();
- }
- }
- });
- }
-
// Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
// interface
@Override
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/Ver3DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
index 9385ba3a0..3f26ff378 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
@@ -68,6 +68,12 @@ public class Ver3DictEncoder implements DictEncoder {
@Override
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();
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
index be4c7c42d..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();
}
}
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/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/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);
+ }
+ }
+}