Merge "Import translations. DO NOT MERGE" into jb-ub-latinimegoogle
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 740b86d..c884e7b 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.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 @@
     /** 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 @@
      */
     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 @@
     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 @@
     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 controller;
+        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 executor;
     }
 
     private static AbstractDictionaryWriter getDictionaryWriter(final Context context,
@@ -151,7 +177,7 @@
         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 @@
      */
     @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 @@
             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 @@
                     + 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 @@
                     + 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 @@
                             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 @@
      * dictionary exists, this method will generate one.
      */
     protected void loadDictionary() {
-        mLocalDictionaryController.mLastUpdateRequestTime = SystemClock.uptimeMillis();
-        asyncReloadDictionaryIfRequired();
+        mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = SystemClock.uptimeMillis();
+        reloadDictionaryIfRequired();
     }
 
     /**
@@ -349,8 +389,8 @@
     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 @@
         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 @@
     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 @@
      */
     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 @@
                         // 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 @@
     }
 
     /**
-     * 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 @@
     @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 @@
     @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 @@
      */
     @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 3d29c5a..6c83ac7e 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.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.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 @@
         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 @@
         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 @@
                 // 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 @@
         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 @@
         }
 
         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 @@
             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 @@
         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 @@
         }
     }
 
-    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 67ef538..3213c92 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
@@ -35,14 +35,14 @@
     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 bea5223..6405b5e 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
@@ -38,14 +38,14 @@
     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 9385ba3..3f26ff3 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 @@
     @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 be4c7c4..5b1d064 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 @@
         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 c8deaf9..5f702ee 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 @@
                     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 0000000..c2e97a3
--- /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 0000000..3c1db65
--- /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/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 86c2394..8da1859 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -204,6 +204,7 @@
     }
     jsize wordLength = env->GetArrayLength(word);
     int codePoints[wordLength];
+    env->GetIntArrayRegion(word, 0, wordLength, codePoints);
     dictionary->addUnigramWord(codePoints, wordLength, probability);
 }
 
@@ -215,8 +216,10 @@
     }
     jsize word0Length = env->GetArrayLength(word0);
     int word0CodePoints[word0Length];
+    env->GetIntArrayRegion(word0, 0, word0Length, word0CodePoints);
     jsize word1Length = env->GetArrayLength(word1);
     int word1CodePoints[word1Length];
+    env->GetIntArrayRegion(word1, 0, word1Length, word1CodePoints);
     dictionary->addBigramWords(word0CodePoints, word0Length, word1CodePoints,
             word1Length, probability);
 }
@@ -229,8 +232,10 @@
     }
     jsize word0Length = env->GetArrayLength(word0);
     int word0CodePoints[word0Length];
+    env->GetIntArrayRegion(word0, 0, word0Length, word0CodePoints);
     jsize word1Length = env->GetArrayLength(word1);
     int word1CodePoints[word1Length];
+    env->GetIntArrayRegion(word1, 0, word1Length, word1CodePoints);
     dictionary->removeBigramWords(word0CodePoints, word0Length, word1CodePoints,
             word1Length);
 }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp
index e31a910..936dc9c 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp
@@ -20,12 +20,13 @@
 
 bool DynamicBigramListPolicy::copyAllBigrams(int *const fromPos, int *const toPos) {
     const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*fromPos);
-    const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
     if (usesAdditionalBuffer) {
         *fromPos -= mBuffer->getOriginalBufferSize();
     }
     BigramListReadWriteUtils::BigramFlags flags;
     do {
+        // The buffer address can be changed after calling buffer writing methods.
+        const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
         flags = BigramListReadWriteUtils::getFlagsAndForwardPointer(buffer, fromPos);
         int bigramPos = BigramListReadWriteUtils::getBigramAddressAndForwardPointer(
                 buffer, flags, fromPos);
@@ -63,7 +64,6 @@
 bool DynamicBigramListPolicy::addBigramEntry(const int bigramPos, const int probability,
         int *const pos) {
     const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*pos);
-    const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
     if (usesAdditionalBuffer) {
         *pos -= mBuffer->getOriginalBufferSize();
     }
@@ -73,6 +73,8 @@
         if (usesAdditionalBuffer) {
             entryPos += mBuffer->getOriginalBufferSize();
         }
+        // The buffer address can be changed after calling buffer writing methods.
+        const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
         flags = BigramListReadWriteUtils::getFlagsAndForwardPointer(buffer, pos);
         BigramListReadWriteUtils::getBigramAddressAndForwardPointer(buffer, flags, pos);
         if (BigramListReadWriteUtils::hasNext(flags)) {
@@ -118,13 +120,14 @@
 
 bool DynamicBigramListPolicy::removeBigram(const int bigramListPos, const int targetBigramPos) {
     const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(bigramListPos);
-    const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
     int pos = bigramListPos;
     if (usesAdditionalBuffer) {
         pos -= mBuffer->getOriginalBufferSize();
     }
     BigramListReadWriteUtils::BigramFlags flags;
     do {
+        // The buffer address can be changed after calling buffer writing methods.
+        const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
         flags = BigramListReadWriteUtils::getFlagsAndForwardPointer(buffer, &pos);
         int bigramOffsetFieldPos = pos;
         if (usesAdditionalBuffer) {
@@ -139,8 +142,7 @@
             continue;
         }
         // Target entry is found. Write 0 into the bigram pos field to mark the bigram invalid.
-        const int bigramOffsetFieldSize =
-                BigramListReadWriteUtils::attributeAddressSize(flags);
+        const int bigramOffsetFieldSize = BigramListReadWriteUtils::attributeAddressSize(flags);
         if (!mBuffer->writeUintAndAdvancePosition(0 /* data */, bigramOffsetFieldSize,
                 &bigramOffsetFieldPos)) {
             return false;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp
index 405628b..5674cb4 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp
@@ -34,7 +34,7 @@
     mFlags = PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
     const int parentPos =
             DynamicPatriciaTrieReadingUtils::getParentPosAndAdvancePosition(dictBuf, &pos);
-    mParentPos = (parentPos != 0) ? mNodePos + parentPos : NOT_A_DICT_POS;
+    mParentPos = (parentPos != 0) ? nodePos + parentPos : NOT_A_DICT_POS;
     if (outCodePoints != 0) {
         mCodePointCount = PatriciaTrieReadingUtils::getCharsAndAdvancePosition(
                 dictBuf, mFlags, maxCodePointCount, outCodePoints, &pos);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
index 62d73bb..2e604a2 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
@@ -59,9 +59,9 @@
     static AK_FORCE_INLINE NodeFlags updateAndGetFlags(const NodeFlags originalFlags,
             const bool isMoved, const bool isDeleted) {
         NodeFlags flags = originalFlags;
-        flags = isMoved ? ((flags & (!MASK_MOVED)) | FLAG_IS_MOVED) : flags;
-        flags = isDeleted ? ((flags & (!MASK_MOVED)) | FLAG_IS_DELETED) : flags;
-        flags = (!isMoved && !isDeleted) ? ((flags & (!MASK_MOVED)) | FLAG_IS_NOT_MOVED) : flags;
+        flags = isMoved ? ((flags & (~MASK_MOVED)) | FLAG_IS_MOVED) : flags;
+        flags = isDeleted ? ((flags & (~MASK_MOVED)) | FLAG_IS_DELETED) : flags;
+        flags = (!isMoved && !isDeleted) ? ((flags & (~MASK_MOVED)) | FLAG_IS_NOT_MOVED) : flags;
         return flags;
     }
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
index e244212..7c0b628 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
@@ -26,13 +26,10 @@
 
 namespace latinime {
 
-// TODO: Enable dynamic update and remove this flag.
-const bool DynamicPatriciaTrieWritingHelper::ENABLE_DYNAMIC_UPDATE = false;
-
 bool DynamicPatriciaTrieWritingHelper::addUnigramWord(
         DynamicPatriciaTrieReadingHelper *const readingHelper,
         const int *const wordCodePoints, const int codePointCount, const int probability) {
-    int parentPos = NOT_A_VALID_WORD_POS;
+    int parentPos = NOT_A_DICT_POS;
     while (!readingHelper->isEnd()) {
         const int matchedCodePointCount = readingHelper->getPrevTotalCodePointCount();
         if (!readingHelper->isMatchedCodePoint(0 /* index */,
@@ -47,33 +44,23 @@
         const int nodeCodePointCount = nodeReader->getCodePointCount();
         for (int j = 1; j < nodeCodePointCount; ++j) {
             const int nextIndex = matchedCodePointCount + j;
-            if (nextIndex >= codePointCount) {
-                // TODO: split current node after j - 1, create child and make this terminal.
-                return false;
-            }
-            if (!readingHelper->isMatchedCodePoint(j,
+            if (nextIndex >= codePointCount || !readingHelper->isMatchedCodePoint(j,
                     wordCodePoints[matchedCodePointCount + j])) {
-                // TODO: split current node after j - 1 and create two children.
-                return false;
+                return reallocatePtNodeAndAddNewPtNodes(nodeReader,
+                        readingHelper->getMergedNodeCodePoints(), j, probability,
+                        wordCodePoints + matchedCodePointCount,
+                        codePointCount - matchedCodePointCount);
             }
         }
         // All characters are matched.
         if (codePointCount == readingHelper->getTotalCodePointCount()) {
-            if (ENABLE_DYNAMIC_UPDATE) {
-                return setPtNodeProbability(nodeReader, probability,
-                        readingHelper->getMergedNodeCodePoints());
-            } else {
-                return false;
-            }
+            return setPtNodeProbability(nodeReader, probability,
+                    readingHelper->getMergedNodeCodePoints());
         }
         if (!nodeReader->hasChildren()) {
-            if (ENABLE_DYNAMIC_UPDATE) {
-                return createChildrenPtNodeArrayAndAChildPtNode(nodeReader, probability,
-                        wordCodePoints + readingHelper->getTotalCodePointCount(),
-                        codePointCount - readingHelper->getTotalCodePointCount());
-            } else {
-                return false;
-            }
+            return createChildrenPtNodeArrayAndAChildPtNode(nodeReader, probability,
+                    wordCodePoints + readingHelper->getTotalCodePointCount(),
+                    codePointCount - readingHelper->getTotalCodePointCount());
         }
         // Advance to the children nodes.
         parentPos = nodeReader->getNodePos();
@@ -84,14 +71,10 @@
         return false;
     }
     int pos = readingHelper->getPosOfLastForwardLinkField();
-    if (ENABLE_DYNAMIC_UPDATE) {
-        return createAndInsertNodeIntoPtNodeArray(parentPos,
-                wordCodePoints + readingHelper->getPrevTotalCodePointCount(),
-                codePointCount - readingHelper->getPrevTotalCodePointCount(),
-                probability, &pos);
-    } else {
-        return false;
-    }
+    return createAndInsertNodeIntoPtNodeArray(parentPos,
+            wordCodePoints + readingHelper->getPrevTotalCodePointCount(),
+            codePointCount - readingHelper->getPrevTotalCodePointCount(),
+            probability, &pos);
 }
 
 bool DynamicPatriciaTrieWritingHelper::addBigramWords(const int word0Pos, const int word1Pos,
@@ -136,20 +119,22 @@
             &writingPos)) {
         return false;
     }
-    // Update moved position, which is stored in the parent position field.
-    if (!DynamicPatriciaTrieWritingUtils::writeParentPositionAndAdvancePosition(
-            mBuffer, movedPos, &writingPos)) {
+    // Update moved position, which is stored in the parent offset field.
+    const int movedPosOffset = movedPos - originalNode->getNodePos();
+    if (!DynamicPatriciaTrieWritingUtils::writeParentOffsetAndAdvancePosition(
+            mBuffer, movedPosOffset, &writingPos)) {
         return false;
     }
     return true;
 }
 
-// Write new node at writingPos.
-bool DynamicPatriciaTrieWritingHelper::writeNodeToBuffer(const bool isBlacklisted,
+// Write new PtNode at writingPos.
+bool DynamicPatriciaTrieWritingHelper::writePtNodeWithFullInfoToBuffer(const bool isBlacklisted,
         const bool isNotAWord, const int parentPos, const int *const codePoints,
         const int codePointCount, const int probability, const int childrenPos,
         const int originalBigramListPos, const int originalShortcutListPos,
         int *const writingPos) {
+    const int nodePos = *writingPos;
     // Create node flags and write them.
     const PatriciaTrieReadingUtils::NodeFlags nodeFlags =
             PatriciaTrieReadingUtils::createAndGetFlags(isBlacklisted, isNotAWord,
@@ -160,9 +145,10 @@
             writingPos)) {
         return false;
     }
-    // Write parent position
-    if (!DynamicPatriciaTrieWritingUtils::writeParentPositionAndAdvancePosition(mBuffer, parentPos,
-            writingPos)) {
+    // Calculate a parent offset and write the offset.
+    const int parentOffset = (parentPos != NOT_A_DICT_POS) ? parentPos - nodePos : NOT_A_DICT_POS;
+    if (!DynamicPatriciaTrieWritingUtils::writeParentOffsetAndAdvancePosition(mBuffer,
+            parentOffset, writingPos)) {
         return false;
     }
     // Write code points
@@ -186,7 +172,9 @@
     // Copy shortcut list when the originalShortcutListPos is valid dictionary position.
     if (originalShortcutListPos != NOT_A_DICT_POS) {
         int fromPos = originalShortcutListPos;
-        mShortcutPolicy->copyAllShortcuts(&fromPos, writingPos);
+        if (!mShortcutPolicy->copyAllShortcutsAndReturnIfSucceededOrNot(&fromPos, writingPos)) {
+            return false;
+        }
     }
     // Copy bigram list when the originalBigramListPos is valid dictionary position.
     if (originalBigramListPos != NOT_A_DICT_POS) {
@@ -198,6 +186,25 @@
     return true;
 }
 
+bool DynamicPatriciaTrieWritingHelper::writePtNodeToBuffer(const int parentPos,
+        const int *const codePoints, const int codePointCount, const int probability,
+        int *const writingPos) {
+    return writePtNodeWithFullInfoToBuffer(false /* isBlacklisted */, false /* isNotAWord */,
+            parentPos, codePoints, codePointCount, probability,
+            NOT_A_DICT_POS /* childrenPos */, NOT_A_DICT_POS /* originalBigramsPos */,
+            NOT_A_DICT_POS /* originalShortcutPos */, writingPos);
+}
+
+bool DynamicPatriciaTrieWritingHelper::writePtNodeToBufferByCopyingPtNodeInfo(
+        const DynamicPatriciaTrieNodeReader *const originalNode, const int parentPos,
+        const int *const codePoints, const int codePointCount, const int probability,
+        int *const writingPos) {
+    return writePtNodeWithFullInfoToBuffer(originalNode->isBlacklisted(),
+            originalNode->isNotAWord(), parentPos, codePoints, codePointCount, probability,
+            originalNode->getChildrenPos(), originalNode->getBigramsPos(),
+            originalNode->getShortcutPos(), writingPos);
+}
+
 bool DynamicPatriciaTrieWritingHelper::createAndInsertNodeIntoPtNodeArray(const int parentPos,
         const int *const nodeCodePoints, const int nodeCodePointCount, const int probability,
         int *const forwardLinkFieldPos) {
@@ -226,10 +233,8 @@
         if (!markNodeAsMovedAndSetPosition(originalPtNode, movedPos)) {
             return false;
         }
-        if (!writeNodeToBuffer(originalPtNode->isBlacklisted(), originalPtNode->isNotAWord(),
-                originalPtNode->getParentPos(), codePoints, originalPtNode->getCodePointCount(),
-                probability, originalPtNode->getChildrenPos(), originalPtNode->getBigramsPos(),
-                originalPtNode->getShortcutPos(), &movedPos)) {
+        if (!writePtNodeToBufferByCopyingPtNodeInfo(originalPtNode, originalPtNode->getParentPos(),
+                codePoints, originalPtNode->getCodePointCount(), probability, &movedPos)) {
             return false;
         }
     }
@@ -257,9 +262,7 @@
             1 /* arraySize */, &writingPos)) {
         return false;
     }
-    if (!writeNodeToBuffer(false /* isBlacklisted */, false /* isNotAWord */, parentPtNodePos,
-            nodeCodePoints, nodeCodePointCount, probability, NOT_A_DICT_POS /* childrenPos */,
-            NOT_A_DICT_POS /* originalBigramsPos */, NOT_A_DICT_POS /* originalShortcutPos */,
+    if (!writePtNodeToBuffer(parentPtNodePos, nodeCodePoints, nodeCodePointCount, probability,
             &writingPos)) {
         return false;
     }
@@ -270,4 +273,69 @@
     return true;
 }
 
+// Returns whether the dictionary updating was succeeded or not.
+bool DynamicPatriciaTrieWritingHelper::reallocatePtNodeAndAddNewPtNodes(
+        const DynamicPatriciaTrieNodeReader *const reallocatingPtNode,
+        const int *const reallocatingPtNodeCodePoints, const int overlappingCodePointCount,
+        const int probabilityOfNewPtNode, const int *const newNodeCodePoints,
+        const int newNodeCodePointCount) {
+    // When addsExtraChild is true, split the reallocating PtNode and add new child.
+    // Reallocating PtNode: abcde, newNode: abcxy.
+    // abc (1st, not terminal) __ de (2nd)
+    //                         \_ xy (extra child, terminal)
+    // Otherwise, this method makes 1st part terminal and write probabilityOfNewPtNode.
+    // Reallocating PtNode: abcde, newNode: abc.
+    // abc (1st, terminal) __ de (2nd)
+    const bool addsExtraChild = newNodeCodePointCount > overlappingCodePointCount;
+    const int firstPtNodePos = mBuffer->getTailPosition();
+    if (!markNodeAsMovedAndSetPosition(reallocatingPtNode, firstPtNodePos)) {
+        return false;
+    }
+    int writingPos = firstPtNodePos;
+    // Write the 1st part of the reallocating node. The children position will be updated later
+    // with actual children position.
+    const int newProbability = addsExtraChild ? NOT_A_PROBABILITY : probabilityOfNewPtNode;
+    if (!writePtNodeToBuffer(reallocatingPtNode->getParentPos(), reallocatingPtNodeCodePoints,
+            overlappingCodePointCount, newProbability, &writingPos)) {
+        return false;
+    }
+    const int actualChildrenPos = writingPos;
+    // Create new children PtNode array.
+    const size_t newPtNodeCount = addsExtraChild ? 2 : 1;
+    if (!DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(mBuffer,
+            newPtNodeCount, &writingPos)) {
+        return false;
+    }
+    // Write the 2nd part of the reallocating node.
+    if (!writePtNodeToBufferByCopyingPtNodeInfo(reallocatingPtNode,
+            reallocatingPtNode->getNodePos(),
+            reallocatingPtNodeCodePoints + overlappingCodePointCount,
+            reallocatingPtNode->getCodePointCount() - overlappingCodePointCount,
+            reallocatingPtNode->getProbability(), &writingPos)) {
+        return false;
+    }
+    if (addsExtraChild) {
+        if (!writePtNodeToBuffer(reallocatingPtNode->getNodePos(),
+                newNodeCodePoints + overlappingCodePointCount,
+                newNodeCodePointCount - overlappingCodePointCount, probabilityOfNewPtNode,
+                &writingPos)) {
+            return false;
+        }
+    }
+    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
+            NOT_A_DICT_POS /* forwardLinkPos */, &writingPos)) {
+        return false;
+    }
+    // Load node info. Information of the 1st part will be fetched.
+    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, mBigramPolicy, mShortcutPolicy);
+    nodeReader.fetchNodeInfoFromBuffer(firstPtNodePos);
+    // Update children position.
+    int childrenPosFieldPos = nodeReader.getChildrenPosFieldPos();
+    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBuffer,
+            actualChildrenPos, &childrenPosFieldPos)) {
+        return false;
+    }
+    return true;
+}
+
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h
index 16b84ba..ada634a 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h
@@ -49,7 +49,6 @@
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieWritingHelper);
 
-    static const bool ENABLE_DYNAMIC_UPDATE;
     BufferWithExtendableBuffer *const mBuffer;
     DynamicBigramListPolicy *const mBigramPolicy;
     DynamicShortcutListPolicy *const mShortcutPolicy;
@@ -57,11 +56,19 @@
     bool markNodeAsMovedAndSetPosition(const DynamicPatriciaTrieNodeReader *const nodeToUpdate,
             const int movedPos);
 
-    bool writeNodeToBuffer(const bool isBlacklisted, const bool isNotAWord, const int parentPos,
-            const int *const codePoints, const int codePointCount, const int probability,
-            const int childrenPos, const int originalBigramListPos,
+    bool writePtNodeWithFullInfoToBuffer(const bool isBlacklisted, const bool isNotAWord,
+            const int parentPos,  const int *const codePoints, const int codePointCount,
+            const int probability, const int childrenPos, const int originalBigramListPos,
             const int originalShortcutListPos, int *const writingPos);
 
+    bool writePtNodeToBuffer(const int parentPos, const int *const codePoints,
+            const int codePointCount, const int probability, int *const writingPos);
+
+    bool writePtNodeToBufferByCopyingPtNodeInfo(
+            const DynamicPatriciaTrieNodeReader *const originalNode, const int parentPos,
+            const int *const codePoints, const int codePointCount, const int probability,
+            int *const writingPos);
+
     bool createAndInsertNodeIntoPtNodeArray(const int parentPos, const int *const nodeCodePoints,
             const int nodeCodePointCount, const int probability, int *const forwardLinkFieldPos);
 
@@ -74,6 +81,12 @@
 
     bool createNewPtNodeArrayWithAChildPtNode(const int parentPos, const int *const nodeCodePoints,
             const int nodeCodePointCount, const int probability);
+
+    bool reallocatePtNodeAndAddNewPtNodes(
+            const DynamicPatriciaTrieNodeReader *const reallocatingPtNode,
+            const int *const reallocatingPtNodeCodePoints, const int overlappingCodePointCount,
+            const int probabilityOfNewPtNode, const int *const newNodeCodePoints,
+            const int newNodeCodePointCount);
 };
 } // namespace latinime
 #endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp
index 4187504..b261e59 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp
@@ -68,11 +68,11 @@
     return buffer->writeUintAndAdvancePosition(nodeFlags, NODE_FLAG_FIELD_SIZE, nodeFlagsFieldPos);
 }
 
-/* static */ bool DynamicPatriciaTrieWritingUtils::writeParentPositionAndAdvancePosition(
-        BufferWithExtendableBuffer *const buffer, const int parentPosition,
+// Note that parentOffset is offset from node's head position.
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeParentOffsetAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer, const int parentOffset,
         int *const parentPosFieldPos) {
-    // Note that parentPosition is offset from node's head position.
-    int offset = (parentPosition != NOT_A_DICT_POS) ? parentPosition : 0;
+    int offset = (parentOffset != NOT_A_DICT_POS) ? parentOffset : 0;
     return writeDictOffset(buffer, offset, parentPosFieldPos);
 }
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h
index 801042d..183ede4 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h
@@ -39,7 +39,7 @@
             const DynamicPatriciaTrieReadingUtils::NodeFlags nodeFlags,
             int *const nodeFlagsFieldPos);
 
-    static bool writeParentPositionAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
+    static bool writeParentOffsetAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
             const int parentPosition, int *const parentPosFieldPos);
 
     static bool writeCodePointsAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h
index 5e9c529..1803c09 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h
@@ -83,8 +83,8 @@
     }
 
     // Copy shortcuts from the shortcut list that starts at fromPos to toPos and advance these
-    // positions after the shortcut lists.
-    void copyAllShortcuts(int *const fromPos, int *const toPos) {
+    // positions after the shortcut lists. This returns whether the copy was succeeded or not.
+    bool copyAllShortcutsAndReturnIfSucceededOrNot(int *const fromPos, int *const toPos) {
         const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*fromPos);
         const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
         if (usesAdditionalBuffer) {
@@ -93,16 +93,23 @@
         const int shortcutListSize = ShortcutListReadingUtils
                 ::getShortcutListSizeAndForwardPointer(buffer, fromPos);
         // Copy shortcut list size.
-        mBuffer->writeUintAndAdvancePosition(
+        if (!mBuffer->writeUintAndAdvancePosition(
                 shortcutListSize + ShortcutListReadingUtils::getShortcutListSizeFieldSize(),
-                ShortcutListReadingUtils::getShortcutListSizeFieldSize(), toPos);
+                ShortcutListReadingUtils::getShortcutListSizeFieldSize(), toPos)) {
+            return false;
+        }
+        // Copy shortcut list.
         for (int i = 0; i < shortcutListSize; ++i) {
-            const uint8_t data = ByteArrayUtils::readUint8AndAdvancePosition(buffer, fromPos);
-            mBuffer->writeUintAndAdvancePosition(data, 1 /* size */, toPos);
+            const uint8_t data = ByteArrayUtils::readUint8AndAdvancePosition(
+                    mBuffer->getBuffer(usesAdditionalBuffer), fromPos);
+            if (!mBuffer->writeUintAndAdvancePosition(data, 1 /* size */, toPos)) {
+                return false;
+            }
         }
         if (usesAdditionalBuffer) {
             *fromPos += mBuffer->getOriginalBufferSize();
         }
+        return true;
     }
 
  private:
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.cpp
index 6326754..0fed275 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.cpp
@@ -66,16 +66,17 @@
 
 bool BufferWithExtendableBuffer::checkAndPrepareWriting(const int pos, const int size) {
     if (isInAdditionalBuffer(pos)) {
-        if (pos == mUsedAdditionalBufferSize) {
+        const int tailPosition = getTailPosition();
+        if (pos == tailPosition) {
             // Append data to the tail.
-            if (pos + size > static_cast<int>(mAdditionalBuffer.size())) {
+            if (pos + size > static_cast<int>(mAdditionalBuffer.size()) + mOriginalBufferSize) {
                 // Need to extend buffer.
                 if (!extendBuffer()) {
                     return false;
                 }
             }
             mUsedAdditionalBufferSize += size;
-        } else if (pos + size >= mUsedAdditionalBufferSize) {
+        } else if (pos + size > tailPosition) {
             // The access will beyond the tail of used region.
             return false;
         }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h b/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h
index b35b47d..c6a4841 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h
@@ -47,6 +47,7 @@
         return position >= mOriginalBufferSize;
     }
 
+    // TODO: Resolve the issue that the address can be changed when the vector is resized.
     // CAVEAT!: Be careful about array out of bound access with buffers
     AK_FORCE_INLINE const uint8_t *getBuffer(const bool usesAdditionalBuffer) const {
         if (usesAdditionalBuffer) {
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
new file mode 100644
index 0000000..bf4954b
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -0,0 +1,125 @@
+/*
+ * 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;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.inputmethod.latin.makedict.DictEncoder;
+import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Locale;
+
+@LargeTest
+public class BinaryDictionaryTests extends AndroidTestCase {
+    private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
+            new FormatSpec.FormatOptions(3 /* version */, true /* supportsDynamicUpdate */);
+    private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
+    private static final String TEST_LOCALE = "test";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private File createEmptyDictionaryAndGetFile(final String filename) throws IOException,
+            UnsupportedFormatException {
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
+                new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false));
+        final File file = File.createTempFile(filename, TEST_DICT_FILE_EXTENSION,
+                getContext().getCacheDir());
+        final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+        dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
+        return file;
+    }
+
+    public void testIsValidDictionary() {
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        assertTrue("binaryDictionary must be valid for existing valid dictionary file.",
+                binaryDictionary.isValidDictionary());
+        binaryDictionary.close();
+        assertFalse("binaryDictionary must be invalid after closing.",
+                binaryDictionary.isValidDictionary());
+        dictFile.delete();
+        binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), 0 /* offset */,
+                dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(),
+                TEST_LOCALE, true /* isUpdatable */);
+        assertFalse("binaryDictionary must be invalid for not existing dictionary file.",
+                binaryDictionary.isValidDictionary());
+        binaryDictionary.close();
+    }
+
+    public void testAddUnigramWord() {
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        final int probability = 100;
+        binaryDictionary.addUnigramWord("aaa", probability);
+        // Reallocate and create.
+        binaryDictionary.addUnigramWord("aab", probability);
+        // Insert into children.
+        binaryDictionary.addUnigramWord("aac", probability);
+        // Make terminal.
+        binaryDictionary.addUnigramWord("aa", probability);
+        // Create children.
+        binaryDictionary.addUnigramWord("aaaa", probability);
+        // Reallocate and make termianl.
+        binaryDictionary.addUnigramWord("a", probability);
+
+        final int updatedProbability = 200;
+        // Update.
+        binaryDictionary.addUnigramWord("aaa", updatedProbability);
+
+        assertEquals(probability, binaryDictionary.getFrequency("aab"));
+        assertEquals(probability, binaryDictionary.getFrequency("aac"));
+        assertEquals(probability, binaryDictionary.getFrequency("aac"));
+        assertEquals(probability, binaryDictionary.getFrequency("aaaa"));
+        assertEquals(probability, binaryDictionary.getFrequency("a"));
+        assertEquals(updatedProbability, binaryDictionary.getFrequency("aaa"));
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index da1fb6f..2603b35 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -48,8 +48,6 @@
     protected static final int DELAY_TO_WAIT_FOR_UNDERLINE = 200;
     // The message that sets predictions is posted with a 200 ms delay
     protected static final int DELAY_TO_WAIT_FOR_PREDICTIONS = 200;
-    // The message that sets auto-corrections is posted within a 100 ms delay.
-    protected static final int DELAY_TO_WAIT_FOR_AUTOCORRECTION = 100;
 
     protected LatinIME mLatinIME;
     protected Keyboard mKeyboard;
@@ -223,7 +221,6 @@
     protected void type(final String stringToType) {
         for (int i = 0; i < stringToType.length(); i = stringToType.offsetByCodePoints(i, 1)) {
             type(stringToType.codePointAt(i));
-            sleep(DELAY_TO_WAIT_FOR_AUTOCORRECTION);
         }
     }
 
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
index 1a20ec5..d15e88b 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -77,7 +77,7 @@
     private void addToDict(final UserHistoryPredictionDictionary dict, final List<String> words) {
         String prevWord = null;
         for (String word : words) {
-            dict.forceAddWordForTest(prevWord, word, true);
+            dict.addToPersonalizationPredictionDictionary(prevWord, word, true);
             prevWord = word;
         }
     }
diff --git a/tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java b/tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java
new file mode 100644
index 0000000..7fd1679
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java
@@ -0,0 +1,73 @@
+/*
+ * 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 android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+@MediumTest
+public class AsyncResultHolderTests extends AndroidTestCase {
+    private static final String TAG = AsyncResultHolderTests.class.getSimpleName();
+
+    private static final int TIMEOUT_IN_MILLISECONDS = 500;
+    private static final int MARGIN_IN_MILLISECONDS = 250;
+    private static final int DEFAULT_VALUE = 2;
+    private static final int SET_VALUE = 1;
+
+    private <T> void setAfterGivenTime(final AsyncResultHolder<T> holder, final T value,
+            final long time) {
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    Thread.sleep(time);
+                } catch (InterruptedException e) {
+                    Log.d(TAG, "Exception while sleeping", e);
+                }
+                holder.set(value);
+            }
+        }).start();
+    }
+
+    public void testGetWithoutSet() {
+        final AsyncResultHolder<Integer> holder = new AsyncResultHolder<Integer>();
+        final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS);
+        assertEquals(DEFAULT_VALUE, resultValue);
+    }
+
+    public void testGetBeforeSet() {
+        final AsyncResultHolder<Integer> holder = new AsyncResultHolder<Integer>();
+        setAfterGivenTime(holder, SET_VALUE, TIMEOUT_IN_MILLISECONDS + MARGIN_IN_MILLISECONDS);
+        final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS);
+        assertEquals(DEFAULT_VALUE, resultValue);
+    }
+
+    public void testGetAfterSet() {
+        final AsyncResultHolder<Integer> holder = new AsyncResultHolder<Integer>();
+        holder.set(SET_VALUE);
+        final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS);
+        assertEquals(SET_VALUE, resultValue);
+    }
+
+    public void testGetBeforeTimeout() {
+        final AsyncResultHolder<Integer> holder = new AsyncResultHolder<Integer>();
+        setAfterGivenTime(holder, SET_VALUE, TIMEOUT_IN_MILLISECONDS - MARGIN_IN_MILLISECONDS);
+        final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS);
+        assertEquals(SET_VALUE, resultValue);
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutorTests.java b/tests/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutorTests.java
new file mode 100644
index 0000000..e075548
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutorTests.java
@@ -0,0 +1,105 @@
+/*
+ * 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 android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Unit tests for PrioritizedSerialExecutor.
+ * TODO: Add more detailed tests to make use of priorities, etc.
+ */
+@MediumTest
+public class PrioritizedSerialExecutorTests extends AndroidTestCase {
+    private static final String TAG = PrioritizedSerialExecutorTests.class.getSimpleName();
+
+    private static final int NUM_OF_TASKS = 10;
+    private static final int DELAY_FOR_WAITING_TASKS_MILLISECONDS = 500;
+
+    public void testExecute() {
+        final PrioritizedSerialExecutor executor = new PrioritizedSerialExecutor();
+        final AtomicInteger v = new AtomicInteger(0);
+        for (int i = 0; i < NUM_OF_TASKS; ++i) {
+            executor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    v.incrementAndGet();
+                }
+            });
+        }
+        try {
+            Thread.sleep(DELAY_FOR_WAITING_TASKS_MILLISECONDS);
+        } catch (InterruptedException e) {
+            Log.d(TAG, "Exception while sleeping.", e);
+        }
+
+        assertEquals(NUM_OF_TASKS, v.get());
+    }
+
+    public void testExecutePrioritized() {
+        final PrioritizedSerialExecutor executor = new PrioritizedSerialExecutor();
+        final AtomicInteger v = new AtomicInteger(0);
+        for (int i = 0; i < NUM_OF_TASKS; ++i) {
+            executor.executePrioritized(new Runnable() {
+                @Override
+                public void run() {
+                    v.incrementAndGet();
+                }
+            });
+        }
+        try {
+            Thread.sleep(DELAY_FOR_WAITING_TASKS_MILLISECONDS);
+        } catch (InterruptedException e) {
+            Log.d(TAG, "Exception while sleeping.", e);
+        }
+
+        assertEquals(NUM_OF_TASKS, v.get());
+    }
+
+    public void testExecuteCombined() {
+        final PrioritizedSerialExecutor executor = new PrioritizedSerialExecutor();
+        final AtomicInteger v = new AtomicInteger(0);
+        for (int i = 0; i < NUM_OF_TASKS; ++i) {
+            executor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    v.incrementAndGet();
+                }
+            });
+        }
+
+        for (int i = 0; i < NUM_OF_TASKS; ++i) {
+            executor.executePrioritized(new Runnable() {
+                @Override
+                public void run() {
+                    v.incrementAndGet();
+                }
+            });
+        }
+
+        try {
+            Thread.sleep(DELAY_FOR_WAITING_TASKS_MILLISECONDS);
+        } catch (InterruptedException e) {
+            Log.d(TAG, "Exception while sleeping.", e);
+        }
+
+        assertEquals(2 * NUM_OF_TASKS, v.get());
+    }
+}