merge in jb-mr1-release history after reset to jb-mr1-dev
diff --git a/java/res/values-sw600dp/touch-position-correction.xml b/java/res/values-sw600dp/touch-position-correction.xml
new file mode 100644
index 0000000..f77d3ae
--- /dev/null
+++ b/java/res/values-sw600dp/touch-position-correction.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Note that correctionX is obsolete (See com.android.inputmethod.keyboard.internal.TouchPositionCorrection)
+        An entry of the touch_position_correction word should be:
+        1. correctionX: (touch_center_x - hitbox_center_x) / hitbox_width
+        2. correctionY: (touch_center_y - hitbox_center_y) / hitbox_height
+        3. correctionR: sweet_spot_radius / sqrt(hitbox_width^2 + hitbox_height^2)
+    -->
+
+    <string-array
+        name="touch_position_correction_data_default"
+        translatable="false"
+    >
+        <!-- The default touch position data (See com.android.inputmethod.keyboard.ProximityInfo)
+             correctionX = 0.0f
+             correctionY = 0.0f
+             correctionR = DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS
+        -->
+    </string-array>
+
+    <string-array
+        name="touch_position_correction_data_gingerbread"
+        translatable="false"
+    >
+        <!-- The default touch position data (See com.android.inputmethod.keyboard.ProximityInfo)
+             correctionX = 0.0f
+             correctionY = 0.0f
+             correctionR = DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS
+        -->
+    </string-array>
+
+    <string-array
+        name="touch_position_correction_data_ice_cream_sandwich"
+        translatable="false"
+    >
+        <!-- The default touch position data (See com.android.inputmethod.keyboard.ProximityInfo)
+             correctionX = 0.0f
+             correctionY = 0.0f
+             correctionR = DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS
+        -->
+    </string-array>
+</resources>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index d520392..928c5f5 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -19,7 +19,7 @@
     <style name="Keyboard">
         <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
         <item name="themeId">0</item>
-        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_empty</item>
+        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_default</item>
         <item name="rowHeight">25%p</item>
         <item name="keyboardHeight">@dimen/keyboardHeight</item>
         <item name="maxKeyboardHeight">@fraction/maxKeyboardHeight</item>
diff --git a/java/res/values/touch-position-correction.xml b/java/res/values/touch-position-correction.xml
index 41b435a..7df86f4 100644
--- a/java/res/values/touch-position-correction.xml
+++ b/java/res/values/touch-position-correction.xml
@@ -18,18 +18,22 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!--
+    <!-- Note that correctionX is obsolete (See com.android.inputmethod.keyboard.internal.TouchPositionCorrection)
         An entry of the touch_position_correction word should be:
-        1. (float) (touch_center_x - key_center_x) / key_width
-        2. (float) (touch_center_y - key_center_y) / key_height
-        3. (float) sweet_spot_radius / (key_width^2 + key_height^2)
-     -->
+        1. correctionX: (touch_center_x - hitbox_center_x) / hitbox_width
+        2. correctionY: (touch_center_y - hitbox_center_y) / hitbox_height
+        3. correctionR: sweet_spot_radius / sqrt(hitbox_width^2 + hitbox_height^2)
+    -->
 
     <string-array
-        name="touch_position_correction_data_empty"
+        name="touch_position_correction_data_default"
         translatable="false"
     >
-        <!-- empty -->
+        <!-- The default touch position data (See com.android.inputmethod.keyboard.ProximityInfo)
+             correctionX = 0.0f
+             correctionY = 0.0f
+             correctionR = DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS
+        -->
     </string-array>
 
     <string-array
diff --git a/java/res/xml-land/kbd_number.xml b/java/res/xml-land/kbd_number.xml
index 7cc0fb2..8d31df1 100644
--- a/java/res/xml-land/kbd_number.xml
+++ b/java/res/xml-land/kbd_number.xml
@@ -22,6 +22,7 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyboardHorizontalEdgesPadding="10%p"
     latin:keyWidth="26.67%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_number" />
diff --git a/java/res/xml-land/kbd_phone.xml b/java/res/xml-land/kbd_phone.xml
index aa54b83..2f8fc35 100644
--- a/java/res/xml-land/kbd_phone.xml
+++ b/java/res/xml-land/kbd_phone.xml
@@ -22,6 +22,7 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyboardHorizontalEdgesPadding="10%p"
     latin:keyWidth="26.67%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_phone" />
diff --git a/java/res/xml-land/kbd_phone_symbols.xml b/java/res/xml-land/kbd_phone_symbols.xml
index 41ba6cf..0e6bcdd 100644
--- a/java/res/xml-land/kbd_phone_symbols.xml
+++ b/java/res/xml-land/kbd_phone_symbols.xml
@@ -22,6 +22,7 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyboardHorizontalEdgesPadding="10%p"
     latin:keyWidth="26.67%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_phone_symbols" />
diff --git a/java/res/xml-sw600dp-land/kbd_number.xml b/java/res/xml-sw600dp-land/kbd_number.xml
index cb86b3b..63dfc90 100644
--- a/java/res/xml-sw600dp-land/kbd_number.xml
+++ b/java/res/xml-sw600dp-land/kbd_number.xml
@@ -22,6 +22,7 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyboardHorizontalEdgesPadding="10%p"
     latin:keyWidth="18%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_number" />
diff --git a/java/res/xml-sw600dp-land/kbd_phone.xml b/java/res/xml-sw600dp-land/kbd_phone.xml
index 71c7c04..b616111 100644
--- a/java/res/xml-sw600dp-land/kbd_phone.xml
+++ b/java/res/xml-sw600dp-land/kbd_phone.xml
@@ -22,6 +22,7 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyboardHorizontalEdgesPadding="10%p"
     latin:keyWidth="18%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_phone" />
diff --git a/java/res/xml-sw600dp-land/kbd_phone_symbols.xml b/java/res/xml-sw600dp-land/kbd_phone_symbols.xml
index 39bdae3..9b0bee0 100644
--- a/java/res/xml-sw600dp-land/kbd_phone_symbols.xml
+++ b/java/res/xml-sw600dp-land/kbd_phone_symbols.xml
@@ -22,6 +22,7 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyboardHorizontalEdgesPadding="10%p"
     latin:keyWidth="18%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <!-- Tablet doesn't have phone symbols keyboard -->
     <include
diff --git a/java/res/xml-sw600dp/kbd_10_10_7_symbols.xml b/java/res/xml-sw600dp/kbd_10_10_7_symbols.xml
index 66254de..dd545b5 100644
--- a/java/res/xml-sw600dp/kbd_10_10_7_symbols.xml
+++ b/java/res/xml-sw600dp/kbd_10_10_7_symbols.xml
@@ -20,6 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_10_10_7_symbols" />
diff --git a/java/res/xml-sw600dp/kbd_10_10_7_symbols_shift.xml b/java/res/xml-sw600dp/kbd_10_10_7_symbols_shift.xml
index 3c5ed5e..c36f009 100644
--- a/java/res/xml-sw600dp/kbd_10_10_7_symbols_shift.xml
+++ b/java/res/xml-sw600dp/kbd_10_10_7_symbols_shift.xml
@@ -20,6 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_10_10_7_symbols_shift" />
diff --git a/java/res/xml-sw600dp/kbd_number.xml b/java/res/xml-sw600dp/kbd_number.xml
index 4a8b08c..71d6622 100644
--- a/java/res/xml-sw600dp/kbd_number.xml
+++ b/java/res/xml-sw600dp/kbd_number.xml
@@ -21,6 +21,7 @@
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="18%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_number" />
diff --git a/java/res/xml-sw600dp/kbd_phone.xml b/java/res/xml-sw600dp/kbd_phone.xml
index f63f1c6..5fdbea2 100644
--- a/java/res/xml-sw600dp/kbd_phone.xml
+++ b/java/res/xml-sw600dp/kbd_phone.xml
@@ -21,6 +21,7 @@
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="18%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_phone" />
diff --git a/java/res/xml-sw600dp/kbd_phone_symbols.xml b/java/res/xml-sw600dp/kbd_phone_symbols.xml
index a0f55b7..ce24d2b 100644
--- a/java/res/xml-sw600dp/kbd_phone_symbols.xml
+++ b/java/res/xml-sw600dp/kbd_phone_symbols.xml
@@ -21,6 +21,7 @@
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="18%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <!-- Tablet doesn't have phone symbols keyboard -->
     <include
diff --git a/java/res/xml-sw768dp-land/kbd_number.xml b/java/res/xml-sw768dp-land/kbd_number.xml
index 3ad25a3..de8d559 100644
--- a/java/res/xml-sw768dp-land/kbd_number.xml
+++ b/java/res/xml-sw768dp-land/kbd_number.xml
@@ -22,6 +22,7 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyboardHorizontalEdgesPadding="10%p"
     latin:keyWidth="13.250%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_number" />
diff --git a/java/res/xml-sw768dp-land/kbd_phone.xml b/java/res/xml-sw768dp-land/kbd_phone.xml
index abe7e7c..f88a076 100644
--- a/java/res/xml-sw768dp-land/kbd_phone.xml
+++ b/java/res/xml-sw768dp-land/kbd_phone.xml
@@ -22,6 +22,7 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyboardHorizontalEdgesPadding="10%p"
     latin:keyWidth="13.250%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_phone" />
diff --git a/java/res/xml-sw768dp-land/kbd_phone_symbols.xml b/java/res/xml-sw768dp-land/kbd_phone_symbols.xml
index 641464d..eaa413e 100644
--- a/java/res/xml-sw768dp-land/kbd_phone_symbols.xml
+++ b/java/res/xml-sw768dp-land/kbd_phone_symbols.xml
@@ -22,6 +22,7 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyboardHorizontalEdgesPadding="10%p"
     latin:keyWidth="13.250%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <!-- Tablet doesn't have phone symbols keyboard -->
     <include
diff --git a/java/res/xml-sw768dp/kbd_number.xml b/java/res/xml-sw768dp/kbd_number.xml
index b20123c..1b46edd 100644
--- a/java/res/xml-sw768dp/kbd_number.xml
+++ b/java/res/xml-sw768dp/kbd_number.xml
@@ -21,6 +21,7 @@
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="13.250%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_number" />
diff --git a/java/res/xml-sw768dp/kbd_phone.xml b/java/res/xml-sw768dp/kbd_phone.xml
index fa9bf1b..947ede0 100644
--- a/java/res/xml-sw768dp/kbd_phone.xml
+++ b/java/res/xml-sw768dp/kbd_phone.xml
@@ -21,6 +21,7 @@
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="13.250%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_phone" />
diff --git a/java/res/xml-sw768dp/kbd_phone_symbols.xml b/java/res/xml-sw768dp/kbd_phone_symbols.xml
index e1a359e..dd9a6ae 100644
--- a/java/res/xml-sw768dp/kbd_phone_symbols.xml
+++ b/java/res/xml-sw768dp/kbd_phone_symbols.xml
@@ -21,6 +21,7 @@
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="13.250%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <!-- Tablet doesn't have phone symbols keyboard -->
     <include
diff --git a/java/res/xml-sw768dp/kbd_thai_symbols.xml b/java/res/xml-sw768dp/kbd_thai_symbols.xml
index 0cd9a61..5ddf574 100644
--- a/java/res/xml-sw768dp/kbd_thai_symbols.xml
+++ b/java/res/xml-sw768dp/kbd_thai_symbols.xml
@@ -24,7 +24,7 @@
     latin:verticalGap="@fraction/key_bottom_gap_5row"
     latin:keyLetterSize="@fraction/key_letter_ratio_5row"
     latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5row"
-    latin:touchPositionCorrectionData="@null"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_thai_symbols" />
diff --git a/java/res/xml-sw768dp/kbd_thai_symbols_shift.xml b/java/res/xml-sw768dp/kbd_thai_symbols_shift.xml
index a68fec4..135222b 100644
--- a/java/res/xml-sw768dp/kbd_thai_symbols_shift.xml
+++ b/java/res/xml-sw768dp/kbd_thai_symbols_shift.xml
@@ -24,7 +24,7 @@
     latin:verticalGap="@fraction/key_bottom_gap_5row"
     latin:keyLetterSize="@fraction/key_letter_ratio_5row"
     latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5row"
-    latin:touchPositionCorrectionData="@null"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_thai_symbols_shift" />
diff --git a/java/res/xml/kbd_10_10_7_symbols.xml b/java/res/xml/kbd_10_10_7_symbols.xml
index 7e075df..4d9861b 100644
--- a/java/res/xml/kbd_10_10_7_symbols.xml
+++ b/java/res/xml/kbd_10_10_7_symbols.xml
@@ -20,6 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_symbols" />
diff --git a/java/res/xml/kbd_10_10_7_symbols_shift.xml b/java/res/xml/kbd_10_10_7_symbols_shift.xml
index 25db3c8..a2d67ca 100644
--- a/java/res/xml/kbd_10_10_7_symbols_shift.xml
+++ b/java/res/xml/kbd_10_10_7_symbols_shift.xml
@@ -20,6 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_symbols_shift" />
diff --git a/java/res/xml/kbd_number.xml b/java/res/xml/kbd_number.xml
index 8b0deea..aa8872f 100644
--- a/java/res/xml/kbd_number.xml
+++ b/java/res/xml/kbd_number.xml
@@ -21,6 +21,7 @@
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="26.67%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_number" />
diff --git a/java/res/xml/kbd_pcqwerty.xml b/java/res/xml/kbd_pcqwerty.xml
index 777c71a..5155bc5 100644
--- a/java/res/xml/kbd_pcqwerty.xml
+++ b/java/res/xml/kbd_pcqwerty.xml
@@ -24,7 +24,7 @@
     latin:verticalGap="@fraction/key_bottom_gap_5row"
     latin:keyLetterSize="@fraction/key_letter_ratio_5row"
     latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5row"
-    latin:touchPositionCorrectionData="@null"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_pcqwerty" />
diff --git a/java/res/xml/kbd_pcqwerty_symbols.xml b/java/res/xml/kbd_pcqwerty_symbols.xml
index a2297f7..bfb39e8 100644
--- a/java/res/xml/kbd_pcqwerty_symbols.xml
+++ b/java/res/xml/kbd_pcqwerty_symbols.xml
@@ -24,7 +24,7 @@
     latin:verticalGap="@fraction/key_bottom_gap_5row"
     latin:keyLetterSize="@fraction/key_letter_ratio_5row"
     latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5row"
-    latin:touchPositionCorrectionData="@null"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_pcqwerty_symbols" />
diff --git a/java/res/xml/kbd_phone.xml b/java/res/xml/kbd_phone.xml
index 91637b6..dab3d49 100644
--- a/java/res/xml/kbd_phone.xml
+++ b/java/res/xml/kbd_phone.xml
@@ -21,6 +21,7 @@
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="26.67%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_phone" />
diff --git a/java/res/xml/kbd_phone_symbols.xml b/java/res/xml/kbd_phone_symbols.xml
index 7f59a85..ba4e464 100644
--- a/java/res/xml/kbd_phone_symbols.xml
+++ b/java/res/xml/kbd_phone_symbols.xml
@@ -21,6 +21,7 @@
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     latin:keyWidth="26.67%p"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_phone_symbols" />
diff --git a/java/res/xml/kbd_symbols.xml b/java/res/xml/kbd_symbols.xml
index f6612a2..47e08d5 100644
--- a/java/res/xml/kbd_symbols.xml
+++ b/java/res/xml/kbd_symbols.xml
@@ -20,6 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_symbols" />
diff --git a/java/res/xml/kbd_symbols_shift.xml b/java/res/xml/kbd_symbols_shift.xml
index 41a5571..932ec01 100644
--- a/java/res/xml/kbd_symbols_shift.xml
+++ b/java/res/xml/kbd_symbols_shift.xml
@@ -20,6 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_symbols_shift" />
diff --git a/java/res/xml/kbd_thai.xml b/java/res/xml/kbd_thai.xml
index b4a4a0b..294bffb 100644
--- a/java/res/xml/kbd_thai.xml
+++ b/java/res/xml/kbd_thai.xml
@@ -24,7 +24,7 @@
     latin:verticalGap="@fraction/key_bottom_gap_5row"
     latin:keyLetterSize="@fraction/key_letter_ratio_5row"
     latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5row"
-    latin:touchPositionCorrectionData="@null"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_thai" />
diff --git a/java/res/xml/kbd_thai_symbols.xml b/java/res/xml/kbd_thai_symbols.xml
index 7e075df..4d9861b 100644
--- a/java/res/xml/kbd_thai_symbols.xml
+++ b/java/res/xml/kbd_thai_symbols.xml
@@ -20,6 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_symbols" />
diff --git a/java/res/xml/kbd_thai_symbols_shift.xml b/java/res/xml/kbd_thai_symbols_shift.xml
index 25db3c8..a2d67ca 100644
--- a/java/res/xml/kbd_thai_symbols_shift.xml
+++ b/java/res/xml/kbd_thai_symbols_shift.xml
@@ -20,6 +20,7 @@
 
 <Keyboard
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:touchPositionCorrectionData="@array/touch_position_correction_data_default"
 >
     <include
         latin:keyboardLayout="@xml/rows_symbols_shift" />
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index 5af5d04..b9b6362 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -47,7 +47,7 @@
  * virtual views, thus conveying their logical structure.
  * </p>
  */
-public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat {
+public final class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat {
     private static final String TAG = AccessibilityEntityProvider.class.getSimpleName();
     private static final int UNDEFINED = Integer.MIN_VALUE;
 
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index 01220a5..fcfa6d4 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -32,7 +32,7 @@
 import com.android.inputmethod.keyboard.PointerTracker;
 import com.android.inputmethod.latin.R;
 
-public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
+public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
     private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
 
     private InputMethodService mInputMethod;
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 5c45448..32618ad 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -30,7 +30,7 @@
 
 import java.util.HashMap;
 
-public class KeyCodeDescriptionMapper {
+public final class KeyCodeDescriptionMapper {
     private static final String TAG = KeyCodeDescriptionMapper.class.getSimpleName();
 
     // The resource ID of the string spoken for obscured keys
diff --git a/java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java
index b6c3e2a..40eed91 100644
--- a/java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java
@@ -20,7 +20,7 @@
 
 import java.lang.reflect.Method;
 
-public class AudioManagerCompatWrapper {
+public final class AudioManagerCompatWrapper {
     private static final Method METHOD_isWiredHeadsetOn = CompatUtils.getMethod(
             AudioManager.class, "isWiredHeadsetOn");
     private static final Method METHOD_isBluetoothA2dpOn = CompatUtils.getMethod(
diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
index cc10a4e..a01c301 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
@@ -27,7 +27,7 @@
 
 // TODO: Override this class with the concrete implementation if we need to take care of the
 // performance.
-public class InputMethodManagerCompatWrapper {
+public final class InputMethodManagerCompatWrapper {
     private static final String TAG = InputMethodManagerCompatWrapper.class.getSimpleName();
     private static final Method METHOD_switchToNextInputMethod = CompatUtils.getMethod(
             InputMethodManager.class, "switchToNextInputMethod", IBinder.class, Boolean.TYPE);
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index cb120a3..30812e8 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -138,7 +138,7 @@
 
     private final OptionalAttributes mOptionalAttributes;
 
-    private static class OptionalAttributes {
+    private static final class OptionalAttributes {
         /** Text to output when pressed. This can be multiple characters, like ".com" */
         public final String mOutputText;
         public final int mAltCode;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 1e52773..5e8a8f6 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -33,7 +33,7 @@
 /**
  * Unique identifier for each keyboard type.
  */
-public class KeyboardId {
+public final class KeyboardId {
     public static final int MODE_TEXT = 0;
     public static final int MODE_URL = 1;
     public static final int MODE_EMAIL = 2;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index d97df74..c7813ab 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -61,7 +61,7 @@
  * A {@link KeyboardLayoutSet} needs to be created for each
  * {@link android.view.inputmethod.EditorInfo}.
  */
-public class KeyboardLayoutSet {
+public final class KeyboardLayoutSet {
     private static final String TAG = KeyboardLayoutSet.class.getSimpleName();
     private static final boolean DEBUG_CACHE = LatinImeLogger.sDBG;
 
@@ -77,7 +77,7 @@
             CollectionUtils.newHashMap();
     private static final KeysCache sKeysCache = new KeysCache();
 
-    public static class KeyboardLayoutSetException extends RuntimeException {
+    public static final class KeyboardLayoutSetException extends RuntimeException {
         public final KeyboardId mKeyboardId;
 
         public KeyboardLayoutSetException(final Throwable cause, final KeyboardId keyboardId) {
@@ -86,13 +86,13 @@
         }
     }
 
-    private static class ElementParams {
+    private static final class ElementParams {
         int mKeyboardXmlId;
         boolean mProximityCharsCorrectionEnabled;
         public ElementParams() {}
     }
 
-    private static class Params {
+    private static final class Params {
         String mKeyboardLayoutSetName;
         int mMode;
         EditorInfo mEditorInfo;
@@ -203,7 +203,7 @@
                 params.mLanguageSwitchKeyEnabled);
     }
 
-    public static class Builder {
+    public static final class Builder {
         private final Context mContext;
         private final String mPackageName;
         private final Resources mResources;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index fd789f0..88d7b66 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -39,12 +39,12 @@
 import com.android.inputmethod.latin.SubtypeSwitcher;
 import com.android.inputmethod.latin.WordComposer;
 
-public class KeyboardSwitcher implements KeyboardState.SwitchActions {
+public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
     private static final String TAG = KeyboardSwitcher.class.getSimpleName();
 
     public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
 
-    static class KeyboardTheme {
+    static final class KeyboardTheme {
         public final int mThemeId;
         public final int mStyleId;
 
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index f1fcfe7..b4c9c7d 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -83,7 +83,7 @@
  * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout
  * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint
  */
-public class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
+public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
         SuddenJumpingTouchEventHandler.ProcessMotionEvent {
     private static final String TAG = MainKeyboardView.class.getSimpleName();
 
@@ -136,7 +136,7 @@
 
     private final KeyTimerHandler mKeyTimerHandler;
 
-    private static class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView>
+    private static final class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView>
             implements TimerProxy {
         private static final int MSG_TYPING_STATE_EXPIRED = 0;
         private static final int MSG_REPEAT_KEY = 1;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
index cd4e300..a2001cb 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
@@ -16,7 +16,7 @@
 
 package com.android.inputmethod.keyboard;
 
-public class MoreKeysDetector extends KeyDetector {
+public final class MoreKeysDetector extends KeyDetector {
     private final int mSlideAllowanceSquare;
     private final int mSlideAllowanceSquareTop;
 
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index c9af888..d7d4be4 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -27,7 +27,7 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StringUtils;
 
-public class MoreKeysKeyboard extends Keyboard {
+public final class MoreKeysKeyboard extends Keyboard {
     private final int mDefaultKeyCoordX;
 
     MoreKeysKeyboard(final MoreKeysKeyboardParams params) {
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index e513a14..a506176 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -33,7 +33,7 @@
  * A view that renders a virtual {@link MoreKeysKeyboard}. It handles rendering of keys and
  * detecting key presses and touch movements.
  */
-public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel {
+public final class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel {
     private final int[] mCoordinates = new int[2];
 
     private final KeyDetector mKeyDetector;
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index ec8f659..9dae40a 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -34,7 +34,7 @@
 
 import java.util.ArrayList;
 
-public class PointerTracker implements PointerTrackerQueue.Element {
+public final class PointerTracker implements PointerTrackerQueue.Element {
     private static final String TAG = PointerTracker.class.getSimpleName();
     private static final boolean DEBUG_EVENT = false;
     private static final boolean DEBUG_MOVE_EVENT = false;
@@ -121,7 +121,7 @@
         }
     }
 
-    static class PointerTrackerParams {
+    static final class PointerTrackerParams {
         public final boolean mSlidingKeyInputEnabled;
         public final int mTouchNoiseThresholdTime;
         public final float mTouchNoiseThresholdDistance;
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 6e13894..06a9e92 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -25,15 +25,15 @@
 
 import java.util.Arrays;
 
-public class ProximityInfo {
+public final class ProximityInfo {
     /** MAX_PROXIMITY_CHARS_SIZE must be the same as MAX_PROXIMITY_CHARS_SIZE_INTERNAL
      * in defines.h */
     public static final int MAX_PROXIMITY_CHARS_SIZE = 16;
     /** Number of key widths from current touch point to search for nearest keys. */
     private static float SEARCH_DISTANCE = 1.2f;
     private static final Key[] EMPTY_KEY_ARRAY = new Key[0];
+    private static final float DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS = 0.15f;
 
-    private final int mKeyHeight;
     private final int mGridWidth;
     private final int mGridHeight;
     private final int mGridSize;
@@ -43,6 +43,7 @@
     private final int mKeyboardMinWidth;
     private final int mKeyboardHeight;
     private final int mMostCommonKeyWidth;
+    private final int mMostCommonKeyHeight;
     private final Key[] mKeys;
     private final Key[][] mGridNeighbors;
     private final String mLocaleStr;
@@ -63,7 +64,7 @@
         mCellHeight = (height + mGridHeight - 1) / mGridHeight;
         mKeyboardMinWidth = minWidth;
         mKeyboardHeight = height;
-        mKeyHeight = mostCommonKeyHeight;
+        mMostCommonKeyHeight = mostCommonKeyHeight;
         mMostCommonKeyWidth = mostCommonKeyWidth;
         mKeys = keys;
         mGridNeighbors = new Key[mGridSize][];
@@ -142,22 +143,22 @@
             sweetSpotCenterXs = new float[keyCount];
             sweetSpotCenterYs = new float[keyCount];
             sweetSpotRadii = new float[keyCount];
+            final float defaultRadius = DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS
+                    * (float)Math.hypot(mMostCommonKeyWidth, mMostCommonKeyHeight);
             for (int i = 0; i < keyCount; i++) {
                 final Key key = keys[i];
                 final Rect hitBox = key.mHitBox;
-                final int row = hitBox.top / mKeyHeight;
+                sweetSpotCenterXs[i] = hitBox.exactCenterX();
+                sweetSpotCenterYs[i] = hitBox.exactCenterY();
+                sweetSpotRadii[i] = defaultRadius;
+                final int row = hitBox.top / mMostCommonKeyHeight;
                 if (row < touchPositionCorrection.getRows()) {
                     final int hitBoxWidth = hitBox.width();
                     final int hitBoxHeight = hitBox.height();
-                    final float x = touchPositionCorrection.getX(row);
-                    final float y = touchPositionCorrection.getY(row);
-                    final float radius = touchPositionCorrection.getRadius(row);
-                    sweetSpotCenterXs[i] = hitBox.exactCenterX() + x * hitBoxWidth;
-                    sweetSpotCenterYs[i] = hitBox.exactCenterY() + y * hitBoxHeight;
-                    // Note that, in recent versions of Android, FloatMath is actually slower than
-                    // java.lang.Math due to the way the JIT optimizes java.lang.Math.
-                    sweetSpotRadii[i] = radius * (float)Math.sqrt(
-                            hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
+                    final float hitBoxDiagonal = (float)Math.hypot(hitBoxWidth, hitBoxHeight);
+                    sweetSpotCenterXs[i] += touchPositionCorrection.getX(row) * hitBoxWidth;
+                    sweetSpotCenterYs[i] += touchPositionCorrection.getY(row) * hitBoxHeight;
+                    sweetSpotRadii[i] = touchPositionCorrection.getRadius(row) * hitBoxDiagonal;
                 }
             }
         } else {
@@ -213,7 +214,8 @@
         }
     }
 
-    public void fillArrayWithNearestKeyCodes(int x, int y, int primaryKeyCode, int[] dest) {
+    public void fillArrayWithNearestKeyCodes(final int x, final int y, final int primaryKeyCode,
+            final int[] dest) {
         final int destLength = dest.length;
         if (destLength < 1) {
             return;
@@ -238,7 +240,7 @@
         }
     }
 
-    public Key[] getNearestKeys(int x, int y) {
+    public Key[] getNearestKeys(final int x, final int y) {
         if (mGridNeighbors == null) {
             return EMPTY_KEY_ARRAY;
         }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java b/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
index 5712df1..44aa72a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
@@ -18,7 +18,7 @@
 
 import android.util.Log;
 
-public class AlphabetShiftState {
+public final class AlphabetShiftState {
     private static final String TAG = AlphabetShiftState.class.getSimpleName();
     private static final boolean DEBUG = false;
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
index 3487b50..5b3f318 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
@@ -16,7 +16,7 @@
 
 import com.android.inputmethod.latin.ResizableIntArray;
 
-public class GestureStrokeWithPreviewPoints extends GestureStroke {
+public final class GestureStrokeWithPreviewPoints extends GestureStroke {
     public static final int PREVIEW_CAPACITY = 256;
 
     private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index 2a57caa..2caa5eb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -46,7 +46,7 @@
  * Note that the '\' is also parsed by XML parser and CSV parser as well.
  * See {@link KeyboardIconsSet} about icon_name.
  */
-public class KeySpecParser {
+public final class KeySpecParser {
     private static final boolean DEBUG = LatinImeLogger.sDBG;
 
     private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
@@ -318,7 +318,7 @@
     }
 
     @SuppressWarnings("serial")
-    public static class KeySpecParserError extends RuntimeException {
+    public static final class KeySpecParserError extends RuntimeException {
         public KeySpecParserError(final String message) {
             super(message);
         }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
index 71fd305..563d224 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
@@ -29,7 +29,7 @@
 
 import java.util.HashMap;
 
-public class KeyStylesSet {
+public final class KeyStylesSet {
     private static final String TAG = KeyStylesSet.class.getSimpleName();
     private static final boolean DEBUG = false;
 
@@ -45,7 +45,7 @@
         mStyles.put(EMPTY_STYLE_NAME, mEmptyKeyStyle);
     }
 
-    private static class EmptyKeyStyle extends KeyStyle {
+    private static final class EmptyKeyStyle extends KeyStyle {
         EmptyKeyStyle(final KeyboardTextsSet textsSet) {
             super(textsSet);
         }
@@ -71,7 +71,7 @@
         }
     }
 
-    private static class DeclaredKeyStyle extends KeyStyle {
+    private static final class DeclaredKeyStyle extends KeyStyle {
         private final HashMap<String, KeyStyle> mStyles;
         private final String mParentStyleName;
         private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
index 04cc152..6ddd2a6 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
@@ -23,7 +23,7 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.ResourceUtils;
 
-public class KeyVisualAttributes {
+public final class KeyVisualAttributes {
     public final Typeface mTypeface;
 
     public final float mLetterRatio;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index f7923d0..840d713 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -21,7 +21,7 @@
 
 import java.util.HashMap;
 
-public class KeyboardCodesSet {
+public final class KeyboardCodesSet {
     private static final HashMap<String, int[]> sLanguageToCodesMap = CollectionUtils.newHashMap();
     private static final HashMap<String, Integer> sNameToIdMap = CollectionUtils.newHashMap();
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 4a98a36..7292e8e 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -27,7 +27,7 @@
 
 import java.util.HashMap;
 
-public class KeyboardIconsSet {
+public final class KeyboardIconsSet {
     private static final String TAG = KeyboardIconsSet.class.getSimpleName();
 
     public static final int ICON_UNDEFINED = 0;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
index eb17b0e..b986262 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
@@ -32,7 +32,7 @@
  * Some of the key size defaults can be overridden per row from what the {@link Keyboard}
  * defines.
  */
-public class KeyboardRow {
+public final class KeyboardRow {
     // keyWidth enum constants
     private static final int KEYWIDTH_NOT_ENUM = 0;
     private static final int KEYWIDTH_FILL_RIGHT = -1;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 4ab6832..58cc897 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -34,7 +34,7 @@
  *
  * The actions are {@link SwitchActions}'s methods.
  */
-public class KeyboardState {
+public final class KeyboardState {
     private static final String TAG = KeyboardState.class.getSimpleName();
     private static final boolean DEBUG_EVENT = false;
     private static final boolean DEBUG_ACTION = false;
@@ -92,7 +92,7 @@
 
     private final SavedKeyboardState mSavedKeyboardState = new SavedKeyboardState();
 
-    static class SavedKeyboardState {
+    static final class SavedKeyboardState {
         public boolean mIsValid;
         public boolean mIsAlphabetMode;
         public boolean mIsAlphabetShiftLocked;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
index f54617c..d1b4c85 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
@@ -21,7 +21,7 @@
 
 import java.util.HashMap;
 
-public class KeysCache {
+public final class KeysCache {
     private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap();
 
     public void clear() {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index c1a5cbe..a52f202 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -22,7 +22,7 @@
 
 import java.util.ArrayList;
 
-public class PointerTrackerQueue {
+public final class PointerTrackerQueue {
     private static final String TAG = PointerTrackerQueue.class.getSimpleName();
     private static final boolean DEBUG = false;
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index 72be7fc..776ac02 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -39,7 +39,7 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
 
-public class PreviewPlacerView extends RelativeLayout {
+public final class PreviewPlacerView extends RelativeLayout {
     // The height of extra area above the keyboard to draw gesture trails.
     // Proportional to the keyboard height.
     private static final float EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO = 0.25f;
@@ -79,7 +79,7 @@
 
     private final DrawingHandler mDrawingHandler;
 
-    private static class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
+    private static final class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
         private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0;
         private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1;
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java b/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java
index edb40c8..90db73d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java
@@ -18,7 +18,7 @@
 
 import android.util.Log;
 
-/* package */ class ShiftKeyState extends ModifierKeyState {
+/* package */ final class ShiftKeyState extends ModifierKeyState {
     private static final int PRESSING_ON_SHIFTED = 3; // both temporary shifted & shift locked
     private static final int IGNORING = 4;
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java b/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java
index a591a7a..c53428f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java
@@ -28,7 +28,7 @@
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.ResearchLogger;
 
-public class SuddenJumpingTouchEventHandler {
+public final class SuddenJumpingTouchEventHandler {
     private static final String TAG = SuddenJumpingTouchEventHandler.class.getSimpleName();
     private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
index 811a620..d8950a7 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
@@ -18,7 +18,7 @@
 
 import com.android.inputmethod.latin.LatinImeLogger;
 
-public class TouchPositionCorrection {
+public final class TouchPositionCorrection {
     private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3;
 
     private boolean mEnabled;
@@ -80,7 +80,9 @@
     }
 
     public float getX(final int row) {
-        return mXs[row];
+        return 0.0f;
+        // Touch position correction data for X coordinate is obsolete.
+        // return mXs[row];
     }
 
     public float getY(final int row) {
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
index ae51d25..d126077 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
@@ -49,7 +49,7 @@
 import java.util.ArrayList;
 import java.util.TreeSet;
 
-public class AdditionalSubtypeSettings extends PreferenceFragment {
+public final class AdditionalSubtypeSettings extends PreferenceFragment {
     private SharedPreferences mPrefs;
     private SubtypeLocaleAdapter mSubtypeLocaleAdapter;
     private KeyboardLayoutSetAdapter mKeyboardLayoutSetAdapter;
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
index 3549a15..29c733b 100644
--- a/java/src/com/android/inputmethod/latin/AssetFileAddress.java
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -24,7 +24,7 @@
  * the package file. Open it correctly thus requires the name of the package it is in, but
  * also the offset in the file and the length of this data. This class encapsulates these three.
  */
-class AssetFileAddress {
+final class AssetFileAddress {
     public final String mFilename;
     public final long mOffset;
     public final long mLength;
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index 55664d4..59ef5e0 100644
--- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -30,7 +30,7 @@
  * It offers a consistent and simple interface that allows LatinIME to forget about the
  * complexity of settings and the like.
  */
-public class AudioAndHapticFeedbackManager {
+public final class AudioAndHapticFeedbackManager {
     final private SettingsValues mSettingsValues;
     final private AudioManager mAudioManager;
     final private VibratorUtils mVibratorUtils;
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index f425e36..84fad15 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -23,7 +23,7 @@
 
 import java.util.concurrent.ConcurrentHashMap;
 
-public class AutoCorrection {
+public final class AutoCorrection {
     private static final boolean DBG = LatinImeLogger.sDBG;
     private static final String TAG = AutoCorrection.class.getSimpleName();
     private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
diff --git a/java/src/com/android/inputmethod/latin/BackupAgent.java b/java/src/com/android/inputmethod/latin/BackupAgent.java
index ee070af..0beb088 100644
--- a/java/src/com/android/inputmethod/latin/BackupAgent.java
+++ b/java/src/com/android/inputmethod/latin/BackupAgent.java
@@ -22,7 +22,7 @@
 /**
  * Backs up the Latin IME shared preferences.
  */
-public class BackupAgent extends BackupAgentHelper {
+public final class BackupAgent extends BackupAgentHelper {
 
     @Override
     public void onCreate() {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 9244f16..e084cb3 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -30,7 +30,7 @@
 /**
  * Implements a static, compacted, binary dictionary of standard words.
  */
-public class BinaryDictionary extends Dictionary {
+public final class BinaryDictionary extends Dictionary {
 
     public static final String DICTIONARY_PACK_AUTHORITY =
             "com.android.inputmethod.latin.dictionarypack";
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 799aea8..0b11594 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -30,7 +30,6 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -40,7 +39,7 @@
  * Group class for static methods to help with creation and getting of the binary dictionary
  * file from the dictionary provider
  */
-public class BinaryDictionaryFileDumper {
+public final class BinaryDictionaryFileDumper {
     private static final String TAG = BinaryDictionaryFileDumper.class.getSimpleName();
     private static final boolean DEBUG = false;
 
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 9764df0..fa9f79e 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -37,7 +37,7 @@
 /**
  * Helper class to get the address of a mmap'able dictionary file.
  */
-class BinaryDictionaryGetter {
+final class BinaryDictionaryGetter {
 
     /**
      * Used for Log actions from this class
@@ -178,7 +178,7 @@
                 context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength());
     }
 
-    static private class DictPackSettings {
+    private static final class DictPackSettings {
         final SharedPreferences mDictPreferences;
         public DictPackSettings(final Context context) {
             Context dictPackContext = null;
@@ -237,7 +237,7 @@
     /**
      * Utility class for the {@link #getCachedWordLists} method
      */
-    private static class FileAndMatchLevel {
+    private static final class FileAndMatchLevel {
         final File mFile;
         final int mMatchLevel;
         public FileAndMatchLevel(final File file, final int matchLevel) {
diff --git a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java b/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
index cf97761..7f7ff31 100644
--- a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
+++ b/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
@@ -25,7 +25,7 @@
 /**
  * A TreeSet that is bounded in size and throws everything that's smaller than its limit
  */
-public class BoundedTreeSet extends TreeSet<SuggestedWordInfo> {
+public final class BoundedTreeSet extends TreeSet<SuggestedWordInfo> {
     private final int mCapacity;
     public BoundedTreeSet(final Comparator<SuggestedWordInfo> comparator, final int capacity) {
         super(comparator);
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 1ea14da..3af3cab 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -30,7 +30,7 @@
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.research.ResearchLogger;
 
-public class DebugSettings extends PreferenceFragment
+public final class DebugSettings extends PreferenceFragment
         implements SharedPreferences.OnSharedPreferenceChangeListener {
 
     private static final String TAG = DebugSettings.class.getSimpleName();
diff --git a/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java b/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
index cde2060..6ef19ee 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
@@ -20,7 +20,7 @@
 import android.os.Bundle;
 import android.preference.PreferenceActivity;
 
-public class DebugSettingsActivity extends PreferenceActivity {
+public final class DebugSettingsActivity extends PreferenceActivity {
     @Override
     public Intent getIntent() {
         final Intent modIntent = new Intent(super.getIntent());
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index 359da72..ce1b646 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -18,7 +18,7 @@
 
 import java.util.Locale;
 
-public class DicTraverseSession {
+public final class DicTraverseSession {
     static {
         JniUtils.loadNativeLibrary();
     }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 4acab6b..d3b1209 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -29,7 +29,7 @@
 /**
  * Class for a collection of dictionaries that behave like one dictionary.
  */
-public class DictionaryCollection extends Dictionary {
+public final class DictionaryCollection extends Dictionary {
     private final String TAG = DictionaryCollection.class.getSimpleName();
     protected final CopyOnWriteArrayList<Dictionary> mDictionaries;
 
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index cdd01d0..f381973 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -29,7 +29,7 @@
 /**
  * Factory for dictionary instances.
  */
-public class DictionaryFactory {
+public final class DictionaryFactory {
     private static final String TAG = DictionaryFactory.class.getSimpleName();
     // This class must be located in the same package as LatinIME.java.
     private static final String RESOURCE_PACKAGE_NAME =
diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
index 9c37d76..f2f3fbd 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
@@ -27,7 +27,7 @@
 /**
  * Takes action to reload the necessary data when a dictionary pack was added/removed.
  */
-public class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver {
+public final class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver {
 
     final LatinIME mService;
     /**
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 8a38d1e..8cdc2a0 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -48,7 +48,7 @@
     // Use this lock before touching mUpdatingDictionary & mRequiresDownload
     private Object mUpdatingLock = new Object();
 
-    private static class Node {
+    private static final class Node {
         Node() {}
         char mCode;
         int mFrequency;
@@ -60,7 +60,7 @@
         LinkedList<NextWord> mNGrams; // Supports ngram
     }
 
-    private static class NodeArray {
+    private static final class NodeArray {
         Node[] mData;
         int mLength = 0;
         private static final int INCREMENT = 2;
@@ -88,7 +88,7 @@
         public int notifyTypedAgainAndGetFrequency();
     }
 
-    private static class NextStaticWord implements NextWord {
+    private static final class NextStaticWord implements NextWord {
         public final Node mWord;
         private final int mFrequency;
         public NextStaticWord(Node word, int frequency) {
@@ -117,7 +117,7 @@
         }
     }
 
-    private static class NextHistoryWord implements NextWord {
+    private static final class NextHistoryWord implements NextWord {
         public final Node mWord;
         public final ForgettingCurveParams mFcp;
 
@@ -703,7 +703,7 @@
         mRoots = new NodeArray();
     }
 
-    private class LoadDictionaryTask extends Thread {
+    private final class LoadDictionaryTask extends Thread {
         LoadDictionaryTask() {}
         @Override
         public void run() {
diff --git a/java/src/com/android/inputmethod/latin/FileTransforms.java b/java/src/com/android/inputmethod/latin/FileTransforms.java
index 8015952..09cf23a 100644
--- a/java/src/com/android/inputmethod/latin/FileTransforms.java
+++ b/java/src/com/android/inputmethod/latin/FileTransforms.java
@@ -21,7 +21,7 @@
 import java.io.OutputStream;
 import java.util.zip.GZIPInputStream;
 
-public class FileTransforms {
+public final class FileTransforms {
     public static OutputStream getCryptedStream(OutputStream out) {
         // Crypt the stream.
         return out;
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 7bcda9b..2f7608a 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -23,7 +23,7 @@
 /**
  * Class to hold attributes of the input field.
  */
-public class InputAttributes {
+public final class InputAttributes {
     private final String TAG = InputAttributes.class.getSimpleName();
 
     final public boolean mInputTypeNoAutoCorrect;
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index ff2feb5..6b48aab 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -17,7 +17,7 @@
 package com.android.inputmethod.latin;
 
 // TODO: This class is not thread-safe.
-public class InputPointers {
+public final class InputPointers {
     private final int mDefaultCapacity;
     private final ResizableIntArray mXCoordinates;
     private final ResizableIntArray mYCoordinates;
diff --git a/java/src/com/android/inputmethod/latin/InputView.java b/java/src/com/android/inputmethod/latin/InputView.java
index c15f453..d7595bf 100644
--- a/java/src/com/android/inputmethod/latin/InputView.java
+++ b/java/src/com/android/inputmethod/latin/InputView.java
@@ -23,7 +23,7 @@
 import android.view.View;
 import android.widget.LinearLayout;
 
-public class InputView extends LinearLayout {
+public final class InputView extends LinearLayout {
     private View mSuggestionStripContainer;
     private View mKeyboardView;
     private int mKeyboardTopPadding;
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index dd73a97..94cdc9b 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -22,7 +22,7 @@
  * This class encapsulates data about a word previously composed, but that has been
  * committed already. This is used for resuming suggestion, and cancel auto-correction.
  */
-public class LastComposedWord {
+public final class LastComposedWord {
     // COMMIT_TYPE_USER_TYPED_WORD is used when the word committed is the exact typed word, with
     // no hinting from the IME. It happens when some external event happens (rotating the device,
     // for example) or when auto-correction is off by settings or editor attributes.
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index b8a8f9a..3e89330 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -85,7 +85,7 @@
 /**
  * Input method implementation for Qwerty'ish keyboard.
  */
-public class LatinIME extends InputMethodService implements KeyboardActionListener,
+public final class LatinIME extends InputMethodService implements KeyboardActionListener,
         SuggestionStripView.Listener, TargetApplicationGetter.OnTargetApplicationKnownListener,
         Suggest.SuggestInitializationListener {
     private static final String TAG = LatinIME.class.getSimpleName();
@@ -183,7 +183,7 @@
 
     public final UIHandler mHandler = new UIHandler(this);
 
-    public static class UIHandler extends StaticInnerHandlerWrapper<LatinIME> {
+    public static final class UIHandler extends StaticInnerHandlerWrapper<LatinIME> {
         private static final int MSG_UPDATE_SHIFT_STATE = 0;
         private static final int MSG_PENDING_IMS_CALLBACK = 1;
         private static final int MSG_UPDATE_SUGGESTION_STRIP = 2;
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index 9eab19c..394a9c7 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -21,7 +21,7 @@
 
 import com.android.inputmethod.keyboard.Keyboard;
 
-public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
+public final class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
 
     public static boolean sDBG = false;
     public static boolean sVISUALDEBUG = false;
diff --git a/java/src/com/android/inputmethod/latin/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/ResizableIntArray.java
index c660f92..9a46f16 100644
--- a/java/src/com/android/inputmethod/latin/ResizableIntArray.java
+++ b/java/src/com/android/inputmethod/latin/ResizableIntArray.java
@@ -19,7 +19,7 @@
 import java.util.Arrays;
 
 // TODO: This class is not thread-safe.
-public class ResizableIntArray {
+public final class ResizableIntArray {
     private int[] mArray;
     private int mLength;
 
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 28c0c0f..1acca58 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -41,7 +41,7 @@
  * all the time to find out what text is in the buffer, when we need it to determine caps mode
  * for example.
  */
-public class RichInputConnection {
+public final class RichInputConnection {
     private static final String TAG = RichInputConnection.class.getSimpleName();
     private static final boolean DBG = false;
     private static final boolean DEBUG_PREVIOUS_TEXT = false;
@@ -415,7 +415,7 @@
     /**
      * Represents a range of text, relative to the current cursor position.
      */
-    public static class Range {
+    public static final class Range {
         /** Characters before selection start */
         public final int mCharsBefore;
 
diff --git a/java/src/com/android/inputmethod/latin/SettingsActivity.java b/java/src/com/android/inputmethod/latin/SettingsActivity.java
index 68f8582..0d3c8eb 100644
--- a/java/src/com/android/inputmethod/latin/SettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/SettingsActivity.java
@@ -19,7 +19,7 @@
 import android.content.Intent;
 import android.preference.PreferenceActivity;
 
-public class SettingsActivity extends PreferenceActivity {
+public final class SettingsActivity extends PreferenceActivity {
     private static final String DEFAULT_FRAGMENT = Settings.class.getName();
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
index de5f515..579f96b 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -30,7 +30,7 @@
 import java.util.HashMap;
 import java.util.Locale;
 
-public class SubtypeLocale {
+public final class SubtypeLocale {
     static final String TAG = SubtypeLocale.class.getSimpleName();
     // This class must be located in the same package as LatinIME.java.
     private static final String RESOURCE_PACKAGE_NAME =
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index c693edc..8e51a37 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -38,7 +38,7 @@
 import java.util.Locale;
 import java.util.Map;
 
-public class SubtypeSwitcher {
+public final class SubtypeSwitcher {
     private static boolean DBG = LatinImeLogger.sDBG;
     private static final String TAG = SubtypeSwitcher.class.getSimpleName();
 
@@ -60,7 +60,7 @@
 
     private boolean mIsNetworkConnected;
 
-    static class NeedsToDisplayLanguage {
+    static final class NeedsToDisplayLanguage {
         private int mEnabledSubtypeCount;
         private boolean mIsSystemLanguageSameAsInputLanguage;
 
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 278c4b9..f0e3b4e 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -34,7 +34,7 @@
  * This class loads a dictionary and provides a list of suggestions for a given sequence of
  * characters. This includes corrections and completions.
  */
-public class Suggest {
+public final class Suggest {
     public static final String TAG = Suggest.class.getSimpleName();
 
     // Session id for
@@ -362,7 +362,8 @@
         return suggestionsList;
     }
 
-    private static class SuggestedWordInfoComparator implements Comparator<SuggestedWordInfo> {
+    private static final class SuggestedWordInfoComparator
+            implements Comparator<SuggestedWordInfo> {
         // This comparator ranks the word info with the higher frequency first. That's because
         // that's the order we want our elements in.
         @Override
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index d9f48c4..52e292a 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -23,7 +23,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 
-public class SuggestedWords {
+public final class SuggestedWords {
     private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
             CollectionUtils.newArrayList(0);
     public static final SuggestedWords EMPTY = new SuggestedWords(
@@ -117,7 +117,7 @@
         return suggestionsList;
     }
 
-    public static class SuggestedWordInfo {
+    public static final class SuggestedWordInfo {
         public static final int MAX_SCORE = Integer.MAX_VALUE;
         public static final int KIND_TYPED = 0; // What user typed
         public static final int KIND_CORRECTION = 1; // Simple correction/suggestion
diff --git a/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java b/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java
index 4a3f42d..d188fc5 100644
--- a/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java
+++ b/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java
@@ -23,7 +23,7 @@
 import android.content.Intent;
 import android.util.Log;
 
-public class SuggestionSpanPickedNotificationReceiver extends BroadcastReceiver {
+public final class SuggestionSpanPickedNotificationReceiver extends BroadcastReceiver {
     private static final boolean DBG = LatinImeLogger.sDBG;
     private static final String TAG =
             SuggestionSpanPickedNotificationReceiver.class.getSimpleName();
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
index bdd988d..8f21b7b 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
@@ -24,7 +24,7 @@
 import java.util.ArrayList;
 import java.util.Locale;
 
-public class SynchronouslyLoadedContactsBinaryDictionary extends ContactsBinaryDictionary {
+public final class SynchronouslyLoadedContactsBinaryDictionary extends ContactsBinaryDictionary {
     private boolean mClosed;
 
     public SynchronouslyLoadedContactsBinaryDictionary(final Context context, final Locale locale) {
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
index b8cfddd..612f54d 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
@@ -23,7 +23,7 @@
 
 import java.util.ArrayList;
 
-public class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDictionary {
+public final class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDictionary {
 
     public SynchronouslyLoadedUserBinaryDictionary(final Context context, final String locale) {
         this(context, locale, false);
diff --git a/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java b/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java
index 4265309..743a8c6 100644
--- a/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java
+++ b/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java
@@ -22,8 +22,7 @@
 import android.os.AsyncTask;
 import android.util.LruCache;
 
-public class TargetApplicationGetter extends AsyncTask<String, Void, ApplicationInfo> {
-
+public final class TargetApplicationGetter extends AsyncTask<String, Void, ApplicationInfo> {
     private static final int MAX_CACHE_ENTRIES = 64; // arbitrary
     private static LruCache<String, ApplicationInfo> sCache =
             new LruCache<String, ApplicationInfo>(MAX_CACHE_ENTRIES);
@@ -32,6 +31,7 @@
         if (null == packageName) return null;
         return sCache.get(packageName);
     }
+
     public static void removeApplicationInfoCache(final String packageName) {
         sCache.remove(packageName);
     }
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
index 05255a6..2cb29d3 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
@@ -38,7 +38,7 @@
  *
  * All the methods in this class are static.
  */
-public class UserHistoryDictIOUtils {
+public final class UserHistoryDictIOUtils {
     private static final String TAG = UserHistoryDictIOUtils.class.getSimpleName();
     private static final boolean DEBUG = false;
 
@@ -115,11 +115,10 @@
     public static void writeDictionaryBinary(final OutputStream destination,
             final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams,
             final FormatOptions formatOptions) {
-
         final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams);
-
         try {
             BinaryDictInputOutput.writeDictionaryBinary(destination, fusionDict, formatOptions);
+            Log.d(TAG, "end writing");
         } catch (IOException e) {
             Log.e(TAG, "IO exception while writing file: " + e);
         } catch (UnsupportedFormatException e) {
@@ -132,16 +131,18 @@
      */
     /* packages for test */ static FusionDictionary constructFusionDictionary(
             final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams) {
-
         final FusionDictionary fusionDict = new FusionDictionary(new Node(),
-                new FusionDictionary.DictionaryOptions(
-                        new HashMap<String,String>(), false, false));
-
+                new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
+                        false));
+        int profTotal = 0;
         for (final String word1 : bigrams.keySet()) {
             final HashMap<String, Byte> word1Bigrams = bigrams.getBigrams(word1);
             for (final String word2 : word1Bigrams.keySet()) {
                 final int freq = dict.getFrequency(word1, word2);
-
+                if (freq == -1) {
+                    // don't add this bigram.
+                    continue;
+                }
                 if (DEBUG) {
                     if (word1 == null) {
                         Log.d(TAG, "add unigram: " + word2 + "," + Integer.toString(freq));
@@ -149,17 +150,22 @@
                         Log.d(TAG, "add bigram: " + word1
                                 + "," + word2 + "," + Integer.toString(freq));
                     }
+                    profTotal++;
                 }
-
                 if (word1 == null) { // unigram
                     fusionDict.add(word2, freq, null, false /* isNotAWord */);
                 } else { // bigram
+                    if (FusionDictionary.findWordInTree(fusionDict.mRoot, word1) == null) {
+                        fusionDict.add(word1, 2, null, false /* isNotAWord */);
+                    }
                     fusionDict.setBigram(word1, word2, freq);
                 }
                 bigrams.updateBigram(word1, word2, (byte)freq);
             }
         }
-
+        if (DEBUG) {
+            Log.d(TAG, "add " + profTotal + "words");
+        }
         return fusionDict;
     }
 
@@ -171,7 +177,6 @@
         final Map<Integer, String> unigrams = CollectionUtils.newTreeMap();
         final Map<Integer, Integer> frequencies = CollectionUtils.newTreeMap();
         final Map<Integer, ArrayList<PendingAttribute>> bigrams = CollectionUtils.newTreeMap();
-
         try {
             BinaryDictIOUtils.readUnigramsAndBigramsBinary(buffer, unigrams, frequencies,
                     bigrams);
@@ -189,14 +194,11 @@
     /* package for test */ static void addWordsFromWordMap(final Map<Integer, String> unigrams,
             final Map<Integer, Integer> frequencies,
             final Map<Integer, ArrayList<PendingAttribute>> bigrams, final OnAddWordListener to) {
-
         for (Map.Entry<Integer, String> entry : unigrams.entrySet()) {
             final String word1 = entry.getValue();
             final int unigramFrequency = frequencies.get(entry.getKey());
             to.setUnigram(word1, null, unigramFrequency);
-
             final ArrayList<PendingAttribute> attrList = bigrams.get(entry.getKey());
-
             if (attrList != null) {
                 for (final PendingAttribute attr : attrList) {
                     to.setBigram(word1, unigrams.get(attr.mAddress),
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index e03af64..3615fa1 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -16,24 +16,25 @@
 
 package com.android.inputmethod.latin;
 
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQueryBuilder;
 import android.os.AsyncTask;
-import android.provider.BaseColumns;
 import android.util.Log;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.UserHistoryDictIOUtils.BigramDictionaryInterface;
+import com.android.inputmethod.latin.UserHistoryDictIOUtils.OnAddWordListener;
 import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.lang.ref.SoftReference;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -41,47 +42,29 @@
  * Locally gathers stats about the words user types and various other signals like auto-correction
  * cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
  */
-public class UserHistoryDictionary extends ExpandableDictionary {
+public final class UserHistoryDictionary extends ExpandableDictionary {
     private static final String TAG = UserHistoryDictionary.class.getSimpleName();
+    private static final String NAME = UserHistoryDictionary.class.getSimpleName();
     public static final boolean DBG_SAVE_RESTORE = false;
     public static final boolean DBG_STRESS_TEST = false;
     public static final boolean DBG_ALWAYS_WRITE = false;
     public static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
 
+    private static final FormatOptions VERSION3 = new FormatOptions(3,
+            true /* supportsDynamicUpdate */);
+
     /** Any pair being typed or picked */
     private static final int FREQUENCY_FOR_TYPED = 2;
 
     /** Maximum number of pairs. Pruning will start when databases goes above this number. */
-    public static final int sMaxHistoryBigrams = 10000;
+    public static final int MAX_HISTORY_BIGRAMS = 10000;
 
     /**
      * When it hits maximum bigram pair, it will delete until you are left with
      * only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs.
      * Do not keep this number small to avoid deleting too often.
      */
-    public static final int sDeleteHistoryBigrams = 1000;
-
-    /**
-     * Database version should increase if the database structure changes
-     */
-    private static final int DATABASE_VERSION = 1;
-
-    private static final String DATABASE_NAME = "userbigram_dict.db";
-
-    /** Name of the words table in the database */
-    private static final String MAIN_TABLE_NAME = "main";
-    // TODO: Consume less space by using a unique id for locale instead of the whole
-    // 2-5 character string.
-    private static final String MAIN_COLUMN_ID = BaseColumns._ID;
-    private static final String MAIN_COLUMN_WORD1 = "word1";
-    private static final String MAIN_COLUMN_WORD2 = "word2";
-    private static final String MAIN_COLUMN_LOCALE = "locale";
-
-    /** Name of the frequency table in the database */
-    private static final String FREQ_TABLE_NAME = "frequency";
-    private static final String FREQ_COLUMN_ID = BaseColumns._ID;
-    private static final String FREQ_COLUMN_PAIR_ID = "pair_id";
-    private static final String COLUMN_FORGETTING_CURVE_VALUE = "freq";
+    public static final int DELETE_HISTORY_BIGRAMS = 1000;
 
     /** Locale for which this user history dictionary is storing words */
     private final String mLocale;
@@ -91,29 +74,13 @@
     private final ReentrantLock mBigramListLock = new ReentrantLock();
     private final SharedPreferences mPrefs;
 
-    private final static HashMap<String, String> sDictProjectionMap;
-    private final static ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
+    // Should always be false except when we use this class for test
+    /* package for test */ boolean isTest = false;
+
+    private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
             sLangDictCache = CollectionUtils.newConcurrentHashMap();
 
-    static {
-        sDictProjectionMap = CollectionUtils.newHashMap();
-        sDictProjectionMap.put(MAIN_COLUMN_ID, MAIN_COLUMN_ID);
-        sDictProjectionMap.put(MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD1);
-        sDictProjectionMap.put(MAIN_COLUMN_WORD2, MAIN_COLUMN_WORD2);
-        sDictProjectionMap.put(MAIN_COLUMN_LOCALE, MAIN_COLUMN_LOCALE);
-
-        sDictProjectionMap.put(FREQ_COLUMN_ID, FREQ_COLUMN_ID);
-        sDictProjectionMap.put(FREQ_COLUMN_PAIR_ID, FREQ_COLUMN_PAIR_ID);
-        sDictProjectionMap.put(COLUMN_FORGETTING_CURVE_VALUE, COLUMN_FORGETTING_CURVE_VALUE);
-    }
-
-    private static DatabaseHelper sOpenHelper = null;
-
-    public String getLocale() {
-        return mLocale;
-    }
-
-    public synchronized static UserHistoryDictionary getInstance(
+    public static synchronized UserHistoryDictionary getInstance(
             final Context context, final String locale, final SharedPreferences sp) {
         if (sLangDictCache.containsKey(locale)) {
             final SoftReference<UserHistoryDictionary> ref = sLangDictCache.get(locale);
@@ -136,9 +103,6 @@
         super(context, Dictionary.TYPE_USER_HISTORY);
         mLocale = locale;
         mPrefs = sp;
-        if (sOpenHelper == null) {
-            sOpenHelper = new DatabaseHelper(getContext());
-        }
         if (mLocale != null && mLocale.length() > 1) {
             loadDictionary();
         }
@@ -190,6 +154,7 @@
             try {
                 super.addWord(
                         word2, null /* the "shortcut" parameter is null */, FREQUENCY_FOR_TYPED);
+                mBigramList.addBigram(null, word2, (byte)FREQUENCY_FOR_TYPED);
                 // Do not insert a word as a bigram of itself
                 if (word2.equals(word1)) {
                     return 0;
@@ -227,11 +192,8 @@
      * Schedules a background thread to write any pending words to the database.
      */
     private void flushPendingWrites() {
-        if (mBigramListLock.isLocked()) {
-            return;
-        }
         // Create a background thread to write the pending entries
-        new UpdateDbTask(sOpenHelper, mBigramList, mLocale, this, mPrefs).execute();
+        new UpdateBinaryTask(mBigramList, mLocale, this, mPrefs, getContext()).execute();
     }
 
     @Override
@@ -245,6 +207,8 @@
         }
     }
 
+    private int profTotal;
+
     private void loadDictionaryAsyncLocked() {
         if (DBG_STRESS_TEST) {
             try {
@@ -257,343 +221,181 @@
         final long last = SettingsValues.getLastUserHistoryWriteTime(mPrefs, mLocale);
         final boolean initializing = last == 0;
         final long now = System.currentTimeMillis();
-        // Load the words that correspond to the current input locale
-        final Cursor cursor = query(MAIN_COLUMN_LOCALE + "=?", new String[] { mLocale });
-        if (null == cursor) return;
-        try {
-            // TODO: Call SQLiteDataBase.beginTransaction / SQLiteDataBase.endTransaction
-            if (cursor.moveToFirst()) {
-                final int word1Index = cursor.getColumnIndex(MAIN_COLUMN_WORD1);
-                final int word2Index = cursor.getColumnIndex(MAIN_COLUMN_WORD2);
-                final int fcIndex = cursor.getColumnIndex(COLUMN_FORGETTING_CURVE_VALUE);
-                while (!cursor.isAfterLast()) {
-                    final String word1 = cursor.getString(word1Index);
-                    final String word2 = cursor.getString(word2Index);
-                    final int fc = cursor.getInt(fcIndex);
+        profTotal = 0;
+        final String fileName = NAME + "." + mLocale + ".dict";
+        final ExpandableDictionary dictionary = this;
+        final OnAddWordListener listener = new OnAddWordListener() {
+            @Override
+            public void setUnigram(String word, String shortcutTarget, int frequency) {
+                profTotal++;
+                if (DBG_SAVE_RESTORE) {
+                    Log.d(TAG, "load unigram: " + word + "," + frequency);
+                }
+                dictionary.addWord(word, shortcutTarget, frequency);
+                mBigramList.addBigram(null, word, (byte)frequency);
+            }
+
+            @Override
+            public void setBigram(String word1, String word2, int frequency) {
+                if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH
+                        && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) {
+                    profTotal++;
                     if (DBG_SAVE_RESTORE) {
-                        Log.d(TAG, "--- Load user history: " + word1 + ", " + word2 + ","
-                                + mLocale + "," + this);
+                        Log.d(TAG, "load bigram: " + word1 + "," + word2 + "," + frequency);
                     }
-                    // Safeguard against adding really long words. Stack may overflow due
-                    // to recursive lookup
-                    if (null == word1) {
-                        super.addWord(word2, null /* shortcut */, fc);
-                    } else if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH
-                            && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) {
-                        super.setBigramAndGetFrequency(
-                                word1, word2, initializing ? new ForgettingCurveParams(true)
-                                : new ForgettingCurveParams(fc, now, last));
-                    }
-                    mBigramList.addBigram(word1, word2, (byte)fc);
-                    cursor.moveToNext();
+                    dictionary.setBigramAndGetFrequency(
+                            word1, word2, initializing ? new ForgettingCurveParams(true)
+                            : new ForgettingCurveParams(frequency, now, last));
+                }
+                mBigramList.addBigram(word1, word2, (byte)frequency);
+            }
+        };
+        
+        // Load the dictionary from binary file
+        FileInputStream inStream = null;
+        try {
+            final File file = new File(getContext().getFilesDir(), fileName);
+            final byte[] buffer = new byte[(int)file.length()];
+            inStream = new FileInputStream(file);
+            inStream.read(buffer);
+            UserHistoryDictIOUtils.readDictionaryBinary(
+                    new UserHistoryDictIOUtils.ByteArrayWrapper(buffer), listener);
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "when loading: file not found" + e);
+        } catch (IOException e) {
+            Log.e(TAG, "IOException when open bytebuffer: " + e);
+        } finally {
+            if (inStream != null) {
+                try {
+                    inStream.close();
+                } catch (IOException e) {
+                    // do nothing
                 }
             }
-        } finally {
-            cursor.close();
             if (PROFILE_SAVE_RESTORE) {
                 final long diff = System.currentTimeMillis() - now;
-                Log.w(TAG, "PROF: Load User HistoryDictionary: "
-                        + mLocale + ", " + diff + "ms.");
+                Log.d(TAG, "PROF: Load UserHistoryDictionary: "
+                        + mLocale + ", " + diff + "ms. load " + profTotal + "entries.");
             }
         }
     }
 
     /**
-     * Query the database
+     * Async task to write pending words to the binarydicts.
      */
-    private static Cursor query(String selection, String[] selectionArgs) {
-        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-
-        // main INNER JOIN frequency ON (main._id=freq.pair_id)
-        qb.setTables(MAIN_TABLE_NAME + " INNER JOIN " + FREQ_TABLE_NAME + " ON ("
-                + MAIN_TABLE_NAME + "." + MAIN_COLUMN_ID + "=" + FREQ_TABLE_NAME + "."
-                + FREQ_COLUMN_PAIR_ID +")");
-
-        qb.setProjectionMap(sDictProjectionMap);
-
-        // Get the database and run the query
-        try {
-            SQLiteDatabase db = sOpenHelper.getReadableDatabase();
-            Cursor c = qb.query(db,
-                    new String[] {
-                            MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD2, COLUMN_FORGETTING_CURVE_VALUE },
-                    selection, selectionArgs, null, null, null);
-            return c;
-        } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) {
-            // Can't open the database : presumably we can't access storage. That may happen
-            // when the device is wedged; do a best effort to still start the keyboard.
-            return null;
-        }
-    }
-
-    /**
-     * This class helps open, create, and upgrade the database file.
-     */
-    private static class DatabaseHelper extends SQLiteOpenHelper {
-
-        DatabaseHelper(Context context) {
-            super(context, DATABASE_NAME, null, DATABASE_VERSION);
-        }
-
-        @Override
-        public void onCreate(SQLiteDatabase db) {
-            db.execSQL("PRAGMA foreign_keys = ON;");
-            db.execSQL("CREATE TABLE " + MAIN_TABLE_NAME + " ("
-                    + MAIN_COLUMN_ID + " INTEGER PRIMARY KEY,"
-                    + MAIN_COLUMN_WORD1 + " TEXT,"
-                    + MAIN_COLUMN_WORD2 + " TEXT,"
-                    + MAIN_COLUMN_LOCALE + " TEXT"
-                    + ");");
-            db.execSQL("CREATE TABLE " + FREQ_TABLE_NAME + " ("
-                    + FREQ_COLUMN_ID + " INTEGER PRIMARY KEY,"
-                    + FREQ_COLUMN_PAIR_ID + " INTEGER,"
-                    + COLUMN_FORGETTING_CURVE_VALUE + " INTEGER,"
-                    + "FOREIGN KEY(" + FREQ_COLUMN_PAIR_ID + ") REFERENCES " + MAIN_TABLE_NAME
-                    + "(" + MAIN_COLUMN_ID + ")" + " ON DELETE CASCADE"
-                    + ");");
-        }
-
-        @Override
-        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
-                    + newVersion + ", which will destroy all old data");
-            db.execSQL("DROP TABLE IF EXISTS " + MAIN_TABLE_NAME);
-            db.execSQL("DROP TABLE IF EXISTS " + FREQ_TABLE_NAME);
-            onCreate(db);
-        }
-    }
-
-    /**
-     * Async task to write pending words to the database so that it stays in sync with
-     * the in-memory trie.
-     */
-    private static class UpdateDbTask extends AsyncTask<Void, Void, Void> {
+    private static final class UpdateBinaryTask extends AsyncTask<Void, Void, Void>
+            implements BigramDictionaryInterface {
         private final UserHistoryDictionaryBigramList mBigramList;
-        private final DatabaseHelper mDbHelper;
+        private final boolean mAddLevel0Bigrams;
         private final String mLocale;
         private final UserHistoryDictionary mUserHistoryDictionary;
         private final SharedPreferences mPrefs;
+        private final Context mContext;
 
-        public UpdateDbTask(
-                DatabaseHelper openHelper, UserHistoryDictionaryBigramList pendingWrites,
-                String locale, UserHistoryDictionary dict, SharedPreferences prefs) {
+        public UpdateBinaryTask(UserHistoryDictionaryBigramList pendingWrites, String locale,
+                UserHistoryDictionary dict, SharedPreferences prefs, Context context) {
             mBigramList = pendingWrites;
             mLocale = locale;
-            mDbHelper = openHelper;
             mUserHistoryDictionary = dict;
             mPrefs = prefs;
-        }
-
-        /** Prune any old data if the database is getting too big. */
-        private static void checkPruneData(SQLiteDatabase db) {
-            db.execSQL("PRAGMA foreign_keys = ON;");
-            Cursor c = db.query(FREQ_TABLE_NAME, new String[] { FREQ_COLUMN_PAIR_ID },
-                    null, null, null, null, null);
-            try {
-                int totalRowCount = c.getCount();
-                // prune out old data if we have too much data
-                if (totalRowCount > sMaxHistoryBigrams) {
-                    int numDeleteRows = (totalRowCount - sMaxHistoryBigrams)
-                            + sDeleteHistoryBigrams;
-                    int pairIdColumnId = c.getColumnIndex(FREQ_COLUMN_PAIR_ID);
-                    c.moveToFirst();
-                    int count = 0;
-                    while (count < numDeleteRows && !c.isAfterLast()) {
-                        String pairId = c.getString(pairIdColumnId);
-                        // Deleting from MAIN table will delete the frequencies
-                        // due to FOREIGN KEY .. ON DELETE CASCADE
-                        db.delete(MAIN_TABLE_NAME, MAIN_COLUMN_ID + "=?",
-                            new String[] { pairId });
-                        c.moveToNext();
-                        count++;
-                    }
-                }
-            } finally {
-                c.close();
-            }
+            mContext = context;
+            mAddLevel0Bigrams = mBigramList.size() <= MAX_HISTORY_BIGRAMS;
         }
 
         @Override
         protected Void doInBackground(Void... v) {
-            SQLiteDatabase db = null;
-            if (mUserHistoryDictionary.mBigramListLock.tryLock()) {
+            if (mUserHistoryDictionary.isTest) {
+                // If isTest == true, wait until the lock is released.
+                mUserHistoryDictionary.mBigramListLock.lock();
                 try {
-                    try {
-                        db = mDbHelper.getWritableDatabase();
-                    } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) {
-                        // If we can't open the db, don't do anything. Exit through the next test
-                        // for non-nullity of the db variable.
-                    }
-                    if (null == db) {
-                        // Not much we can do. Just exit.
-                        return null;
-                    }
-                    db.beginTransaction();
-                    return doLoadTaskLocked(db);
+                    doWriteTaskLocked();
                 } finally {
-                    if (db != null) {
-                        db.endTransaction();
-                    }
                     mUserHistoryDictionary.mBigramListLock.unlock();
                 }
+            } else if (mUserHistoryDictionary.mBigramListLock.tryLock()) {
+                doWriteTaskLocked();
             }
             return null;
         }
 
-        private Void doLoadTaskLocked(SQLiteDatabase db) {
+        private void doWriteTaskLocked() {
             if (DBG_STRESS_TEST) {
                 try {
                     Log.w(TAG, "Start stress in closing: " + mLocale);
                     Thread.sleep(15000);
                     Log.w(TAG, "End stress in closing");
                 } catch (InterruptedException e) {
+                    Log.e(TAG, "In stress test: " + e);
                 }
             }
+
             final long now = PROFILE_SAVE_RESTORE ? System.currentTimeMillis() : 0;
-            int profTotal = 0;
-            int profInsert = 0;
-            int profDelete = 0;
-            db.execSQL("PRAGMA foreign_keys = ON;");
-            final boolean addLevel0Bigram = mBigramList.size() <= sMaxHistoryBigrams;
+            final String fileName = NAME + "." + mLocale + ".dict";
+            final File file = new File(mContext.getFilesDir(), fileName);
+            FileOutputStream out = null;
 
-            // Write all the entries to the db
-            for (String word1 : mBigramList.keySet()) {
-                final HashMap<String, Byte> word1Bigrams = mBigramList.getBigrams(word1);
-                for (String word2 : word1Bigrams.keySet()) {
-                    if (PROFILE_SAVE_RESTORE) {
-                        ++profTotal;
-                    }
-                    // Get new frequency. Do not insert unigrams/bigrams which freq is "-1".
-                    final int freq; // -1, or 0~255
-                    if (word1 == null) { // unigram
-                        freq = FREQUENCY_FOR_TYPED;
-                        final byte prevFc = word1Bigrams.get(word2);
-                        if (prevFc == FREQUENCY_FOR_TYPED) {
-                            // No need to update since we found no changes for this entry.
-                            // Just skip to the next entry.
-                            if (DBG_SAVE_RESTORE) {
-                                Log.d(TAG, "Skip update user history: " + word1 + "," + word2
-                                        + "," + prevFc);
-                            }
-                            if (!DBG_ALWAYS_WRITE) {
-                                continue;
-                            }
-                        }
-                    } else { // bigram
-                        final NextWord nw = mUserHistoryDictionary.getBigramWord(word1, word2);
-                        if (nw != null) {
-                            final ForgettingCurveParams fcp = nw.getFcParams();
-                            final byte prevFc = word1Bigrams.get(word2);
-                            final byte fc = (byte)fcp.getFc();
-                            final boolean isValid = fcp.isValid();
-                            if (prevFc > 0 && prevFc == fc) {
-                                // No need to update since we found no changes for this entry.
-                                // Just skip to the next entry.
-                                if (DBG_SAVE_RESTORE) {
-                                    Log.d(TAG, "Skip update user history: " + word1 + ","
-                                            + word2 + "," + prevFc);
-                                }
-                                if (!DBG_ALWAYS_WRITE) {
-                                    continue;
-                                } else {
-                                    freq = fc;
-                                }
-                            } else if (UserHistoryForgettingCurveUtils.
-                                    needsToSave(fc, isValid, addLevel0Bigram)) {
-                                freq = fc;
-                            } else {
-                                // Delete this entry
-                                freq = -1;
-                            }
-                        } else {
-                            // Delete this entry
-                            freq = -1;
-                        }
-                    }
-                    // TODO: this process of making a text search for each pair each time
-                    // is terribly inefficient. Optimize this.
-                    // Find pair id
-                    Cursor c = null;
+            try {
+                out = new FileOutputStream(file);
+                UserHistoryDictIOUtils.writeDictionaryBinary(out, this, mBigramList, VERSION3);
+                out.flush();
+                out.close();
+            } catch (IOException e) {
+                Log.e(TAG, "IO Exception while writing file: " + e);
+            } finally {
+                if (out != null) {
                     try {
-                        if (null != word1) {
-                            c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID },
-                                    MAIN_COLUMN_WORD1 + "=? AND " + MAIN_COLUMN_WORD2 + "=? AND "
-                                            + MAIN_COLUMN_LOCALE + "=?",
-                                            new String[] { word1, word2, mLocale }, null, null,
-                                            null);
-                        } else {
-                            c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID },
-                                    MAIN_COLUMN_WORD1 + " IS NULL AND " + MAIN_COLUMN_WORD2
-                                    + "=? AND " + MAIN_COLUMN_LOCALE + "=?",
-                                    new String[] { word2, mLocale }, null, null, null);
-                        }
-
-                        final int pairId;
-                        if (c.moveToFirst()) {
-                            if (PROFILE_SAVE_RESTORE) {
-                                ++profDelete;
-                            }
-                            // Delete existing pair
-                            pairId = c.getInt(c.getColumnIndex(MAIN_COLUMN_ID));
-                            db.delete(FREQ_TABLE_NAME, FREQ_COLUMN_PAIR_ID + "=?",
-                                    new String[] { Integer.toString(pairId) });
-                        } else {
-                            // Create new pair
-                            Long pairIdLong = db.insert(MAIN_TABLE_NAME, null,
-                                    getContentValues(word1, word2, mLocale));
-                            pairId = pairIdLong.intValue();
-                        }
-                        // Eliminate freq == 0 because that word is profanity.
-                        if (freq > 0) {
-                            if (PROFILE_SAVE_RESTORE) {
-                                ++profInsert;
-                            }
-                            if (DBG_SAVE_RESTORE) {
-                                Log.d(TAG, "--- Save user history: " + word1 + ", " + word2
-                                        + mLocale + "," + this);
-                            }
-                            // Insert new frequency
-                            db.insert(FREQ_TABLE_NAME, null,
-                                    getFrequencyContentValues(pairId, freq));
-                            // Update an existing bigram entry in mBigramList too in order to
-                            // synchronize the SQL DB and mBigramList.
-                            mBigramList.updateBigram(word1, word2, (byte)freq);
-                        }
-                    } finally {
-                        if (c != null) {
-                            c.close();
-                        }
+                        out.close();
+                    } catch (IOException e) {
+                        // ignore
                     }
                 }
             }
 
-            checkPruneData(db);
-            // Save the timestamp after we finish writing the SQL DB.
+            // Save the timestamp after we finish writing the binary dictionary.
             SettingsValues.setLastUserHistoryWriteTime(mPrefs, mLocale);
             if (PROFILE_SAVE_RESTORE) {
                 final long diff = System.currentTimeMillis() - now;
-                Log.w(TAG, "PROF: Write User HistoryDictionary: " + mLocale + ", "+ diff
-                        + "ms. Total: " + profTotal + ". Insert: " + profInsert + ". Delete: "
-                        + profDelete);
+                Log.w(TAG, "PROF: Write User HistoryDictionary: " + mLocale + ", " + diff + "ms.");
             }
-            db.setTransactionSuccessful();
-            return null;
         }
 
-        private static ContentValues getContentValues(String word1, String word2, String locale) {
-            ContentValues values = new ContentValues(3);
-            values.put(MAIN_COLUMN_WORD1, word1);
-            values.put(MAIN_COLUMN_WORD2, word2);
-            values.put(MAIN_COLUMN_LOCALE, locale);
-            return values;
-        }
-
-        private static ContentValues getFrequencyContentValues(int pairId, int frequency) {
-           ContentValues values = new ContentValues(2);
-           values.put(FREQ_COLUMN_PAIR_ID, pairId);
-           values.put(COLUMN_FORGETTING_CURVE_VALUE, frequency);
-           return values;
+        @Override
+        public int getFrequency(String word1, String word2) {
+            final int freq;
+            if (word1 == null) { // unigram
+                freq = FREQUENCY_FOR_TYPED;
+                final byte prevFc = mBigramList.getBigrams(word1).get(word2);
+            } else { // bigram
+                final NextWord nw = mUserHistoryDictionary.getBigramWord(word1, word2);
+                if (nw != null) {
+                    final ForgettingCurveParams fcp = nw.getFcParams();
+                    final byte prevFc = mBigramList.getBigrams(word1).get(word2);
+                    final byte fc = fcp.getFc();
+                    final boolean isValid = fcp.isValid();
+                    if (prevFc > 0 && prevFc == fc) {
+                        freq = ((int)fc) & 0xFF;
+                    } else if (UserHistoryForgettingCurveUtils.
+                            needsToSave(fc, isValid, mAddLevel0Bigrams)) {
+                        freq = ((int)fc) & 0xFF;
+                    } else {
+                        // Delete this entry
+                        freq = -1;
+                    }
+                } else {
+                    // Delete this entry
+                    freq = -1;
+                }
+            }
+            return freq;
         }
     }
 
+    void forceAddWordForTest(final String word1, final String word2, final boolean isValid) {
+        mBigramListLock.lock();
+        try {
+            addToUserHistory(word1, word2, isValid);
+        } finally {
+            mBigramListLock.unlock();
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
index bb0f542..df44948 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
@@ -26,7 +26,7 @@
  * All bigrams including stale ones in SQL DB should be stored in this class to avoid adding stale
  * bigrams when we write to the SQL DB.
  */
-public class UserHistoryDictionaryBigramList {
+public final class UserHistoryDictionaryBigramList {
     public static final byte FORGETTING_CURVE_INITIAL_VALUE = 0;
     private static final String TAG = UserHistoryDictionaryBigramList.class.getSimpleName();
     private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = CollectionUtils.newHashMap();
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
index 3d3bd98..9053d70 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
@@ -36,7 +36,7 @@
         // This utility class is not publicly instantiable.
     }
 
-    public static class ForgettingCurveParams {
+    public static final class ForgettingCurveParams {
         private byte mFc;
         long mLastTouchedTime = 0;
         private final boolean mIsValid;
@@ -195,7 +195,7 @@
         return (elapsedTime < ELAPSED_TIME_MAX - 1 || level > 0);
     }
 
-    private static class MathUtils {
+    private static final class MathUtils {
         public static final int[][] SCORE_TABLE = new int[FC_LEVEL_MAX][ELAPSED_TIME_MAX + 1];
         static {
             for (int i = 0; i < FC_LEVEL_MAX; ++i) {
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 275ebf3..8a1bbed 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -24,7 +24,7 @@
 /**
  * A place to store the currently composing word with information such as adjacent key codes as well
  */
-public class WordComposer {
+public final class WordComposer {
     private static final int N = BinaryDictionary.MAX_WORD_LENGTH;
 
     public static final int CAPS_MODE_OFF = 0;
diff --git a/java/src/com/android/inputmethod/latin/WordListInfo.java b/java/src/com/android/inputmethod/latin/WordListInfo.java
index 54f04d7..095320e 100644
--- a/java/src/com/android/inputmethod/latin/WordListInfo.java
+++ b/java/src/com/android/inputmethod/latin/WordListInfo.java
@@ -19,7 +19,7 @@
 /**
  * Information container for a word list.
  */
-public class WordListInfo {
+public final class WordListInfo {
     public final String mId;
     public final String mLocale;
     public WordListInfo(final String id, final String locale) {
diff --git a/java/src/com/android/inputmethod/latin/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
index b5cbaf1..75dc68f 100644
--- a/java/src/com/android/inputmethod/latin/XmlParseUtils.java
+++ b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
@@ -36,28 +36,28 @@
     }
 
     @SuppressWarnings("serial")
-    public static class IllegalStartTag extends ParseException {
+    public static final class IllegalStartTag extends ParseException {
         public IllegalStartTag(XmlPullParser parser, String parent) {
             super("Illegal start tag " + parser.getName() + " in " + parent, parser);
         }
     }
 
     @SuppressWarnings("serial")
-    public static class IllegalEndTag extends ParseException {
+    public static final class IllegalEndTag extends ParseException {
         public IllegalEndTag(XmlPullParser parser, String parent) {
             super("Illegal end tag " + parser.getName() + " in " + parent, parser);
         }
     }
 
     @SuppressWarnings("serial")
-    public static class IllegalAttribute extends ParseException {
+    public static final class IllegalAttribute extends ParseException {
         public IllegalAttribute(XmlPullParser parser, String attribute) {
             super("Tag " + parser.getName() + " has illegal attribute " + attribute, parser);
         }
     }
 
     @SuppressWarnings("serial")
-    public static class NonEmptyTag extends ParseException{
+    public static final class NonEmptyTag extends ParseException{
         public NonEmptyTag(String tag, XmlPullParser parser) {
             super(tag + " must be empty tag", parser);
         }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index b97be05..7b0231a 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -27,10 +27,10 @@
 import java.util.Map;
 import java.util.Stack;
 
-public class BinaryDictIOUtils {
+public final class BinaryDictIOUtils {
     private static final boolean DBG = false;
 
-    private static class Position {
+    private static final class Position {
         public static final int NOT_READ_GROUPCOUNT = -1;
 
         public int mAddress;
@@ -77,7 +77,10 @@
                 p.mAddress += BinaryDictInputOutput.getGroupCountSize(p.mNumOfCharGroup);
                 p.mPosition = 0;
             }
-
+            if (p.mNumOfCharGroup == 0) {
+                stack.pop();
+                continue;
+            }
             CharGroupInfo info = BinaryDictInputOutput.readCharGroup(buffer,
                     p.mAddress - headerSize, formatOptions);
             for (int i = 0; i < info.mCharacters.length; ++i) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 9fc6942..b431a4d 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -43,7 +43,7 @@
  *
  * All the methods in this class are static.
  */
-public class BinaryDictInputOutput {
+public final class BinaryDictInputOutput {
 
     private static final boolean DBG = MakedictLog.DBG;
 
@@ -124,7 +124,7 @@
     /**
      * A class grouping utility function for our specific character encoding.
      */
-    private static class CharEncoding {
+    private static final class CharEncoding {
 
         private static final int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
         private static final int MAXIMAL_ONE_BYTE_CHARACTER_VALUE = 0xFF;
diff --git a/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java b/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
index ed93884..8e64082 100644
--- a/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
+++ b/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
@@ -23,7 +23,7 @@
 /**
  * Raw char group info straight out of a file. This will contain numbers for addresses.
  */
-public class CharGroupInfo {
+public final class CharGroupInfo {
 
     public final int mOriginalAddress;
     public final int mEndAddress;
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index 35311f0..b3fbb9f 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -221,7 +221,7 @@
     /**
      * Options about file format.
      */
-    public static class FormatOptions {
+    public static final class FormatOptions {
         public final int mVersion;
         public final boolean mSupportsDynamicUpdate;
         public FormatOptions(final int version) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 98cf308..3193ef4 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -28,8 +28,7 @@
 /**
  * A dictionary that can fusion heads and tails of words for more compression.
  */
-public class FusionDictionary implements Iterable<Word> {
-
+public final class FusionDictionary implements Iterable<Word> {
     private static final boolean DBG = MakedictLog.DBG;
 
     /**
@@ -40,7 +39,7 @@
      * This class also contains fields to cache size and address, to help with binary
      * generation.
      */
-    public static class Node {
+    public static final class Node {
         ArrayList<CharGroup> mData;
         // To help with binary generation
         int mCachedSize = Integer.MIN_VALUE;
@@ -60,7 +59,7 @@
      *
      * This represents an "attribute", that is either a bigram or a shortcut.
      */
-    public static class WeightedString {
+    public static final class WeightedString {
         public final String mWord;
         public int mFrequency;
         public WeightedString(String word, int frequency) {
@@ -94,7 +93,7 @@
      * value is the frequency of this terminal. A terminal may have non-null shortcuts and/or
      * bigrams, but a non-terminal may not. Moreover, children, if present, are null.
      */
-    public static class CharGroup {
+    public static final class CharGroup {
         public static final int NOT_A_TERMINAL = -1;
         final int mChars[];
         ArrayList<WeightedString> mShortcutTargets;
@@ -253,7 +252,7 @@
      *
      * There are no options at the moment, so this class is empty.
      */
-    public static class DictionaryOptions {
+    public static final class DictionaryOptions {
         public final boolean mGermanUmlautProcessing;
         public final boolean mFrenchLigatureProcessing;
         public final HashMap<String, String> mAttributes;
@@ -511,7 +510,7 @@
      * is ignored.
      * This comparator imposes orderings that are inconsistent with equals.
      */
-    static private class CharGroupComparator implements java.util.Comparator<CharGroup> {
+    static private final class CharGroupComparator implements java.util.Comparator<CharGroup> {
         @Override
         public int compare(CharGroup c1, CharGroup c2) {
             if (c1.mChars[0] == c2.mChars[0]) return 0;
@@ -746,9 +745,8 @@
      *
      * This is purely for convenience.
      */
-    public static class DictionaryIterator implements Iterator<Word> {
-
-        private static class Position {
+    public static final class DictionaryIterator implements Iterator<Word> {
+        private static final class Position {
             public Iterator<CharGroup> pos;
             public int length;
             public Position(ArrayList<CharGroup> groups) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java b/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
index 3f0cd07..6c6b00b 100644
--- a/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
+++ b/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
@@ -21,7 +21,7 @@
 /**
  * Wrapper to redirect log events to the right output medium.
  */
-public class MakedictLog {
+public final class MakedictLog {
     public static final boolean DBG = false;
     private static final String TAG = MakedictLog.class.getSimpleName();
 
diff --git a/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java b/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
index 5b41d27..5bb24da 100644
--- a/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
+++ b/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
@@ -22,7 +22,7 @@
  * An attribute is either a bigram or a shortcut.
  * All instances of this class are always immutable.
  */
-public class PendingAttribute {
+public final class PendingAttribute {
     public final int mFrequency;
     public final int mAddress;
     public PendingAttribute(final int frequency, final int address) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java b/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
index bd42fb8..dbb2ea8 100644
--- a/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
+++ b/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
@@ -19,7 +19,7 @@
 /**
  * Simple exception thrown when a file format is not recognized.
  */
-public class UnsupportedFormatException extends Exception {
+public final class UnsupportedFormatException extends Exception {
     public UnsupportedFormatException(String description) {
         super(description);
     }
diff --git a/java/src/com/android/inputmethod/latin/makedict/Word.java b/java/src/com/android/inputmethod/latin/makedict/Word.java
index 4683ef1..4c4f18f 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Word.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Word.java
@@ -26,7 +26,7 @@
  *
  * This is chiefly used to iterate a dictionary.
  */
-public class Word implements Comparable<Word> {
+public final class Word implements Comparable<Word> {
     public final String mWord;
     public final int mFrequency;
     public final ArrayList<WeightedString> mShortcutTargets;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index eef7a51..5a11ae5 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -50,7 +50,7 @@
 /**
  * Service for spell checking, using LatinIME's dictionaries and mechanisms.
  */
-public class AndroidSpellCheckerService extends SpellCheckerService
+public final class AndroidSpellCheckerService extends SpellCheckerService
         implements SharedPreferences.OnSharedPreferenceChangeListener {
     private static final String TAG = AndroidSpellCheckerService.class.getSimpleName();
     private static final boolean DBG = false;
@@ -201,8 +201,8 @@
     }
 
     // TODO: remove this class and replace it by storage local to the session.
-    public static class SuggestionsGatherer {
-        public static class Result {
+    public static final class SuggestionsGatherer {
+        public static final class Result {
             public final String[] mSuggestions;
             public final boolean mHasRecommendedSuggestions;
             public Result(final String[] gatheredSuggestions,
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index 5a1bd37..668e7a6 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -26,7 +26,7 @@
 
 import java.util.ArrayList;
 
-public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
+public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
     private static final String TAG = AndroidSpellCheckerSession.class.getSimpleName();
     private static final boolean DBG = false;
     private final static String[] EMPTY_STRING_ARRAY = new String[0];
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index d9b622a..53ed4d3 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -50,7 +50,7 @@
     protected final SuggestionsCache mSuggestionsCache = new SuggestionsCache();
     private final ContentObserver mObserver;
 
-    private static class SuggestionsParams {
+    private static final class SuggestionsParams {
         public final String[] mSuggestions;
         public final int mFlags;
         public SuggestionsParams(String[] suggestions, int flags) {
@@ -59,7 +59,7 @@
         }
     }
 
-    protected static class SuggestionsCache {
+    protected static final class SuggestionsCache {
         private static final char CHAR_DELIMITER = '\uFFFC';
         private static final int MAX_CACHE_SIZE = 50;
         private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache =
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
index 3dbbd40..9d7c61a 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
@@ -22,7 +22,7 @@
 /**
  * A simple container for both a Dictionary and a ProximityInfo.
  */
-public class DictAndProximity {
+public final class DictAndProximity {
     public final Dictionary mDictionary;
     public final ProximityInfo mProximityInfo;
     public DictAndProximity(final Dictionary dictionary, final ProximityInfo proximityInfo) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index 53aa6c7..1fb2bbb 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -36,7 +36,7 @@
  * the client code, but may help with sloppy clients.
  */
 @SuppressWarnings("serial")
-public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
+public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
     private final static String TAG = DictionaryPool.class.getSimpleName();
     // How many seconds we wait for a dictionary to become available. Past this delay, we give up in
     // fear some bug caused a deadlock, and reset the whole pool.
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index fe5225e..11bb970 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -22,7 +22,7 @@
 
 import java.util.TreeMap;
 
-public class SpellCheckerProximityInfo {
+public final class SpellCheckerProximityInfo {
     /* public for test */
     final public static int NUL = Constants.NOT_A_CODE;
 
@@ -53,7 +53,7 @@
         return result;
     }
 
-    private static class Latin {
+    private static final class Latin {
         // This is a map from the code point to the index in the PROXIMITY array.
         // At the time the native code to read the binary dictionary needs the proximity info be
         // passed as a flat array spaced by MAX_PROXIMITY_CHARS_SIZE columns, one for each input
@@ -122,7 +122,7 @@
         }
     }
 
-    private static class Cyrillic {
+    private static final class Cyrillic {
         final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
         // TODO: The following table is solely based on the keyboard layout. Consult with Russian
         // speakers on commonly misspelled words/letters.
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
index e14db87..e63dff3 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
@@ -23,7 +23,7 @@
 /**
  * Spell checker preference screen.
  */
-public class SpellCheckerSettingsActivity extends PreferenceActivity {
+public final class SpellCheckerSettingsActivity extends PreferenceActivity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
index 7056874..ef5123d 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
@@ -24,7 +24,7 @@
 /**
  * Preference screen.
  */
-public class SpellCheckerSettingsFragment extends PreferenceFragment {
+public final class SpellCheckerSettingsFragment extends PreferenceFragment {
     /**
      * Empty constructor for fragment generation.
      */
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 1f883aa..e9bf0fa 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -30,14 +30,14 @@
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.Utils;
 
-public class MoreSuggestions extends Keyboard {
+public final class MoreSuggestions extends Keyboard {
     public static final int SUGGESTION_CODE_BASE = 1024;
 
     MoreSuggestions(final MoreSuggestionsParam params) {
         super(params);
     }
 
-    private static class MoreSuggestionsParam extends KeyboardParams {
+    private static final class MoreSuggestionsParam extends KeyboardParams {
         private final int[] mWidths = new int[SuggestionStripView.MAX_SUGGESTIONS];
         private final int[] mRowNumbers = new int[SuggestionStripView.MAX_SUGGESTIONS];
         private final int[] mColumnOrders = new int[SuggestionStripView.MAX_SUGGESTIONS];
@@ -163,7 +163,7 @@
         }
     }
 
-    public static class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
+    public static final class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
         private final MoreSuggestionsView mPaneView;
         private SuggestedWords mSuggestions;
         private int mFromPos;
@@ -216,7 +216,7 @@
         }
     }
 
-    private static class Divider extends Key.Spacer {
+    private static final class Divider extends Key.Spacer {
         private final Drawable mIcon;
 
         public Divider(final KeyboardParams params, final Drawable icon, final int x,
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 5b23d7f..9b9a354 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -40,7 +40,7 @@
  * A view that renders a virtual {@link MoreSuggestions}. It handles rendering of keys and detecting
  * key presses and touch movements.
  */
-public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
+public final class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
     private final int[] mCoordinates = new int[2];
 
     final KeyDetector mModalPanelKeyDetector;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 9e8ab81..e926fa2 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -70,7 +70,7 @@
 
 import java.util.ArrayList;
 
-public class SuggestionStripView extends RelativeLayout implements OnClickListener,
+public final class SuggestionStripView extends RelativeLayout implements OnClickListener,
         OnLongClickListener {
     public interface Listener {
         public boolean addWordToUserDictionary(String word);
@@ -105,7 +105,7 @@
 
     private final UiHandler mHandler = new UiHandler(this);
 
-    private static class UiHandler extends StaticInnerHandlerWrapper<SuggestionStripView> {
+    private static final class UiHandler extends StaticInnerHandlerWrapper<SuggestionStripView> {
         private static final int MSG_HIDE_PREVIEW = 0;
 
         public UiHandler(SuggestionStripView outerInstance) {
@@ -131,7 +131,7 @@
         }
     }
 
-    private static class SuggestionStripViewParams {
+    private static final class SuggestionStripViewParams {
         private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
         private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
         private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
diff --git a/native/jni/src/char_utils.h b/native/jni/src/char_utils.h
index 20cf2e8..9008e36 100644
--- a/native/jni/src/char_utils.h
+++ b/native/jni/src/char_utils.h
@@ -67,5 +67,11 @@
 inline static unsigned short toBaseLowerCase(const unsigned short c) {
     return toLowerCase(toBaseChar(c));
 }
+
+inline static bool isSkippableChar(const uint16_t character) {
+    // TODO: Do not hardcode here
+    return character == '\'' || character == '-';
+}
+
 } // namespace latinime
 #endif // LATINIME_CHAR_UTILS_H
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index ad526fb..ea0f0ef 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -368,6 +368,9 @@
 #define MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION 3
 #define MIN_USER_TYPED_LENGTH_FOR_EXCESSIVE_CHARACTER_SUGGESTION 3
 
+// TODO: Remove
+#define MAX_POINTER_COUNT_FOR_G 2
+
 // Size, in bytes, of the bloom filter index for bigrams
 // 128 gives us 1024 buckets. The probability of false positive is (1 - e ** (-kn/m))**k,
 // where k is the number of hash functions, n the number of bigrams, and m the number of
diff --git a/native/jni/src/geometry_utils.h b/native/jni/src/geometry_utils.h
index 3892b46..31359e1 100644
--- a/native/jni/src/geometry_utils.h
+++ b/native/jni/src/geometry_utils.h
@@ -19,15 +19,11 @@
 
 #include <cmath>
 
-#define MAX_PATHS 2
-
 #define DEBUG_DECODER false
 
 #define M_PI_F 3.14159265f
-
 #define ROUND_FLOAT_10000(f) ((f) < 1000.0f && (f) > 0.001f) \
         ? (floorf((f) * 10000.0f) / 10000.0f) : (f)
-
 #define SQUARE_FLOAT(x) ((x) * (x))
 
 namespace latinime {
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp
index d699032..fde93b5 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/proximity_info.cpp
@@ -239,8 +239,8 @@
         // We do not have the coordinate data
         return NOT_AN_INDEX;
     }
-    const int baseLowerC = static_cast<int>(toBaseLowerCase(c));
-    hash_map_compat<int, int>::const_iterator mapPos = mCodeToKeyMap.find(baseLowerC);
+    const int lowerCode = static_cast<int>(toLowerCase(c));
+    hash_map_compat<int, int>::const_iterator mapPos = mCodeToKeyMap.find(lowerCode);
     if (mapPos != mCodeToKeyMap.end()) {
         return mapPos->second;
     }
@@ -258,7 +258,7 @@
     // TODO: Optimize
     for (int i = 0; i < KEY_COUNT; ++i) {
         const int code = mKeyCodePoints[i];
-        const int lowerCode = toBaseLowerCase(code);
+        const int lowerCode = static_cast<int>(toLowerCase(code));
         mCenterXsG[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2;
         mCenterYsG[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2;
         mCodeToKeyMap[lowerCode] = i;
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h
index 2947f9b..70942aa 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/proximity_info.h
@@ -27,11 +27,6 @@
 
 class Correction;
 
-inline bool isSkippableChar(const uint16_t character) {
-    // TODO: Do not hardcode here
-    return character == '\'' || character == '-';
-}
-
 class ProximityInfo {
  public:
     ProximityInfo(JNIEnv *env, const jstring localeJStr, const int maxProximityCharsSize,
diff --git a/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java
new file mode 100644
index 0000000..f2a17d2
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+import com.android.inputmethod.latin.UserHistoryDictionary;
+
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * Unit tests for UserHistoryDictionary
+ */
+public class UserHistoryDictionaryTests extends AndroidTestCase {
+    private static final String TAG = UserHistoryDictionaryTests.class.getSimpleName();
+    private SharedPreferences mPrefs;
+
+    private static final String[] CHARACTERS = {
+        "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
+        "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
+    };
+
+    @Override
+    public void setUp() {
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+    }
+
+    /**
+     * Generates a random word.
+     */
+    private String generateWord(final int value) {
+        final int lengthOfChars = CHARACTERS.length;
+        StringBuilder builder = new StringBuilder();
+        long lvalue = Math.abs((long)value);
+        while (lvalue > 0) {
+            builder.append(CHARACTERS[(int)(lvalue % lengthOfChars)]);
+            lvalue /= lengthOfChars;
+        }
+        return builder.toString();
+    }
+
+    private List<String> generateWords(final int number, final Random random) {
+        final Set<String> wordSet = CollectionUtils.newHashSet();
+        while (wordSet.size() < number) {
+            wordSet.add(generateWord(random.nextInt()));
+        }
+        return new ArrayList<String>(wordSet);
+    }
+
+    private void addToDict(final UserHistoryDictionary dict, final List<String> words) {
+        String prevWord = null;
+        for (String word : words) {
+            dict.forceAddWordForTest(prevWord, word, true);
+            prevWord = word;
+        }
+    }
+
+    public void testRandomWords() {
+        Log.d(TAG, "This test can be used for profiling.");
+        Log.d(TAG, "Usage: please set UserHisotoryDictionary.PROFILE_SAVE_RESTORE to true.");
+        final int numberOfWords = 1000;
+        final Random random = new Random(123456);
+        List<String> words = generateWords(numberOfWords, random);
+
+        final String locale = "testRandomWords";
+        final UserHistoryDictionary dict = UserHistoryDictionary.getInstance(getContext(),
+                locale, mPrefs);
+        dict.isTest = true;
+
+        addToDict(dict, words);
+
+        try {
+            Log.d(TAG, "waiting for adding the word ...");
+            Thread.sleep(2000);
+        } catch (InterruptedException e) {
+            Log.d(TAG, "InterruptedException: " + e);
+        }
+
+        // write to file
+        dict.close();
+
+        try {
+            Log.d(TAG, "waiting for writing ...");
+            Thread.sleep(5000);
+        } catch (InterruptedException e) {
+            Log.d(TAG, "InterruptedException: " + e);
+        }
+    }
+}