Merge "Handle back key and outside touch correctly in more suggestions window"
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index 2baae54..b052532 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -6,7 +6,6 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
     <uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
 
     <application android:label="@string/english_ime_name"
diff --git a/java/res/layout/vibration_settings_dialog.xml b/java/res/layout/vibration_settings_dialog.xml
new file mode 100644
index 0000000..981ba9b
--- /dev/null
+++ b/java/res/layout/vibration_settings_dialog.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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
+**
+**     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.
+*/
+-->
+
+<LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="10dip">
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:layout_margin="10dip">
+        <TextView android:id="@+id/vibration_value"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="20dip"/>
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/settings_ms"
+            android:textSize="20dip"/>
+    </LinearLayout>
+    <SeekBar
+        android:id="@+id/vibration_settings"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:max="250"
+        android:layout_margin="10dip"/>
+</LinearLayout>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 2a76321..34ce527 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -34,7 +34,9 @@
         <attr name="moreSuggestionsViewStyle" format="reference" />
         <attr name="suggestionBackgroundStyle" format="reference" />
         <attr name="suggestionPreviewBackgroundStyle" format="reference" />
-        </declare-styleable>
+        <!-- Touch position correction -->
+        <attr name="touchPositionCorrectionData" format="reference" />
+    </declare-styleable>
 
     <declare-styleable name="KeyboardView">
         <!-- Image for the key. This image needs to be a StateListDrawable, with the following
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 75e22dd..aefaec9 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -165,4 +165,5 @@
     <!-- dictionary pack package name /settings activity (for shared prefs and settings) -->
     <string name="dictionary_pack_package_name">com.google.android.inputmethod.latin.dictionarypack</string>
     <string name="dictionary_pack_settings_activity">com.google.android.inputmethod.latin.dictionarypack.DictionarySettingsActivity</string>
+    <string name="settings_ms">ms</string>
 </resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 2f591fd..a1c1a9f 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -343,4 +343,6 @@
 
     <!-- Title of an option for usability study mode -->
     <string name="prefs_usability_study_mode">Usability study mode</string>
+    <!-- Title of the settings for vibration duration -->
+    <string name="prefs_vibration_duration_settings">Vibration duration settings</string>
 </resources>
diff --git a/java/res/values/themes-basic-highcontrast.xml b/java/res/values/themes-basic-highcontrast.xml
index 9127235..bc3c847 100644
--- a/java/res/values/themes-basic-highcontrast.xml
+++ b/java/res/values/themes-basic-highcontrast.xml
@@ -28,5 +28,6 @@
         <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
         <item name="suggestionBackgroundStyle">@style/SuggestionBackgroundStyle</item>
         <item name="suggestionPreviewBackgroundStyle">@style/SuggestionPreviewBackgroundStyle</item>
+        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_empty</item>
     </style>
 </resources>
diff --git a/java/res/values/themes-basic.xml b/java/res/values/themes-basic.xml
index 6c0e16e..29cb9cc 100644
--- a/java/res/values/themes-basic.xml
+++ b/java/res/values/themes-basic.xml
@@ -28,5 +28,6 @@
         <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
         <item name="suggestionBackgroundStyle">@style/SuggestionBackgroundStyle</item>
         <item name="suggestionPreviewBackgroundStyle">@style/SuggestionPreviewBackgroundStyle</item>
+        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_empty</item>
     </style>
 </resources>
diff --git a/java/res/values/themes-gingerbread.xml b/java/res/values/themes-gingerbread.xml
index 43bff50..c4a0f80 100644
--- a/java/res/values/themes-gingerbread.xml
+++ b/java/res/values/themes-gingerbread.xml
@@ -28,5 +28,6 @@
         <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
         <item name="suggestionBackgroundStyle">@style/SuggestionBackgroundStyle</item>
         <item name="suggestionPreviewBackgroundStyle">@style/SuggestionPreviewBackgroundStyle</item>
+        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_gingerbread</item>
     </style>
 </resources>
diff --git a/java/res/values/themes-ics.xml b/java/res/values/themes-ics.xml
index 1235d4e..dd2b6a3 100644
--- a/java/res/values/themes-ics.xml
+++ b/java/res/values/themes-ics.xml
@@ -28,5 +28,6 @@
         <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle.IceCreamSandwich</item>
         <item name="suggestionBackgroundStyle">@style/SuggestionBackgroundStyle.IceCreamSandwich</item>
         <item name="suggestionPreviewBackgroundStyle">@style/SuggestionPreviewBackgroundStyle.IceCreamSandwich</item>
+        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_ice_cream_sandwich</item>
     </style>
 </resources>
diff --git a/java/res/values/themes-stone-bold.xml b/java/res/values/themes-stone-bold.xml
index 6e25f41..6e864be 100644
--- a/java/res/values/themes-stone-bold.xml
+++ b/java/res/values/themes-stone-bold.xml
@@ -28,5 +28,6 @@
         <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
         <item name="suggestionBackgroundStyle">@style/SuggestionBackgroundStyle</item>
         <item name="suggestionPreviewBackgroundStyle">@style/SuggestionPreviewBackgroundStyle</item>
+        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_empty</item>
     </style>
 </resources>
diff --git a/java/res/values/themes-stone.xml b/java/res/values/themes-stone.xml
index 3cbda81..64c5570 100644
--- a/java/res/values/themes-stone.xml
+++ b/java/res/values/themes-stone.xml
@@ -28,5 +28,6 @@
         <item name="moreSuggestionsViewStyle">@style/MoreSuggestionsViewStyle</item>
         <item name="suggestionBackgroundStyle">@style/SuggestionBackgroundStyle</item>
         <item name="suggestionPreviewBackgroundStyle">@style/SuggestionPreviewBackgroundStyle</item>
+        <item name="touchPositionCorrectionData">@array/touch_position_correction_data_empty</item>
     </style>
 </resources>
diff --git a/java/res/values/touch-position-correction.xml b/java/res/values/touch-position-correction.xml
new file mode 100644
index 0000000..0a0e4e5
--- /dev/null
+++ b/java/res/values/touch-position-correction.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!--
+        An entry of the touch_position_correction word should be:
+        1. (float) (touch_center_x - key_center_x) / key_width
+        2. (float) (touch_center_y - key_center_y) / key_height
+        3. (float) sweet_spot_radius / (key_width^2 + key_height^2)
+     -->
+
+    <string-array
+        name="touch_position_correction_data_empty"
+        translatable="false"
+    >
+        <!-- empty -->
+    </string-array>
+
+    <string-array
+        name="touch_position_correction_data_gingerbread"
+        translatable="false"
+    >
+        <!-- First row -->
+        <item>0.0091285</item>
+        <item>0.1193203</item>
+        <item>0.1622607</item>
+
+        <!-- Second row -->
+        <item>-0.0233128</item>
+        <item>0.1379798</item>
+        <item>0.1585229</item>
+
+        <!-- Third row -->
+        <item>-0.0080185</item>
+        <item>0.1911477</item>
+        <item>0.1570948</item>
+    </string-array>
+
+    <string-array
+        name="touch_position_correction_data_ice_cream_sandwich"
+        translatable="false"
+    >
+        <!-- First row -->
+        <item>0.0038756</item>
+        <item>-0.0005677</item>
+        <item>0.1577026</item>
+
+        <!-- Second row -->
+        <item>-0.0236678</item>
+        <item>0.0381731</item>
+        <item>0.1529972</item>
+
+        <!-- Third row -->
+        <item>-0.0086827</item>
+        <item>0.0880847</item>
+        <item>0.1522819</item>
+    </string-array>
+</resources>
\ No newline at end of file
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 24de95f..312af28 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -125,6 +125,9 @@
                 android:summary="@string/enable_span_insert_summary"
                 android:persistent="true"
                 android:defaultValue="true" />
+            <PreferenceScreen
+                android:key="pref_vibration_duration_settings"
+                android:title="@string/prefs_vibration_duration_settings"/>
             <!-- TODO: evaluate results and revive this option. The code
                 already supports it. -->
             <!-- <CheckBoxPreference -->
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 045f0d7..f77155e 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -138,7 +138,8 @@
 
         mProximityInfo = new ProximityInfo(
                 params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight,
-                mMostCommonKeyWidth, mKeys);
+                mMostCommonKeyWidth, mKeys, params.mTouchPositionCorrectionXs,
+                params.mTouchPositionCorrectionYs, params.mTouchPositionCorrectionRadii);
     }
 
     public ProximityInfo getProximityInfo() {
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 71b46d6..210ab48 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -40,8 +40,13 @@
     private final int mKeyboardHeight;
     private final int[][] mGridNeighbors;
 
-    ProximityInfo(
-            int gridWidth, int gridHeight, int minWidth, int height, int keyWidth, List<Key> keys) {
+    private final float[] mTouchPositionCorrectionXs;
+    private final float[] mTouchPositionCorrectionYs;
+    private final float[] mTouchPositionCorrectionRadii;
+
+    ProximityInfo(int gridWidth, int gridHeight, int minWidth, int height, int keyWidth,
+            List<Key> keys, float[] touchPositionCorrectionXs, float[] touchPositionCorrectionYs,
+            float[] touchPositionCorrectionRadii) {
         mGridWidth = gridWidth;
         mGridHeight = gridHeight;
         mGridSize = mGridWidth * mGridHeight;
@@ -49,6 +54,9 @@
         mCellHeight = (height + mGridHeight - 1) / mGridHeight;
         mKeyboardMinWidth = minWidth;
         mKeyboardHeight = height;
+        mTouchPositionCorrectionXs = touchPositionCorrectionXs;
+        mTouchPositionCorrectionYs = touchPositionCorrectionYs;
+        mTouchPositionCorrectionRadii = touchPositionCorrectionRadii;
         mGridNeighbors = new int[mGridSize][];
         if (minWidth == 0 || height == 0) {
             // No proximity required. Keyboard might be mini keyboard.
@@ -58,7 +66,7 @@
     }
 
     public static ProximityInfo createDummyProximityInfo() {
-        return new ProximityInfo(1, 1, 1, 1, 1, Collections.<Key>emptyList());
+        return new ProximityInfo(1, 1, 1, 1, 1, Collections.<Key>emptyList(), null, null, null);
     }
 
     public static ProximityInfo createSpellCheckerProximityInfo() {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index 187a1ad..46836da 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -30,6 +30,7 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -126,6 +127,8 @@
     private static final int DEFAULT_KEYBOARD_COLUMNS = 10;
     private static final int DEFAULT_KEYBOARD_ROWS = 4;
 
+    private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3;
+
     protected final KP mParams;
     protected final Context mContext;
     protected final Resources mResources;
@@ -248,10 +251,62 @@
         mParams.mThemeId = a.getInt(R.styleable.KeyboardTheme_themeId, 0);
         a.recycle();
 
+        if (!setTouchPositionCorrectionData(context)) {
+            // In the regression test, setTouchPositionCorrectionData() fails
+            mParams.mTouchPositionCorrectionXs = null;
+            mParams.mTouchPositionCorrectionYs = null;
+            mParams.mTouchPositionCorrectionRadii = null;
+        }
+
         mParams.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
         mParams.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
     }
 
+    private boolean setTouchPositionCorrectionData(Context context) {
+        final TypedArray a = context.obtainStyledAttributes(R.styleable.KeyboardTheme);
+        final int resourceId = a.getResourceId(
+                R.styleable.KeyboardTheme_touchPositionCorrectionData, 0);
+        if (resourceId == 0) {
+            // In the regression test, we cannot use theme resources
+            // TODO: Fix this
+            return false;
+        }
+        final String[] data = context.getResources().getStringArray(resourceId);
+        a.recycle();
+        final int dataLength = data.length;
+        if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
+            if (LatinImeLogger.sDBG) {
+                throw new RuntimeException("the size of touch position correction data is invalid");
+            }
+            return false;
+        }
+        final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+        mParams.mTouchPositionCorrectionXs = new float[length];
+        mParams.mTouchPositionCorrectionYs = new float[length];
+        mParams.mTouchPositionCorrectionRadii = new float[length];
+        try {
+            for (int i = 0; i < dataLength; ++i) {
+                final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+                final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+                final float value = Float.parseFloat(data[i]);
+                if (type == 0) {
+                    mParams.mTouchPositionCorrectionXs[index] = value;
+                } else if (type == 1) {
+                    mParams.mTouchPositionCorrectionYs[index] = value;
+                } else {
+                    mParams.mTouchPositionCorrectionRadii[index] = value;
+                }
+            }
+        } catch (NumberFormatException e) {
+            if (LatinImeLogger.sDBG) {
+                throw new RuntimeException(
+                        "the number format for touch position correction data is invalid");
+            }
+            return false;
+        }
+        return true;
+    }
+
     public KeyboardBuilder<KP> load(KeyboardId id) {
         mParams.mId = id;
         try {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
index 97f58fa..d1aea72 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -68,6 +68,10 @@
     public int mMostCommonKeyHeight = 0;
     public int mMostCommonKeyWidth = 0;
 
+    public float[] mTouchPositionCorrectionXs;
+    public float[] mTouchPositionCorrectionYs;
+    public float[] mTouchPositionCorrectionRadii;
+
     protected void clearKeys() {
         mKeys.clear();
         mShiftKeys.clear();
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 32649d5..48a1f8b 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -2099,16 +2099,7 @@
     }
 
     private void updateKeypressVibrationDuration() {
-        final String[] durationPerHardwareList = mResources.getStringArray(
-                R.array.keypress_vibration_durations);
-        final String hardwarePrefix = Build.HARDWARE + ",";
-        for (final String element : durationPerHardwareList) {
-            if (element.startsWith(hardwarePrefix)) {
-                mKeypressVibrationDuration =
-                        Long.parseLong(element.substring(element.lastIndexOf(',') + 1));
-                break;
-            }
-        }
+        mKeypressVibrationDuration = Utils.getCurrentVibrationDuration(mPrefs, mResources);
     }
 
     private void playKeyClick(int primaryCode) {
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index d706cd0..a2e8966 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -36,7 +36,10 @@
 import android.text.TextUtils;
 import android.text.method.LinkMovementMethod;
 import android.util.Log;
+import android.view.View;
 import android.view.inputmethod.EditorInfo;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
 import android.widget.TextView;
 
 import com.android.inputmethod.compat.CompatUtils;
@@ -89,6 +92,9 @@
 
     public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
 
+    public static final String PREF_VIBRATION_DURATION_SETTINGS =
+            "pref_vibration_duration_settings";
+
     // Dialog ids
     private static final int VOICE_INPUT_CONFIRM_DIALOG = 0;
 
@@ -335,6 +341,7 @@
     private boolean mVoiceOn;
 
     private AlertDialog mDialog;
+    private TextView mVibrationSettingsTextView;
 
     private boolean mOkClicked = false;
     private String mVoiceModeOff;
@@ -475,6 +482,19 @@
                 miscSettings.removePreference(pref);
             }
         }
+
+        final PreferenceScreen vibrationSettingsPref =
+                (PreferenceScreen) findPreference(PREF_VIBRATION_DURATION_SETTINGS);
+        if (vibrationSettingsPref != null) {
+            vibrationSettingsPref.setOnPreferenceClickListener(
+                    new OnPreferenceClickListener() {
+                        @Override
+                        public boolean onPreferenceClick(Preference arg0) {
+                            showVibrationSettingsDialog();
+                            return true;
+                        }
+                    });
+        }
     }
 
     @SuppressWarnings("unused")
@@ -621,4 +641,51 @@
             mVoicePreference.setValue(mVoiceModeOff);
         }
     }
-}
+
+    private void showVibrationSettingsDialog() {
+        final SharedPreferences sp = getPreferenceManager().getSharedPreferences();
+        final Activity context = getActivityInternal();
+        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+        builder.setTitle(R.string.prefs_vibration_duration_settings);
+        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int whichButton) {
+                final int ms = Integer.valueOf(mVibrationSettingsTextView.getText().toString());
+                sp.edit().putInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, ms).apply();
+            }
+        });
+        builder.setNegativeButton(android.R.string.cancel,  new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int whichButton) {
+                dialog.dismiss();
+            }
+        });
+        final View v = context.getLayoutInflater().inflate(
+                R.layout.vibration_settings_dialog, null);
+        final int currentMs = Utils.getCurrentVibrationDuration(
+                getPreferenceManager().getSharedPreferences(), getResources());
+        mVibrationSettingsTextView = (TextView)v.findViewById(R.id.vibration_value);
+        final SeekBar sb = (SeekBar)v.findViewById(R.id.vibration_settings);
+        sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+            @Override
+            public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) {
+                final int tempMs = arg1;
+                mVibrationSettingsTextView.setText(String.valueOf(tempMs));
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar arg0) {
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar arg0) {
+                final int tempMs = arg0.getProgress();
+                VibratorCompatWrapper.getInstance(context).vibrate(tempMs);
+            }
+        });
+        sb.setProgress(currentMs);
+        mVibrationSettingsTextView.setText(String.valueOf(currentMs));
+        builder.setView(v);
+        builder.create().show();
+    }
+}
\ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index c35273e..7712765 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -17,9 +17,11 @@
 package com.android.inputmethod.latin;
 
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.inputmethodservice.InputMethodService;
 import android.os.AsyncTask;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
@@ -772,4 +774,20 @@
         // - It also does not work with unicode surrogate code points.
         return s.toUpperCase(locale).charAt(0) + s.substring(1);
     }
+
+    public static int getCurrentVibrationDuration(SharedPreferences sp, Resources res) {
+        final int ms = sp.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, -1);
+        if (ms >= 0) {
+            return ms;
+        }
+        final String[] durationPerHardwareList = res.getStringArray(
+                R.array.keypress_vibration_durations);
+        final String hardwarePrefix = Build.HARDWARE + ",";
+        for (final String element : durationPerHardwareList) {
+            if (element.startsWith(hardwarePrefix)) {
+                return (int)Long.parseLong(element.substring(element.lastIndexOf(',') + 1));
+            }
+        }
+        return -1;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 2546df0..37145b2 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -95,6 +95,8 @@
         private final int DEFAULT_SUGGESTION_LENGTH = 16;
         private final ArrayList<CharSequence> mSuggestions;
         private final int[] mScores;
+        private final String mOriginalText;
+        private final double mThreshold;
         private final int mMaxLength;
         private int mLength = 0;
 
@@ -103,7 +105,10 @@
         private String mBestSuggestion = null;
         private int mBestScore = Integer.MIN_VALUE; // As small as possible
 
-        SuggestionsGatherer(final int maxLength) {
+        SuggestionsGatherer(final String originalText, final double threshold,
+                final int maxLength) {
+            mOriginalText = originalText;
+            mThreshold = threshold;
             mMaxLength = maxLength;
             mSuggestions = new ArrayList<CharSequence>(maxLength + 1);
             mScores = new int[mMaxLength];
@@ -146,8 +151,7 @@
             return true;
         }
 
-        public Result getResults(final CharSequence originalText, final double threshold,
-                final int capitalizeType, final Locale locale) {
+        public Result getResults(final int capitalizeType, final Locale locale) {
             final String[] gatheredSuggestions;
             final boolean hasLikelySuggestions;
             if (0 == mLength) {
@@ -160,8 +164,8 @@
                 } else {
                     gatheredSuggestions = EMPTY_STRING_ARRAY;
                     final double normalizedScore =
-                            Utils.calcNormalizedScore(originalText, mBestSuggestion, mBestScore);
-                    hasLikelySuggestions = (normalizedScore > threshold);
+                            Utils.calcNormalizedScore(mOriginalText, mBestSuggestion, mBestScore);
+                    hasLikelySuggestions = (normalizedScore > mThreshold);
                 }
             } else {
                 if (DBG) {
@@ -194,11 +198,11 @@
                 final int bestScore = mScores[mLength - 1];
                 final CharSequence bestSuggestion = mSuggestions.get(0);
                 final double normalizedScore =
-                        Utils.calcNormalizedScore(originalText, bestSuggestion, bestScore);
-                hasLikelySuggestions = (normalizedScore > threshold);
+                        Utils.calcNormalizedScore(mOriginalText, bestSuggestion, bestScore);
+                hasLikelySuggestions = (normalizedScore > mThreshold);
                 if (DBG) {
                     Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore);
-                    Log.i(TAG, "Normalized score = " + normalizedScore + " (threshold " + threshold
+                    Log.i(TAG, "Normalized score = " + normalizedScore + " (threshold " + mThreshold
                             + ") => hasLikelySuggestions = " + hasLikelySuggestions);
                 }
             }
@@ -351,7 +355,7 @@
 
                 // TODO: Don't gather suggestions if the limit is <= 0 unless necessary
                 final SuggestionsGatherer suggestionsGatherer =
-                        new SuggestionsGatherer(suggestionsLimit);
+                        new SuggestionsGatherer(text, mService.mTypoThreshold, suggestionsLimit);
                 final WordComposer composer = new WordComposer();
                 final int length = text.length();
                 for (int i = 0; i < length; ++i) {
@@ -392,8 +396,8 @@
                     }
                 }
 
-                final SuggestionsGatherer.Result result = suggestionsGatherer.getResults(text,
-                        mService.mTypoThreshold, capitalizeType, mLocale);
+                final SuggestionsGatherer.Result result = suggestionsGatherer.getResults(
+                        capitalizeType, mLocale);
 
                 if (DBG) {
                     Log.i(TAG, "Spell checking results for " + text + " with suggestion limit "
diff --git a/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java b/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java
index 096bfd1..35a7b51 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java
+++ b/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java
@@ -60,6 +60,7 @@
         final FusionDictionary mDictionary;
         int mState; // the state of the parser
         int mFreq; // the currently read freq
+        String mWord; // the current word
         final HashMap<String, ArrayList<WeightedString>> mBigramsMap;
 
         /**
@@ -72,6 +73,7 @@
                 HashMap<String, ArrayList<WeightedString>> bigrams) {
             mDictionary = dict;
             mBigramsMap = bigrams;
+            mWord = "";
             mState = START;
             mFreq = 0;
         }
@@ -80,6 +82,7 @@
         public void startElement(String uri, String localName, String qName, Attributes attrs) {
             if (WORD_TAG.equals(localName)) {
                 mState = WORD;
+                mWord = "";
                 for (int attrIndex = 0; attrIndex < attrs.getLength(); ++attrIndex) {
                     final String attrName = attrs.getLocalName(attrIndex);
                     if (FREQUENCY_ATTR.equals(attrName)) {
@@ -94,14 +97,19 @@
         @Override
         public void characters(char[] ch, int start, int length) {
             if (WORD == mState) {
-                final String word = String.copyValueOf(ch, start, length);
-                mDictionary.add(word, mFreq, mBigramsMap.get(word));
+                // The XML parser is free to return text in arbitrary chunks one after the
+                // other. In particular, this happens in some implementations when it finds
+                // an escape code like "&amp;".
+                mWord += String.copyValueOf(ch, start, length);
             }
         }
 
         @Override
         public void endElement(String uri, String localName, String qName) {
-            if (WORD == mState) mState = START;
+            if (WORD == mState) {
+                mDictionary.add(mWord, mFreq, mBigramsMap.get(mWord));
+                mState = START;
+            }
         }
     }