Merge "Remove useless methods from DicNode."
diff --git a/java/src/com/android/inputmethod/event/InputTransaction.java b/java/src/com/android/inputmethod/event/InputTransaction.java
index 2e9014f..4fe9b40 100644
--- a/java/src/com/android/inputmethod/event/InputTransaction.java
+++ b/java/src/com/android/inputmethod/event/InputTransaction.java
@@ -40,6 +40,7 @@
 
     // Outputs
     private int mRequiredShiftUpdate = SHIFT_NO_UPDATE;
+    private boolean mRequiresUpdateSuggestions = false;
 
     public InputTransaction(final SettingsValues settingsValues, final Event event,
             final long timestamp, final int spaceState, final int shiftState) {
@@ -50,10 +51,34 @@
         mShiftState = shiftState;
     }
 
+    /**
+     * Indicate that this transaction requires some type of shift update.
+     * @param updateType What type of shift update this requires.
+     */
     public void requireShiftUpdate(final int updateType) {
         mRequiredShiftUpdate = Math.max(mRequiredShiftUpdate, updateType);
     }
+
+    /**
+     * Gets what type of shift update this transaction requires.
+     * @return The shift update type.
+     */
     public int getRequiredShiftUpdate() {
         return mRequiredShiftUpdate;
     }
+
+    /**
+     * Indicate that this transaction requires updating the suggestions.
+     */
+    public void setRequiresUpdateSuggestions() {
+        mRequiresUpdateSuggestions = true;
+    }
+
+    /**
+     * Find out whether this transaction requires updating the suggestions.
+     * @return Whether this transaction requires updating the suggestions.
+     */
+    public boolean requiresUpdateSuggestions() {
+        return mRequiresUpdateSuggestions;
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 8246c92..03425ef 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -60,7 +60,7 @@
 import com.android.inputmethod.latin.settings.DebugSettings;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
-import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+import com.android.inputmethod.latin.utils.SpacebarLanguageUtils;
 import com.android.inputmethod.latin.utils.TypefaceUtils;
 import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils;
 import com.android.inputmethod.research.ResearchLogger;
@@ -918,14 +918,13 @@
     // Layout language name on spacebar.
     private String layoutLanguageOnSpacebar(final Paint paint,
             final InputMethodSubtype subtype, final int width) {
-
         // Choose appropriate language name to fit into the width.
-        final String fullText = SubtypeLocaleUtils.getFullDisplayName(subtype);
+        final String fullText = SpacebarLanguageUtils.getFullDisplayName(subtype);
         if (fitsTextIntoWidth(width, fullText, paint)) {
             return fullText;
         }
 
-        final String middleText = SubtypeLocaleUtils.getMiddleDisplayName(subtype);
+        final String middleText = SpacebarLanguageUtils.getMiddleDisplayName(subtype);
         if (fitsTextIntoWidth(width, middleText, paint)) {
             return middleText;
         }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 0c0be82..53e6232 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -38,7 +38,6 @@
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.Message;
-import android.os.SystemClock;
 import android.preference.PreferenceManager;
 import android.text.InputType;
 import android.text.TextUtils;
@@ -1230,7 +1229,7 @@
         final InputTransaction completeInputTransaction =
                 mInputLogic.onCodeInput(mSettings.getCurrent(), event,
                         mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
-        updateShiftModeAfterInputTransaction(completeInputTransaction.getRequiredShiftUpdate());
+        updateStateAfterInputTransaction(completeInputTransaction);
         mKeyboardSwitcher.onCodeInput(codePoint);
     }
 
@@ -1450,7 +1449,7 @@
         final InputTransaction completeInputTransaction = mInputLogic.onPickSuggestionManually(
                 mSettings.getCurrent(), index, suggestionInfo,
                 mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
-        updateShiftModeAfterInputTransaction(completeInputTransaction.getRequiredShiftUpdate());
+        updateStateAfterInputTransaction(completeInputTransaction);
     }
 
     @Override
@@ -1488,8 +1487,14 @@
         }
     }
 
-    private void updateShiftModeAfterInputTransaction(final int requiredShiftUpdate) {
-        switch (requiredShiftUpdate) {
+    /**
+     * After an input transaction has been executed, some state must be updated. This includes
+     * the shift state of the keyboard and suggestions. This method looks at the finished
+     * inputTransaction to find out what is necessary and updates the state accordingly.
+     * @param inputTransaction The transaction that has been executed.
+     */
+    private void updateStateAfterInputTransaction(final InputTransaction inputTransaction) {
+        switch (inputTransaction.getRequiredShiftUpdate()) {
         case InputTransaction.SHIFT_UPDATE_LATER:
             mHandler.postUpdateShiftState();
             break;
@@ -1498,6 +1503,9 @@
             break;
         default: // SHIFT_NO_UPDATE
         }
+        if (inputTransaction.requiresUpdateSuggestions()) {
+            mHandler.postUpdateSuggestionStrip();
+        }
     }
 
     private void hapticAndAudioFeedback(final int code, final int repeatCount) {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index e2cdbb3..ec6bd28 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -130,8 +130,11 @@
         // so we try using some heuristics to find out about these and fix them.
         mConnection.tryFixLyingCursorPosition();
         cancelDoubleSpacePeriodCountdown();
-        mInputLogicHandler.destroy();
-        mInputLogicHandler = new InputLogicHandler(mLatinIME, this);
+        if (InputLogicHandler.NULL_HANDLER == mInputLogicHandler) {
+            mInputLogicHandler = new InputLogicHandler(mLatinIME, this);
+        } else {
+            mInputLogicHandler.reset();
+        }
     }
 
     /**
@@ -142,8 +145,7 @@
             mConnection.finishComposingText();
         }
         resetComposingState(true /* alsoResetLastComposedWord */);
-        mInputLogicHandler.destroy();
-        mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
+        mInputLogicHandler.reset();
     }
 
     /**
@@ -403,7 +405,7 @@
             // A special key, like delete, shift, emoji, or the settings key.
             switch (event.mKeyCode) {
             case Constants.CODE_DELETE:
-                handleBackspace(inputTransaction, handler);
+                handleBackspace(inputTransaction);
                 LatinImeLogger.logOnDelete(event.mX, event.mY);
                 break;
             case Constants.CODE_SHIFT:
@@ -672,7 +674,7 @@
                     commitTyped(inputTransaction.mSettingsValues, LastComposedWord.NOT_A_SEPARATOR);
                 }
             }
-            handleNonSeparator(inputTransaction.mSettingsValues, inputTransaction, handler);
+            handleNonSeparator(inputTransaction.mSettingsValues, inputTransaction);
         }
         return didAutoCorrect;
     }
@@ -683,9 +685,7 @@
      * @param inputTransaction The transaction in progress.
      */
     private void handleNonSeparator(final SettingsValues settingsValues,
-            final InputTransaction inputTransaction,
-            // TODO: Remove this argument
-            final LatinIME.UIHandler handler) {
+            final InputTransaction inputTransaction) {
         final int codePoint = inputTransaction.mEvent.mCodePoint;
         // TODO: refactor this method to stop flipping isComposingWord around all the time, and
         // make it shorter (possibly cut into several pieces). Also factor handleNonSpecialCharacter
@@ -761,7 +761,7 @@
             // In case the "add to dictionary" hint was still displayed.
             mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
         }
-        handler.postUpdateSuggestionStrip();
+        inputTransaction.setRequiresUpdateSuggestions();
         if (settingsValues.mIsInternal) {
             LatinImeLoggerUtils.onNonSeparator((char)codePoint, inputTransaction.mEvent.mX,
                     inputTransaction.mEvent.mY);
@@ -843,7 +843,7 @@
             }
 
             startDoubleSpacePeriodCountdown(inputTransaction);
-            handler.postUpdateSuggestionStrip();
+            inputTransaction.setRequiresUpdateSuggestions();
         } else {
             if (swapWeakSpace) {
                 swapSwapperAndSpace(inputTransaction);
@@ -879,9 +879,7 @@
      * Handle a press on the backspace key.
      * @param inputTransaction The transaction in progress.
      */
-    private void handleBackspace(final InputTransaction inputTransaction,
-            // TODO: remove this argument
-            final LatinIME.UIHandler handler) {
+    private void handleBackspace(final InputTransaction inputTransaction) {
         mSpaceState = SpaceState.NONE;
         mDeleteCount++;
 
@@ -910,7 +908,7 @@
                 mWordComposer.deleteLast(inputTransaction.mEvent);
             }
             mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
-            handler.postUpdateSuggestionStrip();
+            inputTransaction.setRequiresUpdateSuggestions();
             if (!mWordComposer.isComposingWord()) {
                 // If we just removed the last character, auto-caps mode may have changed so we
                 // need to re-evaluate.
@@ -921,7 +919,7 @@
                 if (inputTransaction.mSettingsValues.mIsInternal) {
                     LatinImeLoggerUtils.onAutoCorrectionCancellation();
                 }
-                revertCommit(inputTransaction.mSettingsValues, handler);
+                revertCommit(inputTransaction);
                 return;
             }
             if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
@@ -1400,11 +1398,9 @@
      *
      * This is triggered upon pressing backspace just after a commit with auto-correction.
      *
-     * @param settingsValues the current settings values.
+     * @param inputTransaction The transaction in progress.
      */
-    private void revertCommit(final SettingsValues settingsValues,
-            // TODO: remove this argument
-            final LatinIME.UIHandler handler) {
+    private void revertCommit(final InputTransaction inputTransaction) {
         final String previousWord = mLastComposedWord.mPrevWord;
         final CharSequence originallyTypedWord = mLastComposedWord.mTypedWord;
         final CharSequence committedWord = mLastComposedWord.mCommittedWord;
@@ -1448,7 +1444,8 @@
                 // Given this, we add it to the list of suggestions, otherwise we discard it.
                 if (span instanceof SuggestionSpan) {
                     final SuggestionSpan suggestionSpan = (SuggestionSpan)span;
-                    if (!suggestionSpan.getLocale().equals(settingsValues.mLocale.toString())) {
+                    if (!suggestionSpan.getLocale().equals(
+                            inputTransaction.mSettingsValues.mLocale.toString())) {
                         continue;
                     }
                     for (final String suggestion : suggestionSpan.getSuggestions()) {
@@ -1463,11 +1460,11 @@
                 }
             }
             // Add the suggestion list to the list of suggestions.
-            textToCommit.setSpan(new SuggestionSpan(settingsValues.mLocale,
+            textToCommit.setSpan(new SuggestionSpan(inputTransaction.mSettingsValues.mLocale,
                     suggestions.toArray(new String[suggestions.size()]), 0 /* flags */),
                     0 /* start */, lastCharIndex /* end */, 0 /* flags */);
         }
-        if (settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) {
+        if (inputTransaction.mSettingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) {
             // For languages with spaces, we revert to the typed string, but the cursor is still
             // after the separator so we don't resume suggestions. If the user wants to correct
             // the word, they have to press backspace again.
@@ -1480,7 +1477,7 @@
                     mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), previousWord);
             mConnection.setComposingText(textToCommit, 1);
         }
-        if (settingsValues.mIsInternal) {
+        if (inputTransaction.mSettingsValues.mIsInternal) {
             LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         }
@@ -1493,7 +1490,7 @@
         // separator.
         mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
         // We have a separator between the word and the cursor: we should show predictions.
-        handler.postUpdateSuggestionStrip();
+        inputTransaction.setRequiresUpdateSuggestions();
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
index db96de3..42f0d7c 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
@@ -43,7 +43,7 @@
     // is initialized, though probably only the monkey can actually do this.
     public static final InputLogicHandler NULL_HANDLER = new InputLogicHandler() {
         @Override
-        public void destroy() {}
+        public void reset() {}
         @Override
         public boolean handleMessage(final Message msg) { return true; }
         @Override
@@ -75,8 +75,8 @@
         mInputLogic = inputLogic;
     }
 
-    public void destroy() {
-        mNonUIThreadHandler.getLooper().quit();
+    public void reset() {
+        mNonUIThreadHandler.removeCallbacksAndMessages(null);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index a07e8eb..6a52481 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -268,6 +268,7 @@
             // if it doesn't. See documentation for binarySearch.
             final int insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1;
 
+            // Weak <- insertIndex == 0, ..., insertIndex == mLength -> Strong
             if (insertIndex == 0 && mLength >= mMaxLength) {
                 // In the future, we may want to keep track of the best suggestion score even if
                 // we are asked for 0 suggestions. In this case, we can use the following
@@ -285,11 +286,6 @@
                 // }
                 return true;
             }
-            if (insertIndex >= mMaxLength) {
-                // We found a suggestion, but its score is too weak to be kept considering
-                // the suggestion limit.
-                return true;
-            }
 
             final String wordString = new String(word, wordOffset, wordLength);
             if (mLength < mMaxLength) {
@@ -297,12 +293,13 @@
                 ++mLength;
                 System.arraycopy(mScores, insertIndex, mScores, insertIndex + 1, copyLen);
                 mSuggestions.add(insertIndex, wordString);
+                mScores[insertIndex] = score;
             } else {
-                System.arraycopy(mScores, 1, mScores, 0, insertIndex);
+                System.arraycopy(mScores, 1, mScores, 0, insertIndex - 1);
                 mSuggestions.add(insertIndex, wordString);
                 mSuggestions.remove(0);
+                mScores[insertIndex - 1] = score;
             }
-            mScores[insertIndex] = score;
 
             return true;
         }
diff --git a/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java b/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java
new file mode 100644
index 0000000..89837c6
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java
@@ -0,0 +1,61 @@
+/*
+ * 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 android.view.inputmethod.InputMethodSubtype;
+
+import java.util.Locale;
+
+public final class SpacebarLanguageUtils {
+    private SpacebarLanguageUtils() {
+        // Intentional empty constructor for utility class.
+    }
+
+    // InputMethodSubtype's display name for spacebar text in its locale.
+    //        isAdditionalSubtype (T=true, F=false)
+    // locale layout  |  Middle      Full
+    // ------ ------- - --------- ----------------------
+    //  en_US qwerty  F  English   English (US)           exception
+    //  en_GB qwerty  F  English   English (UK)           exception
+    //  es_US spanish F  Español   Español (EE.UU.)       exception
+    //  fr    azerty  F  Français  Français
+    //  fr_CA qwerty  F  Français  Français (Canada)
+    //  fr_CH swiss   F  Français  Français (Suisse)
+    //  de    qwertz  F  Deutsch   Deutsch
+    //  de_CH swiss   T  Deutsch   Deutsch (Schweiz)
+    //  zz    qwerty  F  QWERTY    QWERTY
+    //  fr    qwertz  T  Français  Français
+    //  de    qwerty  T  Deutsch   Deutsch
+    //  en_US azerty  T  English   English (US)
+    //  zz    azerty  T  AZERTY    AZERTY
+    // Get InputMethodSubtype's full display name in its locale.
+    public static String getFullDisplayName(final InputMethodSubtype subtype) {
+        if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
+            return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
+        }
+        return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(subtype.getLocale());
+    }
+
+    // Get InputMethodSubtype's middle display name in its locale.
+    public static String getMiddleDisplayName(final InputMethodSubtype subtype) {
+        if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
+            return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
+        }
+        final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype);
+        return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(locale.getLanguage());
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 4f556f9..2452864 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -292,41 +292,6 @@
         return keyboardLayoutSet;
     }
 
-    // InputMethodSubtype's display name for spacebar text in its locale.
-    //        isAdditionalSubtype (T=true, F=false)
-    // locale layout  |  Middle      Full
-    // ------ ------- - --------- ----------------------
-    //  en_US qwerty  F  English   English (US)           exception
-    //  en_GB qwerty  F  English   English (UK)           exception
-    //  es_US spanish F  Español   Español (EE.UU.)       exception
-    //  fr    azerty  F  Français  Français
-    //  fr_CA qwerty  F  Français  Français (Canada)
-    //  fr_CH swiss   F  Français  Français (Suisse)
-    //  de    qwertz  F  Deutsch   Deutsch
-    //  de_CH swiss   T  Deutsch   Deutsch (Schweiz)
-    //  zz    qwerty  F  QWERTY    QWERTY
-    //  fr    qwertz  T  Français  Français
-    //  de    qwerty  T  Deutsch   Deutsch
-    //  en_US azerty  T  English   English (US)
-    //  zz    azerty  T  AZERTY    AZERTY
-
-    // Get InputMethodSubtype's full display name in its locale.
-    public static String getFullDisplayName(final InputMethodSubtype subtype) {
-        if (isNoLanguage(subtype)) {
-            return getKeyboardLayoutSetDisplayName(subtype);
-        }
-        return getSubtypeLocaleDisplayName(subtype.getLocale());
-    }
-
-    // Get InputMethodSubtype's middle display name in its locale.
-    public static String getMiddleDisplayName(final InputMethodSubtype subtype) {
-        if (isNoLanguage(subtype)) {
-            return getKeyboardLayoutSetDisplayName(subtype);
-        }
-        final Locale locale = getSubtypeLocale(subtype);
-        return getSubtypeLocaleDisplayName(locale.getLanguage());
-    }
-
     // TODO: Get this information from the framework instead of maintaining here by ourselves.
     // Sorted list of known Right-To-Left language codes.
     private static final String[] SORTED_RTL_LANGUAGES = {
diff --git a/native/jni/src/suggest/core/result/suggestions_output_utils.cpp b/native/jni/src/suggest/core/result/suggestions_output_utils.cpp
index d07f5ca..83140f1 100644
--- a/native/jni/src/suggest/core/result/suggestions_output_utils.cpp
+++ b/native/jni/src/suggest/core/result/suggestions_output_utils.cpp
@@ -17,11 +17,11 @@
 #include "suggest/core/result/suggestions_output_utils.h"
 
 #include <algorithm>
+#include <vector>
 
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_utils.h"
 #include "suggest/core/dictionary/binary_dictionary_shortcut_iterator.h"
-#include "suggest/core/dictionary/dictionary.h"
 #include "suggest/core/dictionary/error_type_utils.h"
 #include "suggest/core/policy/scoring.h"
 #include "suggest/core/result/suggestion_results.h"
@@ -31,110 +31,113 @@
 
 const int SuggestionsOutputUtils::MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT = 16;
 
-// TODO: Split this method.
 /* static */ void SuggestionsOutputUtils::outputSuggestions(
         const Scoring *const scoringPolicy, DicTraverseSession *traverseSession,
         SuggestionResults *const outSuggestionResults) {
 #if DEBUG_EVALUATE_MOST_PROBABLE_STRING
     const int terminalSize = 0;
 #else
-    const int terminalSize = std::min(MAX_RESULTS,
-            static_cast<int>(traverseSession->getDicTraverseCache()->terminalSize()));
+    const int terminalSize = traverseSession->getDicTraverseCache()->terminalSize();
 #endif
-    DicNode terminals[MAX_RESULTS]; // Avoiding non-POD variable length array
-
+    std::vector<DicNode> terminals(terminalSize);
     for (int index = terminalSize - 1; index >= 0; --index) {
         traverseSession->getDicTraverseCache()->popTerminal(&terminals[index]);
     }
 
     const float languageWeight = scoringPolicy->getAdjustedLanguageWeight(
-            traverseSession, terminals, terminalSize);
+            traverseSession, terminals.data(), terminalSize);
     // Force autocorrection for obvious long multi-word suggestions when the top suggestion is
     // a long multiple words suggestion.
     // TODO: Implement a smarter auto-commit method for handling multi-word suggestions.
-    // traverseSession->isPartiallyCommited() always returns false because we never auto partial
-    // commit for now.
-    const bool forceCommitMultiWords = (terminalSize > 0) ?
-            scoringPolicy->autoCorrectsToMultiWordSuggestionIfTop()
-                    && (traverseSession->isPartiallyCommited()
-                            || (traverseSession->getInputSize()
-                                    >= MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT
-                                            && terminals[0].hasMultipleWords())) : false;
+    const bool forceCommitMultiWords = scoringPolicy->autoCorrectsToMultiWordSuggestionIfTop()
+            && (traverseSession->getInputSize() >= MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT
+                    && !terminals.empty() && terminals.front().hasMultipleWords());
     // TODO: have partial commit work even with multiple pointers.
     const bool outputSecondWordFirstLetterInputIndex =
             traverseSession->isOnlyOnePointerUsed(0 /* pointerId */);
     const bool boostExactMatches = traverseSession->getDictionaryStructurePolicy()->
             getHeaderStructurePolicy()->shouldBoostExactMatches();
 
-    int codePoints[MAX_WORD_LENGTH];
     // Output suggestion results here
-    for (int terminalIndex = 0; terminalIndex < terminalSize; ++terminalIndex) {
-        DicNode *terminalDicNode = &terminals[terminalIndex];
-        if (DEBUG_GEO_FULL) {
-            terminalDicNode->dump("OUT:");
-        }
-        const float doubleLetterCost =
-                scoringPolicy->getDoubleLetterDemotionDistanceCost(terminalDicNode);
-        const float compoundDistance = terminalDicNode->getCompoundDistance(languageWeight)
-                + doubleLetterCost;
-        const bool isPossiblyOffensiveWord =
-                traverseSession->getDictionaryStructurePolicy()->getProbability(
-                        terminalDicNode->getProbability(), NOT_A_PROBABILITY) <= 0;
-        const bool isExactMatch =
-                ErrorTypeUtils::isExactMatch(terminalDicNode->getContainedErrorTypes());
-        const bool isFirstCharUppercase = terminalDicNode->isFirstCharUppercase();
-        // Heuristic: We exclude probability=0 first-char-uppercase words from exact match.
-        // (e.g. "AMD" and "and")
-        const bool isSafeExactMatch = isExactMatch
-                && !(isPossiblyOffensiveWord && isFirstCharUppercase);
-        const int outputTypeFlags =
-                (isPossiblyOffensiveWord ? Dictionary::KIND_FLAG_POSSIBLY_OFFENSIVE : 0)
-                | ((isSafeExactMatch && boostExactMatches) ? Dictionary::KIND_FLAG_EXACT_MATCH : 0);
-
-        // Entries that are blacklisted or do not represent a word should not be output.
-        const bool isValidWord = !terminalDicNode->isBlacklistedOrNotAWord();
-
-        // Increase output score of top typing suggestion to ensure autocorrection.
-        // TODO: Better integration with java side autocorrection logic.
-        const int finalScore = scoringPolicy->calculateFinalScore(
-                compoundDistance, traverseSession->getInputSize(),
-                terminalDicNode->getContainedErrorTypes(),
-                (forceCommitMultiWords && terminalDicNode->hasMultipleWords())
-                         || (isValidWord && scoringPolicy->doesAutoCorrectValidWord()),
-                boostExactMatches);
-
-        // Don't output invalid words. However, we still need to submit their shortcuts if any.
-        if (isValidWord) {
-            terminalDicNode->outputResult(codePoints);
-            const int indexToPartialCommit = outputSecondWordFirstLetterInputIndex ?
-                    terminalDicNode->getSecondWordFirstInputIndex(
-                            traverseSession->getProximityInfoState(0)) :
-                    NOT_AN_INDEX;
-            outSuggestionResults->addSuggestion(codePoints,
-                    terminalDicNode->getTotalNodeCodePointCount(),
-                    finalScore, Dictionary::KIND_CORRECTION | outputTypeFlags,
-                    indexToPartialCommit, computeFirstWordConfidence(terminalDicNode));
-        }
-
-        if (!terminalDicNode->hasMultipleWords()) {
-            BinaryDictionaryShortcutIterator shortcutIt(
-                    traverseSession->getDictionaryStructurePolicy()->getShortcutsStructurePolicy(),
-                    traverseSession->getDictionaryStructurePolicy()
-                            ->getShortcutPositionOfPtNode(terminalDicNode->getPtNodePos()));
-            // Shortcut is not supported for multiple words suggestions.
-            // TODO: Check shortcuts during traversal for multiple words suggestions.
-            const bool sameAsTyped = scoringPolicy->sameAsTyped(traverseSession, terminalDicNode);
-            const int shortcutBaseScore = scoringPolicy->doesAutoCorrectValidWord() ?
-                     scoringPolicy->calculateFinalScore(compoundDistance,
-                             traverseSession->getInputSize(),
-                             terminalDicNode->getContainedErrorTypes(),
-                             true /* forceCommit */, boostExactMatches) : finalScore;
-            outputShortcuts(&shortcutIt, shortcutBaseScore, sameAsTyped, outSuggestionResults);
-        }
+    for (auto &terminalDicNode : terminals) {
+        outputSuggestionsOfDicNode(scoringPolicy, traverseSession, &terminalDicNode,
+                languageWeight, boostExactMatches, forceCommitMultiWords,
+                outputSecondWordFirstLetterInputIndex, outSuggestionResults);
     }
     scoringPolicy->getMostProbableString(traverseSession, languageWeight, outSuggestionResults);
 }
 
+/* static */ void SuggestionsOutputUtils::outputSuggestionsOfDicNode(
+        const Scoring *const scoringPolicy, DicTraverseSession *traverseSession,
+        const DicNode *const terminalDicNode, const float languageWeight,
+        const bool boostExactMatches, const bool forceCommitMultiWords,
+        const bool outputSecondWordFirstLetterInputIndex,
+        SuggestionResults *const outSuggestionResults) {
+    if (DEBUG_GEO_FULL) {
+        terminalDicNode->dump("OUT:");
+    }
+    const float doubleLetterCost =
+            scoringPolicy->getDoubleLetterDemotionDistanceCost(terminalDicNode);
+    const float compoundDistance = terminalDicNode->getCompoundDistance(languageWeight)
+            + doubleLetterCost;
+    const bool isPossiblyOffensiveWord =
+            traverseSession->getDictionaryStructurePolicy()->getProbability(
+                    terminalDicNode->getProbability(), NOT_A_PROBABILITY) <= 0;
+    const bool isExactMatch =
+            ErrorTypeUtils::isExactMatch(terminalDicNode->getContainedErrorTypes());
+    const bool isFirstCharUppercase = terminalDicNode->isFirstCharUppercase();
+    // Heuristic: We exclude probability=0 first-char-uppercase words from exact match.
+    // (e.g. "AMD" and "and")
+    const bool isSafeExactMatch = isExactMatch
+            && !(isPossiblyOffensiveWord && isFirstCharUppercase);
+    const int outputTypeFlags =
+            (isPossiblyOffensiveWord ? Dictionary::KIND_FLAG_POSSIBLY_OFFENSIVE : 0)
+            | ((isSafeExactMatch && boostExactMatches) ? Dictionary::KIND_FLAG_EXACT_MATCH : 0);
+
+    // Entries that are blacklisted or do not represent a word should not be output.
+    const bool isValidWord = !terminalDicNode->isBlacklistedOrNotAWord();
+
+    // Increase output score of top typing suggestion to ensure autocorrection.
+    // TODO: Better integration with java side autocorrection logic.
+    const int finalScore = scoringPolicy->calculateFinalScore(
+            compoundDistance, traverseSession->getInputSize(),
+            terminalDicNode->getContainedErrorTypes(),
+            (forceCommitMultiWords && terminalDicNode->hasMultipleWords())
+                     || (isValidWord && scoringPolicy->doesAutoCorrectValidWord()),
+            boostExactMatches);
+
+    // Don't output invalid words. However, we still need to submit their shortcuts if any.
+    if (isValidWord) {
+        int codePoints[MAX_WORD_LENGTH];
+        terminalDicNode->outputResult(codePoints);
+        const int indexToPartialCommit = outputSecondWordFirstLetterInputIndex ?
+                terminalDicNode->getSecondWordFirstInputIndex(
+                        traverseSession->getProximityInfoState(0)) :
+                NOT_AN_INDEX;
+        outSuggestionResults->addSuggestion(codePoints,
+                terminalDicNode->getTotalNodeCodePointCount(),
+                finalScore, Dictionary::KIND_CORRECTION | outputTypeFlags,
+                indexToPartialCommit, computeFirstWordConfidence(terminalDicNode));
+    }
+
+    // Output shortcuts.
+    // Shortcut is not supported for multiple words suggestions.
+    // TODO: Check shortcuts during traversal for multiple words suggestions.
+    if (!terminalDicNode->hasMultipleWords()) {
+        BinaryDictionaryShortcutIterator shortcutIt(
+                traverseSession->getDictionaryStructurePolicy()->getShortcutsStructurePolicy(),
+                traverseSession->getDictionaryStructurePolicy()
+                        ->getShortcutPositionOfPtNode(terminalDicNode->getPtNodePos()));
+        const bool sameAsTyped = scoringPolicy->sameAsTyped(traverseSession, terminalDicNode);
+        const int shortcutBaseScore = scoringPolicy->doesAutoCorrectValidWord() ?
+                 scoringPolicy->calculateFinalScore(compoundDistance,
+                         traverseSession->getInputSize(),
+                         terminalDicNode->getContainedErrorTypes(),
+                         true /* forceCommit */, boostExactMatches) : finalScore;
+        outputShortcuts(&shortcutIt, shortcutBaseScore, sameAsTyped, outSuggestionResults);
+    }
+}
+
 /* static */ int SuggestionsOutputUtils::computeFirstWordConfidence(
         const DicNode *const terminalDicNode) {
     // Get the number of spaces in the first suggestion
diff --git a/native/jni/src/suggest/core/result/suggestions_output_utils.h b/native/jni/src/suggest/core/result/suggestions_output_utils.h
index 26d4b40..73cdb95 100644
--- a/native/jni/src/suggest/core/result/suggestions_output_utils.h
+++ b/native/jni/src/suggest/core/result/suggestions_output_utils.h
@@ -41,11 +41,15 @@
     // Inputs longer than this will autocorrect if the suggestion is multi-word
     static const int MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT;
 
-    static int computeFirstWordConfidence(const DicNode *const terminalDicNode);
-
+    static void outputSuggestionsOfDicNode(const Scoring *const scoringPolicy,
+            DicTraverseSession *traverseSession, const DicNode *const terminalDicNode,
+            const float languageWeight, const bool boostExactMatches,
+            const bool forceCommitMultiWords, const bool outputSecondWordFirstLetterInputIndex,
+            SuggestionResults *const outSuggestionResults);
     static void outputShortcuts(BinaryDictionaryShortcutIterator *const shortcutIt,
             const int finalScore, const bool sameAsTyped,
             SuggestionResults *const outSuggestionResults);
+    static int computeFirstWordConfidence(const DicNode *const terminalDicNode);
 };
 } // namespace latinime
 #endif // LATINIME_SUGGESTIONS_OUTPUT_UTILS
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.cpp b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
index 5070491..77b634e 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.cpp
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
@@ -68,7 +68,6 @@
     mDicNodesCache.reset(thresholdForNextActiveDicNodes /* nextActiveSize */,
             maxWords /* terminalSize */);
     mMultiBigramMap.clear();
-    mPartiallyCommited = false;
 }
 
 void DicTraverseSession::initializeProximityInfoStates(const int *const inputCodePoints,
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.h b/native/jni/src/suggest/core/session/dic_traverse_session.h
index b718fb5..9e5d902 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.h
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.h
@@ -61,7 +61,7 @@
     AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr, bool usesLargeCache)
             : mPrevWordPtNodePos(NOT_A_DICT_POS), mProximityInfo(nullptr),
               mDictionary(nullptr), mSuggestOptions(nullptr), mDicNodesCache(usesLargeCache),
-              mMultiBigramMap(), mInputSize(0), mPartiallyCommited(false), mMaxPointerCount(1),
+              mMultiBigramMap(), mInputSize(0), mMaxPointerCount(1),
               mMultiWordCostMultiplier(1.0f) {
         // NOTE: mProximityInfoStates is an array of instances.
         // No need to initialize it explicitly here.
@@ -95,8 +95,6 @@
         return &mProximityInfoStates[id];
     }
     int getInputSize() const { return mInputSize; }
-    void setPartiallyCommited() { mPartiallyCommited = true; }
-    bool isPartiallyCommited() const { return mPartiallyCommited; }
 
     bool isOnlyOnePointerUsed(int *pointerId) const {
         // Not in the dictionary word
@@ -188,7 +186,6 @@
     ProximityInfoState mProximityInfoStates[MAX_POINTER_COUNT_G];
 
     int mInputSize;
-    bool mPartiallyCommited;
     int mMaxPointerCount;
 
     /////////////////////////////////
diff --git a/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguagetUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguagetUtilsTests.java
new file mode 100644
index 0000000..ff1103e
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguagetUtilsTests.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.RichInputMethodManager;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+@SmallTest
+public class SpacebarLanguagetUtilsTests extends AndroidTestCase {
+    // All input method subtypes of LatinIME.
+    private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList();
+
+    private RichInputMethodManager mRichImm;
+    private Resources mRes;
+
+    InputMethodSubtype EN_US;
+    InputMethodSubtype EN_GB;
+    InputMethodSubtype ES_US;
+    InputMethodSubtype FR;
+    InputMethodSubtype FR_CA;
+    InputMethodSubtype FR_CH;
+    InputMethodSubtype DE;
+    InputMethodSubtype DE_CH;
+    InputMethodSubtype ZZ;
+    InputMethodSubtype DE_QWERTY;
+    InputMethodSubtype FR_QWERTZ;
+    InputMethodSubtype EN_US_AZERTY;
+    InputMethodSubtype EN_UK_DVORAK;
+    InputMethodSubtype ES_US_COLEMAK;
+    InputMethodSubtype ZZ_AZERTY;
+    InputMethodSubtype ZZ_PC;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        final Context context = getContext();
+        RichInputMethodManager.init(context);
+        mRichImm = RichInputMethodManager.getInstance();
+        mRes = context.getResources();
+        SubtypeLocaleUtils.init(context);
+
+        final InputMethodInfo imi = mRichImm.getInputMethodInfoOfThisIme();
+        final int subtypeCount = imi.getSubtypeCount();
+        for (int index = 0; index < subtypeCount; index++) {
+            final InputMethodSubtype subtype = imi.getSubtypeAt(index);
+            mSubtypesList.add(subtype);
+        }
+
+        EN_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.US.toString(), "qwerty");
+        EN_GB = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.UK.toString(), "qwerty");
+        ES_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "es_US", "spanish");
+        FR = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.FRENCH.toString(), "azerty");
+        FR_CA = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.CANADA_FRENCH.toString(), "qwerty");
+        FR_CH = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "fr_CH", "swiss");
+        DE = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.GERMAN.toString(), "qwertz");
+        DE_CH = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "de_CH", "swiss");
+        ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                SubtypeLocaleUtils.NO_LANGUAGE, "qwerty");
+        DE_QWERTY = AdditionalSubtypeUtils.createAdditionalSubtype(
+                Locale.GERMAN.toString(), "qwerty", null);
+        FR_QWERTZ = AdditionalSubtypeUtils.createAdditionalSubtype(
+                Locale.FRENCH.toString(), "qwertz", null);
+        EN_US_AZERTY = AdditionalSubtypeUtils.createAdditionalSubtype(
+                Locale.US.toString(), "azerty", null);
+        EN_UK_DVORAK = AdditionalSubtypeUtils.createAdditionalSubtype(
+                Locale.UK.toString(), "dvorak", null);
+        ES_US_COLEMAK = AdditionalSubtypeUtils.createAdditionalSubtype(
+                "es_US", "colemak", null);
+        ZZ_AZERTY = AdditionalSubtypeUtils.createAdditionalSubtype(
+                SubtypeLocaleUtils.NO_LANGUAGE, "azerty", null);
+        ZZ_PC = AdditionalSubtypeUtils.createAdditionalSubtype(
+                SubtypeLocaleUtils.NO_LANGUAGE, "pcqwerty", null);
+    }
+
+    public void testAllFullDisplayNameForSpacebar() {
+        for (final InputMethodSubtype subtype : mSubtypesList) {
+            final String subtypeName = SubtypeLocaleUtils
+                    .getSubtypeDisplayNameInSystemLocale(subtype);
+            final String spacebarText = SpacebarLanguageUtils.getFullDisplayName(subtype);
+            final String languageName = SubtypeLocaleUtils
+                    .getSubtypeLocaleDisplayName(subtype.getLocale());
+            if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
+                assertFalse(subtypeName, spacebarText.contains(languageName));
+            } else {
+                assertTrue(subtypeName, spacebarText.contains(languageName));
+            }
+        }
+    }
+
+   public void testAllMiddleDisplayNameForSpacebar() {
+        for (final InputMethodSubtype subtype : mSubtypesList) {
+            final String subtypeName = SubtypeLocaleUtils
+                    .getSubtypeDisplayNameInSystemLocale(subtype);
+            final String spacebarText = SpacebarLanguageUtils.getMiddleDisplayName(subtype);
+            if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
+                assertEquals(subtypeName,
+                        SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype), spacebarText);
+            } else {
+                final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype);
+                assertEquals(subtypeName,
+                        SubtypeLocaleUtils.getSubtypeLocaleDisplayName(locale.getLanguage()),
+                        spacebarText);
+            }
+        }
+    }
+
+    // InputMethodSubtype's display name for spacebar text in its locale.
+    //        isAdditionalSubtype (T=true, F=false)
+    // locale layout  |  Middle     Full
+    // ------ ------- - --------- ----------------------
+    //  en_US qwerty  F  English   English (US)           exception
+    //  en_GB qwerty  F  English   English (UK)           exception
+    //  es_US spanish F  Español   Español (EE.UU.)       exception
+    //  fr    azerty  F  Français  Français
+    //  fr_CA qwerty  F  Français  Français (Canada)
+    //  fr_CH swiss   F  Français  Français (Suisse)
+    //  de    qwertz  F  Deutsch   Deutsch
+    //  de_CH swiss   F  Deutsch   Deutsch (Schweiz)
+    //  zz    qwerty  F  QWERTY    QWERTY
+    //  fr    qwertz  T  Français  Français
+    //  de    qwerty  T  Deutsch   Deutsch
+    //  en_US azerty  T  English   English (US)
+    //  zz    azerty  T  AZERTY    AZERTY
+
+    private final RunInLocale<Void> testsPredefinedSubtypesForSpacebar = new RunInLocale<Void>() {
+        @Override
+        protected Void job(final Resources res) {
+            assertEquals("en_US", "English (US)",
+                    SpacebarLanguageUtils.getFullDisplayName(EN_US));
+            assertEquals("en_GB", "English (UK)",
+                    SpacebarLanguageUtils.getFullDisplayName(EN_GB));
+            assertEquals("es_US", "Español (EE.UU.)",
+                    SpacebarLanguageUtils.getFullDisplayName(ES_US));
+            assertEquals("fr", "Français",
+                    SpacebarLanguageUtils.getFullDisplayName(FR));
+            assertEquals("fr_CA", "Français (Canada)",
+                    SpacebarLanguageUtils.getFullDisplayName(FR_CA));
+            assertEquals("fr_CH", "Français (Suisse)",
+                    SpacebarLanguageUtils.getFullDisplayName(FR_CH));
+            assertEquals("de", "Deutsch",
+                    SpacebarLanguageUtils.getFullDisplayName(DE));
+            assertEquals("de_CH", "Deutsch (Schweiz)",
+                    SpacebarLanguageUtils.getFullDisplayName(DE_CH));
+            assertEquals("zz", "QWERTY",
+                    SpacebarLanguageUtils.getFullDisplayName(ZZ));
+
+            assertEquals("en_US", "English",
+                    SpacebarLanguageUtils.getMiddleDisplayName(EN_US));
+            assertEquals("en_GB", "English",
+                    SpacebarLanguageUtils.getMiddleDisplayName(EN_GB));
+            assertEquals("es_US", "Español",
+                    SpacebarLanguageUtils.getMiddleDisplayName(ES_US));
+            assertEquals("fr", "Français",
+                    SpacebarLanguageUtils.getMiddleDisplayName(FR));
+            assertEquals("fr_CA", "Français",
+                    SpacebarLanguageUtils.getMiddleDisplayName(FR_CA));
+            assertEquals("fr_CH", "Français",
+                    SpacebarLanguageUtils.getMiddleDisplayName(FR_CH));
+            assertEquals("de", "Deutsch",
+                    SpacebarLanguageUtils.getMiddleDisplayName(DE));
+            assertEquals("de_CH", "Deutsch",
+                    SpacebarLanguageUtils.getMiddleDisplayName(DE_CH));
+            assertEquals("zz", "QWERTY",
+                    SpacebarLanguageUtils.getMiddleDisplayName(ZZ));
+            return null;
+        }
+    };
+
+    private final RunInLocale<Void> testsAdditionalSubtypesForSpacebar = new RunInLocale<Void>() {
+        @Override
+        protected Void job(final Resources res) {
+            assertEquals("fr qwertz", "Français",
+                    SpacebarLanguageUtils.getFullDisplayName(FR_QWERTZ));
+            assertEquals("de qwerty", "Deutsch",
+                    SpacebarLanguageUtils.getFullDisplayName(DE_QWERTY));
+            assertEquals("en_US azerty", "English (US)",
+                    SpacebarLanguageUtils.getFullDisplayName(EN_US_AZERTY));
+            assertEquals("en_UK dvorak", "English (UK)",
+                    SpacebarLanguageUtils.getFullDisplayName(EN_UK_DVORAK));
+            assertEquals("es_US colemak", "Español (EE.UU.)",
+                    SpacebarLanguageUtils.getFullDisplayName(ES_US_COLEMAK));
+            assertEquals("zz azerty", "AZERTY",
+                    SpacebarLanguageUtils.getFullDisplayName(ZZ_AZERTY));
+            assertEquals("zz pc", "PC",
+                    SpacebarLanguageUtils.getFullDisplayName(ZZ_PC));
+
+            assertEquals("fr qwertz", "Français",
+                    SpacebarLanguageUtils.getMiddleDisplayName(FR_QWERTZ));
+            assertEquals("de qwerty", "Deutsch",
+                    SpacebarLanguageUtils.getMiddleDisplayName(DE_QWERTY));
+            assertEquals("en_US azerty", "English",
+                    SpacebarLanguageUtils.getMiddleDisplayName(EN_US_AZERTY));
+            assertEquals("en_UK dvorak", "English",
+                    SpacebarLanguageUtils.getMiddleDisplayName(EN_UK_DVORAK));
+            assertEquals("es_US colemak", "Español",
+                    SpacebarLanguageUtils.getMiddleDisplayName(ES_US_COLEMAK));
+            assertEquals("zz azerty", "AZERTY",
+                    SpacebarLanguageUtils.getMiddleDisplayName(ZZ_AZERTY));
+            assertEquals("zz pc", "PC",
+                    SpacebarLanguageUtils.getMiddleDisplayName(ZZ_PC));
+            return null;
+        }
+    };
+
+    public void testPredefinedSubtypesForSpacebarInEnglish() {
+        testsPredefinedSubtypesForSpacebar.runInLocale(mRes, Locale.ENGLISH);
+    }
+
+    public void testAdditionalSubtypeForSpacebarInEnglish() {
+        testsAdditionalSubtypesForSpacebar.runInLocale(mRes, Locale.ENGLISH);
+    }
+
+    public void testPredefinedSubtypesForSpacebarInFrench() {
+        testsPredefinedSubtypesForSpacebar.runInLocale(mRes, Locale.FRENCH);
+    }
+
+    public void testAdditionalSubtypeForSpacebarInFrench() {
+        testsAdditionalSubtypesForSpacebar.runInLocale(mRes, Locale.FRENCH);
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
index eb8a61a..ee34590 100644
--- a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
@@ -123,12 +123,23 @@
         assertEquals("en_US", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(EN_US));
         assertEquals("en_GB", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(EN_GB));
         assertEquals("es_US", "spanish", SubtypeLocaleUtils.getKeyboardLayoutSetName(ES_US));
-        assertEquals("fr   ", "azerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR));
+        assertEquals("fr", "azerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR));
         assertEquals("fr_CA", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_CA));
         assertEquals("fr_CH", "swiss", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_CH));
-        assertEquals("de   ", "qwertz", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE));
+        assertEquals("de", "qwertz", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE));
         assertEquals("de_CH", "swiss", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE_CH));
-        assertEquals("zz   ", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(ZZ));
+        assertEquals("zz", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(ZZ));
+
+        assertEquals("de qwerty", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE_QWERTY));
+        assertEquals("fr qwertz", "qwertz", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_QWERTZ));
+        assertEquals("en_US azerty", "azerty",
+                SubtypeLocaleUtils.getKeyboardLayoutSetName(EN_US_AZERTY));
+        assertEquals("en_UK dvorak", "dvorak",
+                SubtypeLocaleUtils.getKeyboardLayoutSetName(EN_UK_DVORAK));
+        assertEquals("es_US colemak", "colemak",
+                SubtypeLocaleUtils.getKeyboardLayoutSetName(ES_US_COLEMAK));
+        assertEquals("zz azerty", "azerty",
+                SubtypeLocaleUtils.getKeyboardLayoutSetName(ZZ_AZERTY));
     }
 
     // InputMethodSubtype's display name in system locale (en_US).
@@ -161,17 +172,17 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_GB));
                 assertEquals("es_US", "Spanish (US)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ES_US));
-                assertEquals("fr   ", "French",
+                assertEquals("fr", "French",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR));
                 assertEquals("fr_CA", "French (Canada)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CA));
                 assertEquals("fr_CH", "French (Switzerland)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CH));
-                assertEquals("de   ", "German",
+                assertEquals("de", "German",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE));
                 assertEquals("de_CH", "German (Switzerland)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_CH));
-                assertEquals("zz   ", "Alphabet (QWERTY)",
+                assertEquals("zz", "Alphabet (QWERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ));
                 return null;
             }
@@ -183,17 +194,19 @@
         final RunInLocale<Void> tests = new RunInLocale<Void>() {
             @Override
             protected Void job(final Resources res) {
-                assertEquals("fr qwertz",    "French (QWERTZ)",
+                assertEquals("fr qwertz", "French (QWERTZ)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_QWERTZ));
-                assertEquals("de qwerty",    "German (QWERTY)",
+                assertEquals("de qwerty", "German (QWERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_QWERTY));
                 assertEquals("en_US azerty", "English (US) (AZERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_US_AZERTY));
-                assertEquals("en_UK dvorak", "English (UK) (Dvorak)",
+                assertEquals("en_UK dvorak","English (UK) (Dvorak)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_UK_DVORAK));
-                assertEquals("es_US colemak","Spanish (US) (Colemak)",
+                assertEquals("es_US colemak", "Spanish (US) (Colemak)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ES_US_COLEMAK));
-                assertEquals("zz pc",    "Alphabet (PC)",
+                assertEquals("zz azerty", "Alphabet (AZERTY)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_AZERTY));
+                assertEquals("zz pc", "Alphabet (PC)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_PC));
                 return null;
             }
@@ -231,17 +244,17 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_GB));
                 assertEquals("es_US", "Espagnol (États-Unis)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ES_US));
-                assertEquals("fr   ", "Français",
+                assertEquals("fr", "Français",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR));
                 assertEquals("fr_CA", "Français (Canada)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CA));
                 assertEquals("fr_CH", "Français (Suisse)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CH));
-                assertEquals("de   ", "Allemand",
+                assertEquals("de", "Allemand",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE));
                 assertEquals("de_CH", "Allemand (Suisse)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_CH));
-                assertEquals("zz   ", "Alphabet latin (QWERTY)",
+                assertEquals("zz", "Alphabet latin (QWERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ));
                 return null;
             }
@@ -253,17 +266,19 @@
         final RunInLocale<Void> tests = new RunInLocale<Void>() {
             @Override
             protected Void job(final Resources res) {
-                assertEquals("fr qwertz",    "Français (QWERTZ)",
+                assertEquals("fr qwertz", "Français (QWERTZ)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_QWERTZ));
-                assertEquals("de qwerty",    "Allemand (QWERTY)",
+                assertEquals("de qwerty", "Allemand (QWERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_QWERTY));
                 assertEquals("en_US azerty", "Anglais (États-Unis) (AZERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_US_AZERTY));
                 assertEquals("en_UK dvorak", "Anglais (Royaume-Uni) (Dvorak)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_UK_DVORAK));
-                assertEquals("es_US colemak","Espagnol (États-Unis) (Colemak)",
+                assertEquals("es_US colemak", "Espagnol (États-Unis) (Colemak)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ES_US_COLEMAK));
-                assertEquals("zz pc",    "Alphabet latin (PC)",
+                assertEquals("zz azerty", "Alphabet latin (AZERTY)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_AZERTY));
+                assertEquals("zz pc", "Alphabet latin (PC)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_PC));
                 return null;
             }
@@ -271,126 +286,6 @@
         tests.runInLocale(mRes, Locale.FRENCH);
     }
 
-    public void testAllFullDisplayNameForSpacebar() {
-        for (final InputMethodSubtype subtype : mSubtypesList) {
-            final String subtypeName = SubtypeLocaleUtils
-                    .getSubtypeDisplayNameInSystemLocale(subtype);
-            final String spacebarText = SubtypeLocaleUtils.getFullDisplayName(subtype);
-            final String languageName = SubtypeLocaleUtils
-                    .getSubtypeLocaleDisplayName(subtype.getLocale());
-            if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
-                assertFalse(subtypeName, spacebarText.contains(languageName));
-            } else {
-                assertTrue(subtypeName, spacebarText.contains(languageName));
-            }
-        }
-    }
-
-   public void testAllMiddleDisplayNameForSpacebar() {
-        for (final InputMethodSubtype subtype : mSubtypesList) {
-            final String subtypeName = SubtypeLocaleUtils
-                    .getSubtypeDisplayNameInSystemLocale(subtype);
-            final String spacebarText = SubtypeLocaleUtils.getMiddleDisplayName(subtype);
-            if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
-                assertEquals(subtypeName,
-                        SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype), spacebarText);
-            } else {
-                final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype);
-                assertEquals(subtypeName,
-                        SubtypeLocaleUtils.getSubtypeLocaleDisplayName(locale.getLanguage()),
-                        spacebarText);
-            }
-        }
-    }
-
-    // InputMethodSubtype's display name for spacebar text in its locale.
-    //        isAdditionalSubtype (T=true, F=false)
-    // locale layout  |  Middle    Full
-    // ------ ------- - --------- ----------------------
-    //  en_US qwerty  F  English   English (US)           exception
-    //  en_GB qwerty  F  English   English (UK)           exception
-    //  es_US spanish F  Español   Español (EE.UU.)       exception
-    //  fr    azerty  F  Français  Français
-    //  fr_CA qwerty  F  Français  Français (Canada)
-    //  fr_CH swiss   F  Français  Français (Suisse)
-    //  de    qwertz  F  Deutsch   Deutsch
-    //  de_CH swiss   F  Deutsch   Deutsch (Schweiz)
-    //  zz    qwerty  F  QWERTY    QWERTY
-    //  fr    qwertz  T  Français  Français
-    //  de    qwerty  T  Deutsch   Deutsch
-    //  en_US azerty  T  English   English (US)
-    //  zz    azerty  T  AZERTY    AZERTY
-
-    private final RunInLocale<Void> testsPredefinedSubtypesForSpacebar = new RunInLocale<Void>() {
-        @Override
-        protected Void job(final Resources res) {
-            assertEquals("en_US", "English (US)", SubtypeLocaleUtils.getFullDisplayName(EN_US));
-            assertEquals("en_GB", "English (UK)", SubtypeLocaleUtils.getFullDisplayName(EN_GB));
-            assertEquals("es_US", "Español (EE.UU.)",
-                    SubtypeLocaleUtils.getFullDisplayName(ES_US));
-            assertEquals("fr   ", "Français",     SubtypeLocaleUtils.getFullDisplayName(FR));
-            assertEquals("fr_CA", "Français (Canada)",
-                    SubtypeLocaleUtils.getFullDisplayName(FR_CA));
-            assertEquals("fr_CH", "Français (Suisse)",
-                    SubtypeLocaleUtils.getFullDisplayName(FR_CH));
-            assertEquals("de   ", "Deutsch",      SubtypeLocaleUtils.getFullDisplayName(DE));
-            assertEquals("de_CH", "Deutsch (Schweiz)",
-                    SubtypeLocaleUtils.getFullDisplayName(DE_CH));
-            assertEquals("zz   ", "QWERTY",       SubtypeLocaleUtils.getFullDisplayName(ZZ));
-
-            assertEquals("en_US", "English",  SubtypeLocaleUtils.getMiddleDisplayName(EN_US));
-            assertEquals("en_GB", "English",  SubtypeLocaleUtils.getMiddleDisplayName(EN_GB));
-            assertEquals("es_US", "Español",  SubtypeLocaleUtils.getMiddleDisplayName(ES_US));
-            assertEquals("fr   ", "Français", SubtypeLocaleUtils.getMiddleDisplayName(FR));
-            assertEquals("fr_CA", "Français", SubtypeLocaleUtils.getMiddleDisplayName(FR_CA));
-            assertEquals("fr_CH", "Français", SubtypeLocaleUtils.getMiddleDisplayName(FR_CH));
-            assertEquals("de   ", "Deutsch",  SubtypeLocaleUtils.getMiddleDisplayName(DE));
-            assertEquals("de_CH", "Deutsch",  SubtypeLocaleUtils.getMiddleDisplayName(DE_CH));
-            assertEquals("zz   ", "QWERTY",   SubtypeLocaleUtils.getMiddleDisplayName(ZZ));
-            return null;
-        }
-    };
-
-    private final RunInLocale<Void> testsAdditionalSubtypesForSpacebar = new RunInLocale<Void>() {
-        @Override
-        protected Void job(final Resources res) {
-            assertEquals("fr qwertz",    "Français",
-                    SubtypeLocaleUtils.getFullDisplayName(FR_QWERTZ));
-            assertEquals("de qwerty",    "Deutsch",
-                    SubtypeLocaleUtils.getFullDisplayName(DE_QWERTY));
-            assertEquals("en_US azerty", "English (US)",
-                    SubtypeLocaleUtils.getFullDisplayName(EN_US_AZERTY));
-            assertEquals("zz azerty",    "AZERTY",
-                    SubtypeLocaleUtils.getFullDisplayName(ZZ_AZERTY));
-
-            assertEquals("fr qwertz",    "Français",
-                    SubtypeLocaleUtils.getMiddleDisplayName(FR_QWERTZ));
-            assertEquals("de qwerty",    "Deutsch",
-                    SubtypeLocaleUtils.getMiddleDisplayName(DE_QWERTY));
-            assertEquals("en_US azerty", "English",
-                    SubtypeLocaleUtils.getMiddleDisplayName(EN_US_AZERTY));
-            assertEquals("zz azerty",    "AZERTY",
-                    SubtypeLocaleUtils.getMiddleDisplayName(ZZ_AZERTY));
-            return null;
-        }
-    };
-
-    public void testPredefinedSubtypesForSpacebarInEnglish() {
-        testsPredefinedSubtypesForSpacebar.runInLocale(mRes, Locale.ENGLISH);
-    }
-
-    public void testAdditionalSubtypeForSpacebarInEnglish() {
-        testsAdditionalSubtypesForSpacebar.runInLocale(mRes, Locale.ENGLISH);
-    }
-
-    public void testPredefinedSubtypesForSpacebarInFrench() {
-        testsPredefinedSubtypesForSpacebar.runInLocale(mRes, Locale.FRENCH);
-    }
-
-    public void testAdditionalSubtypeForSpacebarInFrench() {
-        testsAdditionalSubtypesForSpacebar.runInLocale(mRes, Locale.FRENCH);
-    }
-
     public void testIsRtlLanguage() {
         // Known Right-to-Left language subtypes.
         final InputMethodSubtype ARABIC = mRichImm