Merge "Always keep PtNodes that represent non-word info during GC."
diff --git a/java/src/com/android/inputmethod/event/CombinerChain.java b/java/src/com/android/inputmethod/event/CombinerChain.java
index 990f7de..9e7f04d 100644
--- a/java/src/com/android/inputmethod/event/CombinerChain.java
+++ b/java/src/com/android/inputmethod/event/CombinerChain.java
@@ -56,18 +56,20 @@
      *
      * The combiner chain takes events as inputs and outputs code points and combining state.
      * For example, if the input language is Japanese, the combining chain will typically perform
-     * kana conversion.
+     * kana conversion. This takes a string for initial text, taken to be present before the
+     * cursor: we'll start after this.
      *
+     * @param initialText The text that has already been combined so far.
      * @param combinerList A list of combiners to be applied in order.
      */
-    public CombinerChain(final Combiner... combinerList) {
+    public CombinerChain(final String initialText, final Combiner... combinerList) {
         mCombiners = CollectionUtils.newArrayList();
         // The dead key combiner is always active, and always first
         mCombiners.add(new DeadKeyCombiner());
         for (final Combiner combiner : combinerList) {
             mCombiners.add(combiner);
         }
-        mCombinedText = new StringBuilder();
+        mCombinedText = new StringBuilder(initialText);
         mStateFeedback = new SpannableStringBuilder();
     }
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 8a2ed10..3751a3c 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -84,6 +84,7 @@
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.DialogUtils;
 import com.android.inputmethod.latin.utils.DistracterFilter;
+import com.android.inputmethod.latin.utils.DistracterFilterUtils;
 import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
 import com.android.inputmethod.latin.utils.IntentUtils;
 import com.android.inputmethod.latin.utils.JniUtils;
@@ -1745,7 +1746,8 @@
 
     @UsedForTesting
     /* package for test */ DistracterFilter createDistracterFilter() {
-        return DistracterFilter.createDistracterFilter(mInputLogic.mSuggest, mKeyboardSwitcher);
+        return DistracterFilterUtils.createDistracterFilter(
+                mInputLogic.mSuggest, mKeyboardSwitcher);
     }
 
     public void dumpDictionaryForDebug(final String dictName) {
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index cdee496..ac69729 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -80,7 +80,7 @@
     private boolean mIsFirstCharCapitalized;
 
     public WordComposer() {
-        mCombinerChain = new CombinerChain();
+        mCombinerChain = new CombinerChain("");
         mEvents = CollectionUtils.newArrayList();
         mAutoCorrection = null;
         mIsResumed = false;
@@ -92,18 +92,17 @@
     }
 
     /**
-     * Restart input with a new combining spec.
+     * Restart the combiners, possibly with a new spec.
      * @param combiningSpec The spec string for combining. This is found in the extra value.
      */
-    public void restart(final String combiningSpec) {
+    public void restartCombining(final String combiningSpec) {
         final String nonNullCombiningSpec = null == combiningSpec ? "" : combiningSpec;
-        if (nonNullCombiningSpec.equals(mCombiningSpec)) {
-            mCombinerChain.reset();
-        } else {
-            mCombinerChain = new CombinerChain(CombinerChain.createCombiners(nonNullCombiningSpec));
+        if (!nonNullCombiningSpec.equals(mCombiningSpec)) {
+            mCombinerChain = new CombinerChain(
+                    mCombinerChain.getComposingWordWithCombiningFeedback().toString(),
+                    CombinerChain.createCombiners(nonNullCombiningSpec));
             mCombiningSpec = nonNullCombiningSpec;
         }
-        reset();
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 1156c77..ea58abc 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -127,7 +127,7 @@
     public void startInput(final boolean restarting, final EditorInfo editorInfo,
             final String combiningSpec) {
         mEnteredText = null;
-        mWordComposer.restart(combiningSpec);
+        mWordComposer.restartCombining(combiningSpec);
         resetComposingState(true /* alsoResetLastComposedWord */);
         mDeleteCount = 0;
         mSpaceState = SpaceState.NONE;
@@ -150,7 +150,7 @@
      * @param combiningSpec the spec string for the combining rules
      */
     public void onSubtypeChanged(final String combiningSpec) {
-        mWordComposer.restart(combiningSpec);
+        mWordComposer.restartCombining(combiningSpec);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index 613ff2b..f5f072b 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -193,8 +193,9 @@
     // Dictionary version used for testing.
     public static final int VERSION4_ONLY_FOR_TESTING = 399;
     public static final int VERSION4 = 401;
+    public static final int VERSION4_DEV = 402;
     static final int MINIMUM_SUPPORTED_VERSION = VERSION2;
-    static final int MAXIMUM_SUPPORTED_VERSION = VERSION4;
+    static final int MAXIMUM_SUPPORTED_VERSION = VERSION4_DEV;
 
     // TODO: Make this value adaptative to content data, store it in the header, and
     // use it in the reading code.
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
index 55cbf79..0a03799 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -17,8 +17,6 @@
 package com.android.inputmethod.latin.utils;
 
 import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Suggest;
 import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
@@ -54,18 +52,6 @@
         mKeyboard = keyboard;
     }
 
-    public static DistracterFilter createDistracterFilter(final Suggest suggest,
-            final KeyboardSwitcher keyboardSwitcher) {
-        final MainKeyboardView mainKeyboardView = keyboardSwitcher.getMainKeyboardView();
-        // TODO: Create Keyboard when mainKeyboardView is null.
-        // TODO: Figure out the most reasonable keyboard for the filter. Refer to the
-        // spellchecker's logic.
-        final Keyboard keyboard = (mainKeyboardView != null) ?
-                mainKeyboardView.getKeyboard() : null;
-        final DistracterFilter distracterFilter = new DistracterFilter(suggest, keyboard);
-        return distracterFilter;
-    }
-
     private static boolean suggestionExceedsDistracterThreshold(
             final SuggestedWordInfo suggestion, final String consideredWord,
             final float distracterThreshold) {
@@ -90,19 +76,14 @@
      */
     public boolean isDistracterToWordsInDictionaries(final String prevWord,
             final String testedWord) {
-        if (mSuggest == null) {
+        if (mSuggest == null || mKeyboard == null) {
             return false;
         }
 
         final WordComposer composer = new WordComposer();
         final int[] codePoints = StringUtils.toCodePointArray(testedWord);
         final int[] coordinates;
-        if (null == mKeyboard) {
-            coordinates = CoordinateUtils.newCoordinateArray(codePoints.length,
-                    Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
-        } else {
-            coordinates = mKeyboard.getCoordinates(codePoints);
-        }
+        coordinates = mKeyboard.getCoordinates(codePoints);
         composer.setComposingWord(codePoints, coordinates, prevWord);
 
         final int trailingSingleQuotesCount = composer.trailingSingleQuotesCount();
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterUtils.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterUtils.java
new file mode 100644
index 0000000..df07f97
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterUtils.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.latin.Suggest;
+
+public class DistracterFilterUtils {
+    private DistracterFilterUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static final DistracterFilter createDistracterFilter(final Suggest suggest,
+            final KeyboardSwitcher keyboardSwitcher) {
+        final MainKeyboardView mainKeyboardView = keyboardSwitcher.getMainKeyboardView();
+        // TODO: Create Keyboard when mainKeyboardView is null.
+        // TODO: Figure out the most reasonable keyboard for the filter. Refer to the
+        // spellchecker's logic.
+        final Keyboard keyboard = (mainKeyboardView != null) ?
+                mainKeyboardView.getKeyboard() : null;
+        final DistracterFilter distracterFilter = new DistracterFilter(suggest, keyboard);
+        return distracterFilter;
+    }
+}
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
index da24302..479d151 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
@@ -143,6 +143,8 @@
                 return FormatUtils::VERSION_4_ONLY_FOR_TESTING;
             case FormatUtils::VERSION_4:
                 return FormatUtils::VERSION_4;
+            case FormatUtils::VERSION_4_DEV:
+                return FormatUtils::VERSION_4_DEV;
             default:
                 return FormatUtils::UNKNOWN_VERSION;
         }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
index 2a9028a..a8f8f28 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
@@ -100,6 +100,7 @@
             return false;
         case FormatUtils::VERSION_4_ONLY_FOR_TESTING:
         case FormatUtils::VERSION_4:
+        case FormatUtils::VERSION_4_DEV:
             return buffer->writeUintAndAdvancePosition(version /* data */,
                     HEADER_DICTIONARY_VERSION_SIZE, writingPos);
         default:
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
index c4d1860..59f1f29 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
@@ -19,6 +19,9 @@
 #include <climits>
 
 #include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/backward/v401/ver4_dict_buffers.h"
+#include "suggest/policyimpl/dictionary/structure/backward/v401/ver4_dict_constants.h"
+#include "suggest/policyimpl/dictionary/structure/backward/v401/ver4_patricia_trie_policy.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_writing_utils.h"
 #include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h"
@@ -42,7 +45,7 @@
         if (isUpdatable) {
             AKLOGE("One file dictionaries don't support updating. path: %s", path);
             ASSERT(false);
-            return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
+            return nullptr;
         }
         return newPolicyForFileDict(path, bufOffset, size);
     }
@@ -54,26 +57,43 @@
                 const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap) {
     FormatUtils::FORMAT_VERSION dictFormatVersion = FormatUtils::getFormatVersion(formatVersion);
     switch (dictFormatVersion) {
-        case FormatUtils::VERSION_4_ONLY_FOR_TESTING:
         case FormatUtils::VERSION_4: {
-            HeaderPolicy headerPolicy(dictFormatVersion, locale, attributeMap);
-            Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers =
-                    Ver4DictBuffers::createVer4DictBuffers(&headerPolicy,
-                            Ver4DictConstants::MAX_DICT_EXTENDED_REGION_SIZE);
-            if (!DynamicPtWritingUtils::writeEmptyDictionary(
-                    dictBuffers->getWritableTrieBuffer(), 0 /* rootPos */)) {
-                AKLOGE("Empty ver4 dictionary structure cannot be created on memory.");
-                return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
-            }
-            return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(
-                    new Ver4PatriciaTriePolicy(std::move(dictBuffers)));
+            return newPolicyForOnMemoryV4Dict<backward::v401::Ver4DictConstants,
+                    backward::v401::Ver4DictBuffers,
+                    backward::v401::Ver4DictBuffers::Ver4DictBuffersPtr,
+                    backward::v401::Ver4PatriciaTriePolicy>(
+                            dictFormatVersion, locale, attributeMap);
+        }
+        case FormatUtils::VERSION_4_ONLY_FOR_TESTING:
+        case FormatUtils::VERSION_4_DEV: {
+            return newPolicyForOnMemoryV4Dict<Ver4DictConstants, Ver4DictBuffers,
+                    Ver4DictBuffers::Ver4DictBuffersPtr, Ver4PatriciaTriePolicy>(
+                            dictFormatVersion, locale, attributeMap);
         }
         default:
             AKLOGE("DICT: dictionary format %d is not supported for on memory dictionary",
                     formatVersion);
             break;
     }
-    return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
+    return nullptr;
+}
+
+template<class DictConstants, class DictBuffers, class DictBuffersPtr, class StructurePolicy>
+/* static */ DictionaryStructureWithBufferPolicy::StructurePolicyPtr
+        DictionaryStructureWithBufferPolicyFactory::newPolicyForOnMemoryV4Dict(
+                const FormatUtils::FORMAT_VERSION formatVersion,
+                const std::vector<int> &locale,
+                const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap) {
+    HeaderPolicy headerPolicy(formatVersion, locale, attributeMap);
+    DictBuffersPtr dictBuffers = DictBuffers::createVer4DictBuffers(&headerPolicy,
+            DictConstants::MAX_DICT_EXTENDED_REGION_SIZE);
+    if (!DynamicPtWritingUtils::writeEmptyDictionary(
+            dictBuffers->getWritableTrieBuffer(), 0 /* rootPos */)) {
+        AKLOGE("Empty ver4 dictionary structure cannot be created on memory.");
+        return nullptr;
+    }
+    return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(
+            new StructurePolicy(std::move(dictBuffers)));
 }
 
 /* static */ DictionaryStructureWithBufferPolicy::StructurePolicyPtr
@@ -84,10 +104,10 @@
     getHeaderFilePathInDictDir(path, headerFilePathBufSize, headerFilePath);
     // Allocated buffer in MmapedBuffer::openBuffer() will be freed in the destructor of
     // MmappedBufferPtr if the instance has the responsibility.
-    MmappedBuffer::MmappedBufferPtr mmappedBuffer(
-            MmappedBuffer::openBuffer(headerFilePath, isUpdatable));
+    MmappedBuffer::MmappedBufferPtr mmappedBuffer =
+            MmappedBuffer::openBuffer(headerFilePath, isUpdatable);
     if (!mmappedBuffer) {
-        return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
+        return nullptr;
     }
     const FormatUtils::FORMAT_VERSION formatVersion = FormatUtils::detectFormatVersion(
             mmappedBuffer->getBuffer(), mmappedBuffer->getBufferSize());
@@ -95,34 +115,50 @@
         case FormatUtils::VERSION_2:
             AKLOGE("Given path is a directory but the format is version 2. path: %s", path);
             break;
-        case FormatUtils::VERSION_4_ONLY_FOR_TESTING:
         case FormatUtils::VERSION_4: {
-            const int dictDirPathBufSize = strlen(headerFilePath) + 1 /* terminator */;
-            char dictPath[dictDirPathBufSize];
-            if (!FileUtils::getFilePathWithoutSuffix(headerFilePath,
-                    Ver4DictConstants::HEADER_FILE_EXTENSION, dictDirPathBufSize, dictPath)) {
-                AKLOGE("Dictionary file name is not valid as a ver4 dictionary. path: %s", path);
-                ASSERT(false);
-                return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
-            }
-            Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers(
-                    Ver4DictBuffers::openVer4DictBuffers(dictPath, std::move(mmappedBuffer),
-                            formatVersion));
-            if (!dictBuffers || !dictBuffers->isValid()) {
-                AKLOGE("DICT: The dictionary doesn't satisfy ver4 format requirements. path: %s",
-                        path);
-                ASSERT(false);
-                return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
-            }
-            return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(
-                    new Ver4PatriciaTriePolicy(std::move(dictBuffers)));
+            return newPolicyForV4Dict<backward::v401::Ver4DictConstants,
+                    backward::v401::Ver4DictBuffers,
+                    backward::v401::Ver4DictBuffers::Ver4DictBuffersPtr,
+                    backward::v401::Ver4PatriciaTriePolicy>(
+                            headerFilePath, formatVersion, std::move(mmappedBuffer));
+        }
+        case FormatUtils::VERSION_4_ONLY_FOR_TESTING:
+        case FormatUtils::VERSION_4_DEV: {
+            return newPolicyForV4Dict<Ver4DictConstants, Ver4DictBuffers,
+                    Ver4DictBuffers::Ver4DictBuffersPtr, Ver4PatriciaTriePolicy>(
+                            headerFilePath, formatVersion, std::move(mmappedBuffer));
         }
         default:
             AKLOGE("DICT: dictionary format is unknown, bad magic number. path: %s", path);
             break;
     }
     ASSERT(false);
-    return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
+    return nullptr;
+}
+
+template<class DictConstants, class DictBuffers, class DictBuffersPtr, class StructurePolicy>
+/* static */ DictionaryStructureWithBufferPolicy::StructurePolicyPtr
+        DictionaryStructureWithBufferPolicyFactory::newPolicyForV4Dict(
+                const char *const headerFilePath, const FormatUtils::FORMAT_VERSION formatVersion,
+                MmappedBuffer::MmappedBufferPtr &&mmappedBuffer) {
+    const int dictDirPathBufSize = strlen(headerFilePath) + 1 /* terminator */;
+    char dictPath[dictDirPathBufSize];
+    if (!FileUtils::getFilePathWithoutSuffix(headerFilePath,
+            DictConstants::HEADER_FILE_EXTENSION, dictDirPathBufSize, dictPath)) {
+        AKLOGE("Dictionary file name is not valid as a ver4 dictionary. path: %s", path);
+        ASSERT(false);
+        return nullptr;
+    }
+    DictBuffersPtr dictBuffers =
+            DictBuffers::openVer4DictBuffers(dictPath, std::move(mmappedBuffer), formatVersion);
+    if (!dictBuffers || !dictBuffers->isValid()) {
+        AKLOGE("DICT: The dictionary doesn't satisfy ver4 format requirements. path: %s",
+                path);
+        ASSERT(false);
+        return nullptr;
+    }
+    return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(
+            new StructurePolicy(std::move(dictBuffers)));
 }
 
 /* static */ DictionaryStructureWithBufferPolicy::StructurePolicyPtr
@@ -133,7 +169,7 @@
     MmappedBuffer::MmappedBufferPtr mmappedBuffer(
             MmappedBuffer::openBuffer(path, bufOffset, size, false /* isUpdatable */));
     if (!mmappedBuffer) {
-        return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
+        return nullptr;
     }
     switch (FormatUtils::detectFormatVersion(mmappedBuffer->getBuffer(),
             mmappedBuffer->getBufferSize())) {
@@ -142,6 +178,7 @@
                     new PatriciaTriePolicy(std::move(mmappedBuffer)));
         case FormatUtils::VERSION_4_ONLY_FOR_TESTING:
         case FormatUtils::VERSION_4:
+        case FormatUtils::VERSION_4_DEV:
             AKLOGE("Given path is a file but the format is version 4. path: %s", path);
             break;
         default:
@@ -149,7 +186,7 @@
             break;
     }
     ASSERT(false);
-    return DictionaryStructureWithBufferPolicy::StructurePolicyPtr(nullptr);
+    return nullptr;
 }
 
 /* static */ void DictionaryStructureWithBufferPolicyFactory::getHeaderFilePathInDictDir(
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h
index f71447e..768454d 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h
@@ -22,6 +22,8 @@
 #include "defines.h"
 #include "suggest/core/policy/dictionary_header_structure_policy.h"
 #include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+#include "suggest/policyimpl/dictionary/utils/format_utils.h"
+#include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
 
 namespace latinime {
 
@@ -32,16 +34,26 @@
                     const int size, const bool isUpdatable);
 
     static DictionaryStructureWithBufferPolicy::StructurePolicyPtr
-            newPolicyForOnMemoryDict(const int formatVersion,
-                    const std::vector<int> &locale,
+            newPolicyForOnMemoryDict(const int formatVersion, const std::vector<int> &locale,
                     const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap);
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DictionaryStructureWithBufferPolicyFactory);
 
+    template<class DictConstants, class DictBuffers, class DictBuffersPtr, class StructurePolicy>
+    static DictionaryStructureWithBufferPolicy::StructurePolicyPtr
+            newPolicyForOnMemoryV4Dict(const FormatUtils::FORMAT_VERSION formatVersion,
+                    const std::vector<int> &locale,
+                    const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap);
+
     static DictionaryStructureWithBufferPolicy::StructurePolicyPtr
             newPolicyForDirectoryDict(const char *const path, const bool isUpdatable);
 
+    template<class DictConstants, class DictBuffers, class DictBuffersPtr, class StructurePolicy>
+    static DictionaryStructureWithBufferPolicy::StructurePolicyPtr newPolicyForV4Dict(
+            const char *const headerFilePath, const FormatUtils::FORMAT_VERSION formatVersion,
+                    MmappedBuffer::MmappedBufferPtr &&mmappedBuffer);
+
     static DictionaryStructureWithBufferPolicy::StructurePolicyPtr
             newPolicyForFileDict(const char *const path, const int bufOffset, const int size);
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
index 80970c7..105363d 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
@@ -23,6 +23,7 @@
 #include <sys/types.h>
 
 #include "suggest/policyimpl/dictionary/header/header_policy.h"
+#include "suggest/policyimpl/dictionary/structure/backward/v401/ver4_dict_buffers.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_writing_utils.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h"
 #include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
@@ -40,10 +41,16 @@
     TimeKeeper::setCurrentTime();
     const FormatUtils::FORMAT_VERSION formatVersion = FormatUtils::getFormatVersion(dictVersion);
     switch (formatVersion) {
-        case FormatUtils::VERSION_4_ONLY_FOR_TESTING:
         case FormatUtils::VERSION_4:
-            return createEmptyV4DictFile(filePath, localeAsCodePointVector, attributeMap,
-                    formatVersion);
+            return createEmptyV4DictFile<backward::v401::Ver4DictConstants,
+                    backward::v401::Ver4DictBuffers,
+                    backward::v401::Ver4DictBuffers::Ver4DictBuffersPtr>(
+                            filePath, localeAsCodePointVector, attributeMap, formatVersion);
+        case FormatUtils::VERSION_4_ONLY_FOR_TESTING:
+        case FormatUtils::VERSION_4_DEV:
+            return createEmptyV4DictFile<Ver4DictConstants, Ver4DictBuffers,
+                    Ver4DictBuffers::Ver4DictBuffersPtr>(
+                            filePath, localeAsCodePointVector, attributeMap, formatVersion);
         default:
             AKLOGE("Cannot create dictionary %s because format version %d is not supported.",
                     filePath, dictVersion);
@@ -51,14 +58,14 @@
     }
 }
 
+template<class DictConstants, class DictBuffers, class DictBuffersPtr>
 /* static */ bool DictFileWritingUtils::createEmptyV4DictFile(const char *const dirPath,
         const std::vector<int> localeAsCodePointVector,
         const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap,
         const FormatUtils::FORMAT_VERSION formatVersion) {
     HeaderPolicy headerPolicy(formatVersion, localeAsCodePointVector, attributeMap);
-    Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers(
-            Ver4DictBuffers::createVer4DictBuffers(&headerPolicy,
-                    Ver4DictConstants::MAX_DICT_EXTENDED_REGION_SIZE));
+    DictBuffersPtr dictBuffers = DictBuffers::createVer4DictBuffers(&headerPolicy,
+            DictConstants::MAX_DICT_EXTENDED_REGION_SIZE);
     headerPolicy.fillInAndWriteHeaderToBuffer(true /* updatesLastDecayedTime */,
             0 /* unigramCount */, 0 /* bigramCount */,
             0 /* extendedRegionSize */, dictBuffers->getWritableHeaderBuffer());
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h
index a822989..5df5856 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h
@@ -45,6 +45,12 @@
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DictFileWritingUtils);
 
+    static bool createEmptyV401DictFile(const char *const filePath,
+            const std::vector<int> localeAsCodePointVector,
+            const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap,
+            const FormatUtils::FORMAT_VERSION formatVersion);
+
+    template<class DictConstants, class DictBuffers, class DictBuffersPtr>
     static bool createEmptyV4DictFile(const char *const filePath,
             const std::vector<int> localeAsCodePointVector,
             const DictionaryHeaderStructurePolicy::AttributeMap *const attributeMap,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp
index a8518cd..ba405b0 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp
@@ -33,6 +33,8 @@
             return VERSION_4_ONLY_FOR_TESTING;
         case VERSION_4:
             return VERSION_4;
+        case VERSION_4_DEV:
+            return VERSION_4_DEV;
         default:
             return UNKNOWN_VERSION;
     }
@@ -62,6 +64,8 @@
                 return VERSION_4_ONLY_FOR_TESTING;
             } else if (ByteArrayUtils::readUint16(dict, 4) == VERSION_4) {
                 return VERSION_4;
+            } else if (ByteArrayUtils::readUint16(dict, 4) == VERSION_4_DEV) {
+                return VERSION_4_DEV;
             } else {
                 return UNKNOWN_VERSION;
             }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
index 20dfb9d..c47f30c 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
@@ -33,6 +33,7 @@
         VERSION_2 = 2,
         VERSION_4_ONLY_FOR_TESTING = 399,
         VERSION_4 = 401,
+        VERSION_4_DEV = 402,
         UNKNOWN_VERSION = -1
     };
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.cpp
index d3e0c23..4a126ff 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.cpp
@@ -33,7 +33,7 @@
     const int mmapFd = open(path, O_RDONLY);
     if (mmapFd < 0) {
         AKLOGE("DICT: Can't open the source. path=%s errno=%d", path, errno);
-        return MmappedBufferPtr(nullptr);
+        return nullptr;
     }
     const int pagesize = sysconf(_SC_PAGESIZE);
     const int offset = bufferOffset % pagesize;
@@ -45,13 +45,13 @@
     if (mmappedBuffer == MAP_FAILED) {
         AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
         close(mmapFd);
-        return MmappedBufferPtr(nullptr);
+        return nullptr;
     }
     uint8_t *const buffer = static_cast<uint8_t *>(mmappedBuffer) + offset;
     if (!buffer) {
         AKLOGE("DICT: buffer is null");
         close(mmapFd);
-        return MmappedBufferPtr(nullptr);
+        return nullptr;
     }
     return MmappedBufferPtr(new MmappedBuffer(buffer, bufferSize, mmappedBuffer, alignedSize,
             mmapFd, isUpdatable));
@@ -61,7 +61,7 @@
         const char *const path, const bool isUpdatable) {
     const int fileSize = FileUtils::getFileSize(path);
     if (fileSize == -1) {
-        return MmappedBufferPtr(nullptr);
+        return nullptr;
     } else if (fileSize == 0) {
         return MmappedBufferPtr(new MmappedBuffer(isUpdatable));
     } else {
@@ -76,7 +76,7 @@
     const int filePathLength = snprintf(filePath, filePathBufferSize, "%s%s", dirPath,
             fileName);
     if (filePathLength >= filePathBufferSize) {
-        return MmappedBufferPtr(nullptr);
+        return nullptr;
     }
     return openBuffer(filePath, isUpdatable);
 }
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
index 35d9a4e..90b90ff 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
@@ -46,6 +46,8 @@
     private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
     private static final String TEST_LOCALE = "test";
     private static final int DUMMY_PROBABILITY = 0;
+    private static final int[] DICT_FORMAT_VERSIONS =
+            new int[] { FormatSpec.VERSION4, FormatSpec.VERSION4_DEV };
 
     private int mCurrentTime = 0;
 
@@ -94,7 +96,8 @@
     private File createEmptyDictionaryAndGetFile(final String dictId,
             final int formatVersion) throws IOException {
         if (formatVersion == FormatSpec.VERSION4
-                || formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING) {
+                || formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING
+                || formatVersion == FormatSpec.VERSION4_DEV) {
             return createEmptyVer4DictionaryAndGetFile(dictId, formatVersion);
         } else {
             throw new IOException("Dictionary format version " + formatVersion
@@ -120,7 +123,7 @@
             return file;
         } else {
             throw new IOException("Empty dictionary " + file.getAbsolutePath()
-                    + " cannot be created.");
+                    + " cannot be created. Foramt version: " + formatVersion);
         }
     }
 
@@ -133,7 +136,9 @@
     }
 
     public void testReadDictInJavaSide() {
-        testReadDictInJavaSide(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testReadDictInJavaSide(formatVersion);
+        }
     }
 
     private void testReadDictInJavaSide(final int formatVersion) {
@@ -178,10 +183,6 @@
     }
 
     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);
@@ -197,7 +198,9 @@
     }
 
     public void testAddValidAndInvalidWords() {
-        testAddValidAndInvalidWords(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testAddValidAndInvalidWords(formatVersion);
+        }
     }
 
     private void testAddValidAndInvalidWords(final int formatVersion) {
@@ -240,7 +243,9 @@
     }
 
     public void testDecayingProbability() {
-        testDecayingProbability(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testDecayingProbability(formatVersion);
+        }
     }
 
     private void testDecayingProbability(final int formatVersion) {
@@ -295,7 +300,9 @@
     }
 
     public void testAddManyUnigramsToDecayingDict() {
-        testAddManyUnigramsToDecayingDict(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testAddManyUnigramsToDecayingDict(formatVersion);
+        }
     }
 
     private void testAddManyUnigramsToDecayingDict(final int formatVersion) {
@@ -354,7 +361,9 @@
     }
 
     public void testOverflowUnigrams() {
-        testOverflowUnigrams(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testOverflowUnigrams(formatVersion);
+        }
     }
 
     private void testOverflowUnigrams(final int formatVersion) {
@@ -413,7 +422,9 @@
     }
 
     public void testAddManyBigramsToDecayingDict() {
-        testAddManyBigramsToDecayingDict(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testAddManyBigramsToDecayingDict(formatVersion);
+        }
     }
 
     private void testAddManyBigramsToDecayingDict(final int formatVersion) {
@@ -487,7 +498,9 @@
     }
 
     public void testOverflowBigrams() {
-        testOverflowBigrams(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testOverflowBigrams(formatVersion);
+        }
     }
 
     private void testOverflowBigrams(final int formatVersion) {
@@ -566,7 +579,9 @@
     }
 
     public void testDictMigration() {
-        testDictMigration(FormatSpec.VERSION4_ONLY_FOR_TESTING, FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testDictMigration(FormatSpec.VERSION4_ONLY_FOR_TESTING, formatVersion);
+        }
     }
 
     private void testDictMigration(final int fromFormatVersion, final int toFormatVersion) {
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
index 770e76e..cfeed0a 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -43,11 +43,14 @@
 public class BinaryDictionaryTests extends AndroidTestCase {
     private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
     private static final String TEST_LOCALE = "test";
+    private static final int[] DICT_FORMAT_VERSIONS =
+            new int[] { FormatSpec.VERSION4, FormatSpec.VERSION4_DEV };
 
     private File createEmptyDictionaryAndGetFile(final String dictId,
             final int formatVersion) throws IOException {
         if (formatVersion == FormatSpec.VERSION4
-                || formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING) {
+                || formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING
+                || formatVersion == FormatSpec.VERSION4_DEV) {
             return createEmptyVer4DictionaryAndGetFile(dictId, formatVersion);
         } else {
             throw new IOException("Dictionary format version " + formatVersion
@@ -67,12 +70,14 @@
             return file;
         } else {
             throw new IOException("Empty dictionary " + file.getAbsolutePath()
-                    + " cannot be created.");
+                    + " cannot be created. Format version: " + formatVersion);
         }
     }
 
     public void testIsValidDictionary() {
-        testIsValidDictionary(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testIsValidDictionary(formatVersion);
+        }
     }
 
     private void testIsValidDictionary(final int formatVersion) {
@@ -100,7 +105,9 @@
     }
 
     public void testConstructingDictionaryOnMemory() {
-        testConstructingDictionaryOnMemory(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testConstructingDictionaryOnMemory(formatVersion);
+        }
     }
 
     private void testConstructingDictionaryOnMemory(final int formatVersion) {
@@ -131,7 +138,9 @@
     }
 
     public void testAddTooLongWord() {
-        testAddTooLongWord(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testAddTooLongWord(formatVersion);
+        }
     }
 
     private void testAddTooLongWord(final int formatVersion) {
@@ -190,7 +199,9 @@
     }
 
     public void testAddUnigramWord() {
-        testAddUnigramWord(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testAddUnigramWord(formatVersion);
+        }
     }
 
     private void testAddUnigramWord(final int formatVersion) {
@@ -232,7 +243,9 @@
     }
 
     public void testRandomlyAddUnigramWord() {
-        testRandomlyAddUnigramWord(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testRandomlyAddUnigramWord(formatVersion);
+        }
     }
 
     private void testRandomlyAddUnigramWord(final int formatVersion) {
@@ -268,7 +281,9 @@
     }
 
     public void testAddBigramWords() {
-        testAddBigramWords(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testAddBigramWords(formatVersion);
+        }
     }
 
     private void testAddBigramWords(final int formatVersion) {
@@ -336,7 +351,9 @@
     }
 
     public void testRandomlyAddBigramWords() {
-        testRandomlyAddBigramWords(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testRandomlyAddBigramWords(formatVersion);
+        }
     }
 
     private void testRandomlyAddBigramWords(final int formatVersion) {
@@ -397,7 +414,9 @@
     }
 
     public void testRemoveBigramWords() {
-        testRemoveBigramWords(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testRemoveBigramWords(formatVersion);
+        }
     }
 
     private void testRemoveBigramWords(final int formatVersion) {
@@ -447,7 +466,9 @@
     }
 
     public void testFlushDictionary() {
-        testFlushDictionary(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testFlushDictionary(formatVersion);
+        }
     }
 
     private void testFlushDictionary(final int formatVersion) {
@@ -499,7 +520,9 @@
     }
 
     public void testFlushWithGCDictionary() {
-        testFlushWithGCDictionary(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testFlushWithGCDictionary(formatVersion);
+        }
     }
 
     private void testFlushWithGCDictionary(final int formatVersion) {
@@ -547,7 +570,9 @@
     }
 
     public void testAddBigramWordsAndFlashWithGC() {
-        testAddBigramWordsAndFlashWithGC(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testAddBigramWordsAndFlashWithGC(formatVersion);
+        }
     }
 
     // TODO: Evaluate performance of GC
@@ -616,7 +641,9 @@
     }
 
     public void testRandomOperationsAndFlashWithGC() {
-        testRandomOperationsAndFlashWithGC(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testRandomOperationsAndFlashWithGC(formatVersion);
+        }
     }
 
     private void testRandomOperationsAndFlashWithGC(final int formatVersion) {
@@ -727,7 +754,9 @@
     }
 
     public void testAddManyUnigramsAndFlushWithGC() {
-        testAddManyUnigramsAndFlushWithGC(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testAddManyUnigramsAndFlushWithGC(formatVersion);
+        }
     }
 
     private void testAddManyUnigramsAndFlushWithGC(final int formatVersion) {
@@ -775,7 +804,9 @@
     }
 
     public void testUnigramAndBigramCount() {
-        testUnigramAndBigramCount(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testUnigramAndBigramCount(formatVersion);
+        }
     }
 
     private void testUnigramAndBigramCount(final int formatVersion) {
@@ -834,7 +865,9 @@
     }
 
     public void testAddMultipleDictionaryEntries() {
-        testAddMultipleDictionaryEntries(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testAddMultipleDictionaryEntries(formatVersion);
+        }
     }
 
     private void testAddMultipleDictionaryEntries(final int formatVersion) {
@@ -896,7 +929,9 @@
     }
 
     public void testGetWordProperties() {
-        testGetWordProperties(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testGetWordProperties(formatVersion);
+        }
     }
 
     private void testGetWordProperties(final int formatVersion) {
@@ -995,7 +1030,9 @@
     }
 
     public void testIterateAllWords() {
-        testIterateAllWords(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testIterateAllWords(formatVersion);
+        }
     }
 
     private void testIterateAllWords(final int formatVersion) {
@@ -1091,7 +1128,9 @@
     }
 
     public void testAddShortcuts() {
-        testAddShortcuts(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testAddShortcuts(formatVersion);
+        }
     }
 
     private void testAddShortcuts(final int formatVersion) {
@@ -1151,7 +1190,9 @@
     }
 
     public void testAddManyShortcuts() {
-        testAddManyShortcuts(FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testAddManyShortcuts(formatVersion);
+        }
     }
 
     private void testAddManyShortcuts(final int formatVersion) {
@@ -1227,7 +1268,9 @@
     }
 
     public void testDictMigration() {
-        testDictMigration(FormatSpec.VERSION4_ONLY_FOR_TESTING, FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testDictMigration(FormatSpec.VERSION4_ONLY_FOR_TESTING, formatVersion);
+        }
     }
 
     private void testDictMigration(final int fromFormatVersion, final int toFormatVersion) {
@@ -1271,7 +1314,9 @@
     }
 
     public void testLargeDictMigration() {
-        testLargeDictMigration(FormatSpec.VERSION4_ONLY_FOR_TESTING, FormatSpec.VERSION4);
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testLargeDictMigration(FormatSpec.VERSION4_ONLY_FOR_TESTING, formatVersion);
+        }
     }
 
     private void testLargeDictMigration(final int fromFormatVersion, final int toFormatVersion) {
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index 29423e8..a944416 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -600,4 +600,16 @@
         assertEquals("type words letter by letter", EXPECTED_RESULT,
                 mEditText.getText().toString());
     }
+
+    public void testSwitchLanguages() {
+        final String WORD_TO_TYPE_FIRST_PART = "com";
+        final String WORD_TO_TYPE_SECOND_PART = "md ";
+        final String EXPECTED_RESULT = "comme ";
+        changeLanguage("en");
+        type(WORD_TO_TYPE_FIRST_PART);
+        changeLanguage("fr");
+        type(WORD_TO_TYPE_SECOND_PART);
+        assertEquals("Composing continues after switching languages", EXPECTED_RESULT,
+                mEditText.getText().toString());
+    }
 }
diff --git a/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java b/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java
index 47f781e..538d759 100644
--- a/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java
+++ b/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java
@@ -28,8 +28,10 @@
 // TODO: there should not be a dependency to this in dicttool, so there
 // should be a sensible way to separate them cleanly.
 public class CombinerChain {
-    private StringBuilder mComposingWord = new StringBuilder();
-    public CombinerChain(final Combiner... combinerList) {}
+    private StringBuilder mComposingWord;
+    public CombinerChain(final String initialText, final Combiner... combinerList) {
+        mComposingWord = new StringBuilder(initialText);
+    }
 
     public void processEvent(final ArrayList<Event> previousEvents, final Event newEvent) {
         mComposingWord.append(newEvent.getTextToCommit());