Merge "Add VERSION4_DEV(402) in Java side and use it for tests."
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/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/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
index 55cbf79..f0963f7 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -90,19 +90,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/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/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());