Reset to 9bd6dac4708ad94fd0257c53e977df62b152e20c

The bulk merge from -bayo to klp-dev should not have been merged to master.

Change-Id: I527a03a76f5247e4939a672f27c314dc11cbb854
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 09a63c5..4ca846b 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -17,7 +17,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.inputmethod.latin.tests">
 
-    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17" />
+    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19" />
 
     <uses-permission android:name="android.permission.READ_CONTACTS" />
 
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java
index 2eb448c..cbe2d59 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java
@@ -116,6 +116,16 @@
     private static final String SURROGATE1 = PAIR1 + PAIR2;
     private static final String SURROGATE2 = PAIR1 + PAIR2 + PAIR3;
 
+    public void testResolveNullText() {
+        assertNull("resolve null", KeySpecParser.resolveTextReference(
+                null, mTextsSet));
+    }
+
+    public void testResolveEmptyText() {
+        assertNull("resolve empty text", KeySpecParser.resolveTextReference(
+                "!text/empty_string", mTextsSet));
+    }
+
     public void testSplitZero() {
         assertTextArray("Empty string", "");
         assertTextArray("Empty entry", ",");
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java b/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java
index 279559c..7908b26 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java
@@ -27,7 +27,7 @@
 
         public final int mId;
         public boolean mIsModifier;
-        public boolean mIsInSlidingKeyInput;
+        public boolean mIsInDraggingFinger;
         public long mPhantomUpEventTime = NOT_HAPPENED;
 
         public Element(int id) {
@@ -40,8 +40,8 @@
         }
 
         @Override
-        public boolean isInSlidingKeyInput() {
-            return mIsInSlidingKeyInput;
+        public boolean isInDraggingFinger() {
+            return mIsInDraggingFinger;
         }
 
         @Override
@@ -297,19 +297,19 @@
         assertEquals(Element.NOT_HAPPENED, mElement4.mPhantomUpEventTime);
     }
 
-    public void testIsAnyInSlidingKeyInput() {
+    public void testIsAnyInDraggingFinger() {
         Element.sPhantomUpCount = 0;
-        assertFalse(mQueue.isAnyInSlidingKeyInput());
+        assertFalse(mQueue.isAnyInDraggingFinger());
 
         mQueue.add(mElement1);
         mQueue.add(mElement2);
         mQueue.add(mElement3);
         mQueue.add(mElement4);
 
-        assertFalse(mQueue.isAnyInSlidingKeyInput());
+        assertFalse(mQueue.isAnyInDraggingFinger());
 
-        mElement3.mIsInSlidingKeyInput = true;
-        assertTrue(mQueue.isAnyInSlidingKeyInput());
+        mElement3.mIsInDraggingFinger = true;
+        assertTrue(mQueue.isAnyInDraggingFinger());
 
         assertEquals(0, Element.sPhantomUpCount);
         assertEquals(4, mQueue.size());
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
index cd5384e..e027651 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
@@ -22,6 +22,7 @@
 
 import com.android.inputmethod.latin.makedict.CodePointUtils;
 import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.utils.FileUtils;
 
 import java.io.File;
 import java.io.IOException;
@@ -30,6 +31,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Random;
+import java.util.concurrent.TimeUnit;
 
 @LargeTest
 public class BinaryDictionaryDecayingTests extends AndroidTestCase {
@@ -37,61 +39,146 @@
     private static final String TEST_LOCALE = "test";
 
     // Note that these are corresponding definitions in native code in
-    // latinime::DynamicPatriciaTriePolicy.
-    private static final String SET_NEEDS_TO_DECAY_FOR_TESTING_KEY =
-            "SET_NEEDS_TO_DECAY_FOR_TESTING";
+    // latinime::Ver4PatriciaTriePolicy.
+    private static final String SET_CURRENT_TIME_FOR_TESTING_QUERY =
+            "SET_CURRENT_TIME_FOR_TESTING";
+    private static final String GET_CURRENT_TIME_QUERY = "GET_CURRENT_TIME";
+    private static final String QUIT_TIMEKEEPER_TEST_MODE_QUERY = "QUIT_TIMEKEEPER_TEST_MODE";
 
     private static final int DUMMY_PROBABILITY = 0;
 
+    private int mCurrentTime = 0;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        mCurrentTime = 0;
     }
 
     @Override
     protected void tearDown() throws Exception {
         super.tearDown();
+        try {
+            final File dictFile =
+                    createEmptyDictionaryAndGetFile("TestBinaryDictionary", FormatSpec.VERSION4);
+            final BinaryDictionary binaryDictionary =
+                    new BinaryDictionary(dictFile.getAbsolutePath(), 0 /* offset */,
+                            dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(),
+                            TEST_LOCALE, true /* isUpdatable */);
+            binaryDictionary.getPropertyForTests(QUIT_TIMEKEEPER_TEST_MODE_QUERY);
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+    }
+
+    private void addUnigramWord(final BinaryDictionary binaryDictionary, final String word,
+            final int probability) {
+        binaryDictionary.addUnigramWord(word, probability, "" /* shortcutTarget */,
+                BinaryDictionary.NOT_A_PROBABILITY /* shortcutProbability */,
+                false /* isNotAWord */, false /* isBlacklisted */,
+                mCurrentTime /* timestamp */);
+    }
+
+    private void addBigramWords(final BinaryDictionary binaryDictionary, final String word0,
+            final String word1, final int probability) {
+        binaryDictionary.addBigramWords(word0, word1, probability,
+                mCurrentTime /* timestamp */);
     }
 
     private void forcePassingShortTime(final BinaryDictionary binaryDictionary) {
-        // Entries having low probability would be suppressed once in 3 GCs.
-        final int count = 3;
-        for (int i = 0; i < count; i++) {
-            binaryDictionary.getPropertyForTests(SET_NEEDS_TO_DECAY_FOR_TESTING_KEY);
-            binaryDictionary.flushWithGC();
-        }
+        // 4 days.
+        final int timeToElapse = (int)TimeUnit.SECONDS.convert(4, TimeUnit.DAYS);
+        mCurrentTime += timeToElapse;
+        setCurrentTime(binaryDictionary, mCurrentTime);
+        binaryDictionary.flushWithGC();
     }
 
     private void forcePassingLongTime(final BinaryDictionary binaryDictionary) {
-        // Currently, probabilities are decayed when GC is run. All entries that have never been
-        // typed in 128 GCs would be removed.
-        final int count = 128;
-        for (int i = 0; i < count; i++) {
-            binaryDictionary.getPropertyForTests(SET_NEEDS_TO_DECAY_FOR_TESTING_KEY);
-            binaryDictionary.flushWithGC();
-        }
+        // 60 days.
+        final int timeToElapse = (int)TimeUnit.SECONDS.convert(60, TimeUnit.DAYS);
+        mCurrentTime += timeToElapse;
+        setCurrentTime(binaryDictionary, mCurrentTime);
+        binaryDictionary.flushWithGC();
     }
 
-    private File createEmptyDictionaryAndGetFile(final String filename) throws IOException {
-        final File file = File.createTempFile(filename, TEST_DICT_FILE_EXTENSION,
+    private File createEmptyDictionaryAndGetFile(final String dictId,
+            final int formatVersion) throws IOException {
+        if (formatVersion == FormatSpec.VERSION4) {
+            return createEmptyVer4DictionaryAndGetFile(dictId);
+        } else {
+            throw new IOException("Dictionary format version " + formatVersion
+                    + " is not supported.");
+        }
+    }
+    private File createEmptyVer4DictionaryAndGetFile(final String dictId) throws IOException {
+        final File file = File.createTempFile(dictId, TEST_DICT_FILE_EXTENSION,
                 getContext().getCacheDir());
+        FileUtils.deleteRecursively(file);
         Map<String, String> attributeMap = new HashMap<String, String>();
         attributeMap.put(FormatSpec.FileHeader.SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE,
                 FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
         attributeMap.put(FormatSpec.FileHeader.USES_FORGETTING_CURVE_ATTRIBUTE,
                 FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
+        attributeMap.put(FormatSpec.FileHeader.HAS_HISTORICAL_INFO_ATTRIBUTE,
+                FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
+        final String headerFileName = file.getName() + FormatSpec.HEADER_FILE_EXTENSION;
         if (BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
-                3 /* dictVersion */, attributeMap)) {
+                FormatSpec.VERSION4, attributeMap)) {
             return file;
         } else {
-            throw new IOException("Empty dictionary cannot be created.");
+            throw new IOException("Empty dictionary " + file.getAbsolutePath() + " "
+                    + headerFileName + " cannot be created.");
         }
     }
 
-    public void testAddValidAndInvalidWords() {
+    private static int getCurrentTime(final BinaryDictionary binaryDictionary) {
+        return Integer.parseInt(binaryDictionary.getPropertyForTests(GET_CURRENT_TIME_QUERY));
+    }
+
+    private static void setCurrentTime(final BinaryDictionary binaryDictionary,
+            final int currentTime) {
+        final String query = SET_CURRENT_TIME_FOR_TESTING_QUERY + ":" + currentTime;
+        binaryDictionary.getPropertyForTests(query);
+    }
+
+    public void testControlCurrentTime() {
+        testControlCurrentTime(FormatSpec.VERSION4);
+    }
+
+    private void testControlCurrentTime(final int formatVersion) {
+        final int TEST_COUNT = 1000;
+        final long seed = System.currentTimeMillis();
+        final Random random = new Random(seed);
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        binaryDictionary.getPropertyForTests(QUIT_TIMEKEEPER_TEST_MODE_QUERY);
+        final int startTime = getCurrentTime(binaryDictionary);
+        for (int i = 0; i < TEST_COUNT; i++) {
+            final int currentTime = random.nextInt(Integer.MAX_VALUE);
+            setCurrentTime(binaryDictionary, currentTime);
+            assertEquals(currentTime, getCurrentTime(binaryDictionary));
+        }
+        binaryDictionary.getPropertyForTests(QUIT_TIMEKEEPER_TEST_MODE_QUERY);
+        final int endTime = getCurrentTime(binaryDictionary);
+        final int MAX_ALLOWED_ELAPSED_TIME = 10;
+        assertTrue(startTime <= endTime && endTime <= startTime + MAX_ALLOWED_ELAPSED_TIME);
+    }
+
+    public void testAddValidAndInvalidWords() {
+        testAddValidAndInvalidWords(FormatSpec.VERSION4);
+    }
+
+    private void testAddValidAndInvalidWords(final int formatVersion) {
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
@@ -99,36 +186,35 @@
                 0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
                 Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
 
-        binaryDictionary.addUnigramWord("a", Dictionary.NOT_A_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", Dictionary.NOT_A_PROBABILITY);
         assertFalse(binaryDictionary.isValidWord("a"));
-        binaryDictionary.addUnigramWord("a", Dictionary.NOT_A_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", Dictionary.NOT_A_PROBABILITY);
         assertFalse(binaryDictionary.isValidWord("a"));
-        binaryDictionary.addUnigramWord("a", Dictionary.NOT_A_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", Dictionary.NOT_A_PROBABILITY);
         assertFalse(binaryDictionary.isValidWord("a"));
-        binaryDictionary.addUnigramWord("a", Dictionary.NOT_A_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", Dictionary.NOT_A_PROBABILITY);
         assertTrue(binaryDictionary.isValidWord("a"));
 
-        binaryDictionary.addUnigramWord("b", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY);
         assertTrue(binaryDictionary.isValidWord("b"));
 
-        final int unigramProbability = binaryDictionary.getFrequency("a");
-        binaryDictionary.addBigramWords("a", "b", Dictionary.NOT_A_PROBABILITY);
+        addBigramWords(binaryDictionary, "a", "b", Dictionary.NOT_A_PROBABILITY);
         assertFalse(binaryDictionary.isValidBigram("a", "b"));
-        binaryDictionary.addBigramWords("a", "b", Dictionary.NOT_A_PROBABILITY);
+        addBigramWords(binaryDictionary, "a", "b", Dictionary.NOT_A_PROBABILITY);
         assertFalse(binaryDictionary.isValidBigram("a", "b"));
-        binaryDictionary.addBigramWords("a", "b", Dictionary.NOT_A_PROBABILITY);
+        addBigramWords(binaryDictionary, "a", "b", Dictionary.NOT_A_PROBABILITY);
         assertFalse(binaryDictionary.isValidBigram("a", "b"));
-        binaryDictionary.addBigramWords("a", "b", Dictionary.NOT_A_PROBABILITY);
+        addBigramWords(binaryDictionary, "a", "b", Dictionary.NOT_A_PROBABILITY);
         assertTrue(binaryDictionary.isValidBigram("a", "b"));
 
-        binaryDictionary.addUnigramWord("c", DUMMY_PROBABILITY);
-        binaryDictionary.addBigramWords("a", "c", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "c", DUMMY_PROBABILITY);
+        addBigramWords(binaryDictionary, "a", "c", DUMMY_PROBABILITY);
         assertTrue(binaryDictionary.isValidBigram("a", "c"));
 
         // Add bigrams of not valid unigrams.
-        binaryDictionary.addBigramWords("x", "y", Dictionary.NOT_A_PROBABILITY);
+        addBigramWords(binaryDictionary, "x", "y", Dictionary.NOT_A_PROBABILITY);
         assertFalse(binaryDictionary.isValidBigram("x", "y"));
-        binaryDictionary.addBigramWords("x", "y", DUMMY_PROBABILITY);
+        addBigramWords(binaryDictionary, "x", "y", DUMMY_PROBABILITY);
         assertFalse(binaryDictionary.isValidBigram("x", "y"));
 
         binaryDictionary.close();
@@ -136,9 +222,13 @@
     }
 
     public void testDecayingProbability() {
+        testDecayingProbability(FormatSpec.VERSION4);
+    }
+
+    private void testDecayingProbability(final int formatVersion) {
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
@@ -146,39 +236,44 @@
                 0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
                 Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
 
-        binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
         assertTrue(binaryDictionary.isValidWord("a"));
         forcePassingShortTime(binaryDictionary);
         assertFalse(binaryDictionary.isValidWord("a"));
 
-        binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY);
-        binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY);
-        binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY);
-        binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
+        assertTrue(binaryDictionary.isValidWord("a"));
         forcePassingShortTime(binaryDictionary);
         assertTrue(binaryDictionary.isValidWord("a"));
         forcePassingLongTime(binaryDictionary);
         assertFalse(binaryDictionary.isValidWord("a"));
 
-        binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY);
-        binaryDictionary.addUnigramWord("b", DUMMY_PROBABILITY);
-        binaryDictionary.addBigramWords("a", "b", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY);
+        addBigramWords(binaryDictionary, "a", "b", DUMMY_PROBABILITY);
         assertTrue(binaryDictionary.isValidBigram("a", "b"));
         forcePassingShortTime(binaryDictionary);
         assertFalse(binaryDictionary.isValidBigram("a", "b"));
 
-        binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY);
-        binaryDictionary.addUnigramWord("b", DUMMY_PROBABILITY);
-        binaryDictionary.addBigramWords("a", "b", DUMMY_PROBABILITY);
-        binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY);
-        binaryDictionary.addUnigramWord("b", DUMMY_PROBABILITY);
-        binaryDictionary.addBigramWords("a", "b", DUMMY_PROBABILITY);
-        binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY);
-        binaryDictionary.addUnigramWord("b", DUMMY_PROBABILITY);
-        binaryDictionary.addBigramWords("a", "b", DUMMY_PROBABILITY);
-        binaryDictionary.addUnigramWord("a", DUMMY_PROBABILITY);
-        binaryDictionary.addUnigramWord("b", DUMMY_PROBABILITY);
-        binaryDictionary.addBigramWords("a", "b", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY);
+        addBigramWords(binaryDictionary, "a", "b", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY);
+        addBigramWords(binaryDictionary, "a", "b", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY);
+        addBigramWords(binaryDictionary, "a", "b", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY);
+        addBigramWords(binaryDictionary, "a", "b", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
+        addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY);
+        addBigramWords(binaryDictionary, "a", "b", DUMMY_PROBABILITY);
         assertTrue(binaryDictionary.isValidBigram("a", "b"));
         forcePassingShortTime(binaryDictionary);
         assertTrue(binaryDictionary.isValidBigram("a", "b"));
@@ -190,6 +285,10 @@
     }
 
     public void testAddManyUnigramsToDecayingDict() {
+        testAddManyUnigramsToDecayingDict(FormatSpec.VERSION4);
+    }
+
+    private void testAddManyUnigramsToDecayingDict(final int formatVersion) {
         final int unigramCount = 30000;
         final int unigramTypedCount = 100000;
         final int codePointSetSize = 50;
@@ -198,13 +297,14 @@
 
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
         BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
                 0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
                 Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        setCurrentTime(binaryDictionary, mCurrentTime);
 
         final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
         final ArrayList<String> words = new ArrayList<String>();
@@ -218,14 +318,14 @@
                 binaryDictionary.getPropertyForTests(BinaryDictionary.MAX_UNIGRAM_COUNT_QUERY));
         for (int i = 0; i < unigramTypedCount; i++) {
             final String word = words.get(random.nextInt(words.size()));
-            binaryDictionary.addUnigramWord(word, DUMMY_PROBABILITY);
+            addUnigramWord(binaryDictionary, word, DUMMY_PROBABILITY);
 
             if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
                 final int unigramCountBeforeGC =
                         Integer.parseInt(binaryDictionary.getPropertyForTests(
                                 BinaryDictionary.UNIGRAM_COUNT_QUERY));
                 while (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
-                    binaryDictionary.flushWithGC();
+                    forcePassingShortTime(binaryDictionary);
                 }
                 final int unigramCountAfterGC =
                         Integer.parseInt(binaryDictionary.getPropertyForTests(
@@ -238,9 +338,75 @@
                 BinaryDictionary.UNIGRAM_COUNT_QUERY)) > 0);
         assertTrue(Integer.parseInt(binaryDictionary.getPropertyForTests(
                 BinaryDictionary.UNIGRAM_COUNT_QUERY)) <= maxUnigramCount);
+        forcePassingLongTime(binaryDictionary);
+        assertEquals(0, Integer.parseInt(binaryDictionary.getPropertyForTests(
+                BinaryDictionary.UNIGRAM_COUNT_QUERY)));
+    }
+
+    public void testOverflowUnigrams() {
+        testOverflowUnigrams(FormatSpec.VERSION4);
+    }
+
+    private void testOverflowUnigrams(final int formatVersion) {
+        final int unigramCount = 20000;
+        final int eachUnigramTypedCount = 5;
+        final int strongUnigramTypedCount = 20;
+        final int weakUnigramTypedCount = 1;
+        final int codePointSetSize = 50;
+        final long seed = System.currentTimeMillis();
+        final Random random = new Random(seed);
+
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        setCurrentTime(binaryDictionary, mCurrentTime);
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+
+        final String strong = "strong";
+        final String weak = "weak";
+        for (int j = 0; j < strongUnigramTypedCount; j++) {
+            addUnigramWord(binaryDictionary, strong, DUMMY_PROBABILITY);
+        }
+        for (int j = 0; j < weakUnigramTypedCount; j++) {
+            addUnigramWord(binaryDictionary, weak, DUMMY_PROBABILITY);
+        }
+        assertTrue(binaryDictionary.isValidWord(strong));
+        assertTrue(binaryDictionary.isValidWord(weak));
+
+        for (int i = 0; i < unigramCount; i++) {
+            final String word = CodePointUtils.generateWord(random, codePointSet);
+            for (int j = 0; j < eachUnigramTypedCount; j++) {
+                addUnigramWord(binaryDictionary, word, DUMMY_PROBABILITY);
+            }
+            if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
+                final int unigramCountBeforeGC =
+                        Integer.parseInt(binaryDictionary.getPropertyForTests(
+                                BinaryDictionary.UNIGRAM_COUNT_QUERY));
+                assertTrue(binaryDictionary.isValidWord(strong));
+                assertTrue(binaryDictionary.isValidWord(weak));
+                binaryDictionary.flushWithGC();
+                final int unigramCountAfterGC =
+                        Integer.parseInt(binaryDictionary.getPropertyForTests(
+                                BinaryDictionary.UNIGRAM_COUNT_QUERY));
+                assertTrue(unigramCountBeforeGC > unigramCountAfterGC);
+                assertFalse(binaryDictionary.isValidWord(weak));
+                assertTrue(binaryDictionary.isValidWord(strong));
+                break;
+            }
+        }
     }
 
     public void testAddManyBigramsToDecayingDict() {
+        testAddManyBigramsToDecayingDict(FormatSpec.VERSION4);
+    }
+
+    private void testAddManyBigramsToDecayingDict(final int formatVersion) {
         final int unigramCount = 5000;
         final int bigramCount = 30000;
         final int bigramTypedCount = 100000;
@@ -250,13 +416,14 @@
 
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
         BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
                 0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
                 Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        setCurrentTime(binaryDictionary, mCurrentTime);
 
         final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
         final ArrayList<String> words = new ArrayList<String>();
@@ -282,16 +449,16 @@
                 binaryDictionary.getPropertyForTests(BinaryDictionary.MAX_BIGRAM_COUNT_QUERY));
         for (int i = 0; i < bigramTypedCount; ++i) {
             final Pair<String, String> bigram = bigrams.get(random.nextInt(bigrams.size()));
-            binaryDictionary.addUnigramWord(bigram.first, DUMMY_PROBABILITY);
-            binaryDictionary.addUnigramWord(bigram.second, DUMMY_PROBABILITY);
-            binaryDictionary.addBigramWords(bigram.first, bigram.second, DUMMY_PROBABILITY);
+            addUnigramWord(binaryDictionary, bigram.first, DUMMY_PROBABILITY);
+            addUnigramWord(binaryDictionary, bigram.second, DUMMY_PROBABILITY);
+            addBigramWords(binaryDictionary, bigram.first, bigram.second, DUMMY_PROBABILITY);
 
             if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
                 final int bigramCountBeforeGC =
                         Integer.parseInt(binaryDictionary.getPropertyForTests(
                                 BinaryDictionary.BIGRAM_COUNT_QUERY));
                 while (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
-                    binaryDictionary.flushWithGC();
+                    forcePassingShortTime(binaryDictionary);
                 }
                 final int bigramCountAfterGC =
                         Integer.parseInt(binaryDictionary.getPropertyForTests(
@@ -304,5 +471,87 @@
                 BinaryDictionary.BIGRAM_COUNT_QUERY)) > 0);
         assertTrue(Integer.parseInt(binaryDictionary.getPropertyForTests(
                 BinaryDictionary.BIGRAM_COUNT_QUERY)) <= maxBigramCount);
+        forcePassingLongTime(binaryDictionary);
+        assertEquals(0, Integer.parseInt(binaryDictionary.getPropertyForTests(
+                BinaryDictionary.BIGRAM_COUNT_QUERY)));
+    }
+
+    public void testOverflowBigrams() {
+        testOverflowBigrams(FormatSpec.VERSION4);
+    }
+
+    private void testOverflowBigrams(final int formatVersion) {
+        final int bigramCount = 20000;
+        final int unigramCount = 1000;
+        final int unigramTypedCount = 20;
+        final int eachBigramTypedCount = 5;
+        final int strongBigramTypedCount = 20;
+        final int weakBigramTypedCount = 1;
+        final int codePointSetSize = 50;
+        final long seed = System.currentTimeMillis();
+        final Random random = new Random(seed);
+
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        setCurrentTime(binaryDictionary, mCurrentTime);
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+
+        final ArrayList<String> words = new ArrayList<String>();
+        for (int i = 0; i < unigramCount; i++) {
+            final String word = CodePointUtils.generateWord(random, codePointSet);
+            words.add(word);
+            for (int j = 0; j < unigramTypedCount; j++) {
+                addUnigramWord(binaryDictionary, word, DUMMY_PROBABILITY);
+            }
+        }
+        final String strong = "strong";
+        final String weak = "weak";
+        final String target = "target";
+        for (int j = 0; j < unigramTypedCount; j++) {
+            addUnigramWord(binaryDictionary, strong, DUMMY_PROBABILITY);
+            addUnigramWord(binaryDictionary, weak, DUMMY_PROBABILITY);
+            addUnigramWord(binaryDictionary, target, DUMMY_PROBABILITY);
+        }
+        binaryDictionary.flushWithGC();
+        for (int j = 0; j < strongBigramTypedCount; j++) {
+            addBigramWords(binaryDictionary, strong, target, DUMMY_PROBABILITY);
+        }
+        for (int j = 0; j < weakBigramTypedCount; j++) {
+            addBigramWords(binaryDictionary, weak, target, DUMMY_PROBABILITY);
+        }
+        assertTrue(binaryDictionary.isValidBigram(strong, target));
+        assertTrue(binaryDictionary.isValidBigram(weak, target));
+
+        for (int i = 0; i < bigramCount; i++) {
+            final int word0Index = random.nextInt(words.size());
+            final String word0 = words.get(word0Index);
+            final int index = random.nextInt(words.size() - 1);
+            final int word1Index = (index >= word0Index) ? index + 1 : index;
+            final String word1 = words.get(word1Index);
+
+            for (int j = 0; j < eachBigramTypedCount; j++) {
+                addBigramWords(binaryDictionary, word0, word1, DUMMY_PROBABILITY);
+            }
+            if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
+                final int bigramCountBeforeGC =
+                        Integer.parseInt(binaryDictionary.getPropertyForTests(
+                                BinaryDictionary.BIGRAM_COUNT_QUERY));
+                binaryDictionary.flushWithGC();
+                final int bigramCountAfterGC =
+                        Integer.parseInt(binaryDictionary.getPropertyForTests(
+                                BinaryDictionary.BIGRAM_COUNT_QUERY));
+                assertTrue(bigramCountBeforeGC > bigramCountAfterGC);
+                assertTrue(binaryDictionary.isValidBigram(strong, target));
+                assertFalse(binaryDictionary.isValidBigram(weak, target));
+                break;
+            }
+        }
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
index 5b8f0e9..cfc4c76 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -21,8 +21,12 @@
 import android.text.TextUtils;
 import android.util.Pair;
 
+import com.android.inputmethod.latin.BinaryDictionary.LanguageModelParam;
 import com.android.inputmethod.latin.makedict.CodePointUtils;
 import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.utils.FileUtils;
+import com.android.inputmethod.latin.utils.UnigramProperty;
 
 import java.io.File;
 import java.io.IOException;
@@ -33,6 +37,7 @@
 import java.util.Map;
 import java.util.Random;
 
+// TODO Use the seed passed as an argument for makedict test.
 @LargeTest
 public class BinaryDictionaryTests extends AndroidTestCase {
     private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
@@ -48,24 +53,41 @@
         super.tearDown();
     }
 
-    private File createEmptyDictionaryAndGetFile(final String filename) throws IOException {
-        final File file = File.createTempFile(filename, TEST_DICT_FILE_EXTENSION,
+    private File createEmptyDictionaryAndGetFile(final String dictId,
+            final int formatVersion) throws IOException {
+       if (formatVersion == FormatSpec.VERSION4) {
+            return createEmptyVer4DictionaryAndGetFile(dictId);
+        } else {
+            throw new IOException("Dictionary format version " + formatVersion
+                    + " is not supported.");
+        }
+    }
+
+    private File createEmptyVer4DictionaryAndGetFile(final String dictId) throws IOException {
+        final File file = File.createTempFile(dictId, TEST_DICT_FILE_EXTENSION,
                 getContext().getCacheDir());
+        file.delete();
+        file.mkdir();
         Map<String, String> attributeMap = new HashMap<String, String>();
         attributeMap.put(FormatSpec.FileHeader.SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE,
                 FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
         if (BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
-                3 /* dictVersion */, attributeMap)) {
+                FormatSpec.VERSION4, attributeMap)) {
             return file;
         } else {
-            throw new IOException("Empty dictionary cannot be created.");
+            throw new IOException("Empty dictionary " + file.getAbsolutePath()
+                    + " cannot be created.");
         }
     }
 
     public void testIsValidDictionary() {
+        testIsValidDictionary(FormatSpec.VERSION4);
+    }
+
+    private void testIsValidDictionary(final int formatVersion) {
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
@@ -77,7 +99,7 @@
         binaryDictionary.close();
         assertFalse("binaryDictionary must be invalid after closing.",
                 binaryDictionary.isValidDictionary());
-        dictFile.delete();
+        FileUtils.deleteRecursively(dictFile);
         binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(), 0 /* offset */,
                 dictFile.length(), true /* useFullEditDistance */, Locale.getDefault(),
                 TEST_LOCALE, true /* isUpdatable */);
@@ -86,10 +108,28 @@
         binaryDictionary.close();
     }
 
+    private void addUnigramWord(final BinaryDictionary binaryDictionary, final String word,
+            final int probability) {
+        binaryDictionary.addUnigramWord(word, probability, "" /* shortcutTarget */,
+                BinaryDictionary.NOT_A_PROBABILITY /* shortcutProbability */,
+                false /* isNotAWord */, false /* isBlacklisted */,
+                BinaryDictionary.NOT_A_VALID_TIMESTAMP /* timestamp */);
+    }
+
+    private void addBigramWords(final BinaryDictionary binaryDictionary, final String word0,
+            final String word1, final int probability) {
+        binaryDictionary.addBigramWords(word0, word1, probability,
+                BinaryDictionary.NOT_A_VALID_TIMESTAMP /* timestamp */);
+    }
+
     public void testAddUnigramWord() {
+        testAddUnigramWord(FormatSpec.VERSION4);
+    }
+
+    private void testAddUnigramWord(final int formatVersion) {
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
@@ -98,21 +138,21 @@
                 Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
 
         final int probability = 100;
-        binaryDictionary.addUnigramWord("aaa", probability);
+        addUnigramWord(binaryDictionary, "aaa", probability);
         // Reallocate and create.
-        binaryDictionary.addUnigramWord("aab", probability);
+        addUnigramWord(binaryDictionary, "aab", probability);
         // Insert into children.
-        binaryDictionary.addUnigramWord("aac", probability);
+        addUnigramWord(binaryDictionary, "aac", probability);
         // Make terminal.
-        binaryDictionary.addUnigramWord("aa", probability);
+        addUnigramWord(binaryDictionary, "aa", probability);
         // Create children.
-        binaryDictionary.addUnigramWord("aaaa", probability);
+        addUnigramWord(binaryDictionary, "aaaa", probability);
         // Reallocate and make termianl.
-        binaryDictionary.addUnigramWord("a", probability);
+        addUnigramWord(binaryDictionary, "a", probability);
 
         final int updatedProbability = 200;
         // Update.
-        binaryDictionary.addUnigramWord("aaa", updatedProbability);
+        addUnigramWord(binaryDictionary, "aaa", updatedProbability);
 
         assertEquals(probability, binaryDictionary.getFrequency("aab"));
         assertEquals(probability, binaryDictionary.getFrequency("aac"));
@@ -125,13 +165,17 @@
     }
 
     public void testRandomlyAddUnigramWord() {
+        testRandomlyAddUnigramWord(FormatSpec.VERSION4);
+    }
+
+    private void testRandomlyAddUnigramWord(final int formatVersion) {
         final int wordCount = 1000;
         final int codePointSetSize = 50;
         final long seed = System.currentTimeMillis();
 
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
@@ -148,7 +192,7 @@
             probabilityMap.put(word, random.nextInt(0xFF));
         }
         for (String word : probabilityMap.keySet()) {
-            binaryDictionary.addUnigramWord(word, probabilityMap.get(word));
+            addUnigramWord(binaryDictionary, word, probabilityMap.get(word));
         }
         for (String word : probabilityMap.keySet()) {
             assertEquals(word, (int)probabilityMap.get(word), binaryDictionary.getFrequency(word));
@@ -157,9 +201,13 @@
     }
 
     public void testAddBigramWords() {
+        testAddBigramWords(FormatSpec.VERSION4);
+    }
+
+    private void testAddBigramWords(final int formatVersion) {
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
@@ -170,13 +218,13 @@
         final int unigramProbability = 100;
         final int bigramProbability = 10;
         final int updatedBigramProbability = 15;
-        binaryDictionary.addUnigramWord("aaa", unigramProbability);
-        binaryDictionary.addUnigramWord("abb", unigramProbability);
-        binaryDictionary.addUnigramWord("bcc", unigramProbability);
-        binaryDictionary.addBigramWords("aaa", "abb", bigramProbability);
-        binaryDictionary.addBigramWords("aaa", "bcc", bigramProbability);
-        binaryDictionary.addBigramWords("abb", "aaa", bigramProbability);
-        binaryDictionary.addBigramWords("abb", "bcc", bigramProbability);
+        addUnigramWord(binaryDictionary, "aaa", unigramProbability);
+        addUnigramWord(binaryDictionary, "abb", unigramProbability);
+        addUnigramWord(binaryDictionary, "bcc", unigramProbability);
+        addBigramWords(binaryDictionary, "aaa", "abb", bigramProbability);
+        addBigramWords(binaryDictionary, "aaa", "bcc", bigramProbability);
+        addBigramWords(binaryDictionary, "abb", "aaa", bigramProbability);
+        addBigramWords(binaryDictionary, "abb", "bcc", bigramProbability);
 
         final int probability = binaryDictionary.calculateProbability(unigramProbability,
                 bigramProbability);
@@ -189,7 +237,7 @@
         assertEquals(probability, binaryDictionary.getBigramProbability("abb", "aaa"));
         assertEquals(probability, binaryDictionary.getBigramProbability("abb", "bcc"));
 
-        binaryDictionary.addBigramWords("aaa", "abb", updatedBigramProbability);
+        addBigramWords(binaryDictionary, "aaa", "abb", updatedBigramProbability);
         final int updatedProbability = binaryDictionary.calculateProbability(unigramProbability,
                 updatedBigramProbability);
         assertEquals(updatedProbability, binaryDictionary.getBigramProbability("aaa", "abb"));
@@ -205,22 +253,26 @@
                 binaryDictionary.getBigramProbability("aaa", "aaa"));
 
         // Testing bigram link.
-        binaryDictionary.addUnigramWord("abcde", unigramProbability);
-        binaryDictionary.addUnigramWord("fghij", unigramProbability);
-        binaryDictionary.addBigramWords("abcde", "fghij", bigramProbability);
-        binaryDictionary.addUnigramWord("fgh", unigramProbability);
-        binaryDictionary.addUnigramWord("abc", unigramProbability);
-        binaryDictionary.addUnigramWord("f", unigramProbability);
+        addUnigramWord(binaryDictionary, "abcde", unigramProbability);
+        addUnigramWord(binaryDictionary, "fghij", unigramProbability);
+        addBigramWords(binaryDictionary, "abcde", "fghij", bigramProbability);
+        addUnigramWord(binaryDictionary, "fgh", unigramProbability);
+        addUnigramWord(binaryDictionary, "abc", unigramProbability);
+        addUnigramWord(binaryDictionary, "f", unigramProbability);
         assertEquals(probability, binaryDictionary.getBigramProbability("abcde", "fghij"));
         assertEquals(Dictionary.NOT_A_PROBABILITY,
                 binaryDictionary.getBigramProbability("abcde", "fgh"));
-        binaryDictionary.addBigramWords("abcde", "fghij", updatedBigramProbability);
+        addBigramWords(binaryDictionary, "abcde", "fghij", updatedBigramProbability);
         assertEquals(updatedProbability, binaryDictionary.getBigramProbability("abcde", "fghij"));
 
         dictFile.delete();
     }
 
     public void testRandomlyAddBigramWords() {
+        testRandomlyAddBigramWords(FormatSpec.VERSION4);
+    }
+
+    private void testRandomlyAddBigramWords(final int formatVersion) {
         final int wordCount = 100;
         final int bigramCount = 1000;
         final int codePointSetSize = 50;
@@ -229,7 +281,7 @@
 
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
@@ -249,7 +301,7 @@
             words.add(word);
             final int unigramProbability = random.nextInt(0xFF);
             unigramProbabilities.put(word, unigramProbability);
-            binaryDictionary.addUnigramWord(word, unigramProbability);
+            addUnigramWord(binaryDictionary, word, unigramProbability);
         }
 
         for (int i = 0; i < bigramCount; i++) {
@@ -262,7 +314,7 @@
             bigramWords.add(bigram);
             final int bigramProbability = random.nextInt(0xF);
             bigramProbabilities.put(bigram, bigramProbability);
-            binaryDictionary.addBigramWords(word0, word1, bigramProbability);
+            addBigramWords(binaryDictionary, word0, word1, bigramProbability);
         }
 
         for (final Pair<String, String> bigram : bigramWords) {
@@ -278,9 +330,13 @@
     }
 
     public void testRemoveBigramWords() {
+        testRemoveBigramWords(FormatSpec.VERSION4);
+    }
+
+    private void testRemoveBigramWords(final int formatVersion) {
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
@@ -289,13 +345,13 @@
                 Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
         final int unigramProbability = 100;
         final int bigramProbability = 10;
-        binaryDictionary.addUnigramWord("aaa", unigramProbability);
-        binaryDictionary.addUnigramWord("abb", unigramProbability);
-        binaryDictionary.addUnigramWord("bcc", unigramProbability);
-        binaryDictionary.addBigramWords("aaa", "abb", bigramProbability);
-        binaryDictionary.addBigramWords("aaa", "bcc", bigramProbability);
-        binaryDictionary.addBigramWords("abb", "aaa", bigramProbability);
-        binaryDictionary.addBigramWords("abb", "bcc", bigramProbability);
+        addUnigramWord(binaryDictionary, "aaa", unigramProbability);
+        addUnigramWord(binaryDictionary, "abb", unigramProbability);
+        addUnigramWord(binaryDictionary, "bcc", unigramProbability);
+        addBigramWords(binaryDictionary, "aaa", "abb", bigramProbability);
+        addBigramWords(binaryDictionary, "aaa", "bcc", bigramProbability);
+        addBigramWords(binaryDictionary, "abb", "aaa", bigramProbability);
+        addBigramWords(binaryDictionary, "abb", "bcc", bigramProbability);
 
         assertEquals(true, binaryDictionary.isValidBigram("aaa", "abb"));
         assertEquals(true, binaryDictionary.isValidBigram("aaa", "bcc"));
@@ -304,7 +360,7 @@
 
         binaryDictionary.removeBigramWords("aaa", "abb");
         assertEquals(false, binaryDictionary.isValidBigram("aaa", "abb"));
-        binaryDictionary.addBigramWords("aaa", "abb", bigramProbability);
+        addBigramWords(binaryDictionary, "aaa", "abb", bigramProbability);
         assertEquals(true, binaryDictionary.isValidBigram("aaa", "abb"));
 
 
@@ -324,9 +380,13 @@
     }
 
     public void testFlushDictionary() {
+        testFlushDictionary(FormatSpec.VERSION4);
+    }
+
+    private void testFlushDictionary(final int formatVersion) {
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
@@ -335,8 +395,8 @@
                 Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
 
         final int probability = 100;
-        binaryDictionary.addUnigramWord("aaa", probability);
-        binaryDictionary.addUnigramWord("abcd", probability);
+        addUnigramWord(binaryDictionary, "aaa", probability);
+        addUnigramWord(binaryDictionary, "abcd", probability);
         // Close without flushing.
         binaryDictionary.close();
 
@@ -347,8 +407,8 @@
         assertEquals(Dictionary.NOT_A_PROBABILITY, binaryDictionary.getFrequency("aaa"));
         assertEquals(Dictionary.NOT_A_PROBABILITY, binaryDictionary.getFrequency("abcd"));
 
-        binaryDictionary.addUnigramWord("aaa", probability);
-        binaryDictionary.addUnigramWord("abcd", probability);
+        addUnigramWord(binaryDictionary, "aaa", probability);
+        addUnigramWord(binaryDictionary, "abcd", probability);
         binaryDictionary.flush();
         binaryDictionary.close();
 
@@ -358,7 +418,7 @@
 
         assertEquals(probability, binaryDictionary.getFrequency("aaa"));
         assertEquals(probability, binaryDictionary.getFrequency("abcd"));
-        binaryDictionary.addUnigramWord("bcde", probability);
+        addUnigramWord(binaryDictionary, "bcde", probability);
         binaryDictionary.flush();
         binaryDictionary.close();
 
@@ -372,9 +432,13 @@
     }
 
     public void testFlushWithGCDictionary() {
+        testFlushWithGCDictionary(FormatSpec.VERSION4);
+    }
+
+    private void testFlushWithGCDictionary(final int formatVersion) {
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
@@ -384,13 +448,13 @@
 
         final int unigramProbability = 100;
         final int bigramProbability = 10;
-        binaryDictionary.addUnigramWord("aaa", unigramProbability);
-        binaryDictionary.addUnigramWord("abb", unigramProbability);
-        binaryDictionary.addUnigramWord("bcc", unigramProbability);
-        binaryDictionary.addBigramWords("aaa", "abb", bigramProbability);
-        binaryDictionary.addBigramWords("aaa", "bcc", bigramProbability);
-        binaryDictionary.addBigramWords("abb", "aaa", bigramProbability);
-        binaryDictionary.addBigramWords("abb", "bcc", bigramProbability);
+        addUnigramWord(binaryDictionary, "aaa", unigramProbability);
+        addUnigramWord(binaryDictionary, "abb", unigramProbability);
+        addUnigramWord(binaryDictionary, "bcc", unigramProbability);
+        addBigramWords(binaryDictionary, "aaa", "abb", bigramProbability);
+        addBigramWords(binaryDictionary, "aaa", "bcc", bigramProbability);
+        addBigramWords(binaryDictionary, "abb", "aaa", bigramProbability);
+        addBigramWords(binaryDictionary, "abb", "bcc", bigramProbability);
         binaryDictionary.flushWithGC();
         binaryDictionary.close();
 
@@ -415,8 +479,12 @@
         dictFile.delete();
     }
 
-    // TODO: Evaluate performance of GC
     public void testAddBigramWordsAndFlashWithGC() {
+        testAddBigramWordsAndFlashWithGC(FormatSpec.VERSION4);
+    }
+
+    // TODO: Evaluate performance of GC
+    private void testAddBigramWordsAndFlashWithGC(final int formatVersion) {
         final int wordCount = 100;
         final int bigramCount = 1000;
         final int codePointSetSize = 30;
@@ -425,7 +493,7 @@
 
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
@@ -446,7 +514,7 @@
             words.add(word);
             final int unigramProbability = random.nextInt(0xFF);
             unigramProbabilities.put(word, unigramProbability);
-            binaryDictionary.addUnigramWord(word, unigramProbability);
+            addUnigramWord(binaryDictionary, word, unigramProbability);
         }
 
         for (int i = 0; i < bigramCount; i++) {
@@ -459,7 +527,7 @@
             bigramWords.add(bigram);
             final int bigramProbability = random.nextInt(0xF);
             bigramProbabilities.put(bigram, bigramProbability);
-            binaryDictionary.addBigramWords(word0, word1, bigramProbability);
+            addBigramWords(binaryDictionary, word0, word1, bigramProbability);
         }
 
         binaryDictionary.flushWithGC();
@@ -480,7 +548,11 @@
         dictFile.delete();
     }
 
-    public void testRandomOperetionsAndFlashWithGC() {
+    public void testRandomOperationsAndFlashWithGC() {
+        testRandomOperationsAndFlashWithGC(FormatSpec.VERSION4);
+    }
+
+    private void testRandomOperationsAndFlashWithGC(final int formatVersion) {
         final int flashWithGCIterationCount = 50;
         final int operationCountInEachIteration = 200;
         final int initialUnigramCount = 100;
@@ -494,7 +566,7 @@
 
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
@@ -513,7 +585,7 @@
             words.add(word);
             final int unigramProbability = random.nextInt(0xFF);
             unigramProbabilities.put(word, unigramProbability);
-            binaryDictionary.addUnigramWord(word, unigramProbability);
+            addUnigramWord(binaryDictionary, word, unigramProbability);
         }
         binaryDictionary.flushWithGC();
         binaryDictionary.close();
@@ -529,7 +601,7 @@
                     words.add(word);
                     final int unigramProbability = random.nextInt(0xFF);
                     unigramProbabilities.put(word, unigramProbability);
-                    binaryDictionary.addUnigramWord(word, unigramProbability);
+                    addUnigramWord(binaryDictionary, word, unigramProbability);
                 }
                 // Add bigram.
                 if (random.nextFloat() < addBigramProb && words.size() > 2) {
@@ -547,7 +619,7 @@
                     final Pair<String, String> bigram = new Pair<String, String>(word0, word1);
                     bigramWords.add(bigram);
                     bigramProbabilities.put(bigram, bigramProbability);
-                    binaryDictionary.addBigramWords(word0, word1, bigramProbability);
+                    addBigramWords(binaryDictionary, word0, word1, bigramProbability);
                 }
                 // Remove bigram.
                 if (random.nextFloat() < removeBigramProb && !bigramWords.isEmpty()) {
@@ -588,6 +660,10 @@
     }
 
     public void testAddManyUnigramsAndFlushWithGC() {
+        testAddManyUnigramsAndFlushWithGC(FormatSpec.VERSION4);
+    }
+
+    private void testAddManyUnigramsAndFlushWithGC(final int formatVersion) {
         final int flashWithGCIterationCount = 3;
         final int codePointSetSize = 50;
 
@@ -596,7 +672,7 @@
 
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
@@ -615,7 +691,7 @@
                 words.add(word);
                 final int unigramProbability = random.nextInt(0xFF);
                 unigramProbabilities.put(word, unigramProbability);
-                binaryDictionary.addUnigramWord(word, unigramProbability);
+                addUnigramWord(binaryDictionary, word, unigramProbability);
             }
 
             for (int j = 0; j < words.size(); j++) {
@@ -632,6 +708,10 @@
     }
 
     public void testUnigramAndBigramCount() {
+        testUnigramAndBigramCount(FormatSpec.VERSION4);
+    }
+
+    private void testUnigramAndBigramCount(final int formatVersion) {
         final int flashWithGCIterationCount = 10;
         final int codePointSetSize = 50;
         final int unigramCountPerIteration = 1000;
@@ -641,7 +721,7 @@
 
         File dictFile = null;
         try {
-            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         }
@@ -659,7 +739,7 @@
                 final String word = CodePointUtils.generateWord(random, codePointSet);
                 words.add(word);
                 final int unigramProbability = random.nextInt(0xFF);
-                binaryDictionary.addUnigramWord(word, unigramProbability);
+                addUnigramWord(binaryDictionary, word, unigramProbability);
             }
             for (int j = 0; j < bigramCountPerIteration; j++) {
                 final String word0 = words.get(random.nextInt(words.size()));
@@ -669,7 +749,7 @@
                 }
                 bigrams.add(new Pair<String, String>(word0, word1));
                 final int bigramProbability = random.nextInt(0xF);
-                binaryDictionary.addBigramWords(word0, word1, bigramProbability);
+                addBigramWords(binaryDictionary, word0, word1, bigramProbability);
             }
             assertEquals(new HashSet<String>(words).size(), Integer.parseInt(
                     binaryDictionary.getPropertyForTests(BinaryDictionary.UNIGRAM_COUNT_QUERY)));
@@ -685,4 +765,242 @@
 
         dictFile.delete();
     }
+
+    public void testAddMultipleDictionaryEntries() {
+        testAddMultipleDictionaryEntries(FormatSpec.VERSION4);
+    }
+
+    private void testAddMultipleDictionaryEntries(final int formatVersion) {
+        final int codePointSetSize = 20;
+        final int lmParamCount = 1000;
+        final double bigramContinueRate = 0.9;
+        final long seed = System.currentTimeMillis();
+        final Random random = new Random(seed);
+
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+        final HashMap<String, Integer> unigramProbabilities = new HashMap<String, Integer>();
+        final HashMap<Pair<String, String>, Integer> bigramProbabilities =
+                new HashMap<Pair<String, String>, Integer>();
+
+        final LanguageModelParam[] languageModelParams = new LanguageModelParam[lmParamCount];
+        String prevWord = null;
+        for (int i = 0; i < languageModelParams.length; i++) {
+            final String word = CodePointUtils.generateWord(random, codePointSet);
+            final int probability = random.nextInt(0xFF);
+            final int bigramProbability = random.nextInt(0xF);
+            unigramProbabilities.put(word, probability);
+            if (prevWord == null) {
+                languageModelParams[i] = new LanguageModelParam(word, probability,
+                        BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+            } else {
+                languageModelParams[i] = new LanguageModelParam(prevWord, word, probability,
+                        bigramProbability, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+                bigramProbabilities.put(new Pair<String, String>(prevWord, word),
+                        bigramProbability);
+            }
+            prevWord = (random.nextDouble() < bigramContinueRate) ? word : null;
+        }
+
+        final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        binaryDictionary.addMultipleDictionaryEntries(languageModelParams);
+
+        for (Map.Entry<String, Integer> entry : unigramProbabilities.entrySet()) {
+            assertEquals((int)entry.getValue(), binaryDictionary.getFrequency(entry.getKey()));
+        }
+
+        for (Map.Entry<Pair<String, String>, Integer> entry : bigramProbabilities.entrySet()) {
+            final String word0 = entry.getKey().first;
+            final String word1 = entry.getKey().second;
+            final int unigramProbability = unigramProbabilities.get(word1);
+            final int bigramProbability = entry.getValue();
+            final int probability = binaryDictionary.calculateProbability(
+                    unigramProbability, bigramProbability);
+            assertEquals(probability, binaryDictionary.getBigramProbability(word0, word1));
+        }
+    }
+
+    public void testGetUnigramProperties() {
+        testGetUnigramProperties(FormatSpec.VERSION4);
+    }
+
+    private void testGetUnigramProperties(final int formatVersion) {
+        final long seed = System.currentTimeMillis();
+        final Random random = new Random(seed);
+        final int ITERATION_COUNT = 1000;
+        final int codePointSetSize = 20;
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+        final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        final UnigramProperty invalidUnigramProperty =
+                binaryDictionary.getUnigramProperty("dummyWord");
+        assertFalse(invalidUnigramProperty.isValid());
+
+        for (int i = 0; i < ITERATION_COUNT; i++) {
+            final String word = CodePointUtils.generateWord(random, codePointSet);
+            final int unigramProbability = random.nextInt(0xFF);
+            final boolean isNotAWord = random.nextBoolean();
+            final boolean isBlacklisted = random.nextBoolean();
+            // TODO: Add tests for historical info.
+            binaryDictionary.addUnigramWord(word, unigramProbability,
+                    null /* shortcutTarget */, BinaryDictionary.NOT_A_PROBABILITY,
+                    isNotAWord, isBlacklisted, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+            final UnigramProperty unigramProperty =
+                    binaryDictionary.getUnigramProperty(word);
+            assertEquals(word, unigramProperty.mCodePoints);
+            assertTrue(unigramProperty.isValid());
+            assertEquals(isNotAWord, unigramProperty.mIsNotAWord);
+            assertEquals(isBlacklisted, unigramProperty.mIsBlacklisted);
+            assertEquals(false, unigramProperty.mHasBigrams);
+            assertEquals(false, unigramProperty.mHasShortcuts);
+            assertEquals(unigramProbability, unigramProperty.mProbability);
+            assertTrue(unigramProperty.mShortcutTargets.isEmpty());
+        }
+    }
+
+    public void testAddShortcuts() {
+        testAddShortcuts(FormatSpec.VERSION4);
+    }
+
+    private void testAddShortcuts(final int formatVersion) {
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+        final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        final int unigramProbability = 100;
+        final int shortcutProbability = 10;
+        binaryDictionary.addUnigramWord("aaa", unigramProbability, "zzz",
+                shortcutProbability, false /* isNotAWord */, false /* isBlacklisted */,
+                0 /* timestamp */);
+        UnigramProperty unigramProperty = binaryDictionary.getUnigramProperty("aaa");
+        assertEquals(1, unigramProperty.mShortcutTargets.size());
+        assertEquals("zzz", unigramProperty.mShortcutTargets.get(0).mWord);
+        assertEquals(shortcutProbability, unigramProperty.mShortcutTargets.get(0).mFrequency);
+        final int updatedShortcutProbability = 2;
+        binaryDictionary.addUnigramWord("aaa", unigramProbability, "zzz",
+                updatedShortcutProbability, false /* isNotAWord */, false /* isBlacklisted */,
+                0 /* timestamp */);
+        unigramProperty = binaryDictionary.getUnigramProperty("aaa");
+        assertEquals(1, unigramProperty.mShortcutTargets.size());
+        assertEquals("zzz", unigramProperty.mShortcutTargets.get(0).mWord);
+        assertEquals(updatedShortcutProbability,
+                unigramProperty.mShortcutTargets.get(0).mFrequency);
+        binaryDictionary.addUnigramWord("aaa", unigramProbability, "yyy",
+                shortcutProbability, false /* isNotAWord */, false /* isBlacklisted */,
+                0 /* timestamp */);
+        final HashMap<String, Integer> shortcutTargets = new HashMap<String, Integer>();
+        shortcutTargets.put("zzz", updatedShortcutProbability);
+        shortcutTargets.put("yyy", shortcutProbability);
+        unigramProperty = binaryDictionary.getUnigramProperty("aaa");
+        assertEquals(2, unigramProperty.mShortcutTargets.size());
+        for (WeightedString shortcutTarget : unigramProperty.mShortcutTargets) {
+            assertTrue(shortcutTargets.containsKey(shortcutTarget.mWord));
+            assertEquals((int)shortcutTargets.get(shortcutTarget.mWord), shortcutTarget.mFrequency);
+            shortcutTargets.remove(shortcutTarget.mWord);
+        }
+        shortcutTargets.put("zzz", updatedShortcutProbability);
+        shortcutTargets.put("yyy", shortcutProbability);
+        binaryDictionary.flushWithGC();
+        unigramProperty = binaryDictionary.getUnigramProperty("aaa");
+        assertEquals(2, unigramProperty.mShortcutTargets.size());
+        for (WeightedString shortcutTarget : unigramProperty.mShortcutTargets) {
+            assertTrue(shortcutTargets.containsKey(shortcutTarget.mWord));
+            assertEquals((int)shortcutTargets.get(shortcutTarget.mWord), shortcutTarget.mFrequency);
+            shortcutTargets.remove(shortcutTarget.mWord);
+        }
+    }
+
+    public void testAddManyShortcuts() {
+        testAddManyShortcuts(FormatSpec.VERSION4);
+    }
+
+    private void testAddManyShortcuts(final int formatVersion) {
+        final long seed = System.currentTimeMillis();
+        final Random random = new Random(seed);
+        final int UNIGRAM_COUNT = 1000;
+        final int SHORTCUT_COUNT = 10000;
+        final int codePointSetSize = 20;
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+
+        final ArrayList<String> words = new ArrayList<String>();
+        final HashMap<String, Integer> unigramProbabilities = new HashMap<String, Integer>();
+        final HashMap<String, HashMap<String, Integer>> shortcutTargets =
+                new HashMap<String, HashMap<String, Integer>>();
+
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+        final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        for (int i = 0; i < UNIGRAM_COUNT; i++) {
+            final String word = CodePointUtils.generateWord(random, codePointSet);
+            final int unigramProbability = random.nextInt(0xFF);
+            addUnigramWord(binaryDictionary, word, unigramProbability);
+            words.add(word);
+            unigramProbabilities.put(word, unigramProbability);
+            if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
+                binaryDictionary.flushWithGC();
+            }
+        }
+        for (int i = 0; i < SHORTCUT_COUNT; i++) {
+            final String shortcutTarget = CodePointUtils.generateWord(random, codePointSet);
+            final int shortcutProbability = random.nextInt(0xF);
+            final String word = words.get(random.nextInt(words.size()));
+            final int unigramProbability = unigramProbabilities.get(word);
+            binaryDictionary.addUnigramWord(word, unigramProbability, shortcutTarget,
+                    shortcutProbability, false /* isNotAWord */, false /* isBlacklisted */,
+                    0 /* timestamp */);
+            if (shortcutTargets.containsKey(word)) {
+                final HashMap<String, Integer> shortcutTargetsOfWord = shortcutTargets.get(word);
+                shortcutTargetsOfWord.put(shortcutTarget, shortcutProbability);
+            } else {
+                final HashMap<String, Integer> shortcutTargetsOfWord =
+                        new HashMap<String, Integer>();
+                shortcutTargetsOfWord.put(shortcutTarget, shortcutProbability);
+                shortcutTargets.put(word, shortcutTargetsOfWord);
+            }
+            if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
+                binaryDictionary.flushWithGC();
+            }
+        }
+
+        for (final String word : words) {
+            final UnigramProperty unigramProperty = binaryDictionary.getUnigramProperty(word);
+            assertEquals((int)unigramProbabilities.get(word), unigramProperty.mProbability);
+            assertEquals(shortcutTargets.get(word).size(), unigramProperty.mShortcutTargets.size());
+            for (final WeightedString shortcutTarget : unigramProperty.mShortcutTargets) {
+                final String targetCodePonts = shortcutTarget.mWord;
+                assertEquals((int)shortcutTargets.get(word).get(targetCodePonts),
+                        shortcutTarget.mFrequency);
+            }
+        }
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/ExpandableDictionaryTests.java b/tests/src/com/android/inputmethod/latin/ExpandableDictionaryTests.java
deleted file mode 100644
index 6aae104..0000000
--- a/tests/src/com/android/inputmethod/latin/ExpandableDictionaryTests.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.SmallTest;
-
-/**
- * Unit test for ExpandableDictionary
- */
-@SmallTest
-public class ExpandableDictionaryTests extends AndroidTestCase {
-
-    private final static int UNIGRAM_FREQ = 50;
-    // See UserBinaryDictionary for more information about this variable.
-    // For tests, its actual value does not matter.
-    private final static int SHORTCUT_FREQ = 14;
-
-    public void testAddWordAndGetWordFrequency() {
-        final ExpandableDictionary dict = new ExpandableDictionary(Dictionary.TYPE_USER);
-
-        // Add words
-        dict.addWord("abcde", "abcde", UNIGRAM_FREQ, SHORTCUT_FREQ);
-        dict.addWord("abcef", null, UNIGRAM_FREQ + 1, 0);
-
-        // Check words
-        assertFalse(dict.isValidWord("abcde"));
-        assertEquals(UNIGRAM_FREQ, dict.getWordFrequency("abcde"));
-        assertTrue(dict.isValidWord("abcef"));
-        assertEquals(UNIGRAM_FREQ+1, dict.getWordFrequency("abcef"));
-
-        dict.addWord("abc", null, UNIGRAM_FREQ + 2, 0);
-        assertTrue(dict.isValidWord("abc"));
-        assertEquals(UNIGRAM_FREQ + 2, dict.getWordFrequency("abc"));
-
-        // Add existing word with lower frequency
-        dict.addWord("abc", null, UNIGRAM_FREQ, 0);
-        assertEquals(UNIGRAM_FREQ + 2, dict.getWordFrequency("abc"));
-
-        // Add existing word with higher frequency
-        dict.addWord("abc", null, UNIGRAM_FREQ + 3, 0);
-        assertEquals(UNIGRAM_FREQ + 3, dict.getWordFrequency("abc"));
-    }
-}
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index b9b52a6..aaad740 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -30,6 +30,7 @@
 import android.view.ViewGroup;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodSubtype;
 import android.widget.EditText;
 import android.widget.FrameLayout;
 
@@ -37,15 +38,20 @@
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 import java.util.Locale;
 
 public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> {
 
     private static final String PREF_DEBUG_MODE = "debug_mode";
+    private static final String PREF_AUTO_CORRECTION_THRESHOLD = "auto_correction_threshold";
+    // Default value for auto-correction threshold. This is the string representation of the
+    // index in the resources array of auto-correction threshold settings.
+    private static final String DEFAULT_AUTO_CORRECTION_THRESHOLD = "1";
 
-    // The message that sets the underline is posted with a 200 ms delay
-    protected static final int DELAY_TO_WAIT_FOR_UNDERLINE = 200;
+    // The message that sets the underline is posted with a 500 ms delay
+    protected static final int DELAY_TO_WAIT_FOR_UNDERLINE = 500;
     // The message that sets predictions is posted with a 200 ms delay
     protected static final int DELAY_TO_WAIT_FOR_PREDICTIONS = 200;
 
@@ -54,6 +60,8 @@
     protected MyEditText mEditText;
     protected View mInputView;
     protected InputConnection mInputConnection;
+    private boolean mPreviousDebugSetting;
+    private String mPreviousAutoCorrectSetting;
 
     // A helper class to ease span tests
     public static class SpanGetter {
@@ -135,7 +143,17 @@
         final boolean previousSetting = prefs.getBoolean(key, defaultValue);
         final SharedPreferences.Editor editor = prefs.edit();
         editor.putBoolean(key, value);
-        editor.commit();
+        editor.apply();
+        return previousSetting;
+    }
+
+    protected String setStringPreference(final String key, final String value,
+            final String defaultValue) {
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME);
+        final String previousSetting = prefs.getString(key, defaultValue);
+        final SharedPreferences.Editor editor = prefs.edit();
+        editor.putString(key, value);
+        editor.apply();
         return previousSetting;
     }
 
@@ -154,9 +172,10 @@
         mEditText.setEnabled(true);
         setupService();
         mLatinIME = getService();
-        final boolean previousDebugSetting = setDebugMode(true);
+        mPreviousDebugSetting = setDebugMode(true);
+        mPreviousAutoCorrectSetting = setStringPreference(PREF_AUTO_CORRECTION_THRESHOLD,
+                DEFAULT_AUTO_CORRECTION_THRESHOLD, DEFAULT_AUTO_CORRECTION_THRESHOLD);
         mLatinIME.onCreate();
-        setDebugMode(previousDebugSetting);
         final EditorInfo ei = new EditorInfo();
         final InputConnection ic = mEditText.onCreateInputConnection(ei);
         final LayoutInflater inflater =
@@ -172,6 +191,14 @@
         changeLanguage("en_US");
     }
 
+    @Override
+    protected void tearDown() {
+        mLatinIME.mHandler.removeAllMessages();
+        setStringPreference(PREF_AUTO_CORRECTION_THRESHOLD, mPreviousAutoCorrectSetting,
+                DEFAULT_AUTO_CORRECTION_THRESHOLD);
+        setDebugMode(mPreviousDebugSetting);
+    }
+
     // We need to run the messages added to the handler from LatinIME. The only way to do
     // that is to call Looper#loop() on the right looper, so we're going to get the looper
     // object and call #loop() here. The messages in the handler actually run on the UI
@@ -244,7 +271,18 @@
 
     protected void changeLanguageWithoutWait(final String locale) {
         mEditText.mCurrentLocale = LocaleUtils.constructLocaleFromString(locale);
-        SubtypeSwitcher.getInstance().forceLocale(mEditText.mCurrentLocale);
+        final InputMethodSubtype subtype = new InputMethodSubtype(
+            R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
+            locale, "keyboard", "KeyboardLayoutSet="
+                    // TODO: this is forcing a QWERTY keyboard for all locales, which is wrong.
+                    // It's still better than using whatever keyboard is the current one, but we
+                    // should actually use the default keyboard for this locale.
+                    + SubtypeLocaleUtils.QWERTY
+                    + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
+                    + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE
+                    + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
+                    false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
+        SubtypeSwitcher.getInstance().forceSubtype(subtype);
         mLatinIME.loadKeyboard();
         runMessages();
         mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard();
diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
index c0dd993..6ad1250 100644
--- a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
+++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.latin.settings.SettingsValues;
 import com.android.inputmethod.latin.utils.TextRange;
 
 import android.inputmethodservice.InputMethodService;
@@ -39,7 +40,8 @@
 
     // The following is meant to be a reasonable default for
     // the "word_separators" resource.
-    private static final String sSeparators = ".,:;!?-";
+    private static final SettingsValues sSettings =
+            SettingsValues.makeDummySettingsValuesForTest(Locale.ENGLISH);
 
     @Override
     protected void setUp() throws Exception {
@@ -137,9 +139,9 @@
      */
     public void testGetPreviousWord() {
         // If one of the following cases breaks, the bigram suggestions won't work.
-        assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSeparators, 2), "abc");
-        assertNull(RichInputConnection.getNthPreviousWord("abc", sSeparators, 2));
-        assertNull(RichInputConnection.getNthPreviousWord("abc. def", sSeparators, 2));
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSettings, 2), "abc");
+        assertNull(RichInputConnection.getNthPreviousWord("abc", sSettings, 2));
+        assertNull(RichInputConnection.getNthPreviousWord("abc. def", sSettings, 2));
 
         // The following tests reflect the current behavior of the function
         // RichInputConnection#getNthPreviousWord.
@@ -148,15 +150,15 @@
         // this function if needed - especially since it does not seem very
         // logical. These tests are just there to catch any unintentional
         // changes in the behavior of the RichInputConnection#getPreviousWord method.
-        assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSeparators, 2), "abc");
-        assertEquals(RichInputConnection.getNthPreviousWord("abc def.", sSeparators, 2), "abc");
-        assertEquals(RichInputConnection.getNthPreviousWord("abc def .", sSeparators, 2), "def");
-        assertNull(RichInputConnection.getNthPreviousWord("abc ", sSeparators, 2));
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSettings, 2), "abc");
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def.", sSettings, 2), "abc");
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def .", sSettings, 2), "def");
+        assertNull(RichInputConnection.getNthPreviousWord("abc ", sSettings, 2));
 
-        assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSeparators, 1), "def");
-        assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSeparators, 1), "def");
-        assertNull(RichInputConnection.getNthPreviousWord("abc def.", sSeparators, 1));
-        assertNull(RichInputConnection.getNthPreviousWord("abc def .", sSeparators, 1));
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSettings, 1), "def");
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSettings, 1), "def");
+        assertNull(RichInputConnection.getNthPreviousWord("abc def.", sSettings, 1));
+        assertNull(RichInputConnection.getNthPreviousWord("abc def .", sSettings, 1));
     }
 
     /**
diff --git a/tests/src/com/android/inputmethod/latin/WordComposerTests.java b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
index 1434c6b..a67f6a4 100644
--- a/tests/src/com/android/inputmethod/latin/WordComposerTests.java
+++ b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
@@ -26,8 +26,15 @@
 public class WordComposerTests extends AndroidTestCase {
     public void testMoveCursor() {
         final WordComposer wc = new WordComposer();
+        // BMP is the Basic Multilingual Plane, as defined by Unicode. This includes
+        // most characters for most scripts, including all Roman alphabet languages,
+        // CJK, Arabic, Hebrew. Notable exceptions include some emoji and some
+        // very rare Chinese ideograms. BMP characters can be encoded on 2 bytes
+        // in UTF-16, whereas those outside the BMP need 4 bytes.
+        // http://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
         final String STR_WITHIN_BMP = "abcdef";
-        wc.setComposingWord(STR_WITHIN_BMP, null);
+        final String PREVWORD = "prevword";
+        wc.setComposingWord(STR_WITHIN_BMP, PREVWORD, null /* keyboard */);
         assertEquals(wc.size(),
                 STR_WITHIN_BMP.codePointCount(0, STR_WITHIN_BMP.length()));
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
@@ -43,13 +50,20 @@
         // Move the cursor to after the 'f'
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1));
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
+        // Check the previous word is still there
+        assertEquals(PREVWORD, wc.getPreviousWord());
         // Move the cursor past the end of the word
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(1));
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(15));
+        // Do what LatinIME does when the cursor is moved outside of the word,
+        // and check the behavior is correct.
+        wc.reset();
+        assertNull(wc.getPreviousWord());
 
         // \uD861\uDED7 is 𨛗, a character outside the BMP
         final String STR_WITH_SUPPLEMENTARY_CHAR = "abcde\uD861\uDED7fgh";
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null /* previousWord */,
+                null /* keyboard */);
         assertEquals(wc.size(), STR_WITH_SUPPLEMENTARY_CHAR.codePointCount(0,
                         STR_WITH_SUPPLEMENTARY_CHAR.length()));
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
@@ -59,34 +73,46 @@
         assertTrue(wc.isCursorFrontOrMiddleOfComposingWord());
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1));
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
+        assertNull(wc.getPreviousWord());
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, STR_WITHIN_BMP, null /* keyboard */);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
+        assertEquals(STR_WITHIN_BMP, wc.getPreviousWord());
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, STR_WITH_SUPPLEMENTARY_CHAR,
+                null /* keyboard */);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
+        assertEquals(STR_WITH_SUPPLEMENTARY_CHAR, wc.getPreviousWord());
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, STR_WITHIN_BMP, null /* keyboard */);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-3));
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-1));
+        assertEquals(STR_WITHIN_BMP, wc.getPreviousWord());
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null /* previousWord */,
+                null /* keyboard */);
         wc.setCursorPositionWithinWord(3);
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-9));
+        assertNull(wc.getPreviousWord());
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, STR_WITH_SUPPLEMENTARY_CHAR,
+                null /* keyboard */);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-10));
+        assertEquals(STR_WITH_SUPPLEMENTARY_CHAR, wc.getPreviousWord());
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null /* previousWord */,
+                null /* keyboard */);
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-11));
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null /* previousWord */,
+                null /* keyboard */);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null /* previousWord */,
+                null /* keyboard */);
         wc.setCursorPositionWithinWord(2);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
     }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
index 32c07e1..e3ec2ec 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
@@ -26,10 +26,10 @@
 import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 import com.android.inputmethod.latin.utils.ByteArrayDictBuffer;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 
@@ -52,18 +52,18 @@
 @LargeTest
 public class BinaryDictDecoderEncoderTests extends AndroidTestCase {
     private static final String TAG = BinaryDictDecoderEncoderTests.class.getSimpleName();
-    private static final int DEFAULT_MAX_UNIGRAMS = 100;
+    private static final int DEFAULT_MAX_UNIGRAMS = 300;
     private static final int DEFAULT_CODE_POINT_SET_SIZE = 50;
+    private static final int LARGE_CODE_POINT_SET_SIZE = 300;
     private static final int UNIGRAM_FREQ = 10;
     private static final int BIGRAM_FREQ = 50;
     private static final int TOLERANCE_OF_BIGRAM_FREQ = 5;
     private static final int NUM_OF_NODES_HAVING_SHORTCUTS = 50;
     private static final int NUM_OF_SHORTCUTS = 5;
 
-    private static final int USE_BYTE_ARRAY = 1;
-    private static final int USE_BYTE_BUFFER = 2;
-
     private static final ArrayList<String> sWords = CollectionUtils.newArrayList();
+    private static final ArrayList<String> sWordsWithVariousCodePoints =
+            CollectionUtils.newArrayList();
     private static final SparseArray<List<Integer>> sEmptyBigrams =
             CollectionUtils.newSparseArray();
     private static final SparseArray<List<Integer>> sStarBigrams = CollectionUtils.newSparseArray();
@@ -71,21 +71,6 @@
             CollectionUtils.newSparseArray();
     private static final HashMap<String, List<String>> sShortcuts = CollectionUtils.newHashMap();
 
-    private static final FormatSpec.FormatOptions VERSION2 = new FormatSpec.FormatOptions(2);
-    private static final FormatSpec.FormatOptions VERSION3_WITHOUT_DYNAMIC_UPDATE =
-            new FormatSpec.FormatOptions(3, false /* supportsDynamicUpdate */);
-    private static final FormatSpec.FormatOptions VERSION3_WITH_DYNAMIC_UPDATE =
-            new FormatSpec.FormatOptions(3, true /* supportsDynamicUpdate */);
-    private static final FormatSpec.FormatOptions VERSION4_WITHOUT_DYNAMIC_UPDATE =
-            new FormatSpec.FormatOptions(4, false /* supportsDynamicUpdate */);
-    private static final FormatSpec.FormatOptions VERSION4_WITH_DYNAMIC_UPDATE =
-            new FormatSpec.FormatOptions(4, true /* supportsDynamicUpdate */);
-    private static final FormatSpec.FormatOptions VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP =
-            new FormatSpec.FormatOptions(4, true /* supportsDynamicUpdate */,
-                    true /* hasTimestamp */);
-
-    private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
-
     public BinaryDictDecoderEncoderTests() {
         this(System.currentTimeMillis(), DEFAULT_MAX_UNIGRAMS);
     }
@@ -95,9 +80,8 @@
         Log.e(TAG, "Testing dictionary: seed is " + seed);
         final Random random = new Random(seed);
         sWords.clear();
-        final int[] codePointSet = CodePointUtils.generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE,
-                random);
-        generateWords(maxUnigrams, random, codePointSet);
+        sWordsWithVariousCodePoints.clear();
+        generateWords(maxUnigrams, random);
 
         for (int i = 0; i < sWords.size(); ++i) {
             sChainBigrams.put(i, new ArrayList<Integer>());
@@ -124,23 +108,22 @@
         }
     }
 
-    private DictEncoder getDictEncoder(final File file, final FormatOptions formatOptions) {
-        if (formatOptions.mVersion == FormatSpec.VERSION4) {
-            return new Ver4DictEncoder(getContext().getCacheDir());
-        } else if (formatOptions.mVersion == 3 || formatOptions.mVersion == 2) {
-            return new Ver3DictEncoder(file);
-        } else {
-            throw new RuntimeException("The format option has a wrong version : "
-                    + formatOptions.mVersion);
-        }
-    }
-
-    private void generateWords(final int number, final Random random, final int[] codePointSet) {
+    private void generateWords(final int number, final Random random) {
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE,
+                random);
         final Set<String> wordSet = CollectionUtils.newHashSet();
         while (wordSet.size() < number) {
             wordSet.add(CodePointUtils.generateWord(random, codePointSet));
         }
         sWords.addAll(wordSet);
+
+        final int[] largeCodePointSet = CodePointUtils.generateCodePointSet(
+                LARGE_CODE_POINT_SET_SIZE, random);
+        wordSet.clear();
+        while (wordSet.size() < number) {
+            wordSet.add(CodePointUtils.generateWord(random, largeCodePointSet));
+        }
+        sWordsWithVariousCodePoints.addAll(wordSet);
     }
 
     /**
@@ -186,7 +169,8 @@
         long now = -1, diff = -1;
 
         try {
-            final DictEncoder dictEncoder = getDictEncoder(file, formatOptions);
+            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions,
+                    getContext().getCacheDir());
 
             now = System.currentTimeMillis();
             // If you need to dump the dict to a textual file, uncomment the line below and the
@@ -241,54 +225,21 @@
     private String outputOptions(final int bufferType,
             final FormatSpec.FormatOptions formatOptions) {
         String result = " : buffer type = "
-                + ((bufferType == USE_BYTE_BUFFER) ? "byte buffer" : "byte array");
+                + ((bufferType == BinaryDictUtils.USE_BYTE_BUFFER) ? "byte buffer" : "byte array");
         result += " : version = " + formatOptions.mVersion;
         return result + ", supportsDynamicUpdate = " + formatOptions.mSupportsDynamicUpdate;
     }
 
-    private DictionaryOptions getDictionaryOptions(final String id, final String version) {
-        final DictionaryOptions options = new DictionaryOptions(new HashMap<String, String>(),
-                false, false);
-        options.mAttributes.put("version", version);
-        options.mAttributes.put("dictionary", id);
-        return options;
-    }
-
-    private File setUpDictionaryFile(final String name, final String version) {
-        File file = null;
-        try {
-            file = new File(getContext().getCacheDir(), name + "." + version
-                    + TEST_DICT_FILE_EXTENSION);
-            file.createNewFile();
-        } catch (IOException e) {
-            // do nothing
-        }
-        assertTrue("Failed to create the dictionary file.", file.exists());
-        return file;
-    }
-
-    private DictDecoder getDictDecoder(final File file, final int bufferType,
-            final FormatOptions formatOptions, final DictionaryOptions dictOptions) {
-        if (formatOptions.mVersion == FormatSpec.VERSION4) {
-            final FileHeader header = new FileHeader(0, dictOptions, formatOptions);
-            return FormatSpec.getDictDecoder(new File(getContext().getCacheDir(),
-                    header.getId() + "." + header.getVersion()), bufferType);
-        } else {
-            return FormatSpec.getDictDecoder(file, bufferType);
-        }
-    }
     // Tests for readDictionaryBinary and writeDictionaryBinary
 
     private long timeReadingAndCheckDict(final File file, final List<String> words,
             final SparseArray<List<Integer>> bigrams,
-            final HashMap<String, List<String>> shortcutMap, final int bufferType,
-            final FormatOptions formatOptions, final DictionaryOptions dictOptions) {
+            final HashMap<String, List<String>> shortcutMap, final int bufferType) {
         long now, diff = -1;
 
         FusionDictionary dict = null;
         try {
-            final DictDecoder dictDecoder = getDictDecoder(file, bufferType, formatOptions,
-                    dictOptions);
+            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, bufferType);
             now = System.currentTimeMillis();
             dict = dictDecoder.readDictionaryBinary(null, false /* deleteDictIfBroken */);
             diff  = System.currentTimeMillis() - now;
@@ -310,17 +261,17 @@
 
         final String dictName = "runReadAndWrite";
         final String dictVersion = Long.toString(System.currentTimeMillis());
-        final File file = setUpDictionaryFile(dictName, dictVersion);
+        final File file = BinaryDictUtils.getDictFile(dictName, dictVersion, formatOptions,
+                getContext().getCacheDir());
 
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
-                getDictionaryOptions(dictName, dictVersion));
+                BinaryDictUtils.makeDictionaryOptions(dictName, dictVersion));
         addUnigrams(words.size(), dict, words, shortcuts);
         addBigrams(dict, words, bigrams);
         checkDictionary(dict, words, bigrams, shortcuts);
 
         final long write = timeWritingDictToFile(file, dict, formatOptions);
-        final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType,
-                formatOptions, dict.mOptions);
+        final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType);
 
         return "PROF: read=" + read + "ms, write=" + write + "ms :" + message
                 + " : " + outputOptions(bufferType, formatOptions);
@@ -340,6 +291,9 @@
                 "chain with shortcuts"));
         results.add(runReadAndWrite(sWords, sStarBigrams, sShortcuts, bufferType, formatOptions,
                 "star with shortcuts"));
+        results.add(runReadAndWrite(sWordsWithVariousCodePoints, sEmptyBigrams,
+                null /* shortcuts */, bufferType, formatOptions,
+                "unigram with various code points"));
     }
 
     // Unit test for CharEncoding.readString and CharEncoding.writeString.
@@ -349,8 +303,7 @@
         final byte[] buffer = new byte[50 * 3];
         final DictBuffer dictBuffer = new ByteArrayDictBuffer(buffer);
         for (final String word : sWords) {
-            Log.d("testReadAndWriteString", "write : " + word);
-            Arrays.fill(buffer, (byte)0);
+            Arrays.fill(buffer, (byte) 0);
             CharEncoding.writeString(buffer, 0, word);
             dictBuffer.position(0);
             final String str = CharEncoding.readString(dictBuffer);
@@ -361,12 +314,18 @@
     public void testReadAndWriteWithByteBuffer() {
         final List<String> results = CollectionUtils.newArrayList();
 
-        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION2);
-        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION2);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -376,12 +335,18 @@
     public void testReadAndWriteWithByteArray() {
         final List<String> results = CollectionUtils.newArrayList();
 
-        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION2);
-        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION2);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -437,8 +402,7 @@
     }
 
     private long timeAndCheckReadUnigramsAndBigramsBinary(final File file, final List<String> words,
-            final SparseArray<List<Integer>> bigrams, final int bufferType,
-            final FormatOptions formatOptions, final DictionaryOptions dictOptions) {
+            final SparseArray<List<Integer>> bigrams, final int bufferType) {
         FileInputStream inStream = null;
 
         final TreeMap<Integer, String> resultWords = CollectionUtils.newTreeMap();
@@ -448,8 +412,7 @@
 
         long now = -1, diff = -1;
         try {
-            final DictDecoder dictDecoder = getDictDecoder(file, bufferType, formatOptions,
-                    dictOptions);
+            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, bufferType);
             now = System.currentTimeMillis();
             dictDecoder.readUnigramsAndBigramsBinary(resultWords, resultFreqs, resultBigrams);
             diff = System.currentTimeMillis() - now;
@@ -476,20 +439,20 @@
             final FormatSpec.FormatOptions formatOptions, final String message) {
         final String dictName = "runReadUnigrams";
         final String dictVersion = Long.toString(System.currentTimeMillis());
-        final File file = setUpDictionaryFile(dictName, dictVersion);
+        final File file = BinaryDictUtils.getDictFile(dictName, dictVersion, formatOptions,
+                getContext().getCacheDir());
 
         // making the dictionary from lists of words.
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
-                getDictionaryOptions(dictName, dictVersion));
+                BinaryDictUtils.makeDictionaryOptions(dictName, dictVersion));
         addUnigrams(words.size(), dict, words, null /* shortcutMap */);
         addBigrams(dict, words, bigrams);
 
         timeWritingDictToFile(file, dict, formatOptions);
 
-        long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams, bufferType,
-                formatOptions, dict.mOptions);
+        long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams, bufferType);
         long fullReading = timeReadingAndCheckDict(file, words, bigrams, null /* shortcutMap */,
-                bufferType, formatOptions, dict.mOptions);
+                bufferType);
 
         return "readDictionaryBinary=" + fullReading + ", readUnigramsAndBigramsBinary=" + wordMap
                 + " : " + message + " : " + outputOptions(bufferType, formatOptions);
@@ -508,13 +471,18 @@
     public void testReadUnigramsAndBigramsBinaryWithByteBuffer() {
         final ArrayList<String> results = CollectionUtils.newArrayList();
 
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION2);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER,
-                VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION2);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -524,13 +492,18 @@
     public void testReadUnigramsAndBigramsBinaryWithByteArray() {
         final ArrayList<String> results = CollectionUtils.newArrayList();
 
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION2);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY,
-                VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION2);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -550,7 +523,7 @@
             return null;
         }
         if (fileHeader == null) return null;
-        return BinaryDictDecoderUtils.getWordAtPosition(dictDecoder, fileHeader.mHeaderSize,
+        return BinaryDictDecoderUtils.getWordAtPosition(dictDecoder, fileHeader.mBodyOffset,
                 address, fileHeader.mFormatOptions).mWord;
     }
 
@@ -578,20 +551,21 @@
             final FormatOptions formatOptions, final String message) {
         final String dictName = "testGetTerminalPosition";
         final String dictVersion = Long.toString(System.currentTimeMillis());
-        final File file = setUpDictionaryFile(dictName, dictVersion);
+        final File file = BinaryDictUtils.getDictFile(dictName, dictVersion, formatOptions,
+                getContext().getCacheDir());
 
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
-                getDictionaryOptions(dictName, dictVersion));
+                BinaryDictUtils.makeDictionaryOptions(dictName, dictVersion));
         addUnigrams(sWords.size(), dict, sWords, null /* shortcutMap */);
         addBigrams(dict, words, bigrams);
         timeWritingDictToFile(file, dict, formatOptions);
 
-        final DictDecoder dictDecoder = getDictDecoder(file, DictDecoder.USE_BYTEARRAY,
-                formatOptions, dict.mOptions);
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, DictDecoder.USE_BYTEARRAY);
         try {
             dictDecoder.openDictBuffer();
         } catch (IOException e) {
-            // ignore
+            Log.e(TAG, "IOException while opening the buffer", e);
+        } catch (UnsupportedFormatException e) {
             Log.e(TAG, "IOException while opening the buffer", e);
         }
         assertTrue("Can't get the buffer", dictDecoder.isDictBufferOpen());
@@ -638,65 +612,63 @@
     public void testGetTerminalPosition() {
         final ArrayList<String> results = CollectionUtils.newArrayList();
 
-        runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION2);
-        runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE);
-        runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE);
-        runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE);
-        runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY, BinaryDictUtils.VERSION2);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
 
-        runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION2);
-        runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE);
-        runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE);
-        runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE);
-        runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER, BinaryDictUtils.VERSION2);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
 
         for (final String result : results) {
             Log.d(TAG, result);
         }
     }
 
-    private void runTestDeleteWord(final FormatOptions formatOptions) {
+    private void runTestDeleteWord(final FormatOptions formatOptions)
+            throws IOException, UnsupportedFormatException {
         final String dictName = "testDeleteWord";
         final String dictVersion = Long.toString(System.currentTimeMillis());
-        final File file = setUpDictionaryFile(dictName, dictVersion);
+        final File file = BinaryDictUtils.getDictFile(dictName, dictVersion, formatOptions,
+                getContext().getCacheDir());
 
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
-                new FusionDictionary.DictionaryOptions(
-                        new HashMap<String, String>(), false, false));
+                BinaryDictUtils.makeDictionaryOptions(dictName, dictVersion));
         addUnigrams(sWords.size(), dict, sWords, null /* shortcutMap */);
         timeWritingDictToFile(file, dict, formatOptions);
 
-        final DictUpdater dictUpdater;
-        if (formatOptions.mVersion == 3) {
-            dictUpdater = new Ver3DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER);
-        } else if (formatOptions.mVersion == 4) {
-            dictUpdater = new Ver4DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER);
-        } else {
-            throw new RuntimeException("DictUpdater for version " + formatOptions.mVersion
-                    + " doesn't exist.");
-        }
+        final DictUpdater dictUpdater = BinaryDictUtils.getDictUpdater(file, formatOptions);
+        MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD,
+                dictUpdater.getTerminalPosition(sWords.get(0)));
+        dictUpdater.deleteWord(sWords.get(0));
+        assertEquals(FormatSpec.NOT_VALID_WORD,
+                dictUpdater.getTerminalPosition(sWords.get(0)));
 
-        try {
-            MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD,
-                    dictUpdater.getTerminalPosition(sWords.get(0)));
-            dictUpdater.deleteWord(sWords.get(0));
-            assertEquals(FormatSpec.NOT_VALID_WORD,
-                    dictUpdater.getTerminalPosition(sWords.get(0)));
-
-            MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD,
-                    dictUpdater.getTerminalPosition(sWords.get(5)));
-            dictUpdater.deleteWord(sWords.get(5));
-            assertEquals(FormatSpec.NOT_VALID_WORD,
-                    dictUpdater.getTerminalPosition(sWords.get(5)));
-        } catch (IOException e) {
-        } catch (UnsupportedFormatException e) {
-        }
+        MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD,
+                dictUpdater.getTerminalPosition(sWords.get(5)));
+        dictUpdater.deleteWord(sWords.get(5));
+        assertEquals(FormatSpec.NOT_VALID_WORD,
+                dictUpdater.getTerminalPosition(sWords.get(5)));
     }
 
-    public void testDeleteWord() {
-        runTestDeleteWord(VERSION3_WITH_DYNAMIC_UPDATE);
-        runTestDeleteWord(VERSION4_WITH_DYNAMIC_UPDATE);
+    public void testDeleteWord() throws IOException, UnsupportedFormatException {
+        runTestDeleteWord(BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runTestDeleteWord(BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
index afe5adb..3089194 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
@@ -23,6 +23,7 @@
 
 import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 import com.android.inputmethod.latin.utils.CollectionUtils;
@@ -30,24 +31,16 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.Random;
 
 @LargeTest
 public class BinaryDictIOUtilsTests extends AndroidTestCase {
     private static final String TAG = BinaryDictIOUtilsTests.class.getSimpleName();
-    private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
-            new FormatSpec.FormatOptions(3, true);
 
     private static final ArrayList<String> sWords = CollectionUtils.newArrayList();
     public static final int DEFAULT_MAX_UNIGRAMS = 1500;
     private final int mMaxUnigrams;
 
-    private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
-
-    private static final int VERSION3 = 3;
-    private static final int VERSION4 = 4;
-
     private static final String[] CHARACTERS = {
         "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
         "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
@@ -141,7 +134,7 @@
         int position = FormatSpec.NOT_VALID_WORD;
 
         try {
-            final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file,
+            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file,
                     DictDecoder.USE_READONLY_BYTEBUFFER);
             position = dictDecoder.getTerminalPosition(word);
         } catch (IOException e) {
@@ -159,7 +152,7 @@
      * @throws IOException
      * @throws UnsupportedFormatException
      */
-    private static PtNodeInfo findWordByBinaryDictReader(final DictDecoder dictDecoder,
+    private static PtNodeInfo findWordByDictDecoder(final DictDecoder dictDecoder,
             final String word) throws IOException, UnsupportedFormatException {
         int position = dictDecoder.getTerminalPosition(word);
         if (position != FormatSpec.NOT_VALID_WORD) {
@@ -176,7 +169,7 @@
         PtNodeInfo info = null;
         try {
             dictDecoder.openDictBuffer();
-            info = findWordByBinaryDictReader(dictDecoder, word);
+            info = findWordByDictDecoder(dictDecoder, word);
         } catch (IOException e) {
         } catch (UnsupportedFormatException e) {
         }
@@ -186,16 +179,10 @@
     // return amount of time to insert a word
     private long insertAndCheckWord(final File file, final String word, final int frequency,
             final boolean exist, final ArrayList<WeightedString> bigrams,
-            final ArrayList<WeightedString> shortcuts, final int formatVersion) {
+            final ArrayList<WeightedString> shortcuts, final FormatOptions formatOptions) {
         long amountOfTime = -1;
         try {
-            final DictUpdater dictUpdater;
-            if (formatVersion == VERSION3) {
-                dictUpdater = new Ver3DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER);
-            } else {
-                throw new RuntimeException("DictUpdater for version " + formatVersion + " doesn't"
-                        + " exist.");
-            }
+            final DictUpdater dictUpdater = BinaryDictUtils.getDictUpdater(file, formatOptions);
 
             if (!exist) {
                 assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word));
@@ -212,18 +199,14 @@
         return amountOfTime;
     }
 
-    private void deleteWord(final File file, final String word, final int formatVersion) {
+    private void deleteWord(final File file, final String word, final FormatOptions formatOptions) {
         try {
-            final DictUpdater dictUpdater;
-            if (formatVersion == VERSION3) {
-                dictUpdater = new Ver3DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER);
-            } else {
-                throw new RuntimeException("DictUpdater for version " + formatVersion + " doesn't"
-                        + " exist.");
-            }
+            final DictUpdater dictUpdater = BinaryDictUtils.getDictUpdater(file, formatOptions);
             dictUpdater.deleteWord(word);
         } catch (IOException e) {
+            Log.e(TAG, "Raised an IOException while deleting a word", e);
         } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "Raised an UnsupportedFormatException while deleting a word", e);
         }
     }
 
@@ -233,7 +216,7 @@
             final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file);
             final FileHeader fileHeader = dictDecoder.readHeader();
             assertEquals(word,
-                    BinaryDictDecoderUtils.getWordAtPosition(dictDecoder, fileHeader.mHeaderSize,
+                    BinaryDictDecoderUtils.getWordAtPosition(dictDecoder, fileHeader.mBodyOffset,
                             position, fileHeader.mFormatOptions).mWord);
         } catch (IOException e) {
             Log.e(TAG, "Raised an IOException while looking up a word", e);
@@ -242,23 +225,21 @@
         }
     }
 
-    private void runTestInsertWord(final int formatVersion) {
-        File file = null;
-        try {
-            file = File.createTempFile("testInsertWord", TEST_DICT_FILE_EXTENSION,
-                    getContext().getCacheDir());
-        } catch (IOException e) {
-            fail("IOException while creating temporary file: " + e);
-        }
+    private void runTestInsertWord(final FormatOptions formatOptions) {
+        final String testName = "testInsertWord";
+        final String version = Long.toString(System.currentTimeMillis());
+        final File file = BinaryDictUtils.getDictFile(testName, version, formatOptions,
+                getContext().getCacheDir());
 
         // set an initial dictionary.
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
-                new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false));
+                BinaryDictUtils.makeDictionaryOptions(testName, version));
         dict.add("abcd", 10, null, false);
 
         try {
-            final DictEncoder dictEncoder = new Ver3DictEncoder(file);
-            dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
+            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions,
+                    getContext().getCacheDir());
+            dictEncoder.writeDictionary(dict, formatOptions);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         } catch (UnsupportedFormatException e) {
@@ -266,54 +247,68 @@
         }
 
         MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "abcd"));
-        insertAndCheckWord(file, "abcde", 10, false, null, null, formatVersion);
+        insertAndCheckWord(file, "abcde", 10, false, null, null, formatOptions);
+        checkReverseLookup(file, "abcde", getWordPosition(file, "abcde"));
 
-        insertAndCheckWord(file, "abcdefghijklmn", 10, false, null, null, formatVersion);
+        insertAndCheckWord(file, "abcdefghijklmn", 10, false, null, null, formatOptions);
         checkReverseLookup(file, "abcdefghijklmn", getWordPosition(file, "abcdefghijklmn"));
 
-        insertAndCheckWord(file, "abcdabcd", 10, false, null, null, formatVersion);
+        insertAndCheckWord(file, "abcdabcd", 10, false, null, null, formatOptions);
         checkReverseLookup(file, "abcdabcd", getWordPosition(file, "abcdabcd"));
 
         // update the existing word.
-        insertAndCheckWord(file, "abcdabcd", 15, true, null, null, formatVersion);
+        insertAndCheckWord(file, "abcdabcd", 15, true, null, null, formatOptions);
+        checkReverseLookup(file, "abcdabcd", getWordPosition(file, "abcdabcd"));
 
-        // split 1
-        insertAndCheckWord(file, "ab", 20, false, null, null, formatVersion);
+        // Testing splitOnly
+        insertAndCheckWord(file, "ab", 20, false, null, null, formatOptions);
+        checkReverseLookup(file, "ab", getWordPosition(file, "ab"));
+        checkReverseLookup(file, "abcdabcd", getWordPosition(file, "abcdabcd"));
+        checkReverseLookup(file, "abcde", getWordPosition(file, "abcde"));
+        checkReverseLookup(file, "abcdefghijklmn", getWordPosition(file, "abcdefghijklmn"));
 
-        // split 2
-        insertAndCheckWord(file, "ami", 30, false, null, null, formatVersion);
+        // Testing splitAndBranch
+        insertAndCheckWord(file, "ami", 30, false, null, null, formatOptions);
+        checkReverseLookup(file, "ami", getWordPosition(file, "ami"));
+        checkReverseLookup(file, "ab", getWordPosition(file, "ab"));
+        checkReverseLookup(file, "abcdabcd", getWordPosition(file, "abcdabcd"));
+        checkReverseLookup(file, "abcde", getWordPosition(file, "abcde"));
+        checkReverseLookup(file, "abcdefghijklmn", getWordPosition(file, "abcdefghijklmn"));
+        checkReverseLookup(file, "ami", getWordPosition(file, "ami"));
 
-        deleteWord(file, "ami", formatVersion);
+        insertAndCheckWord(file, "abcdefzzzz", 40, false, null, null, formatOptions);
+        checkReverseLookup(file, "abcdefzzzz", getWordPosition(file, "abcdefzzzz"));
+
+        deleteWord(file, "ami", formatOptions);
         assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "ami"));
 
-        insertAndCheckWord(file, "abcdabfg", 30, false, null, null, formatVersion);
+        insertAndCheckWord(file, "abcdabfg", 30, false, null, null, formatOptions);
 
-        deleteWord(file, "abcd", formatVersion);
+        deleteWord(file, "abcd", formatOptions);
         assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "abcd"));
     }
 
     public void testInsertWord() {
-        runTestInsertWord(VERSION3);
+        runTestInsertWord(BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runTestInsertWord(BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
     }
 
-    private void runTestInsertWordWithBigrams(final int formatVersion) {
-        File file = null;
-        try {
-            file = File.createTempFile("testInsertWordWithBigrams", TEST_DICT_FILE_EXTENSION,
-                    getContext().getCacheDir());
-        } catch (IOException e) {
-            fail("IOException while creating temporary file: " + e);
-        }
+    private void runTestInsertWordWithBigrams(final FormatOptions formatOptions) {
+        final String testName = "testInsertWordWithBigrams";
+        final String version = Long.toString(System.currentTimeMillis());
+        File file = BinaryDictUtils.getDictFile(testName, version, formatOptions,
+                getContext().getCacheDir());
 
         // set an initial dictionary.
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
-                new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false));
+                BinaryDictUtils.makeDictionaryOptions(testName, version));
         dict.add("abcd", 10, null, false);
         dict.add("efgh", 15, null, false);
 
         try {
-            final DictEncoder dictEncoder = new Ver3DictEncoder(file); 
-            dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
+            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions,
+                    getContext().getCacheDir());
+            dictEncoder.writeDictionary(dict, formatOptions);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         } catch (UnsupportedFormatException e) {
@@ -323,8 +318,8 @@
         final ArrayList<WeightedString> banana = new ArrayList<WeightedString>();
         banana.add(new WeightedString("banana", 10));
 
-        insertAndCheckWord(file, "banana", 0, false, null, null, formatVersion);
-        insertAndCheckWord(file, "recursive", 60, true, banana, null, formatVersion);
+        insertAndCheckWord(file, "banana", 0, false, null, null, formatOptions);
+        insertAndCheckWord(file, "recursive", 60, true, banana, null, formatOptions);
 
         final PtNodeInfo info = findWordFromFile(file, "recursive");
         int bananaPos = getWordPosition(file, "banana");
@@ -334,27 +329,25 @@
     }
 
     public void testInsertWordWithBigrams() {
-        runTestInsertWordWithBigrams(VERSION3);
+        runTestInsertWordWithBigrams(BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runTestInsertWordWithBigrams(BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
     }
 
-    private void runTestRandomWords(final int formatVersion) {
-        File file = null;
-        try {
-            file = File.createTempFile("testRandomWord", TEST_DICT_FILE_EXTENSION,
-                    getContext().getCacheDir());
-        } catch (IOException e) {
-        }
-        assertNotNull(file);
+    private void runTestRandomWords(final FormatOptions formatOptions) {
+        final String testName = "testRandomWord";
+        final String version = Long.toString(System.currentTimeMillis());
+        final File file = BinaryDictUtils.getDictFile(testName, version, formatOptions,
+                getContext().getCacheDir());
 
         // set an initial dictionary.
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
-                new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
-                        false));
+                BinaryDictUtils.makeDictionaryOptions(testName, version));
         dict.add("initial", 10, null, false);
 
         try {
-            final DictEncoder dictEncoder = new Ver3DictEncoder(file);
-            dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
+            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions,
+                    getContext().getCacheDir());
+            dictEncoder.writeDictionary(dict, formatOptions);
         } catch (IOException e) {
             assertTrue(false);
         } catch (UnsupportedFormatException e) {
@@ -366,7 +359,7 @@
         int cnt = 0;
         for (final String word : sWords) {
             final long diff = insertAndCheckWord(file, word,
-                    cnt % FormatSpec.MAX_TERMINAL_FREQUENCY, false, null, null, formatVersion);
+                    cnt % FormatSpec.MAX_TERMINAL_FREQUENCY, false, null, null, formatOptions);
             maxTimeToInsert = Math.max(maxTimeToInsert, diff);
             minTimeToInsert = Math.min(minTimeToInsert, diff);
             sum += diff;
@@ -377,13 +370,14 @@
             MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word));
         }
 
-        Log.d(TAG, "Test version " + formatVersion);
+        Log.d(TAG, "Test version " + formatOptions.mVersion);
         Log.d(TAG, "max = " + ((double)maxTimeToInsert/1000000) + " ms.");
         Log.d(TAG, "min = " + ((double)minTimeToInsert/1000000) + " ms.");
         Log.d(TAG, "avg = " + ((double)sum/mMaxUnigrams/1000000) + " ms.");
     }
 
     public void testRandomWords() {
-        runTestRandomWords(VERSION3);
+        runTestRandomWords(BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runTestRandomWords(BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java
new file mode 100644
index 0000000..ad17a71
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java
@@ -0,0 +1,92 @@
+/*
+ * 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.makedict;
+
+import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
+
+import java.io.File;
+import java.util.HashMap;
+
+public class BinaryDictUtils {
+    public static final int USE_BYTE_ARRAY = 1;
+    public static final int USE_BYTE_BUFFER = 2;
+
+    public static final String TEST_DICT_FILE_EXTENSION = ".testDict";
+
+    public static final FormatSpec.FormatOptions VERSION2 =
+            new FormatSpec.FormatOptions(FormatSpec.VERSION2);
+    public static final FormatSpec.FormatOptions VERSION3_WITHOUT_DYNAMIC_UPDATE =
+            new FormatSpec.FormatOptions(FormatSpec.VERSION3, false /* supportsDynamicUpdate */);
+    public static final FormatSpec.FormatOptions VERSION3_WITH_DYNAMIC_UPDATE =
+            new FormatSpec.FormatOptions(FormatSpec.VERSION3, true /* supportsDynamicUpdate */);
+    public static final FormatSpec.FormatOptions VERSION4_WITHOUT_DYNAMIC_UPDATE =
+            new FormatSpec.FormatOptions(FormatSpec.VERSION4, false /* supportsDynamicUpdate */);
+    public static final FormatSpec.FormatOptions VERSION4_WITH_DYNAMIC_UPDATE =
+            new FormatSpec.FormatOptions(FormatSpec.VERSION4, true /* supportsDynamicUpdate */);
+    public static final FormatSpec.FormatOptions VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP =
+            new FormatSpec.FormatOptions(FormatSpec.VERSION4, true /* supportsDynamicUpdate */,
+                    true /* hasTimestamp */);
+
+    public static DictionaryOptions makeDictionaryOptions(final String id, final String version) {
+        final DictionaryOptions options = new DictionaryOptions(new HashMap<String, String>(),
+                false /* germanUmlautProcessing */, false /* frenchLigatureProcessing */);
+        options.mAttributes.put(FileHeader.DICTIONARY_LOCALE_ATTRIBUTE, "en_US");
+        options.mAttributes.put(FileHeader.DICTIONARY_ID_ATTRIBUTE, id);
+        options.mAttributes.put(FileHeader.DICTIONARY_VERSION_ATTRIBUTE, version);
+        return options;
+    }
+
+    public static File getDictFile(final String name, final String version,
+            final FormatOptions formatOptions, final File directory) {
+        if (formatOptions.mVersion == FormatSpec.VERSION2
+                || formatOptions.mVersion == FormatSpec.VERSION3) {
+            return new File(directory, name + "." + version + TEST_DICT_FILE_EXTENSION);
+        } else if (formatOptions.mVersion == FormatSpec.VERSION4) {
+            return new File(directory, name + "." + version);
+        } else {
+            throw new RuntimeException("the format option has a wrong version : "
+                    + formatOptions.mVersion);
+        }
+    }
+
+    public static DictEncoder getDictEncoder(final File file, final FormatOptions formatOptions,
+            final File cacheDir) {
+        if (formatOptions.mVersion == FormatSpec.VERSION4) {
+            return new Ver4DictEncoder(cacheDir);
+        } else if (formatOptions.mVersion == FormatSpec.VERSION3
+                || formatOptions.mVersion == FormatSpec.VERSION2) {
+            return new Ver3DictEncoder(file);
+        } else {
+            throw new RuntimeException("The format option has a wrong version : "
+                    + formatOptions.mVersion);
+        }
+    }
+
+    public static DictUpdater getDictUpdater(final File file, final FormatOptions formatOptions)
+            throws UnsupportedFormatException {
+        if (formatOptions.mVersion == FormatSpec.VERSION4) {
+            return new Ver4DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER);
+        } else if (formatOptions.mVersion == FormatSpec.VERSION3) {
+            return new Ver3DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER);
+        } else {
+            throw new UnsupportedFormatException("The format option has a wrong version : "
+                    + formatOptions.mVersion);
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
index 7c1decb..17423a7 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -16,8 +16,6 @@
 
 package com.android.inputmethod.latin.personalization;
 
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
@@ -28,6 +26,7 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
@@ -38,21 +37,12 @@
 @LargeTest
 public class UserHistoryDictionaryTests extends AndroidTestCase {
     private static final String TAG = UserHistoryDictionaryTests.class.getSimpleName();
-    private SharedPreferences mPrefs;
 
     private static final String[] CHARACTERS = {
         "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
         "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
     };
 
-    private static final int MIN_USER_HISTORY_DICTIONARY_FILE_SIZE = 1000;
-    private static final int WAIT_TERMINATING_IN_MILLISECONDS = 100;
-
-    @Override
-    public void setUp() {
-        mPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
-    }
-
     /**
      * Generates a random word.
      */
@@ -78,7 +68,8 @@
     private void addToDict(final UserHistoryDictionary dict, final List<String> words) {
         String prevWord = null;
         for (String word : words) {
-            dict.addToDictionary(prevWord, word, true);
+            dict.addToDictionary(prevWord, word, true,
+                    (int)TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
             prevWord = word;
         }
     }
@@ -92,14 +83,11 @@
         final List<String> words = generateWords(numberOfWords, random);
         final UserHistoryDictionary dict =
                 PersonalizationHelper.getUserHistoryDictionary(getContext(),
-                        testFilenameSuffix /* locale */, mPrefs);
+                        new Locale(testFilenameSuffix));
         // Add random words to the user history dictionary.
         addToDict(dict, words);
         if (checkContents) {
-            try {
-                Thread.sleep(TimeUnit.MILLISECONDS.convert(5L, TimeUnit.SECONDS));
-            } catch (InterruptedException e) {
-            }
+            dict.waitAllTasksForTests();
             for (int i = 0; i < numberOfWords; ++i) {
                 final String word = words.get(i);
                 assertTrue(dict.isInDictionaryForTests(word));
@@ -116,7 +104,7 @@
     private void clearHistory(final String testFilenameSuffix) {
         final UserHistoryDictionary dict =
                 PersonalizationHelper.getUserHistoryDictionary(getContext(),
-                        testFilenameSuffix /* locale */, mPrefs);
+                        new Locale(testFilenameSuffix));
         dict.clearAndFlushDictionary();
         dict.close();
     }
@@ -126,23 +114,16 @@
      * @param testFilenameSuffix file name suffix used for testing.
      */
     private void waitForWriting(final String testFilenameSuffix) {
-        try {
-            final UserHistoryDictionary dict =
-                    PersonalizationHelper.getUserHistoryDictionary(getContext(),
-                            testFilenameSuffix, mPrefs);
-            dict.shutdownExecutorForTests();
-            while (!dict.isTerminatedForTests()) {
-                Thread.sleep(WAIT_TERMINATING_IN_MILLISECONDS);
-            }
-        } catch (InterruptedException e) {
-            Log.d(TAG, "InterruptedException: ", e);
-        }
+        final UserHistoryDictionary dict =
+                PersonalizationHelper.getUserHistoryDictionary(getContext(),
+                        new Locale(testFilenameSuffix));
+        dict.waitAllTasksForTests();
     }
 
     public void testRandomWords() {
         Log.d(TAG, "This test can be used for profiling.");
         Log.d(TAG, "Usage: please set UserHistoryDictionary.PROFILE_SAVE_RESTORE to true.");
-        final String testFilenameSuffix = "testRandomWords" + System.currentTimeMillis();
+        final String testFilenameSuffix = "test_random_words" + System.currentTimeMillis();
         final String fileName = UserHistoryDictionary.NAME + "." + testFilenameSuffix
                 + ExpandableBinaryDictionary.DICT_FILE_EXTENSION;
 
@@ -159,7 +140,6 @@
             final File dictFile = new File(getContext().getFilesDir(), fileName);
             if (dictFile != null) {
                 assertTrue(dictFile.exists());
-                assertTrue(dictFile.length() >= MIN_USER_HISTORY_DICTIONARY_FILE_SIZE);
                 dictFile.delete();
             }
         }
@@ -177,9 +157,9 @@
 
             // Create filename suffixes for this test.
             for (int i = 0; i < numberOfLanguages; i++) {
-                testFilenameSuffixes[i] = "testSwitchingLanguages" + i;
-                final String fileName = UserHistoryDictionary.NAME + "." +
-                        testFilenameSuffixes[i] + ExpandableBinaryDictionary.DICT_FILE_EXTENSION;
+                testFilenameSuffixes[i] = "test_switching_languages" + i;
+                final String fileName = UserHistoryDictionary.NAME + "." + testFilenameSuffixes[i]
+                        + ExpandableBinaryDictionary.DICT_FILE_EXTENSION;
                 dictFiles[i] = new File(getContext().getFilesDir(), fileName);
                 clearHistory(testFilenameSuffixes[i]);
             }
@@ -205,7 +185,6 @@
             for (final File file : dictFiles) {
                 if (file != null) {
                     assertTrue(file.exists());
-                    assertTrue(file.length() >= MIN_USER_HISTORY_DICTIONARY_FILE_SIZE);
                     file.delete();
                 }
             }
@@ -213,10 +192,8 @@
     }
 
     public void testAddManyWords() {
-        final String testFilenameSuffix = "testRandomWords" + System.currentTimeMillis();
-        final int numberOfWords =
-                ExpandableBinaryDictionary.ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE ?
-                        10000 : 1000;
+        final String testFilenameSuffix = "test_random_words" + System.currentTimeMillis();
+        final int numberOfWords = 10000;
         final Random random = new Random(123456);
         clearHistory(testFilenameSuffix);
         try {
@@ -230,7 +207,6 @@
             final File dictFile = new File(getContext().getFilesDir(), fileName);
             if (dictFile != null) {
                 assertTrue(dictFile.exists());
-                assertTrue(dictFile.length() >= MIN_USER_HISTORY_DICTIONARY_FILE_SIZE);
                 dictFile.delete();
             }
         }
diff --git a/tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
similarity index 90%
rename from tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java
rename to tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
index 4e396a1..2123e84 100644
--- a/tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
@@ -26,7 +26,7 @@
 import java.util.Locale;
 
 @SmallTest
-public class StringUtilsTests extends AndroidTestCase {
+public class StringAndJsonUtilsTests extends AndroidTestCase {
     public void testContainsInArray() {
         assertFalse("empty array", StringUtils.containsInArray("key", new String[0]));
         assertFalse("not in 1 element", StringUtils.containsInArray("key", new String[] {
@@ -44,7 +44,7 @@
         }));
     }
 
-    public void testContainsInExtraValues() {
+    public void testContainsInCommaSplittableText() {
         assertFalse("null", StringUtils.containsInCommaSplittableText("key", null));
         assertFalse("empty", StringUtils.containsInCommaSplittableText("key", ""));
         assertFalse("not in 1 element",
@@ -56,7 +56,28 @@
         assertTrue("in 2 elements", StringUtils.containsInCommaSplittableText("key", "key1,key"));
     }
 
-    public void testAppendToExtraValuesIfNotExists() {
+    public void testJoinCommaSplittableText() {
+        assertEquals("2 nulls", "",
+                StringUtils.joinCommaSplittableText(null, null));
+        assertEquals("null and empty", "",
+                StringUtils.joinCommaSplittableText(null, ""));
+        assertEquals("empty and null", "",
+                StringUtils.joinCommaSplittableText("", null));
+        assertEquals("2 empties", "",
+                StringUtils.joinCommaSplittableText("", ""));
+        assertEquals("text and null", "text",
+                StringUtils.joinCommaSplittableText("text", null));
+        assertEquals("text and empty", "text",
+                StringUtils.joinCommaSplittableText("text", ""));
+        assertEquals("null and text", "text",
+                StringUtils.joinCommaSplittableText(null, "text"));
+        assertEquals("empty and text", "text",
+                StringUtils.joinCommaSplittableText("", "text"));
+        assertEquals("2 texts", "text1,text2",
+                StringUtils.joinCommaSplittableText("text1", "text2"));
+    }
+
+    public void testAppendToCommaSplittableTextIfNotExists() {
         assertEquals("null", "key",
                 StringUtils.appendToCommaSplittableTextIfNotExists("key", null));
         assertEquals("empty", "key",
@@ -77,7 +98,7 @@
                 StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key,key3"));
     }
 
-    public void testRemoveFromExtraValuesIfExists() {
+    public void testRemoveFromCommaSplittableTextIfExists() {
         assertEquals("null", "", StringUtils.removeFromCommaSplittableTextIfExists("key", null));
         assertEquals("empty", "", StringUtils.removeFromCommaSplittableTextIfExists("key", ""));
 
@@ -271,11 +292,11 @@
         assertTrue(bytesStr.equals(bytesStr2));
     }
 
-    public void testJsonStringUtils() {
+    public void testJsonUtils() {
         final Object[] objs = new Object[] { 1, "aaa", "bbb", 3 };
         final List<Object> objArray = Arrays.asList(objs);
-        final String str = StringUtils.listToJsonStr(objArray);
-        final List<Object> newObjArray = StringUtils.jsonStrToList(str);
+        final String str = JsonUtils.listToJsonStr(objArray);
+        final List<Object> newObjArray = JsonUtils.jsonStrToList(str);
         for (int i = 0; i < objs.length; ++i) {
             assertEquals(objs[i], newObjArray.get(i));
         }
diff --git a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
index 856b2db..ccdd567 100644
--- a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
@@ -41,7 +41,9 @@
     InputMethodSubtype ES_US;
     InputMethodSubtype FR;
     InputMethodSubtype FR_CA;
+    InputMethodSubtype FR_CH;
     InputMethodSubtype DE;
+    InputMethodSubtype DE_CH;
     InputMethodSubtype ZZ;
     InputMethodSubtype DE_QWERTY;
     InputMethodSubtype FR_QWERTZ;
@@ -70,8 +72,12 @@
                 Locale.FRENCH.toString(), "azerty");
         FR_CA = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 Locale.CANADA_FRENCH.toString(), "qwerty");
+        FR_CH = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "fr_CH", "swiss");
         DE = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 Locale.GERMAN.toString(), "qwertz");
+        DE_CH = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "de_CH", "swiss");
         ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 SubtypeLocaleUtils.NO_LANGUAGE, "qwerty");
         DE_QWERTY = AdditionalSubtypeUtils.createAdditionalSubtype(
@@ -112,7 +118,9 @@
         assertEquals("es_US", "spanish", SubtypeLocaleUtils.getKeyboardLayoutSetName(ES_US));
         assertEquals("fr   ", "azerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR));
         assertEquals("fr_CA", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_CA));
+        assertEquals("fr_CH", "swiss", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_CH));
         assertEquals("de   ", "qwertz", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE));
+        assertEquals("de_CH", "swiss", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE_CH));
         assertEquals("zz   ", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(ZZ));
     }
 
@@ -125,7 +133,9 @@
     //  es_US spanish F  Spanish (US)            exception
     //  fr    azerty  F  French
     //  fr_CA qwerty  F  French (Canada)
+    //  fr_CH swiss   F  French (Switzerland)
     //  de    qwertz  F  German
+    //  de_CH swiss   F  German (Switzerland)
     //  zz    qwerty  F  Alphabet (QWERTY)
     //  fr    qwertz  T  French (QWERTZ)
     //  de    qwerty  T  German (QWERTY)
@@ -148,8 +158,12 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR));
                 assertEquals("fr_CA", "French (Canada)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CA));
+                assertEquals("fr_CH", "French (Switzerland)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CH));
                 assertEquals("de   ", "German",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE));
+                assertEquals("de_CH", "German (Switzerland)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_CH));
                 assertEquals("zz   ", "Alphabet (QWERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ));
                 return null;
@@ -189,7 +203,9 @@
     //  es_US spanish F  Espagnol (États-Unis)            exception
     //  fr    azerty  F  Français
     //  fr_CA qwerty  F  Français (Canada)
+    //  fr_CH swiss   F  Français (Suisse)
     //  de    qwertz  F  Allemand
+    //  de_CH swiss   F  Allemand (Suisse)
     //  zz    qwerty  F  Aucune langue (QWERTY)
     //  fr    qwertz  T  Français (QWERTZ)
     //  de    qwerty  T  Allemand (QWERTY)
@@ -212,8 +228,12 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR));
                 assertEquals("fr_CA", "Français (Canada)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CA));
+                assertEquals("fr_CH", "Français (Suisse)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CH));
                 assertEquals("de   ", "Allemand",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE));
+                assertEquals("de_CH", "Allemand (Suisse)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_CH));
                 assertEquals("zz   ", "Alphabet latin (QWERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ));
                 return null;
@@ -300,7 +320,9 @@
     //  es_US spanish F  Es  Español   Español (EE.UU.)       exception
     //  fr    azerty  F  Fr  Français  Français
     //  fr_CA qwerty  F  Fr  Français  Français (Canada)
+    //  fr_CH swiss   F  Fr  Français  Français (Suisse)
     //  de    qwertz  F  De  Deutsch   Deutsch
+    //  de_CH swiss   F  De  Deutsch   Deutsch (Schweiz)
     //  zz    qwerty  F      QWERTY    QWERTY
     //  fr    qwertz  T  Fr  Français  Français
     //  de    qwerty  T  De  Deutsch   Deutsch
@@ -317,7 +339,11 @@
             assertEquals("fr   ", "Français",     SubtypeLocaleUtils.getFullDisplayName(FR));
             assertEquals("fr_CA", "Français (Canada)",
                     SubtypeLocaleUtils.getFullDisplayName(FR_CA));
+            assertEquals("fr_CH", "Français (Suisse)",
+                    SubtypeLocaleUtils.getFullDisplayName(FR_CH));
             assertEquals("de   ", "Deutsch",      SubtypeLocaleUtils.getFullDisplayName(DE));
+            assertEquals("de_CH", "Deutsch (Schweiz)",
+                    SubtypeLocaleUtils.getFullDisplayName(DE_CH));
             assertEquals("zz   ", "QWERTY",       SubtypeLocaleUtils.getFullDisplayName(ZZ));
 
             assertEquals("en_US", "English",  SubtypeLocaleUtils.getMiddleDisplayName(EN_US));
@@ -325,7 +351,9 @@
             assertEquals("es_US", "Español",  SubtypeLocaleUtils.getMiddleDisplayName(ES_US));
             assertEquals("fr   ", "Français", SubtypeLocaleUtils.getMiddleDisplayName(FR));
             assertEquals("fr_CA", "Français", SubtypeLocaleUtils.getMiddleDisplayName(FR_CA));
+            assertEquals("fr_CH", "Français", SubtypeLocaleUtils.getMiddleDisplayName(FR_CH));
             assertEquals("de   ", "Deutsch",  SubtypeLocaleUtils.getMiddleDisplayName(DE));
+            assertEquals("de_CH", "Deutsch",  SubtypeLocaleUtils.getMiddleDisplayName(DE_CH));
             assertEquals("zz   ", "QWERTY",   SubtypeLocaleUtils.getMiddleDisplayName(ZZ));
 
             assertEquals("en_US", "En", SubtypeLocaleUtils.getShortDisplayName(EN_US));
@@ -333,7 +361,9 @@
             assertEquals("es_US", "Es", SubtypeLocaleUtils.getShortDisplayName(ES_US));
             assertEquals("fr   ", "Fr", SubtypeLocaleUtils.getShortDisplayName(FR));
             assertEquals("fr_CA", "Fr", SubtypeLocaleUtils.getShortDisplayName(FR_CA));
+            assertEquals("fr_CH", "Fr", SubtypeLocaleUtils.getShortDisplayName(FR_CH));
             assertEquals("de   ", "De", SubtypeLocaleUtils.getShortDisplayName(DE));
+            assertEquals("de_CH", "De", SubtypeLocaleUtils.getShortDisplayName(DE_CH));
             assertEquals("zz   ", "",   SubtypeLocaleUtils.getShortDisplayName(ZZ));
             return null;
         }
diff --git a/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
index 1944fd3..fc921b4 100644
--- a/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
@@ -24,8 +24,10 @@
 import com.android.inputmethod.latin.makedict.DictDecoder;
 import com.android.inputmethod.latin.makedict.DictEncoder;
 import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
 import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
 import com.android.inputmethod.latin.personalization.UserHistoryDictionaryBigramList;
@@ -52,6 +54,12 @@
     private static final ArrayList<String> NOT_HAVE_BIGRAM = new ArrayList<String>();
     private static final FormatSpec.FormatOptions FORMAT_OPTIONS = new FormatSpec.FormatOptions(2);
     private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
+    private static final HashMap<String, String> HEADER_OPTIONS = new HashMap<String, String>();
+    static {
+        HEADER_OPTIONS.put(FileHeader.DICTIONARY_LOCALE_ATTRIBUTE, "en_US");
+        HEADER_OPTIONS.put(FileHeader.DICTIONARY_ID_ATTRIBUTE, "test");
+        HEADER_OPTIONS.put(FileHeader.DICTIONARY_VERSION_ATTRIBUTE, "1000");
+    }
 
     /**
      * Return same frequency for all words and bigrams
@@ -139,18 +147,14 @@
     private void writeDictToFile(final File file,
             final UserHistoryDictionaryBigramList bigramList) {
         final DictEncoder dictEncoder = new Ver3DictEncoder(file);
-        UserHistoryDictIOUtils.writeDictionary(dictEncoder, this, bigramList, FORMAT_OPTIONS);
+        UserHistoryDictIOUtils.writeDictionary(dictEncoder, this, bigramList, FORMAT_OPTIONS,
+                HEADER_OPTIONS);
     }
 
-    private void readDictFromFile(final File file, final OnAddWordListener listener) {
+    private void readDictFromFile(final File file, final OnAddWordListener listener)
+            throws IOException, FileNotFoundException, UnsupportedFormatException {
         final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, DictDecoder.USE_BYTEARRAY);
-        try {
-            dictDecoder.openDictBuffer();
-        } catch (FileNotFoundException e) {
-            Log.e(TAG, "file not found", e);
-        } catch (IOException e) {
-            Log.e(TAG, "IOException", e);
-        }
+        dictDecoder.openDictBuffer();
         UserHistoryDictIOUtils.readDictionaryBinary(dictDecoder, listener);
     }
 
@@ -163,13 +167,14 @@
         addBigramToBigramList("this", "was", addedWords, originalList);
         addBigramToBigramList("hello", "world", addedWords, originalList);
 
-        final FusionDictionary fusionDict =
-                UserHistoryDictIOUtils.constructFusionDictionary(this, originalList);
+        final FusionDictionary fusionDict = UserHistoryDictIOUtils.constructFusionDictionary(
+                this, originalList, HEADER_OPTIONS);
 
         checkWordsInFusionDict(fusionDict, addedWords);
     }
 
-    public void testReadAndWrite() {
+    public void testReadAndWrite() throws IOException, FileNotFoundException,
+            UnsupportedFormatException {
         final Context context = getContext();
 
         File file = null;