merge in jb-mr1-release history after reset to jb-mr1-dev
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 9a888ad..9764df0 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -28,7 +28,7 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.nio.ByteBuffer;
+import java.nio.BufferUnderflowException;
 import java.nio.channels.FileChannel;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -357,14 +357,15 @@
         try {
             // Read the version of the file
             inStream = new FileInputStream(f);
-            final ByteBuffer buffer = inStream.getChannel().map(
-                    FileChannel.MapMode.READ_ONLY, 0, f.length());
-            final int magic = buffer.getInt();
+            final BinaryDictInputOutput.ByteBufferWrapper buffer =
+                    new BinaryDictInputOutput.ByteBufferWrapper(inStream.getChannel().map(
+                            FileChannel.MapMode.READ_ONLY, 0, f.length()));
+            final int magic = buffer.readInt();
             if (magic != FormatSpec.VERSION_2_MAGIC_NUMBER) {
                 return false;
             }
-            final int formatVersion = buffer.getInt();
-            final int headerSize = buffer.getInt();
+            final int formatVersion = buffer.readInt();
+            final int headerSize = buffer.readInt();
             final HashMap<String, String> options = CollectionUtils.newHashMap();
             BinaryDictInputOutput.populateOptions(buffer, headerSize, options);
 
@@ -382,6 +383,8 @@
             return false;
         } catch (NumberFormatException e) {
             return false;
+        } catch (BufferUnderflowException e) {
+            return false;
         } finally {
             if (inStream != null) {
                 try {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index db8f269..10a6e95 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -741,6 +741,7 @@
         // switcher.loadKeyboard; in apps like Talk, we come here when the text is sent and the
         // field gets emptied and we need to re-evaluate the shift state, but not the whole layout
         // which would be disruptive.
+        // Space state must be updated before calling updateShiftState
         mKeyboardSwitcher.updateShiftState();
 
         mHandler.cancelUpdateSuggestionStrip();
@@ -1100,25 +1101,11 @@
 
         final EditorInfo ei = getCurrentInputEditorInfo();
         if (ei == null) return Constants.TextUtils.CAP_MODE_OFF;
-
         final int inputType = ei.inputType;
-        if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
-            return TextUtils.CAP_MODE_CHARACTERS;
-        }
-
-        final boolean noNeedToCheckCapsMode = (inputType & (InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
-                | InputType.TYPE_TEXT_FLAG_CAP_WORDS)) == 0;
-        if (noNeedToCheckCapsMode) return Constants.TextUtils.CAP_MODE_OFF;
-
-        // Avoid making heavy round-trip IPC calls of {@link InputConnection#getCursorCapsMode}
-        // unless needed.
-        if (mWordComposer.isComposingWord()) return Constants.TextUtils.CAP_MODE_OFF;
-
-        // TODO: This blocking IPC call is heavy. Consider doing this without using IPC calls.
-        // Note: getCursorCapsMode() returns the current capitalization mode that is any
-        // combination of CAP_MODE_CHARACTERS, CAP_MODE_WORDS, and CAP_MODE_SENTENCES. 0 means none
-        // of them.
-        return mConnection.getCursorCapsMode(inputType, mSubtypeSwitcher.getCurrentSubtypeLocale());
+        // Warning: this depends on mSpaceState, which may not be the most current value. If
+        // mSpaceState gets updated later, whoever called this may need to be told about it.
+        return mConnection.getCursorCapsMode(inputType, mSubtypeSwitcher.getCurrentSubtypeLocale(),
+                SPACE_STATE_PHANTOM == mSpaceState);
     }
 
     // Factor in auto-caps and manual caps and compute the current caps mode.
@@ -1391,9 +1378,10 @@
         }
         mConnection.commitText(text, 1);
         mConnection.endBatchEdit();
+        // Space state must be updated before calling updateShiftState
+        mSpaceState = SPACE_STATE_NONE;
         mKeyboardSwitcher.updateShiftState();
         mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
-        mSpaceState = SPACE_STATE_NONE;
         mEnteredText = text;
     }
 
@@ -1509,8 +1497,9 @@
         mConnection.setComposingText(batchInputText, 1);
         mExpectingUpdateSelection = true;
         mConnection.endBatchEdit();
-        mKeyboardSwitcher.updateShiftState();
+        // Space state must be updated before calling updateShiftState
         mSpaceState = SPACE_STATE_PHANTOM;
+        mKeyboardSwitcher.updateShiftState();
     }
 
     private CharSequence specificTldProcessingOnTextInput(final CharSequence text) {
@@ -2019,8 +2008,8 @@
         mConnection.endBatchEdit();
         // Don't allow cancellation of manual pick
         mLastComposedWord.deactivate();
+        // Space state must be updated before calling updateShiftState
         mSpaceState = SPACE_STATE_PHANTOM;
-        // TODO: is this necessary?
         mKeyboardSwitcher.updateShiftState();
 
         // We should show the "Touch again to save" hint if the user pressed the first entry
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index e843848..a5b4c68 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -44,7 +44,12 @@
             String before, String after, int position, SuggestedWords suggestedWords) {
     }
 
-    public static void logOnAutoCorrection(String before, String after, int separatorCode) {
+    public static void logOnAutoCorrectionForTyping(
+            String before, String after, int separatorCode) {
+    }
+
+    public static void logOnAutoCorrectionForGeometric(String before, String after,
+            int separatorCode, int[] xCoordinates, int[] yCoordinates, int[] relativeTimes) {
     }
 
     public static void logOnAutoCorrectionCancelled() {
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index b85f9dc..cbc6a93 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -190,7 +190,23 @@
         }
     }
 
-    public int getCursorCapsMode(final int inputType, final Locale locale) {
+    /**
+     * Gets the caps modes we should be in after this specific string.
+     *
+     * This returns a bit set of TextUtils#CAP_MODE_*, masked by the inputType argument.
+     * This method also supports faking an additional space after the string passed in argument,
+     * to support cases where a space will be added automatically, like in phantom space
+     * state for example.
+     * Note that for English, we are using American typography rules (which are not specific to
+     * American English, it's just the most common set of rules for English).
+     *
+     * @param inputType a mask of the caps modes to test for.
+     * @param locale what language should be considered.
+     * @param hasSpaceBefore if we should consider there should be a space after the string.
+     * @return the caps modes that should be on as a set of bits
+     */
+    public int getCursorCapsMode(final int inputType, final Locale locale,
+            final boolean hasSpaceBefore) {
         mIC = mParent.getCurrentInputConnection();
         if (null == mIC) return Constants.TextUtils.CAP_MODE_OFF;
         if (!TextUtils.isEmpty(mComposingText)) return Constants.TextUtils.CAP_MODE_OFF;
@@ -205,7 +221,8 @@
         }
         // This never calls InputConnection#getCapsMode - in fact, it's a static method that
         // never blocks or initiates IPC.
-        return StringUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType, locale);
+        return StringUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType, locale,
+                hasSpaceBefore);
     }
 
     public CharSequence getTextBeforeCursor(final int i, final int j) {
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 6251c9a..180f6c5 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -44,7 +44,7 @@
 
 public class Settings extends InputMethodSettingsFragment
         implements SharedPreferences.OnSharedPreferenceChangeListener {
-    public static final boolean ENABLE_EXPERIMENTAL_SETTINGS = false;
+    public static final boolean ENABLE_INTERNAL_SETTINGS = ProductionFlag.IS_INTERNAL;
 
     // In the same order as xml/prefs.xml
     public static final String PREF_GENERAL_SETTINGS = "general_settings";
@@ -220,7 +220,7 @@
 
         final boolean showUsabilityStudyModeOption =
                 res.getBoolean(R.bool.config_enable_usability_study_mode_option)
-                        || ProductionFlag.IS_EXPERIMENTAL || ENABLE_EXPERIMENTAL_SETTINGS;
+                        || ProductionFlag.IS_EXPERIMENTAL || ENABLE_INTERNAL_SETTINGS;
         final Preference usabilityStudyPref = findPreference(PREF_USABILITY_STUDY_MODE);
         if (!showUsabilityStudyModeOption) {
             if (usabilityStudyPref != null) {
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 6dc1ea8..7b65b73 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -197,13 +197,15 @@
      * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
      * {@link TextUtils#CAP_MODE_SENTENCES}.
      * @param locale The locale to consider for capitalization rules
+     * @param hasSpaceBefore Whether we should consider there is a space inserted at the end of cs
      *
      * @return Returns the actual capitalization modes that can be in effect
      * at the current position, which is any combination of
      * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
      * {@link TextUtils#CAP_MODE_SENTENCES}.
      */
-    public static int getCapsMode(final CharSequence cs, final int reqModes, final Locale locale) {
+    public static int getCapsMode(final CharSequence cs, final int reqModes, final Locale locale,
+            final boolean hasSpaceBefore) {
         // Quick description of what we want to do:
         // CAP_MODE_CHARACTERS is always on.
         // CAP_MODE_WORDS is on if there is some whitespace before the cursor.
@@ -230,11 +232,15 @@
         // single quote since they aren't start punctuation in the unicode sense, but should still
         // be skipped for English. TODO: does this depend on the language?
         int i;
-        for (i = cs.length(); i > 0; i--) {
-            final char c = cs.charAt(i - 1);
-            if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE
-                    && Character.getType(c) != Character.START_PUNCTUATION) {
-                break;
+        if (hasSpaceBefore) {
+            i = cs.length() + 1;
+        } else {
+            for (i = cs.length(); i > 0; i--) {
+                final char c = cs.charAt(i - 1);
+                if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE
+                        && Character.getType(c) != Character.START_PUNCTUATION) {
+                    break;
+                }
             }
         }
 
@@ -247,6 +253,7 @@
         // if the first char that's not a space or tab is a start of line (as in, either \n or
         // start of text).
         int j = i;
+        if (hasSpaceBefore) --j;
         while (j > 0 && Character.isWhitespace(cs.charAt(j - 1))) {
             j--;
         }
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 1c98b92..63b6428 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -419,7 +419,7 @@
             // smileys and other multi-character keys.
             final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
                     : separatorString.codePointAt(0);
-            LatinImeLogger.logOnAutoCorrection(typedWord, correctedWord, codePoint);
+            LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint);
         }
 
         public static void onAutoCorrectionCancellation() {
diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
index de20576..52c066a 100644
--- a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
+++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
@@ -22,4 +22,5 @@
     }
 
     public static final boolean IS_EXPERIMENTAL = false;
+    public static final boolean IS_INTERNAL = false;
 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 6f50869..72d1229 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -370,6 +370,9 @@
             g.mCachedSize = groupSize;
             size += groupSize;
         }
+        if (options.mHasLinkedListNode) {
+            size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+        }
         node.mCachedSize = size;
     }
 
@@ -521,6 +524,9 @@
             group.mCachedSize = groupSize;
             size += groupSize;
         }
+        if (formatOptions.mHasLinkedListNode) {
+            size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+        }
         if (node.mCachedSize != size) {
             node.mCachedSize = size;
             changed = true;
@@ -532,9 +538,11 @@
      * Computes the byte size of a list of nodes and updates each node cached position.
      *
      * @param flatNodes the array of nodes.
+     * @param formatOptions file format options.
      * @return the byte size of the entire stack.
      */
-    private static int stackNodes(final ArrayList<Node> flatNodes) {
+    private static int stackNodes(final ArrayList<Node> flatNodes,
+            final FormatOptions formatOptions) {
         int nodeOffset = 0;
         for (Node n : flatNodes) {
             n.mCachedAddress = nodeOffset;
@@ -544,7 +552,9 @@
                 g.mCachedAddress = groupCountSize + nodeOffset + groupOffset;
                 groupOffset += g.mCachedSize;
             }
-            if (groupOffset + groupCountSize != n.mCachedSize) {
+            final int nodeSize = groupCountSize + groupOffset
+                    + (formatOptions.mHasLinkedListNode ? FormatSpec.FORWARD_LINK_ADDRESS_SIZE : 0);
+            if (nodeSize != n.mCachedSize) {
                 throw new RuntimeException("Bug : Stored and computed node size differ");
             }
             nodeOffset += n.mCachedSize;
@@ -571,7 +581,7 @@
             final ArrayList<Node> flatNodes, final FormatOptions formatOptions) {
         // First get the worst sizes and offsets
         for (Node n : flatNodes) setNodeMaximumSize(n, formatOptions);
-        final int offset = stackNodes(flatNodes);
+        final int offset = stackNodes(flatNodes, formatOptions);
 
         MakedictLog.i("Compressing the array addresses. Original size : " + offset);
         MakedictLog.i("(Recursively seen size : " + offset + ")");
@@ -587,7 +597,7 @@
                 if (oldNodeSize < newNodeSize) throw new RuntimeException("Increased size ?!");
                 changesDone |= changed;
             }
-            stackNodes(flatNodes);
+            stackNodes(flatNodes, formatOptions);
             ++passes;
             if (passes > MAX_PASSES) throw new RuntimeException("Too many passes - probably a bug");
         } while (changesDone);
@@ -776,7 +786,8 @@
         return (options.mFrenchLigatureProcessing ? FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG : 0)
                 + (options.mGermanUmlautProcessing ? FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG : 0)
                 + (hasBigrams ? FormatSpec.CONTAINS_BIGRAMS_FLAG : 0)
-                + (formatOptions.mHasParentAddress ? FormatSpec.HAS_PARENT_ADDRESS : 0);
+                + (formatOptions.mHasParentAddress ? FormatSpec.HAS_PARENT_ADDRESS : 0)
+                + (formatOptions.mHasLinkedListNode ? FormatSpec.HAS_LINKEDLIST_NODE : 0);
     }
 
     /**
@@ -910,6 +921,11 @@
             }
 
         }
+        if (formatOptions.mHasLinkedListNode) {
+            buffer[index] = buffer[index + 1] = buffer[index + 2]
+                    = FormatSpec.NO_FORWARD_LINK_ADDRESS;
+            index += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+        }
         if (index != node.mCachedAddress + node.mCachedSize) throw new RuntimeException(
                 "Not the same size : written "
                 + (index - node.mCachedAddress) + " bytes out of a node that should have "
@@ -1525,7 +1541,8 @@
                         0 != (optionsFlags & FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG),
                         0 != (optionsFlags & FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG)),
                 new FormatOptions(version,
-                        0 != (optionsFlags & FormatSpec.HAS_PARENT_ADDRESS)));
+                        0 != (optionsFlags & FormatSpec.HAS_PARENT_ADDRESS),
+                        0 != (optionsFlags & FormatSpec.HAS_LINKEDLIST_NODE)));
         return header;
     }
 
@@ -1543,11 +1560,6 @@
             options.put(key, value);
         }
     }
-    // TODO: remove this method.
-    public static void populateOptions(final ByteBuffer buffer, final int headerSize,
-            final HashMap<String, String> options) {
-        populateOptions(new ByteBufferWrapper(buffer), headerSize, options);
-    }
 
     /**
      * Reads a buffer and returns the memory representation of the dictionary.
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index 1707ccc..f8f13b1 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -41,6 +41,12 @@
      * u |
      * ps
      *
+     * f |
+     * o | IF HAS_LINKEDLIST_NODE (defined in the file header)
+     * r |     forward link address, 3byte
+     * w | the address must be positive.
+     * a |
+     * rdlinkaddress
      */
 
     /* Node(CharGroup) layout is as follows:
@@ -140,18 +146,23 @@
     static final int NOT_A_VERSION_NUMBER = -1;
     static final int FIRST_VERSION_WITH_HEADER_SIZE = 2;
     static final int FIRST_VERSION_WITH_PARENT_ADDRESS = 3;
+    static final int FIRST_VERSION_WITH_LINKEDLIST_NODE = 3;
 
     // These options need to be the same numeric values as the one in the native reading code.
     static final int GERMAN_UMLAUT_PROCESSING_FLAG = 0x1;
+    // TODO: Make the native reading code read this variable.
     static final int HAS_PARENT_ADDRESS = 0x2;
     static final int FRENCH_LIGATURE_PROCESSING_FLAG = 0x4;
     static final int CONTAINS_BIGRAMS_FLAG = 0x8;
+    // TODO: Make the native reading code read this variable.
+    static final int HAS_LINKEDLIST_NODE = 0x10;
 
     // TODO: Make this value adaptative to content data, store it in the header, and
     // use it in the reading code.
     static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
 
     static final int PARENT_ADDRESS_SIZE = 3;
+    static final int FORWARD_LINK_ADDRESS_SIZE = 3;
 
     static final int MASK_GROUP_ADDRESS_TYPE = 0xC0;
     static final int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00;
@@ -187,6 +198,7 @@
 
     static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE;
     static final int NO_PARENT_ADDRESS = 0;
+    static final int NO_FORWARD_LINK_ADDRESS = 0;
     static final int INVALID_CHARACTER = -1;
 
     static final int MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT = 0x7F; // 127
@@ -201,16 +213,30 @@
     public static class FormatOptions {
         public final int mVersion;
         public final boolean mHasParentAddress;
+        public final boolean mHasLinkedListNode;
         public FormatOptions(final int version) {
             this(version, false);
         }
         public FormatOptions(final int version, final boolean hasParentAddress) {
+            this(version, hasParentAddress, false);
+        }
+        public FormatOptions(final int version, final boolean hasParentAddress,
+                final boolean hasLinkedListNode) {
             mVersion = version;
-            if (version < FormatSpec.FIRST_VERSION_WITH_PARENT_ADDRESS && hasParentAddress) {
+            if (version < FIRST_VERSION_WITH_PARENT_ADDRESS && hasParentAddress) {
                 throw new RuntimeException("Parent addresses are only supported with versions "
-                        + FormatSpec.FIRST_VERSION_WITH_PARENT_ADDRESS + " and ulterior.");
+                        + FIRST_VERSION_WITH_PARENT_ADDRESS + " and ulterior.");
             }
             mHasParentAddress = hasParentAddress;
+
+            if (version < FIRST_VERSION_WITH_LINKEDLIST_NODE && hasLinkedListNode) {
+                throw new RuntimeException("Linked list nodes are only supported with versions "
+                        + FIRST_VERSION_WITH_LINKEDLIST_NODE + " and ulterior.");
+            }
+            if (!hasParentAddress && hasLinkedListNode) {
+                throw new RuntimeException("Linked list nodes need parent addresses.");
+            }
+            mHasLinkedListNode = hasLinkedListNode;
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 6775de8..98cf308 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -556,6 +556,7 @@
         final StringBuilder checker = DBG ? new StringBuilder() : null;
 
         CharGroup currentGroup;
+        final int codePointCountInS = s.codePointCount(0, s.length());
         do {
             int indexOfGroup = findIndexOfChar(node, s.codePointAt(index));
             if (CHARACTER_NOT_FOUND == indexOfGroup) return null;
@@ -570,12 +571,12 @@
             index = newIndex;
 
             if (DBG) checker.append(new String(currentGroup.mChars, 0, currentGroup.mChars.length));
-            if (index < s.length()) {
+            if (index < codePointCountInS) {
                 node = currentGroup.mChildren;
             }
-        } while (null != node && index < s.length());
+        } while (null != node && index < codePointCountInS);
 
-        if (index < s.length()) return null;
+        if (index < codePointCountInS) return null;
         if (!currentGroup.isTerminal()) return null;
         if (DBG && !s.equals(checker.toString())) return null;
         return currentGroup;
diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
index 00cca9d..be3494d 100644
--- a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
@@ -93,22 +93,24 @@
     }
 
     private void onePathForCaps(final CharSequence cs, final int expectedResult, final int mask,
-            final Locale l) {
+            final Locale l, final boolean hasSpaceBefore) {
         int oneTimeResult = expectedResult & mask;
-        assertEquals("After >" + cs + "<", oneTimeResult, StringUtils.getCapsMode(cs, mask, l));
+        assertEquals("After >" + cs + "<", oneTimeResult,
+                StringUtils.getCapsMode(cs, mask, l, hasSpaceBefore));
     }
 
-    private void allPathsForCaps(final CharSequence cs, final int expectedResult, final Locale l) {
+    private void allPathsForCaps(final CharSequence cs, final int expectedResult, final Locale l,
+            final boolean hasSpaceBefore) {
         final int c = TextUtils.CAP_MODE_CHARACTERS;
         final int w = TextUtils.CAP_MODE_WORDS;
         final int s = TextUtils.CAP_MODE_SENTENCES;
-        onePathForCaps(cs, expectedResult, c | w | s, l);
-        onePathForCaps(cs, expectedResult, w | s, l);
-        onePathForCaps(cs, expectedResult, c | s, l);
-        onePathForCaps(cs, expectedResult, c | w, l);
-        onePathForCaps(cs, expectedResult, c, l);
-        onePathForCaps(cs, expectedResult, w, l);
-        onePathForCaps(cs, expectedResult, s, l);
+        onePathForCaps(cs, expectedResult, c | w | s, l, hasSpaceBefore);
+        onePathForCaps(cs, expectedResult, w | s, l, hasSpaceBefore);
+        onePathForCaps(cs, expectedResult, c | s, l, hasSpaceBefore);
+        onePathForCaps(cs, expectedResult, c | w, l, hasSpaceBefore);
+        onePathForCaps(cs, expectedResult, c, l, hasSpaceBefore);
+        onePathForCaps(cs, expectedResult, w, l, hasSpaceBefore);
+        onePathForCaps(cs, expectedResult, s, l, hasSpaceBefore);
     }
 
     public void testGetCapsMode() {
@@ -116,26 +118,31 @@
         final int w = TextUtils.CAP_MODE_WORDS;
         final int s = TextUtils.CAP_MODE_SENTENCES;
         Locale l = Locale.ENGLISH;
-        allPathsForCaps("", c | w | s, l);
-        allPathsForCaps("Word", c, l);
-        allPathsForCaps("Word.", c, l);
-        allPathsForCaps("Word ", c | w, l);
-        allPathsForCaps("Word. ", c | w | s, l);
-        allPathsForCaps("Word..", c, l);
-        allPathsForCaps("Word.. ", c | w | s, l);
-        allPathsForCaps("Word... ", c | w | s, l);
-        allPathsForCaps("Word ... ", c | w | s, l);
-        allPathsForCaps("Word . ", c | w, l);
-        allPathsForCaps("In the U.S ", c | w, l);
-        allPathsForCaps("In the U.S. ", c | w, l);
-        allPathsForCaps("Some stuff (e.g. ", c | w, l);
-        allPathsForCaps("In the U.S.. ", c | w | s, l);
-        allPathsForCaps("\"Word.\" ", c | w | s, l);
-        allPathsForCaps("\"Word\". ", c | w | s, l);
-        allPathsForCaps("\"Word\" ", c | w, l);
+        allPathsForCaps("", c | w | s, l, false);
+        allPathsForCaps("Word", c, l, false);
+        allPathsForCaps("Word.", c, l, false);
+        allPathsForCaps("Word ", c | w, l, false);
+        allPathsForCaps("Word. ", c | w | s, l, false);
+        allPathsForCaps("Word..", c, l, false);
+        allPathsForCaps("Word.. ", c | w | s, l, false);
+        allPathsForCaps("Word... ", c | w | s, l, false);
+        allPathsForCaps("Word ... ", c | w | s, l, false);
+        allPathsForCaps("Word . ", c | w, l, false);
+        allPathsForCaps("In the U.S ", c | w, l, false);
+        allPathsForCaps("In the U.S. ", c | w, l, false);
+        allPathsForCaps("Some stuff (e.g. ", c | w, l, false);
+        allPathsForCaps("In the U.S.. ", c | w | s, l, false);
+        allPathsForCaps("\"Word.\" ", c | w | s, l, false);
+        allPathsForCaps("\"Word\". ", c | w | s, l, false);
+        allPathsForCaps("\"Word\" ", c | w, l, false);
+
+        // Test for phantom space
+        allPathsForCaps("Word", c | w, l, true);
+        allPathsForCaps("Word.", c | w | s, l, true);
+
         l = Locale.FRENCH;
-        allPathsForCaps("\"Word.\" ", c | w, l);
-        allPathsForCaps("\"Word\". ", c | w | s, l);
-        allPathsForCaps("\"Word\" ", c | w, l);
+        allPathsForCaps("\"Word.\" ", c | w, l, false);
+        allPathsForCaps("\"Word\". ", c | w | s, l, false);
+        allPathsForCaps("\"Word\" ", c | w, l, false);
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
index 328784b..a87146e 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
@@ -65,9 +65,12 @@
 
     private static final FormatSpec.FormatOptions VERSION2 = new FormatSpec.FormatOptions(2);
     private static final FormatSpec.FormatOptions VERSION3_WITHOUT_PARENTADDRESS =
-            new FormatSpec.FormatOptions(3, false);
+            new FormatSpec.FormatOptions(3, false /* hasParentAddress */);
     private static final FormatSpec.FormatOptions VERSION3_WITH_PARENTADDRESS =
-            new FormatSpec.FormatOptions(3, true);
+            new FormatSpec.FormatOptions(3, true /* hasParentAddress */);
+    private static final FormatSpec.FormatOptions VERSION3_WITH_LINKEDLIST_NODE =
+            new FormatSpec.FormatOptions(3, true /* hasParentAddress */,
+                    true /* hasLinkedListNode */);
 
     private static final String[] CHARACTERS = {
         "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
@@ -236,7 +239,8 @@
         String result = " : buffer type = "
                 + ((bufferType == USE_BYTE_BUFFER) ? "byte buffer" : "byte array");
         result += " : version = " + formatOptions.mVersion;
-        return result + ", hasParentAddress = " + formatOptions.mHasParentAddress;
+        return result + ", hasParentAddress = " + formatOptions.mHasParentAddress
+                + ", hasLinkedListNode = " + formatOptions.mHasLinkedListNode;
     }
 
     // Tests for readDictionaryBinary and writeDictionaryBinary
@@ -305,6 +309,7 @@
         runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION2);
         runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_PARENTADDRESS);
         runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITH_PARENTADDRESS);
+        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITH_LINKEDLIST_NODE);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -317,6 +322,7 @@
         runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION2);
         runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_PARENTADDRESS);
         runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITH_PARENTADDRESS);
+        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITH_LINKEDLIST_NODE);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -450,6 +456,7 @@
         runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION2);
         runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_PARENTADDRESS);
         runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITH_PARENTADDRESS);
+        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITH_LINKEDLIST_NODE);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -462,6 +469,7 @@
         runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION2);
         runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_PARENTADDRESS);
         runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITH_PARENTADDRESS);
+        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITH_LINKEDLIST_NODE);
 
         for (final String result : results) {
             Log.d(TAG, result);