Merge "Add touch position correction data to resources."
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/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/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/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index b1cc7ca..1e2ea55 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -19,6 +19,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
@@ -86,6 +87,9 @@
     public final int mX;
     /** Y coordinate of the key in the keyboard layout */
     public final int mY;
+    /** Hit bounding box of the key */
+    public final Rect mHitBox = new Rect();
+
     /** Text to output when pressed. This can be multiple characters, like ".com" */
     public final CharSequence mOutputText;
     /** More keys */
@@ -93,14 +97,6 @@
     /** More keys maximum column number */
     public final int mMaxMoreKeysColumn;
 
-    /**
-     * Flags that specify the anchoring to edges of the keyboard for detecting touch events
-     * that are just out of the boundary of the key. This is a bit mask of
-     * {@link Keyboard#EDGE_LEFT}, {@link Keyboard#EDGE_RIGHT},
-     * {@link Keyboard#EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM}.
-     */
-    private int mEdgeFlags;
-
     /** Background type that represents different key background visual than normal one. */
     public final int mBackgroundType;
     public static final int BACKGROUND_TYPE_NORMAL = 0;
@@ -167,23 +163,22 @@
      * This constructor is being used only for key in more keys keyboard.
      */
     public Key(Resources res, KeyboardParams params, String moreKeySpec,
-            int x, int y, int width, int height, int edgeFlags) {
+            int x, int y, int width, int height) {
         this(params, MoreKeySpecParser.getLabel(moreKeySpec), null, getIcon(params, moreKeySpec),
                 getCode(res, params, moreKeySpec), MoreKeySpecParser.getOutputText(moreKeySpec),
-                x, y, width, height, edgeFlags);
+                x, y, width, height);
     }
 
     /**
      * This constructor is being used only for key in popup suggestions pane.
      */
     public Key(KeyboardParams params, CharSequence label, CharSequence hintLabel, Drawable icon,
-            int code, CharSequence outputText, int x, int y, int width, int height, int edgeFlags) {
+            int code, CharSequence outputText, int x, int y, int width, int height) {
         mHeight = height - params.mVerticalGap;
         mHorizontalGap = params.mHorizontalGap;
         mVerticalGap = params.mVerticalGap;
         mVisualInsetsLeft = mVisualInsetsRight = 0;
         mWidth = width - mHorizontalGap;
-        mEdgeFlags = edgeFlags;
         mHintLabel = hintLabel;
         mLabelOption = 0;
         mBackgroundType = BACKGROUND_TYPE_NORMAL;
@@ -197,6 +192,7 @@
         // Horizontal gap is divided equally to both sides of the key.
         mX = x + mHorizontalGap / 2;
         mY = y;
+        mHitBox.set(x, y, x + width + 1, y + height);
     }
 
     /**
@@ -212,8 +208,9 @@
     public Key(Resources res, KeyboardParams params, KeyboardBuilder.Row row,
             XmlResourceParser parser, KeyStyles keyStyles) {
         final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
+        final int keyHeight = row.mRowHeight;
         mVerticalGap = params.mVerticalGap;
-        mHeight = row.mRowHeight - mVerticalGap;
+        mHeight = keyHeight - mVerticalGap;
 
         final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
                 R.styleable.Keyboard_Key);
@@ -230,12 +227,14 @@
 
         final float keyXPos = row.getKeyX(keyAttr);
         final float keyWidth = row.getKeyWidth(keyAttr, keyXPos);
+        final int keyYPos = row.getKeyY();
 
         // Horizontal gap is divided equally to both sides of the key.
         mX = (int) (keyXPos + horizontalGap / 2);
-        mY = row.getKeyY();
+        mY = keyYPos;
         mWidth = (int) (keyWidth - horizontalGap);
         mHorizontalGap = (int) horizontalGap;
+        mHitBox.set((int)keyXPos, keyYPos, (int)(keyXPos + keyWidth) + 1, keyYPos + keyHeight);
         // Update row to have current x coordinate.
         row.setXPos(keyXPos + keyWidth);
 
@@ -256,7 +255,6 @@
                 R.styleable.Keyboard_Key_backgroundType, BACKGROUND_TYPE_NORMAL);
         mRepeatable = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable, false);
         mEnabled = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_enabled, true);
-        mEdgeFlags = 0;
 
         final KeyboardIconsSet iconsSet = params.mIconsSet;
         mVisualInsetsLeft = (int) KeyboardBuilder.getDimensionOrFraction(keyAttr,
@@ -294,8 +292,20 @@
         keyAttr.recycle();
     }
 
-    public void addEdgeFlags(int flags) {
-        mEdgeFlags |= flags;
+    public void markAsLeftEdge(KeyboardParams params) {
+        mHitBox.left = params.mHorizontalEdgesPadding;
+    }
+
+    public void markAsRightEdge(KeyboardParams params) {
+        mHitBox.right = params.mOccupiedWidth - params.mHorizontalEdgesPadding;
+    }
+
+    public void markAsTopEdge(KeyboardParams params) {
+        mHitBox.top = params.mTopPadding;
+    }
+
+    public void markAsBottomEdge(KeyboardParams params) {
+        mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding;
     }
 
     public boolean isSticky() {
@@ -427,23 +437,10 @@
      * @param y the y-coordinate of the point
      * @return whether or not the point falls on the key. If the key is attached to an edge, it will
      * assume that all points between the key and the edge are considered to be on the key.
+     * @see {@link #markAsLeftEdge(KeyboardParams)} etc.
      */
     public boolean isOnKey(int x, int y) {
-        final int left = mX - mHorizontalGap / 2;
-        final int right = left + mWidth + mHorizontalGap;
-        final int top = mY;
-        final int bottom = top + mHeight + mVerticalGap;
-        final int flags = mEdgeFlags;
-        if (flags == 0) {
-            return x >= left && x <= right && y >= top && y <= bottom;
-        }
-        final boolean leftEdge = (flags & Keyboard.EDGE_LEFT) != 0;
-        final boolean rightEdge = (flags & Keyboard.EDGE_RIGHT) != 0;
-        final boolean topEdge = (flags & Keyboard.EDGE_TOP) != 0;
-        final boolean bottomEdge = (flags & Keyboard.EDGE_BOTTOM) != 0;
-        // In order to mitigate rounding errors, we use (left <= x <= right) here.
-        return (x >= left || leftEdge) && (x <= right || rightEdge)
-                && (y >= top || topEdge) && (y <= bottom || bottomEdge);
+        return mHitBox.contains(x, y);
     }
 
     /**
@@ -547,7 +544,7 @@
          * This constructor is being used only for divider in more keys keyboard.
          */
         public Spacer(KeyboardParams params, Drawable icon, int x, int y, int width, int height) {
-            super(params, null, null, icon, Keyboard.CODE_DUMMY, null, x, y, width, height, 0);
+            super(params, null, null, icon, Keyboard.CODE_DUMMY, null, x, y, width, height);
         }
 
         @Override
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index b151729..f77155e 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -47,11 +47,6 @@
  * </pre>
  */
 public class Keyboard {
-    public static final int EDGE_LEFT = 0x01;
-    public static final int EDGE_RIGHT = 0x02;
-    public static final int EDGE_TOP = 0x04;
-    public static final int EDGE_BOTTOM = 0x08;
-
     /** Some common keys code.  These should be aligned with values/keycodes.xml */
     public static final int CODE_ENTER = '\n';
     public static final int CODE_TAB = '\t';
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
index c904394..d4b35a5 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
@@ -193,13 +193,11 @@
                 return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
             }
 
-            public int getRowFlags(int row) {
-                int rowFlags = 0;
+            public void markAsEdgeKey(Key key, int row) {
                 if (row == 0)
-                    rowFlags |= Keyboard.EDGE_TOP;
+                    key.markAsTopEdge(this);
                 if (isTopRow(row))
-                    rowFlags |= Keyboard.EDGE_BOTTOM;
-                return rowFlags;
+                    key.markAsBottomEdge(this);
             }
 
             private boolean isTopRow(int rowCount) {
@@ -254,8 +252,8 @@
                 final String moreKeySpec = mMoreKeys[n].toString();
                 final int row = n / params.mNumColumns;
                 final Key key = new Key(mResources, params, moreKeySpec, params.getX(n, row),
-                        params.getY(row), params.mDefaultKeyWidth, params.mDefaultRowHeight,
-                        params.getRowFlags(row));
+                        params.getY(row), params.mDefaultKeyWidth, params.mDefaultRowHeight);
+                params.markAsEdgeKey(key, row);
                 params.onAddKey(key);
             }
             return new MiniKeyboard(params);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index 70ba48f..46836da 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -808,7 +808,7 @@
         if (mCurrentRow == null)
             throw new InflateException("orphant end row tag");
         if (mRightEdgeKey != null) {
-            mRightEdgeKey.addEdgeFlags(Keyboard.EDGE_RIGHT);
+            mRightEdgeKey.markAsRightEdge(mParams);
             mRightEdgeKey = null;
         }
         addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
@@ -820,11 +820,11 @@
     private void endKey(Key key) {
         mParams.onAddKey(key);
         if (mLeftEdge) {
-            key.addEdgeFlags(Keyboard.EDGE_LEFT);
+            key.markAsLeftEdge(mParams);
             mLeftEdge = false;
         }
         if (mTopEdge) {
-            key.addEdgeFlags(Keyboard.EDGE_TOP);
+            key.markAsTopEdge(mParams);
         }
         mRightEdgeKey = key;
     }
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/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/MoreSuggestions.java
index 1afa072..9a59ef2 100644
--- a/java/src/com/android/inputmethod/latin/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/MoreSuggestions.java
@@ -153,23 +153,19 @@
                 return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow;
             }
 
-            public int getFlags(int pos) {
-                int rowFlags = 0;
-
+            public void markAsEdgeKey(Key key, int pos) {
                 final int row = mRowNumbers[pos];
                 if (row == 0)
-                    rowFlags |= Keyboard.EDGE_BOTTOM;
+                    key.markAsBottomEdge(this);
                 if (row == mNumRows - 1)
-                    rowFlags |= Keyboard.EDGE_TOP;
+                    key.markAsTopEdge(this);
 
                 final int numColumnInRow = mNumColumnsInRow[row];
                 final int column = getColumnNumber(pos);
                 if (column == 0)
-                    rowFlags |= Keyboard.EDGE_LEFT;
+                    key.markAsLeftEdge(this);
                 if (column == numColumnInRow - 1)
-                    rowFlags |= Keyboard.EDGE_RIGHT;
-
-                return rowFlags;
+                    key.markAsRightEdge(this);
             }
         }
 
@@ -214,7 +210,8 @@
                 final int index = pos + SUGGESTION_CODE_BASE;
                 final Key key = new Key(
                         params, word, info, null, index, null, x, y, width,
-                        params.mDefaultRowHeight, params.getFlags(pos));
+                        params.mDefaultRowHeight);
+                params.markAsEdgeKey(key, pos);
                 params.onAddKey(key);
                 final int columnNumber = params.getColumnNumber(pos);
                 final int numColumnInRow = params.getNumColumnInRow(pos);
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 77fbe3e..37145b2 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -85,16 +85,18 @@
     private static class SuggestionsGatherer implements WordCallback {
         public static class Result {
             public final String[] mSuggestions;
-            public final boolean mLooksLikeTypo;
-            public Result(final String[] gatheredSuggestions, final boolean looksLikeTypo) {
+            public final boolean mHasLikelySuggestions;
+            public Result(final String[] gatheredSuggestions, final boolean hasLikelySuggestions) {
                 mSuggestions = gatheredSuggestions;
-                mLooksLikeTypo = looksLikeTypo;
+                mHasLikelySuggestions = hasLikelySuggestions;
             }
         }
 
         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,22 +151,21 @@
             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 looksLikeTypo;
+            final boolean hasLikelySuggestions;
             if (0 == mLength) {
                 // Either we found no suggestions, or we found some BUT the max length was 0.
                 // If we found some mBestSuggestion will not be null. If it is null, then
                 // we found none, regardless of the max length.
                 if (null == mBestSuggestion) {
                     gatheredSuggestions = null;
-                    looksLikeTypo = false;
+                    hasLikelySuggestions = false;
                 } else {
                     gatheredSuggestions = EMPTY_STRING_ARRAY;
                     final double normalizedScore =
-                            Utils.calcNormalizedScore(originalText, mBestSuggestion, mBestScore);
-                    looksLikeTypo = (normalizedScore > threshold);
+                            Utils.calcNormalizedScore(mOriginalText, mBestSuggestion, mBestScore);
+                    hasLikelySuggestions = (normalizedScore > mThreshold);
                 }
             } else {
                 if (DBG) {
@@ -194,15 +198,15 @@
                 final int bestScore = mScores[mLength - 1];
                 final CharSequence bestSuggestion = mSuggestions.get(0);
                 final double normalizedScore =
-                        Utils.calcNormalizedScore(originalText, bestSuggestion, bestScore);
-                looksLikeTypo = (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
-                            + ") => looksLikeTypo = " + looksLikeTypo);
+                    Log.i(TAG, "Normalized score = " + normalizedScore + " (threshold " + mThreshold
+                            + ") => hasLikelySuggestions = " + hasLikelySuggestions);
                 }
             }
-            return new Result(gatheredSuggestions, looksLikeTypo);
+            return new Result(gatheredSuggestions, hasLikelySuggestions);
         }
     }
 
@@ -349,8 +353,9 @@
                     }
                 }
 
+                // 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) {
@@ -391,23 +396,24 @@
                     }
                 }
 
-                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 "
                             + suggestionsLimit);
-                    Log.i(TAG, "IsInDict = " + result.mLooksLikeTypo);
-                    Log.i(TAG, "LooksLikeTypo = " + result.mLooksLikeTypo);
+                    Log.i(TAG, "IsInDict = " + isInDict);
+                    Log.i(TAG, "LooksLikeTypo = " + (!isInDict));
+                    Log.i(TAG, "HasLikelySuggestions = " + result.mHasLikelySuggestions);
                     for (String suggestion : result.mSuggestions) {
                         Log.i(TAG, suggestion);
                     }
                 }
 
+                // TODO: actually use result.mHasLikelySuggestions
                 final int flags =
-                        (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY : 0)
-                                | (result.mLooksLikeTypo
-                                        ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0);
+                        (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
+                                : SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO);
                 return new SuggestionsInfo(flags, result.mSuggestions);
             } catch (RuntimeException e) {
                 // Don't kill the keyboard if there is a bug in the spell checker
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;
+            }
         }
     }