Merge "Prepare for proximity + two words suggestion"
diff --git a/java/res/layout/input_view.xml b/java/res/layout/input_view.xml
index 2e0cddc..b9451f8 100644
--- a/java/res/layout/input_view.xml
+++ b/java/res/layout/input_view.xml
@@ -43,7 +43,7 @@
             android:layout_width="@dimen/suggestions_strip_padding"
             android:layout_height="@dimen/suggestions_strip_height"
             style="?attr/suggestionsStripBackgroundStyle" />
-        <com.android.inputmethod.latin.SuggestionsView
+        <com.android.inputmethod.latin.suggestions.SuggestionsView
             android:id="@+id/suggestions_view"
             android:layout_weight="1.0"
             android:layout_width="0dp"
diff --git a/java/res/layout/more_suggestions.xml b/java/res/layout/more_suggestions.xml
index 6aa82e1..1e59b89 100644
--- a/java/res/layout/more_suggestions.xml
+++ b/java/res/layout/more_suggestions.xml
@@ -24,7 +24,7 @@
         android:orientation="horizontal"
         style="?attr/miniKeyboardPanelStyle"
         >
-    <com.android.inputmethod.latin.MoreSuggestionsView
+    <com.android.inputmethod.latin.suggestions.MoreSuggestionsView
             xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
             android:id="@+id/more_suggestions_view"
             android:layout_alignParentBottom="true"
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 94f7ab7..d085644 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -127,9 +127,11 @@
             <flag name="autoCorrectUnderline" value="0x02" />
             <flag name="validTypedWordBold" value="0x04" />
         </attr>
+        <attr name="colorValidTypedWord" format="color" />
         <attr name="colorTypedWord" format="color" />
         <attr name="colorAutoCorrect" format="color" />
         <attr name="colorSuggested" format="color" />
+        <attr name="alphaValidTypedWord" format="integer" />
         <attr name="alphaTypedWord" format="integer" />
         <attr name="alphaAutoCorrect" format="integer" />
         <attr name="alphaSuggested" format="integer" />
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index bad4bc6..8b99a1f 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -38,7 +38,6 @@
     <bool name="config_default_bigram_prediction">false</bool>
     <bool name="config_default_sound_enabled">false</bool>
     <bool name="config_default_vibration_enabled">true</bool>
-    <bool name="config_auto_correction_spacebar_led_enabled">false</bool>
     <!-- Showing mini keyboard, just above the touched point if true, aligned to the key if false -->
     <bool name="config_show_mini_keyboard_at_touched_point">false</bool>
     <!-- The language is never displayed if == 0, always displayed if < 0 -->
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 43aa583..2025a7d 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -32,8 +32,7 @@
         <item name="maxMoreKeysColumn">@integer/config_max_more_keys_column</item>
     </style>
     <style name="LatinKeyboard">
-        <item name="autoCorrectionSpacebarLedEnabled">@bool/config_auto_correction_spacebar_led_enabled
-        </item>
+        <item name="autoCorrectionSpacebarLedEnabled">true</item>
         <item name="spacebarTextColor">#FFC0C0C0</item>
         <item name="spacebarTextShadowColor">#80000000</item>
     </style>
@@ -98,7 +97,8 @@
         name="SuggestionsViewStyle"
         parent="SuggestionsStripBackgroundStyle"
     >
-        <item name="suggestionStripOption">autoCorrectBold</item>
+        <item name="suggestionStripOption">autoCorrectBold|validTypedWordBold</item>
+        <item name="colorValidTypedWord">#FFFCAE00</item>
         <item name="colorTypedWord">@android:color/white</item>
         <item name="colorAutoCorrect">#FFFCAE00</item>
         <item name="colorSuggested">#FFFCAE00</item>
@@ -242,6 +242,7 @@
         name="LatinKeyboard.IceCreamSandwich"
         parent="LatinKeyboard"
     >
+        <item name="autoCorrectionSpacebarLedEnabled">false</item>
         <item name="disabledShortcutIcon">@drawable/sym_keyboard_voice_off_holo</item>
     </style>
     <style
@@ -296,11 +297,12 @@
     >
         <item name="suggestionStripOption">autoCorrectBold|validTypedWordBold</item>
         <!-- android:color/holo_blue_light=#FF33B5E5 -->
+        <item name="colorValidTypedWord">@android:color/holo_blue_light</item>
         <item name="colorTypedWord">@android:color/holo_blue_light</item>
         <item name="colorAutoCorrect">@android:color/holo_blue_light</item>
         <item name="colorSuggested">@android:color/holo_blue_light</item>
+        <item name="alphaValidTypedWord">85</item>
         <item name="alphaTypedWord">85</item>
-        <item name="alphaAutoCorrect">100</item>
         <item name="alphaSuggested">70</item>
         <item name="alphaObsoleted">70</item>
         <item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index 1836f27..9caed00 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.accessibility;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.inputmethodservice.InputMethodService;
 import android.media.AudioManager;
 import android.os.SystemClock;
@@ -55,15 +54,15 @@
      */
     private static final boolean ENABLE_ACCESSIBILITY = true;
 
-    public static void init(InputMethodService inputMethod, SharedPreferences prefs) {
+    public static void init(InputMethodService inputMethod) {
         if (!ENABLE_ACCESSIBILITY)
             return;
 
         // These only need to be initialized if the kill switch is off.
-        sInstance.initInternal(inputMethod, prefs);
-        KeyCodeDescriptionMapper.init(inputMethod, prefs);
-        AccessibleInputMethodServiceProxy.init(inputMethod, prefs);
-        AccessibleKeyboardViewProxy.init(inputMethod, prefs);
+        sInstance.initInternal(inputMethod);
+        KeyCodeDescriptionMapper.init();
+        AccessibleInputMethodServiceProxy.init(inputMethod);
+        AccessibleKeyboardViewProxy.init(inputMethod);
     }
 
     public static AccessibilityUtils getInstance() {
@@ -74,7 +73,7 @@
         // This class is not publicly instantiable.
     }
 
-    private void initInternal(Context context, SharedPreferences prefs) {
+    private void initInternal(Context context) {
         mContext = context;
         mAccessibilityManager = (AccessibilityManager) context
                 .getSystemService(Context.ACCESSIBILITY_SERVICE);
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
index 4ab9cb8..d834dd1 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.accessibility;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.inputmethodservice.InputMethodService;
 import android.media.AudioManager;
 import android.os.Looper;
@@ -82,8 +81,8 @@
         }
     }
 
-    public static void init(InputMethodService inputMethod, SharedPreferences prefs) {
-        sInstance.initInternal(inputMethod, prefs);
+    public static void init(InputMethodService inputMethod) {
+        sInstance.initInternal(inputMethod);
     }
 
     public static AccessibleInputMethodServiceProxy getInstance() {
@@ -94,7 +93,7 @@
         // Not publicly instantiable.
     }
 
-    private void initInternal(InputMethodService inputMethod, SharedPreferences prefs) {
+    private void initInternal(InputMethodService inputMethod) {
         mInputMethod = inputMethod;
         mVibrator = (Vibrator) inputMethod.getSystemService(Context.VIBRATOR_SERVICE);
         mAudioManager = (AudioManager) inputMethod.getSystemService(Context.AUDIO_SERVICE);
@@ -125,8 +124,6 @@
      */
     @Override
     public void onFlickGesture(int direction) {
-        final int keyEventCode;
-
         switch (direction) {
         case FlickGestureDetector.FLICK_LEFT:
             sendDownUpKeyEvents(KeyEvent.KEYCODE_DPAD_LEFT);
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index 4cb2f20..9141daa 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.accessibility;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.inputmethodservice.InputMethodService;
@@ -43,8 +42,8 @@
 
     private Key mLastHoverKey = null;
 
-    public static void init(InputMethodService inputMethod, SharedPreferences prefs) {
-        sInstance.initInternal(inputMethod, prefs);
+    public static void init(InputMethodService inputMethod) {
+        sInstance.initInternal(inputMethod);
         sInstance.mListener = AccessibleInputMethodServiceProxy.getInstance();
     }
 
@@ -60,7 +59,7 @@
         // Not publicly instantiable.
     }
 
-    private void initInternal(InputMethodService inputMethod, SharedPreferences prefs) {
+    private void initInternal(InputMethodService inputMethod) {
         final Paint paint = new Paint();
         paint.setTextAlign(Paint.Align.LEFT);
         paint.setTextSize(14.0f);
@@ -71,8 +70,7 @@
         mGestureDetector = new KeyboardFlickGestureDetector(inputMethod);
     }
 
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event,
-            PointerTracker tracker) {
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         if (mView == null) {
             Log.e(TAG, "No keyboard view set!");
             return false;
@@ -132,9 +130,9 @@
             final Key key = tracker.getKeyOn(x, y);
 
             if (key != mLastHoverKey) {
-                fireKeyHoverEvent(tracker, mLastHoverKey, false);
+                fireKeyHoverEvent(mLastHoverKey, false);
                 mLastHoverKey = key;
-                fireKeyHoverEvent(tracker, mLastHoverKey, true);
+                fireKeyHoverEvent(mLastHoverKey, true);
             }
 
             return true;
@@ -143,7 +141,7 @@
         return false;
     }
 
-    private void fireKeyHoverEvent(PointerTracker tracker, Key key, boolean entering) {
+    private void fireKeyHoverEvent(Key key, boolean entering) {
         if (mListener == null) {
             Log.e(TAG, "No accessible keyboard action listener set!");
             return;
diff --git a/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java b/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java
index 9d99e31..db12f76 100644
--- a/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java
+++ b/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java
@@ -126,7 +126,6 @@
         }
 
         final float distanceSquare = calculateDistanceSquare(mCachedHoverEnter, event);
-        final long timeout = event.getEventTime() - mCachedHoverEnter.getEventTime();
 
         switch (event.getAction()) {
         case MotionEventCompatUtils.ACTION_HOVER_MOVE:
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index e01262c..3d5ab05 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.accessibility;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.text.TextUtils;
 
 import com.android.inputmethod.keyboard.Key;
@@ -45,8 +44,8 @@
     // Map of shift-locked key codes to spoken description resource IDs
     private final HashMap<Integer, Integer> mShiftLockedKeyCodeMap;
 
-    public static void init(Context context, SharedPreferences prefs) {
-        sInstance.initInternal(context, prefs);
+    public static void init() {
+        sInstance.initInternal();
     }
 
     public static KeyCodeDescriptionMapper getInstance() {
@@ -60,7 +59,7 @@
         mShiftLockedKeyCodeMap = new HashMap<Integer, Integer>();
     }
 
-    private void initInternal(Context context, SharedPreferences prefs) {
+    private void initInternal() {
         // Manual label substitutions for key labels with no string resource
         mKeyLabelMap.put(":-)", R.string.spoken_description_smiley);
 
diff --git a/java/src/com/android/inputmethod/compat/ArraysCompatUtils.java b/java/src/com/android/inputmethod/compat/ArraysCompatUtils.java
index f6afbcf..011473b 100644
--- a/java/src/com/android/inputmethod/compat/ArraysCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/ArraysCompatUtils.java
@@ -16,10 +16,14 @@
 
 package com.android.inputmethod.compat;
 
+import android.util.Log;
+
 import java.lang.reflect.Method;
 import java.util.Arrays;
 
 public class ArraysCompatUtils {
+    private static final String TAG = ArraysCompatUtils.class.getSimpleName();
+
     private static final Method METHOD_Arrays_binarySearch = CompatUtils
             .getMethod(Arrays.class, "binarySearch", int[].class, int.class, int.class, int.class);
 
@@ -33,8 +37,15 @@
         }
     }
 
-    /* package */ static int compatBinarySearch(int[] array, int startIndex, int endIndex,
-            int value) {
+    // TODO: Implement fast binary search
+    /* package for testing */
+    static int compatBinarySearch(int[] array, int startIndex, int endIndex, int value) {
+        // Output error log because this method has strict performance penalty.
+        // Note that this method has been called only from spell checker and spell checker exists
+        // only from IceCreamSandwich and after, so that there is no chance on pre-ICS device to
+        // invoke this method.
+        Log.e(TAG, "Invoked expensive binarySearch");
+
         if (startIndex > endIndex) throw new IllegalArgumentException();
         if (startIndex < 0 || endIndex > array.length) throw new ArrayIndexOutOfBoundsException();
 
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 4e4ccef..42d1fe1 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -73,10 +73,11 @@
     private static final int LABEL_FLAGS_WITH_ICON_RIGHT = 0x2000;
     private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000;
 
+    // TODO: This should be public final
     /** Icon to display instead of a label. Icon takes precedence over a label */
     private Drawable mIcon;
     /** Preview version of the icon, for the preview popup */
-    private Drawable mPreviewIcon;
+    public final Drawable mPreviewIcon;
 
     /** Width of the key, not including the gap */
     public final int mWidth;
@@ -198,6 +199,7 @@
         mCode = code;
         mAltCode = Keyboard.CODE_DUMMY;
         mIcon = icon;
+        mPreviewIcon = null;
         // Horizontal gap is divided equally to both sides of the key.
         mX = x + mHorizontalGap / 2;
         mY = y;
@@ -425,18 +427,11 @@
         return mIcon;
     }
 
-    public Drawable getPreviewIcon() {
-        return mPreviewIcon;
-    }
-
+    // TODO: Get rid of this method.
     public void setIcon(Drawable icon) {
         mIcon = icon;
     }
 
-    public void setPreviewIcon(Drawable icon) {
-        mPreviewIcon = icon;
-    }
-
     /**
      * Informs the key that it has been pressed, in case it needs to change its appearance or
      * state.
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 3b3ff07..b4172d4 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -73,15 +73,6 @@
     private final int mHashCode;
 
     public KeyboardId(int xmlId, int elementState, Locale locale, int orientation, int width,
-            int mode, EditorInfo editorInfo, boolean settingsKeyEnabled,
-            boolean clobberSettingsKey, boolean shortcutKeyEnabled, boolean hasShortcutKey) {
-        this(xmlId, elementState, locale, orientation, width, mode,
-                (editorInfo != null ? editorInfo.inputType : 0),
-                (editorInfo != null ? editorInfo.imeOptions : 0),
-                settingsKeyEnabled, clobberSettingsKey, shortcutKeyEnabled, hasShortcutKey);
-    }
-
-    private KeyboardId(int xmlId, int elementState, Locale locale, int orientation, int width,
             int mode, int inputType, int imeOptions, boolean settingsKeyEnabled,
             boolean clobberSettingsKey, boolean shortcutKeyEnabled, boolean hasShortcutKey) {
         this.mLocale = locale;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
index a28cfa8..34296fa 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
@@ -17,11 +17,9 @@
 package com.android.inputmethod.keyboard;
 
 import android.content.Context;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.util.DisplayMetrics;
 import android.util.Xml;
 import android.view.inputmethod.EditorInfo;
 
@@ -42,9 +40,9 @@
 
 /**
  * This class has a set of {@link KeyboardId}s. Each of them represents a different keyboard
- * specific to a keyboard state, such as alphabet, symbols, and so on.  Layouts in the same
- * {@link KeyboardSet} are related to each other.
- * A {@link KeyboardSet} needs to be created for each {@link android.view.inputmethod.EditorInfo}.
+ * specific to a keyboard state, such as alphabet, symbols, and so on. Layouts in the same
+ * {@link KeyboardSet} are related to each other. A {@link KeyboardSet} needs to be created for each
+ * {@link android.view.inputmethod.EditorInfo}.
  */
 public class KeyboardSet {
     private static final String TAG_KEYBOARD_SET = "KeyboardSet";
@@ -55,56 +53,67 @@
     public final KeyboardId mSymbolsId;
     public final KeyboardId mSymbolsShiftedId;
 
-    KeyboardSet(Builder builder) {
-        mAlphabetId = builder.getKeyboardId(false, false);
-        mSymbolsId = builder.getKeyboardId(true, false);
-        mSymbolsShiftedId = builder.getKeyboardId(true, true);
+    KeyboardSet(Params params) {
+        mAlphabetId = Builder.getKeyboardId(false, false, params);
+        mSymbolsId = Builder.getKeyboardId(true, false, params);
+        mSymbolsShiftedId = Builder.getKeyboardId(true, true, params);
+    }
+
+    private static class Params {
+        int mMode;
+        int mInputTypes;
+        int mImeOptions;
+        boolean mSettingsKeyEnabled;
+        boolean mVoiceKeyEnabled;
+        boolean mVoiceKeyOnMain;
+        boolean mNoSettingsKey;
+        Locale mLocale;
+        int mOrientation;
+        int mWidth;
+        final HashMap<Integer, Integer> mElementKeyboards =
+                new HashMap<Integer, Integer>();
+
+        Params() {}
     }
 
     public static class Builder {
         private final Resources mResources;
-        private final EditorInfo mEditorInfo;
 
-        private final HashMap<Integer, Integer> mElementKeyboards =
-                new HashMap<Integer, Integer>();
-
-        private final int mMode;
-        private final boolean mSettingsKeyEnabled;
-        private final boolean mVoiceKeyEnabled;
-        private final boolean mVoiceKeyOnMain;
-        private final boolean mNoSettingsKey;
-        private final Locale mLocale;
-        private final Configuration mConf;
-        private final DisplayMetrics mMetrics;
+        private final Params mParams = new Params();
 
         public Builder(Context context, EditorInfo editorInfo, SettingsValues settingsValues) {
             mResources = context.getResources();
-            mEditorInfo = editorInfo;
             final SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance();
             final String packageName = context.getPackageName();
+            final Params params = mParams;
 
-            mMode = Utils.getKeyboardMode(mEditorInfo);
-            mSettingsKeyEnabled = settingsValues.isSettingsKeyEnabled();
+            params.mMode = Utils.getKeyboardMode(editorInfo);
+            if (editorInfo != null) {
+                params.mInputTypes = editorInfo.inputType;
+                params.mImeOptions = editorInfo.imeOptions;
+            }
+            params.mSettingsKeyEnabled = settingsValues.isSettingsKeyEnabled();
             @SuppressWarnings("deprecation")
             final boolean noMicrophone = Utils.inPrivateImeOptions(
                     packageName, LatinIME.IME_OPTION_NO_MICROPHONE, editorInfo)
                     || Utils.inPrivateImeOptions(
                             null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo);
-            mVoiceKeyEnabled = settingsValues.isVoiceKeyEnabled(editorInfo) && !noMicrophone;
-            mVoiceKeyOnMain = settingsValues.isVoiceKeyOnMain();
-            mNoSettingsKey = Utils.inPrivateImeOptions(
+            params.mVoiceKeyEnabled = settingsValues.isVoiceKeyEnabled(editorInfo) && !noMicrophone;
+            params.mVoiceKeyOnMain = settingsValues.isVoiceKeyOnMain();
+            params.mNoSettingsKey = Utils.inPrivateImeOptions(
                     packageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, editorInfo);
             final boolean forceAscii = Utils.inPrivateImeOptions(
                     packageName, LatinIME.IME_OPTION_FORCE_ASCII, editorInfo);
             final boolean asciiCapable = subtypeSwitcher.currentSubtypeContainsExtraValueKey(
                     LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE);
-            mLocale = (forceAscii && !asciiCapable) ? Locale.US : subtypeSwitcher.getInputLocale();
-            mConf = mResources.getConfiguration();
-            mMetrics = mResources.getDisplayMetrics();
+            params.mLocale = (forceAscii && !asciiCapable)
+                    ? Locale.US : subtypeSwitcher.getInputLocale();
+            params.mOrientation = mResources.getConfiguration().orientation;
+            params.mWidth = mResources.getDisplayMetrics().widthPixels;
         }
 
         public KeyboardSet build() {
-            final Locale savedLocale = LocaleUtils.setSystemLocale(mResources, mLocale);
+            final Locale savedLocale = LocaleUtils.setSystemLocale(mResources, mParams.mLocale);
             try {
                 parseKeyboardSet(mResources, R.xml.keyboard_set);
             } catch (Exception e) {
@@ -112,16 +121,18 @@
             } finally {
                 LocaleUtils.setSystemLocale(mResources, savedLocale);
             }
-            return new KeyboardSet(this);
+            return new KeyboardSet(mParams);
         }
 
-        KeyboardId getKeyboardId(boolean isSymbols, boolean isShift) {
-            final int elementState = getElementState(mMode, isSymbols, isShift);
-            final int xmlId = mElementKeyboards.get(elementState);
-            final boolean hasShortcutKey = mVoiceKeyEnabled && (isSymbols != mVoiceKeyOnMain);
-            return new KeyboardId(xmlId, elementState, mLocale, mConf.orientation,
-                    mMetrics.widthPixels, mMode, mEditorInfo, mSettingsKeyEnabled, mNoSettingsKey,
-                    mVoiceKeyEnabled, hasShortcutKey);
+        static KeyboardId getKeyboardId(boolean isSymbols, boolean isShift, Params params) {
+            final int elementState = getElementState(params.mMode, isSymbols, isShift);
+            final int xmlId = params.mElementKeyboards.get(elementState);
+            final boolean hasShortcutKey = params.mVoiceKeyEnabled
+                    && (isSymbols != params.mVoiceKeyOnMain);
+            return new KeyboardId(xmlId, elementState, params.mLocale, params.mOrientation,
+                    params.mWidth, params.mMode, params.mInputTypes, params.mImeOptions,
+                    params.mSettingsKeyEnabled, params.mNoSettingsKey, params.mVoiceKeyEnabled,
+                    hasShortcutKey);
         }
 
         private static int getElementState(int mode, boolean isSymbols, boolean isShift) {
@@ -198,7 +209,7 @@
                         R.styleable.KeyboardSet_Element_elementName, 0);
                 final int elementKeyboard = a.getResourceId(
                         R.styleable.KeyboardSet_Element_elementKeyboard, 0);
-                mElementKeyboards.put(elementName, elementKeyboard);
+                mParams.mElementKeyboards.put(elementName, elementKeyboard);
             } finally {
                 a.recycle();
             }
@@ -208,7 +219,8 @@
     public static String parseKeyboardLocale(Resources res, int resId)
             throws XmlPullParserException, IOException {
         final XmlPullParser parser = res.getXml(resId);
-        if (parser == null) return "";
+        if (parser == null)
+            return "";
         int event;
         while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
             if (event == XmlPullParser.START_TAG) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 1f3006d..3d4a6ef 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -166,18 +166,29 @@
                 SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources));
         final boolean localeChanged = (oldKeyboard == null)
                 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
-        mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
+        if (keyboard instanceof LatinKeyboard) {
+            final LatinKeyboard latinKeyboard = (LatinKeyboard)keyboard;
+            latinKeyboard.updateAutoCorrectionState(mIsAutoCorrectionActive);
+            // If the cached keyboard had been switched to another keyboard while the language was
+            // displayed on its spacebar, it might have had arbitrary text fade factor. In such
+            // case, we should reset the text fade factor. It is also applicable to shortcut key.
+            latinKeyboard.updateSpacebarLanguage(0.0f,
+                    Utils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */),
+                    mSubtypeSwitcher.needsToDisplayLanguage(latinKeyboard.mId.mLocale));
+            latinKeyboard.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
+        }
         updateShiftState();
+        mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
     }
 
     // TODO: Move this method to KeyboardSet.
-    private LatinKeyboard getKeyboard(KeyboardId id) {
+    private LatinKeyboard getKeyboard(Context context, KeyboardId id) {
         final SoftReference<LatinKeyboard> ref = mKeyboardCache.get(id);
         LatinKeyboard keyboard = (ref == null) ? null : ref.get();
         if (keyboard == null) {
             final Locale savedLocale = LocaleUtils.setSystemLocale(mResources, id.mLocale);
             try {
-                final LatinKeyboard.Builder builder = new LatinKeyboard.Builder(mThemeContext);
+                final LatinKeyboard.Builder builder = new LatinKeyboard.Builder(context);
                 builder.load(id);
                 builder.setTouchPositionCorrectionEnabled(
                         mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(
@@ -198,14 +209,8 @@
                     + " theme=" + themeName(keyboard.mThemeId));
         }
 
-        keyboard.onAutoCorrectionStateChanged(mIsAutoCorrectionActive);
         keyboard.setShiftLocked(false);
         keyboard.setShifted(false);
-        // If the cached keyboard had been switched to another keyboard while the language was
-        // displayed on its spacebar, it might have had arbitrary text fade factor. In such case,
-        // we should reset the text fade factor. It is also applicable to shortcut key.
-        keyboard.setSpacebarTextFadeFactor(0.0f, null);
-        keyboard.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady(), null);
         return keyboard;
     }
 
@@ -338,19 +343,19 @@
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
     public void setSymbolsKeyboard() {
-        setKeyboard(getKeyboard(mKeyboardSet.mSymbolsId));
+        setKeyboard(getKeyboard(mThemeContext, mKeyboardSet.mSymbolsId));
     }
 
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
     public void setAlphabetKeyboard() {
-        setKeyboard(getKeyboard(mKeyboardSet.mAlphabetId));
+        setKeyboard(getKeyboard(mThemeContext, mKeyboardSet.mAlphabetId));
     }
 
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
     public void setSymbolsShiftedKeyboard() {
-        final Keyboard keyboard = getKeyboard(mKeyboardSet.mSymbolsShiftedId);
+        final Keyboard keyboard = getKeyboard(mThemeContext, mKeyboardSet.mSymbolsShiftedId);
         setKeyboard(keyboard);
         // TODO: Remove this logic once we introduce initial keyboard shift state attribute.
         // Symbol shift keyboard may have a shift key that has a caps lock style indicator (a.k.a.
@@ -451,12 +456,22 @@
         }
     }
 
+    public void onNetworkStateChanged() {
+        final LatinKeyboard keyboard = getLatinKeyboard();
+        if (keyboard == null) return;
+        final Key updatedKey = keyboard.updateShortcutKey(
+                SubtypeSwitcher.getInstance().isShortcutImeReady());
+        if (updatedKey != null && mKeyboardView != null) {
+            mKeyboardView.invalidateKey(updatedKey);
+        }
+    }
+
     public void onAutoCorrectionStateChanged(boolean isAutoCorrection) {
         if (mIsAutoCorrectionActive != isAutoCorrection) {
             mIsAutoCorrectionActive = isAutoCorrection;
             final LatinKeyboard keyboard = getLatinKeyboard();
             if (keyboard != null && keyboard.needsAutoCorrectionSpacebarLed()) {
-                final Key invalidatedKey = keyboard.onAutoCorrectionStateChanged(isAutoCorrection);
+                final Key invalidatedKey = keyboard.updateAutoCorrectionState(isAutoCorrection);
                 final LatinKeyboardView keyboardView = getKeyboardView();
                 if (keyboardView != null)
                     keyboardView.invalidateKey(invalidatedKey);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index d2741ed..ebc0d82 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -889,7 +889,7 @@
             }
             previewText.setText(mKeyboard.adjustLabelCase(key.mLabel));
         } else {
-            final Drawable previewIcon = key.getPreviewIcon();
+            final Drawable previewIcon = key.mPreviewIcon;
             previewText.setCompoundDrawables(null, null, null,
                    previewIcon != null ? previewIcon : key.getIcon());
             previewText.setText(null);
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
index a9fcd9a..abb96f0 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
@@ -31,11 +31,9 @@
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SubtypeSwitcher;
 import com.android.inputmethod.latin.Utils;
 
 import java.util.Arrays;
@@ -48,7 +46,6 @@
 
     private final Resources mRes;
     private final Theme mTheme;
-    private final SubtypeSwitcher mSubtypeSwitcher = SubtypeSwitcher.getInstance();
 
     /* Space key and its icons, drawables and colors. */
     private final Key mSpaceKey;
@@ -57,11 +54,15 @@
     private final Drawable mAutoCorrectionSpacebarLedIcon;
     private final int mSpacebarTextColor;
     private final int mSpacebarTextShadowColor;
-    private float mSpacebarTextFadeFactor = 0.0f;
     private final HashMap<Integer, BitmapDrawable> mSpaceDrawableCache =
             new HashMap<Integer, BitmapDrawable>();
     private final boolean mIsSpacebarTriggeringPopupByLongPress;
 
+    private boolean mAutoCorrectionSpacebarLedOn;
+    private boolean mMultipleEnabledIMEsOrSubtypes;
+    private boolean mNeedsToDisplayLanguage;
+    private float mSpacebarTextFadeFactor = 0.0f;
+
     /* Shortcut key and its icons if available */
     private final Key mShortcutKey;
     private final Drawable mEnabledShortcutIcon;
@@ -140,11 +141,13 @@
         }
     }
 
-    public void setSpacebarTextFadeFactor(float fadeFactor, KeyboardView view) {
+    public Key updateSpacebarLanguage(float fadeFactor, boolean multipleEnabledIMEsOrSubtypes,
+            boolean needsToDisplayLanguage) {
         mSpacebarTextFadeFactor = fadeFactor;
-        updateSpacebarForLocale(false);
-        if (view != null)
-            view.invalidateKey(mSpaceKey);
+        mMultipleEnabledIMEsOrSubtypes = multipleEnabledIMEsOrSubtypes;
+        mNeedsToDisplayLanguage = needsToDisplayLanguage;
+        updateSpacebarIcon();
+        return mSpaceKey;
     }
 
     private static int getSpacebarTextColor(int color, float fadeFactor) {
@@ -153,13 +156,12 @@
         return newColor;
     }
 
-    public void updateShortcutKey(boolean available, KeyboardView view) {
+    public Key updateShortcutKey(boolean available) {
         if (mShortcutKey == null)
-            return;
+            return null;
         mShortcutKey.setEnabled(available);
         mShortcutKey.setIcon(available ? mEnabledShortcutIcon : mDisabledShortcutIcon);
-        if (view != null)
-            view.invalidateKey(mShortcutKey);
+        return mShortcutKey;
     }
 
     public boolean needsAutoCorrectionSpacebarLed() {
@@ -169,8 +171,9 @@
     /**
      * @return a key which should be invalidated.
      */
-    public Key onAutoCorrectionStateChanged(boolean isAutoCorrection) {
-        updateSpacebarForLocale(isAutoCorrection);
+    public Key updateAutoCorrectionState(boolean isAutoCorrection) {
+        mAutoCorrectionSpacebarLedOn = isAutoCorrection;
+        updateSpacebarIcon();
         return mSpaceKey;
     }
 
@@ -183,19 +186,15 @@
         return label;
     }
 
-    private void updateSpacebarForLocale(boolean isAutoCorrection) {
+    private void updateSpacebarIcon() {
         if (mSpaceKey == null) return;
-        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
-        if (imm == null) return;
-        // The "..." popup hint for triggering something by a long-pressing the spacebar
         final boolean shouldShowInputMethodPicker = mIsSpacebarTriggeringPopupByLongPress
-                && Utils.hasMultipleEnabledIMEsOrSubtypes(imm, true /* include aux subtypes */);
+                && mMultipleEnabledIMEsOrSubtypes;
         mSpaceKey.setNeedsSpecialPopupHint(shouldShowInputMethodPicker);
-        // If application locales are explicitly selected.
-        if (mSubtypeSwitcher.needsToDisplayLanguage(mId.mLocale)) {
-            mSpaceKey.setIcon(getSpaceDrawable(mId.mLocale, isAutoCorrection));
-        } else if (isAutoCorrection) {
-            mSpaceKey.setIcon(getSpaceDrawable(null, true));
+        if (mNeedsToDisplayLanguage) {
+            mSpaceKey.setIcon(getSpaceDrawable(mId.mLocale));
+        } else if (mAutoCorrectionSpacebarLedOn) {
+            mSpaceKey.setIcon(getSpaceDrawable(null));
         } else {
             mSpaceKey.setIcon(mSpaceIcon);
         }
@@ -245,15 +244,15 @@
         return language;
     }
 
-    private BitmapDrawable getSpaceDrawable(Locale locale, boolean isAutoCorrection) {
+    private BitmapDrawable getSpaceDrawable(Locale locale) {
         final Integer hashCode = Arrays.hashCode(
-                new Object[] { locale, isAutoCorrection, mSpacebarTextFadeFactor });
+                new Object[] { locale, mAutoCorrectionSpacebarLedOn, mSpacebarTextFadeFactor });
         final BitmapDrawable cached = mSpaceDrawableCache.get(hashCode);
         if (cached != null) {
             return cached;
         }
         final BitmapDrawable drawable = new BitmapDrawable(mRes, drawSpacebar(
-                locale, isAutoCorrection, mSpacebarTextFadeFactor));
+                locale, mAutoCorrectionSpacebarLedOn, mSpacebarTextFadeFactor));
         mSpaceDrawableCache.put(hashCode, drawable);
         return drawable;
     }
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index e56f2ea..c09541f 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -21,6 +21,7 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.os.Message;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.GestureDetector;
@@ -38,9 +39,11 @@
 import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
 import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
 import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
 
 import java.util.WeakHashMap;
 
@@ -62,6 +65,9 @@
     // Timing constants
     private final int mKeyRepeatInterval;
 
+    // TODO: Kill process when the usability study mode was changed.
+    private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy;
+
     // Mini keyboard
     private PopupWindow mMoreKeysWindow;
     private MoreKeysPanel mMoreKeysPanel;
@@ -206,7 +212,7 @@
                     // Detected a double tap on shift key. If we are in the ignoring double tap
                     // mode, it means we have already turned off caps lock in
                     // {@link KeyboardSwitcher#onReleaseShift} .
-                    onDoubleTapShiftKey(tracker, mKeyTimerHandler.isIgnoringDoubleTap());
+                    onDoubleTapShiftKey(mKeyTimerHandler.isIgnoringDoubleTap());
                     return true;
                 }
                 // Otherwise these events should not be handled as double tap.
@@ -342,8 +348,7 @@
         return onLongPress(parentKey, tracker);
     }
 
-    private void onDoubleTapShiftKey(@SuppressWarnings("unused") PointerTracker tracker,
-            final boolean ignore) {
+    private void onDoubleTapShiftKey(final boolean ignore) {
         // When shift key is double tapped, the first tap is correctly processed as usual tap. And
         // the second tap is treated as this double tap event, so that we need not mark tracker
         // calling setAlreadyProcessed() nor remove the tracker from mPointerQueue.
@@ -374,15 +379,6 @@
         return miniKeyboardView;
     }
 
-    public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboard oldKeyboard) {
-        final Keyboard keyboard = getKeyboard();
-        // We should not set text fade factor to the keyboard which does not display the language on
-        // its spacebar.
-        if (keyboard instanceof LatinKeyboard && keyboard == oldKeyboard) {
-            ((LatinKeyboard)keyboard).setSpacebarTextFadeFactor(fadeFactor, this);
-        }
-    }
-
     /**
      * Called when a key is long pressed. By default this will open mini keyboard associated
      * with this key.
@@ -523,6 +519,30 @@
             x = (int)me.getX(index);
             y = (int)me.getY(index);
         }
+        if (ENABLE_USABILITY_STUDY_LOG) {
+            final String eventTag;
+            switch (action) {
+                case MotionEvent.ACTION_UP:
+                    eventTag = "[Up]";
+                    break;
+                case MotionEvent.ACTION_DOWN:
+                    eventTag = "[Down]";
+                    break;
+                case MotionEvent.ACTION_POINTER_UP:
+                    eventTag = "[PointerUp]";
+                    break;
+                case MotionEvent.ACTION_POINTER_DOWN:
+                    eventTag = "[PointerDown]";
+                    break;
+                default:
+                    eventTag = "[Action" + action + "]";
+                    break;
+            }
+            if (!TextUtils.isEmpty(eventTag)) {
+                UsabilityStudyLogUtils.getInstance().write(
+                        eventTag + eventTime + "," + id + "," + x + "," + y + "\t\t");
+            }
+        }
 
         if (mKeyTimerHandler.isInKeyRepeat()) {
             final PointerTracker tracker = getPointerTracker(id);
@@ -579,6 +599,10 @@
                     py = (int)me.getY(i);
                 }
                 tracker.onMoveEvent(px, py, eventTime);
+                if (ENABLE_USABILITY_STUDY_LOG) {
+                    UsabilityStudyLogUtils.getInstance().write("[Move]"  + eventTime + ","
+                            + me.getPointerId(i) + "," + px + "," + py + "\t\t");
+                }
             }
         } else {
             getPointerTracker(id).processMotionEvent(action, x, y, eventTime, this);
@@ -633,9 +657,8 @@
     @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
-            final PointerTracker tracker = getPointerTracker(0);
             return AccessibleKeyboardViewProxy.getInstance().dispatchPopulateAccessibilityEvent(
-                    event, tracker) || super.dispatchPopulateAccessibilityEvent(event);
+                    event) || super.dispatchPopulateAccessibilityEvent(event);
         }
 
         return super.dispatchPopulateAccessibilityEvent(event);
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 778aac3..1a636dc 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -104,6 +104,20 @@
         final int[] keyWidths = new int[keyCount];
         final int[] keyHeights = new int[keyCount];
         final int[] keyCharCodes = new int[keyCount];
+        final float[] sweetSpotCenterXs;
+        final float[] sweetSpotCenterYs;
+        final float[] sweetSpotRadii;
+        final boolean calculateSweetSpotParams;
+        if (touchPositionCorrection != null && touchPositionCorrection.isValid()) {
+            sweetSpotCenterXs = new float[keyCount];
+            sweetSpotCenterYs = new float[keyCount];
+            sweetSpotRadii = new float[keyCount];
+            calculateSweetSpotParams = true;
+        } else {
+            sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null;
+            calculateSweetSpotParams = false;
+        }
+
         for (int i = 0; i < keyCount; ++i) {
             final Key key = keys.get(i);
             keyXCoordinates[i] = key.mX;
@@ -111,18 +125,23 @@
             keyWidths[i] = key.mWidth;
             keyHeights[i] = key.mHeight;
             keyCharCodes[i] = key.mCode;
-        }
-
-        float[] sweetSpotCenterXs = null;
-        float[] sweetSpotCenterYs = null;
-        float[] sweetSpotRadii = null;
-
-        if (touchPositionCorrection != null && touchPositionCorrection.isValid()) {
-            sweetSpotCenterXs = new float[keyCount];
-            sweetSpotCenterYs = new float[keyCount];
-            sweetSpotRadii = new float[keyCount];
-            calculateSweetSpot(keys, touchPositionCorrection,
-                    sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
+            if (calculateSweetSpotParams) {
+                final Rect hitBox = key.mHitBox;
+                final int row = hitBox.top / mKeyHeight;
+                if (row < touchPositionCorrection.mRadii.length) {
+                    final float hitBoxCenterX = (hitBox.left + hitBox.right) * 0.5f;
+                    final float hitBoxCenterY = (hitBox.top + hitBox.bottom) * 0.5f;
+                    final float hitBoxWidth = hitBox.right - hitBox.left;
+                    final float hitBoxHeight = hitBox.bottom - hitBox.top;
+                    final float x = touchPositionCorrection.mXs[row];
+                    final float y = touchPositionCorrection.mYs[row];
+                    final float radius = touchPositionCorrection.mRadii[row];
+                    sweetSpotCenterXs[i] = hitBoxCenterX + x * hitBoxWidth;
+                    sweetSpotCenterYs[i] = hitBoxCenterY + y * hitBoxHeight;
+                    sweetSpotRadii[i] = radius * (float)Math.sqrt(
+                            hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
+                }
+            }
         }
 
         mNativeProximityInfo = setProximityInfoNative(MAX_PROXIMITY_CHARS_SIZE,
@@ -131,32 +150,6 @@
                 sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
     }
 
-    private void calculateSweetSpot(List<Key> keys, TouchPositionCorrection touchPositionCorrection,
-            float[] sweetSpotCenterXs, float[] sweetSpotCenterYs, float[] sweetSpotRadii) {
-        final int keyCount = keys.size();
-        final float[] xs = touchPositionCorrection.mXs;
-        final float[] ys = touchPositionCorrection.mYs;
-        final float[] radii = touchPositionCorrection.mRadii;
-        for (int i = 0; i < keyCount; ++i) {
-            final Key key = keys.get(i);
-            final Rect hitBox = key.mHitBox;
-            final int row = hitBox.top / mKeyHeight;
-            if (row < radii.length) {
-                final float hitBoxCenterX = (hitBox.left + hitBox.right) * 0.5f;
-                final float hitBoxCenterY = (hitBox.top + hitBox.bottom) * 0.5f;
-                final float hitBoxWidth = hitBox.right - hitBox.left;
-                final float hitBoxHeight = hitBox.bottom - hitBox.top;
-                final float x = xs[row];
-                final float y = ys[row];
-                final float radius = radii[row];
-                sweetSpotCenterXs[i] = hitBoxCenterX + x * hitBoxWidth;
-                sweetSpotCenterYs[i] = hitBoxCenterY + y * hitBoxHeight;
-                sweetSpotRadii[i] = radius
-                        * (float)Math.sqrt(hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
-            }
-        }
-    }
-
     public long getNativeProximityInfo() {
         return mNativeProximityInfo;
     }
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index cad69bb..7eec8e2 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -51,6 +51,7 @@
     private Object mUpdatingLock = new Object();
 
     private static class Node {
+        Node() {}
         char mCode;
         int mFrequency;
         boolean mTerminal;
@@ -547,6 +548,7 @@
     }
 
     private class LoadDictionaryTask extends Thread {
+        LoadDictionaryTask() {}
         @Override
         public void run() {
             loadDictionaryAsync();
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
new file mode 100644
index 0000000..28ce916
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.view.inputmethod.EditorInfo;
+
+/**
+ * Class to hold attributes of the input field.
+ */
+public class InputAttributes {
+    private final String TAG = InputAttributes.class.getSimpleName();
+
+    // TODO: make these final
+    public boolean mInsertSpaceOnPickSuggestionManually;
+    public boolean mInputTypeNoAutoCorrect;
+    public boolean mIsSettingsSuggestionStripOn;
+    public boolean mApplicationSpecifiedCompletionOn;
+
+    public InputAttributes(final EditorInfo editorInfo) {
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 98fea1b..71db455 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -67,6 +67,7 @@
 import com.android.inputmethod.keyboard.KeyboardView;
 import com.android.inputmethod.keyboard.LatinKeyboard;
 import com.android.inputmethod.keyboard.LatinKeyboardView;
+import com.android.inputmethod.latin.suggestions.SuggestionsView;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -195,12 +196,7 @@
     private UserUnigramDictionary mUserUnigramDictionary;
     private boolean mIsUserDictionaryAvailable;
 
-    // TODO: Create an inner class to group options and pseudo-options to improve readability.
-    // These variables are initialized according to the {@link EditorInfo#inputType}.
-    private boolean mInsertSpaceOnPickSuggestionManually;
-    private boolean mInputTypeNoAutoCorrect;
-    private boolean mIsSettingsSuggestionStripOn;
-    private boolean mApplicationSpecifiedCompletionOn;
+    private InputAttributes mInputAttributes;
 
     private WordComposer mWordComposer = new WordComposer();
     private boolean mHasUncommittedTypedChars;
@@ -297,19 +293,15 @@
                         || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked()));
                 break;
             case MSG_FADEOUT_LANGUAGE_ON_SPACEBAR:
-                if (inputView != null) {
-                    inputView.setSpacebarTextFadeFactor(
-                            (1.0f + mFinalFadeoutFactorOfLanguageOnSpacebar) / 2,
-                            (LatinKeyboard)msg.obj);
-                }
+                setSpacebarTextFadeFactor(inputView,
+                        (1.0f + mFinalFadeoutFactorOfLanguageOnSpacebar) / 2,
+                        (LatinKeyboard)msg.obj);
                 sendMessageDelayed(obtainMessage(MSG_DISMISS_LANGUAGE_ON_SPACEBAR, msg.obj),
                         mDurationOfFadeoutLanguageOnSpacebar);
                 break;
             case MSG_DISMISS_LANGUAGE_ON_SPACEBAR:
-                if (inputView != null) {
-                    inputView.setSpacebarTextFadeFactor(mFinalFadeoutFactorOfLanguageOnSpacebar,
-                            (LatinKeyboard)msg.obj);
-                }
+                setSpacebarTextFadeFactor(inputView, mFinalFadeoutFactorOfLanguageOnSpacebar,
+                        (LatinKeyboard)msg.obj);
                 break;
             }
         }
@@ -349,6 +341,19 @@
             sendMessage(obtainMessage(MSG_VOICE_RESULTS));
         }
 
+        private static void setSpacebarTextFadeFactor(LatinKeyboardView inputView,
+                float fadeFactor, LatinKeyboard oldKeyboard) {
+            if (inputView == null) return;
+            final Keyboard keyboard = inputView.getKeyboard();
+            if (keyboard instanceof LatinKeyboard && keyboard == oldKeyboard) {
+                final Key updatedKey = ((LatinKeyboard)keyboard).updateSpacebarLanguage(
+                        fadeFactor,
+                        Utils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */),
+                        SubtypeSwitcher.getInstance().needsToDisplayLanguage(keyboard.mId.mLocale));
+                inputView.invalidateKey(updatedKey);
+            }
+        }
+
         public void startDisplayLanguageOnSpacebar(boolean localeChanged) {
             final LatinIME latinIme = getOuterInstance();
             removeMessages(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR);
@@ -361,9 +366,9 @@
                         || mDelayBeforeFadeoutLanguageOnSpacebar < 0;
                 // The language is never displayed when the delay is zero.
                 if (mDelayBeforeFadeoutLanguageOnSpacebar != 0) {
-                    inputView.setSpacebarTextFadeFactor(needsToDisplayLanguage ? 1.0f
-                            : mFinalFadeoutFactorOfLanguageOnSpacebar,
-                            keyboard);
+                    setSpacebarTextFadeFactor(inputView,
+                            needsToDisplayLanguage ? 1.0f : mFinalFadeoutFactorOfLanguageOnSpacebar,
+                                    keyboard);
                 }
                 // The fadeout animation will start when the delay is positive.
                 if (localeChanged && mDelayBeforeFadeoutLanguageOnSpacebar > 0) {
@@ -489,7 +494,7 @@
         InputMethodManagerCompatWrapper.init(this);
         SubtypeSwitcher.init(this);
         KeyboardSwitcher.init(this, prefs);
-        AccessibilityUtils.init(this, prefs);
+        AccessibilityUtils.init(this);
 
         super.onCreate();
 
@@ -505,6 +510,8 @@
 
         loadSettings();
 
+        // TODO: remove the following when it's not needed by updateCorrectionMode() any more
+        mInputAttributes = new InputAttributes(null);
         Utils.GCUtils.getInstance().reset();
         boolean tryGC = true;
         for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
@@ -758,7 +765,7 @@
 
         loadSettings();
         updateCorrectionMode();
-        updateSuggestionVisibility(mPrefs, mResources);
+        updateSuggestionVisibility(mResources);
 
         if (mSuggest != null && mSettingsValues.mAutoCorrectEnabled) {
             mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
@@ -788,71 +795,88 @@
         if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
     }
 
-    private void initializeInputAttributes(EditorInfo editorInfo) {
-        if (editorInfo == null)
-            return;
-        final int inputType = editorInfo.inputType;
-        if (inputType == InputType.TYPE_NULL) {
-            // TODO: We should honor TYPE_NULL specification.
-            Log.i(TAG, "InputType.TYPE_NULL is specified");
-        }
-        final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
-        final int variation = inputType & InputType.TYPE_MASK_VARIATION;
-        if (inputClass == 0) {
-            Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x imeOptions=0x%08x",
-                    inputType, editorInfo.imeOptions));
-        }
+    private void initializeInputAttributes(final EditorInfo editorInfo) {
+        mInputAttributes = new InputAttributes(editorInfo);
 
-        mInsertSpaceOnPickSuggestionManually = false;
-        mInputTypeNoAutoCorrect = false;
-        mIsSettingsSuggestionStripOn = false;
-        mApplicationSpecifiedCompletionOn = false;
-        mApplicationSpecifiedCompletions = null;
+        final boolean insertSpaceOnPickSuggestionManually;
+        boolean inputTypeNoAutoCorrect = false;
+        boolean isSettingsSuggestionStripOn = false;
+        boolean applicationSpecifiedCompletionOn = false;
 
-        if (inputClass == InputType.TYPE_CLASS_TEXT) {
-            mIsSettingsSuggestionStripOn = true;
+        if (editorInfo == null || editorInfo.inputType != InputType.TYPE_CLASS_TEXT) {
+            if (editorInfo.inputType == InputType.TYPE_NULL) {
+                // TODO: We should honor TYPE_NULL specification.
+                Log.i(TAG, "InputType.TYPE_NULL is specified");
+            }
+            mApplicationSpecifiedCompletions = null;
+            insertSpaceOnPickSuggestionManually = false;
+        } else {
+            final int inputType = editorInfo.inputType;
+            final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
+            final int variation = inputType & InputType.TYPE_MASK_VARIATION;
+            if (inputClass == 0) {
+                Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x"
+                        + " imeOptions=0x%08x",
+                        inputType, editorInfo.imeOptions));
+            }
+            final boolean flagNoSuggestions =
+                    0 != (inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+            final boolean flagMultiLine =
+                    0 != (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE);
+            final boolean flagAutoCorrect =
+                    0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
+            final boolean flagAutoComplete =
+                    0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
+
+            mApplicationSpecifiedCompletions = null;
+
+            isSettingsSuggestionStripOn = true;
             // Make sure that passwords are not displayed in {@link SuggestionsView}.
             if (InputTypeCompatUtils.isPasswordInputType(inputType)
                     || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)) {
-                mIsSettingsSuggestionStripOn = false;
+                isSettingsSuggestionStripOn = false;
             }
             if (InputTypeCompatUtils.isEmailVariation(variation)
                     || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) {
                 // The point in turning this off is that we don't want to insert a space after
                 // a name when filling a form: we can't delete trailing spaces when changing fields
-                mInsertSpaceOnPickSuggestionManually = false;
+                insertSpaceOnPickSuggestionManually = false;
             } else {
-                mInsertSpaceOnPickSuggestionManually = true;
+                insertSpaceOnPickSuggestionManually = true;
             }
             if (InputTypeCompatUtils.isEmailVariation(variation)) {
-                mIsSettingsSuggestionStripOn = false;
+                isSettingsSuggestionStripOn = false;
             } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
-                mIsSettingsSuggestionStripOn = false;
+                isSettingsSuggestionStripOn = false;
             } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
-                mIsSettingsSuggestionStripOn = false;
+                isSettingsSuggestionStripOn = false;
             } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
                 // If it's a browser edit field and auto correct is not ON explicitly, then
                 // disable auto correction, but keep suggestions on.
-                if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) {
-                    mInputTypeNoAutoCorrect = true;
+                if (!flagAutoCorrect) {
+                    inputTypeNoAutoCorrect = true;
                 }
             }
 
             // If NO_SUGGESTIONS is set, don't do prediction.
-            if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) {
-                mIsSettingsSuggestionStripOn = false;
-                mInputTypeNoAutoCorrect = true;
+            if (flagNoSuggestions) {
+                isSettingsSuggestionStripOn = false;
+                inputTypeNoAutoCorrect = true;
             }
             // If it's not multiline and the autoCorrect flag is not set, then don't correct
-            if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0
-                    && (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
-                mInputTypeNoAutoCorrect = true;
+            if (!flagAutoCorrect && !flagMultiLine) {
+                inputTypeNoAutoCorrect = true;
             }
-            if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
-                mIsSettingsSuggestionStripOn = false;
-                mApplicationSpecifiedCompletionOn = isFullscreenMode();
+            if (flagAutoComplete) {
+                isSettingsSuggestionStripOn = false;
+                applicationSpecifiedCompletionOn = isFullscreenMode();
             }
         }
+
+        mInputAttributes.mInsertSpaceOnPickSuggestionManually = insertSpaceOnPickSuggestionManually;
+        mInputAttributes.mInputTypeNoAutoCorrect = inputTypeNoAutoCorrect;
+        mInputAttributes.mIsSettingsSuggestionStripOn = isSettingsSuggestionStripOn;
+        mInputAttributes.mApplicationSpecifiedCompletionOn = applicationSpecifiedCompletionOn;
     }
 
     @Override
@@ -1006,7 +1030,7 @@
                 }
             }
         }
-        if (mApplicationSpecifiedCompletionOn) {
+        if (mInputAttributes.mApplicationSpecifiedCompletionOn) {
             mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
             if (applicationSpecifiedCompletions == null) {
                 clearSuggestions();
@@ -1139,6 +1163,7 @@
         if (!mHasUncommittedTypedChars) return;
         mHasUncommittedTypedChars = false;
         final CharSequence typedWord = mWordComposer.getTypedWord();
+        mWordComposer.onCommitWord();
         if (typedWord.length() > 0) {
             if (ic != null) {
                 ic.commitText(typedWord, 1);
@@ -1229,7 +1254,7 @@
         if (isShowingOptionDialog()) return;
         if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) {
             showSubtypeSelectorAndSettings();
-        } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm, false /* exclude aux subtypes */)) {
+        } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(false /* exclude aux subtypes */)) {
             showOptionsMenu();
         } else {
             launchSettings();
@@ -1245,7 +1270,7 @@
         if (isShowingOptionDialog()) return false;
         switch (requestCode) {
         case CODE_SHOW_INPUT_METHOD_PICKER:
-            if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm, true /* include aux subtypes */)) {
+            if (Utils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
                 mImm.showInputMethodPicker();
                 return true;
             }
@@ -1600,7 +1625,7 @@
             // in Italian dov' should not be expanded to dove' because the elision
             // requires the last vowel to be removed.
             final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled
-                    && !mInputTypeNoAutoCorrect;
+                    && !mInputAttributes.mInputTypeNoAutoCorrect;
             if (shouldAutoCorrect && primaryCode != Keyboard.CODE_SINGLE_QUOTE) {
                 commitCurrentAutoCorrection(primaryCode, ic);
             } else {
@@ -1677,7 +1702,7 @@
     }
 
     public boolean isSuggestionsRequested() {
-        return mIsSettingsSuggestionStripOn
+        return mInputAttributes.mIsSettingsSuggestionStripOn
                 && (mCorrectionMode > 0 || isShowingSuggestionsStrip());
     }
 
@@ -1699,7 +1724,7 @@
             return true;
         if (!isShowingSuggestionsStrip())
             return false;
-        if (mApplicationSpecifiedCompletionOn)
+        if (mInputAttributes.mApplicationSpecifiedCompletionOn)
             return true;
         return isSuggestionsRequested();
     }
@@ -1786,7 +1811,8 @@
         final SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(mWordComposer,
                 prevWord, mKeyboardSwitcher.getLatinKeyboard().getProximityInfo(), mCorrectionMode);
 
-        boolean autoCorrectionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection();
+        boolean autoCorrectionAvailable = !mInputAttributes.mInputTypeNoAutoCorrect
+                && mSuggest.hasAutoCorrection();
         final CharSequence typedWord = mWordComposer.getTypedWord();
         // Here, we want to promote a whitelisted word if exists.
         // TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
@@ -1902,7 +1928,8 @@
         if (ic != null) {
             ic.beginBatchEdit();
         }
-        if (mApplicationSpecifiedCompletionOn && mApplicationSpecifiedCompletions != null
+        if (mInputAttributes.mApplicationSpecifiedCompletionOn
+                && mApplicationSpecifiedCompletions != null
                 && index >= 0 && index < mApplicationSpecifiedCompletions.length) {
             if (ic != null) {
                 final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
@@ -1961,7 +1988,7 @@
         LatinImeLogger.logOnManualSuggestion(mWordComposer.getTypedWord().toString(),
                 suggestion.toString(), index, suggestions.mWords);
         // Follow it with a space
-        if (mInsertSpaceOnPickSuggestionManually) {
+        if (mInputAttributes.mInsertSpaceOnPickSuggestionManually) {
             sendMagicSpace();
         }
 
@@ -2023,6 +2050,7 @@
             }
         }
         mHasUncommittedTypedChars = false;
+        mWordComposer.onCommitWord();
     }
 
     private static final WordComposer sEmptyWordComposer = new WordComposer();
@@ -2192,10 +2220,11 @@
             }
         }
         ic.deleteSurroundingText(cancelLength + 1, 0);
-
-        // Re-insert the separator
+        mWordComposer.resumeSuggestionOnKeptWord();
         ic.commitText(mWordComposer.getTypedWord(), 1);
+        // Re-insert the separator
         ic.commitText(separator, 1);
+        mWordComposer.onCommitWord();
         Utils.Stats.onSeparator(separator.charAt(0), WordComposer.NOT_A_COORDINATE,
                 WordComposer.NOT_A_COORDINATE);
         mHandler.cancelUpdateBigramPredictions();
@@ -2224,6 +2253,7 @@
         // restartSuggestionsOnWordBeforeCursorIfAtEndOfWord instead, but retrieving
         // the old WordComposer allows to reuse the actual typed coordinates.
         mHasUncommittedTypedChars = true;
+        mWordComposer.resumeSuggestionOnKeptWord();
         ic.setComposingText(mWordComposer.getTypedWord(), 1);
         mHandler.cancelUpdateBigramPredictions();
         mHandler.postUpdateSuggestions();
@@ -2409,13 +2439,13 @@
     private void updateCorrectionMode() {
         // TODO: cleanup messy flags
         final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled
-                && !mInputTypeNoAutoCorrect;
+                && !mInputAttributes.mInputTypeNoAutoCorrect;
         mCorrectionMode = shouldAutoCorrect ? Suggest.CORRECTION_FULL : Suggest.CORRECTION_NONE;
         mCorrectionMode = (mSettingsValues.mBigramSuggestionEnabled && shouldAutoCorrect)
                 ? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode;
     }
 
-    private void updateSuggestionVisibility(final SharedPreferences prefs, final Resources res) {
+    private void updateSuggestionVisibility(final Resources res) {
         final String suggestionVisiblityStr = mSettingsValues.mShowSuggestionsSetting;
         for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) {
             if (suggestionVisiblityStr.equals(res.getString(visibility))) {
@@ -2508,12 +2538,14 @@
         final Keyboard keyboard = mKeyboardSwitcher.getLatinKeyboard();
         final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1;
         p.println("  Keyboard mode = " + keyboardMode);
-        p.println("  mIsSuggestionsRequested=" + mIsSettingsSuggestionStripOn);
+        p.println("  mIsSuggestionsRequested=" + mInputAttributes.mIsSettingsSuggestionStripOn);
         p.println("  mCorrectionMode=" + mCorrectionMode);
         p.println("  mHasUncommittedTypedChars=" + mHasUncommittedTypedChars);
         p.println("  mAutoCorrectEnabled=" + mSettingsValues.mAutoCorrectEnabled);
-        p.println("  mInsertSpaceOnPickSuggestionManually=" + mInsertSpaceOnPickSuggestionManually);
-        p.println("  mApplicationSpecifiedCompletionOn=" + mApplicationSpecifiedCompletionOn);
+        p.println("  mInsertSpaceOnPickSuggestionManually="
+                + mInputAttributes.mInsertSpaceOnPickSuggestionManually);
+        p.println("  mApplicationSpecifiedCompletionOn="
+                + mInputAttributes.mApplicationSpecifiedCompletionOn);
         p.println("  mSoundOn=" + mSettingsValues.mSoundOn);
         p.println("  mVibrateOn=" + mSettingsValues.mVibrateOn);
         p.println("  mKeyPreviewPopupOn=" + mSettingsValues.mKeyPreviewPopupOn);
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index da5058d..6f1adfe 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -29,6 +29,7 @@
 
     public static boolean sDBG = false;
     public static boolean sVISUALDEBUG = false;
+    public static boolean sUsabilityStudy = false;
 
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 0ad1c15..651d90c 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -52,7 +52,9 @@
     private final String mVoiceMode;
     private final String mAutoCorrectionThresholdRawValue;
     public final String mShowSuggestionsSetting;
+    @SuppressWarnings("unused") // TODO: Use this
     private final boolean mUsabilityStudyMode;
+    @SuppressWarnings("unused") // TODO: Use this
     private final String mKeyPreviewPopupDismissDelayRawValue;
     public final boolean mUseContactsDict;
     // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
@@ -60,7 +62,9 @@
     // Prediction: use bigrams to predict the next word when there is no input for it yet
     public final boolean mBigramPredictionEnabled;
     public final boolean mEnableSuggestionSpanInsertion;
+    @SuppressWarnings("unused") // TODO: Use this
     private final int mVibrationDurationSettingsRawValue;
+    @SuppressWarnings("unused") // TODO: Use this
     private final float mKeypressSoundVolumeRawValue;
 
     // Deduced settings
@@ -111,12 +115,12 @@
                 res.getString(R.string.auto_correction_threshold_mode_index_modest));
         mShowSuggestionsSetting = prefs.getString(Settings.PREF_SHOW_SUGGESTIONS_SETTING,
                 res.getString(R.string.prefs_suggestion_visibility_default_value));
-        mUsabilityStudyMode = getUsabilityStudyMode(prefs, res);
+        mUsabilityStudyMode = getUsabilityStudyMode(prefs);
         mKeyPreviewPopupDismissDelayRawValue = prefs.getString(
                 Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
                 Integer.toString(res.getInteger(R.integer.config_delay_after_preview)));
         mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
-        mAutoCorrectEnabled = isAutoCorrectEnabled(prefs, res, mAutoCorrectionThresholdRawValue);
+        mAutoCorrectEnabled = isAutoCorrectEnabled(res, mAutoCorrectionThresholdRawValue);
         mBigramSuggestionEnabled = mAutoCorrectEnabled
                 && isBigramSuggestionEnabled(prefs, res, mAutoCorrectEnabled);
         mBigramPredictionEnabled = mBigramSuggestionEnabled
@@ -131,7 +135,7 @@
         mKeypressVibrationDuration = getCurrentVibrationDuration(prefs, res);
         mFxVolume = getCurrentKeypressSoundVolume(prefs, res);
         mKeyPreviewPopupDismissDelay = getKeyPreviewPopupDismissDelay(prefs, res);
-        mAutoCorrectionThreshold = getAutoCorrectionThreshold(prefs, res,
+        mAutoCorrectionThreshold = getAutoCorrectionThreshold(res,
                 mAutoCorrectionThresholdRawValue);
         mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff);
         mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain);
@@ -202,8 +206,8 @@
         return mMagicSpaceSwappers.contains(String.valueOf((char)code));
     }
 
-    private static boolean isAutoCorrectEnabled(final SharedPreferences sp,
-            final Resources resources, final String currentAutoCorrectionSetting) {
+    private static boolean isAutoCorrectEnabled(final Resources resources,
+            final String currentAutoCorrectionSetting) {
         final String autoCorrectionOff = resources.getString(
                 R.string.auto_correction_threshold_mode_index_off);
         return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
@@ -244,8 +248,8 @@
                 R.bool.config_default_bigram_prediction));
     }
 
-    private static double getAutoCorrectionThreshold(final SharedPreferences sp,
-            final Resources resources, final String currentAutoCorrectionSetting) {
+    private static double getAutoCorrectionThreshold(final Resources resources,
+            final String currentAutoCorrectionSetting) {
         final String[] autoCorrectionThresholdValues = resources.getStringArray(
                 R.array.auto_correction_threshold_values);
         // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off.
@@ -321,8 +325,7 @@
     }
 
     // Likewise
-    public static boolean getUsabilityStudyMode(final SharedPreferences prefs,
-            final Resources res) {
+    public static boolean getUsabilityStudyMode(final SharedPreferences prefs) {
         // TODO: use mUsabilityStudyMode instead of reading it again here
         return prefs.getBoolean(Settings.PREF_USABILITY_STUDY_MODE, true);
     }
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 8a48620..4211182 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -35,7 +35,6 @@
 import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
 import com.android.inputmethod.deprecated.VoiceProxy;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.LatinKeyboard;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -421,11 +420,7 @@
                 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
         mIsNetworkConnected = !noConnection;
 
-        final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
-        final LatinKeyboard keyboard = switcher.getLatinKeyboard();
-        if (keyboard != null) {
-            keyboard.updateShortcutKey(isShortcutImeReady(), switcher.getKeyboardView());
-        }
+        KeyboardSwitcher.getInstance().onNetworkStateChanged();
     }
 
     //////////////////////////////////
diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
index 8c28324..6d6296e 100644
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionary.java
@@ -18,13 +18,10 @@
 
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.database.ContentObserver;
 import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
 import android.provider.UserDictionary.Words;
 import android.text.TextUtils;
 
diff --git a/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java b/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
index 6af20c7..a7f57ae 100644
--- a/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
@@ -172,7 +172,7 @@
             // Nothing pending? Return
             if (mPendingWrites.isEmpty()) return;
             // Create a background thread to write the pending entries
-            new UpdateDbTask(getContext(), sOpenHelper, mPendingWrites, mLocale).execute();
+            new UpdateDbTask(sOpenHelper, mPendingWrites, mLocale).execute();
             // Create a new map for writing new entries into while the old one is written to db
             mPendingWrites = new HashMap<String, Integer>();
         }
@@ -227,8 +227,8 @@
         private final DatabaseHelper mDbHelper;
         private final String mLocale;
 
-        public UpdateDbTask(@SuppressWarnings("unused") Context context, DatabaseHelper openHelper,
-                HashMap<String, Integer> pendingWrites, String locale) {
+        public UpdateDbTask(DatabaseHelper openHelper, HashMap<String, Integer> pendingWrites,
+                String locale) {
             mMap = pendingWrites;
             mLocale = locale;
             mDbHelper = openHelper;
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 64f4d05..bfa5189 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -118,8 +118,9 @@
     }
 
     public static boolean hasMultipleEnabledIMEsOrSubtypes(
-            final InputMethodManagerCompatWrapper imm,
             final boolean shouldIncludeAuxiliarySubtypes) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) return false;
         final List<InputMethodInfoCompatWrapper> enabledImis = imm.getEnabledInputMethodList();
 
         // Number of the filtered IMEs
@@ -543,13 +544,13 @@
                     final String currentDateTimeString =
                             new SimpleDateFormat("yyyyMMdd-HHmmssZ").format(date);
                     if (mFile == null) {
-                        Log.w(TAG, "No internal log file found.");
+                        Log.w(USABILITY_TAG, "No internal log file found.");
                         return;
                     }
                     if (mIms.checkCallingOrSelfPermission(
                                 android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
                                         != PackageManager.PERMISSION_GRANTED) {
-                        Log.w(TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE");
+                        Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE");
                         return;
                     }
                     mWriter.flush();
@@ -563,20 +564,20 @@
                         src.close();
                         dest.close();
                     } catch (FileNotFoundException e1) {
-                        Log.w(TAG, e1);
+                        Log.w(USABILITY_TAG, e1);
                         return;
                     } catch (IOException e2) {
-                        Log.w(TAG, e2);
+                        Log.w(USABILITY_TAG, e2);
                         return;
                     }
                     if (destFile == null || !destFile.exists()) {
-                        Log.w(TAG, "Dest file doesn't exist.");
+                        Log.w(USABILITY_TAG, "Dest file doesn't exist.");
                         return;
                     }
                     final Intent intent = new Intent(Intent.ACTION_SEND);
                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                     if (LatinImeLogger.sDBG) {
-                        Log.d(TAG, "Destination file URI is " + destFile.toURI());
+                        Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI());
                     }
                     intent.setType("text/plain");
                     intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 8bbcff7..c0204c2 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -32,15 +32,39 @@
     public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE;
     public static final int NOT_A_COORDINATE = -1;
 
-    /**
-     * The list of unicode values for each keystroke (including surrounding keys)
-     */
-    private ArrayList<int[]> mCodes;
+    // Storage for all the info about the current input.
+    private static class CharacterStore {
+        /**
+         * The list of unicode values for each keystroke (including surrounding keys)
+         */
+        ArrayList<int[]> mCodes;
+        int[] mXCoordinates;
+        int[] mYCoordinates;
+        StringBuilder mTypedWord;
+        CharacterStore() {
+            final int N = BinaryDictionary.MAX_WORD_LENGTH;
+            mCodes = new ArrayList<int[]>(N);
+            mTypedWord = new StringBuilder(N);
+            mXCoordinates = new int[N];
+            mYCoordinates = new int[N];
+        }
+        CharacterStore(final CharacterStore that) {
+            mCodes = new ArrayList<int[]>(that.mCodes);
+            mTypedWord = new StringBuilder(that.mTypedWord);
+            mXCoordinates = Arrays.copyOf(that.mXCoordinates, that.mXCoordinates.length);
+            mYCoordinates = Arrays.copyOf(that.mYCoordinates, that.mYCoordinates.length);
+        }
+        void reset() {
+            // For better performance than creating a new character store.
+            mCodes.clear();
+            mTypedWord.setLength(0);
+        }
+    }
 
-    private int[] mXCoordinates;
-    private int[] mYCoordinates;
-
-    private StringBuilder mTypedWord;
+    // The currently typing word. May not be null.
+    private CharacterStore mCurrentWord;
+    // The information being kept for resuming suggestion. May be null if wiped.
+    private CharacterStore mWordKeptForSuggestionResuming;
     // An auto-correction for this word out of the dictionary.
     private CharSequence mAutoCorrection;
 
@@ -56,11 +80,8 @@
     private boolean mIsFirstCharCapitalized;
 
     public WordComposer() {
-        final int N = BinaryDictionary.MAX_WORD_LENGTH;
-        mCodes = new ArrayList<int[]>(N);
-        mTypedWord = new StringBuilder(N);
-        mXCoordinates = new int[N];
-        mYCoordinates = new int[N];
+        mCurrentWord = new CharacterStore();
+        mWordKeptForSuggestionResuming = null;
         mTrailingSingleQuotesCount = 0;
         mAutoCorrection = null;
     }
@@ -70,10 +91,8 @@
     }
 
     public void init(WordComposer source) {
-        mCodes = new ArrayList<int[]>(source.mCodes);
-        mTypedWord = new StringBuilder(source.mTypedWord);
-        mXCoordinates = Arrays.copyOf(source.mXCoordinates, source.mXCoordinates.length);
-        mYCoordinates = Arrays.copyOf(source.mYCoordinates, source.mYCoordinates.length);
+        mCurrentWord = new CharacterStore(source.mCurrentWord);
+        mWordKeptForSuggestionResuming = source.mWordKeptForSuggestionResuming;
         mCapsCount = source.mCapsCount;
         mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
         mAutoCapitalized = source.mAutoCapitalized;
@@ -85,8 +104,8 @@
      * Clear out the keys registered so far.
      */
     public void reset() {
-        mCodes.clear();
-        mTypedWord.setLength(0);
+        mCurrentWord.reset();
+        mWordKeptForSuggestionResuming = null;
         mCapsCount = 0;
         mIsFirstCharCapitalized = false;
         mTrailingSingleQuotesCount = 0;
@@ -98,7 +117,7 @@
      * @return the number of keystrokes
      */
     public final int size() {
-        return mTypedWord.length();
+        return mCurrentWord.mTypedWord.length();
     }
 
     /**
@@ -107,15 +126,15 @@
      * @return the unicode for the pressed and surrounding keys
      */
     public int[] getCodesAt(int index) {
-        return mCodes.get(index);
+        return mCurrentWord.mCodes.get(index);
     }
 
     public int[] getXCoordinates() {
-        return mXCoordinates;
+        return mCurrentWord.mXCoordinates;
     }
 
     public int[] getYCoordinates() {
-        return mYCoordinates;
+        return mCurrentWord.mYCoordinates;
     }
 
     private static boolean isFirstCharCapitalized(int index, int codePoint, boolean previous) {
@@ -130,12 +149,12 @@
      */
     public void add(int primaryCode, int[] codes, int x, int y) {
         final int newIndex = size();
-        mTypedWord.append((char) primaryCode);
+        mCurrentWord.mTypedWord.append((char) primaryCode);
         correctPrimaryJuxtapos(primaryCode, codes);
-        mCodes.add(codes);
+        mCurrentWord.mCodes.add(codes);
         if (newIndex < BinaryDictionary.MAX_WORD_LENGTH) {
-            mXCoordinates[newIndex] = x;
-            mYCoordinates[newIndex] = y;
+            mCurrentWord.mXCoordinates[newIndex] = x;
+            mCurrentWord.mYCoordinates[newIndex] = y;
         }
         mIsFirstCharCapitalized = isFirstCharCapitalized(
                 newIndex, primaryCode, mIsFirstCharCapitalized);
@@ -215,9 +234,9 @@
         final int size = size();
         if (size > 0) {
             final int lastPos = size - 1;
-            char lastChar = mTypedWord.charAt(lastPos);
-            mCodes.remove(lastPos);
-            mTypedWord.deleteCharAt(lastPos);
+            char lastChar = mCurrentWord.mTypedWord.charAt(lastPos);
+            mCurrentWord.mCodes.remove(lastPos);
+            mCurrentWord.mTypedWord.deleteCharAt(lastPos);
             if (Character.isUpperCase(lastChar)) mCapsCount--;
         }
         if (size() == 0) {
@@ -226,8 +245,8 @@
         if (mTrailingSingleQuotesCount > 0) {
             --mTrailingSingleQuotesCount;
         } else {
-            for (int i = mTypedWord.length() - 1; i >= 0; --i) {
-                if (Keyboard.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break;
+            for (int i = mCurrentWord.mTypedWord.length() - 1; i >= 0; --i) {
+                if (Keyboard.CODE_SINGLE_QUOTE != mCurrentWord.mTypedWord.codePointAt(i)) break;
                 ++mTrailingSingleQuotesCount;
             }
         }
@@ -239,7 +258,7 @@
      * @return the word that was typed so far. Never returns null.
      */
     public String getTypedWord() {
-        return mTypedWord.toString();
+        return mCurrentWord.mTypedWord.toString();
     }
 
     /**
@@ -306,4 +325,21 @@
     public CharSequence getAutoCorrectionOrNull() {
         return mAutoCorrection;
     }
+
+    // TODO: pass the information about what was committed and how. Was it an auto-correction?
+    // Was it a completion? Was is what the user typed?
+    public void onCommitWord() {
+        mWordKeptForSuggestionResuming = mCurrentWord;
+        // TODO: improve performance by swapping buffers instead of creating a new object.
+        mCurrentWord = new CharacterStore();
+    }
+
+    public boolean hasWordKeptForSuggestionResuming() {
+        return null != mWordKeptForSuggestionResuming;
+    }
+
+    public void resumeSuggestionOnKeptWord() {
+        mCurrentWord = mWordKeptForSuggestionResuming;
+        mWordKeptForSuggestionResuming = null;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index 9a2bebf..2bc2cfd 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -43,7 +43,7 @@
         return result;
     }
 
-    static class Latin {
+    private static class Latin {
         // This is a map from the code point to the index in the PROXIMITY array.
         // At the time the native code to read the binary dictionary needs the proximity info be
         // passed as a flat array spaced by MAX_PROXIMITY_CHARS_SIZE columns, one for each input
@@ -62,7 +62,7 @@
         // to spell check has been entered with one of the keyboards above. Also, specifically
         // to English, many spelling errors consist of the last vowel of the word being wrong
         // because in English vowels tend to merge with each other in pronunciation.
-        final private static int[] PROXIMITY = {
+        final static int[] PROXIMITY = {
             'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
             'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
             'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
@@ -101,14 +101,14 @@
         static {
             buildProximityIndices(PROXIMITY, INDICES);
         }
-        private static int getIndexOf(int characterCode) {
+        static int getIndexOf(int characterCode) {
             return computeIndex(characterCode, INDICES);
         }
     }
 
-    static class Cyrillic {
+    private static class Cyrillic {
         final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
-        final private static int[] PROXIMITY = {
+        final static int[] PROXIMITY = {
             // TODO: This table is solely based on the keyboard layout. Consult with Russian
             // speakers on commonly misspelled words/letters.
             'ะน', 'ั†', 'ั„', 'ั‹', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
@@ -150,7 +150,7 @@
         static {
             buildProximityIndices(PROXIMITY, INDICES);
         }
-        private static int getIndexOf(int characterCode) {
+        static int getIndexOf(int characterCode) {
             return computeIndex(characterCode, INDICES);
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
similarity index 97%
rename from java/src/com/android/inputmethod/latin/MoreSuggestions.java
rename to java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 86072b6..7f59189 100644
--- a/java/src/com/android/inputmethod/latin/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.suggestions;
 
 import android.content.res.Resources;
 import android.graphics.Paint;
@@ -27,11 +27,12 @@
 import com.android.inputmethod.keyboard.KeyboardView;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
 public class MoreSuggestions extends Keyboard {
-    private static final boolean DBG = LatinImeLogger.sDBG;
-
     public static final int SUGGESTION_CODE_BASE = 1024;
 
     private MoreSuggestions(Builder.MoreSuggestionsParam params) {
@@ -39,6 +40,8 @@
     }
 
     public static class Builder extends KeyboardBuilder<Builder.MoreSuggestionsParam> {
+        private static final boolean DBG = LatinImeLogger.sDBG;
+
         private final MoreSuggestionsView mPaneView;
         private SuggestedWords mSuggestions;
         private int mFromPos;
diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
similarity index 98%
rename from java/src/com/android/inputmethod/latin/MoreSuggestionsView.java
rename to java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index c61dd63..b5f67ac 100644
--- a/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.suggestions;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -34,6 +34,7 @@
 import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
 import com.android.inputmethod.keyboard.PointerTracker.KeyEventHandler;
 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.latin.R;
 
 /**
  * A view that renders a virtual {@link MoreSuggestions}. It handles rendering of keys and detecting
diff --git a/java/src/com/android/inputmethod/latin/SuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
similarity index 88%
rename from java/src/com/android/inputmethod/latin/SuggestionsView.java
rename to java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
index 10f5ec9..40d7826 100644
--- a/java/src/com/android/inputmethod/latin/SuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.suggestions;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -57,7 +57,12 @@
 import com.android.inputmethod.keyboard.KeyboardView;
 import com.android.inputmethod.keyboard.MoreKeysPanel;
 import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.Utils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -72,7 +77,7 @@
     // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
     public static final int MAX_SUGGESTIONS = 18;
 
-    private static final boolean DBG = LatinImeLogger.sDBG;
+    static final boolean DBG = LatinImeLogger.sDBG;
 
     private final ViewGroup mSuggestionsStrip;
     private KeyboardView mKeyboardView;
@@ -100,8 +105,6 @@
     private static class UiHandler extends StaticInnerHandlerWrapper<SuggestionsView> {
         private static final int MSG_HIDE_PREVIEW = 0;
 
-        private static final long DELAY_HIDE_PREVIEW = 1300;
-
         public UiHandler(SuggestionsView outerInstance) {
             super(outerInstance);
         }
@@ -116,11 +119,6 @@
             }
         }
 
-        public void postHidePreview() {
-            cancelHidePreview();
-            sendMessageDelayed(obtainMessage(MSG_HIDE_PREVIEW), DELAY_HIDE_PREVIEW);
-        }
-
         public void cancelHidePreview() {
             removeMessages(MSG_HIDE_PREVIEW);
         }
@@ -148,6 +146,7 @@
         private final List<View> mDividers;
         private final List<TextView> mInfos;
 
+        private final int mColorValidTypedWord;
         private final int mColorTypedWord;
         private final int mColorAutoCorrect;
         private final int mColorSuggested;
@@ -191,6 +190,8 @@
             final TypedArray a = context.obtainStyledAttributes(
                     attrs, R.styleable.SuggestionsView, defStyle, R.style.SuggestionsViewStyle);
             mSuggestionStripOption = a.getInt(R.styleable.SuggestionsView_suggestionStripOption, 0);
+            final float alphaValidTypedWord = getPercent(a,
+                    R.styleable.SuggestionsView_alphaValidTypedWord, 100);
             final float alphaTypedWord = getPercent(a,
                     R.styleable.SuggestionsView_alphaTypedWord, 100);
             final float alphaAutoCorrect = getPercent(a,
@@ -198,6 +199,9 @@
             final float alphaSuggested = getPercent(a,
                     R.styleable.SuggestionsView_alphaSuggested, 100);
             mAlphaObsoleted = getPercent(a, R.styleable.SuggestionsView_alphaSuggested, 100);
+            mColorValidTypedWord = applyAlpha(
+                    a.getColor(R.styleable.SuggestionsView_colorValidTypedWord, 0),
+                    alphaValidTypedWord);
             mColorTypedWord = applyAlpha(
                     a.getColor(R.styleable.SuggestionsView_colorTypedWord, 0), alphaTypedWord);
             mColorAutoCorrect = applyAlpha(
@@ -295,6 +299,8 @@
             final int color;
             if (index == mCenterSuggestionIndex && Utils.willAutoCorrect(suggestions)) {
                 color = mColorAutoCorrect;
+            } else if (index == mCenterSuggestionIndex && suggestions.mTypedWordValid) {
+                color = mColorValidTypedWord;
             } else if (isSuggested) {
                 color = mColorSuggested;
             } else {
@@ -430,7 +436,7 @@
 
                 final TextView word = mWords.get(index);
                 word.setEnabled(true);
-                word.setTextColor(mColorTypedWord);
+                word.setTextColor(mColorAutoCorrect);
                 final CharSequence text = suggestions.getWord(index);
                 word.setText(text);
                 word.setTextScaleX(1.0f);
@@ -468,6 +474,91 @@
             setLayoutWeight(
                     hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
         }
+
+        private static CharSequence getDebugInfo(SuggestedWords suggestions, int pos) {
+            if (DBG && pos < suggestions.size()) {
+                final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
+                if (wordInfo != null) {
+                    final CharSequence debugInfo = wordInfo.getDebugString();
+                    if (!TextUtils.isEmpty(debugInfo)) {
+                        return debugInfo;
+                    }
+                }
+            }
+            return null;
+        }
+
+        private static void setLayoutWeight(View v, float weight, int height) {
+            final ViewGroup.LayoutParams lp = v.getLayoutParams();
+            if (lp instanceof LinearLayout.LayoutParams) {
+                final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
+                llp.weight = weight;
+                llp.width = 0;
+                llp.height = height;
+            }
+        }
+
+        private static float getTextScaleX(CharSequence text, int maxWidth, TextPaint paint) {
+            paint.setTextScaleX(1.0f);
+            final int width = getTextWidth(text, paint);
+            if (width <= maxWidth) {
+                return 1.0f;
+            }
+            return maxWidth / (float)width;
+        }
+
+        private static CharSequence getEllipsizedText(CharSequence text, int maxWidth,
+                TextPaint paint) {
+            if (text == null) return null;
+            paint.setTextScaleX(1.0f);
+            final int width = getTextWidth(text, paint);
+            if (width <= maxWidth) {
+                return text;
+            }
+            final float scaleX = maxWidth / (float)width;
+            if (scaleX >= MIN_TEXT_XSCALE) {
+                paint.setTextScaleX(scaleX);
+                return text;
+            }
+
+            // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To
+            // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
+            final CharSequence ellipsized = TextUtils.ellipsize(
+                    text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
+            paint.setTextScaleX(MIN_TEXT_XSCALE);
+            return ellipsized;
+        }
+
+        private static int getTextWidth(CharSequence text, TextPaint paint) {
+            if (TextUtils.isEmpty(text)) return 0;
+            final Typeface savedTypeface = paint.getTypeface();
+            paint.setTypeface(getTextTypeface(text));
+            final int len = text.length();
+            final float[] widths = new float[len];
+            final int count = paint.getTextWidths(text, 0, len, widths);
+            int width = 0;
+            for (int i = 0; i < count; i++) {
+                width += Math.round(widths[i] + 0.5f);
+            }
+            paint.setTypeface(savedTypeface);
+            return width;
+        }
+
+        private static Typeface getTextTypeface(CharSequence text) {
+            if (!(text instanceof SpannableString))
+                return Typeface.DEFAULT;
+
+            final SpannableString ss = (SpannableString)text;
+            final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class);
+            if (styles.length == 0)
+                return Typeface.DEFAULT;
+
+            switch (styles[0].getStyle()) {
+            case Typeface.BOLD: return Typeface.DEFAULT_BOLD;
+            // TODO: BOLD_ITALIC, ITALIC case?
+            default: return Typeface.DEFAULT;
+            }
+        }
     }
 
     /**
@@ -554,90 +645,6 @@
         mParams.layout(mSuggestions, mSuggestionsStrip, this, getWidth());
     }
 
-    private static CharSequence getDebugInfo(SuggestedWords suggestions, int pos) {
-        if (DBG && pos < suggestions.size()) {
-            final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
-            if (wordInfo != null) {
-                final CharSequence debugInfo = wordInfo.getDebugString();
-                if (!TextUtils.isEmpty(debugInfo)) {
-                    return debugInfo;
-                }
-            }
-        }
-        return null;
-    }
-
-    private static void setLayoutWeight(View v, float weight, int height) {
-        final ViewGroup.LayoutParams lp = v.getLayoutParams();
-        if (lp instanceof LinearLayout.LayoutParams) {
-            final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
-            llp.weight = weight;
-            llp.width = 0;
-            llp.height = height;
-        }
-    }
-
-    private static float getTextScaleX(CharSequence text, int maxWidth, TextPaint paint) {
-        paint.setTextScaleX(1.0f);
-        final int width = getTextWidth(text, paint);
-        if (width <= maxWidth) {
-            return 1.0f;
-        }
-        return maxWidth / (float)width;
-    }
-
-    private static CharSequence getEllipsizedText(CharSequence text, int maxWidth,
-            TextPaint paint) {
-        if (text == null) return null;
-        paint.setTextScaleX(1.0f);
-        final int width = getTextWidth(text, paint);
-        if (width <= maxWidth) {
-            return text;
-        }
-        final float scaleX = maxWidth / (float)width;
-        if (scaleX >= MIN_TEXT_XSCALE) {
-            paint.setTextScaleX(scaleX);
-            return text;
-        }
-
-        // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To get
-        // squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
-        final CharSequence ellipsized = TextUtils.ellipsize(
-                text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
-        paint.setTextScaleX(MIN_TEXT_XSCALE);
-        return ellipsized;
-    }
-
-    private static int getTextWidth(CharSequence text, TextPaint paint) {
-        if (TextUtils.isEmpty(text)) return 0;
-        final Typeface savedTypeface = paint.getTypeface();
-        paint.setTypeface(getTextTypeface(text));
-        final int len = text.length();
-        final float[] widths = new float[len];
-        final int count = paint.getTextWidths(text, 0, len, widths);
-        int width = 0;
-        for (int i = 0; i < count; i++) {
-            width += Math.round(widths[i] + 0.5f);
-        }
-        paint.setTypeface(savedTypeface);
-        return width;
-    }
-
-    private static Typeface getTextTypeface(CharSequence text) {
-        if (!(text instanceof SpannableString))
-            return Typeface.DEFAULT;
-
-        final SpannableString ss = (SpannableString)text;
-        final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class);
-        if (styles.length == 0)
-            return Typeface.DEFAULT;
-
-        switch (styles[0].getStyle()) {
-        case Typeface.BOLD: return Typeface.DEFAULT_BOLD;
-        // TODO: BOLD_ITALIC, ITALIC case?
-        default: return Typeface.DEFAULT;
-        }
-    }
 
     public boolean isShowingAddToDictionaryHint() {
         return mSuggestionsStrip.getChildCount() > 0
diff --git a/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java b/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java
index 7b4c6a9..bfb0301 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java
@@ -19,6 +19,7 @@
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Configuration;
 import android.test.AndroidTestCase;
+import android.text.InputType;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 
@@ -51,7 +52,7 @@
         }
         return new KeyboardId(com.android.inputmethod.latin.R.xml.kbd_qwerty,
                 KeyboardId.ELEMENT_ALPHABET, locale, orientation, width, KeyboardId.MODE_TEXT,
-                null, false, false, false, false);
+                InputType.TYPE_CLASS_TEXT, 0, false, false, false, false);
     }
 
     protected InputStream openTestRawResource(int resIdInTest) {