Merge "Stop considering personalization dicts outputs as words"
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 71e3eee..f3cfae2 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -484,11 +484,9 @@
             return;
         }
 
-        final TextView previewTextView = mKeyPreviewChoreographer.getKeyPreviewTextView(
-                key, getContext());
         locatePreviewPlacerView();
-        mDrawingPreviewPlacerView.addView(
-                previewTextView, ViewLayoutUtils.newLayoutParam(mDrawingPreviewPlacerView, 0, 0));
+        final TextView previewTextView = mKeyPreviewChoreographer.getKeyPreviewTextView(
+                key, mDrawingPreviewPlacerView);
         getLocationInWindow(mOriginCoords);
         mKeyPreviewChoreographer.placeKeyPreview(key, previewTextView, keyboard.mIconsSet,
                 mKeyDrawParams, getWidth(), mOriginCoords);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
index c1922b7..df869b2 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
@@ -58,7 +58,7 @@
         mParams = params;
     }
 
-    public TextView getKeyPreviewTextView(final Key key, final Context context) {
+    public TextView getKeyPreviewTextView(final Key key, final ViewGroup placerView) {
         TextView previewTextView = mShowingKeyPreviewTextViews.remove(key);
         if (previewTextView != null) {
             return previewTextView;
@@ -67,10 +67,15 @@
         if (previewTextView != null) {
             return previewTextView;
         }
+        final Context context = placerView.getContext();
         if (mParams.mLayoutId != 0) {
-            return (TextView)LayoutInflater.from(context).inflate(mParams.mLayoutId, null);
+            previewTextView = (TextView)LayoutInflater.from(context)
+                    .inflate(mParams.mLayoutId, null);
+        } else {
+            previewTextView = new TextView(context);
         }
-        return new TextView(context);
+        placerView.addView(previewTextView, ViewLayoutUtils.newLayoutParam(placerView, 0, 0));
+        return previewTextView;
     }
 
     public boolean isShowingKeyPreview(final Key key) {
@@ -244,7 +249,7 @@
         zoomOutAnimation.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(final Animator animation) {
-                dismissKeyPreview(key, true /* withAnimation */);
+                dismissKeyPreview(key, false /* withAnimation */);
             }
         });
         return zoomOutAnimation;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index c51941d..2925a4b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -56,10 +56,9 @@
         return keySpec.startsWith(KeyboardIconsSet.PREFIX_ICON);
     }
 
-    private static boolean hasCode(final String keySpec) {
-        final int end = indexOfLabelEnd(keySpec, 0);
-        if (end > 0 && end + 1 < keySpec.length() && keySpec.startsWith(
-                KeyboardCodesSet.PREFIX_CODE, end + 1)) {
+    private static boolean hasCode(final String keySpec, final int labelEnd) {
+        if (labelEnd > 0 && labelEnd + 1 < keySpec.length()
+                && keySpec.startsWith(KeyboardCodesSet.PREFIX_CODE, labelEnd + 1)) {
             return true;
         }
         return false;
@@ -84,16 +83,20 @@
         return sb.toString();
     }
 
-    private static int indexOfLabelEnd(final String keySpec, final int start) {
-        if (keySpec.indexOf(BACKSLASH, start) < 0) {
-            final int end = keySpec.indexOf(VERTICAL_BAR, start);
-            if (end == 0) {
-                throw new KeySpecParserError(VERTICAL_BAR + " at " + start + ": " + keySpec);
-            }
-            return end;
-        }
+    private static int indexOfLabelEnd(final String keySpec) {
         final int length = keySpec.length();
-        for (int pos = start; pos < length; pos++) {
+        if (keySpec.indexOf(BACKSLASH) < 0) {
+            final int labelEnd = keySpec.indexOf(VERTICAL_BAR);
+            if (labelEnd == 0) {
+                if (length == 1) {
+                    // Treat a sole vertical bar as a special case of key label.
+                    return -1;
+                }
+                throw new KeySpecParserError("Empty label");
+            }
+            return labelEnd;
+        }
+        for (int pos = 0; pos < length; pos++) {
             final char c = keySpec.charAt(pos);
             if (c == BACKSLASH && pos + 1 < length) {
                 // Skip escape char
@@ -105,35 +108,55 @@
         return -1;
     }
 
+    private static String getBeforeLabelEnd(final String keySpec, final int labelEnd) {
+        return (labelEnd < 0) ? keySpec : keySpec.substring(0, labelEnd);
+    }
+
+    private static String getAfterLabelEnd(final String keySpec, final int labelEnd) {
+        return keySpec.substring(labelEnd + /* VERTICAL_BAR */1);
+    }
+
+    private static void checkDoubleLabelEnd(final String keySpec, final int labelEnd) {
+        if (indexOfLabelEnd(getAfterLabelEnd(keySpec, labelEnd)) < 0) {
+            return;
+        }
+        throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + keySpec);
+    }
+
     public static String getLabel(final String keySpec) {
+        if (keySpec == null) {
+            // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
+            return null;
+        }
         if (hasIcon(keySpec)) {
             return null;
         }
-        final int end = indexOfLabelEnd(keySpec, 0);
-        final String label = (end > 0) ? parseEscape(keySpec.substring(0, end))
-                : parseEscape(keySpec);
+        final int labelEnd = indexOfLabelEnd(keySpec);
+        final String label = parseEscape(getBeforeLabelEnd(keySpec, labelEnd));
         if (label.isEmpty()) {
             throw new KeySpecParserError("Empty label: " + keySpec);
         }
         return label;
     }
 
-    private static String getOutputTextInternal(final String keySpec) {
-        final int end = indexOfLabelEnd(keySpec, 0);
-        if (end <= 0) {
+    private static String getOutputTextInternal(final String keySpec, final int labelEnd) {
+        if (labelEnd <= 0) {
             return null;
         }
-        if (indexOfLabelEnd(keySpec, end + 1) >= 0) {
-            throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + keySpec);
-        }
-        return parseEscape(keySpec.substring(end + /* VERTICAL_BAR */1));
+        checkDoubleLabelEnd(keySpec, labelEnd);
+        return parseEscape(getAfterLabelEnd(keySpec, labelEnd));
     }
 
     public static String getOutputText(final String keySpec) {
-        if (hasCode(keySpec)) {
+        if (keySpec == null) {
+            // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
             return null;
         }
-        final String outputText = getOutputTextInternal(keySpec);
+        final int labelEnd = indexOfLabelEnd(keySpec);
+        if (hasCode(keySpec, labelEnd)) {
+            return null;
+        }
+        final String outputText = getOutputTextInternal(keySpec, labelEnd);
         if (outputText != null) {
             if (StringUtils.codePointCount(outputText) == 1) {
                 // If output text is one code point, it should be treated as a code.
@@ -154,14 +177,16 @@
     }
 
     public static int getCode(final String keySpec, final KeyboardCodesSet codesSet) {
-        if (hasCode(keySpec)) {
-            final int end = indexOfLabelEnd(keySpec, 0);
-            if (indexOfLabelEnd(keySpec, end + 1) >= 0) {
-                throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + keySpec);
-            }
-            return parseCode(keySpec.substring(end + 1), codesSet, CODE_UNSPECIFIED);
+        if (keySpec == null) {
+            // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
+            return CODE_UNSPECIFIED;
         }
-        final String outputText = getOutputTextInternal(keySpec);
+        final int labelEnd = indexOfLabelEnd(keySpec);
+        if (hasCode(keySpec, labelEnd)) {
+            checkDoubleLabelEnd(keySpec, labelEnd);
+            return parseCode(getAfterLabelEnd(keySpec, labelEnd), codesSet, CODE_UNSPECIFIED);
+        }
+        final String outputText = getOutputTextInternal(keySpec, labelEnd);
         if (outputText != null) {
             // If output text is one code point, it should be treated as a code.
             // See {@link #getOutputText(String)}.
@@ -171,35 +196,40 @@
             return CODE_OUTPUT_TEXT;
         }
         final String label = getLabel(keySpec);
-        // Code is automatically generated for one letter label.
-        if (StringUtils.codePointCount(label) == 1) {
-            return label.codePointAt(0);
+        if (label == null) {
+            throw new KeySpecParserError("Empty label: " + keySpec);
         }
-        return CODE_OUTPUT_TEXT;
+        // Code is automatically generated for one letter label.
+        return (StringUtils.codePointCount(label) == 1) ? label.codePointAt(0) : CODE_OUTPUT_TEXT;
     }
 
+    // TODO: Make this method private once Key.code attribute is removed.
     public static int parseCode(final String text, final KeyboardCodesSet codesSet,
             final int defCode) {
-        if (text == null) return defCode;
+        if (text == null) {
+            return defCode;
+        }
         if (text.startsWith(KeyboardCodesSet.PREFIX_CODE)) {
             return codesSet.getCode(text.substring(KeyboardCodesSet.PREFIX_CODE.length()));
-        } else if (text.startsWith(PREFIX_HEX)) {
-            return Integer.parseInt(text.substring(PREFIX_HEX.length()), 16);
-        } else {
-            return Integer.parseInt(text);
         }
+        if (text.startsWith(PREFIX_HEX)) {
+            return Integer.parseInt(text.substring(PREFIX_HEX.length()), 16);
+        }
+        return Integer.parseInt(text);
     }
 
     public static int getIconId(final String keySpec) {
-        if (keySpec != null && hasIcon(keySpec)) {
-            final int end = keySpec.indexOf(
-                    VERTICAL_BAR, KeyboardIconsSet.PREFIX_ICON.length());
-            final String name = (end < 0)
-                    ? keySpec.substring(KeyboardIconsSet.PREFIX_ICON.length())
-                    : keySpec.substring(KeyboardIconsSet.PREFIX_ICON.length(), end);
-            return KeyboardIconsSet.getIconId(name);
+        if (keySpec == null) {
+            // TODO: Throw {@link KeySpecParserError} once Key.keyLabel attribute becomes mandatory.
+            return KeyboardIconsSet.ICON_UNDEFINED;
         }
-        return KeyboardIconsSet.ICON_UNDEFINED;
+        if (!hasIcon(keySpec)) {
+            return KeyboardIconsSet.ICON_UNDEFINED;
+        }
+        final int labelEnd = indexOfLabelEnd(keySpec);
+        final String iconName = getBeforeLabelEnd(keySpec, labelEnd)
+                .substring(KeyboardIconsSet.PREFIX_ICON.length());
+        return KeyboardIconsSet.getIconId(iconName);
     }
 
     @SuppressWarnings("serial")
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
index d3bc0c2..0551e9e 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
@@ -46,6 +46,9 @@
 
     public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, final Locale locale,
             final KeyboardCodesSet codesSet) {
+        if (TextUtils.isEmpty(moreKeySpec)) {
+            throw new KeySpecParser.KeySpecParserError("Empty more key spec");
+        }
         mLabel = StringUtils.toUpperCaseOfStringForLocale(
                 KeySpecParser.getLabel(moreKeySpec), needsToUpperCase, locale);
         final int code = StringUtils.toUpperCaseOfCodeForLocale(
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 80a27e2..b20bcd1 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -139,7 +139,7 @@
     }
 
     private static native boolean createEmptyDictFileNative(String filePath, long dictVersion,
-            String[] attributeKeyStringArray, String[] attributeValueStringArray);
+            String locale, String[] attributeKeyStringArray, String[] attributeValueStringArray);
     private static native long openNative(String sourceDir, long dictOffset, long dictSize,
             boolean isUpdatable);
     private static native void getHeaderInfoNative(long dict, int[] outHeaderSize,
@@ -179,7 +179,7 @@
     private static native String getPropertyNative(long dict, String query);
 
     public static boolean createEmptyDictFile(final String filePath, final long dictVersion,
-            final Map<String, String> attributeMap) {
+            final Locale locale, final Map<String, String> attributeMap) {
         final String[] keyArray = new String[attributeMap.size()];
         final String[] valueArray = new String[attributeMap.size()];
         int index = 0;
@@ -188,7 +188,8 @@
             valueArray[index] = attributeMap.get(key);
             index++;
         }
-        return createEmptyDictFileNative(filePath, dictVersion, keyArray, valueArray);
+        return createEmptyDictFileNative(filePath, dictVersion, locale.toString(), keyArray,
+                valueArray);
     }
 
     // TODO: Move native dict into session
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index e0d952e..48a6588 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -289,10 +289,10 @@
                         Log.e(TAG, "Can't remove a file: " + file.getName());
                     }
                     BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
-                            DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
+                            DICTIONARY_FORMAT_VERSION, mLocale, getHeaderAttributeMap());
                     mBinaryDictionary = new BinaryDictionary(
                             file.getAbsolutePath(), 0 /* offset */, file.length(),
-                            true /* useFullEditDistance */, null, mDictType, mIsUpdatable);
+                            true /* useFullEditDistance */, mLocale, mDictType, mIsUpdatable);
                 } else {
                     mDictionaryWriter.clear();
                 }
@@ -594,7 +594,7 @@
                     Log.e(TAG, "Can't remove a file: " + file.getName());
                 }
                 BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
-                        DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
+                        DICTIONARY_FORMAT_VERSION, mLocale, getHeaderAttributeMap());
             } else {
                 if (mBinaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
                     mBinaryDictionary.flushWithGC();
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 2e163c4..e55c08d 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -67,7 +67,6 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.inputlogic.InputLogic;
-import com.android.inputmethod.latin.inputlogic.SpaceState;
 import com.android.inputmethod.latin.personalization.DictionaryDecayBroadcastReciever;
 import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegistrar;
 import com.android.inputmethod.latin.personalization.PersonalizationHelper;
@@ -75,6 +74,7 @@
 import com.android.inputmethod.latin.settings.SettingsActivity;
 import com.android.inputmethod.latin.settings.SettingsValues;
 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
+import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
 import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.CapsModeUtils;
 import com.android.inputmethod.latin.utils.CompletionInfoUtils;
@@ -82,7 +82,6 @@
 import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
 import com.android.inputmethod.latin.utils.IntentUtils;
 import com.android.inputmethod.latin.utils.JniUtils;
-import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
 import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 import com.android.inputmethod.research.ResearchLogger;
@@ -97,7 +96,7 @@
  * Input method implementation for Qwerty'ish keyboard.
  */
 public class LatinIME extends InputMethodService implements KeyboardActionListener,
-        SuggestionStripView.Listener,
+        SuggestionStripView.Listener, SuggestionStripViewAccessor,
         DictionaryFacilitatorForSuggest.DictionaryInitializationListener {
     private static final String TAG = LatinIME.class.getSimpleName();
     private static final boolean TRACE = false;
@@ -116,13 +115,15 @@
     private static final String SCHEME_PACKAGE = "package";
 
     private final Settings mSettings;
-    private final InputLogic mInputLogic = new InputLogic(this);
+    private final InputLogic mInputLogic = new InputLogic(this /* LatinIME */,
+            this /* SuggestionStripViewAccessor */);
 
     private View mExtractArea;
     private View mKeyPreviewBackingView;
     private SuggestionStripView mSuggestionStripView;
 
-    private CompletionInfo[] mApplicationSpecifiedCompletions;
+    // TODO[IL]: remove this member completely.
+    public CompletionInfo[] mApplicationSpecifiedCompletions;
 
     private RichInputMethodManager mRichImm;
     @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher;
@@ -1305,13 +1306,10 @@
         // Nothing to do so far.
     }
 
-    // TODO[IL]: Move this to InputLogic and make it private
-    // Outside LatinIME, only used by the test suite.
+    // TODO: remove this, read this directly from mInputLogic or something in the tests
     @UsedForTesting
     public boolean isShowingPunctuationList() {
-        if (mInputLogic.mSuggestedWords == null) return false;
-        return mSettings.getCurrent().mSpacingAndPunctuations.mSuggestPuncList
-                == mInputLogic.mSuggestedWords;
+        return mInputLogic.isShowingPunctuationList(mSettings.getCurrent());
     }
 
     // TODO[IL]: Define a clear interface for this
@@ -1330,6 +1328,17 @@
         return currentSettings.isSuggestionsRequested();
     }
 
+    @Override
+    public boolean hasSuggestionStripView() {
+        return null != mSuggestionStripView;
+    }
+
+    @Override
+    public boolean isShowingAddToDictionaryHint() {
+        return hasSuggestionStripView() && mSuggestionStripView.isShowingAddToDictionaryHint();
+    }
+
+    @Override
     public void dismissAddToDictionaryHint() {
         if (null != mSuggestionStripView) {
             mSuggestionStripView.dismissAddToDictionaryHint();
@@ -1399,8 +1408,7 @@
         // the "add to dictionary" hint, we need to revert to suggestions - although it is unclear
         // how we can come here if it's displayed.
         if (suggestedWords.size() > 1 || typedWord.length() <= 1
-                || null == mSuggestionStripView
-                || mSuggestionStripView.isShowingAddToDictionaryHint()) {
+                || null == mSuggestionStripView || isShowingAddToDictionaryHint()) {
             return suggestedWords;
         } else {
             final SuggestedWords punctuationList =
@@ -1418,7 +1426,7 @@
         }
     }
 
-    // TODO[IL]: Define a clean interface for this
+    @Override
     public void showSuggestionStrip(final SuggestedWords sourceSuggestedWords) {
         final SuggestedWords suggestedWords =
                 sourceSuggestedWords.isEmpty() ? SuggestedWords.EMPTY : sourceSuggestedWords;
@@ -1446,99 +1454,20 @@
     // interface
     @Override
     public void pickSuggestionManually(final int index, final SuggestedWordInfo suggestionInfo) {
-        final SuggestedWords suggestedWords = mInputLogic.mSuggestedWords;
-        final String suggestion = suggestionInfo.mWord;
-        // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
-        if (suggestion.length() == 1 && isShowingPunctuationList()) {
-            // Word separators are suggested before the user inputs something.
-            // So, LatinImeLogger logs "" as a user's input.
-            LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
-            // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
-            final int primaryCode = suggestion.charAt(0);
-            onCodeInput(primaryCode,
-                    Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
-            if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
-                        false /* isBatchMode */, suggestedWords.mIsPrediction);
-            }
-            return;
-        }
+        mInputLogic.onPickSuggestionManually(mSettings.getCurrent(), index, suggestionInfo,
+                mHandler, mKeyboardSwitcher);
+    }
 
-        mInputLogic.mConnection.beginBatchEdit();
-        final SettingsValues currentSettings = mSettings.getCurrent();
-        if (SpaceState.PHANTOM == mInputLogic.mSpaceState && suggestion.length() > 0
-                // In the batch input mode, a manually picked suggested word should just replace
-                // the current batch input text and there is no need for a phantom space.
-                && !mInputLogic.mWordComposer.isBatchMode()) {
-            final int firstChar = Character.codePointAt(suggestion, 0);
-            if (!currentSettings.isWordSeparator(firstChar)
-                    || currentSettings.isUsuallyPrecededBySpace(firstChar)) {
-                mInputLogic.promotePhantomSpace(currentSettings);
-            }
-        }
-
-        if (currentSettings.isApplicationSpecifiedCompletionsOn()
-                && mApplicationSpecifiedCompletions != null
-                && index >= 0 && index < mApplicationSpecifiedCompletions.length) {
-            mInputLogic.mSuggestedWords = SuggestedWords.EMPTY;
-            if (mSuggestionStripView != null) {
-                mSuggestionStripView.clear();
-            }
-            mKeyboardSwitcher.updateShiftState();
-            mInputLogic.resetComposingState(true /* alsoResetLastComposedWord */);
-            final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
-            mInputLogic.mConnection.commitCompletion(completionInfo);
-            mInputLogic.mConnection.endBatchEdit();
-            return;
-        }
-
-        // We need to log before we commit, because the word composer will store away the user
-        // typed word.
-        final String replacedWord = mInputLogic.mWordComposer.getTypedWord();
-        LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
-        mInputLogic.commitChosenWord(currentSettings, suggestion,
-                LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR);
-        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
-                    mInputLogic.mWordComposer.isBatchMode(), suggestionInfo.mScore,
-                    suggestionInfo.mKind, suggestionInfo.mSourceDict.mDictType);
-        }
-        mInputLogic.mConnection.endBatchEdit();
-        // Don't allow cancellation of manual pick
-        mInputLogic.mLastComposedWord.deactivate();
-        // Space state must be updated before calling updateShiftState
-        mInputLogic.mSpaceState = SpaceState.PHANTOM;
-        mKeyboardSwitcher.updateShiftState();
-
-        // We should show the "Touch again to save" hint if the user pressed the first entry
-        // AND it's in none of our current dictionaries (main, user or otherwise).
-        // Please note that if mSuggest is null, it means that everything is off: suggestion
-        // and correction, so we shouldn't try to show the hint
-        final Suggest suggest = mInputLogic.mSuggest;
-        final boolean showingAddToDictionaryHint =
-                (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
-                        || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
-                        && suggest != null
-                        // If the suggestion is not in the dictionary, the hint should be shown.
-                        && !suggest.mDictionaryFacilitator.isValidWord(suggestion,
-                                true /* ignoreCase */);
-
-        if (currentSettings.mIsInternal) {
-            LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
-                    Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
-        }
-        if (showingAddToDictionaryHint
-                && suggest.mDictionaryFacilitator.isUserDictionaryEnabled()) {
-            mSuggestionStripView.showAddToDictionaryHint(suggestion);
-        } else {
-            // If we're not showing the "Touch again to save", then update the suggestion strip.
-            mHandler.postUpdateSuggestionStrip();
-        }
+    @Override
+    public void showAddToDictionaryHint(final String word) {
+        if (null == mSuggestionStripView) return;
+        mSuggestionStripView.showAddToDictionaryHint(word);
     }
 
     // TODO[IL]: Define a clean interface for this
     // This will show either an empty suggestion strip (if prediction is enabled) or
     // punctuation suggestions (if it's disabled).
+    @Override
     public void setNeutralSuggestionStrip() {
         final SettingsValues currentSettings = mSettings.getCurrent();
         if (currentSettings.mBigramPredictionEnabled) {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index a994a43..3ecf5f0 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -23,6 +23,7 @@
 import android.util.Log;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.EditorInfo;
 
@@ -44,6 +45,7 @@
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.settings.SettingsValues;
 import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
 import com.android.inputmethod.latin.utils.AsyncResultHolder;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.InputTypeUtils;
@@ -65,6 +67,7 @@
 
     // TODO : Remove this member when we can.
     private final LatinIME mLatinIME;
+    private final SuggestionStripViewAccessor mSuggestionStripViewAccessor;
 
     // Never null.
     private InputLogicHandler mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
@@ -94,8 +97,10 @@
     // Find a way to remove it for readability.
     public boolean mIsAutoCorrectionIndicatorOn;
 
-    public InputLogic(final LatinIME latinIME) {
+    public InputLogic(final LatinIME latinIME,
+            final SuggestionStripViewAccessor suggestionStripViewAccessor) {
         mLatinIME = latinIME;
+        mSuggestionStripViewAccessor = suggestionStripViewAccessor;
         mWordComposer = new WordComposer();
         mEventInterpreter = new EventInterpreter(latinIME);
         mConnection = new RichInputConnection(latinIME);
@@ -179,6 +184,108 @@
     }
 
     /**
+     * A suggestion was picked from the suggestion strip.
+     * @param settingsValues the current values of the settings.
+     * @param index the index of the suggestion.
+     * @param suggestionInfo the suggestion info.
+     */
+    // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
+    // interface
+    public void onPickSuggestionManually(final SettingsValues settingsValues,
+            final int index, final SuggestedWordInfo suggestionInfo,
+            // TODO: remove these two arguments
+            final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher) {
+        final SuggestedWords suggestedWords = mSuggestedWords;
+        final String suggestion = suggestionInfo.mWord;
+        // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
+        if (suggestion.length() == 1 && isShowingPunctuationList(settingsValues)) {
+            // Word separators are suggested before the user inputs something.
+            // So, LatinImeLogger logs "" as a user's input.
+            LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
+            // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
+            final int primaryCode = suggestion.charAt(0);
+            onCodeInput(primaryCode,
+                    Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
+                    settingsValues, handler, keyboardSwitcher);
+            if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+                ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
+                        false /* isBatchMode */, suggestedWords.mIsPrediction);
+            }
+            return;
+        }
+
+        mConnection.beginBatchEdit();
+        if (SpaceState.PHANTOM == mSpaceState && suggestion.length() > 0
+                // In the batch input mode, a manually picked suggested word should just replace
+                // the current batch input text and there is no need for a phantom space.
+                && !mWordComposer.isBatchMode()) {
+            final int firstChar = Character.codePointAt(suggestion, 0);
+            if (!settingsValues.isWordSeparator(firstChar)
+                    || settingsValues.isUsuallyPrecededBySpace(firstChar)) {
+                promotePhantomSpace(settingsValues);
+            }
+        }
+
+        // TODO: stop relying on mApplicationSpecifiedCompletions. The SuggestionInfo object
+        // should contain a reference to the CompletionInfo instead.
+        if (settingsValues.isApplicationSpecifiedCompletionsOn()
+                && mLatinIME.mApplicationSpecifiedCompletions != null
+                && index >= 0 && index < mLatinIME.mApplicationSpecifiedCompletions.length) {
+            mSuggestedWords = SuggestedWords.EMPTY;
+            mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
+            keyboardSwitcher.updateShiftState();
+            resetComposingState(true /* alsoResetLastComposedWord */);
+            final CompletionInfo completionInfo = mLatinIME.mApplicationSpecifiedCompletions[index];
+            mConnection.commitCompletion(completionInfo);
+            mConnection.endBatchEdit();
+            return;
+        }
+
+        // We need to log before we commit, because the word composer will store away the user
+        // typed word.
+        final String replacedWord = mWordComposer.getTypedWord();
+        LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
+        commitChosenWord(settingsValues, suggestion,
+                LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR);
+        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+            ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
+                    mWordComposer.isBatchMode(), suggestionInfo.mScore,
+                    suggestionInfo.mKind, suggestionInfo.mSourceDict.mDictType);
+        }
+        mConnection.endBatchEdit();
+        // Don't allow cancellation of manual pick
+        mLastComposedWord.deactivate();
+        // Space state must be updated before calling updateShiftState
+        mSpaceState = SpaceState.PHANTOM;
+        keyboardSwitcher.updateShiftState();
+
+        // We should show the "Touch again to save" hint if the user pressed the first entry
+        // AND it's in none of our current dictionaries (main, user or otherwise).
+        // Please note that if mSuggest is null, it means that everything is off: suggestion
+        // and correction, so we shouldn't try to show the hint
+        final Suggest suggest = mSuggest;
+        final boolean showingAddToDictionaryHint =
+                (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
+                        || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
+                        && suggest != null
+                        // If the suggestion is not in the dictionary, the hint should be shown.
+                        && !suggest.mDictionaryFacilitator.isValidWord(suggestion,
+                                true /* ignoreCase */);
+
+        if (settingsValues.mIsInternal) {
+            LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
+                    Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+        }
+        if (showingAddToDictionaryHint
+                && suggest.mDictionaryFacilitator.isUserDictionaryEnabled()) {
+            mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion);
+        } else {
+            // If we're not showing the "Touch again to save", then update the suggestion strip.
+            handler.postUpdateSuggestionStrip();
+        }
+    }
+
+    /**
      * Consider an update to the cursor position. Evaluate whether this update has happened as
      * part of normal typing or whether it was an explicit cursor move by the user. In any case,
      * do the necessary adjustments.
@@ -638,7 +745,7 @@
                 mSpaceState = SpaceState.WEAK;
             }
             // In case the "add to dictionary" hint was still displayed.
-            mLatinIME.dismissAddToDictionaryHint();
+            mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
         }
         handler.postUpdateSuggestionStrip();
         if (settingsValues.mIsInternal) {
@@ -714,7 +821,7 @@
                 if (maybeDoubleSpacePeriod(settingsValues, handler)) {
                     keyboardSwitcher.updateShiftState();
                     mSpaceState = SpaceState.DOUBLE;
-                } else if (!mLatinIME.isShowingPunctuationList()) {
+                } else if (!isShowingPunctuationList(settingsValues)) {
                     mSpaceState = SpaceState.WEAK;
                 }
             }
@@ -745,7 +852,7 @@
 
             // Set punctuation right away. onUpdateSelection will fire but tests whether it is
             // already displayed or not, so it's okay.
-            mLatinIME.setNeutralSuggestionStrip();
+            mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
         }
 
         keyboardSwitcher.updateShiftState();
@@ -1098,7 +1205,7 @@
         }
 
         if (!mWordComposer.isComposingWord() && !settingsValues.mBigramPredictionEnabled) {
-            mLatinIME.setNeutralSuggestionStrip();
+            mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
             return;
         }
 
@@ -1120,7 +1227,7 @@
         final SuggestedWords suggestedWords = holder.get(null,
                 Constants.GET_SUGGESTED_WORDS_TIMEOUT);
         if (suggestedWords != null) {
-            mLatinIME.showSuggestionStrip(suggestedWords);
+            mSuggestionStripViewAccessor.showSuggestionStrip(suggestedWords);
         }
     }
 
@@ -1326,6 +1433,15 @@
     }
 
     /**
+     * Find out if the punctuation list is shown in the suggestion strip.
+     * @return whether the current suggestions are the punctuation list.
+     */
+    // TODO: make this private. It's used through LatinIME for tests.
+    public boolean isShowingPunctuationList(final SettingsValues settingsValues) {
+        return settingsValues.mSpacingAndPunctuations.mSuggestPuncList == mSuggestedWords;
+    }
+
+    /**
      * Factor in auto-caps and manual caps and compute the current caps mode.
      * @param settingsValues the current settings values.
      * @param keyboardShiftMode the current shift mode of the keyboard. See
@@ -1482,7 +1598,7 @@
             final int newSelStart, final int newSelEnd) {
         final boolean shouldFinishComposition = mWordComposer.isComposingWord();
         resetComposingState(true /* alsoResetLastComposedWord */);
-        mLatinIME.setNeutralSuggestionStrip();
+        mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
         mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd,
                 shouldFinishComposition);
     }
@@ -1723,9 +1839,8 @@
                 // the segment of text starting at the supplied index and running for the length
                 // of the auto-correction flash. At this moment, the "typedWord" argument is
                 // ignored by TextView.
-                mConnection.commitCorrection(
-                        new CorrectionInfo(
-                        mConnection.getExpectedSelectionEnd() - typedWord.length(),
+                mConnection.commitCorrection(new CorrectionInfo(
+                        mConnection.getExpectedSelectionEnd() - autoCorrection.length(),
                         typedWord, autoCorrection));
             }
         }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
index 3691845..9f23459 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
@@ -511,7 +511,7 @@
                         final WeightedString word = getWordAtPosition(dictDecoder, headerSize,
                                 bigram.mAddress, options);
                         final int reconstructedFrequency =
-                                BinaryDictIOUtils.reconstructBigramFrequency(word.mFrequency,
+                                BinaryDictIOUtils.reconstructBigramFrequency(word.getProbability(),
                                         bigram.mFrequency);
                         bigrams.add(new WeightedString(word.mWord, reconstructedFrequency));
                     }
@@ -618,7 +618,7 @@
                 // words that are not also registered as unigrams so we don't have to avoid
                 // them explicitly here.
                 for (final WeightedString bigram : w.mBigrams) {
-                    newDict.setBigram(w.mWord, bigram.mWord, bigram.mFrequency);
+                    newDict.setBigram(w.mWord, bigram.mWord, bigram.getProbability());
                 }
             }
         }
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 5b0e839..ef23acb 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -67,29 +67,40 @@
     }
 
     /**
-     * A string with a frequency.
+     * A string with a probability.
      *
      * This represents an "attribute", that is either a bigram or a shortcut.
      */
     public static final class WeightedString {
         public final String mWord;
-        public int mFrequency;
-        public WeightedString(String word, int frequency) {
+        public ProbabilityInfo mProbabilityInfo;
+
+        public WeightedString(final String word, final int probability) {
             mWord = word;
-            mFrequency = frequency;
+            mProbabilityInfo = new ProbabilityInfo(probability);
+        }
+
+        public int getProbability() {
+            return mProbabilityInfo.mProbability;
+        }
+
+        public void setProbability(final int probability) {
+            mProbabilityInfo = new ProbabilityInfo(probability);
         }
 
         @Override
         public int hashCode() {
-            return Arrays.hashCode(new Object[] { mWord, mFrequency });
+            return Arrays.hashCode(new Object[] { mWord, mProbabilityInfo.mProbability,
+                    mProbabilityInfo.mTimestamp, mProbabilityInfo.mLevel,
+                    mProbabilityInfo.mCount });
         }
 
         @Override
         public boolean equals(Object o) {
             if (o == this) return true;
             if (!(o instanceof WeightedString)) return false;
-            WeightedString w = (WeightedString)o;
-            return mWord.equals(w.mWord) && mFrequency == w.mFrequency;
+            final WeightedString w = (WeightedString)o;
+            return mWord.equals(w.mWord) && mProbabilityInfo.equals(w.mProbabilityInfo);
         }
     }
 
@@ -200,18 +211,18 @@
         }
 
         /**
-         * Adds a word to the bigram list. Updates the frequency if the word already
+         * Adds a word to the bigram list. Updates the probability if the word already
          * exists.
          */
-        public void addBigram(final String word, final int frequency) {
+        public void addBigram(final String word, final int probability) {
             if (mBigrams == null) {
                 mBigrams = new ArrayList<WeightedString>();
             }
             WeightedString bigram = getBigram(word);
             if (bigram != null) {
-                bigram.mFrequency = frequency;
+                bigram.setProbability(probability);
             } else {
-                bigram = new WeightedString(word, frequency);
+                bigram = new WeightedString(word, probability);
                 mBigrams.add(bigram);
             }
         }
@@ -273,8 +284,8 @@
                         final WeightedString existingShortcut = getShortcut(shortcut.mWord);
                         if (existingShortcut == null) {
                             mShortcutTargets.add(shortcut);
-                        } else if (existingShortcut.mFrequency < shortcut.mFrequency) {
-                            existingShortcut.mFrequency = shortcut.mFrequency;
+                        } else if (existingShortcut.getProbability() < shortcut.getProbability()) {
+                            existingShortcut.setProbability(shortcut.getProbability());
                         }
                     }
                 }
@@ -289,8 +300,8 @@
                         final WeightedString existingBigram = getBigram(bigram.mWord);
                         if (existingBigram == null) {
                             mBigrams.add(bigram);
-                        } else if (existingBigram.mFrequency < bigram.mFrequency) {
-                            existingBigram.mFrequency = bigram.mFrequency;
+                        } else if (existingBigram.getProbability() < bigram.getProbability()) {
+                            existingBigram.setProbability(bigram.getProbability());
                         }
                     }
                 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java b/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java
new file mode 100644
index 0000000..79f924c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java
@@ -0,0 +1,63 @@
+/*
+ * 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.makedict;
+
+import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+
+public final class ProbabilityInfo {
+    public final int mProbability;
+    // mTimestamp, mLevel and mCount are historical info. These values are depend on the
+    // implementation in native code; thus, we must not use them and have any assumptions about
+    // them except for tests.
+    public final int mTimestamp;
+    public final int mLevel;
+    public final int mCount;
+
+    public ProbabilityInfo(final int probability) {
+        this(probability, BinaryDictionary.NOT_A_VALID_TIMESTAMP, 0, 0);
+    }
+
+    public ProbabilityInfo(final int probability, final int timestamp, final int level,
+            final int count) {
+        mProbability = probability;
+        mTimestamp = timestamp;
+        mLevel = level;
+        mCount = count;
+    }
+
+    public boolean hasHistoricalInfo() {
+        return mTimestamp != BinaryDictionary.NOT_A_VALID_TIMESTAMP;
+    }
+
+    @Override
+    public String toString() {
+        return mTimestamp + ":" + mLevel + ":" + mCount;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o == this) return true;
+      if (!(o instanceof ProbabilityInfo)) return false;
+      final ProbabilityInfo p = (ProbabilityInfo)o;
+      if (!hasHistoricalInfo() && !p.hasHistoricalInfo()) {
+          return mProbability == p.mProbability;
+      }
+      return mProbability == p.mProbability && mTimestamp == p.mTimestamp && mLevel == p.mLevel
+              && mCount == p.mCount;
+  }
+}
\ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java
index a3a6c2c..3de083e 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java
@@ -197,7 +197,7 @@
             final WeightedString target = shortcutIterator.next();
             final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags(
                     shortcutIterator.hasNext(),
-                    target.mFrequency);
+                    target.getProbability());
             mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, shortcutFlags,
                     FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
             final int shortcutShift = CharEncoding.writeString(mBuffer, mPosition, target.mWord);
@@ -231,7 +231,7 @@
             final int offset = addressOfBigram
                     - (mPosition + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
             final int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(bigramIterator.hasNext(),
-                    offset, bigram.mFrequency, unigramFrequencyForThisWord, bigram.mWord);
+                    offset, bigram.getProbability(), unigramFrequencyForThisWord, bigram.mWord);
             mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, bigramFlags,
                     FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
             mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition,
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
index 160775d..a5b0135 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
@@ -55,7 +55,9 @@
             throw new UnsupportedFormatException("Given path is not a directory.");
         }
         if (!BinaryDictionary.createEmptyDictFile(mDictPlacedDir.getAbsolutePath(),
-                FormatSpec.VERSION4, dict.mOptions.mAttributes)) {
+                FormatSpec.VERSION4, LocaleUtils.constructLocaleFromString(
+                dict.mOptions.mAttributes.get(DictionaryHeader.DICTIONARY_LOCALE_KEY)),
+                dict.mOptions.mAttributes)) {
             throw new IOException("Cannot create dictionary file : "
                 + mDictPlacedDir.getAbsolutePath());
         }
@@ -78,7 +80,7 @@
             } else {
                 for (final WeightedString shortcutTarget : word.mShortcutTargets) {
                     binaryDict.addUnigramWord(word.mWord, word.mFrequency,
-                            shortcutTarget.mWord, shortcutTarget.mFrequency,
+                            shortcutTarget.mWord, shortcutTarget.getProbability(),
                             word.mIsNotAWord, word.mIsBlacklistEntry, 0 /* timestamp */);
                 }
             }
@@ -89,7 +91,7 @@
         for (final Word word0 : dict) {
             if (null == word0.mBigrams) continue;
             for (final WeightedString word1 : word0.mBigrams) {
-                binaryDict.addBigramWords(word0.mWord, word1.mWord, word1.mFrequency,
+                binaryDict.addBigramWords(word0.mWord, word1.mWord, word1.getProbability(),
                         0 /* timestamp */);
                 if (binaryDict.needsToRunGC(true /* mindsBlockByGC */)) {
                     binaryDict.flushWithGC();
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java
new file mode 100644
index 0000000..60f1c7a
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java
@@ -0,0 +1,31 @@
+/*
+ * 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.suggestions;
+
+import com.android.inputmethod.latin.SuggestedWords;
+
+/**
+ * An object that gives basic control of a suggestion strip and some info on it.
+ */
+public interface SuggestionStripViewAccessor {
+    public boolean hasSuggestionStripView();
+    public void showAddToDictionaryHint(final String word);
+    public boolean isShowingAddToDictionaryHint();
+    public void dismissAddToDictionaryHint();
+    public void setNeutralSuggestionStrip();
+    public void showSuggestionStrip(final SuggestedWords suggestedWords);
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/WordProperty.java b/java/src/com/android/inputmethod/latin/utils/WordProperty.java
index c6630eb..37d1102 100644
--- a/java/src/com/android/inputmethod/latin/utils/WordProperty.java
+++ b/java/src/com/android/inputmethod/latin/utils/WordProperty.java
@@ -20,6 +20,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.BinaryDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.makedict.ProbabilityInfo;
 
 import java.util.ArrayList;
 
@@ -37,28 +38,12 @@
     public final ArrayList<ProbabilityInfo> mBigramProbabilityInfo = CollectionUtils.newArrayList();
     public final ArrayList<WeightedString> mShortcutTargets = CollectionUtils.newArrayList();
 
-    // TODO: Use this kind of Probability class for dictionary read/write code under the makedict
-    // package.
-    public static final class ProbabilityInfo {
-        public final int mProbability;
-        // mTimestamp, mLevel and mCount are historical info. These values are depend on the
-        // implementation in native code; thus, we must not use them and have any assumptions about
-        // them except for tests.
-        public final int mTimestamp;
-        public final int mLevel;
-        public final int mCount;
-
-        public ProbabilityInfo(final int[] probabilityInfo) {
-            mProbability = probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_PROBABILITY_INDEX];
-            mTimestamp = probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX];
-            mLevel = probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_LEVEL_INDEX];
-            mCount = probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_COUNT_INDEX];
-        }
-
-        @Override
-        public String toString() {
-            return mTimestamp + ":" + mLevel + ":" + mCount;
-        }
+    private static ProbabilityInfo createProbabilityInfoFromArray(final int[] probabilityInfo) {
+        return new ProbabilityInfo(
+                probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_PROBABILITY_INDEX],
+                probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX],
+                probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_LEVEL_INDEX],
+                probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_COUNT_INDEX]);
     }
 
     // This represents invalid word when the probability is BinaryDictionary.NOT_A_PROBABILITY.
@@ -73,14 +58,14 @@
         mIsBlacklisted = isBlacklisted;
         mHasBigrams = hasBigram;
         mHasShortcuts = hasShortcuts;
-        mProbabilityInfo = new ProbabilityInfo(probabilityInfo);
+        mProbabilityInfo = createProbabilityInfoFromArray(probabilityInfo);
 
         final int bigramTargetCount = bigramTargets.size();
         for (int i = 0; i < bigramTargetCount; i++) {
             final String bigramTargetString =
                     StringUtils.getStringFromNullTerminatedCodePointArray(bigramTargets.get(i));
             final ProbabilityInfo bigramProbability =
-                    new ProbabilityInfo(bigramProbabilityInfo.get(i));
+                    createProbabilityInfoFromArray(bigramProbabilityInfo.get(i));
             mBigramTargets.add(
                     new WeightedString(bigramTargetString, bigramProbability.mProbability));
             mBigramProbabilityInfo.add(bigramProbability);
@@ -123,7 +108,7 @@
         for (int i = 0; i < mBigramTargets.size(); i++) {
             builder.append("  bigram=" + mBigramTargets.get(i).mWord);
             builder.append(",");
-            builder.append("f=" + mBigramTargets.get(i).mFrequency);
+            builder.append("f=" + mBigramTargets.get(i).getProbability());
             if (mBigramProbabilityInfo.get(i).mTimestamp
                     != BinaryDictionary.NOT_A_VALID_TIMESTAMP) {
                 builder.append(",");
@@ -134,7 +119,7 @@
         for (int i = 0; i < mShortcutTargets.size(); i++) {
             builder.append("  shortcut=" + mShortcutTargets.get(i).mWord);
             builder.append(",");
-            builder.append("f=" + mShortcutTargets.get(i).mFrequency);
+            builder.append("f=" + mShortcutTargets.get(i).getProbability());
             builder.append("\n");
         }
         return builder.toString();
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 4372cbe..f5c3ee6 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -29,6 +29,7 @@
 #include "suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h"
 #include "suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h"
 #include "utils/autocorrection_threshold_utils.h"
+#include "utils/char_utils.h"
 #include "utils/time_keeper.h"
 
 namespace latinime {
@@ -37,13 +38,15 @@
 
 // TODO: Move to makedict.
 static jboolean latinime_BinaryDictionary_createEmptyDictFile(JNIEnv *env, jclass clazz,
-        jstring filePath, jlong dictVersion, jobjectArray attributeKeyStringArray,
+        jstring filePath, jlong dictVersion, jstring locale, jobjectArray attributeKeyStringArray,
         jobjectArray attributeValueStringArray) {
     const jsize filePathUtf8Length = env->GetStringUTFLength(filePath);
     char filePathChars[filePathUtf8Length + 1];
     env->GetStringUTFRegion(filePath, 0, env->GetStringLength(filePath), filePathChars);
     filePathChars[filePathUtf8Length] = '\0';
-
+    jsize localeLength = env->GetStringLength(locale);
+    jchar localeCodePoints[localeLength];
+    env->GetStringRegion(locale, 0, localeLength, localeCodePoints);
     const int keyCount = env->GetArrayLength(attributeKeyStringArray);
     const int valueCount = env->GetArrayLength(attributeValueStringArray);
     if (keyCount != valueCount) {
@@ -73,7 +76,7 @@
     }
 
     return DictFileWritingUtils::createEmptyDictFile(filePathChars, static_cast<int>(dictVersion),
-            &attributeMap);
+            CharUtils::convertShortArrayToIntVector(localeCodePoints, localeLength), &attributeMap);
 }
 
 static jlong latinime_BinaryDictionary_open(JNIEnv *env, jclass clazz, jstring sourceDir,
@@ -503,7 +506,8 @@
 static const JNINativeMethod sMethods[] = {
     {
         const_cast<char *>("createEmptyDictFileNative"),
-        const_cast<char *>("(Ljava/lang/String;J[Ljava/lang/String;[Ljava/lang/String;)Z"),
+        const_cast<char *>(
+                "(Ljava/lang/String;JLjava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)Z"),
         reinterpret_cast<void *>(latinime_BinaryDictionary_createEmptyDictFile)
     },
     {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
index 7504524..b5b5ed7 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.cpp
@@ -32,6 +32,7 @@
 // Historical info is information that is needed to support decaying such as timestamp, level and
 // count.
 const char *const HeaderPolicy::HAS_HISTORICAL_INFO_KEY = "HAS_HISTORICAL_INFO";
+const char *const HeaderPolicy::LOCALE_KEY = "locale"; // match Java declaration
 const int HeaderPolicy::DEFAULT_MULTIPLE_WORDS_DEMOTION_RATE = 100;
 const float HeaderPolicy::MULTIPLE_WORD_COST_MULTIPLIER_SCALE = 100.0f;
 
@@ -59,6 +60,10 @@
     outValue[terminalIndex] = '\0';
 }
 
+const std::vector<int> HeaderPolicy::readLocale() const {
+    return HeaderReadWriteUtils::readCodePointVectorAttributeValue(&mAttributeMap, LOCALE_KEY);
+}
+
 float HeaderPolicy::readMultipleWordCostMultiplier() const {
     const int demotionRate = HeaderReadWriteUtils::readIntAttributeValue(&mAttributeMap,
             MULTIPLE_WORDS_DEMOTION_RATE_KEY, DEFAULT_MULTIPLE_WORDS_DEMOTION_RATE);
@@ -116,6 +121,7 @@
     // Set the current time as the generation time.
     HeaderReadWriteUtils::setIntAttribute(outAttributeMap, DATE_KEY,
             TimeKeeper::peekCurrentTime());
+    HeaderReadWriteUtils::setCodePointVectorAttribute(outAttributeMap, LOCALE_KEY, mLocale);
     if (updatesLastDecayedTime) {
         // Set current time as the last updated time.
         HeaderReadWriteUtils::setIntAttribute(outAttributeMap, LAST_DECAYED_TIME_KEY,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
index 1320c65..a05e00c 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
@@ -23,6 +23,7 @@
 #include "suggest/core/policy/dictionary_header_structure_policy.h"
 #include "suggest/policyimpl/dictionary/header/header_read_write_utils.h"
 #include "suggest/policyimpl/dictionary/utils/format_utils.h"
+#include "utils/char_utils.h"
 #include "utils/time_keeper.h"
 
 namespace latinime {
@@ -35,6 +36,7 @@
               mDictionaryFlags(HeaderReadWriteUtils::getFlags(dictBuf)),
               mSize(HeaderReadWriteUtils::getHeaderSize(dictBuf)),
               mAttributeMap(createAttributeMapAndReadAllAttributes(dictBuf)),
+              mLocale(readLocale()),
               mMultiWordCostMultiplier(readMultipleWordCostMultiplier()),
               mRequiresGermanUmlautProcessing(readRequiresGermanUmlautProcessing()),
               mIsDecayingDict(HeaderReadWriteUtils::readBoolAttributeValue(&mAttributeMap,
@@ -54,10 +56,11 @@
 
     // Constructs header information using an attribute map.
     HeaderPolicy(const FormatUtils::FORMAT_VERSION dictFormatVersion,
+            const std::vector<int> locale,
             const HeaderReadWriteUtils::AttributeMap *const attributeMap)
             : mDictFormatVersion(dictFormatVersion),
               mDictionaryFlags(HeaderReadWriteUtils::createAndGetDictionaryFlagsUsingAttributeMap(
-                      attributeMap)), mSize(0), mAttributeMap(*attributeMap),
+                      attributeMap)), mSize(0), mAttributeMap(*attributeMap), mLocale(locale),
               mMultiWordCostMultiplier(readMultipleWordCostMultiplier()),
               mRequiresGermanUmlautProcessing(readRequiresGermanUmlautProcessing()),
               mIsDecayingDict(HeaderReadWriteUtils::readBoolAttributeValue(&mAttributeMap,
@@ -68,12 +71,13 @@
                       DATE_KEY, TimeKeeper::peekCurrentTime() /* defaultValue */)),
               mUnigramCount(0), mBigramCount(0), mExtendedRegionSize(0),
               mHasHistoricalInfoOfWords(HeaderReadWriteUtils::readBoolAttributeValue(
-                      &mAttributeMap, HAS_HISTORICAL_INFO_KEY, false /* defaultValue */)) {}
+                      &mAttributeMap, HAS_HISTORICAL_INFO_KEY, false /* defaultValue */)) {
+        }
 
     // Temporary dummy header.
     HeaderPolicy()
             : mDictFormatVersion(FormatUtils::UNKNOWN_VERSION), mDictionaryFlags(0), mSize(0),
-              mAttributeMap(), mMultiWordCostMultiplier(0.0f),
+              mAttributeMap(), mLocale(CharUtils::EMPTY_STRING), mMultiWordCostMultiplier(0.0f),
               mRequiresGermanUmlautProcessing(false), mIsDecayingDict(false),
               mDate(0), mLastDecayedTime(0), mUnigramCount(0), mBigramCount(0),
               mExtendedRegionSize(0), mHasHistoricalInfoOfWords(false) {}
@@ -174,6 +178,7 @@
     static const char *const BIGRAM_COUNT_KEY;
     static const char *const EXTENDED_REGION_SIZE_KEY;
     static const char *const HAS_HISTORICAL_INFO_KEY;
+    static const char *const LOCALE_KEY;
     static const int DEFAULT_MULTIPLE_WORDS_DEMOTION_RATE;
     static const float MULTIPLE_WORD_COST_MULTIPLIER_SCALE;
 
@@ -181,6 +186,7 @@
     const HeaderReadWriteUtils::DictionaryFlags mDictionaryFlags;
     const int mSize;
     HeaderReadWriteUtils::AttributeMap mAttributeMap;
+    const std::vector<int> mLocale;
     const float mMultiWordCostMultiplier;
     const bool mRequiresGermanUmlautProcessing;
     const bool mIsDecayingDict;
@@ -191,6 +197,7 @@
     const int mExtendedRegionSize;
     const bool mHasHistoricalInfoOfWords;
 
+    const std::vector<int> readLocale() const;
     float readMultipleWordCostMultiplier() const;
     bool readRequiresGermanUmlautProcessing() const;
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
index 6b45986..850b0d8 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
@@ -130,6 +130,13 @@
     return true;
 }
 
+/* static */ void HeaderReadWriteUtils::setCodePointVectorAttribute(
+        AttributeMap *const headerAttributes, const char *const key, const std::vector<int> value) {
+    AttributeMap::key_type keyVector;
+    insertCharactersIntoVector(key, &keyVector);
+    (*headerAttributes)[keyVector] = value;
+}
+
 /* static */ void HeaderReadWriteUtils::setBoolAttribute(AttributeMap *const headerAttributes,
         const char *const key, const bool value) {
     setIntAttribute(headerAttributes, key, value ? 1 : 0);
@@ -151,6 +158,18 @@
     (*headerAttributes)[*key] = valueVector;
 }
 
+/* static */ const std::vector<int> HeaderReadWriteUtils::readCodePointVectorAttributeValue(
+        const AttributeMap *const headerAttributes, const char *const key) {
+    AttributeMap::key_type keyVector;
+    insertCharactersIntoVector(key, &keyVector);
+    AttributeMap::const_iterator it = headerAttributes->find(keyVector);
+    if (it == headerAttributes->end()) {
+        return std::vector<int>();
+    } else {
+        return it->second;
+    }
+}
+
 /* static */ bool HeaderReadWriteUtils::readBoolAttributeValue(
         const AttributeMap *const headerAttributes, const char *const key,
         const bool defaultValue) {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
index fc24bbd..3433c04 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
@@ -63,12 +63,18 @@
     /**
      * Methods for header attributes.
      */
+    static void setCodePointVectorAttribute(AttributeMap *const headerAttributes,
+            const char *const key, const std::vector<int> value);
+
     static void setBoolAttribute(AttributeMap *const headerAttributes,
             const char *const key, const bool value);
 
     static void setIntAttribute(AttributeMap *const headerAttributes,
             const char *const key, const int value);
 
+    static const std::vector<int> readCodePointVectorAttributeValue(
+            const AttributeMap *const headerAttributes, const char *const key);
+
     static bool readBoolAttributeValue(const AttributeMap *const headerAttributes,
             const char *const key, const bool defaultValue);
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
index 84403c8..335ea0d 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
@@ -31,11 +31,12 @@
 const char *const DictFileWritingUtils::TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE = ".tmp";
 
 /* static */ bool DictFileWritingUtils::createEmptyDictFile(const char *const filePath,
-        const int dictVersion, const HeaderReadWriteUtils::AttributeMap *const attributeMap) {
+        const int dictVersion, const std::vector<int> localeAsCodePointVector,
+        const HeaderReadWriteUtils::AttributeMap *const attributeMap) {
     TimeKeeper::setCurrentTime();
     switch (dictVersion) {
         case FormatUtils::VERSION_4:
-            return createEmptyV4DictFile(filePath, attributeMap);
+            return createEmptyV4DictFile(filePath, localeAsCodePointVector, attributeMap);
         default:
             AKLOGE("Cannot create dictionary %s because format version %d is not supported.",
                     filePath, dictVersion);
@@ -44,8 +45,9 @@
 }
 
 /* static */ bool DictFileWritingUtils::createEmptyV4DictFile(const char *const dirPath,
+        const std::vector<int> localeAsCodePointVector,
         const HeaderReadWriteUtils::AttributeMap *const attributeMap) {
-    HeaderPolicy headerPolicy(FormatUtils::VERSION_4, attributeMap);
+    HeaderPolicy headerPolicy(FormatUtils::VERSION_4, localeAsCodePointVector, attributeMap);
     Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers =
             Ver4DictBuffers::createVer4DictBuffers(&headerPolicy);
     headerPolicy.fillInAndWriteHeaderToBuffer(true /* updatesLastDecayedTime */,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h
index bdf9fd6..c2ecff4 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h
@@ -31,6 +31,7 @@
     static const char *const TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE;
 
     static bool createEmptyDictFile(const char *const filePath, const int dictVersion,
+            const std::vector<int> localeAsCodePointVector,
             const HeaderReadWriteUtils::AttributeMap *const attributeMap);
 
     static bool flushAllHeaderAndBodyToFile(const char *const filePath,
@@ -44,6 +45,7 @@
     DISALLOW_IMPLICIT_CONSTRUCTORS(DictFileWritingUtils);
 
     static bool createEmptyV4DictFile(const char *const filePath,
+            const std::vector<int> localeAsCodePointVector,
             const HeaderReadWriteUtils::AttributeMap *const attributeMap);
 
     static bool flushBufferToFile(const char *const filePath,
diff --git a/native/jni/src/utils/char_utils.cpp b/native/jni/src/utils/char_utils.cpp
index 0e70396..d41fc89 100644
--- a/native/jni/src/utils/char_utils.cpp
+++ b/native/jni/src/utils/char_utils.cpp
@@ -1273,4 +1273,6 @@
     /* U+04F0 */ 0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04F6, 0x04F7,
     /* U+04F8 */ 0x042B, 0x044B, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF,
 };
+
+/* static */ const std::vector<int> CharUtils::EMPTY_STRING(1 /* size */, '\0' /* value */);
 } // namespace latinime
diff --git a/native/jni/src/utils/char_utils.h b/native/jni/src/utils/char_utils.h
index 41663c8..98b8966 100644
--- a/native/jni/src/utils/char_utils.h
+++ b/native/jni/src/utils/char_utils.h
@@ -18,6 +18,7 @@
 #define LATINIME_CHAR_UTILS_H
 
 #include <cctype>
+#include <vector>
 
 #include "defines.h"
 
@@ -85,7 +86,15 @@
         return spaceCount;
     }
 
+    static AK_FORCE_INLINE std::vector<int> convertShortArrayToIntVector(
+            const unsigned short *const source, const int length) {
+        std::vector<int> destination;
+        destination.insert(destination.end(), source, source + length);
+        return destination; // Copies the vector
+    }
+
     static unsigned short latin_tolower(const unsigned short c);
+    static const std::vector<int> EMPTY_STRING;
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(CharUtils);
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
index e29181c..9e43bd4 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
@@ -16,12 +16,15 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED;
+import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;
+
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.inputmethod.latin.Constants;
 
 @SmallTest
-public final class KeySpecParserTests extends KeySpecParserBase {
+public final class KeySpecParserTests extends KeySpecParserTestsBase {
     @Override
     protected void assertParser(final String message, final String keySpec,
             final String expectedLabel, final String expectedOutputText, final int expectedIcon,
@@ -40,4 +43,13 @@
                 Constants.printableCode(expectedCode),
                 Constants.printableCode(actualCode));
     }
+
+    // TODO: Remove this method.
+    // These should throw {@link KeySpecParserError} when Key.keyLabel attribute become mandatory.
+    public void testEmptySpec() {
+        assertParser("Null spec", null,
+                null, null, ICON_UNDEFINED, CODE_UNSPECIFIED);
+        assertParser("Empty spec", "",
+                null, null, ICON_UNDEFINED, CODE_UNSPECIFIED);
+    }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserBase.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTestsBase.java
similarity index 97%
rename from tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserBase.java
rename to tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTestsBase.java
index aecef23..cb640b3 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTestsBase.java
@@ -30,7 +30,7 @@
 
 import java.util.Locale;
 
-abstract class KeySpecParserBase extends AndroidTestCase {
+abstract class KeySpecParserTestsBase extends AndroidTestCase {
     private final static Locale TEST_LOCALE = Locale.ENGLISH;
     protected final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
     protected final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
@@ -101,7 +101,9 @@
                 "a", null, ICON_UNDEFINED, 'a');
         assertParser("Single surrogate", SURROGATE_PAIR1,
                 SURROGATE_PAIR1, null, ICON_UNDEFINED, SURROGATE_CODE1);
-        assertParser("Single escaped bar", "\\|",
+        assertParser("Sole vertical bar", "|",
+                "|", null, ICON_UNDEFINED, '|');
+        assertParser("Single escaped vertical bar", "\\|",
                 "|", null, ICON_UNDEFINED, '|');
         assertParser("Single escaped escape", "\\\\",
                 "\\", null, ICON_UNDEFINED, '\\');
@@ -251,8 +253,6 @@
     }
 
     public void testFormatError() {
-        assertParserError("Empty spec", "", null,
-                null, ICON_UNDEFINED, CODE_UNSPECIFIED);
         assertParserError("Empty label with outputText", "|a",
                 null, "a", ICON_UNDEFINED, CODE_UNSPECIFIED);
         assertParserError("Empty label with code", "|" + CODE_SETTINGS,
@@ -261,8 +261,6 @@
                 "a", null, ICON_UNDEFINED, CODE_UNSPECIFIED);
         assertParserError("Empty outputText with icon", ICON_SETTINGS + "|",
                 null, null, mSettingsIconId, CODE_UNSPECIFIED);
-        assertParserError("Empty icon and code", "|",
-                null, null, ICON_UNDEFINED, CODE_UNSPECIFIED);
         assertParserError("Icon without code", ICON_SETTINGS,
                 null, null, mSettingsIconId, CODE_UNSPECIFIED);
         assertParserError("Non existing icon", ICON_NON_EXISTING + "|abc",
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecTests.java b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecTests.java
index 213e2d4..ea25bcf 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecTests.java
@@ -16,6 +16,9 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED;
+import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;
+
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.inputmethod.latin.Constants;
@@ -24,7 +27,7 @@
 import java.util.Locale;
 
 @SmallTest
-public final class MoreKeySpecTests extends KeySpecParserBase {
+public final class MoreKeySpecTests extends KeySpecParserTestsBase {
     @Override
     protected void assertParser(final String message, final String moreKeySpec,
             final String expectedLabel, final String expectedOutputText, final int expectedIconId,
@@ -42,6 +45,14 @@
                 Constants.printableCode(spec.mCode));
     }
 
+    // TODO: Move this method to {@link KeySpecParserBase}.
+    public void testEmptySpec() {
+        assertParserError("Null spec", null,
+                null, null, ICON_UNDEFINED, CODE_UNSPECIFIED);
+        assertParserError("Empty spec", "",
+                null, null, ICON_UNDEFINED, CODE_UNSPECIFIED);
+    }
+
     private static void assertArrayEquals(final String message, final Object[] expected,
             final Object[] actual) {
         if (expected == actual) {
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
index c41bbd7..7d664c8 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
@@ -28,6 +28,7 @@
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 import com.android.inputmethod.latin.utils.FileUtils;
+import com.android.inputmethod.latin.utils.LocaleUtils;
 
 import java.io.File;
 import java.io.IOException;
@@ -104,15 +105,14 @@
         FileUtils.deleteRecursively(file);
         Map<String, String> attributeMap = new HashMap<String, String>();
         attributeMap.put(DictionaryHeader.DICTIONARY_ID_KEY, dictId);
-        attributeMap.put(DictionaryHeader.DICTIONARY_LOCALE_KEY, dictId);
         attributeMap.put(DictionaryHeader.DICTIONARY_VERSION_KEY,
                 String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
         attributeMap.put(DictionaryHeader.USES_FORGETTING_CURVE_KEY,
                 DictionaryHeader.ATTRIBUTE_VALUE_TRUE);
         attributeMap.put(DictionaryHeader.HAS_HISTORICAL_INFO_KEY,
                 DictionaryHeader.ATTRIBUTE_VALUE_TRUE);
-        if (BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
-                FormatSpec.VERSION4, attributeMap)) {
+        if (BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(), FormatSpec.VERSION4,
+                LocaleUtils.constructLocaleFromString(TEST_LOCALE), attributeMap)) {
             return file;
         } else {
             throw new IOException("Empty dictionary " + file.getAbsolutePath()
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
index bab86e5..b1bdef8 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -69,8 +69,8 @@
         file.delete();
         file.mkdir();
         Map<String, String> attributeMap = new HashMap<String, String>();
-        if (BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
-                FormatSpec.VERSION4, attributeMap)) {
+        if (BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(), FormatSpec.VERSION4,
+                Locale.ENGLISH, attributeMap)) {
             return file;
         } else {
             throw new IOException("Empty dictionary " + file.getAbsolutePath()
@@ -962,7 +962,7 @@
             for (int j = 0; j < unigramProperty.mBigramTargets.size(); j++) {
                 final String word1 = unigramProperty.mBigramTargets.get(j).mWord;
                 assertTrue(bigramWord1s.contains(word1));
-                final int probability = unigramProperty.mBigramTargets.get(j).mFrequency;
+                final int probability = unigramProperty.mBigramTargets.get(j).getProbability();
                 assertEquals((int)bigramProbabilities.get(new Pair<String, String>(word0, word1)),
                         probability);
                 assertEquals(unigramProperty.mBigramProbabilityInfo.get(j).mProbability,
@@ -1053,7 +1053,7 @@
             for (int j = 0; j < wordProperty.mBigramTargets.size(); j++) {
                 final String word1 = wordProperty.mBigramTargets.get(j).mWord;
                 assertTrue(bigramWord1s.contains(word1));
-                final int probability = wordProperty.mBigramTargets.get(j).mFrequency;
+                final int probability = wordProperty.mBigramTargets.get(j).getProbability();
                 final Pair<String, String> bigram = new Pair<String, String>(word0, word1);
                 assertEquals((int)bigramProbabilitiesToCheckLater.get(bigram), probability);
                 bigramSet.remove(bigram);
@@ -1087,7 +1087,7 @@
         WordProperty wordProperty = binaryDictionary.getWordProperty("aaa");
         assertEquals(1, wordProperty.mShortcutTargets.size());
         assertEquals("zzz", wordProperty.mShortcutTargets.get(0).mWord);
-        assertEquals(shortcutProbability, wordProperty.mShortcutTargets.get(0).mFrequency);
+        assertEquals(shortcutProbability, wordProperty.mShortcutTargets.get(0).getProbability());
         final int updatedShortcutProbability = 2;
         binaryDictionary.addUnigramWord("aaa", unigramProbability, "zzz",
                 updatedShortcutProbability, false /* isNotAWord */, false /* isBlacklisted */,
@@ -1096,7 +1096,7 @@
         assertEquals(1, wordProperty.mShortcutTargets.size());
         assertEquals("zzz", wordProperty.mShortcutTargets.get(0).mWord);
         assertEquals(updatedShortcutProbability,
-                wordProperty.mShortcutTargets.get(0).mFrequency);
+                wordProperty.mShortcutTargets.get(0).getProbability());
         binaryDictionary.addUnigramWord("aaa", unigramProbability, "yyy",
                 shortcutProbability, false /* isNotAWord */, false /* isBlacklisted */,
                 0 /* timestamp */);
@@ -1107,7 +1107,8 @@
         assertEquals(2, wordProperty.mShortcutTargets.size());
         for (WeightedString shortcutTarget : wordProperty.mShortcutTargets) {
             assertTrue(shortcutTargets.containsKey(shortcutTarget.mWord));
-            assertEquals((int)shortcutTargets.get(shortcutTarget.mWord), shortcutTarget.mFrequency);
+            assertEquals((int)shortcutTargets.get(shortcutTarget.mWord),
+                    shortcutTarget.getProbability());
             shortcutTargets.remove(shortcutTarget.mWord);
         }
         shortcutTargets.put("zzz", updatedShortcutProbability);
@@ -1117,7 +1118,8 @@
         assertEquals(2, wordProperty.mShortcutTargets.size());
         for (WeightedString shortcutTarget : wordProperty.mShortcutTargets) {
             assertTrue(shortcutTargets.containsKey(shortcutTarget.mWord));
-            assertEquals((int)shortcutTargets.get(shortcutTarget.mWord), shortcutTarget.mFrequency);
+            assertEquals((int)shortcutTargets.get(shortcutTarget.mWord),
+                    shortcutTarget.getProbability());
             shortcutTargets.remove(shortcutTarget.mWord);
         }
     }
@@ -1193,7 +1195,7 @@
             for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) {
                 final String targetCodePonts = shortcutTarget.mWord;
                 assertEquals((int)shortcutTargets.get(word).get(targetCodePonts),
-                        shortcutTarget.mFrequency);
+                        shortcutTarget.getProbability());
             }
         }
     }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java
index 16f82da..b984060 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java
@@ -127,7 +127,7 @@
                 if (null != word) {
                     dict.add(word, freq, shortcuts.isEmpty() ? null : shortcuts, isNotAWord);
                     for (WeightedString s : bigrams) {
-                        dict.setBigram(word, s.mWord, s.mFrequency);
+                        dict.setBigram(word, s.mWord, s.getProbability());
                     }
                 }
                 if (!shortcuts.isEmpty()) shortcuts = new ArrayList<WeightedString>();
@@ -185,7 +185,7 @@
         if (null != word) {
             dict.add(word, freq, shortcuts.isEmpty() ? null : shortcuts, isNotAWord);
             for (WeightedString s : bigrams) {
-                dict.setBigram(word, s.mWord, s.mFrequency);
+                dict.setBigram(word, s.mWord, s.getProbability());
             }
         }
 
@@ -222,13 +222,13 @@
             if (null != word.mShortcutTargets) {
                 for (WeightedString target : word.mShortcutTargets) {
                     destination.write("  " + SHORTCUT_TAG + "=" + target.mWord + ","
-                            + FREQUENCY_TAG + "=" + target.mFrequency + "\n");
+                            + FREQUENCY_TAG + "=" + target.getProbability() + "\n");
                 }
             }
             if (null != word.mBigrams) {
                 for (WeightedString bigram : word.mBigrams) {
                     destination.write("  " + BIGRAM_TAG + "=" + bigram.mWord + ","
-                            + FREQUENCY_TAG + "=" + bigram.mFrequency + "\n");
+                            + FREQUENCY_TAG + "=" + bigram.getProbability() + "\n");
                 }
             }
         }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Diff.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Diff.java
index 7ac3c67..c9f6bd5 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Diff.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Diff.java
@@ -159,7 +159,7 @@
             if (null == list0) return false;
             for (final WeightedString attribute0 : list0) {
                 System.out.println(type + " removed: " + word + " " + attribute0.mWord + " "
-                        + attribute0.mFrequency);
+                        + attribute0.getProbability());
             }
             return true;
         }
@@ -175,8 +175,8 @@
                     for (final WeightedString attribute1 : list1) {
                         if (attribute0.mWord.equals(attribute1.mWord)) {
                             System.out.println(type + " freq changed: " + word + " "
-                                    + attribute0.mWord + " " + attribute0.mFrequency + " -> "
-                                    + attribute1.mFrequency);
+                                    + attribute0.mWord + " " + attribute0.getProbability() + " -> "
+                                    + attribute1.getProbability());
                             list1.remove(attribute1);
                             foundString = true;
                             break;
@@ -185,7 +185,7 @@
                     if (!foundString) {
                         // We come here if we haven't found any matching string.
                         System.out.println(type + " removed: " + word + " " + attribute0.mWord + " "
-                                + attribute0.mFrequency);
+                                + attribute0.getProbability());
                     }
                 } else {
                     list1.remove(attribute0);
@@ -197,7 +197,7 @@
         for (final WeightedString attribute1 : list1) {
             hasDifferences = true;
             System.out.println(type + " added: " + word + " " + attribute1.mWord + " "
-                    + attribute1.mFrequency);
+                    + attribute1.getProbability());
         }
         return hasDifferences;
     }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Info.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Info.java
index 350f427..8f17fcd 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Info.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Info.java
@@ -51,7 +51,8 @@
             if (null != w.mShortcutTargets) {
                 shortcutCount += w.mShortcutTargets.size();
                 for (WeightedString shortcutTarget : w.mShortcutTargets) {
-                    if (FormatSpec.SHORTCUT_WHITELIST_FREQUENCY == shortcutTarget.mFrequency) {
+                    if (FormatSpec.SHORTCUT_WHITELIST_FREQUENCY
+                            == shortcutTarget.getProbability()) {
                         ++whitelistCount;
                     }
                 }
@@ -84,8 +85,9 @@
         } else {
             for (final WeightedString shortcutTarget : shortcutTargets) {
                 System.out.println("  Shortcut target: " + shortcutTarget.mWord + " ("
-                        + (FormatSpec.SHORTCUT_WHITELIST_FREQUENCY == shortcutTarget.mFrequency
-                                ? "whitelist" : shortcutTarget.mFrequency) + ")");
+                        + (FormatSpec.SHORTCUT_WHITELIST_FREQUENCY
+                                == shortcutTarget.getProbability() ?
+                                        "whitelist" : shortcutTarget.getProbability()) + ")");
             }
         }
         final ArrayList<WeightedString> bigrams = ptNode.getBigrams();
@@ -93,7 +95,8 @@
             System.out.println("  No bigrams");
         } else {
             for (final WeightedString bigram : bigrams) {
-                System.out.println("  Bigram: " + bigram.mWord + " (" + bigram.mFrequency + ")");
+                System.out.println(
+                        "  Bigram: " + bigram.mWord + " (" + bigram.getProbability() + ")");
             }
         }
     }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/XmlDictInputOutput.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
index d226251..cdc487b 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
@@ -319,7 +319,7 @@
             final ArrayList<WeightedString> bigramList = bigramMap.get(firstWord);
             for (final WeightedString bigram : bigramList) {
                 if (!dict.hasWord(bigram.mWord)) continue;
-                dict.setBigram(firstWord, bigram.mWord, bigram.mFrequency);
+                dict.setBigram(firstWord, bigram.mWord, bigram.getProbability());
             }
         }
         return dict;
@@ -369,7 +369,7 @@
                 destination.write("\n");
                 for (WeightedString target : word.mShortcutTargets) {
                     destination.write("    <" + SHORTCUT_TAG + " " + FREQUENCY_ATTR + "=\""
-                            + target.mFrequency + "\">" + target.mWord + "</" + SHORTCUT_TAG
+                            + target.getProbability() + "\">" + target.mWord + "</" + SHORTCUT_TAG
                             + ">\n");
                 }
                 destination.write("  ");
@@ -378,7 +378,8 @@
                 destination.write("\n");
                 for (WeightedString bigram : word.mBigrams) {
                     destination.write("    <" + BIGRAM_TAG + " " + FREQUENCY_ATTR + "=\""
-                            + bigram.mFrequency + "\">" + bigram.mWord + "</" + BIGRAM_TAG + ">\n");
+                            + bigram.getProbability() + "\">" + bigram.mWord
+                            + "</" + BIGRAM_TAG + ">\n");
                 }
                 destination.write("  ");
             }