Merge "Extend jni interface to input/output languageWeight."
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 37a4bf8..fd0be6f 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -204,10 +204,12 @@
         keyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
         final boolean subtypeChanged = (oldKeyboard == null)
                 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
-        final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage(
+        final int languageOnSpacebarFormatType = mSubtypeSwitcher.getLanguageOnSpacebarFormatType(
                 keyboard.mId.mSubtype);
-        keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage,
-                RichInputMethodManager.getInstance().hasMultipleEnabledIMEsOrSubtypes(true));
+        final boolean hasMultipleEnabledIMEsOrSubtypes = RichInputMethodManager.getInstance()
+                .hasMultipleEnabledIMEsOrSubtypes(true /* shouldIncludeAuxiliarySubtypes */);
+        keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, languageOnSpacebarFormatType,
+                hasMultipleEnabledIMEsOrSubtypes);
     }
 
     public Keyboard getKeyboard() {
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 03425ef..0f9c39a 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -49,6 +49,7 @@
 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
 import com.android.inputmethod.keyboard.internal.KeyPreviewChoreographer;
 import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
+import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper;
 import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper;
 import com.android.inputmethod.keyboard.internal.SlidingKeyInputDrawingPreview;
 import com.android.inputmethod.keyboard.internal.TimerHandler;
@@ -123,7 +124,7 @@
     // Stuff to draw language name on spacebar.
     private final int mLanguageOnSpacebarFinalAlpha;
     private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
-    private boolean mNeedsToDisplayLanguage;
+    private int mLanguageOnSpacebarFormatType;
     private boolean mHasMultipleEnabledIMEsOrSubtypes;
     private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE;
     private final float mLanguageOnSpacebarTextRatio;
@@ -811,14 +812,16 @@
     }
 
     public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged,
-            final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes) {
-        mNeedsToDisplayLanguage = needsToDisplayLanguage;
+            final int languageOnSpacebarFormatType,
+            final boolean hasMultipleEnabledIMEsOrSubtypes) {
+        mLanguageOnSpacebarFormatType = languageOnSpacebarFormatType;
         mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes;
         final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
         if (animator == null) {
-            mNeedsToDisplayLanguage = false;
+            mLanguageOnSpacebarFormatType = LanguageOnSpacebarHelper.FORMAT_TYPE_NONE;
         } else {
-            if (subtypeChanged && needsToDisplayLanguage) {
+            if (subtypeChanged
+                    && languageOnSpacebarFormatType != LanguageOnSpacebarHelper.FORMAT_TYPE_NONE) {
                 setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE);
                 if (animator.isStarted()) {
                     animator.cancel();
@@ -919,9 +922,11 @@
     private String layoutLanguageOnSpacebar(final Paint paint,
             final InputMethodSubtype subtype, final int width) {
         // Choose appropriate language name to fit into the width.
-        final String fullText = SpacebarLanguageUtils.getFullDisplayName(subtype);
-        if (fitsTextIntoWidth(width, fullText, paint)) {
-            return fullText;
+        if (mLanguageOnSpacebarFormatType == LanguageOnSpacebarHelper.FORMAT_TYPE_FULL_LOCALE) {
+            final String fullText = SpacebarLanguageUtils.getFullDisplayName(subtype);
+            if (fitsTextIntoWidth(width, fullText, paint)) {
+                return fullText;
+            }
         }
 
         final String middleText = SpacebarLanguageUtils.getMiddleDisplayName(subtype);
@@ -937,7 +942,7 @@
         final int height = key.getHeight();
 
         // If input language are explicitly selected.
-        if (mNeedsToDisplayLanguage) {
+        if (mLanguageOnSpacebarFormatType != LanguageOnSpacebarHelper.FORMAT_TYPE_NONE) {
             paint.setTextAlign(Align.CENTER);
             paint.setTypeface(Typeface.DEFAULT);
             paint.setTextSize(mLanguageOnSpacebarTextSize);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java b/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java
new file mode 100644
index 0000000..6400a24
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelper.java
@@ -0,0 +1,68 @@
+/*
+ * 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.keyboard.internal;
+
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class determines that the language name on the spacebar should be displayed in what format.
+ */
+public final class LanguageOnSpacebarHelper {
+    public static final int FORMAT_TYPE_NONE = 0;
+    public static final int FORMAT_TYPE_LANGUAGE_ONLY = 1;
+    public static final int FORMAT_TYPE_FULL_LOCALE = 2;
+
+    private List<InputMethodSubtype> mEnabledSubtypes = Collections.emptyList();
+    private boolean mIsSystemLanguageSameAsInputLanguage;
+
+    public int getLanguageOnSpacebarFormatType(final InputMethodSubtype subtype) {
+        if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
+            return FORMAT_TYPE_FULL_LOCALE;
+        }
+        // Only this subtype is enabled and equals to the system locale.
+        if (mEnabledSubtypes.size() < 2 && mIsSystemLanguageSameAsInputLanguage) {
+            return FORMAT_TYPE_NONE;
+        }
+        final String keyboardLanguage = SubtypeLocaleUtils.getSubtypeLocale(subtype).getLanguage();
+        final String keyboardLayout = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
+        int sameLanguageAndLayoutCount = 0;
+        for (final InputMethodSubtype ims : mEnabledSubtypes) {
+            final String language = SubtypeLocaleUtils.getSubtypeLocale(ims).getLanguage();
+            if (keyboardLanguage.equals(language) && keyboardLayout.equals(
+                    SubtypeLocaleUtils.getKeyboardLayoutSetName(ims))) {
+                sameLanguageAndLayoutCount++;
+            }
+        }
+        // Display full locale name only when there are multiple subtypes that have the same
+        // locale and keyboard layout. Otherwise displaying language name is enough.
+        return sameLanguageAndLayoutCount > 1 ? FORMAT_TYPE_FULL_LOCALE
+                : FORMAT_TYPE_LANGUAGE_ONLY;
+    }
+
+    public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
+        mEnabledSubtypes = enabledSubtypes;
+    }
+
+    public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) {
+        mIsSystemLanguageSameAsInputLanguage = isSame;
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/NeedsToDisplayLanguage.java b/java/src/com/android/inputmethod/keyboard/internal/NeedsToDisplayLanguage.java
deleted file mode 100644
index e548de5..0000000
--- a/java/src/com/android/inputmethod/keyboard/internal/NeedsToDisplayLanguage.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.keyboard.internal;
-
-import android.view.inputmethod.InputMethodSubtype;
-
-import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
-
-/**
- * This class determines that the language name on the spacebar should be displayed or not.
- */
-public final class NeedsToDisplayLanguage {
-    private int mEnabledSubtypeCount;
-    private boolean mIsSystemLanguageSameAsInputLanguage;
-
-    public boolean needsToDisplayLanguage(final InputMethodSubtype subtype) {
-        if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
-            return true;
-        }
-        return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage;
-    }
-
-    public void updateEnabledSubtypeCount(final int count) {
-        mEnabledSubtypeCount = count;
-    }
-
-    public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) {
-        mIsSystemLanguageSameAsInputLanguage = isSame;
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 53e6232..b4807b0 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -156,7 +156,7 @@
         private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3;
         private static final int MSG_RESUME_SUGGESTIONS = 4;
         private static final int MSG_REOPEN_DICTIONARIES = 5;
-        private static final int MSG_ON_END_BATCH_INPUT = 6;
+        private static final int MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED = 6;
         private static final int MSG_RESET_CACHES = 7;
         // Update this when adding new messages
         private static final int MSG_LAST = MSG_RESET_CACHES;
@@ -220,8 +220,9 @@
                 // get any suggestions. Wait one frame.
                 postUpdateSuggestionStrip();
                 break;
-            case MSG_ON_END_BATCH_INPUT:
-                latinIme.mInputLogic.endBatchInputInternal(latinIme.mSettings.getCurrent(),
+            case MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED:
+                latinIme.mInputLogic.onUpdateTailBatchInputCompleted(
+                        latinIme.mSettings.getCurrent(),
                         (SuggestedWords) msg.obj, latinIme.mKeyboardSwitcher);
                 break;
             case MSG_RESET_CACHES:
@@ -304,8 +305,8 @@
                     ARG1_NOT_GESTURE_INPUT, ARG2_UNUSED, suggestedWords).sendToTarget();
         }
 
-        public void onEndBatchInput(final SuggestedWords suggestedWords) {
-            obtainMessage(MSG_ON_END_BATCH_INPUT, suggestedWords).sendToTarget();
+        public void showTailBatchInputResult(final SuggestedWords suggestedWords) {
+            obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget();
         }
 
         // Working variables for the following methods.
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index d430122..0211339 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -34,7 +34,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.internal.NeedsToDisplayLanguage;
+import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper;
 import com.android.inputmethod.latin.utils.LocaleUtils;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
@@ -54,7 +54,8 @@
     private /* final */ Resources mResources;
     private /* final */ ConnectivityManager mConnectivityManager;
 
-    private final NeedsToDisplayLanguage mNeedsToDisplayLanguage = new NeedsToDisplayLanguage();
+    private final LanguageOnSpacebarHelper mLanguageOnSpacebarHelper =
+            new LanguageOnSpacebarHelper();
     private InputMethodInfo mShortcutInputMethodInfo;
     private InputMethodSubtype mShortcutSubtype;
     private InputMethodSubtype mNoLanguageSubtype;
@@ -127,7 +128,7 @@
     public void updateParametersOnStartInputView() {
         final List<InputMethodSubtype> enabledSubtypesOfThisIme =
                 mRichImm.getMyEnabledInputMethodSubtypeList(true);
-        mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size());
+        mLanguageOnSpacebarHelper.updateEnabledSubtypes(enabledSubtypesOfThisIme);
         updateShortcutIME();
     }
 
@@ -176,7 +177,7 @@
         final boolean sameLanguage = systemLocale.getLanguage().equals(newLocale.getLanguage());
         final boolean implicitlyEnabled =
                 mRichImm.checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype);
-        mNeedsToDisplayLanguage.updateIsSystemLanguageSameAsInputLanguage(
+        mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(
                 sameLocale || (sameLanguage && implicitlyEnabled));
 
         updateShortcutIME();
@@ -249,8 +250,8 @@
     // Subtype Switching functions //
     //////////////////////////////////
 
-    public boolean needsToDisplayLanguage(final InputMethodSubtype subtype) {
-        return mNeedsToDisplayLanguage.needsToDisplayLanguage(subtype);
+    public int getLanguageOnSpacebarFormatType(final InputMethodSubtype subtype) {
+        return mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(subtype);
     }
 
     public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 8a321e2..f31fb13 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -23,7 +23,6 @@
 import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 
 /**
@@ -51,11 +50,6 @@
     // The list of events that served to compose this string.
     private final ArrayList<Event> mEvents;
     private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH);
-    // This is the typed word, as a StringBuilder. This has the same contents as mPrimaryKeyCodes
-    // but under a StringBuilder representation for ease of use, depending on what is more useful
-    // at any given time. However this is not limited in size, while mPrimaryKeyCodes is limited
-    // to MAX_WORD_LENGTH code points.
-    private final StringBuilder mTypedWord;
     // The previous word (before the composing word). Used as context for suggestions. May be null
     // after resetting and before starting a new composing word, or when there is no context like
     // at the start of text for example. It can also be set to null externally when the user
@@ -73,6 +67,7 @@
     private String mRejectedBatchModeSuggestion;
 
     // Cache these values for performance
+    private CharSequence mTypedWordCache;
     private int mCapsCount;
     private int mDigitsCount;
     private int mCapitalizedMode;
@@ -93,7 +88,6 @@
         mCombinerChain = new CombinerChain();
         mPrimaryKeyCodes = new int[MAX_WORD_LENGTH];
         mEvents = CollectionUtils.newArrayList();
-        mTypedWord = new StringBuilder(MAX_WORD_LENGTH);
         mAutoCorrection = null;
         mTrailingSingleQuotesCount = 0;
         mIsResumed = false;
@@ -101,7 +95,7 @@
         mCursorPositionWithinWord = 0;
         mRejectedBatchModeSuggestion = null;
         mPreviousWordForSuggestion = null;
-        refreshSize();
+        refreshTypedWordCache();
     }
 
     /**
@@ -109,7 +103,6 @@
      */
     public void reset() {
         mCombinerChain.reset();
-        mTypedWord.setLength(0);
         mEvents.clear();
         mAutoCorrection = null;
         mCapsCount = 0;
@@ -121,11 +114,12 @@
         mCursorPositionWithinWord = 0;
         mRejectedBatchModeSuggestion = null;
         mPreviousWordForSuggestion = null;
-        refreshSize();
+        refreshTypedWordCache();
     }
 
-    private final void refreshSize() {
-        mCodePointSize = mTypedWord.codePointCount(0, mTypedWord.length());
+    private final void refreshTypedWordCache() {
+        mTypedWordCache = mCombinerChain.getComposingWordWithCombiningFeedback();
+        mCodePointSize = Character.codePointCount(mTypedWordCache, 0, mTypedWordCache.length());
     }
 
     /**
@@ -179,13 +173,7 @@
         final int keyX = event.mX;
         final int keyY = event.mY;
         final int newIndex = size();
-        mCombinerChain.processEvent(mEvents, event);
-        // TODO: remove mTypedWord and compute it dynamically when necessary. We also need to
-        // make the views of the composing word a SpannableString.
-        mTypedWord.replace(0, mTypedWord.length(),
-                mCombinerChain.getComposingWordWithCombiningFeedback().toString());
-        mEvents.add(event);
-        refreshSize();
+        processEvent(event);
         mCursorPositionWithinWord = mCodePointSize;
         if (newIndex < MAX_WORD_LENGTH) {
             mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE
@@ -210,6 +198,37 @@
         mAutoCorrection = null;
     }
 
+    private void processEvent(final Event event) {
+        mCombinerChain.processEvent(mEvents, event);
+        mEvents.add(event);
+        refreshTypedWordCache();
+    }
+
+    /**
+     * Delete the last composing unit as a result of hitting backspace.
+     */
+    public void deleteLast(final Event event) {
+        processEvent(event);
+        // We may have deleted the last one.
+        if (0 == size()) {
+            mIsFirstCharCapitalized = false;
+        }
+        if (mTrailingSingleQuotesCount > 0) {
+            --mTrailingSingleQuotesCount;
+        } else {
+            int i = mTypedWordCache.length();
+            while (i > 0) {
+                i = Character.offsetByCodePoints(mTypedWordCache, i, -1);
+                if (Constants.CODE_SINGLE_QUOTE != Character.codePointAt(mTypedWordCache, i)) {
+                    break;
+                }
+                ++mTrailingSingleQuotesCount;
+            }
+        }
+        mCursorPositionWithinWord = mCodePointSize;
+        mAutoCorrection = null;
+    }
+
     public void setCursorPositionWithinWord(final int posWithinWord) {
         mCursorPositionWithinWord = posWithinWord;
         // TODO: compute where that puts us inside the events
@@ -242,7 +261,7 @@
             // If we have more than MAX_WORD_LENGTH characters, we don't have everything inside
             // mPrimaryKeyCodes. This should be rare enough that we can afford to just compute
             // the array on the fly when this happens.
-            codePoints = StringUtils.toCodePointArray(mTypedWord.toString());
+            codePoints = StringUtils.toCodePointArray(mTypedWordCache);
         } else {
             codePoints = mPrimaryKeyCodes;
         }
@@ -307,38 +326,11 @@
     }
 
     /**
-     * Delete the last composing unit as a result of hitting backspace.
-     */
-    public void deleteLast(final Event event) {
-        mCombinerChain.processEvent(mEvents, event);
-        mTypedWord.replace(0, mTypedWord.length(),
-                mCombinerChain.getComposingWordWithCombiningFeedback().toString());
-        mEvents.add(event);
-        refreshSize();
-        // We may have deleted the last one.
-        if (0 == size()) {
-            mIsFirstCharCapitalized = false;
-        }
-        if (mTrailingSingleQuotesCount > 0) {
-            --mTrailingSingleQuotesCount;
-        } else {
-            int i = mTypedWord.length();
-            while (i > 0) {
-                i = mTypedWord.offsetByCodePoints(i, -1);
-                if (Constants.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break;
-                ++mTrailingSingleQuotesCount;
-            }
-        }
-        mCursorPositionWithinWord = mCodePointSize;
-        mAutoCorrection = null;
-    }
-
-    /**
      * Returns the word as it was typed, without any correction applied.
      * @return the word that was typed so far. Never returns null.
      */
     public String getTypedWord() {
-        return mTypedWord.toString();
+        return mTypedWordCache.toString();
     }
 
     public String getPreviousWordForSuggestion() {
@@ -447,7 +439,7 @@
         final int[] primaryKeyCodes = mPrimaryKeyCodes;
         mPrimaryKeyCodes = new int[MAX_WORD_LENGTH];
         final LastComposedWord lastComposedWord = new LastComposedWord(primaryKeyCodes, mEvents,
-                mInputPointers, mTypedWord.toString(), committedWord, separatorString,
+                mInputPointers, mTypedWordCache.toString(), committedWord, separatorString,
                 prevWord, mCapitalizedMode);
         mInputPointers.reset();
         if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
@@ -458,14 +450,13 @@
         mDigitsCount = 0;
         mIsBatchMode = false;
         mPreviousWordForSuggestion = committedWord.toString();
-        mTypedWord.setLength(0);
         mCombinerChain.reset();
         mEvents.clear();
         mCodePointSize = 0;
         mTrailingSingleQuotesCount = 0;
         mIsFirstCharCapitalized = false;
         mCapitalizedMode = CAPS_MODE_OFF;
-        refreshSize();
+        refreshTypedWordCache();
         mAutoCorrection = null;
         mCursorPositionWithinWord = 0;
         mIsResumed = false;
@@ -486,10 +477,8 @@
         mEvents.clear();
         Collections.copy(mEvents, lastComposedWord.mEvents);
         mInputPointers.set(lastComposedWord.mInputPointers);
-        mTypedWord.setLength(0);
         mCombinerChain.reset();
-        mTypedWord.append(lastComposedWord.mTypedWord);
-        refreshSize();
+        refreshTypedWordCache();
         mCapitalizedMode = lastComposedWord.mCapitalizedMode;
         mAutoCorrection = null; // This will be filled by the next call to updateSuggestion.
         mCursorPositionWithinWord = mCodePointSize;
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index ec6bd28..2fd8ace 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -219,13 +219,11 @@
                     Event.NOT_A_KEY_CODE /* keyCode*/,
                     Constants.SUGGESTION_STRIP_COORDINATE /* x */,
                     Constants.SUGGESTION_STRIP_COORDINATE /* y */);
-            final InputTransaction completeTransaction = onCodeInput(settingsValues, event,
-                    keyboardShiftState, handler);
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
                         false /* isBatchMode */, suggestedWords.mIsPrediction);
             }
-            return completeTransaction;
+            return onCodeInput(settingsValues, event, keyboardShiftState, handler);
         }
 
         final Event event = Event.createSuggestionPickedEvent(suggestionInfo);
@@ -601,7 +599,7 @@
     }
 
     public void onEndBatchInput(final InputPointers batchPointers) {
-        mInputLogicHandler.onEndBatchInput(batchPointers, mAutoCommitSequenceNumber);
+        mInputLogicHandler.updateTailBatchInput(batchPointers, mAutoCommitSequenceNumber);
         ++mAutoCommitSequenceNumber;
     }
 
@@ -1794,7 +1792,7 @@
      * @param settingsValues the current values of the settings.
      * @param suggestedWords suggestedWords to use.
      */
-    public void endBatchInputInternal(final SettingsValues settingsValues,
+    public void onUpdateTailBatchInputCompleted(final SettingsValues settingsValues,
             final SuggestedWords suggestedWords,
             // TODO: remove this argument
             final KeyboardSwitcher keyboardSwitcher) {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
index 42f0d7c..e3b8ab4 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
@@ -54,7 +54,8 @@
         @Override
         public void onCancelBatchInput() {}
         @Override
-        public void onEndBatchInput(final InputPointers batchPointers, final int sequenceNumber) {}
+        public void updateTailBatchInput(final InputPointers batchPointers,
+                final int sequenceNumber) {}
         @Override
         public void getSuggestedWords(final int sessionId, final int sequenceNumber,
                 final OnGetSuggestedWordsCallback callback) {}
@@ -106,7 +107,7 @@
      * Fetch suggestions corresponding to an update of a batch input.
      * @param batchPointers the updated pointers, including the part that was passed last time.
      * @param sequenceNumber the sequence number associated with this batch input.
-     * @param forEnd true if this is the end of a batch input, false if it's an update.
+     * @param isTailBatchInput true if this is the end of a batch input, false if it's an update.
      */
     // This method can be called from any thread and will see to it that the correct threads
     // are used for parts that require it. This method will send a message to the Non-UI handler
@@ -115,7 +116,7 @@
     // send a message to the UI handler in LatinIME so that showing suggestions can be done on
     // the UI thread.
     private void updateBatchInput(final InputPointers batchPointers,
-            final int sequenceNumber, final boolean forEnd) {
+            final int sequenceNumber, final boolean isTailBatchInput) {
         synchronized (mLock) {
             if (!mInBatchInput) {
                 // Batch input has ended or canceled while the message was being delivered.
@@ -136,12 +137,12 @@
                                 suggestedWords = mInputLogic.mSuggestedWords;
                             }
                             mLatinIME.mHandler.showGesturePreviewAndSuggestionStrip(suggestedWords,
-                                    forEnd /* dismissGestureFloatingPreviewText */);
-                            if (forEnd) {
+                                    isTailBatchInput /* dismissGestureFloatingPreviewText */);
+                            if (isTailBatchInput) {
                                 mInBatchInput = false;
                                 // The following call schedules onEndBatchInputInternal
                                 // to be called on the UI thread.
-                                mLatinIME.mHandler.onEndBatchInput(suggestedWords);
+                                mLatinIME.mHandler.showTailBatchInputResult(suggestedWords);
                             }
                         }
                     });
@@ -159,13 +160,13 @@
     // Called on the UI thread by InputLogic.
     public void onUpdateBatchInput(final InputPointers batchPointers,
             final int sequenceNumber) {
-        updateBatchInput(batchPointers, sequenceNumber, false /* forEnd */);
+        updateBatchInput(batchPointers, sequenceNumber, false /* isTailBatchInput */);
     }
 
     /**
      * Cancel a batch input.
      *
-     * Note that as opposed to onEndBatchInput, we do the UI side of this immediately on the
+     * Note that as opposed to updateTailBatchInput, we do the UI side of this immediately on the
      * same thread, rather than get this to call a method in LatinIME. This is because
      * canceling a batch input does not necessitate the long operation of pulling suggestions.
      */
@@ -177,17 +178,20 @@
     }
 
     /**
-     * Finish a batch input.
+     * Trigger an update for a tail batch input.
      *
-     * This fetches suggestions, updates the suggestion strip and commits the first suggestion.
-     * It also dismisses the floating text preview.
+     * A tail batch input is the last update for a gesture, the one that is triggered after the
+     * user lifts their finger. This method schedules fetching suggestions on the non-UI thread,
+     * then when the suggestions are computed it comes back on the UI thread to update the
+     * suggestion strip, commit the first suggestion, and dismiss the floating text preview.
      *
      * @param batchPointers the updated batch pointers.
      * @param sequenceNumber the sequence number associated with this batch input.
      */
     // Called on the UI thread by InputLogic.
-    public void onEndBatchInput(final InputPointers batchPointers, final int sequenceNumber) {
-        updateBatchInput(batchPointers, sequenceNumber, true /* forEnd */);
+    public void updateTailBatchInput(final InputPointers batchPointers,
+            final int sequenceNumber) {
+        updateBatchInput(batchPointers, sequenceNumber, true /* isTailBatchInput */);
     }
 
     public void getSuggestedWords(final int sessionId, final int sequenceNumber,
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index b9d526b..accbc8b 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -172,28 +172,29 @@
 
     private static final int[] EMPTY_CODEPOINTS = {};
 
-    public static int[] toCodePointArray(final String string) {
-        return toCodePointArray(string, 0, string.length());
+    public static int[] toCodePointArray(final CharSequence charSequence) {
+        return toCodePointArray(charSequence, 0, charSequence.length());
     }
 
     /**
      * Converts a range of a string to an array of code points.
-     * @param string the source string.
+     * @param charSequence the source string.
      * @param startIndex the start index inside the string in java chars, inclusive.
      * @param endIndex the end index inside the string in java chars, exclusive.
      * @return a new array of code points. At most endIndex - startIndex, but possibly less.
      */
-    public static int[] toCodePointArray(final String string,
+    public static int[] toCodePointArray(final CharSequence charSequence,
             final int startIndex, final int endIndex) {
-        final int length = string.length();
+        final int length = charSequence.length();
         if (length <= 0) {
             return EMPTY_CODEPOINTS;
         }
-        final int[] codePoints = new int[string.codePointCount(startIndex, endIndex)];
+        final int[] codePoints =
+                new int[Character.codePointCount(charSequence, startIndex, endIndex)];
         int destIndex = 0;
         for (int index = startIndex; index < endIndex;
-                index = string.offsetByCodePoints(index, 1)) {
-            codePoints[destIndex] = string.codePointAt(index);
+                index = Character.offsetByCodePoints(charSequence, index, 1)) {
+            codePoints[destIndex] = Character.codePointAt(charSequence, index);
             destIndex++;
         }
         return codePoints;
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index c1372bd..5c117ce 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -87,6 +87,8 @@
 LOCAL_LDFLAGS += -ldl
 
 include $(BUILD_SHARED_LIBRARY)
-
 #################### Clean up the tmp vars
 include $(LOCAL_PATH)/CleanupNativeFileList.mk
+
+#################### Unit test on host environment
+include $(LOCAL_PATH)/HostUnitTests.mk
diff --git a/native/jni/CleanupNativeFileList.mk b/native/jni/CleanupNativeFileList.mk
index 5420f16..1738f8c 100644
--- a/native/jni/CleanupNativeFileList.mk
+++ b/native/jni/CleanupNativeFileList.mk
@@ -13,5 +13,6 @@
 # limitations under the License.
 
 LATIN_IME_CORE_SRC_FILES :=
+LATIN_IME_CORE_TEST_FILES :=
 LATIN_IME_JNI_SRC_FILES :=
 LATIN_IME_SRC_DIR :=
diff --git a/native/jni/HostUnitTests.mk b/native/jni/HostUnitTests.mk
new file mode 100644
index 0000000..828edce
--- /dev/null
+++ b/native/jni/HostUnitTests.mk
@@ -0,0 +1,53 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+######################################
+include $(CLEAR_VARS)
+
+include $(LOCAL_PATH)/NativeFileList.mk
+
+#################### Host library for unit test
+# TODO: Remove -std=c++11 once it is set by default on host build.
+LATIN_IME_SRC_DIR := src
+LOCAL_CFLAGS += -std=c++11 -Wno-unused-parameter -Wno-unused-function
+LOCAL_CLANG := true
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(LATIN_IME_SRC_DIR)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE := liblatinime_host_static_for_unittests
+LOCAL_MODULE_TAGS := optional
+LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_SRC_FILES := $(addprefix $(LATIN_IME_SRC_DIR)/, $(LATIN_IME_CORE_SRC_FILES))
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+#################### Host native tests
+include $(CLEAR_VARS)
+LATIN_IME_TEST_SRC_DIR := tests
+# TODO: Remove -std=c++11 once it is set by default on host build.
+LOCAL_CFLAGS += -std=c++11 -Wno-unused-parameter -Wno-unused-function
+LOCAL_CLANG := true
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(LATIN_IME_SRC_DIR)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE := liblatinime_host_unittests
+LOCAL_MODULE_TAGS := tests
+LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_SRC_FILES := $(addprefix $(LATIN_IME_TEST_SRC_DIR)/, $(LATIN_IME_CORE_TEST_FILES))
+LOCAL_STATIC_LIBRARIES += liblatinime_host_static_for_unittests libgtest_host libgtest_main_host
+include $(BUILD_HOST_NATIVE_TEST)
+
+#################### Clean up the tmp vars
+LATIN_IME_SRC_DIR :=
+LATIN_IME_TEST_SRC_DIR :=
+include $(LOCAL_PATH)/CleanupNativeFileList.mk
diff --git a/native/jni/NativeFileList.mk b/native/jni/NativeFileList.mk
index 2ee4caa..3da9d56 100644
--- a/native/jni/NativeFileList.mk
+++ b/native/jni/NativeFileList.mk
@@ -99,3 +99,6 @@
         char_utils.cpp \
         log_utils.cpp \
         time_keeper.cpp)
+
+LATIN_IME_CORE_TEST_FILES := \
+    utils/autocorrection_threshold_utils_test.cpp
diff --git a/native/jni/run-tests.sh b/native/jni/run-tests.sh
new file mode 100755
index 0000000..5b60e0d
--- /dev/null
+++ b/native/jni/run-tests.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+# Copyright 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.
+
+if [[ $(type -t mmm) != function ]]; then
+echo "Usage:" 1>&2
+echo "    source $0" 1>&2
+echo "  or" 1>&2
+echo "    . $0" 1>&2
+if [[ ${BASH_SOURCE[0]} != $0 ]]; then return; else exit 1; fi
+fi
+
+pushd $PWD > /dev/null
+cd $(gettop)
+mmm -j16 packages/inputmethods/LatinIME/native/jni || \
+    make -j16 liblatinime_host_unittests
+${ANDROID_HOST_OUT}/bin/liblatinime_host_unittests
+popd > /dev/null
\ No newline at end of file
diff --git a/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp b/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp
index c4c5716..fa9600c 100644
--- a/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp
+++ b/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp
@@ -52,8 +52,8 @@
 const float ScoringParams::COST_NEW_WORD = 0.0314f;
 const float ScoringParams::COST_SECOND_OR_LATER_WORD_FIRST_CHAR_UPPERCASE = 0.3224f;
 const float ScoringParams::DISTANCE_WEIGHT_LANGUAGE = 1.1214f;
-const float ScoringParams::COST_FIRST_LOOKAHEAD = 0.4836f;
-const float ScoringParams::COST_LOOKAHEAD = 0.00624f;
+const float ScoringParams::COST_FIRST_COMPLETION = 0.4836f;
+const float ScoringParams::COST_COMPLETION = 0.00624f;
 const float ScoringParams::HAS_PROXIMITY_TERMINAL_COST = 0.0683f;
 const float ScoringParams::HAS_EDIT_CORRECTION_TERMINAL_COST = 0.0362f;
 const float ScoringParams::HAS_MULTI_WORD_TERMINAL_COST = 0.4182f;
diff --git a/native/jni/src/suggest/policyimpl/typing/scoring_params.h b/native/jni/src/suggest/policyimpl/typing/scoring_params.h
index de7410d..b669620 100644
--- a/native/jni/src/suggest/policyimpl/typing/scoring_params.h
+++ b/native/jni/src/suggest/policyimpl/typing/scoring_params.h
@@ -59,8 +59,8 @@
     static const float COST_NEW_WORD;
     static const float COST_SECOND_OR_LATER_WORD_FIRST_CHAR_UPPERCASE;
     static const float DISTANCE_WEIGHT_LANGUAGE;
-    static const float COST_FIRST_LOOKAHEAD;
-    static const float COST_LOOKAHEAD;
+    static const float COST_FIRST_COMPLETION;
+    static const float COST_COMPLETION;
     static const float HAS_PROXIMITY_TERMINAL_COST;
     static const float HAS_EDIT_CORRECTION_TERMINAL_COST;
     static const float HAS_MULTI_WORD_TERMINAL_COST;
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
index b36605a..0ba439b 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
@@ -166,8 +166,8 @@
         const bool firstCompletion = dicNode->getInputIndex(0)
                 == traverseSession->getInputSize();
         // TODO: Change the cost for the first completion for the gesture?
-        const float cost = firstCompletion ? ScoringParams::COST_FIRST_LOOKAHEAD
-                : ScoringParams::COST_LOOKAHEAD;
+        const float cost = firstCompletion ? ScoringParams::COST_FIRST_COMPLETION
+                : ScoringParams::COST_COMPLETION;
         return cost;
     }
 
diff --git a/native/jni/tests/utils/autocorrection_threshold_utils_test.cpp b/native/jni/tests/utils/autocorrection_threshold_utils_test.cpp
new file mode 100644
index 0000000..cc8db70
--- /dev/null
+++ b/native/jni/tests/utils/autocorrection_threshold_utils_test.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#include "utils/autocorrection_threshold_utils.h"
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+namespace latinime {
+namespace {
+
+int CalcEditDistance(const std::vector<int> &before,
+        const std::vector<int> &after) {
+    return AutocorrectionThresholdUtils::editDistance(
+            &before[0], before.size(), &after[0], after.size());
+}
+
+TEST(AutocorrectionThresholdUtilsTest, SameData) {
+    EXPECT_EQ(0, CalcEditDistance({1}, {1}));
+    EXPECT_EQ(0, CalcEditDistance({2, 2}, {2, 2}));
+    EXPECT_EQ(0, CalcEditDistance({3, 3, 3}, {3, 3, 3}));
+}
+
+}  // namespace
+}  // namespace latinime
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelperTests.java b/tests/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelperTests.java
new file mode 100644
index 0000000..0be1e37
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelperTests.java
@@ -0,0 +1,167 @@
+/*
+ * 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.keyboard.internal;
+
+import static com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper.FORMAT_TYPE_FULL_LOCALE;
+import static com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper.FORMAT_TYPE_LANGUAGE_ONLY;
+import static com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper.FORMAT_TYPE_NONE;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+@SmallTest
+public class LanguageOnSpacebarHelperTests extends AndroidTestCase {
+    private final LanguageOnSpacebarHelper mLanguageOnSpacebarHelper =
+            new LanguageOnSpacebarHelper();
+
+    private RichInputMethodManager mRichImm;
+
+    InputMethodSubtype EN_US_QWERTY;
+    InputMethodSubtype EN_GB_QWERTY;
+    InputMethodSubtype FR_AZERTY;
+    InputMethodSubtype FR_CA_QWERTY;
+    InputMethodSubtype FR_CH_SWISS;
+    InputMethodSubtype FR_CH_QWERTY;
+    InputMethodSubtype FR_CH_QWERTZ;
+    InputMethodSubtype ZZ_QWERTY;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        final Context context = getContext();
+        RichInputMethodManager.init(context);
+        mRichImm = RichInputMethodManager.getInstance();
+        SubtypeLocaleUtils.init(context);
+
+        EN_US_QWERTY = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.US.toString(), "qwerty");
+        EN_GB_QWERTY = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.UK.toString(), "qwerty");
+        FR_AZERTY = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.FRENCH.toString(), "azerty");
+        FR_CA_QWERTY = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.CANADA_FRENCH.toString(), "qwerty");
+        FR_CH_SWISS = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "fr_CH", "swiss");
+        FR_CH_QWERTZ = AdditionalSubtypeUtils.createAdditionalSubtype(
+                "fr_CH", "qwertz", null);
+        FR_CH_QWERTY = AdditionalSubtypeUtils.createAdditionalSubtype(
+                "fr_CH", "qwerty", null);
+        ZZ_QWERTY = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                SubtypeLocaleUtils.NO_LANGUAGE, "qwerty");
+    }
+
+    private static List<InputMethodSubtype> asList(final InputMethodSubtype ... subtypes) {
+        return Arrays.asList(subtypes);
+    }
+
+    public void testOneSubtype() {
+        mLanguageOnSpacebarHelper.updateEnabledSubtypes(asList(EN_US_QWERTY));
+        mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(true /* isSame */);
+        assertEquals("one same English (US)", FORMAT_TYPE_NONE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY));
+        assertEquals("one same NoLanguage", FORMAT_TYPE_FULL_LOCALE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY));
+
+        mLanguageOnSpacebarHelper.updateEnabledSubtypes(asList(FR_AZERTY));
+        mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(false /* isSame */);
+        assertEquals("one diff English (US)", FORMAT_TYPE_LANGUAGE_ONLY,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY));
+        assertEquals("one diff NoLanguage", FORMAT_TYPE_FULL_LOCALE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY));
+    }
+
+    public void testTwoSubtypes() {
+        mLanguageOnSpacebarHelper.updateEnabledSubtypes(asList(EN_US_QWERTY, FR_AZERTY));
+
+        mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(true /* isSame */);
+        assertEquals("two same English (US)", FORMAT_TYPE_LANGUAGE_ONLY,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY));
+        assertEquals("two same French)", FORMAT_TYPE_LANGUAGE_ONLY,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_AZERTY));
+        assertEquals("two same NoLanguage", FORMAT_TYPE_FULL_LOCALE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY));
+
+        mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(false /* isSame */);
+        assertEquals("two diff English (US)", FORMAT_TYPE_LANGUAGE_ONLY,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY));
+        assertEquals("two diff French", FORMAT_TYPE_LANGUAGE_ONLY,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_AZERTY));
+        assertEquals("two diff NoLanguage", FORMAT_TYPE_FULL_LOCALE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY));
+    }
+
+    public void testSameLanuageSubtypes() {
+        mLanguageOnSpacebarHelper.updateEnabledSubtypes(
+                asList(EN_US_QWERTY, EN_GB_QWERTY, FR_AZERTY, ZZ_QWERTY));
+
+        mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(true /* isSame */);
+        assertEquals("two same English (US)", FORMAT_TYPE_FULL_LOCALE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY));
+        assertEquals("two same English (UK)", FORMAT_TYPE_FULL_LOCALE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_GB_QWERTY));
+        assertEquals("two same NoLanguage", FORMAT_TYPE_FULL_LOCALE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY));
+
+        mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(false /* isSame */);
+        assertEquals("two diff English (US)", FORMAT_TYPE_FULL_LOCALE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_US_QWERTY));
+        assertEquals("two diff English (UK)", FORMAT_TYPE_FULL_LOCALE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(EN_GB_QWERTY));
+        assertEquals("two diff NoLanguage", FORMAT_TYPE_FULL_LOCALE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(ZZ_QWERTY));
+    }
+
+    public void testMultiSameLanuageSubtypes() {
+        mLanguageOnSpacebarHelper.updateEnabledSubtypes(
+                asList(FR_AZERTY, FR_CA_QWERTY, FR_CH_SWISS, FR_CH_QWERTY, FR_CH_QWERTZ));
+
+        mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(true /* isSame */);
+        assertEquals("multi same French", FORMAT_TYPE_LANGUAGE_ONLY,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_AZERTY));
+        assertEquals("multi same French (CA)", FORMAT_TYPE_FULL_LOCALE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CA_QWERTY));
+        assertEquals("multi same French (CH)", FORMAT_TYPE_LANGUAGE_ONLY,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_SWISS));
+        assertEquals("multi same French (CH) (QWERTY)", FORMAT_TYPE_FULL_LOCALE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_QWERTY));
+        assertEquals("multi same French (CH) (QWERTZ)", FORMAT_TYPE_LANGUAGE_ONLY,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_QWERTZ));
+
+        mLanguageOnSpacebarHelper.updateIsSystemLanguageSameAsInputLanguage(false /* isSame */);
+        assertEquals("multi diff French", FORMAT_TYPE_LANGUAGE_ONLY,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_AZERTY));
+        assertEquals("multi diff French (CA)", FORMAT_TYPE_FULL_LOCALE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CA_QWERTY));
+        assertEquals("multi diff French (CH)", FORMAT_TYPE_LANGUAGE_ONLY,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_SWISS));
+        assertEquals("multi diff French (CH) (QWERTY)", FORMAT_TYPE_FULL_LOCALE,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_QWERTY));
+        assertEquals("multi diff French (CH) (QWERTZ)", FORMAT_TYPE_LANGUAGE_ONLY,
+                mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(FR_CH_QWERTZ));
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/NeedsToDisplayLanguageTests.java b/tests/src/com/android/inputmethod/keyboard/internal/NeedsToDisplayLanguageTests.java
deleted file mode 100644
index e03bce1..0000000
--- a/tests/src/com/android/inputmethod/keyboard/internal/NeedsToDisplayLanguageTests.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.keyboard.internal;
-
-import android.content.Context;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.view.inputmethod.InputMethodSubtype;
-
-import com.android.inputmethod.latin.RichInputMethodManager;
-import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
-
-import java.util.Locale;
-
-@SmallTest
-public class NeedsToDisplayLanguageTests extends AndroidTestCase {
-    private final NeedsToDisplayLanguage mNeedsToDisplayLanguage = new NeedsToDisplayLanguage();
-
-    private RichInputMethodManager mRichImm;
-
-    InputMethodSubtype EN_US;
-    InputMethodSubtype FR;
-    InputMethodSubtype ZZ;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        final Context context = getContext();
-        RichInputMethodManager.init(context);
-        mRichImm = RichInputMethodManager.getInstance();
-        SubtypeLocaleUtils.init(context);
-
-        EN_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
-                Locale.US.toString(), "qwerty");
-        FR = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
-                Locale.FRENCH.toString(), "azerty");
-        ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
-                SubtypeLocaleUtils.NO_LANGUAGE, "qwerty");
-    }
-
-    public void testOneSubtype() {
-        mNeedsToDisplayLanguage.updateEnabledSubtypeCount(1);
-
-        mNeedsToDisplayLanguage.updateIsSystemLanguageSameAsInputLanguage(true /* isSame */);
-        assertFalse("one same English (US)", mNeedsToDisplayLanguage.needsToDisplayLanguage(EN_US));
-        assertTrue("one same NoLanguage", mNeedsToDisplayLanguage.needsToDisplayLanguage(ZZ));
-
-        mNeedsToDisplayLanguage.updateIsSystemLanguageSameAsInputLanguage(false /* isSame */);
-        assertTrue("one diff English (US)", mNeedsToDisplayLanguage.needsToDisplayLanguage(EN_US));
-        assertTrue("one diff NoLanguage", mNeedsToDisplayLanguage.needsToDisplayLanguage(ZZ));
-    }
-
-    public void testTwoSubtype() {
-        mNeedsToDisplayLanguage.updateEnabledSubtypeCount(2);
-
-        mNeedsToDisplayLanguage.updateIsSystemLanguageSameAsInputLanguage(true /* isSame */);
-        assertTrue("two same English (US)", mNeedsToDisplayLanguage.needsToDisplayLanguage(EN_US));
-        assertTrue("two same French", mNeedsToDisplayLanguage.needsToDisplayLanguage(FR));
-        assertTrue("two same NoLanguage", mNeedsToDisplayLanguage.needsToDisplayLanguage(ZZ));
-
-        mNeedsToDisplayLanguage.updateIsSystemLanguageSameAsInputLanguage(false /* isSame */);
-        assertTrue("two diff English (US)", mNeedsToDisplayLanguage.needsToDisplayLanguage(EN_US));
-        assertTrue("two diff French", mNeedsToDisplayLanguage.needsToDisplayLanguage(ZZ));
-        assertTrue("two diff NoLanguage", mNeedsToDisplayLanguage.needsToDisplayLanguage(FR));
-    }
-}
diff --git a/tools/dicttool/tests/etc/test-dicttool.sh b/tools/dicttool/tests/etc/test-dicttool.sh
index 5eb44fc..f96db68 100755
--- a/tools/dicttool/tests/etc/test-dicttool.sh
+++ b/tools/dicttool/tests/etc/test-dicttool.sh
@@ -18,7 +18,7 @@
 echo "    source $0" 1>&2
 echo "  or" 1>&2
 echo "    . $0" 1>&2
-exit 1
+if [[ ${BASH_SOURCE[0]} != $0 ]]; then return; else exit 1; fi
 fi
 
 find out -name "dicttool_aosp*" -exec rm -rf {} \; > /dev/null 2>&1