Merge "[Rlog78b] Make log privacy filtering decisions on n-grams"
diff --git a/dictionaries/ru_wordlist.combined.gz b/dictionaries/ru_wordlist.combined.gz
index dc15e60..9f4d937 100644
--- a/dictionaries/ru_wordlist.combined.gz
+++ b/dictionaries/ru_wordlist.combined.gz
Binary files differ
diff --git a/java/res/raw/main_ru.dict b/java/res/raw/main_ru.dict
index f858da4..9f3884d 100644
--- a/java/res/raw/main_ru.dict
+++ b/java/res/raw/main_ru.dict
Binary files differ
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 2f1a209..e10bdb1 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -51,46 +51,13 @@
         <!-- Blur radius of key text shadow. -->
         <attr name="keyTextShadowRadius" format="float" />
 
-        <!-- Layout resource for key press feedback.-->
-        <attr name="keyPreviewLayout" format="reference" />
         <!-- Key preview background states -->
         <attr name="state_left_edge" format="boolean" />
         <attr name="state_right_edge" format="boolean" />
         <attr name="state_has_morekeys" format="boolean" />
-        <!-- Vertical offset of the key press feedback from the key. -->
-        <attr name="keyPreviewOffset" format="dimension" />
-        <!-- Height of the key press feedback popup. -->
-        <attr name="keyPreviewHeight" format="dimension" />
-        <!-- Delay after key releasing and key press feedback dismissing in millisecond -->
-        <attr name="keyPreviewLingerTimeout" format="integer" />
 
         <!-- Amount to offset the touch Y coordinate by, for bias correction. -->
         <attr name="verticalCorrection" format="dimension" />
-
-        <!-- Layout resource for more keys panel -->
-        <attr name="moreKeysLayout" format="reference" />
-
-        <attr name="backgroundDimAlpha" format="integer" />
-
-        <!-- Attributes for PreviewPlacerView -->
-        <attr name="gestureFloatingPreviewTextSize" format="dimension" />
-        <attr name="gestureFloatingPreviewTextColor" format="color" />
-        <attr name="gestureFloatingPreviewTextOffset" format="dimension" />
-        <attr name="gestureFloatingPreviewColor" format="color" />
-        <attr name="gestureFloatingPreviewHorizontalPadding" format="dimension" />
-        <attr name="gestureFloatingPreviewVerticalPadding" format="dimension" />
-        <attr name="gestureFloatingPreviewRoundRadius" format="dimension" />
-        <!-- Delay after gesture input and gesture floating preview text dismissing in millisecond -->
-        <attr name="gestureFloatingPreviewTextLingerTimeout" format="integer" />
-        <!-- Delay after gesture trail starts fading out in millisecond. -->
-        <attr name="gesturePreviewTrailFadeoutStartDelay" format="integer" />
-        <!-- Duration while gesture preview trail is fading out in millisecond. -->
-        <attr name="gesturePreviewTrailFadeoutDuration" format="integer" />
-        <!-- Interval of updating gesture preview trail in millisecond. -->
-        <attr name="gesturePreviewTrailUpdateInterval" format="integer" />
-        <attr name="gesturePreviewTrailColor" format="color" />
-        <attr name="gesturePreviewTrailStartWidth" format="dimension" />
-        <attr name="gesturePreviewTrailEndWidth" format="dimension" />
     </declare-styleable>
 
     <declare-styleable name="MainKeyboardView">
@@ -126,8 +93,38 @@
         <attr name="longPressShiftKeyTimeout" format="integer" />
         <!-- Ignore special key timeout while typing in millisecond. -->
         <attr name="ignoreAltCodeKeyTimeout" format="integer" />
+        <!-- Layout resource for key press feedback.-->
+        <attr name="keyPreviewLayout" format="reference" />
+        <!-- Vertical offset of the key press feedback from the key. -->
+        <attr name="keyPreviewOffset" format="dimension" />
+        <!-- Height of the key press feedback popup. -->
+        <attr name="keyPreviewHeight" format="dimension" />
+        <!-- Delay after key releasing and key press feedback dismissing in millisecond -->
+        <attr name="keyPreviewLingerTimeout" format="integer" />
+        <!-- Layout resource for more keys keyboard -->
+        <attr name="moreKeysKeyboardLayout" format="reference" />
+        <attr name="backgroundDimAlpha" format="integer" />
         <!-- More keys keyboard will shown at touched point. -->
         <attr name="showMoreKeysKeyboardAtTouchedPoint" format="boolean" />
+        <!-- Delay after gesture trail starts fading out in millisecond. -->
+        <attr name="gesturePreviewTrailFadeoutStartDelay" format="integer" />
+        <!-- Duration while gesture preview trail is fading out in millisecond. -->
+        <attr name="gesturePreviewTrailFadeoutDuration" format="integer" />
+        <!-- Interval of updating gesture preview trail in millisecond. -->
+        <attr name="gesturePreviewTrailUpdateInterval" format="integer" />
+        <attr name="gesturePreviewTrailColor" format="color" />
+        <attr name="gesturePreviewTrailStartWidth" format="dimension" />
+        <attr name="gesturePreviewTrailEndWidth" format="dimension" />
+        <!-- Delay after gesture input and gesture floating preview text dismissing in millisecond -->
+        <attr name="gestureFloatingPreviewTextLingerTimeout" format="integer" />
+        <!-- Attributes for GestureFloatingPreviewText -->
+        <attr name="gestureFloatingPreviewTextSize" format="dimension" />
+        <attr name="gestureFloatingPreviewTextColor" format="color" />
+        <attr name="gestureFloatingPreviewTextOffset" format="dimension" />
+        <attr name="gestureFloatingPreviewColor" format="color" />
+        <attr name="gestureFloatingPreviewHorizontalPadding" format="dimension" />
+        <attr name="gestureFloatingPreviewVerticalPadding" format="dimension" />
+        <attr name="gestureFloatingPreviewRoundRadius" format="dimension" />
         <!-- Static threshold for gesture after fast typing (msec) -->
         <attr name="gestureStaticTimeThresholdAfterFastTyping" format="integer" />
         <!-- Static threshold for starting gesture detection (keyWidth%/sec) -->
@@ -434,4 +431,9 @@
         <!-- Enable proximity characters correction. Disabled by default. -->
         <attr name="enableProximityCharsCorrection" format="boolean" />
     </declare-styleable>
+
+    <declare-styleable name="SeekBarDialogPreference">
+        <attr name="valueFormatText" format="reference" />
+        <attr name="maxValue" format="integer" />
+    </declare-styleable>
 </resources>
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index dca370a..503e923 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -33,6 +33,7 @@
     <bool name="config_default_next_word_prediction">true</bool>
     <bool name="config_default_sound_enabled">false</bool>
     <bool name="config_default_vibration_enabled">true</bool>
+    <integer name="config_max_vibration_duration">250</integer> <!-- milliseconds -->
     <integer name="config_delay_update_suggestions">100</integer>
     <integer name="config_delay_update_old_suggestions">300</integer>
     <integer name="config_delay_update_shift_state">100</integer>
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index c7d9936..eb0934c 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -100,7 +100,7 @@
     <fraction name="center_suggestion_percentile">36%</fraction>
 
     <!-- Gesture preview trail parameters -->
-    <dimen name="gesture_preview_trail_start_width">12.6dp</dimen>
+    <dimen name="gesture_preview_trail_start_width">10.0dp</dimen>
     <dimen name="gesture_preview_trail_end_width">2.5dp</dimen>
     <!-- Gesture floating preview text parameters -->
     <dimen name="gesture_floating_preview_text_size">24dp</dimen>
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 70ace77..36412b4 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -29,19 +29,6 @@
     <string name="symbols_word_separators">"&#x0009;&#x0020;\n"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"</string>
     <!-- Word connectors -->
     <string name="symbols_word_connectors">\'-</string>
-    <!-- Symbol characters list that should switch back to the main layout -->
-    <!-- U+2018: "‘" LEFT SINGLE QUOTATION MARK
-         U+2019: "’" RIGHT SINGLE QUOTATION MARK
-         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-         U+201C: "“" LEFT DOUBLE QUOTATION MARK
-         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-         U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
-         U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -->
-    <!-- <string name="layout_switch_back_symbols">\"\'&#x2018;&#x2019;&#x201A;&#x201B;&#x201C;&#x201D;&#x201E;&#x201F;&#x00AB;&#x00BB;</string> -->
-    <string name="layout_switch_back_symbols"></string>
 
     <!--  Always show the suggestion strip -->
     <string name="prefs_suggestion_visibility_show_value">0</string>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index f9d51ff..54ff23b 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -76,8 +76,8 @@
     <!-- Description for delay for dismissing a popup on screen: default value of the delay [CHAR LIMIT=15] -->
     <string name="key_preview_popup_dismiss_default_delay">Default</string>
 
-    <!-- Units abbreviation for the keypress vibration duration (milliseconds) [CHAR LIMIT=10] -->
-    <string name="settings_keypress_vibration_duration"><xliff:g id="milliseconds">%s</xliff:g>ms</string>
+    <!-- Units abbreviation for the duration (milliseconds) [CHAR LIMIT=10] -->
+    <string name="abbreviation_unit_milliseconds"><xliff:g id="milliseconds">%s</xliff:g>ms</string>
 
     <!-- Option name for enabling or disabling the use of names of people in Contacts for suggestion and correction [CHAR LIMIT=25] -->
     <string name="use_contacts_dict">Suggest Contact names</string>
@@ -375,6 +375,16 @@
     <string name="prefs_keypress_vibration_duration_settings">Keypress vibration duration settings</string>
     <!-- Title of the settings for keypress sound volume -->
     <string name="prefs_keypress_sound_volume_settings">Keypress sound volume settings</string>
+    <!-- Title of the settings for reading an external dictionary file -->
+    <string name="prefs_read_external_dictionary">Read external dictionary file</string>
+    <!-- Message to show when there are no files to install as an external dictionary [CHAR LIMIT=100] -->
+    <string name="read_external_dictionary_no_files_message">No dictionary files in the Downloads folder</string>
+    <!-- Title of the dialog that selects a file to install as an external dictionary [CHAR LIMIT=50] -->
+    <string name="read_external_dictionary_multiple_files_title">Select a dictionary file to install</string>
+    <!-- Title of the confirmation dialog to install a file as an external dictionary [CHAR LIMIT=50] -->
+    <string name="read_external_dictionary_confirm_install_message">Really install this file for <xliff:g id="locale_name">%s</xliff:g>?</string>
+    <!-- Title for an error dialog that contains the details of the error in the body [CHAR LIMIT=80] -->
+    <string name="error">There was an error</string>
 
     <!-- Title of the button to revert to the default value of the device in the settings dialog [CHAR LIMIT=15] -->
     <string name="button_default">Default</string>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 0bcf943..c398b59 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -53,13 +53,8 @@
         <item name="keyHintLetterPadding">@dimen/key_hint_letter_padding</item>
         <item name="keyPopupHintLetterPadding">@dimen/key_popup_hint_letter_padding</item>
         <item name="keyShiftedLetterHintPadding">@dimen/key_uppercase_letter_padding</item>
-        <item name="keyPreviewLayout">@layout/key_preview</item>
         <item name="keyPreviewTextColor">@color/key_text_color_default</item>
-        <item name="keyPreviewOffset">@dimen/key_preview_offset</item>
-        <item name="keyPreviewHeight">@dimen/key_preview_height</item>
         <item name="keyPreviewTextRatio">@fraction/key_preview_text_ratio</item>
-        <item name="keyPreviewLingerTimeout">@integer/config_key_preview_linger_timeout</item>
-        <item name="moreKeysLayout">@layout/more_keys_keyboard</item>
         <item name="verticalCorrection">@dimen/keyboard_vertical_correction</item>
         <item name="keyTextShadowColor">@color/key_text_shadow_color_default</item>
         <item name="keyTextShadowRadius">2.75</item>
@@ -71,7 +66,6 @@
         <item name="gestureFloatingPreviewHorizontalPadding">@dimen/gesture_floating_preview_horizontal_padding</item>
         <item name="gestureFloatingPreviewVerticalPadding">@dimen/gesture_floating_preview_vertical_padding</item>
         <item name="gestureFloatingPreviewRoundRadius">@dimen/gesture_floating_preview_round_radius</item>
-        <item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item>
         <item name="gesturePreviewTrailFadeoutStartDelay">@integer/config_gesture_preview_trail_fadeout_start_delay</item>
         <item name="gesturePreviewTrailFadeoutDuration">@integer/config_gesture_preview_trail_fadeout_duration</item>
         <item name="gesturePreviewTrailUpdateInterval">@integer/config_gesture_preview_trail_update_interval</item>
@@ -89,12 +83,18 @@
         <item name="longPressKeyTimeout">@integer/config_long_press_key_timeout</item>
         <item name="longPressShiftKeyTimeout">@integer/config_long_press_shift_key_timeout</item>
         <item name="ignoreAltCodeKeyTimeout">@integer/config_ignore_alt_code_key_timeout</item>
+        <item name="keyPreviewLayout">@layout/key_preview</item>
+        <item name="keyPreviewOffset">@dimen/key_preview_offset</item>
+        <item name="keyPreviewHeight">@dimen/key_preview_height</item>
+        <item name="keyPreviewLingerTimeout">@integer/config_key_preview_linger_timeout</item>
+        <item name="moreKeysKeyboardLayout">@layout/more_keys_keyboard</item>
         <item name="showMoreKeysKeyboardAtTouchedPoint">@bool/config_show_more_keys_keyboard_at_touched_point</item>
         <item name="languageOnSpacebarFinalAlpha">@integer/config_language_on_spacebar_final_alpha</item>
         <item name="languageOnSpacebarFadeoutAnimator">@anim/language_on_spacebar_fadeout</item>
         <item name="altCodeKeyWhileTypingFadeoutAnimator">@anim/alt_code_key_while_typing_fadeout</item>
         <item name="altCodeKeyWhileTypingFadeinAnimator">@anim/alt_code_key_while_typing_fadein</item>
         <!-- Common attributes of MainKeyboardView for gesture typing detection and recognition -->
+        <item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item>
         <item name="gestureStaticTimeThresholdAfterFastTyping">@integer/config_gesture_static_time_threshold_after_fast_typing</item>
         <item name="gestureDetectFastMoveSpeedThreshold">@fraction/config_gesture_detect_fast_move_speed_threshold</item>
         <item name="gestureDynamicThresholdDecayDuration">@integer/config_gesture_dynamic_threshold_decay_duration</item>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 4ffbf14..cc1b52b 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -16,6 +16,7 @@
 
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     android:title="@string/english_ime_settings"
     android:key="english_ime_settings">
     <PreferenceCategory
@@ -144,12 +145,15 @@
             <ListPreference
                 android:key="pref_key_preview_popup_dismiss_delay"
                 android:title="@string/key_preview_popup_dismiss_delay" />
-            <PreferenceScreen
+            <com.android.inputmethod.latin.SeekBarDialogPreference
                 android:key="pref_vibration_duration_settings"
-                android:title="@string/prefs_keypress_vibration_duration_settings"/>
-            <PreferenceScreen
+                android:title="@string/prefs_keypress_vibration_duration_settings"
+                latin:valueFormatText="@string/abbreviation_unit_milliseconds"
+                latin:maxValue="@integer/config_max_vibration_duration" />
+            <com.android.inputmethod.latin.SeekBarDialogPreference
                 android:key="pref_keypress_sound_volume"
-                android:title="@string/prefs_keypress_sound_volume_settings" />
+                android:title="@string/prefs_keypress_sound_volume_settings"
+                latin:maxValue="100" /> <!-- percent -->
         </PreferenceScreen>
         <PreferenceScreen
             android:key="debug_settings"
diff --git a/java/res/xml/prefs_for_debug.xml b/java/res/xml/prefs_for_debug.xml
index 605a02f..8efd1c9 100644
--- a/java/res/xml/prefs_for_debug.xml
+++ b/java/res/xml/prefs_for_debug.xml
@@ -23,8 +23,7 @@
             android:title="@string/prefs_enable_log"
             android:summary="@string/prefs_description_log"
             android:persistent="true"
-            android:defaultValue="false"
-            />
+            android:defaultValue="false" />
 
     <ListPreference
             android:key="pref_keyboard_layout_20110916"
@@ -32,26 +31,27 @@
             android:persistent="true"
             android:entryValues="@array/keyboard_layout_modes_values"
             android:entries="@array/keyboard_layout_modes"
-            android:defaultValue="@string/config_default_keyboard_theme_index"
-            />
+            android:defaultValue="@string/config_default_keyboard_theme_index" />
 
     <CheckBoxPreference
             android:key="debug_mode"
             android:title="@string/prefs_debug_mode"
             android:persistent="true"
-            android:defaultValue="false"
-            />
+            android:defaultValue="false" />
 
     <CheckBoxPreference
             android:key="force_non_distinct_multitouch"
             android:title="@string/prefs_force_non_distinct_multitouch"
             android:persistent="true"
-            android:defaultValue="false"
-            />
+            android:defaultValue="false" />
 
     <CheckBoxPreference
             android:key="usability_study_mode"
             android:title="@string/prefs_usability_study_mode"
             android:persistent="true"
             android:defaultValue="false" />
+
+    <PreferenceScreen
+        android:key="read_external_dictionary"
+        android:title="@string/prefs_read_external_dictionary" />
 </PreferenceScreen>
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index d73924d..d05fd9e 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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.accessibility;
diff --git a/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java b/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
index 2fb7fe8..a2463c2 100644
--- a/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
+++ b/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
@@ -55,13 +55,20 @@
                 // A dead key.
                 return Event.createDeadEvent(
                         codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT_MASK, null /* next */);
-            } else {
-                // A committable character. This should be committed right away, taking into
-                // account the current state.
-                return Event.createCommittableEvent(codePointAndFlags, null /* next */);
             }
-        } else {
-            return Event.createNotHandledEvent();
+            if (KeyEvent.KEYCODE_ENTER == keyCode) {
+                // The Enter key. If the Shift key is not being pressed, this should send a
+                // CODE_ACTION_ENTER to trigger the action if any, or a carriage return
+                // otherwise. If the Shift key is depressed, this should send a
+                // CODE_SHIFT_ENTER and let Latin IME decide what to do with it.
+                return Event.createCommittableEvent(keyEvent.isShiftPressed()
+                        ? Constants.CODE_SHIFT_ENTER : Constants.CODE_ACTION_ENTER,
+                        null /* next */);
+            }
+            // If not Enter, then we have a committable character. This should be committed
+            // right away, taking into account the current state.
+            return Event.createCommittableEvent(codePointAndFlags, null /* next */);
         }
+        return Event.createNotHandledEvent();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 7346a9c..9b97175 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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.keyboard;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 0a91284..17e707f 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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.keyboard;
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index a1b1f5d..c2036fc 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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.keyboard;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index 14da9eb..c76acd1 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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.keyboard;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 02116ca..4b43bcc 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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.keyboard;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 2950475..f060ad0 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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.keyboard;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index bc9dbc0..30949ae 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2008 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
+ * 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
+ *      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.
+ * 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.keyboard;
@@ -149,7 +149,7 @@
                 settingsValues.isLanguageSwitchKeyEnabled());
         mKeyboardLayoutSet = builder.build();
         try {
-            mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols));
+            mState.onLoadKeyboard();
             mFeedbackManager.onSettingsChanged(settingsValues);
         } catch (KeyboardLayoutSetException e) {
             Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index b7584d4..670564c 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -28,29 +28,16 @@
 import android.graphics.Region;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
-import android.os.Message;
 import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
 import android.util.SparseArray;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
 
 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
-import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
 import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
-import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.CoordinateUtils;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
-import com.android.inputmethod.latin.StringUtils;
-import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.ResearchLogger;
 
@@ -60,30 +47,11 @@
  * A view that renders a virtual {@link Keyboard}.
  *
  * @attr ref R.styleable#KeyboardView_keyBackground
- * @attr ref R.styleable#KeyboardView_moreKeysLayout
- * @attr ref R.styleable#KeyboardView_keyPreviewLayout
- * @attr ref R.styleable#KeyboardView_keyPreviewOffset
- * @attr ref R.styleable#KeyboardView_keyPreviewHeight
- * @attr ref R.styleable#KeyboardView_keyPreviewLingerTimeout
  * @attr ref R.styleable#KeyboardView_keyLabelHorizontalPadding
  * @attr ref R.styleable#KeyboardView_keyHintLetterPadding
  * @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding
  * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding
  * @attr ref R.styleable#KeyboardView_keyTextShadowRadius
- * @attr ref R.styleable#KeyboardView_backgroundDimAlpha
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextSize
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextColor
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextOffset
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewColor
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewHorizontalPadding
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewVerticalPadding
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewRoundRadius
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextLingerTimeout
- * @attr ref R.styleable#KeyboardView_gesturePreviewTrailFadeoutStartDelay
- * @attr ref R.styleable#KeyboardView_gesturePreviewTrailFadeoutDuration
- * @attr ref R.styleable#KeyboardView_gesturePreviewTrailUpdateInterval
- * @attr ref R.styleable#KeyboardView_gesturePreviewTrailColor
- * @attr ref R.styleable#KeyboardView_gesturePreviewTrailWidth
  * @attr ref R.styleable#KeyboardView_verticalCorrection
  * @attr ref R.styleable#Keyboard_Key_keyTypeface
  * @attr ref R.styleable#Keyboard_Key_keyLetterSize
@@ -103,10 +71,7 @@
  * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintActivatedColor
  * @attr ref R.styleable#Keyboard_Key_keyPreviewTextColor
  */
-public class KeyboardView extends View implements PointerTracker.DrawingProxy,
-        MoreKeysPanel.Controller {
-    private static final String TAG = KeyboardView.class.getSimpleName();
-
+public class KeyboardView extends View {
     // XML attributes
     protected final KeyVisualAttributes mKeyVisualAttributes;
     private final int mKeyLabelHorizontalPadding;
@@ -115,10 +80,8 @@
     private final float mKeyShiftedLetterHintPadding;
     private final float mKeyTextShadowRadius;
     protected final float mVerticalCorrection;
-    protected final int mMoreKeysLayout;
     protected final Drawable mKeyBackground;
     protected final Rect mKeyBackgroundPadding = new Rect();
-    private final int mBackgroundDimAlpha;
 
     // HORIZONTAL ELLIPSIS "...", character for popup hint.
     private static final String POPUP_HINT_CHAR = "\u2026";
@@ -135,54 +98,7 @@
     private Keyboard mKeyboard;
     protected final KeyDrawParams mKeyDrawParams = new KeyDrawParams();
 
-    // Preview placer view
-    private final PreviewPlacerView mPreviewPlacerView;
-    private final int[] mOriginCoords = CoordinateUtils.newInstance();
-
-    // More keys panel (used by both more keys keyboard and more suggestions view)
-    // TODO: Consider extending to support multiple more keys panels
-    protected MoreKeysPanel mMoreKeysPanel;
-
-    // Key preview
-    private static final int PREVIEW_ALPHA = 240;
-    private final int mKeyPreviewLayoutId;
-    private final int mKeyPreviewOffset;
-    private final int mKeyPreviewHeight;
-    private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
-    protected final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams();
-    private boolean mShowKeyPreviewPopup = true;
-    private int mKeyPreviewLingerTimeout;
-
-    // Gesture floating preview text
-    // TODO: Make this parameter customizable by user via settings.
-    private int mGestureFloatingPreviewTextLingerTimeout;
-
-    // Background state set
-    private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = {
-        { // STATE_MIDDLE
-            EMPTY_STATE_SET,
-            { R.attr.state_has_morekeys }
-        },
-        { // STATE_LEFT
-            { R.attr.state_left_edge },
-            { R.attr.state_left_edge, R.attr.state_has_morekeys }
-        },
-        { // STATE_RIGHT
-            { R.attr.state_right_edge },
-            { R.attr.state_right_edge, R.attr.state_has_morekeys }
-        }
-    };
-    private static final int STATE_MIDDLE = 0;
-    private static final int STATE_LEFT = 1;
-    private static final int STATE_RIGHT = 2;
-    private static final int STATE_NORMAL = 0;
-    private static final int STATE_HAS_MOREKEYS = 1;
-    private static final int[] KEY_PREVIEW_BACKGROUND_DEFAULT_STATE =
-            KEY_PREVIEW_BACKGROUND_STATE_TABLE[STATE_MIDDLE][STATE_NORMAL];
-
     // Drawing
-    /** True if the entire keyboard needs to be dimmed. */
-    private boolean mNeedsToDimEntireKeyboard;
     /** True if all keys should be drawn */
     private boolean mInvalidateAllKeys;
     /** The keys that should be drawn */
@@ -204,55 +120,6 @@
     private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
     private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
 
-    private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
-
-    public static class DrawingHandler extends StaticInnerHandlerWrapper<KeyboardView> {
-        private static final int MSG_DISMISS_KEY_PREVIEW = 0;
-        private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
-
-        public DrawingHandler(final KeyboardView outerInstance) {
-            super(outerInstance);
-        }
-
-        @Override
-        public void handleMessage(final Message msg) {
-            final KeyboardView keyboardView = getOuterInstance();
-            if (keyboardView == null) return;
-            final PointerTracker tracker = (PointerTracker) msg.obj;
-            switch (msg.what) {
-            case MSG_DISMISS_KEY_PREVIEW:
-                final TextView previewText = keyboardView.mKeyPreviewTexts.get(tracker.mPointerId);
-                if (previewText != null) {
-                    previewText.setVisibility(INVISIBLE);
-                }
-                break;
-            case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
-                keyboardView.mPreviewPlacerView.setGestureFloatingPreviewText(SuggestedWords.EMPTY);
-                break;
-            }
-        }
-
-        public void dismissKeyPreview(final long delay, final PointerTracker tracker) {
-            sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
-        }
-
-        public void cancelDismissKeyPreview(final PointerTracker tracker) {
-            removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
-        }
-
-        private void cancelAllDismissKeyPreviews() {
-            removeMessages(MSG_DISMISS_KEY_PREVIEW);
-        }
-
-        public void dismissGestureFloatingPreviewText(final long delay) {
-            sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay);
-        }
-
-        public void cancelAllMessages() {
-            cancelAllDismissKeyPreviews();
-        }
-    }
-
     public KeyboardView(final Context context, final AttributeSet attrs) {
         this(context, attrs, R.attr.keyboardViewStyle);
     }
@@ -264,12 +131,6 @@
                 R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
         mKeyBackground = keyboardViewAttr.getDrawable(R.styleable.KeyboardView_keyBackground);
         mKeyBackground.getPadding(mKeyBackgroundPadding);
-        mKeyPreviewOffset = keyboardViewAttr.getDimensionPixelOffset(
-                R.styleable.KeyboardView_keyPreviewOffset, 0);
-        mKeyPreviewHeight = keyboardViewAttr.getDimensionPixelSize(
-                R.styleable.KeyboardView_keyPreviewHeight, 80);
-        mKeyPreviewLingerTimeout = keyboardViewAttr.getInt(
-                R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
         mKeyLabelHorizontalPadding = keyboardViewAttr.getDimensionPixelOffset(
                 R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
         mKeyHintLetterPadding = keyboardViewAttr.getDimension(
@@ -280,19 +141,8 @@
                 R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0);
         mKeyTextShadowRadius = keyboardViewAttr.getFloat(
                 R.styleable.KeyboardView_keyTextShadowRadius, 0.0f);
-        mKeyPreviewLayoutId = keyboardViewAttr.getResourceId(
-                R.styleable.KeyboardView_keyPreviewLayout, 0);
-        if (mKeyPreviewLayoutId == 0) {
-            mShowKeyPreviewPopup = false;
-        }
         mVerticalCorrection = keyboardViewAttr.getDimension(
                 R.styleable.KeyboardView_verticalCorrection, 0);
-        mMoreKeysLayout = keyboardViewAttr.getResourceId(
-                R.styleable.KeyboardView_moreKeysLayout, 0);
-        mBackgroundDimAlpha = keyboardViewAttr.getInt(
-                R.styleable.KeyboardView_backgroundDimAlpha, 0);
-        mGestureFloatingPreviewTextLingerTimeout = keyboardViewAttr.getInt(
-                R.styleable.KeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
         keyboardViewAttr.recycle();
 
         final TypedArray keyAttr = context.obtainStyledAttributes(attrs,
@@ -300,7 +150,6 @@
         mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
         keyAttr.recycle();
 
-        mPreviewPlacerView = new PreviewPlacerView(context, attrs);
         mPaint.setAntiAlias(true);
     }
 
@@ -336,33 +185,6 @@
         return mKeyboard;
     }
 
-    /**
-     * Enables or disables the key feedback popup. This is a popup that shows a magnified
-     * version of the depressed key. By default the preview is enabled.
-     * @param previewEnabled whether or not to enable the key feedback preview
-     * @param delay the delay after which the preview is dismissed
-     * @see #isKeyPreviewPopupEnabled()
-     */
-    public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
-        mShowKeyPreviewPopup = previewEnabled;
-        mKeyPreviewLingerTimeout = delay;
-    }
-
-    /**
-     * Returns the enabled state of the key feedback preview
-     * @return whether or not the key feedback preview is enabled
-     * @see #setKeyPreviewPopupEnabled(boolean, int)
-     */
-    public boolean isKeyPreviewPopupEnabled() {
-        return mShowKeyPreviewPopup;
-    }
-
-    public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
-            final boolean drawsGestureFloatingPreviewText) {
-        mPreviewPlacerView.setGesturePreviewMode(
-                drawsGesturePreviewTrail, drawsGestureFloatingPreviewText);
-    }
-
     @Override
     protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
         if (mKeyboard != null) {
@@ -375,7 +197,7 @@
     }
 
     @Override
-    public void onDraw(final Canvas canvas) {
+    protected void onDraw(final Canvas canvas) {
         super.onDraw(canvas);
         if (canvas.isHardwareAccelerated()) {
             onDrawKeyboard(canvas);
@@ -465,14 +287,6 @@
             }
         }
 
-        // Overlay a dark rectangle to dim.
-        if (mNeedsToDimEntireKeyboard) {
-            paint.setColor(Color.BLACK);
-            paint.setAlpha(mBackgroundDimAlpha);
-            // Note: clipRegion() above is in effect if it was called.
-            canvas.drawRect(0, 0, width, height, paint);
-        }
-
         // ResearchLogging indicator.
         // TODO: Reimplement using a keyboard background image specific to the ResearchLogger,
         // and remove this call.
@@ -484,14 +298,6 @@
         mInvalidateAllKeys = false;
     }
 
-    public void dimEntireKeyboard(final boolean dimmed) {
-        final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed;
-        mNeedsToDimEntireKeyboard = dimmed;
-        if (needsRedrawing) {
-            invalidateAllKeys();
-        }
-    }
-
     private void onDrawKey(final Key key, final Canvas canvas, final Paint paint) {
         final int keyDrawX = key.getDrawX() + getPaddingLeft();
         final int keyDrawY = key.mY + getPaddingTop();
@@ -801,200 +607,6 @@
         return paint;
     }
 
-    public void cancelAllMessages() {
-        mDrawingHandler.cancelAllMessages();
-    }
-
-    private TextView getKeyPreviewText(final int pointerId) {
-        TextView previewText = mKeyPreviewTexts.get(pointerId);
-        if (previewText != null) {
-            return previewText;
-        }
-        final Context context = getContext();
-        if (mKeyPreviewLayoutId != 0) {
-            previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
-        } else {
-            previewText = new TextView(context);
-        }
-        mKeyPreviewTexts.put(pointerId, previewText);
-        return previewText;
-    }
-
-    private void dismissAllKeyPreviews() {
-        final int pointerCount = mKeyPreviewTexts.size();
-        for (int id = 0; id < pointerCount; id++) {
-            final TextView previewText = mKeyPreviewTexts.get(id);
-            if (previewText != null) {
-                previewText.setVisibility(INVISIBLE);
-            }
-        }
-        PointerTracker.setReleasedKeyGraphicsToAllKeys();
-    }
-
-    @Override
-    public void dismissKeyPreview(final PointerTracker tracker) {
-        mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, tracker);
-    }
-
-    private void addKeyPreview(final TextView keyPreview) {
-        locatePreviewPlacerView();
-        mPreviewPlacerView.addView(
-                keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
-    }
-
-    private void locatePreviewPlacerView() {
-        if (mPreviewPlacerView.getParent() != null) {
-            return;
-        }
-        final int width = getWidth();
-        final int height = getHeight();
-        if (width == 0 || height == 0) {
-            // In transient state.
-            return;
-        }
-        getLocationInWindow(mOriginCoords);
-        final DisplayMetrics dm = getResources().getDisplayMetrics();
-        if (CoordinateUtils.y(mOriginCoords) < dm.heightPixels / 4) {
-            // In transient state.
-            return;
-        }
-        final View rootView = getRootView();
-        if (rootView == null) {
-            Log.w(TAG, "Cannot find root view");
-            return;
-        }
-        final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
-        // Note: It'd be very weird if we get null by android.R.id.content.
-        if (windowContentView == null) {
-            Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView");
-        } else {
-            windowContentView.addView(mPreviewPlacerView);
-            mPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, width, height);
-        }
-    }
-
-    @Override
-    public void showSlidingKeyInputPreview(final PointerTracker tracker) {
-        locatePreviewPlacerView();
-        mPreviewPlacerView.showSlidingKeyInputPreview(tracker);
-    }
-
-    @Override
-    public void dismissSlidingKeyInputPreview() {
-        mPreviewPlacerView.dismissSlidingKeyInputPreview();
-    }
-
-    public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
-        locatePreviewPlacerView();
-        mPreviewPlacerView.setGestureFloatingPreviewText(suggestedWords);
-    }
-
-    public void dismissGestureFloatingPreviewText() {
-        locatePreviewPlacerView();
-        mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout);
-    }
-
-    @Override
-    public void showGesturePreviewTrail(final PointerTracker tracker,
-            final boolean isOldestTracker) {
-        locatePreviewPlacerView();
-        mPreviewPlacerView.invalidatePointer(tracker, isOldestTracker);
-    }
-
-    @Override
-    public void showKeyPreview(final PointerTracker tracker) {
-        final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
-        if (!mShowKeyPreviewPopup) {
-            previewParams.mPreviewVisibleOffset = -mKeyboard.mVerticalGap;
-            return;
-        }
-
-        final TextView previewText = getKeyPreviewText(tracker.mPointerId);
-        // If the key preview has no parent view yet, add it to the ViewGroup which can place
-        // key preview absolutely in SoftInputWindow.
-        if (previewText.getParent() == null) {
-            addKeyPreview(previewText);
-        }
-
-        mDrawingHandler.cancelDismissKeyPreview(tracker);
-        final Key key = tracker.getKey();
-        // If key is invalid or IME is already closed, we must not show key preview.
-        // Trying to show key preview while root window is closed causes
-        // WindowManager.BadTokenException.
-        if (key == null) {
-            return;
-        }
-
-        final KeyDrawParams drawParams = mKeyDrawParams;
-        previewText.setTextColor(drawParams.mPreviewTextColor);
-        final Drawable background = previewText.getBackground();
-        if (background != null) {
-            background.setState(KEY_PREVIEW_BACKGROUND_DEFAULT_STATE);
-            background.setAlpha(PREVIEW_ALPHA);
-        }
-        final String label = key.isShiftedLetterActivated() ? key.mHintLabel : key.mLabel;
-        // What we show as preview should match what we show on a key top in onDraw().
-        if (label != null) {
-            // TODO Should take care of temporaryShiftLabel here.
-            previewText.setCompoundDrawables(null, null, null, null);
-            if (StringUtils.codePointCount(label) > 1) {
-                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mLetterSize);
-                previewText.setTypeface(Typeface.DEFAULT_BOLD);
-            } else {
-                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mPreviewTextSize);
-                previewText.setTypeface(key.selectTypeface(drawParams));
-            }
-            previewText.setText(label);
-        } else {
-            previewText.setCompoundDrawables(null, null, null,
-                    key.getPreviewIcon(mKeyboard.mIconsSet));
-            previewText.setText(null);
-        }
-
-        previewText.measure(
-                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-        final int keyDrawWidth = key.getDrawWidth();
-        final int previewWidth = previewText.getMeasuredWidth();
-        final int previewHeight = mKeyPreviewHeight;
-        // The width and height of visible part of the key preview background. The content marker
-        // of the background 9-patch have to cover the visible part of the background.
-        previewParams.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
-                - previewText.getPaddingRight();
-        previewParams.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
-                - previewText.getPaddingBottom();
-        // The distance between the top edge of the parent key and the bottom of the visible part
-        // of the key preview background.
-        previewParams.mPreviewVisibleOffset = mKeyPreviewOffset - previewText.getPaddingBottom();
-        getLocationInWindow(mOriginCoords);
-        // The key preview is horizontally aligned with the center of the visible part of the
-        // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
-        // the left/right background is used if such background is specified.
-        final int statePosition;
-        int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
-                + CoordinateUtils.x(mOriginCoords);
-        if (previewX < 0) {
-            previewX = 0;
-            statePosition = STATE_LEFT;
-        } else if (previewX > getWidth() - previewWidth) {
-            previewX = getWidth() - previewWidth;
-            statePosition = STATE_RIGHT;
-        } else {
-            statePosition = STATE_MIDDLE;
-        }
-        // The key preview is placed vertically above the top edge of the parent key with an
-        // arbitrary offset.
-        final int previewY = key.mY - previewHeight + mKeyPreviewOffset
-                + CoordinateUtils.y(mOriginCoords);
-
-        if (background != null) {
-            final int hasMoreKeys = (key.mMoreKeys != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
-            background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
-        }
-        ViewLayoutUtils.placeViewAt(
-                previewText, previewX, previewY, previewWidth, previewHeight);
-        previewText.setVisibility(VISIBLE);
-    }
-
     /**
      * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
      * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
@@ -1014,7 +626,6 @@
      * @param key key in the attached {@link Keyboard}.
      * @see #invalidateAllKeys
      */
-    @Override
     public void invalidateKey(final Key key) {
         if (mInvalidateAllKeys) return;
         if (key == null) return;
@@ -1025,53 +636,15 @@
     }
 
     public void closing() {
-        dismissAllKeyPreviews();
-        cancelAllMessages();
-        onCancelMoreKeysPanel();
         mInvalidateAllKeys = true;
-        requestLayout();
-    }
-
-    @Override
-    public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
-        if (isShowingMoreKeysPanel()) {
-            onDismissMoreKeysPanel();
-        }
-        mMoreKeysPanel = panel;
-        mPreviewPlacerView.addView(mMoreKeysPanel.getContainerView());
-    }
-
-    public boolean isShowingMoreKeysPanel() {
-        return (mMoreKeysPanel != null);
-    }
-
-    @Override
-    public void onCancelMoreKeysPanel() {
-        if (isShowingMoreKeysPanel()) {
-            mMoreKeysPanel.dismissMoreKeysPanel();
-        }
-    }
-
-    @Override
-    public boolean onDismissMoreKeysPanel() {
-        if (isShowingMoreKeysPanel()) {
-            mPreviewPlacerView.removeView(mMoreKeysPanel.getContainerView());
-            mMoreKeysPanel = null;
-            return true;
-        }
-        return false;
-    }
-
-    public void purgeKeyboardAndClosing() {
         mKeyboard = null;
-        closing();
+        requestLayout();
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         closing();
-        mPreviewPlacerView.removeAllViews();
         freeOffscreenBuffer();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 625575d..d8ff5c2 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -24,6 +24,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Paint.Align;
 import android.graphics.Typeface;
@@ -32,21 +33,31 @@
 import android.os.SystemClock;
 import android.preference.PreferenceManager;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.inputmethod.InputMethodSubtype;
+import android.widget.TextView;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
 import com.android.inputmethod.annotations.ExternallyReferenced;
 import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.keyboard.internal.GestureFloatingPreviewText;
+import com.android.inputmethod.keyboard.internal.GestureTrailsPreview;
 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
+import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
+import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
+import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview;
 import com.android.inputmethod.keyboard.internal.TouchScreenRegulator;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.CoordinateUtils;
 import com.android.inputmethod.latin.DebugSettings;
@@ -57,6 +68,7 @@
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
 import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.SubtypeLocale;
+import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.ResearchLogger;
@@ -85,7 +97,14 @@
  * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout
  * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout
  * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout
+ * @attr ref R.styleable#MainKeyboardView_keyPreviewLayout
+ * @attr ref R.styleable#MainKeyboardView_keyPreviewOffset
+ * @attr ref R.styleable#MainKeyboardView_keyPreviewHeight
+ * @attr ref R.styleable#MainKeyboardView_keyPreviewLingerTimeout
+ * @attr ref R.styleable#MainKeyboardView_moreKeysKeyboardLayout
+ * @attr ref R.styleable#MainKeyboardView_backgroundDimAlpha
  * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint
+ * @attr ref R.styleable#MainKeyboardView_gestureFloatingPreviewTextLingerTimeout
  * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping
  * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold
  * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration
@@ -99,6 +118,7 @@
  * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
  */
 public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
+        PointerTracker.DrawingProxy, MoreKeysPanel.Controller,
         TouchScreenRegulator.ProcessMotionEvent {
     private static final String TAG = MainKeyboardView.class.getSimpleName();
 
@@ -134,14 +154,41 @@
     private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
     private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE;
 
+    // Preview placer view
+    private final PreviewPlacerView mPreviewPlacerView;
+    private final int[] mOriginCoords = CoordinateUtils.newInstance();
+    private final GestureFloatingPreviewText mGestureFloatingPreviewText;
+    private final GestureTrailsPreview mGestureTrailsPreview;
+    private final SlidingKeyInputPreview mSlidingKeyInputPreview;
+
+    // Key preview
+    private static final int PREVIEW_ALPHA = 240;
+    private final int mKeyPreviewLayoutId;
+    private final int mKeyPreviewOffset;
+    private final int mKeyPreviewHeight;
+    private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
+    private final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams();
+    private boolean mShowKeyPreviewPopup = true;
+    private int mKeyPreviewLingerTimeout;
+
     // More keys keyboard
+    private final Paint mBackgroundDimAlphaPaint = new Paint();
+    private boolean mNeedsToDimEntireKeyboard;
     private final WeakHashMap<Key, MoreKeysPanel> mMoreKeysPanelCache =
             new WeakHashMap<Key, MoreKeysPanel>();
+    private final int mMoreKeysLayout;
     private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
+    // More keys panel (used by both more keys keyboard and more suggestions view)
+    // TODO: Consider extending to support multiple more keys panels
+    private MoreKeysPanel mMoreKeysPanel;
+
+    // Gesture floating preview text
+    // TODO: Make this parameter customizable by user via settings.
+    private int mGestureFloatingPreviewTextLingerTimeout;
 
     private final TouchScreenRegulator mTouchScreenRegulator;
 
-    protected KeyDetector mKeyDetector;
+    private KeyDetector mKeyDetector;
     private final boolean mHasDistinctMultitouch;
     private int mOldPointerCount = 1;
     private Key mOldKey;
@@ -381,6 +428,56 @@
         }
     }
 
+    private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
+
+    public static class DrawingHandler extends StaticInnerHandlerWrapper<MainKeyboardView> {
+        private static final int MSG_DISMISS_KEY_PREVIEW = 0;
+        private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
+
+        public DrawingHandler(final MainKeyboardView outerInstance) {
+            super(outerInstance);
+        }
+
+        @Override
+        public void handleMessage(final Message msg) {
+            final MainKeyboardView mainKeyboardView = getOuterInstance();
+            if (mainKeyboardView == null) return;
+            final PointerTracker tracker = (PointerTracker) msg.obj;
+            switch (msg.what) {
+            case MSG_DISMISS_KEY_PREVIEW:
+                final TextView previewText = mainKeyboardView.mKeyPreviewTexts.get(
+                        tracker.mPointerId);
+                if (previewText != null) {
+                    previewText.setVisibility(INVISIBLE);
+                }
+                break;
+            case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
+                mainKeyboardView.showGestureFloatingPreviewText(SuggestedWords.EMPTY);
+                break;
+            }
+        }
+
+        public void dismissKeyPreview(final long delay, final PointerTracker tracker) {
+            sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
+        }
+
+        public void cancelDismissKeyPreview(final PointerTracker tracker) {
+            removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
+        }
+
+        private void cancelAllDismissKeyPreviews() {
+            removeMessages(MSG_DISMISS_KEY_PREVIEW);
+        }
+
+        public void dismissGestureFloatingPreviewText(final long delay) {
+            sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay);
+        }
+
+        public void cancelAllMessages() {
+            cancelAllDismissKeyPreviews();
+        }
+    }
+
     public MainKeyboardView(final Context context, final AttributeSet attrs) {
         this(context, attrs, R.attr.mainKeyboardViewStyle);
     }
@@ -401,39 +498,73 @@
                 ResourceUtils.getDeviceOverrideValue(
                         res, R.array.phantom_sudden_move_event_device_list));
         PointerTracker.init(needsPhantomSuddenMoveEventHack);
+        mPreviewPlacerView = new PreviewPlacerView(context, attrs);
 
-        final TypedArray a = context.obtainStyledAttributes(
+        final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
                 attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
-        mAutoCorrectionSpacebarLedEnabled = a.getBoolean(
+        final int backgroundDimAlpha = mainKeyboardViewAttr.getInt(
+                R.styleable.MainKeyboardView_backgroundDimAlpha, 0);
+        mBackgroundDimAlphaPaint.setColor(Color.BLACK);
+        mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha);
+        mAutoCorrectionSpacebarLedEnabled = mainKeyboardViewAttr.getBoolean(
                 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
-        mAutoCorrectionSpacebarLedIcon = a.getDrawable(
+        mAutoCorrectionSpacebarLedIcon = mainKeyboardViewAttr.getDrawable(
                 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
-        mSpacebarTextRatio = a.getFraction(
+        mSpacebarTextRatio = mainKeyboardViewAttr.getFraction(
                 R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f);
-        mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0);
-        mSpacebarTextShadowColor = a.getColor(
+        mSpacebarTextColor = mainKeyboardViewAttr.getColor(
+                R.styleable.MainKeyboardView_spacebarTextColor, 0);
+        mSpacebarTextShadowColor = mainKeyboardViewAttr.getColor(
                 R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
-        mLanguageOnSpacebarFinalAlpha = a.getInt(
+        mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt(
                 R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha,
                 Constants.Color.ALPHA_OPAQUE);
-        final int languageOnSpacebarFadeoutAnimatorResId = a.getResourceId(
+        final int languageOnSpacebarFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId(
                 R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0);
-        final int altCodeKeyWhileTypingFadeoutAnimatorResId = a.getResourceId(
+        final int altCodeKeyWhileTypingFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId(
                 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0);
-        final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId(
+        final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId(
                 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
 
-        final float keyHysteresisDistance = a.getDimension(
+        final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension(
                 R.styleable.MainKeyboardView_keyHysteresisDistance, 0);
-        final float keyHysteresisDistanceForSlidingModifier = a.getDimension(
+        final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension(
                 R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0);
         mKeyDetector = new KeyDetector(
                 keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
-        mKeyTimerHandler = new KeyTimerHandler(this, a);
-        mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean(
+        mKeyTimerHandler = new KeyTimerHandler(this, mainKeyboardViewAttr);
+        mKeyPreviewOffset = mainKeyboardViewAttr.getDimensionPixelOffset(
+                R.styleable.MainKeyboardView_keyPreviewOffset, 0);
+        mKeyPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize(
+                R.styleable.MainKeyboardView_keyPreviewHeight, 0);
+        mKeyPreviewLingerTimeout = mainKeyboardViewAttr.getInt(
+                R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0);
+        mKeyPreviewLayoutId = mainKeyboardViewAttr.getResourceId(
+                R.styleable.MainKeyboardView_keyPreviewLayout, 0);
+        if (mKeyPreviewLayoutId == 0) {
+            mShowKeyPreviewPopup = false;
+        }
+        mMoreKeysLayout = mainKeyboardViewAttr.getResourceId(
+                R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0);
+        mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean(
                 R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
-        PointerTracker.setParameters(a);
-        a.recycle();
+
+        mGestureFloatingPreviewTextLingerTimeout = mainKeyboardViewAttr.getInt(
+                R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
+        PointerTracker.setParameters(mainKeyboardViewAttr);
+
+        mGestureFloatingPreviewText = new GestureFloatingPreviewText(
+                mPreviewPlacerView, mainKeyboardViewAttr);
+        mPreviewPlacerView.addPreview(mGestureFloatingPreviewText);
+
+        mGestureTrailsPreview = new GestureTrailsPreview(
+                mPreviewPlacerView, mainKeyboardViewAttr);
+        mPreviewPlacerView.addPreview(mGestureTrailsPreview);
+
+        mSlidingKeyInputPreview = new SlidingKeyInputPreview(
+                mPreviewPlacerView, mainKeyboardViewAttr);
+        mPreviewPlacerView.addPreview(mSlidingKeyInputPreview);
+        mainKeyboardViewAttr.recycle();
 
         mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
                 languageOnSpacebarFadeoutAnimatorResId, this);
@@ -538,6 +669,248 @@
         AccessibleKeyboardViewProxy.getInstance().setKeyboard();
     }
 
+    /**
+     * Enables or disables the key feedback popup. This is a popup that shows a magnified
+     * version of the depressed key. By default the preview is enabled.
+     * @param previewEnabled whether or not to enable the key feedback preview
+     * @param delay the delay after which the preview is dismissed
+     * @see #isKeyPreviewPopupEnabled()
+     */
+    public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
+        mShowKeyPreviewPopup = previewEnabled;
+        mKeyPreviewLingerTimeout = delay;
+    }
+
+
+    private void locatePreviewPlacerView() {
+        if (mPreviewPlacerView.getParent() != null) {
+            return;
+        }
+        final int width = getWidth();
+        final int height = getHeight();
+        if (width == 0 || height == 0) {
+            // In transient state.
+            return;
+        }
+        getLocationInWindow(mOriginCoords);
+        final DisplayMetrics dm = getResources().getDisplayMetrics();
+        if (CoordinateUtils.y(mOriginCoords) < dm.heightPixels / 4) {
+            // In transient state.
+            return;
+        }
+        final View rootView = getRootView();
+        if (rootView == null) {
+            Log.w(TAG, "Cannot find root view");
+            return;
+        }
+        final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
+        // Note: It'd be very weird if we get null by android.R.id.content.
+        if (windowContentView == null) {
+            Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView");
+        } else {
+            windowContentView.addView(mPreviewPlacerView);
+            mPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, width, height);
+        }
+    }
+
+    /**
+     * Returns the enabled state of the key feedback preview
+     * @return whether or not the key feedback preview is enabled
+     * @see #setKeyPreviewPopupEnabled(boolean, int)
+     */
+    public boolean isKeyPreviewPopupEnabled() {
+        return mShowKeyPreviewPopup;
+    }
+
+    private void addKeyPreview(final TextView keyPreview) {
+        locatePreviewPlacerView();
+        mPreviewPlacerView.addView(
+                keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
+    }
+
+    private TextView getKeyPreviewText(final int pointerId) {
+        TextView previewText = mKeyPreviewTexts.get(pointerId);
+        if (previewText != null) {
+            return previewText;
+        }
+        final Context context = getContext();
+        if (mKeyPreviewLayoutId != 0) {
+            previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
+        } else {
+            previewText = new TextView(context);
+        }
+        mKeyPreviewTexts.put(pointerId, previewText);
+        return previewText;
+    }
+
+    private void dismissAllKeyPreviews() {
+        final int pointerCount = mKeyPreviewTexts.size();
+        for (int id = 0; id < pointerCount; id++) {
+            final TextView previewText = mKeyPreviewTexts.get(id);
+            if (previewText != null) {
+                previewText.setVisibility(INVISIBLE);
+            }
+        }
+        PointerTracker.setReleasedKeyGraphicsToAllKeys();
+    }
+
+    // Background state set
+    private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = {
+        { // STATE_MIDDLE
+            EMPTY_STATE_SET,
+            { R.attr.state_has_morekeys }
+        },
+        { // STATE_LEFT
+            { R.attr.state_left_edge },
+            { R.attr.state_left_edge, R.attr.state_has_morekeys }
+        },
+        { // STATE_RIGHT
+            { R.attr.state_right_edge },
+            { R.attr.state_right_edge, R.attr.state_has_morekeys }
+        }
+    };
+    private static final int STATE_MIDDLE = 0;
+    private static final int STATE_LEFT = 1;
+    private static final int STATE_RIGHT = 2;
+    private static final int STATE_NORMAL = 0;
+    private static final int STATE_HAS_MOREKEYS = 1;
+    private static final int[] KEY_PREVIEW_BACKGROUND_DEFAULT_STATE =
+            KEY_PREVIEW_BACKGROUND_STATE_TABLE[STATE_MIDDLE][STATE_NORMAL];
+
+    @Override
+    public void showKeyPreview(final PointerTracker tracker) {
+        final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
+        final Keyboard keyboard = getKeyboard();
+        if (!mShowKeyPreviewPopup) {
+            previewParams.mPreviewVisibleOffset = -keyboard.mVerticalGap;
+            return;
+        }
+
+        final TextView previewText = getKeyPreviewText(tracker.mPointerId);
+        // If the key preview has no parent view yet, add it to the ViewGroup which can place
+        // key preview absolutely in SoftInputWindow.
+        if (previewText.getParent() == null) {
+            addKeyPreview(previewText);
+        }
+
+        mDrawingHandler.cancelDismissKeyPreview(tracker);
+        final Key key = tracker.getKey();
+        // If key is invalid or IME is already closed, we must not show key preview.
+        // Trying to show key preview while root window is closed causes
+        // WindowManager.BadTokenException.
+        if (key == null) {
+            return;
+        }
+
+        final KeyDrawParams drawParams = mKeyDrawParams;
+        previewText.setTextColor(drawParams.mPreviewTextColor);
+        final Drawable background = previewText.getBackground();
+        if (background != null) {
+            background.setState(KEY_PREVIEW_BACKGROUND_DEFAULT_STATE);
+            background.setAlpha(PREVIEW_ALPHA);
+        }
+        final String label = key.isShiftedLetterActivated() ? key.mHintLabel : key.mLabel;
+        // What we show as preview should match what we show on a key top in onDraw().
+        if (label != null) {
+            // TODO Should take care of temporaryShiftLabel here.
+            previewText.setCompoundDrawables(null, null, null, null);
+            if (StringUtils.codePointCount(label) > 1) {
+                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mLetterSize);
+                previewText.setTypeface(Typeface.DEFAULT_BOLD);
+            } else {
+                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mPreviewTextSize);
+                previewText.setTypeface(key.selectTypeface(drawParams));
+            }
+            previewText.setText(label);
+        } else {
+            previewText.setCompoundDrawables(null, null, null,
+                    key.getPreviewIcon(keyboard.mIconsSet));
+            previewText.setText(null);
+        }
+
+        previewText.measure(
+                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        final int keyDrawWidth = key.getDrawWidth();
+        final int previewWidth = previewText.getMeasuredWidth();
+        final int previewHeight = mKeyPreviewHeight;
+        // The width and height of visible part of the key preview background. The content marker
+        // of the background 9-patch have to cover the visible part of the background.
+        previewParams.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
+                - previewText.getPaddingRight();
+        previewParams.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
+                - previewText.getPaddingBottom();
+        // The distance between the top edge of the parent key and the bottom of the visible part
+        // of the key preview background.
+        previewParams.mPreviewVisibleOffset = mKeyPreviewOffset - previewText.getPaddingBottom();
+        getLocationInWindow(mOriginCoords);
+        // The key preview is horizontally aligned with the center of the visible part of the
+        // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
+        // the left/right background is used if such background is specified.
+        final int statePosition;
+        int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
+                + CoordinateUtils.x(mOriginCoords);
+        if (previewX < 0) {
+            previewX = 0;
+            statePosition = STATE_LEFT;
+        } else if (previewX > getWidth() - previewWidth) {
+            previewX = getWidth() - previewWidth;
+            statePosition = STATE_RIGHT;
+        } else {
+            statePosition = STATE_MIDDLE;
+        }
+        // The key preview is placed vertically above the top edge of the parent key with an
+        // arbitrary offset.
+        final int previewY = key.mY - previewHeight + mKeyPreviewOffset
+                + CoordinateUtils.y(mOriginCoords);
+
+        if (background != null) {
+            final int hasMoreKeys = (key.mMoreKeys != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
+            background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
+        }
+        ViewLayoutUtils.placeViewAt(
+                previewText, previewX, previewY, previewWidth, previewHeight);
+        previewText.setVisibility(VISIBLE);
+    }
+
+    @Override
+    public void dismissKeyPreview(final PointerTracker tracker) {
+        mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, tracker);
+    }
+
+    @Override
+    public void showSlidingKeyInputPreview(final PointerTracker tracker) {
+        locatePreviewPlacerView();
+        mSlidingKeyInputPreview.setPreviewPosition(tracker);
+    }
+
+    @Override
+    public void dismissSlidingKeyInputPreview() {
+        mSlidingKeyInputPreview.dismissSlidingKeyInputPreview();
+    }
+
+    public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
+            final boolean drawsGestureFloatingPreviewText) {
+        mGestureFloatingPreviewText.setPreviewEnabled(drawsGestureFloatingPreviewText);
+        mGestureTrailsPreview.setPreviewEnabled(drawsGesturePreviewTrail);
+    }
+
+    public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
+        locatePreviewPlacerView();
+        mGestureFloatingPreviewText.setSuggetedWords(suggestedWords);
+    }
+
+    public void dismissGestureFloatingPreviewText() {
+        locatePreviewPlacerView();
+        mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout);
+    }
+
+    @Override
+    public void showGesturePreviewTrail(final PointerTracker tracker) {
+        locatePreviewPlacerView();
+        mGestureFloatingPreviewText.setPreviewPosition(tracker);
+        mGestureTrailsPreview.setPreviewPosition(tracker);
+    }
+
     // Note that this method is called from a non-UI thread.
     public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
         PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
@@ -561,6 +934,7 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
+        mPreviewPlacerView.removeAllViews();
         // Notify the research logger that the keyboard view has been detached.  This is needed
         // to invalidate the reference of {@link MainKeyboardView} to null.
         if (ProductionFlag.IS_EXPERIMENTAL) {
@@ -568,12 +942,6 @@
         }
     }
 
-    @Override
-    public void cancelAllMessages() {
-        mKeyTimerHandler.cancelAllMessages();
-        super.cancelAllMessages();
-    }
-
     private boolean openMoreKeysKeyboardIfRequired(final Key parentKey,
             final PointerTracker tracker) {
         // Check if we have a popup layout specified first.
@@ -591,8 +959,7 @@
         return onLongPress(parentKey, tracker);
     }
 
-    // This default implementation returns a more keys panel.
-    protected MoreKeysPanel onCreateMoreKeysPanel(final Key parentKey) {
+    private MoreKeysPanel onCreateMoreKeysPanel(final Key parentKey) {
         if (parentKey.mMoreKeys == null) {
             return null;
         }
@@ -604,7 +971,8 @@
 
         final MoreKeysKeyboardView moreKeysKeyboardView =
                 (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view);
-        final Keyboard moreKeysKeyboard = new MoreKeysKeyboard.Builder(container, parentKey, this)
+        final Keyboard moreKeysKeyboard = new MoreKeysKeyboard.Builder(
+                container, parentKey, this, mKeyPreviewDrawParams)
                 .build();
         moreKeysKeyboardView.setKeyboard(moreKeysKeyboard);
         container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -613,14 +981,13 @@
     }
 
     /**
-     * Called when a key is long pressed. By default this will open more keys keyboard associated
-     * with this key.
+     * Called when a key is long pressed.
      * @param parentKey the key that was long pressed
      * @param tracker the pointer tracker which pressed the parent key
      * @return true if the long press is handled, false otherwise. Subclasses should call the
      * method on the base class if the subclass doesn't wish to handle the call.
      */
-    protected boolean onLongPress(final Key parentKey, final PointerTracker tracker) {
+    private boolean onLongPress(final Key parentKey, final PointerTracker tracker) {
         if (ProductionFlag.IS_EXPERIMENTAL) {
             ResearchLogger.mainKeyboardView_onLongPress();
         }
@@ -697,6 +1064,38 @@
         return PointerTracker.isAnyInSlidingKeyInput();
     }
 
+    @Override
+    public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
+        if (isShowingMoreKeysPanel()) {
+            onDismissMoreKeysPanel();
+        }
+        mMoreKeysPanel = panel;
+        mPreviewPlacerView.addView(mMoreKeysPanel.getContainerView());
+    }
+
+    public boolean isShowingMoreKeysPanel() {
+        return (mMoreKeysPanel != null);
+    }
+
+    @Override
+    public void onCancelMoreKeysPanel() {
+        if (isShowingMoreKeysPanel()) {
+            mMoreKeysPanel.dismissMoreKeysPanel();
+        }
+        PointerTracker.dismissAllMoreKeysPanels();
+    }
+
+    @Override
+    public boolean onDismissMoreKeysPanel() {
+        dimEntireKeyboard(false /* dimmed */);
+        if (isShowingMoreKeysPanel()) {
+            mPreviewPlacerView.removeView(mMoreKeysPanel.getContainerView());
+            mMoreKeysPanel = null;
+            return true;
+        }
+        return false;
+    }
+
     public int getPointerCount() {
         return mOldPointerCount;
     }
@@ -846,25 +1245,20 @@
                 eventTag + eventTime + "," + id + "," + x + "," + y + "," + size + "," + pressure);
     }
 
+    public void cancelAllMessages() {
+        mKeyTimerHandler.cancelAllMessages();
+        mDrawingHandler.cancelAllMessages();
+    }
+
     @Override
     public void closing() {
+        dismissAllKeyPreviews();
+        cancelAllMessages();
         super.closing();
         onCancelMoreKeysPanel();
         mMoreKeysPanelCache.clear();
     }
 
-    @Override
-    public void onCancelMoreKeysPanel() {
-        super.onCancelMoreKeysPanel();
-        PointerTracker.dismissAllMoreKeysPanels();
-    }
-
-    @Override
-    public boolean onDismissMoreKeysPanel() {
-        dimEntireKeyboard(false /* dimmed */);
-        return super.onDismissMoreKeysPanel();
-    }
-
     /**
      * Receives hover events from the input framework.
      *
@@ -937,6 +1331,24 @@
         invalidateKey(mSpaceKey);
     }
 
+    public void dimEntireKeyboard(final boolean dimmed) {
+        final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed;
+        mNeedsToDimEntireKeyboard = dimmed;
+        if (needsRedrawing) {
+            invalidateAllKeys();
+        }
+    }
+
+    @Override
+    protected void onDraw(final Canvas canvas) {
+        super.onDraw(canvas);
+
+        // Overlay a dark rectangle to dim.
+        if (mNeedsToDimEntireKeyboard) {
+            canvas.drawRect(0, 0, getWidth(), getHeight(), mBackgroundDimAlphaPaint);
+        }
+    }
+
     @Override
     protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
             final KeyDrawParams params) {
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index 3826a39..6df883e 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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.keyboard;
@@ -21,6 +21,7 @@
 import android.view.View;
 
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
@@ -262,9 +263,11 @@
          * @param containerView the container of {@link MoreKeysKeyboardView}.
          * @param parentKey the {@link Key} that invokes more keys keyboard.
          * @param parentKeyboardView the {@link KeyboardView} that contains the parentKey.
+         * @param keyPreviewDrawParams the parameter to place key preview.
          */
         public Builder(final View containerView, final Key parentKey,
-                final KeyboardView parentKeyboardView) {
+                final MainKeyboardView parentKeyboardView,
+                final KeyPreviewDrawParams keyPreviewDrawParams) {
             super(containerView.getContext(), new MoreKeysKeyboardParams());
             final Keyboard parentKeyboard = parentKeyboardView.getKeyboard();
             load(parentKeyboard.mMoreKeysTemplate, parentKeyboard.mId);
@@ -285,8 +288,8 @@
                 // left/right/top paddings. The bottom paddings of both backgrounds don't need to
                 // be considered because the vertical positions of both backgrounds were already
                 // adjusted with their bottom paddings deducted.
-                width = parentKeyboardView.mKeyPreviewDrawParams.mPreviewVisibleWidth;
-                height = parentKeyboardView.mKeyPreviewDrawParams.mPreviewVisibleHeight
+                width = keyPreviewDrawParams.mPreviewVisibleWidth;
+                height = keyPreviewDrawParams.mPreviewVisibleHeight
                         + mParams.mVerticalGap;
             } else {
                 width = getMaxKeyWidth(parentKeyboardView, parentKey, mParams.mDefaultKeyWidth);
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 8a5b7da..0d42ab2 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -53,7 +53,6 @@
         final Resources res = context.getResources();
         mKeyDetector = new MoreKeysDetector(
                 res.getDimension(R.dimen.more_keys_keyboard_slide_allowance));
-        setKeyPreviewPopupEnabled(false, 0);
     }
 
     @Override
@@ -76,13 +75,6 @@
     }
 
     @Override
-    public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
-        // More keys keyboard needs no pop-up key preview displayed, so we pass always false with a
-        // delay of 0. The delay does not matter actually since the popup is not shown anyway.
-        super.setKeyPreviewPopupEnabled(false, 0);
-    }
-
-    @Override
     public void showMoreKeysPanel(final View parentView, final Controller controller,
             final int pointX, final int pointY, final KeyboardActionListener listener) {
         mController = controller;
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 036372c..1988bb8 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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.keyboard;
@@ -83,7 +83,7 @@
         public void dismissKeyPreview(PointerTracker tracker);
         public void showSlidingKeyInputPreview(PointerTracker tracker);
         public void dismissSlidingKeyInputPreview();
-        public void showGesturePreviewTrail(PointerTracker tracker, boolean isOldestTracker);
+        public void showGesturePreviewTrail(PointerTracker tracker);
     }
 
     public interface TimerProxy {
@@ -709,8 +709,8 @@
         return sPointerTrackerQueue.size();
     }
 
-    private static boolean isOldestTrackerInQueue(final PointerTracker tracker) {
-        return sPointerTrackerQueue.getOldestElement() == tracker;
+    public boolean isOldestTrackerInQueue() {
+        return sPointerTrackerQueue.getOldestElement() == this;
     }
 
     private void mayStartBatchInput(final Key key) {
@@ -732,7 +732,7 @@
             dismissAllMoreKeysPanels();
         }
         mTimerProxy.cancelLongPressTimer();
-        mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this));
+        mDrawingProxy.showGesturePreviewTrail(this);
     }
 
     public void updateBatchInputByTimer(final long eventTime) {
@@ -748,7 +748,7 @@
         if (mIsTrackingCanceled) {
             return;
         }
-        mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this));
+        mDrawingProxy.showGesturePreviewTrail(this);
     }
 
     private void updateBatchInput(final long eventTime) {
@@ -789,7 +789,7 @@
         if (mIsTrackingCanceled) {
             return;
         }
-        mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this));
+        mDrawingProxy.showGesturePreviewTrail(this);
     }
 
     private void cancelBatchInput() {
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 0fdbb0d..29c65f1 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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.keyboard;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
index 8a3f064..b814fc1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
@@ -1,22 +1,23 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
 
 import android.graphics.Canvas;
+import android.view.View;
 
 import com.android.inputmethod.keyboard.PointerTracker;
 
@@ -25,9 +26,18 @@
  * GestureFloatingPrevewText, GestureTrail, and SlidingKeyInputPreview.
  */
 public abstract class AbstractDrawingPreview {
+    private final View mDrawingView;
     private boolean mPreviewEnabled;
 
-    public void setPreviewEnabled(final boolean enabled) {
+    protected AbstractDrawingPreview(final View drawingView) {
+        mDrawingView = drawingView;
+    }
+
+    public final View getDrawingView() {
+        return mDrawingView;
+    }
+
+    public final void setPreviewEnabled(final boolean enabled) {
         mPreviewEnabled = enabled;
     }
 
@@ -35,15 +45,23 @@
         return mPreviewEnabled;
     }
 
+    public void setKeyboardGeometry(final int[] originCoords, final int width, final int height) {
+        // Default implementation is empty.
+    }
+
+    public void onDetachFromWindow() {
+        // Default implementation is empty.
+    }
+
     /**
      * Draws the preview
      * @param canvas The canvas where the preview is drawn.
      */
-    public abstract void onDraw(final Canvas canvas);
+    public abstract void drawPreview(final Canvas canvas);
 
     /**
      * Set the position of the preview.
-     * @param pt The new location of the preview is based on the points in PointerTracker pt.
+     * @param tracker The new location of the preview is based on the points in PointerTracker.
      */
-    public abstract void setPreviewPosition(final PointerTracker pt);
+    public abstract void setPreviewPosition(final PointerTracker tracker);
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java b/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
index 44aa72a..33f6b49 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
index aed23a4..0954a7a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
@@ -1,22 +1,21 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
 
-import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
@@ -24,6 +23,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.text.TextUtils;
+import android.view.View;
 
 import com.android.inputmethod.keyboard.PointerTracker;
 import com.android.inputmethod.latin.CoordinateUtils;
@@ -34,6 +34,14 @@
 /**
  * The class for single gesture preview text. The class for multiple gesture preview text will be
  * derived from it.
+ *
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextSize
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextColor
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextOffset
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewColor
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewHorizontalPadding
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewVerticalPadding
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewRoundRadius
  */
 public class GestureFloatingPreviewText extends AbstractDrawingPreview {
     private static final class GesturePreviewTextParams {
@@ -49,21 +57,21 @@
 
         private static final char[] TEXT_HEIGHT_REFERENCE_CHAR = { 'M' };
 
-        public GesturePreviewTextParams(final TypedArray keyboardViewAttr) {
-            mGesturePreviewTextSize = keyboardViewAttr.getDimensionPixelSize(
-                    R.styleable.KeyboardView_gestureFloatingPreviewTextSize, 0);
-            mGesturePreviewTextColor = keyboardViewAttr.getColor(
-                    R.styleable.KeyboardView_gestureFloatingPreviewTextColor, 0);
-            mGesturePreviewTextOffset = keyboardViewAttr.getDimensionPixelOffset(
-                    R.styleable.KeyboardView_gestureFloatingPreviewTextOffset, 0);
-            mGesturePreviewColor = keyboardViewAttr.getColor(
-                    R.styleable.KeyboardView_gestureFloatingPreviewColor, 0);
-            mGesturePreviewHorizontalPadding = keyboardViewAttr.getDimension(
-                    R.styleable.KeyboardView_gestureFloatingPreviewHorizontalPadding, 0.0f);
-            mGesturePreviewVerticalPadding = keyboardViewAttr.getDimension(
-                    R.styleable.KeyboardView_gestureFloatingPreviewVerticalPadding, 0.0f);
-            mGesturePreviewRoundRadius = keyboardViewAttr.getDimension(
-                    R.styleable.KeyboardView_gestureFloatingPreviewRoundRadius, 0.0f);
+        public GesturePreviewTextParams(final TypedArray mainKeyboardViewAttr) {
+            mGesturePreviewTextSize = mainKeyboardViewAttr.getDimensionPixelSize(
+                    R.styleable.MainKeyboardView_gestureFloatingPreviewTextSize, 0);
+            mGesturePreviewTextColor = mainKeyboardViewAttr.getColor(
+                    R.styleable.MainKeyboardView_gestureFloatingPreviewTextColor, 0);
+            mGesturePreviewTextOffset = mainKeyboardViewAttr.getDimensionPixelOffset(
+                    R.styleable.MainKeyboardView_gestureFloatingPreviewTextOffset, 0);
+            mGesturePreviewColor = mainKeyboardViewAttr.getColor(
+                    R.styleable.MainKeyboardView_gestureFloatingPreviewColor, 0);
+            mGesturePreviewHorizontalPadding = mainKeyboardViewAttr.getDimension(
+                    R.styleable.MainKeyboardView_gestureFloatingPreviewHorizontalPadding, 0.0f);
+            mGesturePreviewVerticalPadding = mainKeyboardViewAttr.getDimension(
+                    R.styleable.MainKeyboardView_gestureFloatingPreviewVerticalPadding, 0.0f);
+            mGesturePreviewRoundRadius = mainKeyboardViewAttr.getDimension(
+                    R.styleable.MainKeyboardView_gestureFloatingPreviewRoundRadius, 0.0f);
 
             final Paint textPaint = new Paint();
             textPaint.setAntiAlias(true);
@@ -90,16 +98,18 @@
             PREVIEW_TEXT_ARRAY_CAPACITY);
 
     protected SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
-    protected final Context mContext;
     public final int[] mLastPointerCoords = CoordinateUtils.newInstance();
 
-    public GestureFloatingPreviewText(final TypedArray typedArray, final Context context) {
+    public GestureFloatingPreviewText(final View drawingView, final TypedArray typedArray) {
+        super(drawingView);
         mParams = new GesturePreviewTextParams(typedArray);
         mHighlightedWordIndex = 0;
-        mContext = context;
     }
 
     public void setSuggetedWords(final SuggestedWords suggestedWords) {
+        if (!isPreviewEnabled()) {
+            return;
+        }
         mSuggestedWords = suggestedWords;
         updatePreviewPosition();
     }
@@ -112,8 +122,13 @@
     }
 
     @Override
-    public void setPreviewPosition(final PointerTracker pt) {
-        pt.getLastCoordinates(mLastPointerCoords);
+    public void setPreviewPosition(final PointerTracker tracker) {
+        final boolean needsToUpdateLastPointer =
+                tracker.isOldestTrackerInQueue() && isPreviewEnabled();
+        if (!needsToUpdateLastPointer) {
+            return;
+        }
+        tracker.getLastCoordinates(mLastPointerCoords);
         updatePreviewPosition();
     }
 
@@ -122,7 +137,7 @@
      * @param canvas The canvas where preview text is drawn.
      */
     @Override
-    public void onDraw(final Canvas canvas) {
+    public void drawPreview(final Canvas canvas) {
         if (!isPreviewEnabled() || mSuggestedWords.isEmpty()
                 || TextUtils.isEmpty(mSuggestedWords.getWord(0))) {
             return;
@@ -156,7 +171,7 @@
         final float rectWidth = textWidth + hPad * 2.0f;
         final float rectHeight = textHeight + vPad * 2.0f;
 
-        final int displayWidth = mContext.getResources().getDisplayMetrics().widthPixels;
+        final int displayWidth = getDrawingView().getResources().getDisplayMetrics().widthPixels;
         final float rectX = Math.min(
                 Math.max(CoordinateUtils.x(mLastPointerCoords) - rectWidth / 2.0f, 0.0f),
                 displayWidth - rectWidth);
@@ -168,5 +183,7 @@
         final int textY = (int)(rectY + vPad) + textHeight;
         mPreviewTextXArray.add(0, textX);
         mPreviewTextYArray.add(0, textY);
+        // TODO: Should narrow the invalidate region.
+        getDrawingView().invalidate();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
index 4a8407c..b047fe0 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -1,15 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
@@ -25,6 +27,13 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.ResizableIntArray;
 
+/*
+ * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailFadeoutStartDelay
+ * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailFadeoutDuration
+ * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailUpdateInterval
+ * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailColor
+ * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailWidth
+ */
 final class GesturePreviewTrail {
     private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;
 
@@ -46,20 +55,20 @@
 
         public final int mTrailLingerDuration;
 
-        public Params(final TypedArray keyboardViewAttr) {
-            mTrailColor = keyboardViewAttr.getColor(
-                    R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
-            mTrailStartWidth = keyboardViewAttr.getDimension(
-                    R.styleable.KeyboardView_gesturePreviewTrailStartWidth, 0.0f);
-            mTrailEndWidth = keyboardViewAttr.getDimension(
-                    R.styleable.KeyboardView_gesturePreviewTrailEndWidth, 0.0f);
-            mFadeoutStartDelay = keyboardViewAttr.getInt(
-                    R.styleable.KeyboardView_gesturePreviewTrailFadeoutStartDelay, 0);
-            mFadeoutDuration = keyboardViewAttr.getInt(
-                    R.styleable.KeyboardView_gesturePreviewTrailFadeoutDuration, 0);
+        public Params(final TypedArray mainKeyboardViewAttr) {
+            mTrailColor = mainKeyboardViewAttr.getColor(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailColor, 0);
+            mTrailStartWidth = mainKeyboardViewAttr.getDimension(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailStartWidth, 0.0f);
+            mTrailEndWidth = mainKeyboardViewAttr.getDimension(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailEndWidth, 0.0f);
+            mFadeoutStartDelay = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailFadeoutStartDelay, 0);
+            mFadeoutDuration = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailFadeoutDuration, 0);
             mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
-            mUpdateInterval = keyboardViewAttr.getInt(
-                    R.styleable.KeyboardView_gesturePreviewTrailUpdateInterval, 0);
+            mUpdateInterval = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailUpdateInterval, 0);
         }
     }
 
@@ -131,10 +140,8 @@
      * @return the width of a gesture trail
      */
     private static float getWidth(final int elapsedTime, final Params params) {
-        final int deltaTime = params.mTrailLingerDuration - elapsedTime;
         final float deltaWidth = params.mTrailStartWidth - params.mTrailEndWidth;
-        return Math.max(
-                (deltaTime * deltaWidth) / params.mTrailLingerDuration, params.mTrailEndWidth);
+        return params.mTrailStartWidth - (deltaWidth * elapsedTime) / params.mTrailLingerDuration;
     }
 
     private final RoundedLine mRoundedLine = new RoundedLine();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index ea03f1b..53da47c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -1,15 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
index 7ab7e9a..fc81410 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
@@ -1,15 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
new file mode 100644
index 0000000..85558f1
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2013 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.keyboard.internal;
+
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.os.Message;
+import android.util.SparseArray;
+import android.view.View;
+
+import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.Params;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+
+/**
+ * Draw gesture trail preview graphics during gesture.
+ */
+public final class GestureTrailsPreview extends AbstractDrawingPreview {
+    private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
+            CollectionUtils.newSparseArray();
+    private final Params mGesturePreviewTrailParams;
+    private final Paint mGesturePaint;
+    private int mOffscreenWidth;
+    private int mOffscreenHeight;
+    private int mOffscreenOffsetY;
+    private Bitmap mOffscreenBuffer;
+    private final Canvas mOffscreenCanvas = new Canvas();
+    private final Rect mOffscreenSrcRect = new Rect();
+    private final Rect mDirtyRect = new Rect();
+    private final Rect mGesturePreviewTrailBoundsRect = new Rect(); // per trail
+
+    private final DrawingHandler mDrawingHandler;
+
+    private static final class DrawingHandler
+            extends StaticInnerHandlerWrapper<GestureTrailsPreview> {
+        private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 0;
+
+        private final Params mGesturePreviewTrailParams;
+
+        public DrawingHandler(final GestureTrailsPreview outerInstance,
+                final Params gesturePreviewTrailParams) {
+            super(outerInstance);
+            mGesturePreviewTrailParams = gesturePreviewTrailParams;
+        }
+
+        @Override
+        public void handleMessage(final Message msg) {
+            final GestureTrailsPreview preview = getOuterInstance();
+            if (preview == null) return;
+            switch (msg.what) {
+            case MSG_UPDATE_GESTURE_PREVIEW_TRAIL:
+                preview.getDrawingView().invalidate();
+                break;
+            }
+        }
+
+        public void postUpdateGestureTrailPreview() {
+            removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
+            sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL),
+                    mGesturePreviewTrailParams.mUpdateInterval);
+        }
+    }
+
+    public GestureTrailsPreview(final View drawingView, final TypedArray mainKeyboardViewAttr) {
+        super(drawingView);
+        mGesturePreviewTrailParams = new Params(mainKeyboardViewAttr);
+        mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
+        final Paint gesturePaint = new Paint();
+        gesturePaint.setAntiAlias(true);
+        gesturePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+        mGesturePaint = gesturePaint;
+    }
+
+    @Override
+    public void setKeyboardGeometry(final int[] originCoords, final int width, final int height) {
+        mOffscreenOffsetY = (int)(
+                height * GestureStroke.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
+        mOffscreenWidth = width;
+        mOffscreenHeight = mOffscreenOffsetY + height;
+    }
+
+    @Override
+    public void onDetachFromWindow() {
+        freeOffscreenBuffer();
+    }
+
+    private void freeOffscreenBuffer() {
+        if (mOffscreenBuffer != null) {
+            mOffscreenBuffer.recycle();
+            mOffscreenBuffer = null;
+        }
+    }
+
+    private void mayAllocateOffscreenBuffer() {
+        if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == mOffscreenWidth
+                && mOffscreenBuffer.getHeight() == mOffscreenHeight) {
+            return;
+        }
+        freeOffscreenBuffer();
+        mOffscreenBuffer = Bitmap.createBitmap(
+                mOffscreenWidth, mOffscreenHeight, Bitmap.Config.ARGB_8888);
+        mOffscreenCanvas.setBitmap(mOffscreenBuffer);
+        mOffscreenCanvas.translate(0, mOffscreenOffsetY);
+    }
+
+    private boolean drawGestureTrails(final Canvas offscreenCanvas, final Paint paint,
+            final Rect dirtyRect) {
+        // Clear previous dirty rectangle.
+        if (!dirtyRect.isEmpty()) {
+            paint.setColor(Color.TRANSPARENT);
+            paint.setStyle(Paint.Style.FILL);
+            offscreenCanvas.drawRect(dirtyRect, paint);
+        }
+        dirtyRect.setEmpty();
+        boolean needsUpdatingGesturePreviewTrail = false;
+        // Draw gesture trails to offscreen buffer.
+        synchronized (mGesturePreviewTrails) {
+            // Trails count == fingers count that have ever been active.
+            final int trailsCount = mGesturePreviewTrails.size();
+            for (int index = 0; index < trailsCount; index++) {
+                final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
+                needsUpdatingGesturePreviewTrail |=
+                        trail.drawGestureTrail(offscreenCanvas, paint,
+                                mGesturePreviewTrailBoundsRect, mGesturePreviewTrailParams);
+                // {@link #mGesturePreviewTrailBoundsRect} has bounding box of the trail.
+                dirtyRect.union(mGesturePreviewTrailBoundsRect);
+            }
+        }
+        return needsUpdatingGesturePreviewTrail;
+    }
+
+    /**
+     * Draws the preview
+     * @param canvas The canvas where the preview is drawn.
+     */
+    @Override
+    public void drawPreview(final Canvas canvas) {
+        if (!isPreviewEnabled()) {
+            return;
+        }
+        mayAllocateOffscreenBuffer();
+        // Draw gesture trails to offscreen buffer.
+        final boolean needsUpdatingGesturePreviewTrail = drawGestureTrails(
+                mOffscreenCanvas, mGesturePaint, mDirtyRect);
+        if (needsUpdatingGesturePreviewTrail) {
+            mDrawingHandler.postUpdateGestureTrailPreview();
+        }
+        // Transfer offscreen buffer to screen.
+        if (!mDirtyRect.isEmpty()) {
+            mOffscreenSrcRect.set(mDirtyRect);
+            mOffscreenSrcRect.offset(0, mOffscreenOffsetY);
+            canvas.drawBitmap(mOffscreenBuffer, mOffscreenSrcRect, mDirtyRect, null);
+            // Note: Defer clearing the dirty rectangle here because we will get cleared
+            // rectangle on the canvas.
+        }
+    }
+
+    /**
+     * Set the position of the preview.
+     * @param tracker The new location of the preview is based on the points in PointerTracker.
+     */
+    @Override
+    public void setPreviewPosition(final PointerTracker tracker) {
+        if (!isPreviewEnabled()) {
+            return;
+        }
+        GesturePreviewTrail trail;
+        synchronized (mGesturePreviewTrails) {
+            trail = mGesturePreviewTrails.get(tracker.mPointerId);
+            if (trail == null) {
+                trail = new GesturePreviewTrail();
+                mGesturePreviewTrails.put(tracker.mPointerId, trail);
+            }
+        }
+        trail.addStroke(tracker.getGestureStrokeWithPreviewPoints(), tracker.getDownTime());
+
+        // TODO: Should narrow the invalidate region.
+        getDrawingView().invalidate();
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index 9f6e2f3..509068a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
index e8cacf9..fe75e20 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
index 563d224..a048ad0 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index 0f1d5cc..04b8ecb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index 0d852dd..6ad9d28 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 7292e8e..4ac2549 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
index 642e1a1..e13dbe5 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
index b986262..2278020 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 25a1c6a..95d9ccb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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.keyboard.internal;
@@ -26,7 +26,7 @@
  *
  * This class contains all keyboard state transition logic.
  *
- * The input events are {@link #onLoadKeyboard(String)}, {@link #onSaveKeyboardState()},
+ * The input events are {@link #onLoadKeyboard()}, {@link #onSaveKeyboardState()},
  * {@link #onPressKey(int, boolean, int)}, {@link #onReleaseKey(int, boolean)},
  * {@link #onCodeInput(int, boolean, int)}, {@link #onCancelInput(boolean)},
  * {@link #onUpdateShiftState(int)}, {@link #onLongPressTimeout(int)}.
@@ -74,7 +74,6 @@
     private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3;
     private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4;
     private int mSwitchState = SWITCH_STATE_ALPHA;
-    private String mLayoutSwitchBackSymbols;
 
     private boolean mIsAlphabetMode;
     private AlphabetShiftState mAlphabetShiftState = new AlphabetShiftState();
@@ -109,15 +108,14 @@
         }
     }
 
-    public KeyboardState(SwitchActions switchActions) {
+    public KeyboardState(final SwitchActions switchActions) {
         mSwitchActions = switchActions;
     }
 
-    public void onLoadKeyboard(String layoutSwitchBackSymbols) {
+    public void onLoadKeyboard() {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onLoadKeyboard: " + this);
         }
-        mLayoutSwitchBackSymbols = layoutSwitchBackSymbols;
         // Reset alphabet shift state.
         mAlphabetShiftState.setShiftLocked(false);
         mPrevMainKeyboardWasShiftLocked = false;
@@ -177,7 +175,7 @@
     private static final int AUTOMATIC_SHIFT = 2;
     private static final int SHIFT_LOCK_SHIFTED = 3;
 
-    private void setShifted(int shiftMode) {
+    private void setShifted(final int shiftMode) {
         if (DEBUG_ACTION) {
             Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode) + " " + this);
         }
@@ -216,7 +214,7 @@
         }
     }
 
-    private void setShiftLocked(boolean shiftLocked) {
+    private void setShiftLocked(final boolean shiftLocked) {
         if (DEBUG_ACTION) {
             Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked + " " + this);
         }
@@ -313,7 +311,7 @@
         mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
     }
 
-    public void onPressKey(int code, boolean isSinglePointer, int autoCaps) {
+    public void onPressKey(final int code, final boolean isSinglePointer, final int autoCaps) {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code)
                    + " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + this);
@@ -346,7 +344,7 @@
         }
     }
 
-    public void onReleaseKey(int code, boolean withSliding) {
+    public void onReleaseKey(final int code, final boolean withSliding) {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onReleaseKey: code=" + Constants.printableCode(code)
                     + " sliding=" + withSliding + " " + this);
@@ -364,7 +362,7 @@
         mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
     }
 
-    private void onReleaseSymbol(boolean withSliding) {
+    private void onReleaseSymbol(final boolean withSliding) {
         if (mSymbolKeyState.isChording()) {
             // Switch back to the previous keyboard mode if the user chords the mode change key and
             // another key, then releases the mode change key.
@@ -378,7 +376,7 @@
         mSymbolKeyState.onRelease();
     }
 
-    public void onLongPressTimeout(int code) {
+    public void onLongPressTimeout(final int code) {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onLongPressTimeout: code=" + Constants.printableCode(code) + " " + this);
         }
@@ -388,7 +386,7 @@
         }
     }
 
-    public void onUpdateShiftState(int autoCaps) {
+    public void onUpdateShiftState(final int autoCaps) {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + " " + this);
         }
@@ -404,7 +402,7 @@
         resetKeyboardStateToAlphabet();
     }
 
-    private void updateAlphabetShiftState(int autoCaps) {
+    private void updateAlphabetShiftState(final int autoCaps) {
         if (!mIsAlphabetMode) return;
         if (!mShiftKeyState.isReleasing()) {
             // Ignore update shift state event while the shift key is being pressed (including
@@ -468,7 +466,7 @@
         }
     }
 
-    private void onReleaseShift(boolean withSliding) {
+    private void onReleaseShift(final boolean withSliding) {
         if (mIsAlphabetMode) {
             final boolean isShiftLocked = mAlphabetShiftState.isShiftLocked();
             mIsInAlphabetUnshiftedFromShifted = false;
@@ -523,7 +521,7 @@
         mShiftKeyState.onRelease();
     }
 
-    public void onCancelInput(boolean isSinglePointer) {
+    public void onCancelInput(final boolean isSinglePointer) {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onCancelInput: single=" + isSinglePointer + " " + this);
         }
@@ -542,17 +540,11 @@
                 || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
     }
 
-    private static boolean isSpaceCharacter(int c) {
+    private static boolean isSpaceCharacter(final int c) {
         return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER;
     }
 
-    private boolean isLayoutSwitchBackCharacter(int c) {
-        if (TextUtils.isEmpty(mLayoutSwitchBackSymbols)) return false;
-        if (mLayoutSwitchBackSymbols.indexOf(c) >= 0) return true;
-        return false;
-    }
-
-    public void onCodeInput(int code, boolean isSinglePointer, int autoCaps) {
+    public void onCodeInput(final int code, final boolean isSinglePointer, final int autoCaps) {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onCodeInput: code=" + Constants.printableCode(code)
                     + " single=" + isSinglePointer
@@ -592,17 +584,11 @@
                     || code == Constants.CODE_OUTPUT_TEXT)) {
                 mSwitchState = SWITCH_STATE_SYMBOL;
             }
-            // Switch back to alpha keyboard mode immediately if user types one of the switch back
-            // characters.
-            if (isLayoutSwitchBackCharacter(code)) {
-                toggleAlphabetAndSymbols();
-                mPrevSymbolsKeyboardWasShifted = false;
-            }
             break;
         case SWITCH_STATE_SYMBOL:
             // Switch back to alpha keyboard mode if user types one or more non-space/enter
-            // characters followed by a space/enter or one of the switch back characters.
-            if (isSpaceCharacter(code) || isLayoutSwitchBackCharacter(code)) {
+            // characters followed by a space/enter.
+            if (isSpaceCharacter(code)) {
                 toggleAlphabetAndSymbols();
                 mPrevSymbolsKeyboardWasShifted = false;
             }
@@ -615,7 +601,7 @@
         }
     }
 
-    private static String shiftModeToString(int shiftMode) {
+    private static String shiftModeToString(final int shiftMode) {
         switch (shiftMode) {
         case UNSHIFT: return "UNSHIFT";
         case MANUAL_SHIFT: return "MANUAL";
@@ -624,7 +610,7 @@
         }
     }
 
-    private static String switchStateToString(int switchState) {
+    private static String switchStateToString(final int switchState) {
         switch (switchState) {
         case SWITCH_STATE_ALPHA: return "ALPHA";
         case SWITCH_STATE_SYMBOL_BEGIN: return "SYMBOL-BEGIN";
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 6ad7f9b..493093e 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
@@ -528,6 +528,7 @@
         // U+064E: "َ" ARABIC FATHA
         // U+0640: "ـ" ARABIC TATWEEL
         // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
+        // Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
         /* 57 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
         /* 58 */ "\u0651",
         // U+0661: "١" ARABIC-INDIC DIGIT ONE
@@ -1327,6 +1328,7 @@
         // U+064E: "َ" ARABIC FATHA
         // U+0640: "ـ" ARABIC TATWEEL
         // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
+        // Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
         /* 57 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
         /* 58 */ "\u064B",
         // U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
index d1b4c85..db154a3 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ModifierKeyState.java b/java/src/com/android/inputmethod/keyboard/internal/ModifierKeyState.java
index b39b977..b254ab8 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/ModifierKeyState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/ModifierKeyState.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
index ca16163..b38d79f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 00fc885..2df7e5c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index 7c87467..2376110 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -17,249 +17,64 @@
 package com.android.inputmethod.keyboard.internal;
 
 import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.os.Message;
 import android.util.AttributeSet;
-import android.util.SparseArray;
 import android.widget.RelativeLayout;
 
-import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.Params;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.CoordinateUtils;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
-import com.android.inputmethod.latin.SuggestedWords;
+
+import java.util.ArrayList;
 
 public final class PreviewPlacerView extends RelativeLayout {
     private final int[] mKeyboardViewOrigin = CoordinateUtils.newInstance();
 
-    // TODO: Consolidate gesture preview trail with {@link KeyboardView}
-    private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
-            CollectionUtils.newSparseArray();
-    private final Params mGesturePreviewTrailParams;
-    private final Paint mGesturePaint;
-    private boolean mDrawsGesturePreviewTrail;
-    private int mOffscreenWidth;
-    private int mOffscreenHeight;
-    private int mOffscreenOffsetY;
-    private Bitmap mOffscreenBuffer;
-    private final Canvas mOffscreenCanvas = new Canvas();
-    private final Rect mOffscreenSrcRect = new Rect();
-    private final Rect mDirtyRect = new Rect();
-    private final Rect mGesturePreviewTrailBoundsRect = new Rect(); // per trail
-    private final GestureFloatingPreviewText mGestureFloatingPreviewText;
-    private boolean mShowSlidingKeyInputPreview;
-    private final int[] mRubberBandFrom = CoordinateUtils.newInstance();
-    private final int[] mRubberBandTo = CoordinateUtils.newInstance();
-
-    private final DrawingHandler mDrawingHandler;
-
-    // TODO: Remove drawing handler.
-    private static final class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
-        private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 0;
-
-        private final Params mGesturePreviewTrailParams;
-
-        public DrawingHandler(final PreviewPlacerView outerInstance,
-                final Params gesturePreviewTrailParams) {
-            super(outerInstance);
-            mGesturePreviewTrailParams = gesturePreviewTrailParams;
-        }
-
-        @Override
-        public void handleMessage(final Message msg) {
-            final PreviewPlacerView placerView = getOuterInstance();
-            if (placerView == null) return;
-            switch (msg.what) {
-            case MSG_UPDATE_GESTURE_PREVIEW_TRAIL:
-                placerView.invalidate();
-                break;
-            }
-        }
-
-        public void postUpdateGestureTrailPreview() {
-            removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
-            sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL),
-                    mGesturePreviewTrailParams.mUpdateInterval);
-        }
-    }
+    private final ArrayList<AbstractDrawingPreview> mPreviews = CollectionUtils.newArrayList();
 
     public PreviewPlacerView(final Context context, final AttributeSet attrs) {
-        this(context, attrs, R.attr.keyboardViewStyle);
-    }
-
-    public PreviewPlacerView(final Context context, final AttributeSet attrs, final int defStyle) {
-        super(context);
+        super(context, attrs);
         setWillNotDraw(false);
 
-        final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
-                attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
-        // TODO: mGestureFloatingPreviewText could be an instance of GestureFloatingPreviewText or
-        // MultiGesturePreviewText, depending on the user's choice in the settings.
-        mGestureFloatingPreviewText = new GestureFloatingPreviewText(keyboardViewAttr, context);
-        mGesturePreviewTrailParams = new Params(keyboardViewAttr);
-        keyboardViewAttr.recycle();
-
-        mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
-
-        final Paint gesturePaint = new Paint();
-        gesturePaint.setAntiAlias(true);
-        gesturePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
-        mGesturePaint = gesturePaint;
-
         final Paint layerPaint = new Paint();
         layerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
         setLayerType(LAYER_TYPE_HARDWARE, layerPaint);
     }
 
-    public void setKeyboardViewGeometry(final int[] originCoords, final int w, final int h) {
+    public void addPreview(final AbstractDrawingPreview preview) {
+        mPreviews.add(preview);
+    }
+
+    public void setKeyboardViewGeometry(final int[] originCoords, final int width,
+            final int height) {
         CoordinateUtils.copy(mKeyboardViewOrigin, originCoords);
-        mOffscreenOffsetY = (int)(h * GestureStroke.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
-        mOffscreenWidth = w;
-        mOffscreenHeight = mOffscreenOffsetY + h;
-    }
-
-    public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
-            final boolean drawsGestureFloatingPreviewText) {
-        mDrawsGesturePreviewTrail = drawsGesturePreviewTrail;
-        mGestureFloatingPreviewText.setPreviewEnabled(drawsGestureFloatingPreviewText);
-    }
-
-    public void invalidatePointer(final PointerTracker tracker, final boolean isOldestTracker) {
-        final boolean needsToUpdateLastPointer =
-                isOldestTracker && mGestureFloatingPreviewText.isPreviewEnabled();
-        if (needsToUpdateLastPointer) {
-            mGestureFloatingPreviewText.setPreviewPosition(tracker);
+        final int count = mPreviews.size();
+        for (int i = 0; i < count; i++) {
+            mPreviews.get(i).setKeyboardGeometry(originCoords, width, height);
         }
-
-        if (mDrawsGesturePreviewTrail) {
-            GesturePreviewTrail trail;
-            synchronized (mGesturePreviewTrails) {
-                trail = mGesturePreviewTrails.get(tracker.mPointerId);
-                if (trail == null) {
-                    trail = new GesturePreviewTrail();
-                    mGesturePreviewTrails.put(tracker.mPointerId, trail);
-                }
-            }
-            trail.addStroke(tracker.getGestureStrokeWithPreviewPoints(), tracker.getDownTime());
-        }
-
-        // TODO: Should narrow the invalidate region.
-        if (mDrawsGesturePreviewTrail || needsToUpdateLastPointer) {
-            invalidate();
-        }
-    }
-
-    public void showSlidingKeyInputPreview(final PointerTracker tracker) {
-        if (!tracker.isInSlidingKeyInputFromModifier()) {
-            mShowSlidingKeyInputPreview = false;
-            return;
-        }
-        tracker.getDownCoordinates(mRubberBandFrom);
-        tracker.getLastCoordinates(mRubberBandTo);
-        mShowSlidingKeyInputPreview = true;
-        invalidate();
-    }
-
-    public void dismissSlidingKeyInputPreview() {
-        mShowSlidingKeyInputPreview = false;
     }
 
     @Override
     protected void onDetachedFromWindow() {
-        freeOffscreenBuffer();
-    }
-
-    private void freeOffscreenBuffer() {
-        if (mOffscreenBuffer != null) {
-            mOffscreenBuffer.recycle();
-            mOffscreenBuffer = null;
+        super.onDetachedFromWindow();
+        final int count = mPreviews.size();
+        for (int i = 0; i < count; i++) {
+            mPreviews.get(i).onDetachFromWindow();
         }
     }
 
-    private void mayAllocateOffscreenBuffer() {
-        if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == mOffscreenWidth
-                && mOffscreenBuffer.getHeight() == mOffscreenHeight) {
-            return;
-        }
-        freeOffscreenBuffer();
-        mOffscreenBuffer = Bitmap.createBitmap(
-                mOffscreenWidth, mOffscreenHeight, Bitmap.Config.ARGB_8888);
-        mOffscreenCanvas.setBitmap(mOffscreenBuffer);
-        mOffscreenCanvas.translate(0, mOffscreenOffsetY);
-    }
-
     @Override
     public void onDraw(final Canvas canvas) {
         super.onDraw(canvas);
         final int originX = CoordinateUtils.x(mKeyboardViewOrigin);
         final int originY = CoordinateUtils.y(mKeyboardViewOrigin);
         canvas.translate(originX, originY);
-        if (mDrawsGesturePreviewTrail) {
-            mayAllocateOffscreenBuffer();
-            // Draw gesture trails to offscreen buffer.
-            final boolean needsUpdatingGesturePreviewTrail = drawGestureTrails(
-                    mOffscreenCanvas, mGesturePaint, mDirtyRect);
-            if (needsUpdatingGesturePreviewTrail) {
-                mDrawingHandler.postUpdateGestureTrailPreview();
-            }
-            // Transfer offscreen buffer to screen.
-            if (!mDirtyRect.isEmpty()) {
-                mOffscreenSrcRect.set(mDirtyRect);
-                mOffscreenSrcRect.offset(0, mOffscreenOffsetY);
-                canvas.drawBitmap(mOffscreenBuffer, mOffscreenSrcRect, mDirtyRect, null);
-                // Note: Defer clearing the dirty rectangle here because we will get cleared
-                // rectangle on the canvas.
-            }
-        }
-        mGestureFloatingPreviewText.onDraw(canvas);
-        if (mShowSlidingKeyInputPreview) {
-            drawSlidingKeyInputPreview(canvas);
+        final int count = mPreviews.size();
+        for (int i = 0; i < count; i++) {
+            mPreviews.get(i).drawPreview(canvas);
         }
         canvas.translate(-originX, -originY);
     }
-
-    private boolean drawGestureTrails(final Canvas offscreenCanvas, final Paint paint,
-            final Rect dirtyRect) {
-        // Clear previous dirty rectangle.
-        if (!dirtyRect.isEmpty()) {
-            paint.setColor(Color.TRANSPARENT);
-            paint.setStyle(Paint.Style.FILL);
-            offscreenCanvas.drawRect(dirtyRect, paint);
-        }
-        dirtyRect.setEmpty();
-        boolean needsUpdatingGesturePreviewTrail = false;
-        // Draw gesture trails to offscreen buffer.
-        synchronized (mGesturePreviewTrails) {
-            // Trails count == fingers count that have ever been active.
-            final int trailsCount = mGesturePreviewTrails.size();
-            for (int index = 0; index < trailsCount; index++) {
-                final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
-                needsUpdatingGesturePreviewTrail |=
-                        trail.drawGestureTrail(offscreenCanvas, paint,
-                                mGesturePreviewTrailBoundsRect, mGesturePreviewTrailParams);
-                // {@link #mGesturePreviewTrailBoundsRect} has bounding box of the trail.
-                dirtyRect.union(mGesturePreviewTrailBoundsRect);
-            }
-        }
-        return needsUpdatingGesturePreviewTrail;
-    }
-
-    public void setGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
-        if (!mGestureFloatingPreviewText.isPreviewEnabled()) return;
-        mGestureFloatingPreviewText.setSuggetedWords(suggestedWords);
-        invalidate();
-    }
-
-    private void drawSlidingKeyInputPreview(final Canvas canvas) {
-        // TODO: Implement rubber band preview
-    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
index cd6efc4..2eefd6a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
@@ -1,15 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java b/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java
index 90db73d..a497b2e 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
new file mode 100644
index 0000000..322f981
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013 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.keyboard.internal;
+
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.view.View;
+
+import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.latin.CoordinateUtils;
+
+/**
+ * Draw rubber band preview graphics during sliding key input.
+ */
+public final class SlidingKeyInputPreview extends AbstractDrawingPreview {
+    private boolean mShowSlidingKeyInputPreview;
+    private final int[] mRubberBandFrom = CoordinateUtils.newInstance();
+    private final int[] mRubberBandTo = CoordinateUtils.newInstance();
+
+    public SlidingKeyInputPreview(final View drawingView, final TypedArray mainKeyboardViewAttr) {
+        super(drawingView);
+    }
+
+    public void dismissSlidingKeyInputPreview() {
+        mShowSlidingKeyInputPreview = false;
+    }
+
+    /**
+     * Draws the preview
+     * @param canvas The canvas where the preview is drawn.
+     */
+    @Override
+    public void drawPreview(final Canvas canvas) {
+        if (!isPreviewEnabled() || mShowSlidingKeyInputPreview == false) {
+            return;
+        }
+        // TODO: Implement rubber band preview
+    }
+
+    /**
+     * Set the position of the preview.
+     * @param tracker The new location of the preview is based on the points in PointerTracker.
+     */
+    @Override
+    public void setPreviewPosition(final PointerTracker tracker) {
+        if (!tracker.isInSlidingKeyInputFromModifier()) {
+            mShowSlidingKeyInputPreview = false;
+            return;
+        }
+        tracker.getDownCoordinates(mRubberBandFrom);
+        tracker.getLastCoordinates(mRubberBandTo);
+        mShowSlidingKeyInputPreview = true;
+        getDrawingView().invalidate();
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
index d7a2b6f..a75384b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java b/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java
index e7a0a70..ac83ff8 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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.keyboard.internal;
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
index 820733b..99b95ea 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
index a7e85e4..9e5e183 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
@@ -1,17 +1,17 @@
-/**
+/*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
index 29c733b..16296f0 100644
--- a/java/src/com/android/inputmethod/latin/AssetFileAddress.java
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index 8a16131..986b1a1 100644
--- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
@@ -29,8 +29,6 @@
  * complexity of settings and the like.
  */
 public final class AudioAndHapticFeedbackManager {
-    public static final int MAX_KEYPRESS_VIBRATION_DURATION = 250; // millisecond
-
     private AudioManager mAudioManager;
     private Vibrator mVibrator;
 
diff --git a/java/src/com/android/inputmethod/latin/BackupAgent.java b/java/src/com/android/inputmethod/latin/BackupAgent.java
index 54f77ba..1f04461 100644
--- a/java/src/com/android/inputmethod/latin/BackupAgent.java
+++ b/java/src/com/android/inputmethod/latin/BackupAgent.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2008 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 27af3d1..59d51b0 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2008 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 5eab292..d0bd01f 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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;
@@ -25,6 +25,7 @@
 import android.util.Log;
 
 import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -162,9 +163,9 @@
             InputStream inputStream = null;
             InputStream uncompressedStream = null;
             InputStream decryptedStream = null;
-            BufferedInputStream bufferedStream = null;
+            BufferedInputStream bufferedInputStream = null;
             File outputFile = null;
-            FileOutputStream outputStream = null;
+            BufferedOutputStream bufferedOutputStream = null;
             AssetFileDescriptor afd = null;
             final Uri wordListUri = wordListUriBuilder.build();
             try {
@@ -178,7 +179,6 @@
                 // Just to be sure, delete the file. This may fail silently, and return false: this
                 // is the right thing to do, as we just want to continue anyway.
                 outputFile.delete();
-                outputStream = new FileOutputStream(outputFile);
                 // Get the appropriate decryption method for this try
                 switch (mode) {
                     case COMPRESSED_CRYPTED_COMPRESSED:
@@ -206,10 +206,11 @@
                         inputStream = originalSourceStream;
                         break;
                 }
-                bufferedStream = new BufferedInputStream(inputStream);
-                checkMagicAndCopyFileTo(bufferedStream, outputStream);
-                outputStream.flush();
-                outputStream.close();
+                bufferedInputStream = new BufferedInputStream(inputStream);
+                bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(outputFile));
+                checkMagicAndCopyFileTo(bufferedInputStream, bufferedOutputStream);
+                bufferedOutputStream.flush();
+                bufferedOutputStream.close();
                 final File finalFile = new File(finalFileName);
                 finalFile.delete();
                 if (!outputFile.renameTo(finalFile)) {
@@ -241,12 +242,12 @@
                     if (null != inputStream) inputStream.close();
                     if (null != uncompressedStream) uncompressedStream.close();
                     if (null != decryptedStream) decryptedStream.close();
-                    if (null != bufferedStream) bufferedStream.close();
+                    if (null != bufferedInputStream) bufferedInputStream.close();
                 } catch (Exception e) {
                     Log.e(TAG, "Exception while closing a file descriptor : " + e);
                 }
                 try {
-                    if (null != outputStream) outputStream.close();
+                    if (null != bufferedOutputStream) bufferedOutputStream.close();
                 } catch (Exception e) {
                     Log.e(TAG, "Exception while closing a file : " + e);
                 }
@@ -301,9 +302,8 @@
      * @param input the stream to be copied.
      * @param output an output stream to copy the data to.
      */
-    // TODO: make output a BufferedOutputStream
-    private static void checkMagicAndCopyFileTo(final BufferedInputStream input,
-            final FileOutputStream output) throws FileNotFoundException, IOException {
+    public static void checkMagicAndCopyFileTo(final BufferedInputStream input,
+            final BufferedOutputStream output) throws FileNotFoundException, IOException {
         // Check the magic number
         final int length = MAGIC_NUMBER_VERSION_2.length;
         final byte[] magicNumberBuffer = new byte[length];
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 83dabbe..22b5cd5 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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;
@@ -56,7 +56,7 @@
     private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs";
 
     // Name of the category for the main dictionary
-    private static final String MAIN_DICTIONARY_CATEGORY = "main";
+    public static final String MAIN_DICTIONARY_CATEGORY = "main";
     public static final String ID_CATEGORY_SEPARATOR = ":";
 
     // The key considered to read the version attribute in a dictionary file.
diff --git a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java b/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
index 7f7ff31..489a74e 100644
--- a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
+++ b/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 483504c..748900b 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index d1b32a2..8b5a76a 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -1,15 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 9be8784..7df266e 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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;
@@ -25,6 +25,7 @@
 import android.preference.CheckBoxPreference;
 import android.preference.Preference;
 import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
 import android.util.Log;
 
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
@@ -38,6 +39,7 @@
     public static final String PREF_FORCE_NON_DISTINCT_MULTITOUCH = "force_non_distinct_multitouch";
     public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
     public static final String PREF_STATISTICS_LOGGING = "enable_logging";
+    private static final String PREF_READ_EXTERNAL_DICTIONARY = "read_external_dictionary";
     private static final boolean SHOW_STATISTICS_LOGGING = false;
 
     private boolean mServiceNeedsRestart = false;
@@ -66,6 +68,21 @@
             }
         }
 
+        PreferenceScreen readExternalDictionary =
+                (PreferenceScreen) findPreference(PREF_READ_EXTERNAL_DICTIONARY);
+        if (null != readExternalDictionary) {
+            readExternalDictionary.setOnPreferenceClickListener(
+                    new Preference.OnPreferenceClickListener() {
+                        @Override
+                        public boolean onPreferenceClick(final Preference arg0) {
+                            ExternalDictionaryGetterForDebug.chooseAndInstallDictionary(
+                                    getActivity());
+                            mServiceNeedsRestart = true;
+                            return true;
+                        }
+                    });
+        }
+
         mServiceNeedsRestart = false;
         mDebugMode = (CheckBoxPreference) findPreference(PREF_DEBUG_MODE);
         updateDebugMode();
diff --git a/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java b/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
index 6ef19ee..2a501a6 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index a218509..ff3d83f 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2008 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 9a771cf..2832ad4 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 22cf5b3..388ad6c 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
index f2f3fbd..a8513ff 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 2c7fdcc..28ed88a 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -1,15 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java b/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java
new file mode 100644
index 0000000..6e5a37c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2013 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 android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Environment;
+import android.util.Log;
+
+import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
+import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * A class to read a local file as a dictionary for debugging purposes.
+ */
+public class ExternalDictionaryGetterForDebug {
+    private static final String SOURCE_FOLDER = Environment.getExternalStorageDirectory().getPath()
+            + "/Download";
+    private static final String DICTIONARY_LOCALE_ATTRIBUTE = "locale";
+
+    private static FileHeader getDictionaryFileHeaderOrNull(final File file) {
+        try {
+            final FileHeader header = BinaryDictIOUtils.getDictionaryFileHeader(file);
+            return header;
+        } catch (UnsupportedFormatException e) {
+            return null;
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    private static String[] findDictionariesInTheDownloadedFolder() {
+        final File[] files = new File(SOURCE_FOLDER).listFiles();
+        final ArrayList<String> eligibleList = CollectionUtils.newArrayList();
+        for (File f : files) {
+            final FileHeader header = getDictionaryFileHeaderOrNull(f);
+            if (null == header) continue;
+            eligibleList.add(f.getName());
+        }
+        return eligibleList.toArray(new String[0]);
+    }
+
+    public static void chooseAndInstallDictionary(final Context context) {
+        final String[] fileNames = findDictionariesInTheDownloadedFolder();
+        if (0 == fileNames.length) {
+            showNoFileDialog(context);
+        } else if (1 == fileNames.length) {
+            askInstallFile(context, fileNames[0]);
+        } else {
+            showChooseFileDialog(context, fileNames);
+        }
+    }
+
+    private static void showNoFileDialog(final Context context) {
+        new AlertDialog.Builder(context)
+                .setMessage(R.string.read_external_dictionary_no_files_message)
+                .setPositiveButton(android.R.string.ok, new OnClickListener() {
+                    @Override
+                    public void onClick(final DialogInterface dialog, final int which) {
+                        dialog.dismiss();
+                    }
+                }).create().show();
+    }
+
+    private static void showChooseFileDialog(final Context context, final String[] fileNames) {
+        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+        builder.setTitle(R.string.read_external_dictionary_multiple_files_title)
+                .setItems(fileNames, new OnClickListener() {
+                    @Override
+                    public void onClick(final DialogInterface dialog, final int which) {
+                        askInstallFile(context, fileNames[which]);
+                    }
+                })
+                .create().show();
+    }
+
+    private static void askInstallFile(final Context context, final String fileName) {
+        final File file = new File(SOURCE_FOLDER, fileName.toString());
+        final FileHeader header = getDictionaryFileHeaderOrNull(file);
+        final StringBuilder message = new StringBuilder();
+        final String locale =
+                header.mDictionaryOptions.mAttributes.get(DICTIONARY_LOCALE_ATTRIBUTE);
+        for (String key : header.mDictionaryOptions.mAttributes.keySet()) {
+            message.append(key + " = " + header.mDictionaryOptions.mAttributes.get(key));
+            message.append("\n");
+        }
+        final String languageName = LocaleUtils.constructLocaleFromString(locale)
+                .getDisplayName(Locale.getDefault());
+        final String title = String.format(
+                context.getString(R.string.read_external_dictionary_confirm_install_message),
+                languageName);
+        new AlertDialog.Builder(context)
+                .setTitle(title)
+                .setMessage(message)
+                .setNegativeButton(android.R.string.cancel, new OnClickListener() {
+                    @Override
+                    public void onClick(final DialogInterface dialog, final int which) {
+                        dialog.dismiss();
+                    }
+                }).setPositiveButton(android.R.string.ok, new OnClickListener() {
+                    @Override
+                    public void onClick(final DialogInterface dialog, final int which) {
+                        installFile(context, file, header);
+                        dialog.dismiss();
+                    }
+                }).create().show();
+    }
+
+    private static void installFile(final Context context, final File file,
+            final FileHeader header) {
+        BufferedOutputStream outputStream = null;
+        File tempFile = null;
+        try {
+            final String locale =
+                    header.mDictionaryOptions.mAttributes.get(DICTIONARY_LOCALE_ATTRIBUTE);
+            // Create the id for a main dictionary for this locale
+            final String id = BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY
+                    + BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale;
+            final String finalFileName =
+                    BinaryDictionaryGetter.getCacheFileName(id, locale, context);
+            final String tempFileName = BinaryDictionaryGetter.getTempFileName(id, context);
+            tempFile = new File(tempFileName);
+            tempFile.delete();
+            outputStream = new BufferedOutputStream(new FileOutputStream(tempFile));
+            final BufferedInputStream bufferedStream = new BufferedInputStream(
+                    new FileInputStream(file));
+            BinaryDictionaryFileDumper.checkMagicAndCopyFileTo(bufferedStream, outputStream);
+            outputStream.flush();
+            final File finalFile = new File(finalFileName);
+            finalFile.delete();
+            if (!tempFile.renameTo(finalFile)) {
+                throw new IOException("Can't move the file to its final name");
+            }
+        } catch (IOException e) {
+            // There was an error: show a dialog
+            new AlertDialog.Builder(context)
+                    .setTitle(R.string.error)
+                    .setMessage(e.toString())
+                    .setPositiveButton(android.R.string.ok, new OnClickListener() {
+                        @Override
+                        public void onClick(final DialogInterface dialog, final int which) {
+                            dialog.dismiss();
+                        }
+                    }).create().show();
+            return;
+        } finally {
+            try {
+                if (null != outputStream) outputStream.close();
+                if (null != tempFile) tempFile.delete();
+            } catch (IOException e) {
+                // Don't do anything
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/FileTransforms.java b/java/src/com/android/inputmethod/latin/FileTransforms.java
index 09cf23a..692f3c7 100644
--- a/java/src/com/android/inputmethod/latin/FileTransforms.java
+++ b/java/src/com/android/inputmethod/latin/FileTransforms.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 1c5bff3..dd58db5 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index 7dffd96..4d6c4f3 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
index e2eacb3..2ad619b 100644
--- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/InputView.java b/java/src/com/android/inputmethod/latin/InputView.java
index d7595bf..5359c81 100644
--- a/java/src/com/android/inputmethod/latin/InputView.java
+++ b/java/src/com/android/inputmethod/latin/InputView.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index a4019e9..826dc11 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d02c4df..a48778a 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2008 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
+ * 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
+ *      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.
+ * 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;
@@ -82,6 +82,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Locale;
+import java.util.TreeSet;
 
 /**
  * Input method implementation for Qwerty'ish keyboard.
@@ -167,6 +168,7 @@
     private int mDeleteCount;
     private long mLastKeyTime;
     private int mActionId;
+    private TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet();
 
     // Member variables for remembering the current device orientation.
     private int mDisplayOrientation;
@@ -721,6 +723,7 @@
         resetComposingState(true /* alsoResetLastComposedWord */);
         mDeleteCount = 0;
         mSpaceState = SPACE_STATE_NONE;
+        mCurrentlyPressedHardwareKeys.clear();
 
         if (mSuggestionStripView != null) {
             // This will set the punctuation suggestions if next word suggestion is off;
@@ -773,7 +776,8 @@
         // to the user dictionary.
         if (null != mPositionalInfoForUserDictPendingAddition
                 && mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord(
-                        mConnection, editorInfo, mLastSelectionEnd)) {
+                        mConnection, editorInfo, mLastSelectionEnd,
+                        mSubtypeSwitcher.getCurrentSubtypeLocale())) {
             mPositionalInfoForUserDictPendingAddition = null;
         }
         // If tryReplaceWithActualWord returns false, we don't know what word was
@@ -1223,11 +1227,17 @@
             mPositionalInfoForUserDictPendingAddition = null;
             return;
         }
+        final String wordToEdit;
+        if (StringUtils.isAutoCapsMode(mLastComposedWord.mCapitalizedMode)) {
+            wordToEdit = word.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
+        } else {
+            wordToEdit = word;
+        }
         mPositionalInfoForUserDictPendingAddition =
                 new PositionalInfoForUserDictPendingAddition(
-                        word, mLastSelectionEnd, getCurrentInputEditorInfo(),
+                        wordToEdit, mLastSelectionEnd, getCurrentInputEditorInfo(),
                         mLastComposedWord.mCapitalizedMode);
-        mUserDictionary.addWordToUserDictionary(word, 128);
+        mUserDictionary.addWordToUserDictionary(wordToEdit);
     }
 
     public void onWordAddedToUserDictionary(final String newSpelling) {
@@ -1240,7 +1250,8 @@
         }
         mPositionalInfoForUserDictPendingAddition.setActualWordBeingAdded(newSpelling);
         if (mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord(
-                mConnection, getCurrentInputEditorInfo(), mLastSelectionEnd)) {
+                mConnection, getCurrentInputEditorInfo(), mLastSelectionEnd,
+                mSubtypeSwitcher.getCurrentSubtypeLocale())) {
             mPositionalInfoForUserDictPendingAddition = null;
         }
     }
@@ -1476,7 +1487,10 @@
                     Stats.onAutoCorrection("", mWordComposer.getTypedWord(), " ", mWordComposer);
                 }
             }
-            if (mWordComposer.size() <= 1) {
+            final int wordComposerSize = mWordComposer.size();
+            // Since isComposingWord() is true, the size is at least 1.
+            final int lastChar = mWordComposer.getCodeAt(wordComposerSize - 1);
+            if (wordComposerSize <= 1) {
                 // We auto-correct the previous (typed, not gestured) string iff it's one character
                 // long. The reason for this is, even in the middle of gesture typing, you'll still
                 // tap one-letter words and you want them auto-corrected (typically, "i" in English
@@ -1490,8 +1504,14 @@
             }
             mExpectingUpdateSelection = true;
             // The following is necessary for the case where the user typed something but didn't
-            // manual pick it and didn't input any separator.
-            mSpaceState = SPACE_STATE_PHANTOM;
+            // manual pick it and didn't input any separator: we want to put a space between what
+            // has been entered and the coming gesture input result, so we go into phantom space
+            // state, which will be promoted to a space when the gesture result is committed. But if
+            // the current input ends in a word connector on the other hand, then we want to have
+            // the next input stick to the current input so we don't switch to phantom space state.
+            if (!mSettings.getCurrent().isWordConnector(lastChar)) {
+                mSpaceState = SPACE_STATE_PHANTOM;
+            }
         } else {
             final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
             if (mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
@@ -1597,7 +1617,7 @@
     private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
             final boolean dismissGestureFloatingPreviewText) {
         showSuggestionStrip(suggestedWords, null);
-        final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+        final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         mainKeyboardView.showGestureFloatingPreviewText(suggestedWords);
         if (dismissGestureFloatingPreviewText) {
             mainKeyboardView.dismissGestureFloatingPreviewText();
@@ -1861,6 +1881,7 @@
             final int spaceState) {
         if (ProductionFlag.IS_EXPERIMENTAL) {
             ResearchLogger.recordTimeForLogUnitSplit();
+            ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord());
         }
         boolean didAutoCorrect = false;
         // Handle separator
@@ -2409,15 +2430,24 @@
     // Hooks for hardware keyboard
     @Override
     public boolean onKeyDown(final int keyCode, final KeyEvent event) {
+        if (!ProductionFlag.IS_HARDWARE_KEYBOARD_SUPPORTED) return super.onKeyDown(keyCode, event);
         // onHardwareKeyEvent, like onKeyDown returns true if it handled the event, false if
         // it doesn't know what to do with it and leave it to the application. For example,
         // hardware key events for adjusting the screen's brightness are passed as is.
-        if (mEventInterpreter.onHardwareKeyEvent(event)) return true;
+        if (mEventInterpreter.onHardwareKeyEvent(event)) {
+            final long keyIdentifier = event.getDeviceId() << 32 + event.getKeyCode();
+            mCurrentlyPressedHardwareKeys.add(keyIdentifier);
+            return true;
+        }
         return super.onKeyDown(keyCode, event);
     }
 
     @Override
     public boolean onKeyUp(final int keyCode, final KeyEvent event) {
+        final long keyIdentifier = event.getDeviceId() << 32 + event.getKeyCode();
+        if (mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) {
+            return true;
+        }
         return super.onKeyUp(keyCode, event);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java
index feb1b2d..fcf7270 100644
--- a/java/src/com/android/inputmethod/latin/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java b/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
index a33cefc..9fdbf87 100644
--- a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
+++ b/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
@@ -1,23 +1,25 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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 android.view.inputmethod.EditorInfo;
 
+import java.util.Locale;
+
 /**
  * Holder class for data about a word already committed but that may still be edited.
  *
@@ -70,10 +72,11 @@
      * @param connection The RichInputConnection through which to contact the editor.
      * @param editorInfo Information pertaining to the editor we are currently in.
      * @param currentCursorPosition The current cursor position, for checking purposes.
+     * @param locale The locale for changing case, if necessary
      * @return true if the edit has been successfully made, false if we need to try again later
      */
     public boolean tryReplaceWithActualWord(final RichInputConnection connection,
-            final EditorInfo editorInfo, final int currentCursorPosition) {
+            final EditorInfo editorInfo, final int currentCursorPosition, final Locale locale) {
         // If we still don't know the actual word being added, we need to try again later.
         if (null == mActualWordBeingAdded) return false;
         // The entered text and the registered text were the same anyway : we can
@@ -92,9 +95,12 @@
         // so that it won't be tried again
         if (currentCursorPosition != mCursorPos) return true;
         // We have made all the checks : do the replacement and report success
+        // If this was auto-capitalized, we need to restore the case before committing
+        final String wordWithCaseFixed = StringUtils.applyAutoCapsMode(mActualWordBeingAdded,
+                mCapitalizedMode, locale);
         connection.setComposingRegion(currentCursorPosition - mOriginalWord.length(),
                 currentCursorPosition);
-        connection.commitText(mActualWordBeingAdded, mActualWordBeingAdded.length());
+        connection.commitText(wordWithCaseFixed, wordWithCaseFixed.length());
         return true;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/ResizableIntArray.java
index 9a46f16..691f060 100644
--- a/java/src/com/android/inputmethod/latin/ResizableIntArray.java
+++ b/java/src/com/android/inputmethod/latin/ResizableIntArray.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 0e75533..521cea9 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/SeekBarDialog.java b/java/src/com/android/inputmethod/latin/SeekBarDialog.java
deleted file mode 100644
index c736d1b..0000000
--- a/java/src/com/android/inputmethod/latin/SeekBarDialog.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * 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 android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.SeekBar;
-import android.widget.SeekBar.OnSeekBarChangeListener;
-import android.widget.TextView;
-
-public final class SeekBarDialog implements DialogInterface.OnClickListener,
-        OnSeekBarChangeListener {
-    public interface Listener {
-        public void onPositiveButtonClick(final SeekBarDialog dialog);
-        public void onNegativeButtonClick(final SeekBarDialog dialog);
-        public void onNeutralButtonClick(final SeekBarDialog dialog);
-        public void onDismiss(final SeekBarDialog dialog);
-        public void onProgressChanged(final SeekBarDialog dialog);
-        public void onStartTrackingTouch(final SeekBarDialog dialog);
-        public void onStopTrackingTouch(final SeekBarDialog dialog);
-    }
-
-    public static class Adapter implements Listener {
-        @Override
-        public void onPositiveButtonClick(final SeekBarDialog dialog) {}
-        @Override
-        public void onNegativeButtonClick(final SeekBarDialog dialog) {}
-        @Override
-        public void onNeutralButtonClick(final SeekBarDialog dialog) {}
-        @Override
-        public void onDismiss(final SeekBarDialog dialog) {}
-        @Override
-        public void onProgressChanged(final SeekBarDialog dialog) {}
-        @Override
-        public void onStartTrackingTouch(final SeekBarDialog dialog) {}
-        @Override
-        public void onStopTrackingTouch(final SeekBarDialog dialog) {}
-    }
-
-    private static final Listener EMPTY_ADAPTER = new Adapter();
-
-    private final AlertDialog mDialog;
-    private final Listener mListener;
-    private final TextView mValueView;
-    private final SeekBar mSeekBar;
-    private final String mValueFormat;
-
-    private int mValue;
-
-    private SeekBarDialog(final Builder builder) {
-        final AlertDialog.Builder dialogBuilder = builder.mDialogBuilder;
-        dialogBuilder.setView(builder.mView);
-        dialogBuilder.setPositiveButton(android.R.string.ok, this);
-        dialogBuilder.setNegativeButton(android.R.string.cancel, this);
-        if (builder.mNeutralButtonTextResId != 0) {
-            dialogBuilder.setNeutralButton(builder.mNeutralButtonTextResId, this);
-        }
-        mDialog = dialogBuilder.create();
-        mListener = (builder.mListener == null) ? EMPTY_ADAPTER : builder.mListener;
-        mValueView = (TextView)builder.mView.findViewById(R.id.seek_bar_dialog_value);
-        mSeekBar = (SeekBar)builder.mView.findViewById(R.id.seek_bar_dialog_bar);
-        mSeekBar.setMax(builder.mMaxValue);
-        mSeekBar.setOnSeekBarChangeListener(this);
-        if (builder.mValueFormatResId == 0) {
-            mValueFormat = "%s";
-        } else {
-            mValueFormat = mDialog.getContext().getString(builder.mValueFormatResId);
-        }
-    }
-
-    public void setValue(final int value, final boolean fromUser) {
-        mValue = value;
-        mValueView.setText(String.format(mValueFormat, value));
-        if (!fromUser) {
-            mSeekBar.setProgress(value);
-        }
-    }
-
-    public int getValue() {
-        return mValue;
-    }
-
-    public CharSequence getValueText() {
-        return mValueView.getText();
-    }
-
-    public void show() {
-        mDialog.show();
-    }
-
-    public void dismiss() {
-        mDialog.dismiss();
-    }
-
-    @Override
-    public void onClick(final DialogInterface dialog, final int which) {
-        switch (which) {
-        case DialogInterface.BUTTON_POSITIVE:
-            mListener.onPositiveButtonClick(this);
-            break;
-        case DialogInterface.BUTTON_NEGATIVE:
-            mListener.onNegativeButtonClick(this);
-            break;
-        case DialogInterface.BUTTON_NEUTRAL:
-            mListener.onNeutralButtonClick(this);
-            break;
-        default:
-            return;
-        }
-        mListener.onDismiss(this);
-    }
-
-    @Override
-    public void onProgressChanged(final SeekBar seekBar, final int progress,
-            final boolean fromUser) {
-        setValue(progress, fromUser);
-        if (fromUser) {
-            mListener.onProgressChanged(this);
-        }
-    }
-
-    @Override
-    public void onStartTrackingTouch(final SeekBar seekBar) {
-        mListener.onStartTrackingTouch(this);
-    }
-
-    @Override
-    public void onStopTrackingTouch(final SeekBar seekBar) {
-        mListener.onStopTrackingTouch(this);
-    }
-
-    public static final class Builder {
-        final AlertDialog.Builder mDialogBuilder;
-        final View mView;
-
-        int mNeutralButtonTextResId;
-        int mMaxValue;
-        int mValueFormatResId;
-        int mValue;
-        Listener mListener;
-
-        public Builder(final Context context) {
-            mDialogBuilder = new AlertDialog.Builder(context);
-            mView = LayoutInflater.from(context).inflate(R.layout.seek_bar_dialog, null);
-        }
-
-        public Builder setTitle(final int resId) {
-            mDialogBuilder.setTitle(resId);
-            return this;
-        }
-
-        public Builder setNeutralButtonText(final int resId) {
-            mNeutralButtonTextResId = resId;
-            return this;
-        }
-
-        public Builder setMaxValue(final int max) {
-            mMaxValue = max;
-            mValue = Math.min(mValue, max);
-            return this;
-        }
-
-        public Builder setValueFromat(final int resId) {
-            mValueFormatResId = resId;
-            return this;
-        }
-
-        public Builder setValue(final int value) {
-            mValue = Math.min(value, mMaxValue);
-            return this;
-        }
-
-        public Builder setListener(final Listener listener) {
-            mListener = listener;
-            return this;
-        }
-
-        public SeekBarDialog create() {
-            final SeekBarDialog dialog = new SeekBarDialog(this);
-            dialog.setValue(mValue, false /* fromUser */);
-            return dialog;
-        }
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java b/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java
new file mode 100644
index 0000000..6041564
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2013 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 android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+public final class SeekBarDialogPreference extends DialogPreference
+        implements SeekBar.OnSeekBarChangeListener {
+    public interface ValueProxy {
+        public int readValue(final String key);
+        public int readDefaultValue(final String key);
+        public void writeValue(final int value, final String key);
+        public void feedbackValue(final int value);
+    }
+
+    private final int mValueFormatResId;
+    private final int mMaxValue;
+
+    private TextView mValueView;
+    private SeekBar mSeekBar;
+
+    private ValueProxy mValueProxy;
+
+    public SeekBarDialogPreference(final Context context, final AttributeSet attrs) {
+        super(context, attrs);
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.SeekBarDialogPreference, 0, 0);
+        mValueFormatResId = a.getResourceId(R.styleable.SeekBarDialogPreference_valueFormatText, 0);
+        mMaxValue = a.getInt(R.styleable.SeekBarDialogPreference_maxValue, 0);
+        a.recycle();
+        setDialogLayoutResource(R.layout.seek_bar_dialog);
+    }
+
+    public void setInterface(final ValueProxy proxy) {
+        mValueProxy = proxy;
+        setSummary(getValueText(proxy.readValue(getKey())));
+    }
+
+    private String getValueText(final int value) {
+        if (mValueFormatResId == 0) {
+            return Integer.toString(value);
+        } else {
+            return getContext().getString(mValueFormatResId, value);
+        }
+    }
+
+    @Override
+    protected View onCreateDialogView() {
+        final View view = super.onCreateDialogView();
+        mSeekBar = (SeekBar)view.findViewById(R.id.seek_bar_dialog_bar);
+        mSeekBar.setMax(mMaxValue);
+        mSeekBar.setOnSeekBarChangeListener(this);
+        mValueView = (TextView)view.findViewById(R.id.seek_bar_dialog_value);
+        return view;
+    }
+
+    private void setValue(final int value, final boolean fromUser) {
+        mValueView.setText(getValueText(value));
+        if (!fromUser) {
+            mSeekBar.setProgress(value);
+        }
+    }
+
+    @Override
+    protected void onBindDialogView(final View view) {
+        setValue(mValueProxy.readValue(getKey()), false /* fromUser */);
+    }
+
+    @Override
+    protected void onPrepareDialogBuilder(final AlertDialog.Builder builder) {
+        builder.setPositiveButton(android.R.string.ok, this)
+            .setNegativeButton(android.R.string.cancel, this)
+            .setNeutralButton(R.string.button_default, this);
+    }
+
+    @Override
+    public void onClick(final DialogInterface dialog, final int which) {
+        super.onClick(dialog, which);
+        if (which == DialogInterface.BUTTON_NEUTRAL) {
+            setValue(mValueProxy.readDefaultValue(getKey()), false /* fromUser */);
+        }
+        if (which != DialogInterface.BUTTON_NEGATIVE) {
+            setSummary(mValueView.getText());
+            mValueProxy.writeValue(mSeekBar.getProgress(), getKey());
+        }
+    }
+
+    @Override
+    public void onProgressChanged(final SeekBar seekBar, final int progress,
+            final boolean fromUser) {
+        setValue(progress, fromUser);
+    }
+
+    @Override
+    public void onStartTrackingTouch(final SeekBar seekBar) {}
+
+    @Override
+    public void onStopTrackingTouch(final SeekBar seekBar) {
+        mValueProxy.feedbackValue(seekBar.getProgress());
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 866bef0..aa705da 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2013 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/SettingsActivity.java b/java/src/com/android/inputmethod/latin/SettingsActivity.java
index 3aeb101..ed8cf6d 100644
--- a/java/src/com/android/inputmethod/latin/SettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/SettingsActivity.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index 6a43718..afe6f35 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2008 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
+ * 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
+ *      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.
+ * 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;
@@ -26,7 +26,6 @@
 import android.preference.CheckBoxPreference;
 import android.preference.ListPreference;
 import android.preference.Preference;
-import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceGroup;
 import android.preference.PreferenceScreen;
 import android.view.inputmethod.InputMethodSubtype;
@@ -36,8 +35,6 @@
 
 public final class SettingsFragment extends InputMethodSettingsFragment
         implements SharedPreferences.OnSharedPreferenceChangeListener {
-    private PreferenceScreen mKeypressVibrationDurationSettingsPref;
-    private PreferenceScreen mKeypressSoundVolumeSettingsPref;
     private ListPreference mVoicePreference;
     private ListPreference mShowCorrectionSuggestionsPreference;
     private ListPreference mAutoCorrectionThresholdPreference;
@@ -167,36 +164,8 @@
             getPreferenceScreen().removePreference(gestureTypingSettings);
         }
 
-        mKeypressVibrationDurationSettingsPref =
-                (PreferenceScreen) findPreference(Settings.PREF_VIBRATION_DURATION_SETTINGS);
-        if (mKeypressVibrationDurationSettingsPref != null) {
-            mKeypressVibrationDurationSettingsPref.setOnPreferenceClickListener(
-                    new OnPreferenceClickListener() {
-                        @Override
-                        public boolean onPreferenceClick(Preference arg0) {
-                            showKeypressVibrationDurationSettingsDialog();
-                            return true;
-                        }
-                    });
-            mKeypressVibrationDurationSettingsPref.setSummary(
-                    res.getString(R.string.settings_keypress_vibration_duration,
-                            Settings.readKeypressVibrationDuration(prefs, res)));
-        }
-
-        mKeypressSoundVolumeSettingsPref =
-                (PreferenceScreen) findPreference(Settings.PREF_KEYPRESS_SOUND_VOLUME);
-        if (mKeypressSoundVolumeSettingsPref != null) {
-            mKeypressSoundVolumeSettingsPref.setOnPreferenceClickListener(
-                    new OnPreferenceClickListener() {
-                        @Override
-                        public boolean onPreferenceClick(Preference arg0) {
-                            showKeypressSoundVolumeSettingDialog();
-                            return true;
-                        }
-                    });
-            mKeypressSoundVolumeSettingsPref.setSummary(String.valueOf(
-                    getCurrentKeyPressSoundVolumePercent(prefs, res)));
-        }
+        setupKeypressVibrationDurationSettings(prefs, res);
+        setupKeypressSoundVolumeSettings(prefs, res);
         refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, res);
     }
 
@@ -287,127 +256,86 @@
 
     private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
             final SharedPreferences sp, final Resources res) {
-        if (mKeypressVibrationDurationSettingsPref != null) {
-            final boolean hasVibratorHardware =
-                    AudioAndHapticFeedbackManager.getInstance().hasVibrator();
-            final boolean vibrateOnByUser = sp.getBoolean(Settings.PREF_VIBRATE_ON,
-                    res.getBoolean(R.bool.config_default_vibration_enabled));
-            mKeypressVibrationDurationSettingsPref.setEnabled(
-                    hasVibratorHardware && vibrateOnByUser);
-        }
+        final boolean hasVibratorHardware =
+                AudioAndHapticFeedbackManager.getInstance().hasVibrator();
+        final boolean vibrateOnByUser = sp.getBoolean(Settings.PREF_VIBRATE_ON,
+                res.getBoolean(R.bool.config_default_vibration_enabled));
+        setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS,
+                hasVibratorHardware && vibrateOnByUser);
 
-        if (mKeypressSoundVolumeSettingsPref != null) {
-            final boolean soundOn = sp.getBoolean(Settings.PREF_SOUND_ON,
-                    res.getBoolean(R.bool.config_default_sound_enabled));
-            mKeypressSoundVolumeSettingsPref.setEnabled(soundOn);
-        }
+        final boolean soundOn = sp.getBoolean(Settings.PREF_SOUND_ON,
+                res.getBoolean(R.bool.config_default_sound_enabled));
+        setPreferenceEnabled(Settings.PREF_KEYPRESS_SOUND_VOLUME, soundOn);
     }
 
-    private void showKeypressVibrationDurationSettingsDialog() {
-        final SharedPreferences sp = getPreferenceManager().getSharedPreferences();
-        final Context context = getActivity();
-        final PreferenceScreen settingsPref = mKeypressVibrationDurationSettingsPref;
-        final SeekBarDialog.Listener listener = new SeekBarDialog.Adapter() {
-            private void writePreference(final SharedPreferences sp, final int value) {
-                sp.edit().putInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, value).apply();
+    private void setupKeypressVibrationDurationSettings(final SharedPreferences sp,
+            final Resources res) {
+        final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
+                Settings.PREF_VIBRATION_DURATION_SETTINGS);
+        if (pref == null) {
+            return;
+        }
+        pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
+            @Override
+            public void writeValue(final int value, final String key) {
+                sp.edit().putInt(key, value).apply();
             }
 
-            private void feedbackSettingsValue(final int value) {
+            @Override
+            public int readValue(final String key) {
+                return Settings.readKeypressVibrationDuration(sp, res);
+            }
+
+            @Override
+            public int readDefaultValue(final String key) {
+                return Settings.readDefaultKeypressVibrationDuration(res);
+            }
+
+            @Override
+            public void feedbackValue(final int value) {
                 AudioAndHapticFeedbackManager.getInstance().vibrate(value);
             }
-
-            @Override
-            public void onPositiveButtonClick(final SeekBarDialog dialog) {
-                writePreference(sp, dialog.getValue());
-            }
-
-            @Override
-            public void onNeutralButtonClick(final SeekBarDialog dialog) {
-                final int defaultValue =
-                        Settings.readDefaultKeypressVibrationDuration(context.getResources());
-                dialog.setValue(defaultValue, false /* fromUser */);
-                writePreference(sp, defaultValue);
-            }
-
-            @Override
-            public void onDismiss(final SeekBarDialog dialog) {
-                if (settingsPref != null) {
-                    settingsPref.setSummary(dialog.getValueText());
-                }
-            }
-
-            @Override
-            public void onStopTrackingTouch(final SeekBarDialog dialog) {
-                feedbackSettingsValue(dialog.getValue());
-            }
-        };
-        final int currentMs = Settings.readKeypressVibrationDuration(sp, getResources());
-        final SeekBarDialog.Builder builder = new SeekBarDialog.Builder(context);
-        builder.setTitle(R.string.prefs_keypress_vibration_duration_settings)
-                .setNeutralButtonText(R.string.button_default)
-                .setListener(listener)
-                .setMaxValue(AudioAndHapticFeedbackManager.MAX_KEYPRESS_VIBRATION_DURATION)
-                .setValueFromat(R.string.settings_keypress_vibration_duration)
-                .setValue(currentMs)
-                .create()
-                .show();
+        });
     }
 
-    private static final int PERCENT_INT = 100;
-    private static final float PERCENT_FLOAT = 100.0f;
+    private void setupKeypressSoundVolumeSettings(final SharedPreferences sp, final Resources res) {
+        final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
+                Settings.PREF_KEYPRESS_SOUND_VOLUME);
+        if (pref == null) {
+            return;
+        }
+        final AudioManager am = (AudioManager)getActivity().getSystemService(Context.AUDIO_SERVICE);
+        pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
+            private static final float PERCENTAGE_FLOAT = 100.0f;
 
-    private static int getCurrentKeyPressSoundVolumePercent(final SharedPreferences sp,
-            final Resources res) {
-        return (int)(Settings.readKeypressSoundVolume(sp, res) * PERCENT_FLOAT);
-    }
-
-    private void showKeypressSoundVolumeSettingDialog() {
-        final SharedPreferences sp = getPreferenceManager().getSharedPreferences();
-        final Context context = getActivity();
-        final AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        final PreferenceScreen settingsPref = mKeypressSoundVolumeSettingsPref;
-        final SeekBarDialog.Listener listener = new SeekBarDialog.Adapter() {
-            private void writePreference(final SharedPreferences sp, final float value) {
-                sp.edit().putFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, value).apply();
+            private float getValueFromPercentage(final int percentage) {
+                return percentage / PERCENTAGE_FLOAT;
             }
 
-            private void feedbackSettingsValue(final float value) {
-                am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, value);
+            private int getPercentageFromValue(final float floatValue) {
+                return (int)(floatValue * PERCENTAGE_FLOAT);
             }
 
             @Override
-            public void onPositiveButtonClick(final SeekBarDialog dialog) {
-                writePreference(sp, dialog.getValue() / PERCENT_FLOAT);
+            public void writeValue(final int value, final String key) {
+                sp.edit().putFloat(key, getValueFromPercentage(value)).apply();
             }
 
             @Override
-            public void onNeutralButtonClick(final SeekBarDialog dialog) {
-                final float defaultValue =
-                        Settings.readDefaultKeypressSoundVolume(context.getResources());
-                dialog.setValue((int)(defaultValue * PERCENT_INT), false /* fromUser */);
-                writePreference(sp, defaultValue);
+            public int readValue(final String key) {
+                return getPercentageFromValue(Settings.readKeypressSoundVolume(sp, res));
             }
 
             @Override
-            public void onDismiss(final SeekBarDialog dialog) {
-                if (settingsPref != null) {
-                    settingsPref.setSummary(dialog.getValueText());
-                }
+            public int readDefaultValue(final String key) {
+                return getPercentageFromValue(Settings.readDefaultKeypressSoundVolume(res));
             }
 
             @Override
-            public void onStopTrackingTouch(final SeekBarDialog dialog) {
-                feedbackSettingsValue(dialog.getValue() / PERCENT_FLOAT);
+            public void feedbackValue(final int value) {
+                am.playSoundEffect(
+                        AudioManager.FX_KEYPRESS_STANDARD, getValueFromPercentage(value));
             }
-        };
-        final SeekBarDialog.Builder builder = new SeekBarDialog.Builder(context);
-        final int currentVolumeInt = getCurrentKeyPressSoundVolumePercent(sp, getResources());
-        builder.setTitle(R.string.prefs_keypress_sound_volume_settings)
-                .setNeutralButtonText(R.string.button_default)
-                .setListener(listener)
-                .setMaxValue(PERCENT_INT)
-                .setValue(currentVolumeInt)
-                .create()
-                .show();
+        });
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 1e3bdf0..d6556d6 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index ddaa5ff..d00edbe 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -103,6 +103,37 @@
         }
     }
 
+    /**
+     * Apply an auto-caps mode to a string.
+     *
+     * This intentionally does NOT apply manual caps mode. It only changes the capitalization if
+     * the mode is one of the auto-caps modes.
+     * @param s The string to capitalize.
+     * @param capitalizeMode The mode in which to capitalize.
+     * @param locale The locale for capitalizing.
+     * @return The capitalized string.
+     */
+    public static String applyAutoCapsMode(final String s, final int capitalizeMode,
+            final Locale locale) {
+        if (WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == capitalizeMode) {
+            return s.toUpperCase(locale);
+        } else if (WordComposer.CAPS_MODE_AUTO_SHIFTED == capitalizeMode) {
+            return toTitleCase(s, locale);
+        } else {
+            return s;
+        }
+    }
+
+    /**
+     * Return whether a constant represents an auto-caps mode (either auto-shift or auto-shift-lock)
+     * @param mode The mode to test for
+     * @return true if this represents an auto-caps mode, false otherwise
+     */
+    public static boolean isAutoCapsMode(final int mode) {
+        return WordComposer.CAPS_MODE_AUTO_SHIFTED == mode
+                || WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == mode;
+    }
+
     public static String toTitleCase(final String s, final Locale locale) {
         if (s.length() <= 1) {
             // TODO: is this really correct? Shouldn't this be s.toUpperCase()?
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 2abf75d..cf78e7d 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2008 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index b7ca60f..3d6fe2d 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java b/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java
index 743a8c6..1ea4ac3 100644
--- a/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java
+++ b/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index a167849..0d5bde6 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -216,17 +216,13 @@
      *
      * @param word the word to add. If the word is capitalized, then the dictionary will
      * recognize it as a capitalized word when searched.
-     * @param frequency the frequency of occurrence of the word. A frequency of 255 is considered
-     * the highest.
-     * @TODO use a higher or float range for frequency
      */
-    public synchronized void addWordToUserDictionary(final String word, final int frequency) {
+    public synchronized void addWordToUserDictionary(final String word) {
         // TODO: do something for the UI. With the following, any sufficiently long word will
         // look like it will go to the user dictionary but it won't.
         // Safeguard against adding long words. Can cause stack overflow.
         if (word.length() >= MAX_WORD_LENGTH) return;
 
-        // TODO: Add an argument to the intent to specify the frequency.
         Intent intent = new Intent(ACTION_USER_DICTIONARY_INSERT);
         intent.putExtra(Words.WORD, word);
         intent.putExtra(Words.LOCALE, mLocale);
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
index 4fa3d7d..eb5c387 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index 4c78848..81bc9f5 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2010 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
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index b9ec497..31f616d 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2008 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
+ * 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
+ *      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.
+ * 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;
@@ -360,8 +360,10 @@
         mDigitsCount = 0;
         mIsBatchMode = false;
         mTypedWord.setLength(0);
+        mCodePointSize = 0;
         mTrailingSingleQuotesCount = 0;
         mIsFirstCharCapitalized = false;
+        mCapitalizedMode = CAPS_MODE_OFF;
         refreshSize();
         mAutoCorrection = null;
         mIsResumed = false;
diff --git a/java/src/com/android/inputmethod/latin/WordListInfo.java b/java/src/com/android/inputmethod/latin/WordListInfo.java
index 095320e..5ac806a 100644
--- a/java/src/com/android/inputmethod/latin/WordListInfo.java
+++ b/java/src/com/android/inputmethod/latin/WordListInfo.java
@@ -1,17 +1,17 @@
-/**
+/*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
index 75dc68f..f01d4c5 100644
--- a/java/src/com/android/inputmethod/latin/XmlParseUtils.java
+++ b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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;
diff --git a/java/src/com/android/inputmethod/latin/define/JniLibName.java b/java/src/com/android/inputmethod/latin/define/JniLibName.java
index e23e1a9..abfc36d 100644
--- a/java/src/com/android/inputmethod/latin/define/JniLibName.java
+++ b/java/src/com/android/inputmethod/latin/define/JniLibName.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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.define;
diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
index a14398f..fe9be16 100644
--- a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
+++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.define;
@@ -28,4 +28,6 @@
     // be false, and any privacy controls should be enforced.  IS_EXPERIMENTAL_DEBUG should be false
     // for any released build.
     public static final boolean IS_EXPERIMENTAL_DEBUG = false;
+
+    public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = true;
 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index ee0e9cd..9e1f751 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package com.android.inputmethod.latin.makedict;
@@ -25,8 +25,12 @@
 import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.nio.channels.FileChannel;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
@@ -977,4 +981,27 @@
         }
         return null;
     }
+
+    /**
+     * Convenience method to read the header of a binary file.
+     *
+     * This is quite resource intensive - don't call when performance is critical.
+     *
+     * @param file The file to read.
+     */
+    private static final int HEADER_READING_BUFFER_SIZE = 16384;
+    public static FileHeader getDictionaryFileHeader(final File file)
+        throws FileNotFoundException, IOException, UnsupportedFormatException {
+        final byte[] buffer = new byte[HEADER_READING_BUFFER_SIZE];
+        final FileInputStream inStream = new FileInputStream(file);
+        try {
+            inStream.read(buffer);
+            final BinaryDictInputOutput.ByteBufferWrapper wrapper =
+                    new BinaryDictInputOutput.ByteBufferWrapper(inStream.getChannel().map(
+                            FileChannel.MapMode.READ_ONLY, 0, file.length()));
+            return BinaryDictInputOutput.readHeader(wrapper);
+        } finally {
+            inStream.close();
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 937d7ab..58ec1e8 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package com.android.inputmethod.latin.makedict;
diff --git a/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java b/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
index 8e64082..b361744 100644
--- a/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
+++ b/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package com.android.inputmethod.latin.makedict;
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index 705f664..c22ea3b 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package com.android.inputmethod.latin.makedict;
@@ -252,7 +252,7 @@
     /**
      * Class representing file header.
      */
-    static final class FileHeader {
+    public static final class FileHeader {
         public final int mHeaderSize;
         public final DictionaryOptions mDictionaryOptions;
         public final FormatOptions mFormatOptions;
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index bfc275d..5c80559 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package com.android.inputmethod.latin.makedict;
diff --git a/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java b/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
index 6c6b00b..cf07209 100644
--- a/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
+++ b/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package com.android.inputmethod.latin.makedict;
diff --git a/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java b/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
index 5bb24da..70e24cc 100644
--- a/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
+++ b/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package com.android.inputmethod.latin.makedict;
diff --git a/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java b/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
index dbb2ea8..4f38615 100644
--- a/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
+++ b/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package com.android.inputmethod.latin.makedict;
diff --git a/java/src/com/android/inputmethod/latin/makedict/Word.java b/java/src/com/android/inputmethod/latin/makedict/Word.java
index 4c4f18f..0eabb7b 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Word.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Word.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package com.android.inputmethod.latin.makedict;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 907c0cd..38a2648 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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.spellcheck;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index 668e7a6..63f46b7 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.spellcheck;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java
index 8eb1eb6..e0418d4 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.spellcheck;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 6581978..b0e4716 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.spellcheck;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
index 9d7c61a..017a4f5 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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.spellcheck;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index eae5d2e..81dd92d 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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.spellcheck;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index 572a826..49dca21 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * 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.spellcheck;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
index e63dff3..119ca47 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
@@ -1,17 +1,17 @@
-/**
+/*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy
- * of the License at
+ * 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
+ *      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.
+ * 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.spellcheck;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
index ef5123d..9606b03 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
@@ -1,17 +1,17 @@
-/**
+/*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy
- * of the License at
+ * 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
+ *      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.
+ * 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.spellcheck;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 35d5a00..ed408bb 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package com.android.inputmethod.latin.suggestions;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 14bb95b..92b96e7 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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
+ *      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.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package com.android.inputmethod.latin.suggestions;
@@ -52,7 +52,7 @@
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.keyboard.MoreKeysPanel;
 import com.android.inputmethod.keyboard.ViewLayoutUtils;
 import com.android.inputmethod.latin.AutoCorrection;
@@ -81,7 +81,7 @@
     static final boolean DBG = LatinImeLogger.sDBG;
 
     private final ViewGroup mSuggestionsStrip;
-    KeyboardView mKeyboardView;
+    MainKeyboardView mMainKeyboardView;
 
     private final View mMoreSuggestionsContainer;
     private final MoreSuggestionsView mMoreSuggestionsView;
@@ -610,7 +610,7 @@
      */
     public void setListener(final Listener listener, final View inputView) {
         mListener = listener;
-        mKeyboardView = (KeyboardView)inputView.findViewById(R.id.keyboard_view);
+        mMainKeyboardView = (MainKeyboardView)inputView.findViewById(R.id.keyboard_view);
     }
 
     public void setSuggestions(final SuggestedWords suggestedWords) {
@@ -676,13 +676,13 @@
             new MoreKeysPanel.Controller() {
         @Override
         public boolean onDismissMoreKeysPanel() {
-            mKeyboardView.dimEntireKeyboard(false /* dimmed */);
-            return mKeyboardView.onDismissMoreKeysPanel();
+            mMainKeyboardView.dimEntireKeyboard(false /* dimmed */);
+            return mMainKeyboardView.onDismissMoreKeysPanel();
         }
 
         @Override
         public void onShowMoreKeysPanel(MoreKeysPanel panel) {
-            mKeyboardView.onShowMoreKeysPanel(panel);
+            mMainKeyboardView.onShowMoreKeysPanel(panel);
         }
 
         @Override
@@ -728,7 +728,7 @@
         mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING;
         mOriginX = mLastX;
         mOriginY = mLastY;
-        mKeyboardView.dimEntireKeyboard(true /* dimmed */);
+        mMainKeyboardView.dimEntireKeyboard(true /* dimmed */);
         for (int i = 0; i < params.mSuggestionsCountInStrip; i++) {
             mWords.get(i).setPressed(false);
         }
diff --git a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
index 5124a35..c5f0959 100644
--- a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.research;
diff --git a/java/src/com/android/inputmethod/research/FeedbackActivity.java b/java/src/com/android/inputmethod/research/FeedbackActivity.java
index 11eae88..f66d55b 100644
--- a/java/src/com/android/inputmethod/research/FeedbackActivity.java
+++ b/java/src/com/android/inputmethod/research/FeedbackActivity.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.research;
diff --git a/java/src/com/android/inputmethod/research/FeedbackFragment.java b/java/src/com/android/inputmethod/research/FeedbackFragment.java
index a2e08e2..a9adbb7 100644
--- a/java/src/com/android/inputmethod/research/FeedbackFragment.java
+++ b/java/src/com/android/inputmethod/research/FeedbackFragment.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.research;
diff --git a/java/src/com/android/inputmethod/research/FeedbackLayout.java b/java/src/com/android/inputmethod/research/FeedbackLayout.java
index f2cbfe3..d283d14 100644
--- a/java/src/com/android/inputmethod/research/FeedbackLayout.java
+++ b/java/src/com/android/inputmethod/research/FeedbackLayout.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.research;
diff --git a/java/src/com/android/inputmethod/research/FixedLogBuffer.java b/java/src/com/android/inputmethod/research/FixedLogBuffer.java
index 5270730..73f284a 100644
--- a/java/src/com/android/inputmethod/research/FixedLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/FixedLogBuffer.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.research;
diff --git a/java/src/com/android/inputmethod/research/JsonUtils.java b/java/src/com/android/inputmethod/research/JsonUtils.java
index ceba08d..24cd8d9 100644
--- a/java/src/com/android/inputmethod/research/JsonUtils.java
+++ b/java/src/com/android/inputmethod/research/JsonUtils.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.research;
diff --git a/java/src/com/android/inputmethod/research/LogBuffer.java b/java/src/com/android/inputmethod/research/LogBuffer.java
index 9d095f8..b07b761 100644
--- a/java/src/com/android/inputmethod/research/LogBuffer.java
+++ b/java/src/com/android/inputmethod/research/LogBuffer.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.research;
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
index 3534ef9..715000d 100644
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.research;
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index c187e33..57d5c41 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.research;
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 64ee830..24bf7d1 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.research;
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index cc57842..0a24af6 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.research;
@@ -1744,7 +1744,7 @@
     /**
      * Record the current time in case the LogUnit is later split.
      *
-     * If the current logUnitis split, then tapping, motion events, etc. before this time should
+     * If the current logUnit is split, then tapping, motion events, etc. before this time should
      * be assigned to one LogUnit, and events after this time should go into the following LogUnit.
      */
     public static void recordTimeForLogUnitSplit() {
@@ -1754,6 +1754,22 @@
     }
 
     /**
+     * Log a call to LatinIME.handleSeparator()
+     *
+     * SystemResponse: The system is inserting a separator character, possibly performing auto-
+     * correction or other actions appropriate at the end of a word.
+     */
+    private static final LogStatement LOGSTATEMENT_LATINIME_HANDLESEPARATOR =
+            new LogStatement("LatinIMEHandleSeparator", false, false, "primaryCode",
+                    "isComposingWord");
+    public static void latinIME_handleSeparator(final int primaryCode,
+            final boolean isComposingWord) {
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLESEPARATOR, primaryCode,
+                isComposingWord);
+    }
+
+    /**
      * Log statistics.
      *
      * ContextualData, recorded at the end of a session.
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
index f0cb157..50e2b7f 100644
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.research;
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
index a1ecc11..5e3cf55 100644
--- a/java/src/com/android/inputmethod/research/UploaderService.java
+++ b/java/src/com/android/inputmethod/research/UploaderService.java
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.research;
diff --git a/native/jni/src/additional_proximity_chars.h b/native/jni/src/additional_proximity_chars.h
index 21eb0bf..a88fd6c 100644
--- a/native/jni/src/additional_proximity_chars.h
+++ b/native/jni/src/additional_proximity_chars.h
@@ -45,7 +45,7 @@
     }
 
  public:
-    static int getAdditionalCharsSize(const char *localeStr, const int c) {
+    static int getAdditionalCharsSize(const char *const localeStr, const int c) {
         if (!isEnLocale(localeStr)) {
             return 0;
         }
@@ -65,7 +65,7 @@
         }
     }
 
-    static const int *getAdditionalChars(const char *localeStr, const int c) {
+    static const int *getAdditionalChars(const char *const localeStr, const int c) {
         if (!isEnLocale(localeStr)) {
             return 0;
         }
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 4d5a2b2..f5f5278 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -392,6 +392,8 @@
 template<typename T> inline T min(T a, T b) { return a < b ? a : b; }
 template<typename T> inline T max(T a, T b) { return a > b ? a : b; }
 
+#define M_PI_F 3.14159265f
+
 #define NELEMS(x) (sizeof(x) / sizeof((x)[0]))
 
 // The ratio of neutral area radius to sweet spot radius.
diff --git a/native/jni/src/geometry_utils.h b/native/jni/src/geometry_utils.h
index 4bff80f..4cbb127 100644
--- a/native/jni/src/geometry_utils.h
+++ b/native/jni/src/geometry_utils.h
@@ -21,7 +21,6 @@
 
 #include "defines.h"
 
-#define M_PI_F 3.14159265f
 #define ROUND_FLOAT_10000(f) ((f) < 1000.0f && (f) > 0.001f) \
         ? (floorf((f) * 10000.0f) / 10000.0f) : (f)
 
@@ -29,15 +28,6 @@
 
 static inline float SQUARE_FLOAT(const float x) { return x * x; }
 
-static inline float getSquaredDistanceFloat(const float x1, const float y1, const float x2,
-        const float y2) {
-    return SQUARE_FLOAT(x1 - x2) + SQUARE_FLOAT(y1 - y2);
-}
-
-static AK_FORCE_INLINE int getDistanceInt(const int x1, const int y1, const int x2, const int y2) {
-    return static_cast<int>(hypotf(static_cast<float>(x1 - x2), static_cast<float>(y1 - y2)));
-}
-
 static AK_FORCE_INLINE float getAngle(const int x1, const int y1, const int x2, const int y2) {
     const int dx = x1 - x2;
     const int dy = y1 - y2;
@@ -55,51 +45,9 @@
     return diff;
 }
 
-static inline float pointToLineSegSquaredDistanceFloat(const float x, const float y, const float x1,
-        const float y1, const float x2, const float y2, const bool extend) {
-    const float ray1x = x - x1;
-    const float ray1y = y - y1;
-    const float ray2x = x2 - x1;
-    const float ray2y = y2 - y1;
-
-    const float dotProduct = ray1x * ray2x + ray1y * ray2y;
-    const float lineLengthSqr = SQUARE_FLOAT(ray2x) + SQUARE_FLOAT(ray2y);
-    const float projectionLengthSqr = dotProduct / lineLengthSqr;
-
-    float projectionX;
-    float projectionY;
-    if (!extend && projectionLengthSqr < 0.0f) {
-        projectionX = x1;
-        projectionY = y1;
-    } else if (!extend && projectionLengthSqr > 1.0f) {
-        projectionX = x2;
-        projectionY = y2;
-    } else {
-        projectionX = x1 + projectionLengthSqr * ray2x;
-        projectionY = y1 + projectionLengthSqr * ray2y;
-    }
-    return getSquaredDistanceFloat(x, y, projectionX, projectionY);
+static AK_FORCE_INLINE int getDistanceInt(const int x1, const int y1, const int x2,
+        const int y2) {
+    return static_cast<int>(hypotf(static_cast<float>(x1 - x2), static_cast<float>(y1 - y2)));
 }
-
-// Normal distribution N(u, sigma^2).
-struct NormalDistribution {
- public:
-    NormalDistribution(const float u, const float sigma)
-            : mU(u), mSigma(sigma),
-              mPreComputedNonExpPart(1.0f / sqrtf(2.0f * M_PI_F * SQUARE_FLOAT(sigma))),
-              mPreComputedExponentPart(-1.0f / (2.0f * SQUARE_FLOAT(sigma))) {}
-
-    float getProbabilityDensity(const float x) const {
-        const float shiftedX = x - mU;
-        return mPreComputedNonExpPart * expf(mPreComputedExponentPart * SQUARE_FLOAT(shiftedX));
-    }
-
-private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(NormalDistribution);
-    const float mU; // mean value
-    const float mSigma; // standard deviation
-    const float mPreComputedNonExpPart; // = 1 / sqrt(2 * PI * sigma^2)
-    const float mPreComputedExponentPart; // = -1 / (2 * sigma^2)
-}; // struct NormalDistribution
 } // namespace latinime
 #endif // LATINIME_GEOMETRY_UTILS_H
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp
index 9b99554..c563b07 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/proximity_info.cpp
@@ -94,11 +94,6 @@
     delete[] mProximityCharsArray;
 }
 
-inline int ProximityInfo::getStartIndexFromCoordinates(const int x, const int y) const {
-    return ((y / CELL_HEIGHT) * GRID_WIDTH + (x / CELL_WIDTH))
-            * MAX_PROXIMITY_CHARS_SIZE;
-}
-
 bool ProximityInfo::hasSpaceProximity(const int x, const int y) const {
     if (x < 0 || y < 0) {
         if (DEBUG_DICT) {
@@ -109,7 +104,8 @@
         return false;
     }
 
-    const int startIndex = getStartIndexFromCoordinates(x, y);
+    const int startIndex = ProximityInfoUtils::getStartIndexFromCoordinates(
+            MAX_PROXIMITY_CHARS_SIZE, x, y, CELL_HEIGHT, CELL_WIDTH, GRID_WIDTH);
     if (DEBUG_PROXIMITY_INFO) {
         AKLOGI("hasSpaceProximity: index %d, %d, %d", startIndex, x, y);
     }
@@ -144,101 +140,8 @@
     const float touchX = static_cast<float>(x);
     const float touchY = static_cast<float>(y);
     const float keyWidth = static_cast<float>(getMostCommonKeyWidth());
-    return getSquaredDistanceFloat(centerX, centerY, touchX, touchY) / SQUARE_FLOAT(keyWidth);
-}
-
-int ProximityInfo::squaredDistanceToEdge(const int keyId, const int x, const int y) const {
-    if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
-    const int left = mKeyXCoordinates[keyId];
-    const int top = mKeyYCoordinates[keyId];
-    const int right = left + mKeyWidths[keyId];
-    const int bottom = top + mKeyHeights[keyId];
-    const int edgeX = x < left ? left : (x > right ? right : x);
-    const int edgeY = y < top ? top : (y > bottom ? bottom : y);
-    const int dx = x - edgeX;
-    const int dy = y - edgeY;
-    return dx * dx + dy * dy;
-}
-
-void ProximityInfo::calculateNearbyKeyCodes(
-        const int x, const int y, const int primaryKey, int *inputCodes) const {
-    int *proximityCharsArray = mProximityCharsArray;
-    int insertPos = 0;
-    inputCodes[insertPos++] = primaryKey;
-    const int startIndex = getStartIndexFromCoordinates(x, y);
-    if (startIndex >= 0) {
-        for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
-            const int c = proximityCharsArray[startIndex + i];
-            if (c < KEYCODE_SPACE || c == primaryKey) {
-                continue;
-            }
-            const int keyIndex = getKeyIndexOf(c);
-            const bool onKey = isOnKey(keyIndex, x, y);
-            const int distance = squaredDistanceToEdge(keyIndex, x, y);
-            if (onKey || distance < MOST_COMMON_KEY_WIDTH_SQUARE) {
-                inputCodes[insertPos++] = c;
-                if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
-                    if (DEBUG_DICT) {
-                        ASSERT(false);
-                    }
-                    return;
-                }
-            }
-        }
-        const int additionalProximitySize =
-                AdditionalProximityChars::getAdditionalCharsSize(mLocaleStr, primaryKey);
-        if (additionalProximitySize > 0) {
-            inputCodes[insertPos++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
-            if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
-                if (DEBUG_DICT) {
-                    ASSERT(false);
-                }
-                return;
-            }
-
-            const int *additionalProximityChars =
-                    AdditionalProximityChars::getAdditionalChars(mLocaleStr, primaryKey);
-            for (int j = 0; j < additionalProximitySize; ++j) {
-                const int ac = additionalProximityChars[j];
-                int k = 0;
-                for (; k < insertPos; ++k) {
-                    if (ac == inputCodes[k]) {
-                        break;
-                    }
-                }
-                if (k < insertPos) {
-                    continue;
-                }
-                inputCodes[insertPos++] = ac;
-                if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
-                    if (DEBUG_DICT) {
-                        ASSERT(false);
-                    }
-                    return;
-                }
-            }
-        }
-    }
-    // Add a delimiter for the proximity characters
-    for (int i = insertPos; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
-        inputCodes[i] = NOT_A_CODE_POINT;
-    }
-}
-
-int ProximityInfo::getKeyIndexOf(const int c) const {
-    if (KEY_COUNT == 0) {
-        // We do not have the coordinate data
-        return NOT_AN_INDEX;
-    }
-    if (c == NOT_A_CODE_POINT) {
-        return NOT_AN_INDEX;
-    }
-    const int lowerCode = toLowerCase(c);
-    hash_map_compat<int, int>::const_iterator mapPos = mCodeToKeyMap.find(lowerCode);
-    if (mapPos != mCodeToKeyMap.end()) {
-        return mapPos->second;
-    }
-    return NOT_AN_INDEX;
+    return ProximityInfoUtils::getSquaredDistanceFloat(centerX, centerY, touchX, touchY)
+            / SQUARE_FLOAT(keyWidth);
 }
 
 int ProximityInfo::getCodePointOf(const int keyIndex) const {
@@ -269,11 +172,13 @@
 }
 
 int ProximityInfo::getKeyCenterXOfCodePointG(int charCode) const {
-    return getKeyCenterXOfKeyIdG(getKeyIndexOf(charCode));
+    return getKeyCenterXOfKeyIdG(
+            ProximityInfoUtils::getKeyIndexOf(KEY_COUNT, charCode, &mCodeToKeyMap));
 }
 
 int ProximityInfo::getKeyCenterYOfCodePointG(int charCode) const {
-    return getKeyCenterYOfKeyIdG(getKeyIndexOf(charCode));
+    return getKeyCenterYOfKeyIdG(
+            ProximityInfoUtils::getKeyIndexOf(KEY_COUNT, charCode, &mCodeToKeyMap));
 }
 
 int ProximityInfo::getKeyCenterXOfKeyIdG(int keyId) const {
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h
index d002283..cd0bc32 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/proximity_info.h
@@ -20,6 +20,7 @@
 #include "defines.h"
 #include "hash_map_compat.h"
 #include "jni.h"
+#include "proximity_info_utils.h"
 
 namespace latinime {
 
@@ -40,7 +41,6 @@
     float getNormalizedSquaredDistanceFromCenterFloatG(
             const int keyId, const int x, const int y) const;
     bool sameAsTyped(const unsigned short *word, int length) const;
-    int getKeyIndexOf(const int c) const;
     int getCodePointOf(const int keyIndex) const;
     bool hasSweetSpotData(const int keyIndex) const {
         // When there are no calibration data for a key,
@@ -71,10 +71,6 @@
         return MOST_COMMON_KEY_WIDTH_SQUARE;
     }
 
-    const char *getLocaleStr() const {
-        return mLocaleStr;
-    }
-
     int getKeyCount() const {
         return KEY_COUNT;
     }
@@ -109,23 +105,26 @@
     int getKeyCenterYOfKeyIdG(int keyId) const;
     int getKeyKeyDistanceG(int keyId0, int keyId1) const;
 
+    void initializeProximities(const int *const inputCodes, const int *const inputXCoordinates,
+            const int *const inputYCoordinates, const int inputSize, int *allInputCodes) const {
+        ProximityInfoUtils::initializeProximities(inputCodes, inputXCoordinates, inputYCoordinates,
+                inputSize, mKeyXCoordinates, mKeyYCoordinates, mKeyWidths, mKeyHeights,
+                mProximityCharsArray, MAX_PROXIMITY_CHARS_SIZE, CELL_HEIGHT, CELL_WIDTH,
+                GRID_WIDTH, MOST_COMMON_KEY_WIDTH, KEY_COUNT, mLocaleStr, &mCodeToKeyMap,
+                allInputCodes);
+    }
+
+    int getKeyIndexOf(const int c) const {
+        return ProximityInfoUtils::getKeyIndexOf(KEY_COUNT, c, &mCodeToKeyMap);
+    }
+
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfo);
     static const float NOT_A_DISTANCE_FLOAT;
 
-    int getStartIndexFromCoordinates(const int x, const int y) const;
     void initializeG();
     float calculateNormalizedSquaredDistance(const int keyIndex, const int inputIndex) const;
     bool hasInputCoordinates() const;
-    int squaredDistanceToEdge(const int keyId, const int x, const int y) const;
-    bool isOnKey(const int keyId, const int x, const int y) const {
-        if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
-        const int left = mKeyXCoordinates[keyId];
-        const int top = mKeyYCoordinates[keyId];
-        const int right = left + mKeyWidths[keyId] + 1;
-        const int bottom = top + mKeyHeights[keyId];
-        return left < right && top < bottom && x >= left && x < right && y >= top && y < bottom;
-    }
 
     const int MAX_PROXIMITY_CHARS_SIZE;
     const int GRID_WIDTH;
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index aa02929..31b6e4b 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -23,6 +23,7 @@
 #include "geometry_utils.h"
 #include "proximity_info.h"
 #include "proximity_info_state.h"
+#include "proximity_info_state_utils.h"
 
 namespace latinime {
 
@@ -46,40 +47,17 @@
     mProximityInfo = proximityInfo;
     mHasTouchPositionCorrectionData = proximityInfo->hasTouchPositionCorrectionData();
     mMostCommonKeyWidthSquare = proximityInfo->getMostCommonKeyWidthSquare();
-    mLocaleStr = proximityInfo->getLocaleStr();
     mKeyCount = proximityInfo->getKeyCount();
     mCellHeight = proximityInfo->getCellHeight();
     mCellWidth = proximityInfo->getCellWidth();
     mGridHeight = proximityInfo->getGridWidth();
     mGridWidth = proximityInfo->getGridHeight();
 
-    memset(mInputCodes, 0, sizeof(mInputCodes));
+    memset(mInputProximities, 0, sizeof(mInputProximities));
 
     if (!isGeometric && pointerId == 0) {
-        // Initialize
-        // - mInputCodes
-        // - mNormalizedSquaredDistances
-        // TODO: Merge
-        for (int i = 0; i < inputSize; ++i) {
-            const int primaryKey = inputCodes[i];
-            const int x = xCoordinates[i];
-            const int y = yCoordinates[i];
-            int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL];
-            mProximityInfo->calculateNearbyKeyCodes(x, y, primaryKey, proximities);
-        }
-
-        if (DEBUG_PROXIMITY_CHARS) {
-            for (int i = 0; i < inputSize; ++i) {
-                AKLOGI("---");
-                for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL; ++j) {
-                    int icc = mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
-                    int icfjc = inputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
-                    icc += 0;
-                    icfjc += 0;
-                    AKLOGI("--- (%d)%c,%c", i, icc, icfjc); AKLOGI("--- A<%d>,B<%d>", icc, icfjc);
-                }
-            }
-        }
+        mProximityInfo->initializeProximities(inputCodes, xCoordinates, yCoordinates,
+                inputSize, mInputProximities);
     }
 
     ///////////////////////
@@ -116,82 +94,11 @@
     mSampledInputSize = 0;
 
     if (xCoordinates && yCoordinates) {
-        if (DEBUG_SAMPLING_POINTS) {
-            if (isGeometric) {
-                for (int i = 0; i < inputSize; ++i) {
-                    AKLOGI("(%d) x %d, y %d, time %d",
-                            i, xCoordinates[i], yCoordinates[i], times[i]);
-                }
-            }
-        }
-#ifdef DO_ASSERT_TEST
-        if (times) {
-            for (int i = 0; i < inputSize; ++i) {
-                if (i > 0) {
-                    ASSERT(times[i] >= times[i - 1]);
-                }
-            }
-        }
-#endif
-        const bool proximityOnly = !isGeometric && (xCoordinates[0] < 0 || yCoordinates[0] < 0);
-        int lastInputIndex = pushTouchPointStartIndex;
-        for (int i = lastInputIndex; i < inputSize; ++i) {
-            const int pid = pointerIds ? pointerIds[i] : 0;
-            if (pointerId == pid) {
-                lastInputIndex = i;
-            }
-        }
-        if (DEBUG_GEO_FULL) {
-            AKLOGI("Init ProximityInfoState: last input index = %d", lastInputIndex);
-        }
-        // Working space to save near keys distances for current, prev and prevprev input point.
-        NearKeysDistanceMap nearKeysDistances[3];
-        // These pointers are swapped for each inputs points.
-        NearKeysDistanceMap *currentNearKeysDistances = &nearKeysDistances[0];
-        NearKeysDistanceMap *prevNearKeysDistances = &nearKeysDistances[1];
-        NearKeysDistanceMap *prevPrevNearKeysDistances = &nearKeysDistances[2];
-        // "sumAngle" is accumulated by each angle of input points. And when "sumAngle" exceeds
-        // the threshold we save that point, reset sumAngle. This aims to keep the figure of
-        // the curve.
-        float sumAngle = 0.0f;
-
-        for (int i = pushTouchPointStartIndex; i <= lastInputIndex; ++i) {
-            // Assuming pointerId == 0 if pointerIds is null.
-            const int pid = pointerIds ? pointerIds[i] : 0;
-            if (DEBUG_GEO_FULL) {
-                AKLOGI("Init ProximityInfoState: (%d)PID = %d", i, pid);
-            }
-            if (pointerId == pid) {
-                const int c = isGeometric ? NOT_A_COORDINATE : getPrimaryCodePointAt(i);
-                const int x = proximityOnly ? NOT_A_COORDINATE : xCoordinates[i];
-                const int y = proximityOnly ? NOT_A_COORDINATE : yCoordinates[i];
-                const int time = times ? times[i] : -1;
-
-                if (i > 1) {
-                    const float prevAngle = getAngle(xCoordinates[i - 2], yCoordinates[i - 2],
-                            xCoordinates[i - 1], yCoordinates[i - 1]);
-                    const float currentAngle =
-                            getAngle(xCoordinates[i - 1], yCoordinates[i - 1], x, y);
-                    sumAngle += getAngleDiff(prevAngle, currentAngle);
-                }
-
-                if (pushTouchPoint(i, c, x, y, time, isGeometric /* do sampling */,
-                        i == lastInputIndex, sumAngle, currentNearKeysDistances,
-                        prevNearKeysDistances, prevPrevNearKeysDistances)) {
-                    // Previous point information was popped.
-                    NearKeysDistanceMap *tmp = prevNearKeysDistances;
-                    prevNearKeysDistances = currentNearKeysDistances;
-                    currentNearKeysDistances = tmp;
-                } else {
-                    NearKeysDistanceMap *tmp = prevPrevNearKeysDistances;
-                    prevPrevNearKeysDistances = prevNearKeysDistances;
-                    prevNearKeysDistances = currentNearKeysDistances;
-                    currentNearKeysDistances = tmp;
-                    sumAngle = 0.0f;
-                }
-            }
-        }
-        mSampledInputSize = mSampledInputXs.size();
+        mSampledInputSize = ProximityInfoStateUtils::updateTouchPoints(
+                mProximityInfo->getMostCommonKeyWidth(), mProximityInfo, mMaxPointToKeyLength,
+                mInputProximities, xCoordinates, yCoordinates, times, pointerIds, inputSize,
+                isGeometric, pointerId, pushTouchPointStartIndex,
+                &mSampledInputXs, &mSampledInputYs, &mTimes, &mLengthCache, &mInputIndice);
     }
 
     if (mSampledInputSize > 0 && isGeometric) {
@@ -497,163 +404,6 @@
     return true;
 }
 
-// Calculating point to key distance for all near keys and returning the distance between
-// the given point and the nearest key position.
-float ProximityInfoState::updateNearKeysDistances(const int x, const int y,
-        NearKeysDistanceMap *const currentNearKeysDistances) {
-    static const float NEAR_KEY_THRESHOLD = 2.0f;
-
-    currentNearKeysDistances->clear();
-    const int keyCount = mProximityInfo->getKeyCount();
-    float nearestKeyDistance = mMaxPointToKeyLength;
-    for (int k = 0; k < keyCount; ++k) {
-        const float dist = mProximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(k, x, y);
-        if (dist < NEAR_KEY_THRESHOLD) {
-            currentNearKeysDistances->insert(std::pair<int, float>(k, dist));
-        }
-        if (nearestKeyDistance > dist) {
-            nearestKeyDistance = dist;
-        }
-    }
-    return nearestKeyDistance;
-}
-
-// Check if previous point is at local minimum position to near keys.
-bool ProximityInfoState::isPrevLocalMin(const NearKeysDistanceMap *const currentNearKeysDistances,
-        const NearKeysDistanceMap *const prevNearKeysDistances,
-        const NearKeysDistanceMap *const prevPrevNearKeysDistances) const {
-    static const float MARGIN = 0.01f;
-
-    for (NearKeysDistanceMap::const_iterator it = prevNearKeysDistances->begin();
-        it != prevNearKeysDistances->end(); ++it) {
-        NearKeysDistanceMap::const_iterator itPP = prevPrevNearKeysDistances->find(it->first);
-        NearKeysDistanceMap::const_iterator itC = currentNearKeysDistances->find(it->first);
-        if ((itPP == prevPrevNearKeysDistances->end() || itPP->second > it->second + MARGIN)
-                && (itC == currentNearKeysDistances->end() || itC->second > it->second + MARGIN)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-// Calculating a point score that indicates usefulness of the point.
-float ProximityInfoState::getPointScore(
-        const int x, const int y, const int time, const bool lastPoint, const float nearest,
-        const float sumAngle, const NearKeysDistanceMap *const currentNearKeysDistances,
-        const NearKeysDistanceMap *const prevNearKeysDistances,
-        const NearKeysDistanceMap *const prevPrevNearKeysDistances) const {
-    static const int DISTANCE_BASE_SCALE = 100;
-    static const float NEAR_KEY_THRESHOLD = 0.6f;
-    static const int CORNER_CHECK_DISTANCE_THRESHOLD_SCALE = 25;
-    static const float NOT_LOCALMIN_DISTANCE_SCORE = -1.0f;
-    static const float LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE = 1.0f;
-    static const float CORNER_ANGLE_THRESHOLD = M_PI_F * 2.0f / 3.0f;
-    static const float CORNER_SUM_ANGLE_THRESHOLD = M_PI_F / 4.0f;
-    static const float CORNER_SCORE = 1.0f;
-
-    const size_t size = mSampledInputXs.size();
-    // If there is only one point, add this point. Besides, if the previous point's distance map
-    // is empty, we re-compute nearby keys distances from the current point.
-    // Note that the current point is the first point in the incremental input that needs to
-    // be re-computed.
-    if (size <= 1 || prevNearKeysDistances->empty()) {
-        return 0.0f;
-    }
-
-    const int baseSampleRate = mProximityInfo->getMostCommonKeyWidth();
-    const int distPrev = getDistanceInt(mSampledInputXs.back(), mSampledInputYs.back(),
-            mSampledInputXs[size - 2], mSampledInputYs[size - 2]) * DISTANCE_BASE_SCALE;
-    float score = 0.0f;
-
-    // Location
-    if (!isPrevLocalMin(currentNearKeysDistances, prevNearKeysDistances,
-        prevPrevNearKeysDistances)) {
-        score += NOT_LOCALMIN_DISTANCE_SCORE;
-    } else if (nearest < NEAR_KEY_THRESHOLD) {
-        // Promote points nearby keys
-        score += LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE;
-    }
-    // Angle
-    const float angle1 = getAngle(x, y, mSampledInputXs.back(), mSampledInputYs.back());
-    const float angle2 = getAngle(mSampledInputXs.back(), mSampledInputYs.back(),
-            mSampledInputXs[size - 2], mSampledInputYs[size - 2]);
-    const float angleDiff = getAngleDiff(angle1, angle2);
-
-    // Save corner
-    if (distPrev > baseSampleRate * CORNER_CHECK_DISTANCE_THRESHOLD_SCALE
-            && (sumAngle > CORNER_SUM_ANGLE_THRESHOLD || angleDiff > CORNER_ANGLE_THRESHOLD)) {
-        score += CORNER_SCORE;
-    }
-    return score;
-}
-
-// Sampling touch point and pushing information to vectors.
-// Returning if previous point is popped or not.
-bool ProximityInfoState::pushTouchPoint(const int inputIndex, const int nodeCodePoint, int x, int y,
-        const int time, const bool sample, const bool isLastPoint, const float sumAngle,
-        NearKeysDistanceMap *const currentNearKeysDistances,
-        const NearKeysDistanceMap *const prevNearKeysDistances,
-        const NearKeysDistanceMap *const prevPrevNearKeysDistances) {
-    static const int LAST_POINT_SKIP_DISTANCE_SCALE = 4;
-
-    size_t size = mSampledInputXs.size();
-    bool popped = false;
-    if (nodeCodePoint < 0 && sample) {
-        const float nearest = updateNearKeysDistances(x, y, currentNearKeysDistances);
-        const float score = getPointScore(x, y, time, isLastPoint, nearest, sumAngle,
-                currentNearKeysDistances, prevNearKeysDistances, prevPrevNearKeysDistances);
-        if (score < 0) {
-            // Pop previous point because it would be useless.
-            popInputData();
-            size = mSampledInputXs.size();
-            popped = true;
-        } else {
-            popped = false;
-        }
-        // Check if the last point should be skipped.
-        if (isLastPoint && size > 0) {
-            if (getDistanceInt(x, y, mSampledInputXs.back(), mSampledInputYs.back())
-                    * LAST_POINT_SKIP_DISTANCE_SCALE < mProximityInfo->getMostCommonKeyWidth()) {
-                // This point is not used because it's too close to the previous point.
-                if (DEBUG_GEO_FULL) {
-                    AKLOGI("p0: size = %zd, x = %d, y = %d, lx = %d, ly = %d, dist = %d, "
-                           "width = %d", size, x, y, mSampledInputXs.back(), mSampledInputYs.back(),
-                           getDistanceInt(x, y, mSampledInputXs.back(), mSampledInputYs.back()),
-                           mProximityInfo->getMostCommonKeyWidth()
-                                   / LAST_POINT_SKIP_DISTANCE_SCALE);
-                }
-                return popped;
-            }
-        }
-    }
-
-    if (nodeCodePoint >= 0 && (x < 0 || y < 0)) {
-        const int keyId = mProximityInfo->getKeyIndexOf(nodeCodePoint);
-        if (keyId >= 0) {
-            x = mProximityInfo->getKeyCenterXOfKeyIdG(keyId);
-            y = mProximityInfo->getKeyCenterYOfKeyIdG(keyId);
-        }
-    }
-
-    // Pushing point information.
-    if (size > 0) {
-        mLengthCache.push_back(
-                mLengthCache.back() + getDistanceInt(
-                        x, y, mSampledInputXs.back(), mSampledInputYs.back()));
-    } else {
-        mLengthCache.push_back(0);
-    }
-    mSampledInputXs.push_back(x);
-    mSampledInputYs.push_back(y);
-    mTimes.push_back(time);
-    mInputIndice.push_back(inputIndex);
-    if (DEBUG_GEO_FULL) {
-        AKLOGI("pushTouchPoint: x = %03d, y = %03d, time = %d, index = %d, popped ? %01d",
-                x, y, time, inputIndex, popped);
-    }
-    return popped;
-}
-
 float ProximityInfoState::calculateNormalizedSquaredDistance(
         const int keyIndex, const int inputIndex) const {
     if (keyIndex == NOT_AN_INDEX) {
@@ -826,11 +576,8 @@
 }
 
 void ProximityInfoState::popInputData() {
-    mSampledInputXs.pop_back();
-    mSampledInputYs.pop_back();
-    mTimes.pop_back();
-    mLengthCache.pop_back();
-    mInputIndice.pop_back();
+    ProximityInfoStateUtils::popInputData(&mSampledInputXs, &mSampledInputYs, &mTimes,
+            &mLengthCache, &mInputIndice);
 }
 
 float ProximityInfoState::getDirection(const int index0, const int index1) const {
@@ -889,7 +636,8 @@
     const int keyX = mProximityInfo->getKeyCenterXOfKeyIdG(keyId);
     const int keyY = mProximityInfo->getKeyCenterYOfKeyIdG(keyId);
 
-    return pointToLineSegSquaredDistanceFloat(keyX, keyY, x0, y0, x1, y1, extend);
+    return ProximityInfoUtils::pointToLineSegSquaredDistanceFloat(
+            keyX, keyY, x0, y0, x1, y1, extend);
 }
 
 // Updates probabilities of aligning to some keys and skipping.
@@ -1008,7 +756,8 @@
                         MAX_SPEEDxNEAREST_RATE_FOR_STANDERD_DIVIATION);
         const float sigma = speedxAngleRate + speedxNearestKeyDistanceRate + MIN_STANDERD_DIVIATION;
 
-        NormalDistribution distribution(CENTER_VALUE_OF_NORMALIZED_DISTRIBUTION, sigma);
+        ProximityInfoUtils::NormalDistribution
+                distribution(CENTER_VALUE_OF_NORMALIZED_DISTRIBUTION, sigma);
         static const float PREV_DISTANCE_WEIGHT = 0.5f;
         static const float NEXT_DISTANCE_WEIGHT = 0.6f;
         // Summing up probability densities of all near keys.
@@ -1207,6 +956,11 @@
     return sumLogProbability;
 }
 
+bool ProximityInfoState::hasSpaceProximity(const int index) const {
+    ASSERT(0 <= index && index < mSampledInputSize);
+    return mProximityInfo->hasSpaceProximity(getInputX(index), getInputY(index));
+}
+
 // Returns a probability of mapping index to keyIndex.
 float ProximityInfoState::getProbability(const int index, const int keyIndex) const {
     ASSERT(0 <= index && index < mSampledInputSize);
diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h
index d747bae..0f0eb7d 100644
--- a/native/jni/src/proximity_info_state.h
+++ b/native/jni/src/proximity_info_state.h
@@ -19,12 +19,12 @@
 
 #include <bitset>
 #include <cstring> // for memset()
-#include <string>
 #include <vector>
 
 #include "char_utils.h"
 #include "defines.h"
 #include "hash_map_compat.h"
+#include "proximity_info_state_utils.h"
 
 namespace latinime {
 
@@ -55,13 +55,13 @@
     /////////////////////////////////////////
     AK_FORCE_INLINE ProximityInfoState()
             : mProximityInfo(0), mMaxPointToKeyLength(0.0f), mAverageSpeed(0.0f),
-              mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0), mLocaleStr(),
+              mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0),
               mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0),
               mIsContinuationPossible(false), mSampledInputXs(), mSampledInputYs(), mTimes(),
               mInputIndice(), mLengthCache(), mBeelineSpeedPercentiles(), mDistanceCache_G(),
               mSpeedRates(), mDirections(), mCharProbabilities(), mNearKeysVector(),
               mSearchKeysVector(), mTouchPositionCorrectionEnabled(false), mSampledInputSize(0) {
-        memset(mInputCodes, 0, sizeof(mInputCodes));
+        memset(mInputProximities, 0, sizeof(mInputProximities));
         memset(mNormalizedSquaredDistances, 0, sizeof(mNormalizedSquaredDistances));
         memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord));
     }
@@ -117,12 +117,12 @@
         if (length != mSampledInputSize) {
             return false;
         }
-        const int *inputCodes = mInputCodes;
+        const int *inputProximities = mInputProximities;
         while (length--) {
-            if (*inputCodes != *word) {
+            if (*inputProximities != *word) {
                 return false;
             }
-            inputCodes += MAX_PROXIMITY_CHARS_SIZE_INTERNAL;
+            inputProximities += MAX_PROXIMITY_CHARS_SIZE_INTERNAL;
             word++;
         }
         return true;
@@ -146,6 +146,8 @@
         return mSampledInputYs[index];
     }
 
+    bool hasSpaceProximity(const int index) const;
+
     int getLengthCache(const int index) const {
         return mLengthCache[index];
     }
@@ -229,7 +231,7 @@
     }
 
     inline const int *getProximityCodePointsAt(const int index) const {
-        return mInputCodes + (index * MAX_PROXIMITY_CHARS_SIZE_INTERNAL);
+        return ProximityInfoStateUtils::getProximityCodePointsAt(mInputProximities, index);
     }
 
     float updateNearKeysDistances(const int x, const int y,
@@ -260,7 +262,6 @@
     float mAverageSpeed;
     bool mHasTouchPositionCorrectionData;
     int mMostCommonKeyWidthSquare;
-    std::string mLocaleStr;
     int mKeyCount;
     int mCellHeight;
     int mCellWidth;
@@ -289,7 +290,7 @@
     // inputs including the current input point.
     std::vector<NearKeycodesSet> mSearchKeysVector;
     bool mTouchPositionCorrectionEnabled;
-    int mInputCodes[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH];
+    int mInputProximities[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH];
     int mNormalizedSquaredDistances[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH];
     int mSampledInputSize;
     int mPrimaryInputWord[MAX_WORD_LENGTH];
diff --git a/native/jni/src/proximity_info_state_utils.h b/native/jni/src/proximity_info_state_utils.h
new file mode 100644
index 0000000..53b992a
--- /dev/null
+++ b/native/jni/src/proximity_info_state_utils.h
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#ifndef LATINIME_PROXIMITY_INFO_STATE_UTILS_H
+#define LATINIME_PROXIMITY_INFO_STATE_UTILS_H
+
+#include <vector>
+
+#include "defines.h"
+#include "geometry_utils.h"
+#include "hash_map_compat.h"
+#include "proximity_info.h"
+
+namespace latinime {
+class ProximityInfoStateUtils {
+ public:
+    static int updateTouchPoints(const int mostCommonKeyWidth,
+            const ProximityInfo *const proximityInfo, const int maxPointToKeyLength,
+            const int *const inputProximities,
+            const int *const inputXCoordinates, const int *const inputYCoordinates,
+            const int *const times, const int *const pointerIds, const int inputSize,
+            const bool isGeometric, const int pointerId, const int pushTouchPointStartIndex,
+            std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs,
+            std::vector<int> *sampledInputTimes, std::vector<int> *sampledLengthCache,
+            std::vector<int> *sampledInputIndice) {
+        if (DEBUG_SAMPLING_POINTS) {
+            if (times) {
+                for (int i = 0; i < inputSize; ++i) {
+                    AKLOGI("(%d) x %d, y %d, time %d",
+                            i, xCoordinates[i], yCoordinates[i], times[i]);
+                }
+            }
+        }
+#ifdef DO_ASSERT_TEST
+        if (times) {
+            for (int i = 0; i < inputSize; ++i) {
+                if (i > 0) {
+                    ASSERT(times[i] >= times[i - 1]);
+                }
+            }
+        }
+#endif
+        const bool proximityOnly = !isGeometric
+                && (inputXCoordinates[0] < 0 || inputYCoordinates[0] < 0);
+        int lastInputIndex = pushTouchPointStartIndex;
+        for (int i = lastInputIndex; i < inputSize; ++i) {
+            const int pid = pointerIds ? pointerIds[i] : 0;
+            if (pointerId == pid) {
+                lastInputIndex = i;
+            }
+        }
+        if (DEBUG_GEO_FULL) {
+            AKLOGI("Init ProximityInfoState: last input index = %d", lastInputIndex);
+        }
+        // Working space to save near keys distances for current, prev and prevprev input point.
+        NearKeysDistanceMap nearKeysDistances[3];
+        // These pointers are swapped for each inputs points.
+        NearKeysDistanceMap *currentNearKeysDistances = &nearKeysDistances[0];
+        NearKeysDistanceMap *prevNearKeysDistances = &nearKeysDistances[1];
+        NearKeysDistanceMap *prevPrevNearKeysDistances = &nearKeysDistances[2];
+        // "sumAngle" is accumulated by each angle of input points. And when "sumAngle" exceeds
+        // the threshold we save that point, reset sumAngle. This aims to keep the figure of
+        // the curve.
+        float sumAngle = 0.0f;
+
+        for (int i = pushTouchPointStartIndex; i <= lastInputIndex; ++i) {
+            // Assuming pointerId == 0 if pointerIds is null.
+            const int pid = pointerIds ? pointerIds[i] : 0;
+            if (DEBUG_GEO_FULL) {
+                AKLOGI("Init ProximityInfoState: (%d)PID = %d", i, pid);
+            }
+            if (pointerId == pid) {
+                const int c = isGeometric ?
+                        NOT_A_COORDINATE : getPrimaryCodePointAt(inputProximities, i);
+                const int x = proximityOnly ? NOT_A_COORDINATE : inputXCoordinates[i];
+                const int y = proximityOnly ? NOT_A_COORDINATE : inputYCoordinates[i];
+                const int time = times ? times[i] : -1;
+
+                if (i > 1) {
+                    const float prevAngle = getAngle(
+                            inputXCoordinates[i - 2], inputYCoordinates[i - 2],
+                            inputXCoordinates[i - 1], inputYCoordinates[i - 1]);
+                    const float currentAngle =
+                            getAngle(inputXCoordinates[i - 1], inputYCoordinates[i - 1], x, y);
+                    sumAngle += getAngleDiff(prevAngle, currentAngle);
+                }
+
+                if (pushTouchPoint(mostCommonKeyWidth, proximityInfo, maxPointToKeyLength,
+                        i, c, x, y, time, isGeometric /* do sampling */,
+                        i == lastInputIndex, sumAngle, currentNearKeysDistances,
+                        prevNearKeysDistances, prevPrevNearKeysDistances,
+                        sampledInputXs, sampledInputYs, sampledInputTimes, sampledLengthCache,
+                        sampledInputIndice)) {
+                    // Previous point information was popped.
+                    NearKeysDistanceMap *tmp = prevNearKeysDistances;
+                    prevNearKeysDistances = currentNearKeysDistances;
+                    currentNearKeysDistances = tmp;
+                } else {
+                    NearKeysDistanceMap *tmp = prevPrevNearKeysDistances;
+                    prevPrevNearKeysDistances = prevNearKeysDistances;
+                    prevNearKeysDistances = currentNearKeysDistances;
+                    currentNearKeysDistances = tmp;
+                    sumAngle = 0.0f;
+                }
+            }
+        }
+        return sampledInputXs->size();
+    }
+
+    static const int *getProximityCodePointsAt(
+            const int *const inputProximities, const int index) {
+        return inputProximities + (index * MAX_PROXIMITY_CHARS_SIZE_INTERNAL);
+    }
+
+    static int getPrimaryCodePointAt(const int *const inputProximities, const int index) {
+        return getProximityCodePointsAt(inputProximities, index)[0];
+    }
+
+    static void popInputData(std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs,
+            std::vector<int> *sampledInputTimes, std::vector<int> *sampledLengthCache,
+            std::vector<int> *sampledInputIndice) {
+        sampledInputXs->pop_back();
+        sampledInputYs->pop_back();
+        sampledInputTimes->pop_back();
+        sampledLengthCache->pop_back();
+        sampledInputIndice->pop_back();
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfoStateUtils);
+
+    typedef hash_map_compat<int, float> NearKeysDistanceMap;
+
+    // Calculating point to key distance for all near keys and returning the distance between
+    // the given point and the nearest key position.
+    static float updateNearKeysDistances(const ProximityInfo *const proximityInfo,
+            const float maxPointToKeyLength, const int x, const int y,
+            NearKeysDistanceMap *const currentNearKeysDistances) {
+        static const float NEAR_KEY_THRESHOLD = 2.0f;
+
+        currentNearKeysDistances->clear();
+        const int keyCount = proximityInfo->getKeyCount();
+        float nearestKeyDistance = maxPointToKeyLength;
+        for (int k = 0; k < keyCount; ++k) {
+            const float dist = proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(k, x, y);
+            if (dist < NEAR_KEY_THRESHOLD) {
+                currentNearKeysDistances->insert(std::pair<int, float>(k, dist));
+            }
+            if (nearestKeyDistance > dist) {
+                nearestKeyDistance = dist;
+            }
+        }
+        return nearestKeyDistance;
+    }
+
+    // Check if previous point is at local minimum position to near keys.
+    static bool isPrevLocalMin(const NearKeysDistanceMap *const currentNearKeysDistances,
+            const NearKeysDistanceMap *const prevNearKeysDistances,
+            const NearKeysDistanceMap *const prevPrevNearKeysDistances) {
+        static const float MARGIN = 0.01f;
+
+        for (NearKeysDistanceMap::const_iterator it = prevNearKeysDistances->begin();
+            it != prevNearKeysDistances->end(); ++it) {
+            NearKeysDistanceMap::const_iterator itPP = prevPrevNearKeysDistances->find(it->first);
+            NearKeysDistanceMap::const_iterator itC = currentNearKeysDistances->find(it->first);
+            if ((itPP == prevPrevNearKeysDistances->end() || itPP->second > it->second + MARGIN)
+                    && (itC == currentNearKeysDistances->end()
+                            || itC->second > it->second + MARGIN)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Calculating a point score that indicates usefulness of the point.
+    static float getPointScore(const int mostCommonKeyWidth,
+            const int x, const int y, const int time, const bool lastPoint, const float nearest,
+            const float sumAngle, const NearKeysDistanceMap *const currentNearKeysDistances,
+            const NearKeysDistanceMap *const prevNearKeysDistances,
+            const NearKeysDistanceMap *const prevPrevNearKeysDistances,
+            std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs) {
+        static const int DISTANCE_BASE_SCALE = 100;
+        static const float NEAR_KEY_THRESHOLD = 0.6f;
+        static const int CORNER_CHECK_DISTANCE_THRESHOLD_SCALE = 25;
+        static const float NOT_LOCALMIN_DISTANCE_SCORE = -1.0f;
+        static const float LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE = 1.0f;
+        static const float CORNER_ANGLE_THRESHOLD = M_PI_F * 2.0f / 3.0f;
+        static const float CORNER_SUM_ANGLE_THRESHOLD = M_PI_F / 4.0f;
+        static const float CORNER_SCORE = 1.0f;
+
+        const size_t size = sampledInputXs->size();
+        // If there is only one point, add this point. Besides, if the previous point's distance map
+        // is empty, we re-compute nearby keys distances from the current point.
+        // Note that the current point is the first point in the incremental input that needs to
+        // be re-computed.
+        if (size <= 1 || prevNearKeysDistances->empty()) {
+            return 0.0f;
+        }
+
+        const int baseSampleRate = mostCommonKeyWidth;
+        const int distPrev = getDistanceInt(
+                sampledInputXs->back(), sampledInputYs->back(),
+                (*sampledInputXs)[size - 2], (*sampledInputYs)[size - 2]) * DISTANCE_BASE_SCALE;
+        float score = 0.0f;
+
+        // Location
+        if (!isPrevLocalMin(currentNearKeysDistances, prevNearKeysDistances,
+            prevPrevNearKeysDistances)) {
+            score += NOT_LOCALMIN_DISTANCE_SCORE;
+        } else if (nearest < NEAR_KEY_THRESHOLD) {
+            // Promote points nearby keys
+            score += LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE;
+        }
+        // Angle
+        const float angle1 = getAngle(x, y, sampledInputXs->back(), sampledInputYs->back());
+        const float angle2 = getAngle(sampledInputXs->back(), sampledInputYs->back(),
+                (*sampledInputXs)[size - 2], (*sampledInputYs)[size - 2]);
+        const float angleDiff = getAngleDiff(angle1, angle2);
+
+        // Save corner
+        if (distPrev > baseSampleRate * CORNER_CHECK_DISTANCE_THRESHOLD_SCALE
+                && (sumAngle > CORNER_SUM_ANGLE_THRESHOLD || angleDiff > CORNER_ANGLE_THRESHOLD)) {
+            score += CORNER_SCORE;
+        }
+        return score;
+    }
+
+    // Sampling touch point and pushing information to vectors.
+    // Returning if previous point is popped or not.
+    static bool pushTouchPoint(const int mostCommonKeyWidth,
+            const ProximityInfo *const proximityInfo, const int maxPointToKeyLength,
+            const int inputIndex, const int nodeCodePoint, int x, int y,
+            const int time, const bool sample, const bool isLastPoint, const float sumAngle,
+            NearKeysDistanceMap *const currentNearKeysDistances,
+            const NearKeysDistanceMap *const prevNearKeysDistances,
+            const NearKeysDistanceMap *const prevPrevNearKeysDistances,
+            std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs,
+            std::vector<int> *sampledInputTimes, std::vector<int> *sampledLengthCache,
+            std::vector<int> *sampledInputIndice) {
+        static const int LAST_POINT_SKIP_DISTANCE_SCALE = 4;
+
+        size_t size = sampledInputXs->size();
+        bool popped = false;
+        if (nodeCodePoint < 0 && sample) {
+            const float nearest = updateNearKeysDistances(
+                    proximityInfo, maxPointToKeyLength, x, y, currentNearKeysDistances);
+            const float score = getPointScore(mostCommonKeyWidth, x, y, time, isLastPoint, nearest,
+                    sumAngle, currentNearKeysDistances, prevNearKeysDistances,
+                    prevPrevNearKeysDistances, sampledInputXs, sampledInputYs);
+            if (score < 0) {
+                // Pop previous point because it would be useless.
+                popInputData(sampledInputXs, sampledInputYs, sampledInputTimes, sampledLengthCache,
+                        sampledInputIndice);
+                size = sampledInputXs->size();
+                popped = true;
+            } else {
+                popped = false;
+            }
+            // Check if the last point should be skipped.
+            if (isLastPoint && size > 0) {
+                if (getDistanceInt(x, y, sampledInputXs->back(),
+                        sampledInputYs->back()) * LAST_POINT_SKIP_DISTANCE_SCALE
+                                < mostCommonKeyWidth) {
+                    // This point is not used because it's too close to the previous point.
+                    if (DEBUG_GEO_FULL) {
+                        AKLOGI("p0: size = %zd, x = %d, y = %d, lx = %d, ly = %d, dist = %d, "
+                               "width = %d", size, x, y, mSampledInputXs.back(),
+                               mSampledInputYs.back(), ProximityInfoUtils::getDistanceInt(
+                                       x, y, mSampledInputXs.back(), mSampledInputYs.back()),
+                               mProximityInfo->getMostCommonKeyWidth()
+                                       / LAST_POINT_SKIP_DISTANCE_SCALE);
+                    }
+                    return popped;
+                }
+            }
+        }
+
+        if (nodeCodePoint >= 0 && (x < 0 || y < 0)) {
+            const int keyId = proximityInfo->getKeyIndexOf(nodeCodePoint);
+            if (keyId >= 0) {
+                x = proximityInfo->getKeyCenterXOfKeyIdG(keyId);
+                y = proximityInfo->getKeyCenterYOfKeyIdG(keyId);
+            }
+        }
+
+        // Pushing point information.
+        if (size > 0) {
+            sampledLengthCache->push_back(
+                    sampledLengthCache->back() + getDistanceInt(
+                            x, y, sampledInputXs->back(), sampledInputYs->back()));
+        } else {
+            sampledLengthCache->push_back(0);
+        }
+        sampledInputXs->push_back(x);
+        sampledInputYs->push_back(y);
+        sampledInputTimes->push_back(time);
+        sampledInputIndice->push_back(inputIndex);
+        if (DEBUG_GEO_FULL) {
+            AKLOGI("pushTouchPoint: x = %03d, y = %03d, time = %d, index = %d, popped ? %01d",
+                    x, y, time, inputIndex, popped);
+        }
+        return popped;
+    }
+};
+} // namespace latinime
+#endif // LATINIME_PROXIMITY_INFO_STATE_UTILS_H
diff --git a/native/jni/src/proximity_info_utils.h b/native/jni/src/proximity_info_utils.h
new file mode 100644
index 0000000..0930207
--- /dev/null
+++ b/native/jni/src/proximity_info_utils.h
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#ifndef LATINIME_PROXIMITY_INFO_UTILS_H
+#define LATINIME_PROXIMITY_INFO_UTILS_H
+
+#include <cmath>
+
+#include "additional_proximity_chars.h"
+#include "char_utils.h"
+#include "defines.h"
+#include "geometry_utils.h"
+#include "hash_map_compat.h"
+
+namespace latinime {
+class ProximityInfoUtils {
+ public:
+    static int getKeyIndexOf(const int keyCount, const int c,
+            const hash_map_compat<int, int> *const codeToKeyMap) {
+        if (keyCount == 0) {
+            // We do not have the coordinate data
+            return NOT_AN_INDEX;
+        }
+        if (c == NOT_A_CODE_POINT) {
+            return NOT_AN_INDEX;
+        }
+        const int lowerCode = toLowerCase(c);
+        hash_map_compat<int, int>::const_iterator mapPos = codeToKeyMap->find(lowerCode);
+        if (mapPos != codeToKeyMap->end()) {
+            return mapPos->second;
+        }
+        return NOT_AN_INDEX;
+    }
+
+    static void initializeProximities(const int *const inputCodes,
+            const int *const inputXCoordinates, const int *const inputYCoordinates,
+            const int inputSize, const int *const keyXCoordinates,
+            const int *const keyYCoordinates, const int *const keyWidths, const int *keyHeights,
+            const int *const proximityCharsArray, const int maxProximityCharsSize,
+            const int cellHeight, const int cellWidth, const int gridWidth,
+            const int mostCommonKeyWidth, const int keyCount, const char *const localeStr,
+            const hash_map_compat<int, int> *const codeToKeyMap, int *inputProximities) {
+        // Initialize
+        // - mInputCodes
+        // - mNormalizedSquaredDistances
+        // TODO: Merge
+        for (int i = 0; i < inputSize; ++i) {
+            const int primaryKey = inputCodes[i];
+            const int x = inputXCoordinates[i];
+            const int y = inputYCoordinates[i];
+            int *proximities = &inputProximities[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL];
+            calculateProximities(keyXCoordinates, keyYCoordinates, keyWidths, keyHeights,
+                    proximityCharsArray, maxProximityCharsSize, cellHeight, cellWidth, gridWidth,
+                    mostCommonKeyWidth, keyCount, x, y, primaryKey, localeStr, codeToKeyMap,
+                    proximities);
+        }
+
+        if (DEBUG_PROXIMITY_CHARS) {
+            for (int i = 0; i < inputSize; ++i) {
+                AKLOGI("---");
+                for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL; ++j) {
+                    int proximityChar =
+                            inputProximities[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
+                    proximityChar += 0;
+                    AKLOGI("--- (%d)%c", i, proximityChar);
+                }
+            }
+        }
+    }
+
+    static AK_FORCE_INLINE int getStartIndexFromCoordinates(const int maxProximityCharsSize,
+            const int x, const int y, const int cellHeight, const int cellWidth,
+            const int gridWidth) {
+        return ((y / cellHeight) * gridWidth + (x / cellWidth)) * maxProximityCharsSize;
+    }
+
+    static inline float getSquaredDistanceFloat(const float x1, const float y1, const float x2,
+            const float y2) {
+        return SQUARE_FLOAT(x1 - x2) + SQUARE_FLOAT(y1 - y2);
+    }
+
+    static inline float pointToLineSegSquaredDistanceFloat(const float x, const float y,
+        const float x1, const float y1, const float x2, const float y2, const bool extend) {
+        const float ray1x = x - x1;
+        const float ray1y = y - y1;
+        const float ray2x = x2 - x1;
+        const float ray2y = y2 - y1;
+
+        const float dotProduct = ray1x * ray2x + ray1y * ray2y;
+        const float lineLengthSqr = SQUARE_FLOAT(ray2x) + SQUARE_FLOAT(ray2y);
+        const float projectionLengthSqr = dotProduct / lineLengthSqr;
+
+        float projectionX;
+        float projectionY;
+        if (!extend && projectionLengthSqr < 0.0f) {
+            projectionX = x1;
+            projectionY = y1;
+        } else if (!extend && projectionLengthSqr > 1.0f) {
+            projectionX = x2;
+            projectionY = y2;
+        } else {
+            projectionX = x1 + projectionLengthSqr * ray2x;
+            projectionY = y1 + projectionLengthSqr * ray2y;
+        }
+        return getSquaredDistanceFloat(x, y, projectionX, projectionY);
+    }
+
+    // Normal distribution N(u, sigma^2).
+    struct NormalDistribution {
+     public:
+        NormalDistribution(const float u, const float sigma)
+                : mU(u), mSigma(sigma),
+                  mPreComputedNonExpPart(1.0f / sqrtf(2.0f * M_PI_F * SQUARE_FLOAT(sigma))),
+                  mPreComputedExponentPart(-1.0f / (2.0f * SQUARE_FLOAT(sigma))) {}
+
+        float getProbabilityDensity(const float x) const {
+            const float shiftedX = x - mU;
+            return mPreComputedNonExpPart * expf(mPreComputedExponentPart * SQUARE_FLOAT(shiftedX));
+        }
+
+     private:
+        DISALLOW_IMPLICIT_CONSTRUCTORS(NormalDistribution);
+        const float mU; // mean value
+        const float mSigma; // standard deviation
+        const float mPreComputedNonExpPart; // = 1 / sqrt(2 * PI * sigma^2)
+        const float mPreComputedExponentPart; // = -1 / (2 * sigma^2)
+    }; // struct NormalDistribution
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfoUtils);
+
+    static bool isOnKey(const int *const keyXCoordinates, const int *const keyYCoordinates,
+            const int *const keyWidths, const int *keyHeights, const int keyId, const int x,
+            const int y) {
+        if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
+        const int left = keyXCoordinates[keyId];
+        const int top = keyYCoordinates[keyId];
+        const int right = left + keyWidths[keyId] + 1;
+        const int bottom = top + keyHeights[keyId];
+        return left < right && top < bottom && x >= left && x < right && y >= top && y < bottom;
+    }
+
+    static void calculateProximities(
+            const int *const keyXCoordinates, const int *const keyYCoordinates,
+            const int *const keyWidths, const int *keyHeights,
+            const int *const proximityCharsArray,
+            const int maxProximityCharsSize, const int cellHeight, const int cellWidth,
+            const int gridWidth, const int mostCommonKeyWidth, const int keyCount,
+            const int x, const int y, const int primaryKey, const char *const localeStr,
+            const hash_map_compat<int, int> *const codeToKeyMap, int *proximities) {
+        const int mostCommonKeyWidthSquare = mostCommonKeyWidth * mostCommonKeyWidth;
+        int insertPos = 0;
+        proximities[insertPos++] = primaryKey;
+        const int startIndex = getStartIndexFromCoordinates(
+                maxProximityCharsSize, x, y, cellHeight, cellWidth, gridWidth);
+        if (startIndex >= 0) {
+            for (int i = 0; i < maxProximityCharsSize; ++i) {
+                const int c = proximityCharsArray[startIndex + i];
+                if (c < KEYCODE_SPACE || c == primaryKey) {
+                    continue;
+                }
+                const int keyIndex = getKeyIndexOf(keyCount, c, codeToKeyMap);
+                const bool onKey = isOnKey(keyXCoordinates, keyYCoordinates, keyWidths, keyHeights,
+                        keyIndex, x, y);
+                const int distance = squaredLengthToEdge(keyXCoordinates, keyYCoordinates,
+                        keyWidths, keyHeights, keyIndex, x, y);
+                if (onKey || distance < mostCommonKeyWidthSquare) {
+                    proximities[insertPos++] = c;
+                    if (insertPos >= maxProximityCharsSize) {
+                        if (DEBUG_DICT) {
+                            ASSERT(false);
+                        }
+                        return;
+                    }
+                }
+            }
+            const int additionalProximitySize =
+                    AdditionalProximityChars::getAdditionalCharsSize(localeStr, primaryKey);
+            if (additionalProximitySize > 0) {
+                proximities[insertPos++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
+                if (insertPos >= maxProximityCharsSize) {
+                    if (DEBUG_DICT) {
+                        ASSERT(false);
+                    }
+                    return;
+                }
+
+                const int *additionalProximityChars =
+                        AdditionalProximityChars::getAdditionalChars(localeStr, primaryKey);
+                for (int j = 0; j < additionalProximitySize; ++j) {
+                    const int ac = additionalProximityChars[j];
+                    int k = 0;
+                    for (; k < insertPos; ++k) {
+                        if (ac == proximities[k]) {
+                            break;
+                        }
+                    }
+                    if (k < insertPos) {
+                        continue;
+                    }
+                    proximities[insertPos++] = ac;
+                    if (insertPos >= maxProximityCharsSize) {
+                        if (DEBUG_DICT) {
+                            ASSERT(false);
+                        }
+                        return;
+                    }
+                }
+            }
+        }
+        // Add a delimiter for the proximity characters
+        for (int i = insertPos; i < maxProximityCharsSize; ++i) {
+            proximities[i] = NOT_A_CODE_POINT;
+        }
+    }
+
+    static int squaredLengthToEdge(const int *const keyXCoordinates,
+            const int *const keyYCoordinates, const int *const keyWidths, const int *keyHeights,
+            const int keyId, const int x, const int y) {
+        // NOT_A_ID is -1, but return whenever < 0 just in case
+        if (keyId < 0) return MAX_POINT_TO_KEY_LENGTH;
+        const int left = keyXCoordinates[keyId];
+        const int top = keyYCoordinates[keyId];
+        const int right = left + keyWidths[keyId];
+        const int bottom = top + keyHeights[keyId];
+        const int edgeX = x < left ? left : (x > right ? right : x);
+        const int edgeY = y < top ? top : (y > bottom ? bottom : y);
+        const int dx = x - edgeX;
+        const int dy = y - edgeY;
+        return dx * dx + dy * dy;
+    }
+};
+} // namespace latinime
+#endif // LATINIME_PROXIMITY_INFO_UTILS_H
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
index a1ceb8e..74ff879 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
@@ -150,59 +150,6 @@
         pressAndReleaseKey(CODE_SPACE, SYMBOLS_SHIFTED, ALPHABET_SHIFT_LOCKED);
     }
 
-    // Automatic switch back to alphabet by registered letters.
-    public void testSwitchBackChar() {
-        // Set switch back chars.
-        final String switchBackSymbols = "'";
-        final int switchBackCode = switchBackSymbols.codePointAt(0);
-        setLayoutSwitchBackSymbols(switchBackSymbols);
-        loadKeyboard(ALPHABET_UNSHIFTED);
-
-        // Press/release "?123" key, enter into symbols.
-        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
-        // Enter symbol letter.
-        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
-        // Enter switch back letter, switch back to alphabet.
-        pressAndReleaseKey(switchBackCode, SYMBOLS_UNSHIFTED, ALPHABET_UNSHIFTED);
-
-        // Press/release "?123" key, enter into symbols.
-        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
-        // Press/release "=\<" key, enter into symbols shifted.
-        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
-        // Enter symbol shift letter.
-        pressAndReleaseKey('~', SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
-        // Enter switch abck letter, switch back to alphabet.
-        pressAndReleaseKey(switchBackCode, SYMBOLS_SHIFTED, ALPHABET_UNSHIFTED);
-    }
-
-    // Automatic switch back to alphabet shift locked by registered letters.
-    public void testSwitchBackCharShiftLocked() {
-        // Set switch back chars.
-        final String switchBackSymbols = "'";
-        final int switchBackCode = switchBackSymbols.codePointAt(0);
-        setLayoutSwitchBackSymbols(switchBackSymbols);
-        loadKeyboard(ALPHABET_UNSHIFTED);
-        // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
-                ALPHABET_SHIFT_LOCKED);
-
-        // Press/release "?123" key, enter into symbols.
-        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
-        // Enter symbol letter.
-        pressAndReleaseKey('1', SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
-        // Enter switch back letter, switch back to alphabet shift locked.
-        pressAndReleaseKey(switchBackCode, SYMBOLS_UNSHIFTED, ALPHABET_SHIFT_LOCKED);
-
-        // Press/release "?123" key, enter into symbols.
-        pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
-        // Press/release "=\<" key, enter into symbols shifted.
-        pressAndReleaseKey(CODE_SHIFT, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
-        // Enter symbol shift letter.
-        pressAndReleaseKey(CODE_SPACE, SYMBOLS_SHIFTED, SYMBOLS_SHIFTED);
-        // Enter switch back letter, switch back to alphabet shift locked.
-        pressAndReleaseKey(switchBackCode, SYMBOLS_SHIFTED, ALPHABET_SHIFT_LOCKED);
-    }
-
     // Automatic upper case test
     public void testAutomaticUpperCase() {
         // Set capitalize the first character of all words mode.
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
index c75f826..08199a0 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
@@ -22,8 +22,6 @@
         implements MockKeyboardSwitcher.MockConstants {
     protected MockKeyboardSwitcher mSwitcher;
 
-    private String mLayoutSwitchBackSymbols = "";
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -34,88 +32,86 @@
         loadKeyboard(ALPHABET_UNSHIFTED);
     }
 
-    public void setAutoCapsMode(int autoCaps) {
+    public void setAutoCapsMode(final int autoCaps) {
         mSwitcher.setAutoCapsMode(autoCaps);
     }
 
-    public void setLayoutSwitchBackSymbols(String switchBackSymbols) {
-        mLayoutSwitchBackSymbols = switchBackSymbols;
-    }
-
-    private static void assertLayout(String message, int expected, int actual) {
+    private static void assertLayout(final String message, final int expected, final int actual) {
         assertTrue(message + ": expected=" + MockKeyboardSwitcher.getLayoutName(expected)
                 + " actual=" + MockKeyboardSwitcher.getLayoutName(actual),
                 expected == actual);
     }
 
-    public void updateShiftState(int afterUpdate) {
+    public void updateShiftState(final int afterUpdate) {
         mSwitcher.updateShiftState();
         assertLayout("afterUpdate", afterUpdate, mSwitcher.getLayoutId());
     }
 
-    public void loadKeyboard(int afterLoad) {
-        mSwitcher.loadKeyboard(mLayoutSwitchBackSymbols);
+    public void loadKeyboard(final int afterLoad) {
+        mSwitcher.loadKeyboard();
         mSwitcher.updateShiftState();
         assertLayout("afterLoad", afterLoad, mSwitcher.getLayoutId());
     }
 
-    public void rotateDevice(int afterRotate) {
+    public void rotateDevice(final int afterRotate) {
         mSwitcher.saveKeyboardState();
-        mSwitcher.loadKeyboard(mLayoutSwitchBackSymbols);
+        mSwitcher.loadKeyboard();
         assertLayout("afterRotate", afterRotate, mSwitcher.getLayoutId());
     }
 
-    private void pressKeyWithoutTimerExpire(int code, boolean isSinglePointer, int afterPress) {
+    private void pressKeyWithoutTimerExpire(final int code, final boolean isSinglePointer,
+            final int afterPress) {
         mSwitcher.onPressKey(code, isSinglePointer);
         assertLayout("afterPress", afterPress, mSwitcher.getLayoutId());
     }
 
-    public void pressKey(int code, int afterPress) {
+    public void pressKey(final int code, final int afterPress) {
         mSwitcher.expireDoubleTapTimeout();
         pressKeyWithoutTimerExpire(code, true, afterPress);
     }
 
-    public void releaseKey(int code, int afterRelease) {
+    public void releaseKey(final int code, final int afterRelease) {
         mSwitcher.onCodeInput(code, SINGLE);
         mSwitcher.onReleaseKey(code, NOT_SLIDING);
         assertLayout("afterRelease", afterRelease, mSwitcher.getLayoutId());
     }
 
-    public void pressAndReleaseKey(int code, int afterPress, int afterRelease) {
+    public void pressAndReleaseKey(final int code, final int afterPress, final int afterRelease) {
         pressKey(code, afterPress);
         releaseKey(code, afterRelease);
     }
 
-    public void chordingPressKey(int code, int afterPress) {
+    public void chordingPressKey(final int code, final int afterPress) {
         mSwitcher.expireDoubleTapTimeout();
         pressKeyWithoutTimerExpire(code, false, afterPress);
     }
 
-    public void chordingReleaseKey(int code, int afterRelease) {
+    public void chordingReleaseKey(final int code, final int afterRelease) {
         mSwitcher.onCodeInput(code, MULTI);
         mSwitcher.onReleaseKey(code, NOT_SLIDING);
         assertLayout("afterRelease", afterRelease, mSwitcher.getLayoutId());
     }
 
-    public void chordingPressAndReleaseKey(int code, int afterPress, int afterRelease) {
+    public void chordingPressAndReleaseKey(final int code, final int afterPress,
+            final int afterRelease) {
         chordingPressKey(code, afterPress);
         chordingReleaseKey(code, afterRelease);
     }
 
-    public void pressAndSlideFromKey(int code, int afterPress, int afterSlide) {
+    public void pressAndSlideFromKey(final int code, final int afterPress, final int afterSlide) {
         pressKey(code, afterPress);
         mSwitcher.onReleaseKey(code, SLIDING);
         assertLayout("afterSlide", afterSlide, mSwitcher.getLayoutId());
     }
 
-    public void longPressKey(int code, int afterPress, int afterLongPress) {
+    public void longPressKey(final int code, final int afterPress, final int afterLongPress) {
         pressKey(code, afterPress);
         mSwitcher.onLongPressTimeout(code);
         assertLayout("afterLongPress", afterLongPress, mSwitcher.getLayoutId());
     }
 
-    public void longPressAndReleaseKey(int code, int afterPress, int afterLongPress,
-            int afterRelease) {
+    public void longPressAndReleaseKey(final int code, final int afterPress,
+            final int afterLongPress, final int afterRelease) {
         longPressKey(code, afterPress, afterLongPress);
         releaseKey(code, afterRelease);
     }
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
index 0213744..ac35585 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
@@ -61,7 +61,7 @@
         return mLayout;
     }
 
-    public static String getLayoutName(int layoutId) {
+    public static String getLayoutName(final int layoutId) {
         switch (layoutId) {
         case MockConstants.ALPHABET_UNSHIFTED: return "ALPHABET_UNSHIFTED";
         case MockConstants.ALPHABET_MANUAL_SHIFTED: return "ALPHABET_MANUAL_SHIFTED";
@@ -74,7 +74,7 @@
         }
     }
 
-    public void setAutoCapsMode(int autoCaps) {
+    public void setAutoCapsMode(final int autoCaps) {
         mAutoCapsMode = autoCaps;
         mAutoCapsState = autoCaps;
     }
@@ -139,7 +139,7 @@
     }
 
     @Override
-    public void startLongPressTimer(int code) {
+    public void startLongPressTimer(final int code) {
         mLongPressTimeoutCode = code;
     }
 
@@ -149,11 +149,11 @@
     }
 
     @Override
-    public void hapticAndAudioFeedback(int code) {
+    public void hapticAndAudioFeedback(final int code) {
         // Nothing to do.
     }
 
-    public void onLongPressTimeout(int code) {
+    public void onLongPressTimeout(final int code) {
         // TODO: Handle simultaneous long presses.
         if (mLongPressTimeoutCode == code) {
             mLongPressTimeoutCode = 0;
@@ -165,26 +165,26 @@
         mState.onUpdateShiftState(mAutoCapsState);
     }
 
-    public void loadKeyboard(String layoutSwitchBackSymbols) {
-        mState.onLoadKeyboard(layoutSwitchBackSymbols);
+    public void loadKeyboard() {
+        mState.onLoadKeyboard();
     }
 
     public void saveKeyboardState() {
         mState.onSaveKeyboardState();
     }
 
-    public void onPressKey(int code, boolean isSinglePointer) {
+    public void onPressKey(final int code, final boolean isSinglePointer) {
         mState.onPressKey(code, isSinglePointer, mAutoCapsState);
     }
 
-    public void onReleaseKey(int code, boolean withSliding) {
+    public void onReleaseKey(final int code, final boolean withSliding) {
         mState.onReleaseKey(code, withSliding);
         if (mLongPressTimeoutCode == code) {
             mLongPressTimeoutCode = 0;
         }
     }
 
-    public void onCodeInput(int code, boolean isSinglePointer) {
+    public void onCodeInput(final int code, final boolean isSinglePointer) {
         if (mAutoCapsMode == MockConstants.CAP_MODE_WORDS) {
             if (Constants.isLetterCode(code)) {
                 mAutoCapsState = (code == MockConstants.CODE_AUTO_CAPS_TRIGGER)
@@ -196,7 +196,7 @@
         mState.onCodeInput(code, isSinglePointer, mAutoCapsState);
     }
 
-    public void onCancelInput(boolean isSinglePointer) {
+    public void onCancelInput(final boolean isSinglePointer) {
         mState.onCancelInput(isSinglePointer);
     }
 }
\ No newline at end of file
diff --git a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl b/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
index a134923..2409bfc 100644
--- a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
+++ b/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
@@ -1,17 +1,17 @@
 /*
  * 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
+ * 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
+ *      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.
+ * 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.keyboard.internal;