Merge remote-tracking branch 'goog/jb-mr1-dev' into mergescriptpackage
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index 0fb8877..c7d9936 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -100,7 +100,7 @@
     <fraction name="center_suggestion_percentile">36%</fraction>
 
     <!-- Gesture preview trail parameters -->
-    <dimen name="gesture_preview_trail_start_width">18.0dp</dimen>
+    <dimen name="gesture_preview_trail_start_width">12.6dp</dimen>
     <dimen name="gesture_preview_trail_end_width">2.5dp</dimen>
     <!-- Gesture floating preview text parameters -->
     <dimen name="gesture_floating_preview_text_size">24dp</dimen>
diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml
index 1d7e57b..370959c 100644
--- a/java/res/values/keypress-vibration-durations.xml
+++ b/java/res/values/keypress-vibration-durations.xml
@@ -22,7 +22,7 @@
         <!-- Build.HARDWARE,duration_in_milliseconds -->
         <item>herring,5</item>
         <item>tuna,5</item>
-        <item>mako,20</item>
+        <item>mako,5</item>
         <item>manta,16</item>
     </string-array>
 </resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index bd60844..392a070 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -61,8 +61,10 @@
     <string name="include_other_imes_in_language_switch_list">Switch to other input methods</string>
     <!-- Option summary for including other IMEs in the language switch list [CHAR LIMIT=65] -->
     <string name="include_other_imes_in_language_switch_list_summary">Language switch key covers other input methods too</string>
-    <!-- Option to suppress language switch key [CHAR LIMIT=30] -->
-    <string name="suppress_language_switch_key">Suppress language switch key</string>
+    <!-- Option to show language switch key [CHAR LIMIT=30] -->
+    <string name="show_language_switch_key">Language switch key</string>
+    <!-- Option summary for showing language switch key [CHAR LIMIT=65] -->
+    <string name="show_language_switch_key_summary">Show when multiple input languages are enabled</string>
 
     <!-- Option for the dismiss delay of the key popup [CHAR LIMIT=25] -->
     <string name="key_preview_popup_dismiss_delay">Key popup dismiss delay</string>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index ef6be3e..3e83fc0 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -108,10 +108,11 @@
                 android:persistent="true"
                 android:defaultValue="true" />
             <CheckBoxPreference
-                android:key="pref_suppress_language_switch_key"
-                android:title="@string/suppress_language_switch_key"
+                android:key="pref_show_language_switch_key"
+                android:title="@string/show_language_switch_key"
+                android:summary="@string/show_language_switch_key_summary"
                 android:persistent="true"
-                android:defaultValue="false" />
+                android:defaultValue="true" />
             <CheckBoxPreference
                 android:key="pref_include_other_imes_in_language_switch_list"
                 android:title="@string/include_other_imes_in_language_switch_list"
@@ -138,11 +139,11 @@
                 android:persistent="true"
                 android:defaultValue="true" />
             <CheckBoxPreference
-                android:key="pref_gesture_floating_preview_text"
+                android:key="pref_show_gesture_floating_preview_text"
                 android:title="@string/gesture_floating_preview_text"
                 android:summary="@string/gesture_floating_preview_text_summary"
                 android:persistent="true"
-                android:defaultValue="true" />
+                android:defaultValue="false" />
         </PreferenceScreen>
     </PreferenceCategory>
 </PreferenceScreen>
diff --git a/java/res/xml/rowkeys_hindi1.xml b/java/res/xml/rowkeys_hindi1.xml
index 656ba01..1120804 100644
--- a/java/res/xml/rowkeys_hindi1.xml
+++ b/java/res/xml/rowkeys_hindi1.xml
@@ -29,50 +29,61 @@
                  U+0912/U+0902: "ऒं" DEVANAGARI LETTER SHORT O//DEVANAGARI SIGN ANUSVARA -->
             <Key
                 latin:keyLabel="&#x0914;"
-                latin:moreKeys="&#x0912;&#x0902;" />
+                latin:moreKeys="&#x0912;&#x0902;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0910: "ऐ" DEVANAGARI LETTER AI
                  U+0910/U+0902: "ऐं" DEVANAGARI LETTER AI/DEVANAGARI SIGN ANUSVARA -->
             <Key
                 latin:keyLabel="&#x0910;"
-                latin:moreKeys="&#x0910;&#x0902;" />
+                latin:moreKeys="&#x0910;&#x0902;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0906: "आ" DEVANAGARI LETTER AA
                  U+0906/U+0902: "आं" DEVANAGARI LETTER AA/DEVANAGARI SIGN ANUSVARA
                  U+0906/U+0901: "आँ" DEVANAGARI LETTER AA/DEVANAGARI SIGN CANDRABINDU -->
             <Key
                 latin:keyLabel="&#x0906;"
-                latin:moreKeys="&#x0906;&#x0902;,&#x0906;&#x0901;" />
+                latin:moreKeys="&#x0906;&#x0902;,&#x0906;&#x0901;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0908: "ई" DEVANAGARI LETTER II
                  U+0908/U+0902: "ईं" DEVANAGARI LETTER II/DEVANAGARI SIGN ANUSVARA -->
             <Key
                 latin:keyLabel="&#x0908;"
-                latin:moreKeys="&#x0908;&#x0902;" />
+                latin:moreKeys="&#x0908;&#x0902;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+090A: "ऊ" DEVANAGARI LETTER UU
                  U+090A/U+0902: "ऊं" DEVANAGARI LETTER UU/DEVANAGARI SIGN ANUSVARA
                  U+090A/U+0901: "ऊँ" DEVANAGARI LETTER UU/DEVANAGARI SIGN CANDRABINDU -->
             <Key
                 latin:keyLabel="&#x090A;"
-                latin:moreKeys="&#x090A;&#x0902;,&#x090A;&#x0901;" />
+                latin:moreKeys="&#x090A;&#x0902;,&#x090A;&#x0901;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+092D: "भ" DEVANAGARI LETTER BHA -->
             <Key
-                latin:keyLabel="&#x092D;" />
+                latin:keyLabel="&#x092D;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0903: "ः" DEVANAGARI SIGN VISARGA -->
             <Key
-                latin:keyLabel="&#x0903;" />
+                latin:keyLabel="&#x0903;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0918: "घ" DEVANAGARI LETTER GHA -->
             <Key
-                latin:keyLabel="&#x0918;" />
+                latin:keyLabel="&#x0918;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0927: "ध" DEVANAGARI LETTER DHA
                  U+0915/U+094D/U+0937: "क्ष" DEVANAGARI LETTER KA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER SSA
                  U+0936/U+094D/U+0930: "श्र" DEVANAGARI LETTER SHA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA -->
             <Key
                 latin:keyLabel="&#x0927;"
-                latin:moreKeys="&#x0915;&#x094D;&#x0937;,&#x0936;&#x094D;&#x0930;" />
+                latin:moreKeys="&#x0915;&#x094D;&#x0937;,&#x0936;&#x094D;&#x0930;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+091D: "झ" DEVANAGARI LETTER JHA -->
             <Key
-                latin:keyLabel="&#x091D;" />
+                latin:keyLabel="&#x091D;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0922: "ढ" DEVANAGARI LETTER DDHA -->
             <Key
-                latin:keyLabel="&#x0922;" />
+                latin:keyLabel="&#x0922;"
+                latin:keyLabelFlags="fontNormal" />
         </case>
         <default>
             <!-- U+094C: "ौ" DEVANAGARI VOWEL SIGN AU
@@ -82,7 +93,8 @@
                 latin:keyLabel="&#x094C;"
                 latin:moreKeys="&#x094C;&#x0902;,%"
                 latin:keyHintLabel="1"
-                latin:additionalMoreKeys="&#x0967;,1" />
+                latin:additionalMoreKeys="&#x0967;,1"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0948: "ै" DEVANAGARI VOWEL SIGN AI
                  U+0948/U+0902: "ैं" DEVANAGARI VOWEL SIGN AI/DEVANAGARI SIGN ANUSVARA
                  U+0968: "२" DEVANAGARI DIGIT TWO -->
@@ -90,7 +102,8 @@
                 latin:keyLabel="&#x0948;"
                 latin:moreKeys="&#x0948;&#x0902;,%"
                 latin:keyHintLabel="2"
-                latin:additionalMoreKeys="&#x0968;,2" />
+                latin:additionalMoreKeys="&#x0968;,2"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+093E: "ा" DEVANAGARI VOWEL SIGN AA
                  U+093E/U+0902: "ां" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN ANUSVARA
                  U+093E/U+0901: "ाँ" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN CANDRABINDU
@@ -99,7 +112,8 @@
                 latin:keyLabel="&#x093E;"
                 latin:moreKeys="&#x093E;&#x0902;,&#x093E;&#x0901;,%"
                 latin:keyHintLabel="3"
-                latin:additionalMoreKeys="&#x0969;,3" />
+                latin:additionalMoreKeys="&#x0969;,3"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0940: "ी" DEVANAGARI VOWEL SIGN II
                  U+0940/U+0902: "ीं" DEVANAGARI VOWEL SIGN II/DEVANAGARI SIGN ANUSVARA
                  U+096A: "४" DEVANAGARI DIGIT FOUR -->
@@ -107,7 +121,8 @@
                 latin:keyLabel="&#x0940;"
                 latin:moreKeys="&#x0940;&#x0902;,%"
                 latin:keyHintLabel="4"
-                latin:additionalMoreKeys="&#x096A;,4" />
+                latin:additionalMoreKeys="&#x096A;,4"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0942: "ू" DEVANAGARI VOWEL SIGN UU
                  U+0942/U+0902: "ूं" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN ANUSVARA
                  U+0942/U+0901: "ूँ" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN CANDRABINDU
@@ -116,20 +131,23 @@
                 latin:keyLabel="&#x0942;"
                 latin:moreKeys="&#x0942;&#x0902;,&#x0942;&#x0901;,%"
                 latin:keyHintLabel="5"
-                latin:additionalMoreKeys="&#x096B;,5" />
+                latin:additionalMoreKeys="&#x096B;,5"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+092C: "ब" DEVANAGARI LETTER BA
                  U+092C/U+0952: "ब॒" DEVANAGARI LETTER BA/DEVANAGARI STRESS SIGN ANUDATTA -->
             <Key
                 latin:keyLabel="&#x092C;"
                 latin:moreKeys="&#x092C;&#x0952;,%"
                 latin:keyHintLabel="6"
-                latin:additionalMoreKeys="&#x096C;,6" />
+                latin:additionalMoreKeys="&#x096C;,6"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0939: "ह" DEVANAGARI LETTER HA
                  U+096D: "७" DEVANAGARI DIGIT SEVEN -->
             <Key
                 latin:keyLabel="&#x0939;"
                 latin:keyHintLabel="7"
-                latin:additionalMoreKeys="&#x096D;,7" />
+                latin:additionalMoreKeys="&#x096D;,7"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0917: "ग" DEVANAGARI LETTER GA
                  U+091C/U+094D/U+091E: "ज्ञ" DEVANAGARI LETTER JA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER NYA
                  U+0917/U+093C: "ग़" DEVANAGARI LETTER GA/DEVANAGARI SIGN NUKTA
@@ -139,13 +157,15 @@
                 latin:keyLabel="&#x0917;"
                 latin:moreKeys="&#x091C;&#x094D;&#x091E;,&#x0917;&#x093C;,&#x0917;&#x0952;,%"
                 latin:keyHintLabel="8"
-                latin:additionalMoreKeys="&#x096E;,8" />
+                latin:additionalMoreKeys="&#x096E;,8"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0926: "द" DEVANAGARI LETTER DA
                  U+096F: "९" DEVANAGARI DIGIT NINE -->
             <Key
                 latin:keyLabel="&#x0926;"
                 latin:keyHintLabel="9"
-                latin:additionalMoreKeys="9" />
+                latin:additionalMoreKeys="9"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+091C: "ज" DEVANAGARI LETTER JA
                  U+091C/U+0952: "ज॒" DEVANAGARI LETTER JA/DEVANAGARI STRESS SIGN ANUDATTA
                  U+091C/U+094D/U+091E: "ज्ञ" DEVANAGARI LETTER JA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER NYA
@@ -155,13 +175,15 @@
                 latin:keyLabel="&#x091C;"
                 latin:moreKeys="&#x091C;&#x0952;,&#x091C;&#x094D;&#x091E;,&#x091C;&#x093C;,%"
                 latin:keyHintLabel="0"
-                latin:additionalMoreKeys="&#x0966;,0" />
+                latin:additionalMoreKeys="&#x0966;,0"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0921: "ड" DEVANAGARI LETTER DDA
                  U+0921/U+0952: "ड॒" DEVANAGARI LETTER DDA/DEVANAGARI STRESS SIGN ANUDATTA
                  U+0921/U+093C: "ड़" DEVANAGARI LETTER DDA/DEVANAGARI SIGN NUKTA -->
             <Key
                 latin:keyLabel="&#x0921;"
-                latin:moreKeys="&#x0921;&#x0952;,&#x0921;&#x093C;" />
+                latin:moreKeys="&#x0921;&#x0952;,&#x0921;&#x093C;"
+                latin:keyLabelFlags="fontNormal" />
          </default>
     </switch>
 </merge>
diff --git a/java/res/xml/rowkeys_hindi2.xml b/java/res/xml/rowkeys_hindi2.xml
index 05e3db2..e7c67db 100644
--- a/java/res/xml/rowkeys_hindi2.xml
+++ b/java/res/xml/rowkeys_hindi2.xml
@@ -31,7 +31,8 @@
                  U+0912: "ऒ" DEVANAGARI LETTER SHORT O -->
             <Key
                 latin:keyLabel="&#x0913;"
-                latin:moreKeys="&#x0913;&#x0902;,&#x0911;,&#x0912;" />
+                latin:moreKeys="&#x0913;&#x0902;,&#x0911;,&#x0912;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+090F: "ए" DEVANAGARI LETTER E
                  U+090F/U+0902: "एं" DEVANAGARI LETTER E/DEVANAGARI SIGN ANUSVARA
                  U+090F/U+0901: "एँ" DEVANAGARI LETTER E/DEVANAGARI SIGN CANDRABINDU
@@ -39,50 +40,60 @@
                  U+090E: "ऎ" DEVANAGARI LETTER SHORT E -->
             <Key
                 latin:keyLabel="&#x090F;"
-                latin:moreKeys="&#x090F;&#x0902;,&#x090F;&#x0901;,&#x090D;,&#x090E;" />
+                latin:moreKeys="&#x090F;&#x0902;,&#x090F;&#x0901;,&#x090D;,&#x090E;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0905: "अ" DEVANAGARI LETTER A
                  U+0905/U+0902: "अं" DEVANAGARI LETTER A/DEVANAGARI SIGN ANUSVARA
                  U+0905/U+0901: "अँ" DEVANAGARI LETTER A/DEVANAGARI SIGN CANDRABINDU -->
             <Key
                 latin:keyLabel="&#x0905;"
-                latin:moreKeys="&#x0905;&#x0902;,&#x0905;&#x0901;" />
+                latin:moreKeys="&#x0905;&#x0902;,&#x0905;&#x0901;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0907: "इ" DEVANAGARI LETTER I
                  U+0907/U+0902: "इं" DEVANAGARI LETTER I/DEVANAGARI SIGN ANUSVARA
                  U+0907/U+0901: "इं" DEVANAGARI LETTER I/DEVANAGARI SIGN CANDRABINDU -->
             <Key
                 latin:keyLabel="&#x0907;"
-                latin:moreKeys="&#x0907;&#x0902;,&#x0907;&#x0901;" />
+                latin:moreKeys="&#x0907;&#x0902;,&#x0907;&#x0901;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0909: "उ" DEVANAGARI LETTER U
                  U+0909/U+0902: "उं" DEVANAGARI LETTER U/DEVANAGARI SIGN ANUSVARA
                  U+0909/U+0901: "उँ" DEVANAGARI LETTER U/DEVANAGARI SIGN CANDRABINDU -->
             <Key
                 latin:keyLabel="&#x0909;"
-                latin:moreKeys="&#x0909;&#x0902;,&#x0909;&#x0901;" />
+                latin:moreKeys="&#x0909;&#x0902;,&#x0909;&#x0901;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+092B: "फ" DEVANAGARI LETTER PHA
                  U+092B/U+093C: "फ़" DEVANAGARI LETTER PHA/DEVANAGARI SIGN NUKTA -->
             <Key
                 latin:keyLabel="&#x092B;"
-                latin:moreKeys="&#x092B;&#x093C;" />
+                latin:moreKeys="&#x092B;&#x093C;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0931: "ऱ" DEVANAGARI LETTER RRA
                  U+094D/U+0930: "्र" DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA
                  U+0930/U+094D: "र्" DEVANAGARI LETTER RA/DEVANAGARI SIGN VIRAMA -->
             <Key
                 latin:keyLabel="&#x0931;"
-                latin:moreKeys="&#x094D;&#x0930;,&#x0930;&#x094D;" />
+                latin:moreKeys="&#x094D;&#x0930;,&#x0930;&#x094D;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0916: "ख" DEVANAGARI LETTER KHA
                  U+0916/U+093C: "ख़" DEVANAGARI LETTER KHA/DEVANAGARI SIGN NUKTA -->
             <Key
                 latin:keyLabel="&#x0916;"
-                latin:moreKeys="&#x0916;&#x093C;" />
+                latin:moreKeys="&#x0916;&#x093C;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0925: "थ" DEVANAGARI LETTER THA -->
             <Key
-                latin:keyLabel="&#x0925;" />
+                latin:keyLabel="&#x0925;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+091B: "छ" DEVANAGARI LETTER CHA -->
             <Key
-                latin:keyLabel="&#x091B;" />
+                latin:keyLabel="&#x091B;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0920: "ठ" DEVANAGARI LETTER TTHA -->
             <Key
-                latin:keyLabel="&#x0920;" />
+                latin:keyLabel="&#x0920;"
+                latin:keyLabelFlags="fontNormal" />
         </case>
         <default>
             <!-- U+094B: "ो" DEVANAGARI VOWEL SIGN O
@@ -91,52 +102,63 @@
                  U+094A: "ॊ" DEVANAGARI VOWEL SIGN SHORT O -->
             <Key
                 latin:keyLabel="&#x094B;"
-                latin:moreKeys="&#x094B;&#x0902;,&#x0949;,&#x094A;" />
+                latin:moreKeys="&#x094B;&#x0902;,&#x0949;,&#x094A;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0947: "े" DEVANAGARI VOWEL SIGN E
                  U+0947/U+0902: "ें" DEVANAGARI VOWEL SIGN E/DEVANAGARI SIGN ANUSVARA -->
             <Key
                 latin:keyLabel="&#x0947;"
-                latin:moreKeys="&#x0947;&#x0902;" />
+                latin:moreKeys="&#x0947;&#x0902;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+094D: "्" DEVANAGARI SIGN VIRAMA -->
             <Key
-                latin:keyLabel="&#x094D;" />
+                latin:keyLabel="&#x094D;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+093F: "ि" DEVANAGARI VOWEL SIGN I
                  U+093F/U+0902: "िं" DEVANAGARI VOWEL SIGN I/DEVANAGARI SIGN ANUSVARA -->
             <Key
                 latin:keyLabel="&#x093F;"
-                latin:moreKeys="&#x093F;&#x0902;" />
+                latin:moreKeys="&#x093F;&#x0902;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0941: "ु" DEVANAGARI VOWEL SIGN U
                  U+0941/U+0902: "ुं" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN ANUSVARA
                  U+0941/U+0901: "ुँ" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN CANDRABINDU -->
             <Key
                 latin:keyLabel="&#x0941;"
-                latin:moreKeys="&#x0941;&#x0902;,&#x0941;&#x0901;" />
+                latin:moreKeys="&#x0941;&#x0902;,&#x0941;&#x0901;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+092A: "प" DEVANAGARI LETTER PA -->
             <Key
-                latin:keyLabel="&#x092A;" />
+                latin:keyLabel="&#x092A;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0930: "र" DEVANAGARI LETTER RA
                  U+090B: "ऋ" DEVANAGARI LETTER VOCALIC R
                  U+0930/U+093C: "ऱ" DEVANAGARI LETTER RA/DEVANAGARI SIGN NUKTA
                  U+0960: "ॠ" DEVANAGARI LETTER VOCALIC RR -->
             <Key
                 latin:keyLabel="&#x0930;"
-                latin:moreKeys="&#x090B;,&#x0930;&#x093C;,&#x0960;" />
+                latin:moreKeys="&#x090B;,&#x0930;&#x093C;,&#x0960;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0915: "क" DEVANAGARI LETTER KA
                  U+0915/U+093C: "क़" DEVANAGARI LETTER KA/DEVANAGARI SIGN NUKTA -->
             <Key
                 latin:keyLabel="&#x0915;"
-                latin:moreKeys="&#x0915;&#x093C;" />
+                latin:moreKeys="&#x0915;&#x093C;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0924: "त" DEVANAGARI LETTER TA
                  U+0924/U+094D/U+0930: "त्र" DEVANAGARI LETTER TA/DEVANAGARI SIGN VIRAMA/DEVANAGARI LETTER RA -->
             <Key
                 latin:keyLabel="&#x0924;"
-                latin:moreKeys="&#x0924;&#x094D;&#x0930;" />
+                latin:moreKeys="&#x0924;&#x094D;&#x0930;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+091A: "च" DEVANAGARI LETTER CA -->
             <Key
-                latin:keyLabel="&#x091A;" />
+                latin:keyLabel="&#x091A;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+091F: "ट" DEVANAGARI LETTER TTA -->
             <Key
-                latin:keyLabel="&#x091F;" />
+                latin:keyLabel="&#x091F;"
+                latin:keyLabelFlags="fontNormal" />
          </default>
     </switch>
 </merge>
diff --git a/java/res/xml/rowkeys_hindi3.xml b/java/res/xml/rowkeys_hindi3.xml
index 92bcb56..ebbff3e 100644
--- a/java/res/xml/rowkeys_hindi3.xml
+++ b/java/res/xml/rowkeys_hindi3.xml
@@ -27,15 +27,18 @@
         >
             <!-- U+0911: "ऑ" DEVANAGARI LETTER CANDRA O -->
             <Key
-                latin:keyLabel="&#x0911;" />
+                latin:keyLabel="&#x0911;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU
                  U+0945: "ॅ" DEVANAGARI VOWEL SIGN CANDRA E-->
             <Key
                 latin:keyLabel="&#x0901;"
-                latin:moreKeys="&#x0945;" />
+                latin:moreKeys="&#x0945;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0923: "ण" DEVANAGARI LETTER NNA -->
             <Key
-                latin:keyLabel="&#x0923;" />
+                latin:keyLabel="&#x0923;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0929: "ऩ" DEVANAGARI LETTER NNNA -->
             <Key
                 latin:keyLabel="&#x0929;" />
@@ -43,65 +46,79 @@
                  U+0934: "ऴ" DEVANAGARI LETTER LLLA -->
             <Key
                 latin:keyLabel="&#x0933;"
-                latin:moreKeys="&#x0934;" />
+                latin:moreKeys="&#x0934;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0936: "श" DEVANAGARI LETTER SHA -->
             <Key
-                latin:keyLabel="&#x0936;" />
+                latin:keyLabel="&#x0936;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0937: "ष" DEVANAGARI LETTER SSA -->
             <Key
-                latin:keyLabel="&#x0937;" />
+                latin:keyLabel="&#x0937;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R
                  U+0944: "ॄ" DEVANAGARI VOWEL SIGN VOCALIC RR -->
             <Key
                 latin:keyLabel="&#x0943;"
-                latin:moreKeys="&#x0944;" />
+                latin:moreKeys="&#x0944;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+091E: "ञ" DEVANAGARI LETTER NYA -->
             <Key
-                latin:keyLabel="&#x091E;" />
+                latin:keyLabel="&#x091E;"
+                latin:keyLabelFlags="fontNormal" />
         </case>
         <default>
             <!-- U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O -->
             <Key
-                latin:keyLabel="&#x0949;" />
+                latin:keyLabel="&#x0949;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0902: "ं" DEVANAGARI SIGN ANUSVARA -->
             <Key
-                latin:keyLabel="&#x0902;" />
+                latin:keyLabel="&#x0902;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+092E: "म" DEVANAGARI LETTER MA
                  U+0950: "ॐ" DEVANAGARI OM -->
             <Key
                 latin:keyLabel="&#x092E;"
-                latin:moreKeys="&#x0950;" />
+                latin:moreKeys="&#x0950;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0928: "न" DEVANAGARI LETTER NA
                  U+091E: "ञ" DEVANAGARI LETTER NYA
                  U+0919: "ङ" DEVANAGARI LETTER NGA
                  U+0928/U+093C: "ऩ" DEVANAGARI LETTER NA/DEVANAGARI SIGN NUKTA -->
             <Key
                 latin:keyLabel="&#x0928;"
-                latin:moreKeys="&#x091E;,&#x0919;,&#x0928;&#x093C;" />
+                latin:moreKeys="&#x091E;,&#x0919;,&#x0928;&#x093C;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0935: "व" DEVANAGARI LETTER VA -->
             <Key
-                latin:keyLabel="&#x0935;" />
+                latin:keyLabel="&#x0935;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0932: "ल" DEVANAGARI LETTER LA
                  U+090C: "ऌ" DEVANAGARI LETTER VOCALIC L
                  U+0961: "ॡ" DEVANAGARI LETTER VOCALIC LL -->
             <Key
                 latin:keyLabel="&#x0932;"
-                latin:moreKeys="&#x090C;,&#x0961;" />
+                latin:moreKeys="&#x090C;,&#x0961;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+0938: "स" DEVANAGARI LETTER SA -->
             <Key
-                latin:keyLabel="&#x0938;" />
+                latin:keyLabel="&#x0938;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+092F: "य" DEVANAGARI LETTER YA
                  U+095F: "य़" DEVANAGARI LETTER YYA -->
             <Key
                 latin:keyLabel="&#x092F;"
-                latin:moreKeys="&#x095F;" />
+                latin:moreKeys="&#x095F;"
+                latin:keyLabelFlags="fontNormal" />
             <!-- U+093C: "़" DEVANAGARI SIGN NUKTA
                  U+097D: "ॽ" DEVANAGARI LETTER GLOTTAL STOP
                  U+0970: "॰" DEVANAGARI ABBREVIATION SIGN
                  U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA -->
             <Key
                 latin:keyLabel="&#x093C;"
-                latin:moreKeys="&#x097D;,&#x0970;,&#x093D;" />
+                latin:moreKeys="&#x097D;,&#x0970;,&#x093D;"
+                latin:keyLabelFlags="fontNormal" />
          </default>
     </switch>
 </merge>
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index a6439c4..03d610a 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -23,7 +23,7 @@
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.keyboard.internal.GestureStroke;
-import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewTrail;
+import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints;
 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.InputPointers;
@@ -47,8 +47,6 @@
     private static boolean sGestureHandlingEnabledByInputField = false;
     private static boolean sGestureHandlingEnabledByUser = false;
 
-    private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec
-
     public interface KeyEventHandler {
         /**
          * Get KeyDetector object that is used for this PointerTracker.
@@ -208,7 +206,7 @@
     private static final KeyboardActionListener EMPTY_LISTENER =
             new KeyboardActionListener.Adapter();
 
-    private final GestureStrokeWithPreviewTrail mGestureStrokeWithPreviewTrail;
+    private final GestureStrokeWithPreviewPoints mGestureStrokeWithPreviewPoints;
 
     public static void init(boolean hasDistinctMultitouch,
             boolean needsPhantomSuddenMoveEventHack) {
@@ -293,7 +291,7 @@
             throw new NullPointerException();
         }
         mPointerId = id;
-        mGestureStrokeWithPreviewTrail = new GestureStrokeWithPreviewTrail(id);
+        mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints(id);
         setKeyDetectorInner(handler.getKeyDetector());
         mListener = handler.getKeyboardActionListener();
         mDrawingProxy = handler.getDrawingProxy();
@@ -392,7 +390,7 @@
     private void setKeyDetectorInner(final KeyDetector keyDetector) {
         mKeyDetector = keyDetector;
         mKeyboard = keyDetector.getKeyboard();
-        mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
+        mGestureStrokeWithPreviewPoints.setKeyboardGeometry(mKeyboard.mMostCommonKeyWidth);
         final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
         if (newKey != mCurrentKey) {
             if (mDrawingProxy != null) {
@@ -502,8 +500,8 @@
         mDrawingProxy.invalidateKey(key);
     }
 
-    public GestureStrokeWithPreviewTrail getGestureStrokeWithPreviewTrail() {
-        return mGestureStrokeWithPreviewTrail;
+    public GestureStrokeWithPreviewPoints getGestureStrokeWithPreviewPoints() {
+        return mGestureStrokeWithPreviewPoints;
     }
 
     public int getLastX() {
@@ -544,8 +542,8 @@
         return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size();
     }
 
-    private void startBatchInput() {
-        if (sInGesture || !mGestureStrokeWithPreviewTrail.isStartOfAGesture()) {
+    private void mayStartBatchInput() {
+        if (sInGesture || !mGestureStrokeWithPreviewPoints.isStartOfAGesture()) {
             return;
         }
         if (DEBUG_LISTENER) {
@@ -559,10 +557,10 @@
 
     private void updateBatchInput(final long eventTime) {
         synchronized (sAggregratedPointers) {
-            mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints(sAggregratedPointers);
+            mGestureStrokeWithPreviewPoints.appendIncrementalBatchPoints(sAggregratedPointers);
             final int size = sAggregratedPointers.getPointerSize();
             if (size > sLastRecognitionPointSize
-                    && eventTime > sLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
+                    && GestureStroke.hasRecognitionTimePast(eventTime, sLastRecognitionTime)) {
                 sLastRecognitionPointSize = size;
                 sLastRecognitionTime = eventTime;
                 if (DEBUG_LISTENER) {
@@ -575,10 +573,10 @@
         mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker);
     }
 
-    private void endBatchInput() {
+    private void mayEndBatchInput() {
         synchronized (sAggregratedPointers) {
-            mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
-            mGestureStrokeWithPreviewTrail.reset();
+            mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregratedPointers);
+            mGestureStrokeWithPreviewPoints.reset();
             if (getActivePointerTrackerCount() == 1) {
                 if (DEBUG_LISTENER) {
                     Log.d(TAG, "onEndBatchInput: batchPoints="
@@ -601,7 +599,7 @@
         final int trackersSize = sTrackers.size();
         for (int i = 0; i < trackersSize; ++i) {
             final PointerTracker tracker = sTrackers.get(i);
-            tracker.mGestureStrokeWithPreviewTrail.reset();
+            tracker.mGestureStrokeWithPreviewPoints.reset();
         }
         sAggregratedPointers.reset();
         sLastRecognitionPointSize = 0;
@@ -678,18 +676,21 @@
                     && mKeyboard.mId.isAlphabetKeyboard();
             if (isAlphabetKeyboard && !mIsShowingMoreKeysPanel && key != null
                     && Keyboard.isLetterCode(key.mCode)) {
-                mIsDetectingGesture = true;
                 sGestureFirstDownTime = eventTime;
-                mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false /* isHistorical */);
+                onGestureDownEvent(x, y, eventTime);
             }
         } else if (sInGesture && activePointerTrackerCount > 1) {
-            mIsDetectingGesture = true;
-            final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
-            mGestureStrokeWithPreviewTrail.addPoint(x, y, elapsedTimeFromFirstDown,
-                    false /* isHistorical */);
+            onGestureDownEvent(x, y, eventTime);
         }
     }
 
+    private void onGestureDownEvent(final int x, final int y, final long eventTime) {
+        mIsDetectingGesture = true;
+        final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
+        mGestureStrokeWithPreviewPoints.addPoint(x, y, elapsedTimeFromFirstDown,
+                false /* isHistorical */);
+    }
+
     private void onDownEventInternal(final int x, final int y, final long eventTime) {
         Key key = onDownKey(x, y, eventTime);
         // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
@@ -726,8 +727,8 @@
             final boolean isHistorical, final Key key) {
         final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
         if (mIsDetectingGesture) {
-            mGestureStrokeWithPreviewTrail.addPoint(x, y, gestureTime, isHistorical);
-            startBatchInput();
+            mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isHistorical);
+            mayStartBatchInput();
             if (sInGesture && key != null) {
                 updateBatchInput(eventTime);
             }
@@ -919,7 +920,7 @@
             if (currentKey != null) {
                 callListenerOnRelease(currentKey, currentKey.mCode, true);
             }
-            endBatchInput();
+            mayEndBatchInput();
             return;
         }
         // This event will be recognized as a regular code input. Clear unused possible batch points
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
index 4311fa7..699aaea 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -17,7 +17,9 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.SystemClock;
 
 import com.android.inputmethod.latin.Constants;
@@ -25,7 +27,7 @@
 import com.android.inputmethod.latin.ResizableIntArray;
 
 final class GesturePreviewTrail {
-    private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewTrail.PREVIEW_CAPACITY;
+    private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;
 
     private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
@@ -78,7 +80,7 @@
                 ? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark;
     }
 
-    public void addStroke(final GestureStrokeWithPreviewTrail stroke, final long downTime) {
+    public void addStroke(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
         final int trailSize = mEventTimes.getLength();
         stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
         if (mEventTimes.getLength() == trailSize) {
@@ -116,6 +118,99 @@
                 / params.mTrailLingerDuration, 0.0f);
     }
 
+    static final class WorkingSet {
+        // Input
+        // Previous point (P1) coordinates and trail radius.
+        public float p1x, p1y;
+        public float r1;
+        // Current point (P2) coordinates and trail radius.
+        public float p2x, p2y;
+        public float r2;
+
+        // Output
+        // Closing point of arc at P1.
+        public float p1ax, p1ay;
+        // Opening point of arc at P1.
+        public float p1bx, p1by;
+        // Opening point of arc at P2.
+        public float p2ax, p2ay;
+        // Closing point of arc at P2.
+        public float p2bx, p2by;
+        // Start angle of the trail arcs.
+        public float aa;
+        // Sweep angle of the trail arc at P1.
+        public float a1;
+        public RectF arc1 = new RectF();
+        // Sweep angle of the trail arc at P2.
+        public float a2;
+        public RectF arc2 = new RectF();
+    }
+
+    private static final float RIGHT_ANGLE = (float)(Math.PI / 2.0d);
+    private static final float RADIAN_TO_DEGREE = (float)(180.0d / Math.PI);
+
+    private static boolean calculatePathPoints(final WorkingSet w) {
+        final float dx = w.p2x - w.p1x;
+        final float dy = w.p2y - w.p1y;
+        // Distance of the points.
+        final double l = Math.hypot(dx, dy);
+        if (Double.compare(0.0d, l) == 0) {
+            return false;
+        }
+        // Angle of the line p1-p2
+        final float a = (float)Math.atan2(dy, dx);
+        // Difference of trail cap radius.
+        final float dr = w.r2 - w.r1;
+        // Variation of angle at trail cap.
+        final float ar = (float)Math.asin(dr / l);
+        // The start angle of trail cap arc at P1.
+        final float aa = a - (RIGHT_ANGLE + ar);
+        // The end angle of trail cap arc at P2.
+        final float ab = a + (RIGHT_ANGLE + ar);
+        final float cosa = (float)Math.cos(aa);
+        final float sina = (float)Math.sin(aa);
+        final float cosb = (float)Math.cos(ab);
+        final float sinb = (float)Math.sin(ab);
+        w.p1ax = w.p1x + w.r1 * cosa;
+        w.p1ay = w.p1y + w.r1 * sina;
+        w.p1bx = w.p1x + w.r1 * cosb;
+        w.p1by = w.p1y + w.r1 * sinb;
+        w.p2ax = w.p2x + w.r2 * cosa;
+        w.p2ay = w.p2y + w.r2 * sina;
+        w.p2bx = w.p2x + w.r2 * cosb;
+        w.p2by = w.p2y + w.r2 * sinb;
+        w.aa = aa * RADIAN_TO_DEGREE;
+        final float ar2degree = ar * 2.0f * RADIAN_TO_DEGREE;
+        w.a1 = -180.0f + ar2degree;
+        w.a2 = 180.0f + ar2degree;
+        w.arc1.set(w.p1x, w.p1y, w.p1x, w.p1y);
+        w.arc1.inset(-w.r1, -w.r1);
+        w.arc2.set(w.p2x, w.p2y, w.p2x, w.p2y);
+        w.arc2.inset(-w.r2, -w.r2);
+        return true;
+    }
+
+    private static void createPath(final Path path, final WorkingSet w) {
+        path.rewind();
+        // Trail cap at P1.
+        path.moveTo(w.p1x, w.p1y);
+        path.arcTo(w.arc1, w.aa, w.a1);
+        // Trail cap at P2.
+        path.moveTo(w.p2x, w.p2y);
+        path.arcTo(w.arc2, w.aa, w.a2);
+        // Two trapezoids connecting P1 and P2.
+        path.moveTo(w.p1ax, w.p1ay);
+        path.lineTo(w.p1x, w.p1y);
+        path.lineTo(w.p1bx, w.p1by);
+        path.lineTo(w.p2bx, w.p2by);
+        path.lineTo(w.p2x, w.p2y);
+        path.lineTo(w.p2ax, w.p2ay);
+        path.close();
+    }
+
+    private final WorkingSet mWorkingSet = new WorkingSet();
+    private final Path mPath = new Path();
+
     /**
      * Draw gesture preview trail
      * @param canvas The canvas to draw the gesture preview trail
@@ -147,30 +242,38 @@
 
         if (startIndex < trailSize) {
             paint.setColor(params.mTrailColor);
-            paint.setStyle(Paint.Style.STROKE);
-            paint.setStrokeCap(Paint.Cap.ROUND);
-            int lastX = getXCoordValue(xCoords[startIndex]);
-            int lastY = yCoords[startIndex];
-            float maxWidth = getWidth(sinceDown - eventTimes[startIndex], params);
+            paint.setStyle(Paint.Style.FILL);
+            final Path path = mPath;
+            final WorkingSet w = mWorkingSet;
+            w.p1x = getXCoordValue(xCoords[startIndex]);
+            w.p1y = yCoords[startIndex];
+            int lastTime = sinceDown - eventTimes[startIndex];
+            float maxWidth = getWidth(lastTime, params);
+            w.r1 = maxWidth / 2.0f;
             // Initialize bounds rectangle.
-            outBoundsRect.set(lastX, lastY, lastX, lastY);
+            outBoundsRect.set((int)w.p1x, (int)w.p1y, (int)w.p1x, (int)w.p1y);
             for (int i = startIndex + 1; i < trailSize - 1; i++) {
-                final int x = xCoords[i];
-                final int y = yCoords[i];
                 final int elapsedTime = sinceDown - eventTimes[i];
+                w.p2x = getXCoordValue(xCoords[i]);
+                w.p2y = yCoords[i];
                 // Draw trail line only when the current point isn't a down point.
-                if (!isDownEventXCoord(x)) {
+                if (!isDownEventXCoord(xCoords[i])) {
                     final int alpha = getAlpha(elapsedTime, params);
                     paint.setAlpha(alpha);
                     final float width = getWidth(elapsedTime, params);
-                    paint.setStrokeWidth(width);
-                    canvas.drawLine(lastX, lastY, x, y, paint);
+                    w.r2 = width / 2.0f;
+                    if (calculatePathPoints(w)) {
+                        createPath(path, w);
+                        canvas.drawPath(path, paint);
+                        outBoundsRect.union((int)w.p2x, (int)w.p2y);
+                    }
                     // Take union for the bounds.
-                    outBoundsRect.union(x, y);
                     maxWidth = Math.max(maxWidth, width);
                 }
-                lastX = getXCoordValue(x);
-                lastY = y;
+                w.p1x = w.p2x;
+                w.p1y = w.p2y;
+                w.r1 = w.r2;
+                lastTime = elapsedTime;
             }
             // Take care of trail line width.
             final int inset = -((int)maxWidth + 1);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index 8251344..f0be0ee 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -25,44 +25,50 @@
     private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private float mLength;
-    private float mAngle;
     private int mIncrementalRecognitionSize;
     private int mLastIncrementalBatchSize;
     private long mLastPointTime;
     private int mLastPointX;
     private int mLastPointY;
 
-    private int mMinGestureLength;
-    private int mMinGestureSampleLength;
+    private int mMinGestureLength; // pixel
+    private int mMinGestureSampleLength; // pixel
+    private int mGestureRecognitionThreshold; // pixel / sec
 
     // TODO: Move some of these to resource.
     private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 0.75f;
-    private static final int MIN_GESTURE_DURATION = 100; // msec
+    private static final int MIN_GESTURE_START_DURATION = 100; // msec
+    private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec
     private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH = 1.0f / 6.0f;
-    private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; // dip/msec
-    private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float)(Math.PI / 4.0f);
+    private static final float GESTURE_RECOGNITION_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH =
+            5.5f; // keyWidth / sec
+    private static final int MSEC_PER_SEC = 1000;
 
-    private static final float DOUBLE_PI = (float)(2.0f * Math.PI);
+    public static final boolean hasRecognitionTimePast(
+            final long currentTime, final long lastRecognitionTime) {
+        return currentTime > lastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME;
+    }
 
     public GestureStroke(final int pointerId) {
         mPointerId = pointerId;
     }
 
-    public void setGestureSampleLength(final int keyWidth) {
+    public void setKeyboardGeometry(final int keyWidth) {
         // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
         mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH);
         mMinGestureSampleLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH);
+        mGestureRecognitionThreshold =
+                (int)(keyWidth * GESTURE_RECOGNITION_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH);
     }
 
     public boolean isStartOfAGesture() {
         final int size = mEventTimes.getLength();
         final int downDuration = (size > 0) ? mEventTimes.get(size - 1) : 0;
-        return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength;
+        return downDuration > MIN_GESTURE_START_DURATION && mLength > mMinGestureLength;
     }
 
     public void reset() {
         mLength = 0;
-        mAngle = 0;
         mIncrementalRecognitionSize = 0;
         mLastIncrementalBatchSize = 0;
         mLastPointTime = 0;
@@ -71,56 +77,43 @@
         mYCoordinates.setLength(0);
     }
 
-    private void updateLastPoint(final int x, final int y, final int time) {
+    public void addPoint(final int x, final int y, final int time, final boolean isHistorical) {
+        final boolean needsSampling;
+        final int size = mEventTimes.getLength();
+        if (size == 0) {
+            needsSampling = true;
+        } else {
+            final int lastIndex = size - 1;
+            final int lastX = mXCoordinates.get(lastIndex);
+            final int lastY = mYCoordinates.get(lastIndex);
+            final float dist = getDistance(lastX, lastY, x, y);
+            needsSampling = dist > mMinGestureSampleLength;
+            mLength += dist;
+        }
+        if (needsSampling) {
+            mEventTimes.add(time);
+            mXCoordinates.add(x);
+            mYCoordinates.add(y);
+        }
+        if (!isHistorical) {
+            updateIncrementalRecognitionSize(x, y, time);
+        }
+    }
+
+    private void updateIncrementalRecognitionSize(final int x, final int y, final int time) {
+        final int msecs = (int)(time - mLastPointTime);
+        if (msecs > 0) {
+            final int pixels = (int)getDistance(mLastPointX, mLastPointY, x, y);
+            // Equivalent to (pixels / msecs < mGestureRecognitionThreshold / MSEC_PER_SEC)
+            if (pixels * MSEC_PER_SEC < mGestureRecognitionThreshold * msecs) {
+                mIncrementalRecognitionSize = mEventTimes.getLength();
+            }
+        }
         mLastPointTime = time;
         mLastPointX = x;
         mLastPointY = y;
     }
 
-    public void addPoint(final int x, final int y, final int time, final boolean isHistorical) {
-        final int size = mEventTimes.getLength();
-        if (size == 0) {
-            mEventTimes.add(time);
-            mXCoordinates.add(x);
-            mYCoordinates.add(y);
-            if (!isHistorical) {
-                updateLastPoint(x, y, time);
-            }
-            return;
-        }
-
-        final int lastX = mXCoordinates.get(size - 1);
-        final int lastY = mYCoordinates.get(size - 1);
-        final float dist = getDistance(lastX, lastY, x, y);
-        if (dist > mMinGestureSampleLength) {
-            mEventTimes.add(time);
-            mXCoordinates.add(x);
-            mYCoordinates.add(y);
-            mLength += dist;
-            final float angle = getAngle(lastX, lastY, x, y);
-            if (size > 1) {
-                final float curvature = getAngleDiff(angle, mAngle);
-                if (curvature > GESTURE_RECOG_CURVATURE_THRESHOLD) {
-                    if (size > mIncrementalRecognitionSize) {
-                        mIncrementalRecognitionSize = size;
-                    }
-                }
-            }
-            mAngle = angle;
-        }
-
-        if (!isHistorical) {
-            final int duration = (int)(time - mLastPointTime);
-            if (mLastPointTime != 0 && duration > 0) {
-                final float speed = getDistance(mLastPointX, mLastPointY, x, y) / duration;
-                if (speed < GESTURE_RECOG_SPEED_THRESHOLD) {
-                    mIncrementalRecognitionSize = size;
-                }
-            }
-            updateLastPoint(x, y, time);
-        }
-    }
-
     public void appendAllBatchPoints(final InputPointers out) {
         appendBatchPoints(out, mEventTimes.getLength());
     }
@@ -146,21 +139,4 @@
         // java.lang.Math due to the way the JIT optimizes java.lang.Math.
         return (float)Math.sqrt(dx * dx + dy * dy);
     }
-
-    private static float getAngle(final int x1, final int y1, final int x2, final int y2) {
-        final int dx = x1 - x2;
-        final int dy = y1 - y2;
-        if (dx == 0 && dy == 0) return 0;
-        // Would it be faster to call atan2f() directly via JNI?  Not sure about what the JIT
-        // does with Math.atan2().
-        return (float)Math.atan2(dy, dx);
-    }
-
-    private static float getAngleDiff(final float a1, final float a2) {
-        final float diff = Math.abs(a1 - a2);
-        if (diff > Math.PI) {
-            return DOUBLE_PI - diff;
-        }
-        return diff;
-    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
similarity index 65%
rename from java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java
rename to java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
index 6c1a9bc..ce39140 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
@@ -16,7 +16,7 @@
 
 import com.android.inputmethod.latin.ResizableIntArray;
 
-public class GestureStrokeWithPreviewTrail extends GestureStroke {
+public class GestureStrokeWithPreviewPoints extends GestureStroke {
     public static final int PREVIEW_CAPACITY = 256;
 
     private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY);
@@ -26,7 +26,14 @@
     private int mStrokeId;
     private int mLastPreviewSize;
 
-    public GestureStrokeWithPreviewTrail(final int pointerId) {
+    private int mMinPreviewSampleLengthSquare;
+    private int mLastX;
+    private int mLastY;
+
+    // TODO: Move this to resource.
+    private static final float MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH = 0.1f;
+
+    public GestureStrokeWithPreviewPoints(final int pointerId) {
         super(pointerId);
     }
 
@@ -49,11 +56,31 @@
     }
 
     @Override
+    public void setKeyboardGeometry(final int keyWidth) {
+        super.setKeyboardGeometry(keyWidth);
+        final float sampleLength = keyWidth * MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH;
+        mMinPreviewSampleLengthSquare = (int)(sampleLength * sampleLength);
+    }
+
+    private boolean needsSampling(final int x, final int y) {
+        final int dx = x - mLastX;
+        final int dy = y - mLastY;
+        final boolean needsSampling = (dx * dx + dy * dy >= mMinPreviewSampleLengthSquare);
+        if (needsSampling) {
+            mLastX = x;
+            mLastY = y;
+        }
+        return needsSampling;
+    }
+
+    @Override
     public void addPoint(final int x, final int y, final int time, final boolean isHistorical) {
         super.addPoint(x, y, time, isHistorical);
-        mPreviewEventTimes.add(time);
-        mPreviewXCoordinates.add(x);
-        mPreviewYCoordinates.add(y);
+        if (mPreviewEventTimes.getLength() == 0 || isHistorical || needsSampling(x, y)) {
+            mPreviewEventTimes.add(time);
+            mPreviewXCoordinates.add(x);
+            mPreviewYCoordinates.add(y);
+        }
     }
 
     public void appendPreviewStroke(final ResizableIntArray eventTimes,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index 3a85009..15170e0 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -46,7 +46,6 @@
     private final float mGestureFloatingPreviewHorizontalPadding;
     private final float mGestureFloatingPreviewVerticalPadding;
     private final float mGestureFloatingPreviewRoundRadius;
-    /* package */ final int mGestureFloatingPreviewTextLingerTimeout;
 
     private int mXOrigin;
     private int mYOrigin;
@@ -78,11 +77,14 @@
         private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1;
 
         private final Params mGesturePreviewTrailParams;
+        private final int mGestureFloatingPreviewTextLingerTimeout;
 
         public DrawingHandler(final PreviewPlacerView outerInstance,
-                final Params gesturePreviewTrailParams) {
+                final Params gesturePreviewTrailParams,
+                final int getstureFloatinPreviewTextLinerTimeout) {
             super(outerInstance);
             mGesturePreviewTrailParams = gesturePreviewTrailParams;
+            mGestureFloatingPreviewTextLingerTimeout = getstureFloatinPreviewTextLinerTimeout;
         }
 
         @Override
@@ -105,10 +107,8 @@
 
         public void dismissGestureFloatingPreviewText() {
             cancelDismissGestureFloatingPreviewText();
-            final PreviewPlacerView placerView = getOuterInstance();
-            sendMessageDelayed(
-                    obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT),
-                    placerView.mGestureFloatingPreviewTextLingerTimeout);
+            sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT),
+                    mGestureFloatingPreviewTextLingerTimeout);
         }
 
         private void cancelUpdateGestureTrailPreview() {
@@ -122,7 +122,6 @@
         }
 
         public void cancelAllMessages() {
-            cancelDismissGestureFloatingPreviewText();
             cancelUpdateGestureTrailPreview();
         }
     }
@@ -151,12 +150,13 @@
                 R.styleable.KeyboardView_gestureFloatingPreviewVerticalPadding, 0.0f);
         mGestureFloatingPreviewRoundRadius = keyboardViewAttr.getDimension(
                 R.styleable.KeyboardView_gestureFloatingPreviewRoundRadius, 0.0f);
-        mGestureFloatingPreviewTextLingerTimeout = keyboardViewAttr.getInt(
+        final int gestureFloatingPreviewTextLingerTimeout = keyboardViewAttr.getInt(
                 R.styleable.KeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
         mGesturePreviewTrailParams = new Params(keyboardViewAttr);
         keyboardViewAttr.recycle();
 
-        mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
+        mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams,
+                gestureFloatingPreviewTextLingerTimeout);
 
         final Paint gesturePaint = new Paint();
         gesturePaint.setAntiAlias(true);
@@ -189,22 +189,29 @@
     }
 
     public void invalidatePointer(final PointerTracker tracker, final boolean isOldestTracker) {
-        GesturePreviewTrail trail;
-        synchronized (mGesturePreviewTrails) {
-            trail = mGesturePreviewTrails.get(tracker.mPointerId);
-            if (trail == null) {
-                trail = new GesturePreviewTrail();
-                mGesturePreviewTrails.put(tracker.mPointerId, trail);
-            }
-        }
-        trail.addStroke(tracker.getGestureStrokeWithPreviewTrail(), tracker.getDownTime());
-
-        if (isOldestTracker) {
+        final boolean needsToUpdateLastPointer =
+                isOldestTracker && mDrawsGestureFloatingPreviewText;
+        if (needsToUpdateLastPointer) {
             mLastPointerX = tracker.getLastX();
             mLastPointerY = tracker.getLastY();
         }
+
+        if (mDrawsGesturePreviewTrail) {
+            GesturePreviewTrail trail;
+            synchronized (mGesturePreviewTrails) {
+                trail = mGesturePreviewTrails.get(tracker.mPointerId);
+                if (trail == null) {
+                    trail = new GesturePreviewTrail();
+                    mGesturePreviewTrails.put(tracker.mPointerId, trail);
+                }
+            }
+            trail.addStroke(tracker.getGestureStrokeWithPreviewPoints(), tracker.getDownTime());
+        }
+
         // TODO: Should narrow the invalidate region.
-        invalidate();
+        if (mDrawsGesturePreviewTrail || needsToUpdateLastPointer) {
+            invalidate();
+        }
     }
 
     @Override
@@ -262,6 +269,7 @@
     }
 
     public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) {
+        if (!mDrawsGestureFloatingPreviewText) return;
         mGestureFloatingPreviewText = gestureFloatingPreviewText;
         invalidate();
     }
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..9252b09 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -72,6 +72,7 @@
 import com.android.inputmethod.keyboard.KeyboardView;
 import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
+import com.android.inputmethod.latin.Utils.Stats;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
 import com.android.inputmethod.research.ResearchLogger;
@@ -148,7 +149,7 @@
     private boolean mIsUserDictionaryAvailable;
 
     private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
-    private WordComposer mWordComposer = new WordComposer();
+    private final WordComposer mWordComposer = new WordComposer();
     private RichInputConnection mConnection = new RichInputConnection(this);
 
     // Keep track of the last selection range to decide if we need to show word alternatives
@@ -741,6 +742,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 +1102,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.
@@ -1344,6 +1332,12 @@
                 didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState);
             } else {
                 if (SPACE_STATE_PHANTOM == spaceState) {
+                    if (ProductionFlag.IS_INTERNAL) {
+                        if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
+                            Stats.onAutoCorrection(
+                                    "", mWordComposer.getTypedWord(), " ", mWordComposer);
+                        }
+                    }
                     commitTyped(LastComposedWord.NOT_A_SEPARATOR);
                 }
                 final int keyX, keyY;
@@ -1391,9 +1385,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;
     }
 
@@ -1401,13 +1396,29 @@
     public void onStartBatchInput() {
         mConnection.beginBatchEdit();
         if (mWordComposer.isComposingWord()) {
-            commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+            if (ProductionFlag.IS_INTERNAL) {
+                if (mWordComposer.isBatchMode()) {
+                    Stats.onAutoCorrection("", mWordComposer.getTypedWord(), " ", mWordComposer);
+                }
+            }
+            if (mWordComposer.size() <= 1) {
+                // We auto-correct the previous (typed, not gestured) string iff it's one character
+                // long. The reason for this is, even in the middle of gesture typing, you'll still
+                // tap one-letter words and you want them auto-corrected (typically, "i" in English
+                // should become "I"). However for any longer word, we assume that the reason for
+                // tapping probably is that the word you intend to type is not in the dictionary,
+                // so we do not attempt to correct, on the assumption that if that was a dictionary
+                // word, the user would probably have gestured instead.
+                commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
+            } else {
+                commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+            }
             mExpectingUpdateSelection = true;
-            // TODO: Can we remove this?
+            // The following is necessary for the case where the user typed something but didn't
+            // manual pick it and didn't input any separator.
             mSpaceState = SPACE_STATE_PHANTOM;
         }
         mConnection.endBatchEdit();
-        // TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
         mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
     }
 
@@ -1509,8 +1520,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) {
@@ -1558,7 +1570,9 @@
             }
         } else {
             if (mLastComposedWord.canRevertCommit()) {
-                Utils.Stats.onAutoCorrectionCancellation();
+                if (ProductionFlag.IS_INTERNAL) {
+                    Stats.onAutoCorrectionCancellation();
+                }
                 revertCommit();
                 return;
             }
@@ -1707,7 +1721,9 @@
             if (null != mSuggestionStripView) mSuggestionStripView.dismissAddToDictionaryHint();
         }
         mHandler.postUpdateSuggestionStrip();
-        Utils.Stats.onNonSeparator((char)primaryCode, x, y);
+        if (ProductionFlag.IS_INTERNAL) {
+            Utils.Stats.onNonSeparator((char)primaryCode, x, y);
+        }
     }
 
     // Returns true if we did an autocorrection, false otherwise.
@@ -1771,8 +1787,9 @@
             // already displayed or not, so it's okay.
             setPunctuationSuggestions();
         }
-
-        Utils.Stats.onSeparator((char)primaryCode, x, y);
+        if (ProductionFlag.IS_INTERNAL) {
+            Utils.Stats.onSeparator((char)primaryCode, x, y);
+        }
 
         mHandler.postUpdateShiftState();
         return didAutoCorrect;
@@ -1941,7 +1958,10 @@
                 throw new RuntimeException("We have an auto-correction but the typed word "
                         + "is empty? Impossible! I must commit suicide.");
             }
-            Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorString);
+            if (ProductionFlag.IS_INTERNAL) {
+                Stats.onAutoCorrection(
+                        typedWord, autoCorrection.toString(), separatorString, mWordComposer);
+            }
             mExpectingUpdateSelection = true;
             commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
                     separatorString);
@@ -2019,8 +2039,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
@@ -2031,8 +2051,10 @@
                 // If the suggestion is not in the dictionary, the hint should be shown.
                 && !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true);
 
-        Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE,
-                Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+        if (ProductionFlag.IS_INTERNAL) {
+            Stats.onSeparator((char)Keyboard.CODE_SPACE,
+                    Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+        }
         if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
             mSuggestionStripView.showAddToDictionaryHint(
                     suggestion, mCurrentSettings.mHintToSaveText);
@@ -2149,8 +2171,10 @@
                     previousWord.toString(), committedWord.toString());
         }
         mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1);
-        Utils.Stats.onSeparator(mLastComposedWord.mSeparatorString,
-                Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+        if (ProductionFlag.IS_INTERNAL) {
+            Stats.onSeparator(mLastComposedWord.mSeparatorString,
+                    Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+        }
         if (ProductionFlag.IS_EXPERIMENTAL) {
             ResearchLogger.latinIME_revertCommit(originallyTypedWord);
         }
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index e843848..9eab19c 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, InputPointers inputPointers) {
     }
 
     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..28c0c0f 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -190,10 +190,35 @@
         }
     }
 
-    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;
+        if (!TextUtils.isEmpty(mComposingText)) {
+            if (hasSpaceBefore) {
+                // If we have some composing text and a space before, then we should have
+                // MODE_CHARACTERS and MODE_WORDS on.
+                return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & inputType;
+            } else {
+                // We have some composing text - we should be in MODE_CHARACTERS only.
+                return TextUtils.CAP_MODE_CHARACTERS & inputType;
+            }
+        }
         // TODO: this will generally work, but there may be cases where the buffer contains SOME
         // information but not enough to determine the caps mode accurately. This may happen after
         // heavy pressing of delete, for example DEFAULT_TEXT_CACHE_SIZE - 5 times or so.
@@ -205,7 +230,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..9479a88 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -42,9 +42,9 @@
 import com.android.inputmethod.research.ResearchLogger;
 import com.android.inputmethodcommon.InputMethodSettingsFragment;
 
-public class Settings extends InputMethodSettingsFragment
+public final 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";
@@ -63,8 +63,8 @@
             "last_user_dictionary_write_time";
     public static final String PREF_ADVANCED_SETTINGS = "pref_advanced_settings";
     public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict";
-    public static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY =
-            "pref_suppress_language_switch_key";
+    public static final String PREF_SHOW_LANGUAGE_SWITCH_KEY =
+            "pref_show_language_switch_key";
     public static final String PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST =
             "pref_include_other_imes_in_language_switch_list";
     public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles";
@@ -77,8 +77,8 @@
     public static final String PREF_KEYPRESS_SOUND_VOLUME =
             "pref_keypress_sound_volume";
     public static final String PREF_GESTURE_PREVIEW_TRAIL = "pref_gesture_preview_trail";
-    public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT =
-            "pref_gesture_floating_preview_text";
+    public static final String PREF_SHOW_GESTURE_FLOATING_PREVIEW_TEXT =
+            "pref_show_gesture_floating_preview_text";
 
     public static final String PREF_INPUT_LANGUAGE = "input_language";
     public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
@@ -97,7 +97,7 @@
     private TextView mKeypressVibrationDurationSettingsTextView;
     private TextView mKeypressSoundVolumeSettingsTextView;
 
-    private static void setPreferenceEnabled(Preference preference, boolean enabled) {
+    private static void setPreferenceEnabled(final Preference preference, final boolean enabled) {
         if (preference != null) {
             preference.setEnabled(enabled);
         }
@@ -111,7 +111,7 @@
     }
 
     @Override
-    public void onCreate(Bundle icicle) {
+    public void onCreate(final Bundle icicle) {
         super.onCreate(icicle);
         setInputMethodSettingsCategoryTitle(R.string.language_selection_title);
         setSubtypeEnablerTitle(R.string.select_language);
@@ -192,7 +192,7 @@
         }
 
         setPreferenceEnabled(findPreference(PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST),
-                !SettingsValues.isLanguageSwitchKeySupressed(prefs));
+                SettingsValues.showsLanguageSwitchKey(prefs));
 
         final PreferenceScreen dictionaryLink =
                 (PreferenceScreen) findPreference(PREF_CONFIGURE_DICTIONARIES_KEY);
@@ -207,7 +207,7 @@
                 R.bool.config_gesture_input_enabled_by_build_config);
         final Preference gesturePreviewTrail = findPreference(PREF_GESTURE_PREVIEW_TRAIL);
         final Preference gestureFloatingPreviewText = findPreference(
-                PREF_GESTURE_FLOATING_PREVIEW_TEXT);
+                PREF_SHOW_GESTURE_FLOATING_PREVIEW_TEXT);
         if (!gestureInputEnabledByBuildConfig) {
             miscSettings.removePreference(findPreference(PREF_GESTURE_INPUT));
             miscSettings.removePreference(gesturePreviewTrail);
@@ -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) {
@@ -288,14 +288,14 @@
     }
 
     @Override
-    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
         (new BackupManager(getActivity())).dataChanged();
         if (key.equals(PREF_POPUP_ON)) {
             setPreferenceEnabled(findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY),
                     prefs.getBoolean(PREF_POPUP_ON, true));
-        } else if (key.equals(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY)) {
+        } else if (key.equals(PREF_SHOW_LANGUAGE_SWITCH_KEY)) {
             setPreferenceEnabled(findPreference(PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST),
-                    !SettingsValues.isLanguageSwitchKeySupressed(prefs));
+                    SettingsValues.showsLanguageSwitchKey(prefs));
         } else if (key.equals(PREF_GESTURE_INPUT)) {
             final boolean gestureInputEnabledByConfig = getResources().getBoolean(
                     R.bool.config_gesture_input_enabled_by_build_config);
@@ -304,7 +304,7 @@
                         PREF_GESTURE_INPUT, true);
                 setPreferenceEnabled(findPreference(PREF_GESTURE_PREVIEW_TRAIL),
                         gestureInputEnabledByUser);
-                setPreferenceEnabled(findPreference(PREF_GESTURE_FLOATING_PREVIEW_TEXT),
+                setPreferenceEnabled(findPreference(PREF_SHOW_GESTURE_FLOATING_PREVIEW_TEXT),
                         gestureInputEnabledByUser);
             }
         }
@@ -352,7 +352,7 @@
     }
 
     private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
-            SharedPreferences sp, Resources res) {
+            final SharedPreferences sp, final Resources res) {
         if (mKeypressVibrationDurationSettingsPref != null) {
             final boolean hasVibratorHardware = VibratorUtils.getInstance(getActivity())
                     .hasVibrator();
@@ -370,7 +370,7 @@
     }
 
     private void updateKeypressVibrationDurationSettingsSummary(
-            SharedPreferences sp, Resources res) {
+            final SharedPreferences sp, final Resources res) {
         if (mKeypressVibrationDurationSettingsPref != null) {
             mKeypressVibrationDurationSettingsPref.setSummary(
                     SettingsValues.getCurrentVibrationDuration(sp, res)
@@ -428,7 +428,7 @@
         builder.create().show();
     }
 
-    private void updateKeypressSoundVolumeSummary(SharedPreferences sp, Resources res) {
+    private void updateKeypressSoundVolumeSummary(final SharedPreferences sp, final Resources res) {
         if (mKeypressSoundVolumeSettingsPref != null) {
             mKeypressSoundVolumeSettingsPref.setSummary(String.valueOf(
                     (int)(SettingsValues.getCurrentKeypressSoundVolume(sp, res) * 100)));
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 5e9c870..9d8379a 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -72,7 +72,7 @@
     @SuppressWarnings("unused") // TODO: Use this
     private final boolean mUsabilityStudyMode;
     public final boolean mIncludesOtherImesInLanguageSwitchList;
-    public final boolean mIsLanguageSwitchKeySuppressed;
+    public final boolean mShowsLanguageSwitchKey;
     @SuppressWarnings("unused") // TODO: Use this
     private final String mKeyPreviewPopupDismissDelayRawValue;
     public final boolean mUseContactsDict;
@@ -151,7 +151,7 @@
         mUsabilityStudyMode = getUsabilityStudyMode(prefs);
         mIncludesOtherImesInLanguageSwitchList = prefs.getBoolean(
                 Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false);
-        mIsLanguageSwitchKeySuppressed = isLanguageSwitchKeySupressed(prefs);
+        mShowsLanguageSwitchKey = showsLanguageSwitchKey(prefs);
         mKeyPreviewPopupDismissDelayRawValue = prefs.getString(
                 Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
                 Integer.toString(res.getInteger(R.integer.config_key_preview_linger_timeout)));
@@ -178,7 +178,7 @@
                 && prefs.getBoolean(Settings.PREF_GESTURE_INPUT, true);
         mGesturePreviewTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true);
         mGestureFloatingPreviewTextEnabled = prefs.getBoolean(
-                Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
+                Settings.PREF_SHOW_GESTURE_FLOATING_PREVIEW_TEXT, false);
         mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
         mSuggestionVisibility = createSuggestionVisibility(res);
     }
@@ -336,12 +336,25 @@
         return mVoiceKeyOnMain;
     }
 
-    public static boolean isLanguageSwitchKeySupressed(final SharedPreferences prefs) {
-        return prefs.getBoolean(Settings.PREF_SUPPRESS_LANGUAGE_SWITCH_KEY, false);
+    // This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead.
+    // This is being used only for the backward compatibility.
+    private static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY =
+            "pref_suppress_language_switch_key";
+
+    public static boolean showsLanguageSwitchKey(final SharedPreferences prefs) {
+        if (prefs.contains(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY)) {
+            final boolean suppressLanguageSwitchKey = prefs.getBoolean(
+                    PREF_SUPPRESS_LANGUAGE_SWITCH_KEY, false);
+            final SharedPreferences.Editor editor = prefs.edit();
+            editor.remove(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY);
+            editor.putBoolean(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, !suppressLanguageSwitchKey);
+            editor.apply();
+        }
+        return prefs.getBoolean(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, true);
     }
 
     public boolean isLanguageSwitchKeyEnabled(final Context context) {
-        if (mIsLanguageSwitchKeySuppressed) {
+        if (!mShowsLanguageSwitchKey) {
             return false;
         }
         if (mIncludesOtherImesInLanguageSwitchList) {
@@ -353,7 +366,7 @@
         }
     }
 
-    public boolean isFullscreenModeAllowed(final Resources res) {
+    public static boolean isFullscreenModeAllowed(final Resources res) {
         return res.getBoolean(R.bool.config_use_fullscreen_mode);
     }
 
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/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 0418d31..278c4b9 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -306,6 +306,10 @@
                     wordComposer, prevWordForBigram, proximityInfo, sessionId));
         }
 
+        for (SuggestedWordInfo wordInfo : suggestionsSet) {
+            LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), wordInfo.mSourceDict);
+        }
+
         final ArrayList<SuggestedWordInfo> suggestionsContainer =
                 CollectionUtils.newArrayList(suggestionsSet);
         final int suggestionsCount = suggestionsContainer.size();
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
index 550e4e5..4a3d11a 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
@@ -18,6 +18,7 @@
 
 import android.util.Log;
 
+import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
 import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
 import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
@@ -96,6 +97,11 @@
         public void put(final byte b) {
             mBuffer[mPosition++] = b;
         }
+
+        @Override
+        public int limit() {
+            return mBuffer.length;
+        }
     }
 
     /**
@@ -162,7 +168,7 @@
         final Map<Integer, ArrayList<PendingAttribute>> bigrams = CollectionUtils.newTreeMap();
 
         try {
-            BinaryDictInputOutput.readUnigramsAndBigramsBinary(buffer, unigrams, frequencies,
+            BinaryDictIOUtils.readUnigramsAndBigramsBinary(buffer, unigrams, frequencies,
                     bigrams);
             addWordsFromWordMap(unigrams, frequencies, bigrams, dict);
         } catch (IOException e) {
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 1c98b92..876bc8e 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -412,14 +412,24 @@
         }
 
         public static void onAutoCorrection(final String typedWord, final String correctedWord,
-                final String separatorString) {
-            if (TextUtils.isEmpty(typedWord)) return;
+                final String separatorString, final WordComposer wordComposer) {
+            final boolean isBatchMode = wordComposer.isBatchMode();
+            if (!isBatchMode && TextUtils.isEmpty(typedWord)) return;
             // TODO: this fails when the separator is more than 1 code point long, but
             // the backend can't handle it yet. The only case when this happens is with
             // smileys and other multi-character keys.
             final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
                     : separatorString.codePointAt(0);
-            LatinImeLogger.logOnAutoCorrection(typedWord, correctedWord, codePoint);
+            if (!isBatchMode) {
+                LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint);
+            } else {
+                if (!TextUtils.isEmpty(correctedWord)) {
+                    // We must make sure that InputPointer contains only the relative timestamps,
+                    // not actual timestamps.
+                    LatinImeLogger.logOnAutoCorrectionForGeometric(
+                            "", correctedWord, codePoint, wordComposer.getInputPointers());
+                }
+            }
         }
 
         public static void onAutoCorrectionCancellation() {
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 4b7adf2..275ebf3 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -265,9 +265,12 @@
      * @return true if all user typed chars are upper case, false otherwise
      */
     public boolean isAllUpperCase() {
-        return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
-                || mCapitalizedMode == CAPS_MODE_MANUAL_SHIFT_LOCKED
-                || (mCapsCount > 0) && (mCapsCount == size());
+        if (size() <= 1) {
+            return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
+                    || mCapitalizedMode == CAPS_MODE_MANUAL_SHIFT_LOCKED;
+        } else {
+            return mCapsCount == size();
+        }
     }
 
     public boolean wasShiftedNoLock() {
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/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
new file mode 100644
index 0000000..7a1b9dc
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2012 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.Constants;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
+import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Stack;
+
+public class BinaryDictIOUtils {
+    private static final boolean DBG = false;
+
+    private static class Position {
+        public static final int NOT_READ_GROUPCOUNT = -1;
+
+        public int mAddress;
+        public int mNumOfCharGroup;
+        public int mPosition;
+        public int mLength;
+
+        public Position(int address, int length) {
+            mAddress = address;
+            mLength = length;
+            mNumOfCharGroup = NOT_READ_GROUPCOUNT;
+        }
+    }
+
+    /**
+     * Tours all node without recursive call.
+     */
+    private static void readUnigramsAndBigramsBinaryInner(
+            final FusionDictionaryBufferInterface buffer, final int headerSize,
+            final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
+            final Map<Integer, ArrayList<PendingAttribute>> bigrams,
+            final FormatOptions formatOptions) {
+        int[] pushedChars = new int[FormatSpec.MAX_WORD_LENGTH + 1];
+
+        Stack<Position> stack = new Stack<Position>();
+        int index = 0;
+
+        Position initPos = new Position(headerSize, 0);
+        stack.push(initPos);
+
+        while (!stack.empty()) {
+            Position p = stack.peek();
+
+            if (DBG) {
+                MakedictLog.d("read: address=" + p.mAddress + ", numOfCharGroup=" +
+                        p.mNumOfCharGroup + ", position=" + p.mPosition + ", length=" + p.mLength);
+            }
+
+            if (buffer.position() != p.mAddress) buffer.position(p.mAddress);
+            if (index != p.mLength) index = p.mLength;
+
+            if (p.mNumOfCharGroup == Position.NOT_READ_GROUPCOUNT) {
+                p.mNumOfCharGroup = BinaryDictInputOutput.readCharGroupCount(buffer);
+                p.mAddress += BinaryDictInputOutput.getGroupCountSize(p.mNumOfCharGroup);
+                p.mPosition = 0;
+            }
+
+            CharGroupInfo info = BinaryDictInputOutput.readCharGroup(buffer,
+                    p.mAddress - headerSize, formatOptions);
+            for (int i = 0; i < info.mCharacters.length; ++i) {
+                pushedChars[index++] = info.mCharacters[i];
+            }
+            p.mPosition++;
+
+            if (info.mFrequency != FusionDictionary.CharGroup.NOT_A_TERMINAL) { // found word
+                words.put(info.mOriginalAddress, new String(pushedChars, 0, index));
+                frequencies.put(info.mOriginalAddress, info.mFrequency);
+                if (info.mBigrams != null) bigrams.put(info.mOriginalAddress, info.mBigrams);
+            }
+
+            if (p.mPosition == p.mNumOfCharGroup) {
+                stack.pop();
+            } else {
+                // the node has more groups.
+                p.mAddress = buffer.position();
+            }
+
+            if (BinaryDictInputOutput.hasChildrenAddress(info.mChildrenAddress)) {
+                Position childrenPos = new Position(info.mChildrenAddress + headerSize, index);
+                stack.push(childrenPos);
+            }
+        }
+    }
+
+    /**
+     * Reads unigrams and bigrams from the binary file.
+     * Doesn't make the memory representation of the dictionary.
+     *
+     * @param buffer the buffer to read.
+     * @param words the map to store the address as a key and the word as a value.
+     * @param frequencies the map to store the address as a key and the frequency as a value.
+     * @param bigrams the map to store the address as a key and the list of address as a value.
+     * @throws IOException
+     * @throws UnsupportedFormatException
+     */
+    public static void readUnigramsAndBigramsBinary(final FusionDictionaryBufferInterface buffer,
+            final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
+            final Map<Integer, ArrayList<PendingAttribute>> bigrams) throws IOException,
+            UnsupportedFormatException {
+        // Read header
+        final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
+        readUnigramsAndBigramsBinaryInner(buffer, header.mHeaderSize, words, frequencies, bigrams,
+                header.mFormatOptions);
+    }
+
+    /**
+     * Gets the address of the last CharGroup of the exact matching word in the dictionary.
+     * If no match is found, returns NOT_VALID_WORD.
+     *
+     * @param buffer the buffer to read.
+     * @param word the word we search for.
+     * @return the address of the terminal node.
+     * @throws IOException
+     * @throws UnsupportedFormatException
+     */
+    public static int getTerminalPosition(final FusionDictionaryBufferInterface buffer,
+            final String word) throws IOException, UnsupportedFormatException {
+        if (word == null) return FormatSpec.NOT_VALID_WORD;
+        if (buffer.position() != 0) buffer.position(0);
+
+        final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
+        int wordPos = 0;
+        final int wordLen = word.codePointCount(0, word.length());
+        for (int depth = 0; depth < Constants.Dictionary.MAX_WORD_LENGTH; ++depth) {
+            if (wordPos >= wordLen) return FormatSpec.NOT_VALID_WORD;
+            int groupOffset = buffer.position() - header.mHeaderSize;
+            final int charGroupCount = BinaryDictInputOutput.readCharGroupCount(buffer);
+            groupOffset += BinaryDictInputOutput.getGroupCountSize(charGroupCount);
+
+            for (int i = 0; i < charGroupCount; ++i) {
+                final int charGroupPos = buffer.position();
+                final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer,
+                        buffer.position(), header.mFormatOptions);
+                boolean same = true;
+                for (int p = 0, j = word.offsetByCodePoints(0, wordPos);
+                        p < currentInfo.mCharacters.length;
+                        ++p, j = word.offsetByCodePoints(j, 1)) {
+                    if (wordPos + p >= wordLen
+                            || word.codePointAt(j) != currentInfo.mCharacters[p]) {
+                        same = false;
+                        break;
+                    }
+                }
+
+                if (same) {
+                    if (wordPos + currentInfo.mCharacters.length == wordLen) {
+                        if (currentInfo.mFrequency == CharGroup.NOT_A_TERMINAL) {
+                            return FormatSpec.NOT_VALID_WORD;
+                        } else {
+                            return charGroupPos;
+                        }
+                    }
+                    wordPos += currentInfo.mCharacters.length;
+                    if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) {
+                        return FormatSpec.NOT_VALID_WORD;
+                    }
+                    buffer.position(currentInfo.mChildrenAddress);
+                    break;
+                }
+                groupOffset = currentInfo.mEndAddress;
+
+                // not found
+                if (i >= charGroupCount - 1) {
+                    return FormatSpec.NOT_VALID_WORD;
+                }
+            }
+        }
+        return FormatSpec.NOT_VALID_WORD;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 6f50869..1d3e94b 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -63,6 +63,7 @@
         public int position();
         public void position(int newPosition);
         public void put(final byte b);
+        public int limit();
     }
 
     public static final class ByteBufferWrapper implements FusionDictionaryBufferInterface {
@@ -107,6 +108,11 @@
         public void put(final byte b) {
             mBuffer.put(b);
         }
+
+        @Override
+        public int limit() {
+            return mBuffer.limit();
+        }
     }
 
     /**
@@ -284,7 +290,7 @@
      * @param count the group count
      * @return the size of the group count, either 1 or 2 bytes.
      */
-    private static int getGroupCountSize(final int count) {
+    public static int getGroupCountSize(final int count) {
         if (FormatSpec.MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= count) {
             return 1;
         } else if (FormatSpec.MAX_CHARGROUPS_IN_A_NODE >= count) {
@@ -370,13 +376,16 @@
             g.mCachedSize = groupSize;
             size += groupSize;
         }
+        if (options.mHasLinkedListNode) {
+            size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+        }
         node.mCachedSize = size;
     }
 
     /**
      * Helper method to hide the actual value of the no children address.
      */
-    private static boolean hasChildrenAddress(final int address) {
+    public static boolean hasChildrenAddress(final int address) {
         return FormatSpec.NO_CHILDREN_ADDRESS != address;
     }
 
@@ -521,6 +530,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 +544,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 +558,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 +587,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 +603,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 +792,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 +927,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 "
@@ -1083,7 +1105,7 @@
     // readDictionaryBinary is the public entry point for them.
 
     private static final int[] CHARACTER_BUFFER = new int[FormatSpec.MAX_WORD_LENGTH];
-    private static CharGroupInfo readCharGroup(final FusionDictionaryBufferInterface buffer,
+    public static CharGroupInfo readCharGroup(final FusionDictionaryBufferInterface buffer,
             final int originalGroupAddress, final FormatOptions options) {
         int addressPointer = originalGroupAddress;
         final int flags = buffer.readUnsignedByte();
@@ -1196,7 +1218,7 @@
     /**
      * Reads and returns the char group count out of a buffer and forwards the pointer.
      */
-    private static int readCharGroupCount(final FusionDictionaryBufferInterface buffer) {
+    public static int readCharGroupCount(final FusionDictionaryBufferInterface buffer) {
         final int msb = buffer.readUnsignedByte();
         if (FormatSpec.MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) {
             return msb;
@@ -1220,8 +1242,9 @@
      * @param formatOptions file format options.
      * @return the word, as a string.
      */
-    private static String getWordAtAddress(final FusionDictionaryBufferInterface buffer,
-            final int headerSize, final int address, final FormatOptions formatOptions) {
+    /* packages for tests */ static String getWordAtAddress(
+            final FusionDictionaryBufferInterface buffer, final int headerSize, final int address,
+            final FormatOptions formatOptions) {
         final String cachedString = wordCache.get(address);
         if (null != cachedString) return cachedString;
 
@@ -1325,146 +1348,67 @@
             final Map<Integer, Node> reverseNodeMap, final Map<Integer, CharGroup> reverseGroupMap,
             final FormatOptions options)
             throws IOException {
-        final int nodeOrigin = buffer.position() - headerSize;
-        final int count = readCharGroupCount(buffer);
         final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>();
-        int groupOffset = nodeOrigin + getGroupCountSize(count);
-        for (int i = count; i > 0; --i) {
-            CharGroupInfo info = readCharGroup(buffer, groupOffset, options);
-            ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
-            ArrayList<WeightedString> bigrams = null;
-            if (null != info.mBigrams) {
-                bigrams = new ArrayList<WeightedString>();
-                for (PendingAttribute bigram : info.mBigrams) {
-                    final String word = getWordAtAddress(
-                            buffer, headerSize, bigram.mAddress, options);
-                    bigrams.add(new WeightedString(word, bigram.mFrequency));
+        final int nodeOrigin = buffer.position() - headerSize;
+
+        do { // Scan the linked-list node.
+            final int nodeHeadPosition = buffer.position() - headerSize;
+            final int count = readCharGroupCount(buffer);
+            int groupOffset = nodeHeadPosition + getGroupCountSize(count);
+            for (int i = count; i > 0; --i) { // Scan the array of CharGroup.
+                CharGroupInfo info = readCharGroup(buffer, groupOffset, options);
+                ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
+                ArrayList<WeightedString> bigrams = null;
+                if (null != info.mBigrams) {
+                    bigrams = new ArrayList<WeightedString>();
+                    for (PendingAttribute bigram : info.mBigrams) {
+                        final String word = getWordAtAddress(
+                                buffer, headerSize, bigram.mAddress, options);
+                        bigrams.add(new WeightedString(word, bigram.mFrequency));
+                    }
+                }
+                if (hasChildrenAddress(info.mChildrenAddress)) {
+                    Node children = reverseNodeMap.get(info.mChildrenAddress);
+                    if (null == children) {
+                        final int currentPosition = buffer.position();
+                        buffer.position(info.mChildrenAddress + headerSize);
+                        children = readNode(
+                                buffer, headerSize, reverseNodeMap, reverseGroupMap, options);
+                        buffer.position(currentPosition);
+                    }
+                    nodeContents.add(
+                            new CharGroup(info.mCharacters, shortcutTargets, bigrams,
+                                    info.mFrequency,
+                                    0 != (info.mFlags & FormatSpec.FLAG_IS_NOT_A_WORD),
+                                    0 != (info.mFlags & FormatSpec.FLAG_IS_BLACKLISTED), children));
+                } else {
+                    nodeContents.add(
+                            new CharGroup(info.mCharacters, shortcutTargets, bigrams,
+                                    info.mFrequency,
+                                    0 != (info.mFlags & FormatSpec.FLAG_IS_NOT_A_WORD),
+                                    0 != (info.mFlags & FormatSpec.FLAG_IS_BLACKLISTED)));
+                }
+                groupOffset = info.mEndAddress;
+            }
+
+            // reach the end of the array.
+            if (options.mHasLinkedListNode) {
+                final int nextAddress = buffer.readUnsignedInt24();
+                if (nextAddress >= 0 && nextAddress < buffer.limit()) {
+                    buffer.position(nextAddress);
+                } else {
+                    break;
                 }
             }
-            if (hasChildrenAddress(info.mChildrenAddress)) {
-                Node children = reverseNodeMap.get(info.mChildrenAddress);
-                if (null == children) {
-                    final int currentPosition = buffer.position();
-                    buffer.position(info.mChildrenAddress + headerSize);
-                    children = readNode(
-                            buffer, headerSize, reverseNodeMap, reverseGroupMap, options);
-                    buffer.position(currentPosition);
-                }
-                nodeContents.add(
-                        new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency,
-                                0 != (info.mFlags & FormatSpec.FLAG_IS_NOT_A_WORD),
-                                0 != (info.mFlags & FormatSpec.FLAG_IS_BLACKLISTED), children));
-            } else {
-                nodeContents.add(
-                        new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency,
-                                0 != (info.mFlags & FormatSpec.FLAG_IS_NOT_A_WORD),
-                                0 != (info.mFlags & FormatSpec.FLAG_IS_BLACKLISTED)));
-            }
-            groupOffset = info.mEndAddress;
-        }
+        } while (options.mHasLinkedListNode &&
+                buffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS);
+
         final Node node = new Node(nodeContents);
         node.mCachedAddress = nodeOrigin;
         reverseNodeMap.put(node.mCachedAddress, node);
         return node;
     }
 
-    // TODO: move these methods (readUnigramsAndBigramsBinary(|Inner)) and an inner class (Position)
-    // out of this class.
-    private static class Position {
-        public static final int NOT_READ_GROUPCOUNT = -1;
-
-        public int mAddress;
-        public int mNumOfCharGroup;
-        public int mPosition;
-        public int mLength;
-
-        public Position(int address, int length) {
-            mAddress = address;
-            mLength = length;
-            mNumOfCharGroup = NOT_READ_GROUPCOUNT;
-        }
-    }
-
-    /**
-     * Tours all node without recursive call.
-     */
-    private static void readUnigramsAndBigramsBinaryInner(
-            final FusionDictionaryBufferInterface buffer, final int headerSize,
-            final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
-            final Map<Integer, ArrayList<PendingAttribute>> bigrams,
-            final FormatOptions formatOptions) {
-        int[] pushedChars = new int[FormatSpec.MAX_WORD_LENGTH + 1];
-
-        Stack<Position> stack = new Stack<Position>();
-        int index = 0;
-
-        Position initPos = new Position(headerSize, 0);
-        stack.push(initPos);
-
-        while (!stack.empty()) {
-            Position p = stack.peek();
-
-            if (DBG) {
-                MakedictLog.d("read: address=" + p.mAddress + ", numOfCharGroup=" +
-                        p.mNumOfCharGroup + ", position=" + p.mPosition + ", length=" + p.mLength);
-            }
-
-            if (buffer.position() != p.mAddress) buffer.position(p.mAddress);
-            if (index != p.mLength) index = p.mLength;
-
-            if (p.mNumOfCharGroup == Position.NOT_READ_GROUPCOUNT) {
-                p.mNumOfCharGroup = readCharGroupCount(buffer);
-                p.mAddress += getGroupCountSize(p.mNumOfCharGroup);
-                p.mPosition = 0;
-            }
-
-            CharGroupInfo info = readCharGroup(buffer, p.mAddress - headerSize, formatOptions);
-            for (int i = 0; i < info.mCharacters.length; ++i) {
-                pushedChars[index++] = info.mCharacters[i];
-            }
-            p.mPosition++;
-
-            if (info.mFrequency != FusionDictionary.CharGroup.NOT_A_TERMINAL) { // found word
-                words.put(info.mOriginalAddress, new String(pushedChars, 0, index));
-                frequencies.put(info.mOriginalAddress, info.mFrequency);
-                if (info.mBigrams != null) bigrams.put(info.mOriginalAddress, info.mBigrams);
-            }
-
-            if (p.mPosition == p.mNumOfCharGroup) {
-                stack.pop();
-            } else {
-                // the node has more groups.
-                p.mAddress = buffer.position();
-            }
-
-            if (hasChildrenAddress(info.mChildrenAddress)) {
-                Position childrenPos = new Position(info.mChildrenAddress + headerSize, index);
-                stack.push(childrenPos);
-            }
-        }
-    }
-
-    /**
-     * Reads unigrams and bigrams from the binary file.
-     * Doesn't make the memory representation of the dictionary.
-     *
-     * @param buffer the buffer to read.
-     * @param words the map to store the address as a key and the word as a value.
-     * @param frequencies the map to store the address as a key and the frequency as a value.
-     * @param bigrams the map to store the address as a key and the list of address as a value.
-     * @throws IOException
-     * @throws UnsupportedFormatException
-     */
-    public static void readUnigramsAndBigramsBinary(final FusionDictionaryBufferInterface buffer,
-            final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
-            final Map<Integer, ArrayList<PendingAttribute>> bigrams) throws IOException,
-            UnsupportedFormatException {
-        // Read header
-        final FileHeader header = readHeader(buffer);
-        readUnigramsAndBigramsBinaryInner(buffer, header.mHeaderSize, words, frequencies, bigrams,
-                header.mFormatOptions);
-    }
-
     /**
      * Helper function to get the binary format version from the header.
      * @throws IOException
@@ -1501,7 +1445,7 @@
      * @throws IOException
      * @throws UnsupportedFormatException
      */
-    private static FileHeader readHeader(final FusionDictionaryBufferInterface buffer)
+    public static FileHeader readHeader(final FusionDictionaryBufferInterface buffer)
             throws IOException, UnsupportedFormatException {
         final int version = checkFormatVersion(buffer);
         final int optionsFlags = buffer.readUnsignedShort();
@@ -1525,7 +1469,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 +1488,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..adc6037 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
@@ -195,22 +207,39 @@
     static final int MAX_TERMINAL_FREQUENCY = 255;
     static final int MAX_BIGRAM_FREQUENCY = 15;
 
+    // This option needs to be the same numeric value as the one in binary_format.h.
+    static final int NOT_VALID_WORD = -99;
+
     /**
      * Options about file format.
      */
     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/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index f4784ff..d9b622a 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -16,6 +16,9 @@
 
 package com.android.inputmethod.latin.spellcheck;
 
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.provider.UserDictionary.Words;
 import android.service.textservice.SpellCheckerService.Session;
 import android.text.TextUtils;
 import android.util.Log;
@@ -45,6 +48,7 @@
     private int mScript; // One of SCRIPT_LATIN or SCRIPT_CYRILLIC for now.
     private final AndroidSpellCheckerService mService;
     protected final SuggestionsCache mSuggestionsCache = new SuggestionsCache();
+    private final ContentObserver mObserver;
 
     private static class SuggestionsParams {
         public final String[] mSuggestions;
@@ -83,10 +87,23 @@
             mUnigramSuggestionsInfoCache.put(
                     generateKey(query, prevWord), new SuggestionsParams(suggestions, flags));
         }
+
+        public void clearCache() {
+            mUnigramSuggestionsInfoCache.evictAll();
+        }
     }
 
     AndroidWordLevelSpellCheckerSession(final AndroidSpellCheckerService service) {
         mService = service;
+        final ContentResolver cres = service.getContentResolver();
+
+        mObserver = new ContentObserver(null) {
+            @Override
+            public void onChange(boolean self) {
+                mSuggestionsCache.clearCache();
+            }
+        };
+        cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
     }
 
     @Override
@@ -97,6 +114,12 @@
         mScript = AndroidSpellCheckerService.getScriptFromLocale(mLocale);
     }
 
+    @Override
+    public void onClose() {
+        final ContentResolver cres = mService.getContentResolver();
+        cres.unregisterContentObserver(mObserver);
+    }
+
     /*
      * Returns whether the code point is a letter that makes sense for the specified
      * locale for this spell checker.
diff --git a/native/jni/src/geometry_utils.h b/native/jni/src/geometry_utils.h
index bad5eda..3892b46 100644
--- a/native/jni/src/geometry_utils.h
+++ b/native/jni/src/geometry_utils.h
@@ -64,17 +64,8 @@
     return diff;
 }
 
-// static float pointToLineSegSquaredDistanceFloat(
-//         float x, float y, float x1, float y1, float x2, float y2) {
-//     float A = x - x1;
-//     float B = y - y1;
-//     float C = x2 - x1;
-//     float D = y2 - y1;
-//     return fabsf(A * D - C * B) / sqrtf(C * C + D * D);
-// }
-
 static inline float pointToLineSegSquaredDistanceFloat(
-        float x, float y, float x1, float y1, float x2, float y2) {
+        float x, float y, float x1, float y1, float x2, float y2, bool extend) {
     const float ray1x = x - x1;
     const float ray1y = y - y1;
     const float ray2x = x2 - x1;
@@ -86,10 +77,10 @@
 
     float projectionX;
     float projectionY;
-    if (projectionLengthSqr < 0.0f) {
+    if (!extend && projectionLengthSqr < 0.0f) {
         projectionX = x1;
         projectionY = y1;
-    } else if (projectionLengthSqr > 1.0f) {
+    } else if (!extend && projectionLengthSqr > 1.0f) {
         projectionX = x2;
         projectionY = y2;
     } else {
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index 9b18918..c5f2884 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -409,11 +409,11 @@
                 }
                 NearKeysDistanceMap::const_iterator itPP =
                         prevNearKeysDistances->find(minChar);
-                if (DEBUG_GEO_FULL) {
-                    AKLOGI("p1: char = %c, minDist = %f, prevNear key minDist = %f",
-                            minChar, itPP->second, minDist);
-                }
                 if (itPP != prevNearKeysDistances->end() && minDist > itPP->second) {
+                    if (DEBUG_GEO_FULL) {
+                        AKLOGI("p1: char = %c, minDist = %f, prevNear key minDist = %f",
+                                minChar, itPP->second, minDist);
+                    }
                     return popped;
                 }
             }
@@ -464,8 +464,8 @@
 }
 
 int ProximityInfoState::getDuration(const int index) const {
-    if (mInputSize > 0 && index > 0 && index < mInputSize - 1) {
-        return mTimes[index + 1] - mTimes[index - 1];
+    if (mInputSize > 0 && index >= 0 && index < mInputSize - 1) {
+        return mTimes[index + 1] - mTimes[index];
     }
     return 0;
 }
@@ -504,7 +504,7 @@
     if (index >= mInputXs.size()) {
         return filterSize;
     }
-    int i = filterSize;
+    int newFilterSize = filterSize;
     for (int j = 0; j < mProximityInfo->getKeyCount(); ++j) {
         if (mNearKeysVector[index].test(j)) {
             const int32_t keyCodePoint = mProximityInfo->getCodePointOf(j);
@@ -517,11 +517,11 @@
                 }
             }
             if (insert) {
-                filter[i++] = keyCodePoint;
+                filter[newFilterSize++] = keyCodePoint;
             }
         }
     }
-    return i;
+    return newFilterSize;
 }
 
 float ProximityInfoState::getAveragePointDuration() const {
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..24776d5 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
@@ -19,7 +19,7 @@
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.UserHistoryDictIOUtils;
 import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
-import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
 import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
 import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
@@ -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);
@@ -385,7 +391,7 @@
         assertNotNull("Can't get buffer.", buffer);
         try {
             now = System.currentTimeMillis();
-            BinaryDictInputOutput.readUnigramsAndBigramsBinary(buffer, resultWords, resultFreqs,
+            BinaryDictIOUtils.readUnigramsAndBigramsBinary(buffer, resultWords, resultFreqs,
                     resultBigrams);
             diff = System.currentTimeMillis() - now;
         } catch (IOException e) {
@@ -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,9 +469,99 @@
         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);
         }
     }
+
+    // Tests for getTerminalPosition
+    private String getWordFromBinary(final FusionDictionaryBufferInterface buffer,
+            final int address) {
+        if (buffer.position() != 0) buffer.position(0);
+
+        FileHeader header = null;
+        try {
+            header = BinaryDictInputOutput.readHeader(buffer);
+        } catch (IOException e) {
+            return null;
+        } catch (UnsupportedFormatException e) {
+            return null;
+        }
+        if (header == null) return null;
+        return BinaryDictInputOutput.getWordAtAddress(buffer, header.mHeaderSize,
+                address - header.mHeaderSize, header.mFormatOptions);
+    }
+
+    private long runGetTerminalPosition(final FusionDictionaryBufferInterface buffer,
+            final String word, int index, boolean contained) {
+        final int expectedFrequency = (UNIGRAM_FREQ + index) % 255;
+        long diff = -1;
+        int position = -1;
+        try {
+            final long now = System.nanoTime();
+            position = BinaryDictIOUtils.getTerminalPosition(buffer, word);
+            diff = System.nanoTime() - now;
+        } catch (IOException e) {
+            Log.e(TAG, "IOException while getTerminalPosition: " + e);
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "UnsupportedFormatException while getTermianlPosition: " + e);
+        }
+
+        assertEquals(FormatSpec.NOT_VALID_WORD != position, contained);
+        if (contained) assertEquals(getWordFromBinary(buffer, position), word);
+        return diff;
+    }
+
+    public void testGetTerminalPosition() {
+        File file = null;
+        try {
+            file = File.createTempFile("runReadUnigrams", ".dict");
+        } catch (IOException e) {
+            // do nothing
+        }
+        assertNotNull(file);
+
+        final FusionDictionary dict = new FusionDictionary(new Node(),
+                new FusionDictionary.DictionaryOptions(
+                        new HashMap<String, String>(), false, false));
+        addUnigrams(sWords.size(), dict, sWords, null /* shortcutMap */);
+        timeWritingDictToFile(file, dict, VERSION3_WITH_LINKEDLIST_NODE);
+
+        final FusionDictionaryBufferInterface buffer = getBuffer(file, USE_BYTE_ARRAY);
+
+        try {
+            // too long word
+            final String longWord = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
+            assertEquals(FormatSpec.NOT_VALID_WORD,
+                    BinaryDictIOUtils.getTerminalPosition(buffer, longWord));
+
+            // null
+            assertEquals(FormatSpec.NOT_VALID_WORD,
+                    BinaryDictIOUtils.getTerminalPosition(buffer, null));
+
+            // empty string
+            assertEquals(FormatSpec.NOT_VALID_WORD,
+                    BinaryDictIOUtils.getTerminalPosition(buffer, ""));
+        } catch (IOException e) {
+        } catch (UnsupportedFormatException e) {
+        }
+
+        // Test a word that is contained within the dictionary.
+        long sum = 0;
+        for (int i = 0; i < sWords.size(); ++i) {
+            final long time = runGetTerminalPosition(buffer, sWords.get(i), i, true);
+            sum += time == -1 ? 0 : time;
+        }
+        Log.d(TAG, "per a search : " + (((double)sum) / sWords.size() / 1000000));
+
+        // Test a word that isn't contained within the dictionary.
+        final Random random = new Random((int)System.currentTimeMillis());
+        for (int i = 0; i < 1000; ++i) {
+            final String word = generateWord(random.nextInt());
+            if (sWords.indexOf(word) != -1) continue;
+            runGetTerminalPosition(buffer, word, i, false);
+        }
+    }
 }