merge in jb-mr1-release history after reset to jb-mr1-dev
diff --git a/java/res/values-cs/bools.xml b/java/res/values-cs/bools.xml
index 897f4b3..dabfff8 100644
--- a/java/res/values-cs/bools.xml
+++ b/java/res/values-cs/bools.xml
@@ -1,22 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* 
+/*
 **
 ** Copyright 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 
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 -->
 <resources>
-	<bool name="im_is_default">true</bool>
+    <bool name="im_is_default">true</bool>
 </resources>
diff --git a/java/res/values-de/bools.xml b/java/res/values-de/bools.xml
index 897f4b3..dabfff8 100644
--- a/java/res/values-de/bools.xml
+++ b/java/res/values-de/bools.xml
@@ -1,22 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* 
+/*
 **
 ** Copyright 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 
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 -->
 <resources>
-	<bool name="im_is_default">true</bool>
+    <bool name="im_is_default">true</bool>
 </resources>
diff --git a/java/res/values-en/bools.xml b/java/res/values-en/bools.xml
index 897f4b3..dabfff8 100644
--- a/java/res/values-en/bools.xml
+++ b/java/res/values-en/bools.xml
@@ -1,22 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* 
+/*
 **
 ** Copyright 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 
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 -->
 <resources>
-	<bool name="im_is_default">true</bool>
+    <bool name="im_is_default">true</bool>
 </resources>
diff --git a/java/res/values-es/bools.xml b/java/res/values-es/bools.xml
index 897f4b3..dabfff8 100644
--- a/java/res/values-es/bools.xml
+++ b/java/res/values-es/bools.xml
@@ -1,22 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* 
+/*
 **
 ** Copyright 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 
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 -->
 <resources>
-	<bool name="im_is_default">true</bool>
+    <bool name="im_is_default">true</bool>
 </resources>
diff --git a/java/res/values-fr/bools.xml b/java/res/values-fr/bools.xml
index 897f4b3..dabfff8 100644
--- a/java/res/values-fr/bools.xml
+++ b/java/res/values-fr/bools.xml
@@ -1,22 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* 
+/*
 **
 ** Copyright 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 
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 -->
 <resources>
-	<bool name="im_is_default">true</bool>
+    <bool name="im_is_default">true</bool>
 </resources>
diff --git a/java/res/values-land/dimens.xml b/java/res/values-land/dimens.xml
index a1546f1..fdd9434 100644
--- a/java/res/values-land/dimens.xml
+++ b/java/res/values-land/dimens.xml
@@ -73,4 +73,10 @@
     <dimen name="more_keys_keyboard_slide_allowance">53.76dp</dimen>
     <!-- popup_key_height x -1.0 -->
     <dimen name="more_keys_keyboard_vertical_correction">-44.8dp</dimen>
+
+    <!-- Gesture floating preview text parameters -->
+    <dimen name="gesture_floating_preview_text_size">23dp</dimen>
+    <dimen name="gesture_floating_preview_text_offset">54dp</dimen>
+    <dimen name="gesture_floating_preview_horizontal_padding">23dp</dimen>
+    <dimen name="gesture_floating_preview_vertical_padding">15dp</dimen>
 </resources>
diff --git a/java/res/values-nl/bools.xml b/java/res/values-nl/bools.xml
index 897f4b3..dabfff8 100644
--- a/java/res/values-nl/bools.xml
+++ b/java/res/values-nl/bools.xml
@@ -1,22 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* 
+/*
 **
 ** Copyright 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 
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 -->
 <resources>
-	<bool name="im_is_default">true</bool>
+    <bool name="im_is_default">true</bool>
 </resources>
diff --git a/java/res/values-pl/bools.xml b/java/res/values-pl/bools.xml
index 897f4b3..dabfff8 100644
--- a/java/res/values-pl/bools.xml
+++ b/java/res/values-pl/bools.xml
@@ -1,22 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* 
+/*
 **
 ** Copyright 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 
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 -->
 <resources>
-	<bool name="im_is_default">true</bool>
+    <bool name="im_is_default">true</bool>
 </resources>
diff --git a/java/res/values-sw600dp-land/dimens.xml b/java/res/values-sw600dp-land/dimens.xml
index 9664bf9..51c710f 100644
--- a/java/res/values-sw600dp-land/dimens.xml
+++ b/java/res/values-sw600dp-land/dimens.xml
@@ -61,4 +61,10 @@
     <dimen name="suggestions_strip_padding">252.0dp</dimen>
     <integer name="max_more_suggestions_row">5</integer>
     <fraction name="min_more_suggestions_width">50%</fraction>
+
+    <!-- Gesture floating preview text parameters -->
+    <dimen name="gesture_floating_preview_text_size">26dp</dimen>
+    <dimen name="gesture_floating_preview_text_offset">76dp</dimen>
+    <dimen name="gesture_floating_preview_horizontal_padding">26dp</dimen>
+    <dimen name="gesture_floating_preview_vertical_padding">17dp</dimen>
 </resources>
diff --git a/java/res/values-sw600dp/dimens.xml b/java/res/values-sw600dp/dimens.xml
index e608f7d..586fbe6 100644
--- a/java/res/values-sw600dp/dimens.xml
+++ b/java/res/values-sw600dp/dimens.xml
@@ -84,4 +84,13 @@
     <dimen name="suggestion_padding">12dp</dimen>
     <dimen name="suggestion_text_size">22dp</dimen>
     <dimen name="more_suggestions_hint_text_size">33dp</dimen>
+
+    <!-- Gesture preview trail parameters -->
+    <dimen name="gesture_preview_trail_width">2.5dp</dimen>
+    <!-- Gesture floating preview text parameters -->
+    <dimen name="gesture_floating_preview_text_size">28dp</dimen>
+    <dimen name="gesture_floating_preview_text_offset">87dp</dimen>
+    <dimen name="gesture_floating_preview_horizontal_padding">28dp</dimen>
+    <dimen name="gesture_floating_preview_vertical_padding">19dp</dimen>
+    <dimen name="gesture_floating_preview_round_radius">3dp</dimen>
 </resources>
diff --git a/java/res/values-sw768dp-land/dimens.xml b/java/res/values-sw768dp-land/dimens.xml
index 5112170..f4a57ff 100644
--- a/java/res/values-sw768dp-land/dimens.xml
+++ b/java/res/values-sw768dp-land/dimens.xml
@@ -64,4 +64,10 @@
 
     <dimen name="suggestions_strip_padding">252.0dp</dimen>
     <fraction name="min_more_suggestions_width">50%</fraction>
+
+    <!-- Gesture floating preview text parameters -->
+    <dimen name="gesture_floating_preview_text_size">32dp</dimen>
+    <dimen name="gesture_floating_preview_text_offset">100dp</dimen>
+    <dimen name="gesture_floating_preview_horizontal_padding">32dp</dimen>
+    <dimen name="gesture_floating_preview_vertical_padding">21dp</dimen>
 </resources>
diff --git a/java/res/values-sw768dp/dimens.xml b/java/res/values-sw768dp/dimens.xml
index ec9d759..2fd7322 100644
--- a/java/res/values-sw768dp/dimens.xml
+++ b/java/res/values-sw768dp/dimens.xml
@@ -85,4 +85,13 @@
     <dimen name="suggestion_padding">8dp</dimen>
     <dimen name="suggestion_text_size">22dp</dimen>
     <dimen name="more_suggestions_hint_text_size">33dp</dimen>
+
+    <!-- Gesture preview trail parameters -->
+    <dimen name="gesture_preview_trail_width">2.5dp</dimen>
+    <!-- Gesture floating preview text parameters -->
+    <dimen name="gesture_floating_preview_text_size">26dp</dimen>
+    <dimen name="gesture_floating_preview_text_offset">86dp</dimen>
+    <dimen name="gesture_floating_preview_horizontal_padding">26dp</dimen>
+    <dimen name="gesture_floating_preview_vertical_padding">17dp</dimen>
+    <dimen name="gesture_floating_preview_round_radius">3dp</dimen>
 </resources>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 05c53a4..7e8c77e 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -77,12 +77,10 @@
         <attr name="gestureFloatingPreviewTextSize" format="dimension" />
         <attr name="gestureFloatingPreviewTextColor" format="color" />
         <attr name="gestureFloatingPreviewTextOffset" format="dimension" />
-        <attr name="gestureFloatingPreviewTextShadingColor" format="color" />
-        <attr name="gestureFloatingPreviewTextShadingBorder" format="dimension" />
-        <attr name="gestureFloatingPreviewTextShadowColor" format="color" />
-        <attr name="gestureFloatingPreviewTextShadowBorder" format="dimension" />
-        <attr name="gestureFloatingPreviewTextConnectorColor" format="color" />
-        <attr name="gestureFloatingPreviewTextConnectorWidth" 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. -->
@@ -92,7 +90,8 @@
         <!-- Interval of updating gesture preview trail in millisecond. -->
         <attr name="gesturePreviewTrailUpdateInterval" format="integer" />
         <attr name="gesturePreviewTrailColor" format="color" />
-        <attr name="gesturePreviewTrailWidth" format="dimension" />
+        <attr name="gesturePreviewTrailStartWidth" format="dimension" />
+        <attr name="gesturePreviewTrailEndWidth" format="dimension" />
     </declare-styleable>
 
     <declare-styleable name="MainKeyboardView">
diff --git a/java/res/values/bools.xml b/java/res/values/bools.xml
index 889d8f7..0069596 100644
--- a/java/res/values/bools.xml
+++ b/java/res/values/bools.xml
@@ -1,19 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* 
+/*
 **
 ** Copyright 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 
+** 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.
 */
 -->
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index aa16c77..d92a715 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -99,13 +99,15 @@
     <integer name="suggestions_count_in_strip">3</integer>
     <fraction name="center_suggestion_percentile">36%</fraction>
 
-    <!-- Gesture preview parameters -->
-    <dimen name="gesture_preview_trail_width">2.5dp</dimen>
-    <dimen name="gesture_floating_preview_text_size">35dp</dimen>
-    <dimen name="gesture_floating_preview_text_offset">75dp</dimen>
-    <dimen name="gesture_floating_preview_text_shadow_border">17.5dp</dimen>
-    <dimen name="gesture_floating_preview_text_shading_border">7.5dp</dimen>
-    <dimen name="gesture_floating_preview_text_connector_width">1.0dp</dimen>
+    <!-- Gesture preview trail parameters -->
+    <dimen name="gesture_preview_trail_start_width">18.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>
+    <dimen name="gesture_floating_preview_text_offset">73dp</dimen>
+    <dimen name="gesture_floating_preview_horizontal_padding">24dp</dimen>
+    <dimen name="gesture_floating_preview_vertical_padding">16dp</dimen>
+    <dimen name="gesture_floating_preview_round_radius">3dp</dimen>
 
     <!-- Inset used in Accessibility mode to avoid accidental key presses when a finger slides off the screen. -->
     <dimen name="accessibility_edge_slop">8dp</dimen>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 40f1ff3..ed92440 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -66,20 +66,19 @@
         <item name="backgroundDimAlpha">128</item>
         <!-- android:color/holo_blue_light=#FF33B5E5 -->
         <item name="gestureFloatingPreviewTextSize">@dimen/gesture_floating_preview_text_size</item>
-        <item name="gestureFloatingPreviewTextColor">@android:color/white</item>
+        <item name="gestureFloatingPreviewTextColor">@android:color/holo_blue_light</item>
         <item name="gestureFloatingPreviewTextOffset">@dimen/gesture_floating_preview_text_offset</item>
-        <item name="gestureFloatingPreviewTextShadingColor">@android:color/holo_blue_light</item>
-        <item name="gestureFloatingPreviewTextShadingBorder">@dimen/gesture_floating_preview_text_shading_border</item>
-        <item name="gestureFloatingPreviewTextShadowColor">#FF252525</item>
-        <item name="gestureFloatingPreviewTextShadowBorder">@dimen/gesture_floating_preview_text_shadow_border</item>
-        <item name="gestureFloatingPreviewTextConnectorColor">@android:color/white</item>
-        <item name="gestureFloatingPreviewTextConnectorWidth">@dimen/gesture_floating_preview_text_connector_width</item>
+        <item name="gestureFloatingPreviewColor">#C0000000</item>
+        <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>
         <item name="gesturePreviewTrailColor">@android:color/holo_blue_light</item>
-        <item name="gesturePreviewTrailWidth">@dimen/gesture_preview_trail_width</item>
+        <item name="gesturePreviewTrailStartWidth">@dimen/gesture_preview_trail_start_width</item>
+        <item name="gesturePreviewTrailEndWidth">@dimen/gesture_preview_trail_end_width</item>
         <!-- Common attributes of MainKeyboardView -->
         <item name="keyHysteresisDistance">@dimen/config_key_hysteresis_distance</item>
         <item name="touchNoiseThresholdTime">@integer/config_touch_noise_threshold_time</item>
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index ce7d797..db3a7b1 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -71,12 +71,10 @@
  * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextSize
  * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextColor
  * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextOffset
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextShadingColor
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextShadingBorder
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextShadowColor
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextShadowBorder
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextConnectorColor
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextConnectorWidth
+ * @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
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
index e814d80..7442b7f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -17,16 +17,18 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Xfermode;
 import android.os.SystemClock;
 
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.ResizableIntArray;
 
-class GesturePreviewTrail {
+final class GesturePreviewTrail {
     private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewTrail.PREVIEW_CAPACITY;
 
-    private final GesturePreviewTrailParams mPreviewParams;
     private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
@@ -34,28 +36,39 @@
     private long mCurrentDownTime;
     private int mTrailStartIndex;
 
+    private final static Xfermode PORTER_DUFF_MODE_SRC =
+            new PorterDuffXfermode(PorterDuff.Mode.SRC);
+
     // Use this value as imaginary zero because x-coordinates may be zero.
     private static final int DOWN_EVENT_MARKER = -128;
 
-    static class GesturePreviewTrailParams {
+    static final class Params {
+        public final int mTrailColor;
+        public final float mTrailStartWidth;
+        public final float mTrailEndWidth;
         public final int mFadeoutStartDelay;
         public final int mFadeoutDuration;
         public final int mUpdateInterval;
 
-        public GesturePreviewTrailParams(final TypedArray keyboardViewAttr) {
+        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);
+            mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
             mUpdateInterval = keyboardViewAttr.getInt(
                     R.styleable.KeyboardView_gesturePreviewTrailUpdateInterval, 0);
         }
     }
 
-    public GesturePreviewTrail(final GesturePreviewTrailParams params) {
-        mPreviewParams = params;
-    }
-
     private static int markAsDownEvent(final int xCoord) {
         return DOWN_EVENT_MARKER - xCoord;
     }
@@ -94,23 +107,30 @@
         }
     }
 
-    private int getAlpha(final int elapsedTime) {
-        if (elapsedTime < mPreviewParams.mFadeoutStartDelay) {
+    private static int getAlpha(final int elapsedTime, final Params params) {
+        if (elapsedTime < params.mFadeoutStartDelay) {
             return Constants.Color.ALPHA_OPAQUE;
         }
         final int decreasingAlpha = Constants.Color.ALPHA_OPAQUE
-                * (elapsedTime - mPreviewParams.mFadeoutStartDelay)
-                / mPreviewParams.mFadeoutDuration;
+                * (elapsedTime - params.mFadeoutStartDelay)
+                / params.mFadeoutDuration;
         return Constants.Color.ALPHA_OPAQUE - decreasingAlpha;
     }
 
+    private static float getWidth(final int elapsedTime, final Params params) {
+        return Math.max((params.mTrailLingerDuration - elapsedTime)
+                * (params.mTrailStartWidth - params.mTrailEndWidth)
+                / params.mTrailLingerDuration, 0.0f);
+    }
+
     /**
      * Draw gesture preview trail
      * @param canvas The canvas to draw the gesture preview trail
      * @param paint The paint object to be used to draw the gesture preview trail
+     * @param params The drawing parameters of gesture preview trail
      * @return true if some gesture preview trails remain to be drawn
      */
-    public boolean drawGestureTrail(final Canvas canvas, final Paint paint) {
+    public boolean drawGestureTrail(final Canvas canvas, final Paint paint, final Params params) {
         final int trailSize = mEventTimes.getLength();
         if (trailSize == 0) {
             return false;
@@ -120,13 +140,11 @@
         final int[] xCoords = mXCoordinates.getPrimitiveArray();
         final int[] yCoords = mYCoordinates.getPrimitiveArray();
         final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentDownTime);
-        final int lingeringDuration = mPreviewParams.mFadeoutStartDelay
-                + mPreviewParams.mFadeoutDuration;
         int startIndex;
         for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) {
             final int elapsedTime = sinceDown - eventTimes[startIndex];
             // Skip too old trail points.
-            if (elapsedTime < lingeringDuration) {
+            if (elapsedTime < params.mTrailLingerDuration) {
                 break;
             }
         }
@@ -135,13 +153,20 @@
         if (startIndex < trailSize) {
             int lastX = getXCoordValue(xCoords[startIndex]);
             int lastY = yCoords[startIndex];
+            paint.setColor(params.mTrailColor);
+            paint.setStyle(Paint.Style.STROKE);
+            paint.setStrokeCap(Paint.Cap.ROUND);
+            paint.setXfermode(PORTER_DUFF_MODE_SRC);
             for (int i = startIndex + 1; i < trailSize - 1; i++) {
                 final int x = xCoords[i];
                 final int y = yCoords[i];
                 final int elapsedTime = sinceDown - eventTimes[i];
                 // Draw trail line only when the current point isn't a down point.
                 if (!isDownEventXCoord(x)) {
-                    paint.setAlpha(getAlpha(elapsedTime));
+                    final int alpha = getAlpha(elapsedTime, params);
+                    paint.setAlpha(alpha);
+                    final float width = getWidth(elapsedTime, params);
+                    paint.setStrokeWidth(width);
                     canvas.drawLine(lastX, lastY, x, y, paint);
                 }
                 lastX = getXCoordValue(x);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index 269b202..8dde4d6 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -21,6 +21,10 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Paint.Align;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Message;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -28,22 +32,18 @@
 import android.widget.RelativeLayout;
 
 import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.GesturePreviewTrailParams;
+import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.Params;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
 
 public class PreviewPlacerView extends RelativeLayout {
-    private final Paint mGesturePaint;
-    private final Paint mTextPaint;
     private final int mGestureFloatingPreviewTextColor;
     private final int mGestureFloatingPreviewTextOffset;
-    private final int mGestureFloatingPreviewTextShadowColor;
-    private final int mGestureFloatingPreviewTextShadowBorder;
-    private final int mGestureFloatingPreviewTextShadingColor;
-    private final int mGestureFloatingPreviewTextShadingBorder;
-    private final int mGestureFloatingPreviewTextConnectorColor;
-    private final int mGestureFloatingPreviewTextConnectorWidth;
+    private final int mGestureFloatingPreviewColor;
+    private final float mGestureFloatingPreviewHorizontalPadding;
+    private final float mGestureFloatingPreviewVerticalPadding;
+    private final float mGestureFloatingPreviewRoundRadius;
     /* package */ final int mGestureFloatingPreviewTextLingerTimeout;
 
     private int mXOrigin;
@@ -51,13 +51,18 @@
 
     private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
             CollectionUtils.newSparseArray();
-    private final GesturePreviewTrailParams mGesturePreviewTrailParams;
+    private final Params mGesturePreviewTrailParams;
+    private final Paint mGesturePaint;
+    private boolean mDrawsGesturePreviewTrail;
 
+    private final Paint mTextPaint;
     private String mGestureFloatingPreviewText;
+    private final int mGestureFloatingPreviewTextHeight;
+    // {@link RectF} is needed for {@link Canvas#drawRoundRect(RectF, float, float, Paint)}.
+    private final RectF mGestureFloatingPreviewRectangle = new RectF();
     private int mLastPointerX;
     private int mLastPointerY;
-
-    private boolean mDrawsGesturePreviewTrail;
+    private static final char[] TEXT_HEIGHT_REFERENCE_CHAR = { 'M' };
     private boolean mDrawsGestureFloatingPreviewText;
 
     private final DrawingHandler mDrawingHandler;
@@ -66,10 +71,10 @@
         private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0;
         private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1;
 
-        private final GesturePreviewTrailParams mGesturePreviewTrailParams;
+        private final Params mGesturePreviewTrailParams;
 
         public DrawingHandler(final PreviewPlacerView outerInstance,
-                final GesturePreviewTrailParams gesturePreviewTrailParams) {
+                final Params gesturePreviewTrailParams) {
             super(outerInstance);
             mGesturePreviewTrailParams = gesturePreviewTrailParams;
         }
@@ -132,41 +137,38 @@
                 R.styleable.KeyboardView_gestureFloatingPreviewTextColor, 0);
         mGestureFloatingPreviewTextOffset = keyboardViewAttr.getDimensionPixelOffset(
                 R.styleable.KeyboardView_gestureFloatingPreviewTextOffset, 0);
-        mGestureFloatingPreviewTextShadowColor = keyboardViewAttr.getColor(
-                R.styleable.KeyboardView_gestureFloatingPreviewTextShadowColor, 0);
-        mGestureFloatingPreviewTextShadowBorder = keyboardViewAttr.getDimensionPixelSize(
-                R.styleable.KeyboardView_gestureFloatingPreviewTextShadowBorder, 0);
-        mGestureFloatingPreviewTextShadingColor = keyboardViewAttr.getColor(
-                R.styleable.KeyboardView_gestureFloatingPreviewTextShadingColor, 0);
-        mGestureFloatingPreviewTextShadingBorder = keyboardViewAttr.getDimensionPixelSize(
-                R.styleable.KeyboardView_gestureFloatingPreviewTextShadingBorder, 0);
-        mGestureFloatingPreviewTextConnectorColor = keyboardViewAttr.getColor(
-                R.styleable.KeyboardView_gestureFloatingPreviewTextConnectorColor, 0);
-        mGestureFloatingPreviewTextConnectorWidth = keyboardViewAttr.getDimensionPixelSize(
-                R.styleable.KeyboardView_gestureFloatingPreviewTextConnectorWidth, 0);
+        mGestureFloatingPreviewColor = keyboardViewAttr.getColor(
+                R.styleable.KeyboardView_gestureFloatingPreviewColor, 0);
+        mGestureFloatingPreviewHorizontalPadding = keyboardViewAttr.getDimension(
+                R.styleable.KeyboardView_gestureFloatingPreviewHorizontalPadding, 0.0f);
+        mGestureFloatingPreviewVerticalPadding = keyboardViewAttr.getDimension(
+                R.styleable.KeyboardView_gestureFloatingPreviewVerticalPadding, 0.0f);
+        mGestureFloatingPreviewRoundRadius = keyboardViewAttr.getDimension(
+                R.styleable.KeyboardView_gestureFloatingPreviewRoundRadius, 0.0f);
         mGestureFloatingPreviewTextLingerTimeout = keyboardViewAttr.getInt(
                 R.styleable.KeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
-        final int gesturePreviewTrailColor = keyboardViewAttr.getColor(
-                R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
-        final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize(
-                R.styleable.KeyboardView_gesturePreviewTrailWidth, 0);
-        mGesturePreviewTrailParams = new GesturePreviewTrailParams(keyboardViewAttr);
+        mGesturePreviewTrailParams = new Params(keyboardViewAttr);
         keyboardViewAttr.recycle();
 
         mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
 
-        mGesturePaint = new Paint();
-        mGesturePaint.setAntiAlias(true);
-        mGesturePaint.setStyle(Paint.Style.STROKE);
-        mGesturePaint.setStrokeJoin(Paint.Join.ROUND);
-        mGesturePaint.setColor(gesturePreviewTrailColor);
-        mGesturePaint.setStrokeWidth(gesturePreviewTrailWidth);
+        final Paint gesturePaint = new Paint();
+        gesturePaint.setAntiAlias(true);
+        mGesturePaint = gesturePaint;
 
-        mTextPaint = new Paint();
-        mTextPaint.setAntiAlias(true);
-        mTextPaint.setStrokeJoin(Paint.Join.ROUND);
-        mTextPaint.setTextAlign(Align.CENTER);
-        mTextPaint.setTextSize(gestureFloatingPreviewTextSize);
+        final Paint textPaint = new Paint();
+        textPaint.setAntiAlias(true);
+        textPaint.setStyle(Paint.Style.FILL);
+        textPaint.setTextAlign(Align.CENTER);
+        textPaint.setTextSize(gestureFloatingPreviewTextSize);
+        mTextPaint = textPaint;
+        final Rect textRect = new Rect();
+        textPaint.getTextBounds(TEXT_HEIGHT_REFERENCE_CHAR, 0, 1, textRect);
+        mGestureFloatingPreviewTextHeight = textRect.height();
+
+        final Paint layerPaint = new Paint();
+        layerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
+        setLayerType(LAYER_TYPE_HARDWARE, layerPaint);
     }
 
     public void setOrigin(final int x, final int y) {
@@ -185,7 +187,7 @@
         synchronized (mGesturePreviewTrails) {
             trail = mGesturePreviewTrails.get(tracker.mPointerId);
             if (trail == null) {
-                trail = new GesturePreviewTrail(mGesturePreviewTrailParams);
+                trail = new GesturePreviewTrail();
                 mGesturePreviewTrails.put(tracker.mPointerId, trail);
             }
         }
@@ -209,7 +211,8 @@
                 for (int index = 0; index < trailsCount; index++) {
                     final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
                     needsUpdatingGesturePreviewTrail |=
-                            trail.drawGestureTrail(canvas, mGesturePaint);
+                            trail.drawGestureTrail(canvas, mGesturePaint,
+                                    mGesturePreviewTrailParams);
                 }
             }
             if (needsUpdatingGesturePreviewTrail) {
@@ -242,45 +245,30 @@
         }
 
         final Paint paint = mTextPaint;
+        final RectF rectangle = mGestureFloatingPreviewRectangle;
         // TODO: Figure out how we should deal with the floating preview text with multiple moving
         // fingers.
-        final int lastX = mLastPointerX;
-        final int lastY = mLastPointerY;
-        final int textSize = (int)paint.getTextSize();
+
+        // Paint the round rectangle background.
+        final int textHeight = mGestureFloatingPreviewTextHeight;
+        final float textWidth = paint.measureText(gestureFloatingPreviewText);
+        final float hPad = mGestureFloatingPreviewHorizontalPadding;
+        final float vPad = mGestureFloatingPreviewVerticalPadding;
+        final float rectWidth = textWidth + hPad * 2.0f;
+        final float rectHeight = textHeight + vPad * 2.0f;
         final int canvasWidth = canvas.getWidth();
-
-        final int halfTextWidth = (int)paint.measureText(gestureFloatingPreviewText) / 2 + textSize;
-        final int textX = Math.min(Math.max(lastX, halfTextWidth), canvasWidth - halfTextWidth);
-
-        int textY = Math.max(-textSize, lastY - mGestureFloatingPreviewTextOffset);
-        if (textY < 0) {
-            // Paint black text shadow if preview extends above keyboard region.
-            paint.setStyle(Paint.Style.FILL_AND_STROKE);
-            paint.setColor(mGestureFloatingPreviewTextShadowColor);
-            paint.setStrokeWidth(mGestureFloatingPreviewTextShadowBorder);
-            canvas.drawText(gestureFloatingPreviewText, textX, textY, paint);
-        }
-
-        // Paint the vertical line connecting the touch point to the preview text.
-        paint.setStyle(Paint.Style.STROKE);
-        paint.setColor(mGestureFloatingPreviewTextConnectorColor);
-        paint.setStrokeWidth(mGestureFloatingPreviewTextConnectorWidth);
-        final int lineTopY = textY - textSize / 4;
-        canvas.drawLine(lastX, lastY, lastX, lineTopY, paint);
-        if (lastX != textX) {
-            // Paint the horizontal line connection the touch point to the preview text.
-            canvas.drawLine(lastX, lineTopY, textX, lineTopY, paint);
-        }
-
-        // Paint the shading for the text preview
-        paint.setStyle(Paint.Style.FILL_AND_STROKE);
-        paint.setColor(mGestureFloatingPreviewTextShadingColor);
-        paint.setStrokeWidth(mGestureFloatingPreviewTextShadingBorder);
-        canvas.drawText(gestureFloatingPreviewText, textX, textY, paint);
+        final float rectX = Math.min(Math.max(mLastPointerX - rectWidth / 2.0f, 0.0f),
+                canvasWidth - rectWidth);
+        final float rectY = mLastPointerY - mGestureFloatingPreviewTextOffset - rectHeight;
+        rectangle.set(rectX, rectY, rectX + rectWidth, rectY + rectHeight);
+        final float round = mGestureFloatingPreviewRoundRadius;
+        paint.setColor(mGestureFloatingPreviewColor);
+        canvas.drawRoundRect(rectangle, round, round, paint);
 
         // Paint the text preview
         paint.setColor(mGestureFloatingPreviewTextColor);
-        paint.setStyle(Paint.Style.FILL);
+        final float textX = rectX + hPad + textWidth / 2.0f;
+        final float textY = rectY + vPad + textHeight;
         canvas.drawText(gestureFloatingPreviewText, textX, textY, paint);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
index 81d61e9..d6fa661 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
@@ -54,7 +54,7 @@
         private byte[] mBuffer;
         private int mPosition;
 
-        ByteArrayWrapper(final byte[] buffer) {
+        public ByteArrayWrapper(final byte[] buffer) {
             mBuffer = buffer;
             mPosition = 0;
         }
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index cd9ff85..70c38e9 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -93,7 +93,7 @@
         mFile = outputFile;
     }
 
-    public synchronized void close() {
+    public synchronized void close(final Runnable onClosed) {
         mExecutor.submit(new Callable<Object>() {
             @Override
             public Object call() throws Exception {
@@ -102,7 +102,14 @@
                         mJsonWriter.endArray();
                         mJsonWriter.flush();
                         mJsonWriter.close();
+                        if (DEBUG) {
+                            Log.d(TAG, "wrote log to " + mFile);
+                        }
                         mHasWrittenData = false;
+                    } else {
+                        if (DEBUG) {
+                            Log.d(TAG, "close() called, but no data, not outputting");
+                        }
                     }
                 } catch (Exception e) {
                     Log.d(TAG, "error when closing ResearchLog:");
@@ -111,6 +118,9 @@
                     if (mFile.exists()) {
                         mFile.setWritable(false, false);
                     }
+                    if (onClosed != null) {
+                        onClosed.run();
+                    }
                 }
                 return null;
             }
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 5c24871..763fd6e 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -84,6 +84,7 @@
  */
 public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
     private static final String TAG = ResearchLogger.class.getSimpleName();
+    private static final boolean DEBUG = false;
     private static final boolean OUTPUT_ENTIRE_BUFFER = false;  // true may disclose private info
     public static final boolean DEFAULT_USABILITY_STUDY_MODE = false;
     /* package */ static boolean sIsLogging = false;
@@ -344,6 +345,9 @@
     }
 
     private void start() {
+        if (DEBUG) {
+            Log.d(TAG, "start called");
+        }
         maybeShowSplashScreen();
         updateSuspendedState();
         requestIndicatorRedraw();
@@ -371,21 +375,27 @@
     }
 
     /* package */ void stop() {
+        if (DEBUG) {
+            Log.d(TAG, "stop called");
+        }
         logStatistics();
         commitCurrentLogUnit();
 
         if (mMainLogBuffer != null) {
             publishLogBuffer(mMainLogBuffer, mMainResearchLog, false /* isIncludingPrivateData */);
-            mMainResearchLog.close();
+            mMainResearchLog.close(null /* callback */);
             mMainLogBuffer = null;
         }
         if (mFeedbackLogBuffer != null) {
-            mFeedbackLog.close();
+            mFeedbackLog.close(null /* callback */);
             mFeedbackLogBuffer = null;
         }
     }
 
     public boolean abort() {
+        if (DEBUG) {
+            Log.d(TAG, "abort called");
+        }
         boolean didAbortMainLog = false;
         if (mMainLogBuffer != null) {
             mMainLogBuffer.clear();
@@ -549,12 +559,19 @@
                 false /* isPotentiallyPrivate */);
         mFeedbackLogBuffer.shiftIn(feedbackLogUnit);
         publishLogBuffer(mFeedbackLogBuffer, mFeedbackLog, true /* isIncludingPrivateData */);
-        mFeedbackLog.close();
-        uploadNow();
+        mFeedbackLog.close(new Runnable() {
+            @Override
+            public void run() {
+                uploadNow();
+            }
+        });
         mFeedbackLog = new ResearchLog(createLogFile(mFilesDir));
     }
 
     public void uploadNow() {
+        if (DEBUG) {
+            Log.d(TAG, "calling uploadNow()");
+        }
         mInputMethodService.startService(mUploadIntent);
     }
 
@@ -574,6 +591,13 @@
     }
 
     private boolean isAllowedToLog() {
+        if (DEBUG) {
+            Log.d(TAG, "iatl: " +
+                "mipw=" + mIsPasswordView +
+                ", mils=" + mIsLoggingSuspended +
+                ", sil=" + sIsLogging +
+                ", mInFeedbackDialog=" + mInFeedbackDialog);
+        }
         return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog;
     }
 
@@ -662,6 +686,9 @@
     }
 
     /* package for test */ void commitCurrentLogUnit() {
+        if (DEBUG) {
+            Log.d(TAG, "commitCurrentLogUnit");
+        }
         if (!mCurrentLogUnit.isEmpty()) {
             if (mMainLogBuffer != null) {
                 mMainLogBuffer.shiftIn(mCurrentLogUnit);
@@ -1014,26 +1041,26 @@
             final boolean isPasswordView = kid.passwordInput();
             getInstance().setIsPasswordView(isPasswordView);
             final Object[] values = {
-                    KeyboardId.elementIdToName(kid.mElementId),
-                    kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
-                    kid.mOrientation,
-                    kid.mWidth,
-                    KeyboardId.modeName(kid.mMode),
-                    kid.imeAction(),
-                    kid.navigateNext(),
-                    kid.navigatePrevious(),
-                    kid.mClobberSettingsKey,
-                    isPasswordView,
-                    kid.mShortcutKeyEnabled,
-                    kid.mHasShortcutKey,
-                    kid.mLanguageSwitchKeyEnabled,
-                    kid.isMultiLine(),
-                    keyboard.mOccupiedWidth,
-                    keyboard.mOccupiedHeight,
-                    keyboard.mKeys
-                };
-            getInstance().enqueueEvent(EVENTKEYS_MAINKEYBOARDVIEW_SETKEYBOARD, values);
+                KeyboardId.elementIdToName(kid.mElementId),
+                kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
+                kid.mOrientation,
+                kid.mWidth,
+                KeyboardId.modeName(kid.mMode),
+                kid.imeAction(),
+                kid.navigateNext(),
+                kid.navigatePrevious(),
+                kid.mClobberSettingsKey,
+                isPasswordView,
+                kid.mShortcutKeyEnabled,
+                kid.mHasShortcutKey,
+                kid.mLanguageSwitchKeyEnabled,
+                kid.isMultiLine(),
+                keyboard.mOccupiedWidth,
+                keyboard.mOccupiedHeight,
+                keyboard.mKeys
+            };
             getInstance().setIsPasswordView(isPasswordView);
+            getInstance().enqueueEvent(EVENTKEYS_MAINKEYBOARDVIEW_SETKEYBOARD, values);
         }
     }
 
diff --git a/native/jni/src/binary_format.h b/native/jni/src/binary_format.h
index 5d8b2a0..eec52e3 100644
--- a/native/jni/src/binary_format.h
+++ b/native/jni/src/binary_format.h
@@ -360,7 +360,7 @@
     while (true) {
         // If we already traversed the tree further than the word is long, there means
         // there was no match (or we would have found it).
-        if (wordPos > length) return NOT_VALID_WORD;
+        if (wordPos >= length) return NOT_VALID_WORD;
         int charGroupCount = BinaryFormat::getGroupCountAndForwardPointer(root, &pos);
         const int32_t wChar = forceLowerCaseSearch ? toLowerCase(inWord[wordPos]) : inWord[wordPos];
         while (true) {
@@ -383,7 +383,7 @@
                         // character that does not match, as explained above, it means the word is
                         // not in the dictionary (by virtue of this chargroup being the only one to
                         // match the word on the first character, but not matching the whole word).
-                        if (wordPos > length) return NOT_VALID_WORD;
+                        if (wordPos >= length) return NOT_VALID_WORD;
                         if (inWord[wordPos] != character) return NOT_VALID_WORD;
                         character = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
                     }
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index b363dcc..c62b9b3 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -224,7 +224,7 @@
 bool ProximityInfoState::isPrevLocalMin(const NearKeysDistanceMap *const currentNearKeysDistances,
         const NearKeysDistanceMap *const prevNearKeysDistances,
         const NearKeysDistanceMap *const prevPrevNearKeysDistances) const {
-    static const float MARGIN = 0.5f;
+    static const float MARGIN = 0.05f;
 
     for (NearKeysDistanceMap::const_iterator it = prevNearKeysDistances->begin();
         it != prevNearKeysDistances->end(); ++it) {
@@ -245,14 +245,14 @@
         const NearKeysDistanceMap *const prevNearKeysDistances,
         const NearKeysDistanceMap *const prevPrevNearKeysDistances) const {
     static const float BASE_SAMPLE_RATE_SCALE = 0.1f;
-    static const float SAVE_DISTANCE_SCALE = 12.0f;
+    static const float SAVE_DISTANCE_SCALE = 14.0f;
     static const float SAVE_DISTANCE_SCORE = 2.0f;
     static const float SKIP_DISTANCE_SCALE = 1.5f;
     static const float SKIP_DISTANCE_SCORE = -1.0f;
-    static const float CHECK_LOCALMIN_DISTANCE_THRESHOLD_SCALE = 2.5f;
+    static const float CHECK_LOCALMIN_DISTANCE_THRESHOLD_SCALE = 3.5f;
     static const float CHECK_LOCALMIN_DISTANCE_SCORE = -1.0f;
     static const float STRAIGHT_ANGLE_THRESHOLD = M_PI_F / 32.0f;
-    static const float STRAIGHT_SKIP_DISTANCE_THRESHOLD_SCALE = 4.0f;
+    static const float STRAIGHT_SKIP_DISTANCE_THRESHOLD_SCALE = 5.0f;
     static const float STRAIGHT_SKIP_NEAREST_DISTANCE_THRESHOLD = 0.5f;
     static const float STRAIGHT_SKIP_SCORE = -1.0f;
 
@@ -275,19 +275,19 @@
         score += SKIP_DISTANCE_SCORE;
     }
     // Location
-    if (!isPrevLocalMin(currentNearKeysDistances, currentNearKeysDistances,
+    if (distPrev < baseSampleRate * CHECK_LOCALMIN_DISTANCE_THRESHOLD_SCALE) {
+        if (!isPrevLocalMin(currentNearKeysDistances, currentNearKeysDistances,
             prevPrevNearKeysDistances)) {
-        if (distPrev < baseSampleRate * CHECK_LOCALMIN_DISTANCE_THRESHOLD_SCALE) {
             score += CHECK_LOCALMIN_DISTANCE_SCORE;
         }
     }
     // Angle
-    const float angle1 = getAngle(x, y, mInputXs.back(), mInputYs.back());
-    const float angle2 = getAngle(mInputXs.back(), mInputYs.back(),
-            mInputXs[size - 2], mInputYs[size - 2]);
-    if (getAngleDiff(angle1, angle2) < STRAIGHT_ANGLE_THRESHOLD) {
-        if (nearest > STRAIGHT_SKIP_NEAREST_DISTANCE_THRESHOLD
-                && distPrev < baseSampleRate * STRAIGHT_SKIP_DISTANCE_THRESHOLD_SCALE) {
+    if (nearest > STRAIGHT_SKIP_NEAREST_DISTANCE_THRESHOLD
+            && distPrev < baseSampleRate * STRAIGHT_SKIP_DISTANCE_THRESHOLD_SCALE) {
+        const float angle1 = getAngle(x, y, mInputXs.back(), mInputYs.back());
+        const float angle2 = getAngle(mInputXs.back(), mInputYs.back(),
+                mInputXs[size - 2], mInputYs[size - 2]);
+        if (getAngleDiff(angle1, angle2) < STRAIGHT_ANGLE_THRESHOLD) {
             score += STRAIGHT_SKIP_SCORE;
         }
     }
@@ -383,13 +383,14 @@
 }
 
 int ProximityInfoState::getDuration(const int index) const {
-    if (mInputSize > 0 && index > 0 && index < static_cast<int>(mInputSize) - 1) {
+    if (mInputSize > 0 && index > 0 && index < mInputSize - 1) {
         return mTimes[index + 1] - mTimes[index - 1];
     }
     return 0;
 }
 
-float ProximityInfoState::getPointToKeyLength(int inputIndex, int codePoint, float scale) {
+float ProximityInfoState::getPointToKeyLength(const int inputIndex, const int codePoint,
+        const float scale) const {
     const int keyId = mProximityInfo->getKeyIndexOf(codePoint);
     if (keyId != NOT_AN_INDEX) {
         const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
@@ -404,11 +405,7 @@
     return MAX_POINT_TO_KEY_LENGTH;
 }
 
-int ProximityInfoState::getKeyKeyDistance(int key0, int key1) {
-    return mProximityInfo->getKeyKeyDistanceG(key0, key1);
-}
-
-int ProximityInfoState::getSpaceY() {
+int ProximityInfoState::getSpaceY() const {
     const int keyId = mProximityInfo->getKeyIndexOf(' ');
     return mProximityInfo->getKeyCenterYOfKeyIdG(keyId);
 }
@@ -447,4 +444,12 @@
     }
     return i;
 }
+
+float ProximityInfoState::getAveragePointDuration() const {
+    if (mInputSize == 0) {
+        return 0;
+    }
+    return (mTimes[mInputSize - 1] - mTimes[0]) / static_cast<float>(mInputSize);
+}
+
 } // namespace latinime
diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h
index 80b84e9..1d57773 100644
--- a/native/jni/src/proximity_info_state.h
+++ b/native/jni/src/proximity_info_state.h
@@ -200,27 +200,26 @@
         return mInputSize;
     }
 
-    int getInputX(int index) const {
+    int getInputX(const int index) const {
         return mInputXs[index];
     }
 
-    int getInputY(int index) const {
+    int getInputY(const int index) const {
         return mInputYs[index];
     }
 
-    int getLengthCache(int index) const {
+    int getLengthCache(const int index) const {
         return mLengthCache[index];
     }
 
-    float getPointToKeyLength(int inputIndex, int charCode, float scale);
+    float getPointToKeyLength(const int inputIndex, const int charCode, const float scale) const;
 
-    int getKeyKeyDistance(int key0, int key1);
-
-    int getSpaceY();
+    int getSpaceY() const;
 
     int32_t getAllPossibleChars(
-            const size_t startIndex, int32_t *const filter, int32_t filterSize) const;
+            const size_t startIndex, int32_t *const filter, const int32_t filterSize) const;
 
+    float getAveragePointDuration() const;
  private:
     DISALLOW_COPY_AND_ASSIGN(ProximityInfoState);
     typedef hash_map_compat<int, float> NearKeysDistanceMap;
diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp
index b7e245a..cf806c1 100644
--- a/native/jni/src/unigram_dictionary.cpp
+++ b/native/jni/src/unigram_dictionary.cpp
@@ -451,7 +451,7 @@
         const bool hasAutoCorrectionCandidate, const int currentWordIndex,
         const int inputWordStartPos, const int inputWordLength,
         const int outputWordStartPos, const bool isSpaceProximity, int *freqArray,
-        int*wordLengthArray, unsigned short *outputWord, int *outputWordLength) const {
+        int *wordLengthArray, unsigned short *outputWord, int *outputWordLength) const {
     if (inputWordLength > MULTIPLE_WORDS_SUGGESTION_MAX_WORD_LENGTH) {
         return FLAG_MULTIPLE_SUGGEST_ABORT;
     }
@@ -546,9 +546,9 @@
         freq = score >> (nextWordLength + TWO_WORDS_PLUS_OTHER_ERROR_CORRECTION_DEMOTION_DIVIDER);
     }
     if (DEBUG_DICT) {
-        AKLOGI("Freq(%d): %d, length: %d, input length: %d, input start: %d (%d)"
-                , currentWordIndex, freq, nextWordLength, inputWordLength, inputWordStartPos,
-                wordLengthArray[0]);
+        AKLOGI("Freq(%d): %d, length: %d, input length: %d, input start: %d (%d)",
+                currentWordIndex, freq, nextWordLength, inputWordLength, inputWordStartPos,
+                (currentWordIndex > 0) ? wordLengthArray[0] : 0);
     }
     if (freq <= 0 || nextWordLength <= 0
             || MAX_WORD_LENGTH <= (outputWordStartPos + nextWordLength)) {
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
index 6036562..523287b 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
@@ -17,23 +17,22 @@
 package com.android.inputmethod.latin.makedict;
 
 import com.android.inputmethod.latin.CollectionUtils;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
-import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.UserHistoryDictIOUtils;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
 import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
 import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
-import com.android.inputmethod.latin.makedict.PendingAttribute;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 
 import android.test.AndroidTestCase;
 import android.util.Log;
 import android.util.SparseArray;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -53,16 +52,76 @@
     private static final int BIGRAM_FREQ = 50;
     private static final int TOLERANCE_OF_BIGRAM_FREQ = 5;
 
+    private static final int USE_BYTE_ARRAY = 1;
+    private static final int USE_BYTE_BUFFER = 2;
+
+    private static final List<String> sWords = CollectionUtils.newArrayList();
+    private static final SparseArray<List<Integer>> sEmptyBigrams =
+            CollectionUtils.newSparseArray();
+    private static final SparseArray<List<Integer>> sStarBigrams = CollectionUtils.newSparseArray();
+    private static final SparseArray<List<Integer>> sChainBigrams =
+            CollectionUtils.newSparseArray();
+
     private static final BinaryDictInputOutput.FormatOptions VERSION2 =
             new BinaryDictInputOutput.FormatOptions(2);
 
-    private static final String[] CHARACTERS =
-        {
+    private static final String[] CHARACTERS = {
         "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
         "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
-        };
+    };
+
+    public BinaryDictIOTests() {
+        super();
+
+        final Random random = new Random(123456);
+        sWords.clear();
+        generateWords(MAX_UNIGRAMS, random);
+
+        for (int i = 0; i < sWords.size(); ++i) {
+            sChainBigrams.put(i, new ArrayList<Integer>());
+            if (i > 0) {
+                sChainBigrams.get(i-1).add(i);
+            }
+        }
+
+        sStarBigrams.put(0, new ArrayList<Integer>());
+        for (int i = 1; i < sWords.size(); ++i) {
+            sStarBigrams.get(0).add(i);
+        }
+    }
 
     // Utilities for test
+
+    /**
+     * Makes new buffer according to BUFFER_TYPE.
+     */
+    private FusionDictionaryBufferInterface getBuffer(final File file,final int bufferType) {
+        FileInputStream inStream = null;
+        try {
+            inStream = new FileInputStream(file);
+            if (bufferType == USE_BYTE_ARRAY) {
+                final byte[] array = new byte[(int)file.length()];
+                inStream.read(array);
+                return new UserHistoryDictIOUtils.ByteArrayWrapper(array);
+            } else if (bufferType == USE_BYTE_BUFFER){
+                final ByteBuffer buffer = inStream.getChannel().map(
+                        FileChannel.MapMode.READ_ONLY, 0, file.length());
+                return new BinaryDictInputOutput.ByteBufferWrapper(buffer);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "IOException while making buffer: " + e);
+        } finally {
+            if (inStream != null) {
+                try {
+                    inStream.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "IOException while closing stream: " + e);
+                }
+            }
+        }
+        return null;
+    }
+
     /**
      * Generates a random word.
      */
@@ -77,23 +136,29 @@
         return builder.toString();
     }
 
-    private List<String> generateWords(final int number, final Random random) {
+    private void generateWords(final int number, final Random random) {
         final Set<String> wordSet = CollectionUtils.newHashSet();
         while (wordSet.size() < number) {
             wordSet.add(generateWord(random.nextInt()));
         }
-        return new ArrayList<String>(wordSet);
+        sWords.addAll(wordSet);
     }
 
     /**
      * Adds unigrams to the dictionary.
      */
-    private void addUnigrams(final int number,
-            final FusionDictionary dict,
-            final List<String> words) {
+    private void addUnigrams(final int number, final FusionDictionary dict,
+            final List<String> words, final Map<String, List<String>> shortcutMap) {
         for (int i = 0; i < number; ++i) {
             final String word = words.get(i);
-            dict.add(word, UNIGRAM_FREQ, null, false /* isNotAWord */);
+            final ArrayList<WeightedString> shortcuts = CollectionUtils.newArrayList();
+            if (shortcutMap != null && shortcutMap.containsKey(word)) {
+                for (final String shortcut : shortcutMap.get(word)) {
+                    shortcuts.add(new WeightedString(shortcut, UNIGRAM_FREQ));
+                }
+            }
+            dict.add(word, UNIGRAM_FREQ, (shortcutMap == null) ? null : shortcuts,
+                    false /* isNotAWord */);
         }
     }
 
@@ -130,9 +195,8 @@
         return diff;
     }
 
-    private void checkDictionary(final FusionDictionary dict,
-            final List<String> words,
-            final SparseArray<List<Integer>> bigrams) {
+    private void checkDictionary(final FusionDictionary dict, final List<String> words,
+            final SparseArray<List<Integer>> bigrams, final Map<String, List<String>> shortcutMap) {
         assertNotNull(dict);
 
         // check unigram
@@ -149,94 +213,93 @@
                 assertNotNull(words.get(w1) + "," + words.get(w2), cg.getBigram(words.get(w2)));
             }
         }
+
+        // check shortcut
+        if (shortcutMap != null) {
+            for (final Map.Entry<String, List<String>> entry : shortcutMap.entrySet()) {
+                final CharGroup group = FusionDictionary.findWordInTree(dict.mRoot, entry.getKey());
+                for (final String word : entry.getValue()) {
+                    assertNotNull("shortcut not found: " + entry.getKey() + ", " + word,
+                            group.getShortcut(word));
+                }
+            }
+        }
     }
 
     // Tests for readDictionaryBinary and writeDictionaryBinary
 
     private long timeReadingAndCheckDict(final File file, final List<String> words,
-            final SparseArray<List<Integer>> bigrams) {
-
+            final SparseArray<List<Integer>> bigrams, final Map<String, List<String>> shortcutMap,
+            final int bufferType) {
         long now, diff = -1;
+        final FusionDictionaryBufferInterface buffer = getBuffer(file, bufferType);
+        assertNotNull(buffer);
 
-        FileInputStream inStream = null;
+        FusionDictionary dict = null;
         try {
-            inStream = new FileInputStream(file);
-            final ByteBuffer buffer = inStream.getChannel().map(
-                    FileChannel.MapMode.READ_ONLY, 0, file.length());
-
             now = System.currentTimeMillis();
-
-            final FusionDictionary dict =
-                    BinaryDictInputOutput.readDictionaryBinary(buffer, null);
-
-            diff = System.currentTimeMillis() - now;
-
-            checkDictionary(dict, words, bigrams);
-            return diff;
-
+            dict = BinaryDictInputOutput.readDictionaryBinary(buffer, null);
+            diff  = System.currentTimeMillis() - now;
         } catch (IOException e) {
-            Log.e(TAG, "raise IOException while reading file " + e);
+            Log.e(TAG, "IOException while reading dictionary: " + e);
         } catch (UnsupportedFormatException e) {
-            Log.e(TAG, "Unsupported format: " + e);
-        } finally {
-            if (inStream != null) {
-                try {
-                    inStream.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
+            Log.e(TAG, "Unsupported format: "+ e);
         }
 
+        checkDictionary(dict, words, bigrams, shortcutMap);
         return diff;
     }
 
+    // Tests for readDictionaryBinary and writeDictionaryBinary
     private String runReadAndWrite(final List<String> words,
-            final SparseArray<List<Integer>> bigrams,
-            final String message) {
-        final FusionDictionary dict = new FusionDictionary(new Node(),
-                new FusionDictionary.DictionaryOptions(
-                        new HashMap<String,String>(), false, false));
-
+            final SparseArray<List<Integer>> bigrams, final Map<String, List<String>> shortcuts,
+            final int bufferType, final String message) {
         File file = null;
         try {
             file = File.createTempFile("runReadAndWrite", ".dict");
         } catch (IOException e) {
             Log.e(TAG, "IOException: " + e);
         }
-
         assertNotNull(file);
 
-        addUnigrams(words.size(), dict, words);
+        final FusionDictionary dict = new FusionDictionary(new Node(),
+                new FusionDictionary.DictionaryOptions(
+                        new HashMap<String,String>(), false, false));
+        addUnigrams(words.size(), dict, words, shortcuts);
         addBigrams(dict, words, bigrams);
-        // check original dictionary
-        checkDictionary(dict, words, bigrams);
+        checkDictionary(dict, words, bigrams, shortcuts);
 
         final long write = timeWritingDictToFile(file, dict);
-        final long read = timeReadingAndCheckDict(file, words, bigrams);
+        final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType);
 
-        return "PROF: read=" + read + "ms, write=" + write + "ms    :" + message;
+        return "PROF: read=" + read + "ms, write=" + write + "ms    :" + message +
+                " : buffer type = " + bufferType;
     }
 
-    public void testReadAndWrite() {
-        final List<String> results = new ArrayList<String>();
+    public void testReadAndWriteWithByteBuffer() {
+        final List<String> results = CollectionUtils.newArrayList();
 
-        final Random random = new Random(123456);
-        final List<String> words = generateWords(MAX_UNIGRAMS, random);
-        final SparseArray<List<Integer>> emptyArray = CollectionUtils.newSparseArray();
+        results.add(runReadAndWrite(sWords, sEmptyBigrams, null /* shortcuts */, USE_BYTE_BUFFER,
+                "unigram"));
+        results.add(runReadAndWrite(sWords, sChainBigrams, null /* shortcuts */, USE_BYTE_BUFFER,
+                "chain"));
+        results.add(runReadAndWrite(sWords, sStarBigrams, null /* shortcuts */, USE_BYTE_BUFFER,
+                "star"));
 
-        final SparseArray<List<Integer>> chain = CollectionUtils.newSparseArray();
-        for (int i = 0; i < words.size(); ++i) chain.put(i, new ArrayList<Integer>());
-        for (int i = 1; i < words.size(); ++i) chain.get(i-1).add(i);
+        for (final String result : results) {
+            Log.d(TAG, result);
+        }
+    }
 
-        final SparseArray<List<Integer>> star = CollectionUtils.newSparseArray();
-        final List<Integer> list0 = CollectionUtils.newArrayList();
-        star.put(0, list0);
-        for (int i = 1; i < words.size(); ++i) star.get(0).add(i);
+    public void testReadAndWriteWithByteArray() {
+        final List<String> results = CollectionUtils.newArrayList();
 
-        results.add(runReadAndWrite(words, emptyArray, "only unigram"));
-        results.add(runReadAndWrite(words, chain, "chain"));
-        results.add(runReadAndWrite(words, star, "star"));
+        results.add(runReadAndWrite(sWords, sEmptyBigrams, null /* shortcuts */, USE_BYTE_ARRAY,
+                "unigram"));
+        results.add(runReadAndWrite(sWords, sChainBigrams, null /* shortcuts */, USE_BYTE_ARRAY,
+                "chain"));
+        results.add(runReadAndWrite(sWords, sStarBigrams, null /* shortcuts */, USE_BYTE_ARRAY,
+                "star"));
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -292,7 +355,7 @@
     }
 
     private long timeAndCheckReadUnigramsAndBigramsBinary(final File file, final List<String> words,
-            final SparseArray<List<Integer>> bigrams) {
+            final SparseArray<List<Integer>> bigrams, final int bufferType) {
         FileInputStream inStream = null;
 
         final Map<Integer, String> resultWords = CollectionUtils.newTreeMap();
@@ -301,17 +364,13 @@
         final Map<Integer, Integer> resultFreqs = CollectionUtils.newTreeMap();
 
         long now = -1, diff = -1;
+        final FusionDictionaryBufferInterface buffer = getBuffer(file, bufferType);
+        assertNotNull("Can't get buffer.", buffer);
         try {
-            inStream = new FileInputStream(file);
-            final ByteBuffer buffer = inStream.getChannel().map(
-                    FileChannel.MapMode.READ_ONLY, 0, file.length());
-
             now = System.currentTimeMillis();
-            BinaryDictInputOutput.readUnigramsAndBigramsBinary(
-                    new BinaryDictInputOutput.ByteBufferWrapper(buffer), resultWords, resultFreqs,
+            BinaryDictInputOutput.readUnigramsAndBigramsBinary(buffer, resultWords, resultFreqs,
                     resultBigrams);
             diff = System.currentTimeMillis() - now;
-            checkWordMap(words, bigrams, resultWords, resultFreqs, resultBigrams);
         } catch (IOException e) {
             Log.e(TAG, "IOException " + e);
         } catch (UnsupportedFormatException e) {
@@ -326,50 +385,64 @@
             }
         }
 
+        checkWordMap(words, bigrams, resultWords, resultFreqs, resultBigrams);
         return diff;
     }
 
-    private void runReadUnigramsAndBigramsBinary(final List<String> words,
-            final SparseArray<List<Integer>> bigrams) {
-
-        // making the dictionary from lists of words.
-        final FusionDictionary dict = new FusionDictionary(new Node(),
-                new FusionDictionary.DictionaryOptions(
-                        new HashMap<String, String>(), false, false));
-
+    private String runReadUnigramsAndBigramsBinary(final List<String> words,
+            final SparseArray<List<Integer>> bigrams, final int bufferType,
+            final String message) {
         File file = null;
         try {
             file = File.createTempFile("runReadUnigrams", ".dict");
         } catch (IOException e) {
             Log.e(TAG, "IOException: " + e);
         }
-
         assertNotNull(file);
 
-        addUnigrams(words.size(), dict, words);
+        // making the dictionary from lists of words.
+        final FusionDictionary dict = new FusionDictionary(new Node(),
+                new FusionDictionary.DictionaryOptions(
+                        new HashMap<String, String>(), false, false));
+        addUnigrams(words.size(), dict, words, null /* shortcutMap */);
         addBigrams(dict, words, bigrams);
+
         timeWritingDictToFile(file, dict);
 
-        long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams);
-        long fullReading = timeReadingAndCheckDict(file, words, bigrams);
+        long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams, bufferType);
+        long fullReading = timeReadingAndCheckDict(file, words, bigrams, null /* shortcutMap */,
+                bufferType);
 
-        Log.d(TAG, "read=" + fullReading + ", bytearray=" + wordMap);
+        return "readDictionaryBinary=" + fullReading + ", readUnigramsAndBigramsBinary=" + wordMap
+                + " : " + message + " : buffer type = " + bufferType;
     }
 
-    public void testReadUnigramsAndBigramsBinary() {
-        final List<String> results = new ArrayList<String>();
+    public void testReadUnigramsAndBigramsBinaryWithByteBuffer() {
+        final List<String> results = CollectionUtils.newArrayList();
 
-        final Random random = new Random(123456);
-        final List<String> words = generateWords(MAX_UNIGRAMS, random);
-        final SparseArray<List<Integer>> emptyArray = CollectionUtils.newSparseArray();
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sEmptyBigrams, USE_BYTE_BUFFER,
+                "unigram"));
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sChainBigrams, USE_BYTE_BUFFER,
+                "chain"));
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sStarBigrams, USE_BYTE_BUFFER,
+                "star"));
 
-        runReadUnigramsAndBigramsBinary(words, emptyArray);
-
-        final SparseArray<List<Integer>> star = CollectionUtils.newSparseArray();
-        for (int i = 1; i < words.size(); ++i) {
-            star.put(i-1, new ArrayList<Integer>());
-            star.get(i-1).add(i);
+        for (final String result : results) {
+            Log.d(TAG, result);
         }
-        runReadUnigramsAndBigramsBinary(words, star);
+    }
+
+    public void testReadUnigramsAndBigramsBinaryWithByteArray() {
+        final List<String> results = CollectionUtils.newArrayList();
+
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sEmptyBigrams, USE_BYTE_ARRAY,
+                "unigram"));
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sChainBigrams, USE_BYTE_ARRAY,
+                "chain"));
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sStarBigrams, USE_BYTE_ARRAY, "star"));
+
+        for (final String result : results) {
+            Log.d(TAG, result);
+        }
     }
 }