Merge "Cleanup unused code"
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 57aa5dd..70fc7f8 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -242,6 +242,9 @@
             <flag name="withIconLeft" value="0x1000" />
             <flag name="withIconRight" value="0x2000" />
             <flag name="autoXScale" value="0x4000" />
+            <!-- If true, character case of code, altCode, moreKeys, keyOutputText, keyLabel,
+                 or keyHintLabel will never be subject to change. -->
+            <flag name="preserveCase" value="0x8000" />
         </attr>
         <!-- The icon to display on the key instead of the label. -->
         <attr name="keyIcon" format="enum">
@@ -294,6 +297,19 @@
     </declare-styleable>
 
     <declare-styleable name="Keyboard_Case">
+        <!-- This should be aligned with KeyboardSet_Element's elementName. -->
+        <attr name="keyboardSetElement" format="enum|string">
+            <enum name="alphabet" value="0" />
+            <enum name="alphabetManualShifted" value="1" />
+            <enum name="alphabetAutomaticShifted" value="2" />
+            <enum name="alphabetShiftLocked" value="3" />
+            <enum name="alphabetShiftLockShifted" value="4" />
+            <enum name="symbols" value="5" />
+            <enum name="symbolsShifted" value="6"  />
+            <enum name="phone" value="7"  />
+            <enum name="phoneShifted" value="8"  />
+            <enum name="number" value="9"  />
+        </attr>
         <!-- This should be aligned with KeyboardId.MODE_* -->
         <attr name="mode" format="enum|string">
             <enum name="text" value="0" />
diff --git a/java/res/xml/keyboard_set.xml b/java/res/xml/keyboard_set.xml
index 03eb778..27ef316 100644
--- a/java/res/xml/keyboard_set.xml
+++ b/java/res/xml/keyboard_set.xml
@@ -23,7 +23,8 @@
     latin:keyboardLocale="en_GB,en_US">
     <Element
         latin:elementName="alphabet"
-        latin:elementKeyboard="@xml/kbd_qwerty" />
+        latin:elementKeyboard="@xml/kbd_qwerty"
+        latin:elementAutoGenerate="true" />
     <Element
         latin:elementName="alphabetManualShifted"
         latin:elementKeyboard="@xml/kbd_qwerty"
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index b3c5ed7..9c495fd 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -71,14 +71,16 @@
     private static final int LABEL_FLAGS_WITH_ICON_LEFT = 0x1000;
     private static final int LABEL_FLAGS_WITH_ICON_RIGHT = 0x2000;
     private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000;
+    private static final int LABEL_FLAGS_PRESERVE_CASE = 0x8000;
 
-    // TODO: These icon references could be int (icon attribute id)
     /** Icon to display instead of a label. Icon takes precedence over a label */
+    private final int mIconAttrId;
+    // TODO: Remove this variable.
     private Drawable mIcon;
     /** Icon for disabled state */
-    private Drawable mDisabledIcon;
+    private final int mDisabledIconAttrId;
     /** Preview version of the icon, for the preview popup */
-    public final Drawable mPreviewIcon;
+    public final int mPreviewIconAttrId;
 
     /** Width of the key, not including the gap */
     public final int mWidth;
@@ -204,9 +206,10 @@
         mOutputText = outputText;
         mCode = code;
         mAltCode = Keyboard.CODE_UNSPECIFIED;
+        mIconAttrId = KeyboardIconsSet.ATTR_UNDEFINED;
         mIcon = icon;
-        mDisabledIcon = null;
-        mPreviewIcon = null;
+        mDisabledIconAttrId = KeyboardIconsSet.ATTR_UNDEFINED;
+        mPreviewIconAttrId = KeyboardIconsSet.ATTR_UNDEFINED;
         // Horizontal gap is divided equally to both sides of the key.
         mX = x + mHorizontalGap / 2;
         mY = y;
@@ -260,8 +263,34 @@
         // Update row to have current x coordinate.
         row.setXPos(keyXPos + keyWidth);
 
-        final String[] moreKeys = style.getStringArray(keyAttr,
-                R.styleable.Keyboard_Key_moreKeys);
+        mBackgroundType = style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_backgroundType, BACKGROUND_TYPE_NORMAL);
+        mActionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags, 0);
+
+        final KeyboardIconsSet iconsSet = params.mIconsSet;
+        mVisualInsetsLeft = (int) Keyboard.Builder.getDimensionOrFraction(keyAttr,
+                R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0);
+        mVisualInsetsRight = (int) Keyboard.Builder.getDimensionOrFraction(keyAttr,
+                R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0);
+        mPreviewIconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_keyIconPreview, KeyboardIconsSet.ICON_UNDEFINED));
+        mIconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_keyIcon, KeyboardIconsSet.ICON_UNDEFINED));
+        mIcon = iconsSet.getIconByAttrId(mIconAttrId);
+        mDisabledIconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_keyIconDisabled, KeyboardIconsSet.ICON_UNDEFINED));
+
+        mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags, 0);
+        final boolean preserveCase = (mLabelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0;
+
+        final String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
+        if (moreKeys != null) {
+            for (int i = 0; i < moreKeys.length; i++) {
+                moreKeys[i] = adjustCaseOfStringForKeyboardId(
+                        moreKeys[i], preserveCase, params.mId);
+            }
+        }
+        // TODO: Add new key label flag to control this.
         // In Arabic symbol layouts, we'd like to keep digits in more keys regardless of
         // config_digit_more_keys_enabled.
         if (params.mId.isAlphabetKeyboard()
@@ -273,33 +302,17 @@
         mMaxMoreKeysColumn = style.getInt(keyAttr,
                 R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMiniKeyboardColumn);
 
-        mBackgroundType = style.getInt(keyAttr,
-                R.styleable.Keyboard_Key_backgroundType, BACKGROUND_TYPE_NORMAL);
-        mActionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags, 0);
-
-        final KeyboardIconsSet iconsSet = params.mIconsSet;
-        mVisualInsetsLeft = (int) Keyboard.Builder.getDimensionOrFraction(keyAttr,
-                R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0);
-        mVisualInsetsRight = (int) Keyboard.Builder.getDimensionOrFraction(keyAttr,
-                R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0);
-        final int previewIconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr,
-                R.styleable.Keyboard_Key_keyIconPreview, KeyboardIconsSet.ICON_UNDEFINED));
-        mPreviewIcon = iconsSet.getIconByAttrId(previewIconAttrId);
-        final int iconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr,
-                R.styleable.Keyboard_Key_keyIcon, KeyboardIconsSet.ICON_UNDEFINED));
-        mIcon = iconsSet.getIconByAttrId(iconAttrId);
-        final int disabledIconAttrId = KeyboardIconsSet.getIconAttrId(style.getInt(keyAttr,
-                R.styleable.Keyboard_Key_keyIconDisabled, KeyboardIconsSet.ICON_UNDEFINED));
-        mDisabledIcon = iconsSet.getIconByAttrId(disabledIconAttrId);
-        mHintLabel = style.getString(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
-
-        mLabel = style.getString(keyAttr, R.styleable.Keyboard_Key_keyLabel);
-        mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags, 0);
-        mOutputText = style.getString(keyAttr, R.styleable.Keyboard_Key_keyOutputText);
+        mLabel = adjustCaseOfStringForKeyboardId(style.getString(
+                keyAttr, R.styleable.Keyboard_Key_keyLabel), preserveCase, params.mId);
+        mHintLabel = adjustCaseOfStringForKeyboardId(style.getString(
+                keyAttr, R.styleable.Keyboard_Key_keyHintLabel), preserveCase, params.mId);
+        mOutputText = adjustCaseOfStringForKeyboardId(style.getString(
+                keyAttr, R.styleable.Keyboard_Key_keyOutputText), preserveCase, params.mId);
         // Choose the first letter of the label as primary code if not
         // specified.
-        final int code = style.getInt(keyAttr, R.styleable.Keyboard_Key_code,
-                Keyboard.CODE_UNSPECIFIED);
+        final int code = adjustCaseOfCodeForKeyboardId(style.getInt(
+                keyAttr, R.styleable.Keyboard_Key_code, Keyboard.CODE_UNSPECIFIED), preserveCase,
+                params.mId);
         if (code == Keyboard.CODE_UNSPECIFIED && mOutputText == null
                 && !TextUtils.isEmpty(mLabel)) {
             if (mLabel.length() != 1) {
@@ -312,13 +325,36 @@
         } else {
             mCode = code;
         }
-        mAltCode = style.getInt(keyAttr,
-                R.styleable.Keyboard_Key_altCode, Keyboard.CODE_UNSPECIFIED);
+        mAltCode = adjustCaseOfCodeForKeyboardId(style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_altCode, Keyboard.CODE_UNSPECIFIED), preserveCase,
+                params.mId);
         mHashCode = hashCode(this);
 
         keyAttr.recycle();
     }
 
+    private static int adjustCaseOfCodeForKeyboardId(int code, boolean preserveCase,
+            KeyboardId id) {
+        if (!Keyboard.isLetterCode(code) || preserveCase) return code;
+        final String text = new String(new int[] { code } , 0, 1);
+        final String casedText = adjustCaseOfStringForKeyboardId(text, preserveCase, id);
+        return casedText.codePointAt(0);
+    }
+
+    private static String adjustCaseOfStringForKeyboardId(String text, boolean preserveCase,
+            KeyboardId id) {
+        if (text == null || preserveCase) return text;
+        switch (id.mElementId) {
+        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+            return text.toUpperCase(id.mLocale);
+        default:
+            return text;
+        }
+    }
+
     private static int hashCode(Key key) {
         return Arrays.hashCode(new Object[] {
                 key.mX,
@@ -328,13 +364,15 @@
                 key.mCode,
                 key.mLabel,
                 key.mHintLabel,
+                key.mIconAttrId,
                 // Key can be distinguishable without the following members.
                 // key.mAltCode,
                 // key.mOutputText,
                 // key.mActionFlags,
                 // key.mLabelFlags,
                 // key.mIcon,
-                // key.mPreviewIcon,
+                // key.mDisabledIconAttrId,
+                // key.mPreviewIconAttrId,
                 // key.mBackgroundType,
                 // key.mHorizontalGap,
                 // key.mVerticalGap,
@@ -471,8 +509,9 @@
         return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0;
     }
 
-    public Drawable getIcon() {
-        return mEnabled ? mIcon : mDisabledIcon;
+    // TODO: Get rid of this method.
+    public Drawable getIcon(KeyboardIconsSet iconSet) {
+        return mEnabled ? mIcon : iconSet.getIconByAttrId(mDisabledIconAttrId);
     }
 
     // TODO: Get rid of this method.
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 8c386da..abe5b62 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -293,6 +293,8 @@
         public final Set<Key> mShiftLockKeys = new HashSet<Key>();
         public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
 
+        public KeyboardSet.KeysCache mKeysCache;
+
         public int mMostCommonKeyHeight = 0;
         public int mMostCommonKeyWidth = 0;
 
@@ -361,7 +363,8 @@
             clearHistogram();
         }
 
-        public void onAddKey(Key key) {
+        public void onAddKey(Key newKey) {
+            final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
             mKeys.add(key);
             updateHistogram(key);
             if (key.mCode == Keyboard.CODE_SHIFT) {
@@ -688,6 +691,10 @@
             params.mTouchPositionCorrection.load(data);
         }
 
+        public void setAutoGenerate(KeyboardSet.KeysCache keysCache) {
+            mParams.mKeysCache = keysCache;
+        }
+
         public Builder<KP> load(int xmlId, KeyboardId id) {
             mParams.mId = id;
             final XmlResourceParser parser = mResources.getXml(xmlId);
@@ -1034,6 +1041,9 @@
             final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
                     R.styleable.Keyboard_Case);
             try {
+                final boolean keyboardSetElementMatched = matchTypedValue(a,
+                        R.styleable.Keyboard_Case_keyboardSetElement, id.mElementId,
+                        KeyboardId.elementIdToName(id.mElementId));
                 final boolean modeMatched = matchTypedValue(a,
                         R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode));
                 final boolean navigateActionMatched = matchBoolean(a,
@@ -1062,13 +1072,15 @@
                         R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
                 final boolean countryCodeMatched = matchString(a,
                         R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
-                final boolean selected = modeMatched && navigateActionMatched
-                        && passwordInputMatched && hasSettingsKeyMatched && f2KeyModeMatched
-                        && clobberSettingsKeyMatched && shortcutKeyEnabledMatched
-                        && hasShortcutKeyMatched && imeActionMatched && localeCodeMatched
-                        && languageCodeMatched && countryCodeMatched;
+                final boolean selected = keyboardSetElementMatched && modeMatched
+                        && navigateActionMatched && passwordInputMatched && hasSettingsKeyMatched
+                        && f2KeyModeMatched && clobberSettingsKeyMatched
+                        && shortcutKeyEnabledMatched && hasShortcutKeyMatched && imeActionMatched
+                        && localeCodeMatched && languageCodeMatched && countryCodeMatched;
 
-                if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s%s%s%s%s%s> %s", TAG_CASE,
+                if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s> %s", TAG_CASE,
+                        textAttr(a.getString(R.styleable.Keyboard_Case_keyboardSetElement),
+                                "keyboardSetElement"),
                         textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"),
                         booleanAttr(a, R.styleable.Keyboard_Case_navigateAction, "navigateAction"),
                         booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, "passwordInput"),
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
index c7f964a..cacb8a3 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
@@ -57,6 +57,25 @@
 
     private final Context mContext;
     private final Params mParams;
+    private final KeysCache mKeysCache = new KeysCache();
+
+    public static class KeysCache {
+        private final Map<Key, Key> mMap;
+
+        public KeysCache() {
+            mMap = new HashMap<Key, Key>();
+        }
+
+        public Key get(Key key) {
+            final Key existingKey = mMap.get(key);
+            if (existingKey != null) {
+                // Reuse the existing element that equals to "key" without adding "key" to the map.
+                return existingKey;
+            }
+            mMap.put(key, key);
+            return key;
+        }
+    }
 
     static class KeyboardElement {
         final int mElementId;
@@ -99,15 +118,15 @@
     }
 
     public Keyboard getMainKeyboard() {
-        return getKeyboard(false, false);
+        return getKeyboard(false, false, false);
     }
 
     public Keyboard getSymbolsKeyboard() {
-        return getKeyboard(true, false);
+        return getKeyboard(true, false, false);
     }
 
     public Keyboard getSymbolsShiftedKeyboard() {
-        final Keyboard keyboard = getKeyboard(true, true);
+        final Keyboard keyboard = getKeyboard(true, false, true);
         // 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.
         // sticky shift key). To show or dismiss the indicator, we need to call setShiftLocked()
@@ -116,22 +135,23 @@
         return keyboard;
     }
 
-    private Keyboard getKeyboard(boolean isSymbols, boolean isShift) {
-        final int elementId = KeyboardSet.getElementId(mParams.mMode, isSymbols, isShift);
+    private Keyboard getKeyboard(boolean isSymbols, boolean isShiftLock, boolean isShift) {
+        final int elementId = KeyboardSet.getElementId(
+                mParams.mMode, isSymbols, isShiftLock, isShift);
         final KeyboardElement keyboardElement = mParams.mElementKeyboards.get(elementId);
         // TODO: If keyboardElement.mAutoGenerate is true, the keyboard will be auto generated
         // based on keyboardElement.mKayoutId Keyboard XML definition.
         final KeyboardId id = KeyboardSet.getKeyboardId(elementId, isSymbols, mParams);
-        final Keyboard keyboard = getKeyboard(mContext, keyboardElement.mLayoutId, id);
+        final Keyboard keyboard = getKeyboard(mContext, keyboardElement, id);
         return keyboard;
     }
 
     public KeyboardId getMainKeyboardId() {
-        final int elementId = KeyboardSet.getElementId(mParams.mMode, false, false);
+        final int elementId = KeyboardSet.getElementId(mParams.mMode, false, false, false);
         return KeyboardSet.getKeyboardId(elementId, false, mParams);
     }
 
-    private Keyboard getKeyboard(Context context, int xmlId, KeyboardId id) {
+    private Keyboard getKeyboard(Context context, KeyboardElement element, KeyboardId id) {
         final Resources res = context.getResources();
         final SoftReference<Keyboard> ref = sKeyboardCache.get(id);
         Keyboard keyboard = (ref == null) ? null : ref.get();
@@ -140,7 +160,10 @@
             try {
                 final Keyboard.Builder<Keyboard.Params> builder =
                         new Keyboard.Builder<Keyboard.Params>(context, new Keyboard.Params());
-                builder.load(xmlId, id);
+                if (element.mAutoGenerate) {
+                    builder.setAutoGenerate(mKeysCache);
+                }
+                builder.load(element.mLayoutId, id);
                 builder.setTouchPositionCorrectionEnabled(mParams.mTouchPositionCorrectionEnabled);
                 keyboard = builder.build();
             } finally {
@@ -162,7 +185,8 @@
         return keyboard;
     }
 
-    private static int getElementId(int mode, boolean isSymbols, boolean isShift) {
+    private static int getElementId(int mode, boolean isSymbols, boolean isShiftLock,
+            boolean isShift) {
         switch (mode) {
         case KeyboardId.MODE_PHONE:
             return (isSymbols && isShift)
@@ -174,6 +198,7 @@
                 return isShift
                         ? KeyboardId.ELEMENT_SYMBOLS_SHIFTED : KeyboardId.ELEMENT_SYMBOLS;
             }
+            // TODO: Consult isShiftLock and isShift to determine the element.
             return KeyboardId.ELEMENT_ALPHABET;
         }
     }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 732c8af..afcf510 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -39,6 +39,7 @@
 import android.widget.TextView;
 
 import com.android.inputmethod.compat.FrameLayoutCompatUtils;
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
@@ -552,7 +553,7 @@
         }
 
         // Draw key label.
-        final Drawable icon = key.getIcon();
+        final Drawable icon = key.getIcon(mKeyboard.mIconsSet);
         float positionX = centerX;
         if (key.mLabel != null) {
             // Switch the character to uppercase if shift is pressed
@@ -898,9 +899,10 @@
             }
             previewText.setText(mKeyboard.adjustLabelCase(key.mLabel));
         } else {
-            final Drawable previewIcon = key.mPreviewIcon;
+            final Drawable previewIcon = mKeyboard.mIconsSet.getIconByAttrId(
+                    key.mPreviewIconAttrId);
             previewText.setCompoundDrawables(null, null, null,
-                   previewIcon != null ? previewIcon : key.getIcon());
+                    previewIcon != null ? previewIcon : key.getIcon(mKeyboard.mIconsSet));
             previewText.setText(null);
         }
         previewText.setBackgroundDrawable(params.mPreviewBackground);
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 6dcc1dc..f5b282d 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -20,14 +20,11 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Paint.Align;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
+import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.os.Message;
 import android.text.TextUtils;
@@ -54,8 +51,6 @@
 import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
 
-import java.util.Arrays;
-import java.util.HashMap;
 import java.util.Locale;
 import java.util.WeakHashMap;
 
@@ -72,38 +67,33 @@
 
     private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true;
 
-    /* Space key and its icons, drawables and colors. */
+    // TODO: Kill process when the usability study mode was changed.
+    private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy;
+
+    /** Listener for {@link KeyboardActionListener}. */
+    private KeyboardActionListener mKeyboardActionListener;
+
+    /* Space key and its icons */
     private Key mSpaceKey;
     private Drawable mSpaceIcon;
-    private final boolean mIsSpacebarTriggeringPopupByLongPress;
-    private static final int SPACE_LED_LENGTH_PERCENT = 80;
-    private final boolean mAutoCorrectionSpacebarLedEnabled;
-    private final Drawable mAutoCorrectionSpacebarLedIcon;
+    // Stuff to draw language name on spacebar.
+    private boolean mNeedsToDisplayLanguage;
+    private Locale mSpacebarLocale;
+    private float mSpacebarTextFadeFactor = 0.0f;
     private final float mSpacebarTextRatio;
     private float mSpacebarTextSize;
     private final int mSpacebarTextColor;
     private final int mSpacebarTextShadowColor;
-    private final HashMap<Integer, BitmapDrawable> mSpacebarDrawableCache =
-            new HashMap<Integer, BitmapDrawable>();
-
-    private boolean mAutoCorrectionSpacebarLedOn;
-    private boolean mNeedsToDisplayLanguage;
-    private Locale mSpacebarLocale;
-    private float mSpacebarTextFadeFactor = 0.0f;
-
     // Height in space key the language name will be drawn. (proportional to space key height)
-    public static final float SPACEBAR_LANGUAGE_BASELINE = 0.6f;
+    private static final float SPACEBAR_LANGUAGE_BASELINE = 0.6f;
     // If the full language name needs to be smaller than this value to be drawn on space key,
     // its short language name will be used instead.
     private static final float MINIMUM_SCALE_OF_LANGUAGE_NAME = 0.8f;
-
-    private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
-
-    // 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;
+    // Stuff to draw auto correction LED on spacebar.
+    private boolean mAutoCorrectionSpacebarLedOn;
+    private final boolean mAutoCorrectionSpacebarLedEnabled;
+    private final Drawable mAutoCorrectionSpacebarLedIcon;
+    private static final int SPACE_LED_LENGTH_PERCENT = 80;
 
     // Mini keyboard
     private PopupWindow mMoreKeysWindow;
@@ -111,17 +101,16 @@
     private int mMoreKeysPanelPointerTrackerId;
     private final WeakHashMap<Key, MoreKeysPanel> mMoreKeysPanelCache =
             new WeakHashMap<Key, MoreKeysPanel>();
+    private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
 
-    /** Listener for {@link KeyboardActionListener}. */
-    private KeyboardActionListener mKeyboardActionListener;
+    private final boolean mIsSpacebarTriggeringPopupByLongPress;
+    private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
 
+    protected KeyDetector mKeyDetector;
     private boolean mHasDistinctMultitouch;
     private int mOldPointerCount = 1;
     private Key mOldKey;
 
-    private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
-    protected KeyDetector mKeyDetector;
-
     // To detect double tap.
     protected GestureDetector mGestureDetector;
 
@@ -134,10 +123,14 @@
         private static final int MSG_IGNORE_DOUBLE_TAP = 3;
         private static final int MSG_KEY_TYPED = 4;
 
+        private final int mKeyRepeatInterval;
         private boolean mInKeyRepeat;
 
         public KeyTimerHandler(LatinKeyboardView outerInstance) {
             super(outerInstance);
+            // TODO: This should be the attribute of LatinKeyboardView.
+            final Resources res = outerInstance.getContext().getResources();
+            mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
         }
 
         @Override
@@ -147,7 +140,7 @@
             switch (msg.what) {
             case MSG_REPEAT_KEY:
                 tracker.onRepeatKey(tracker.getKey());
-                startKeyRepeatTimer(keyboardView.mKeyRepeatInterval, tracker);
+                startKeyRepeatTimer(mKeyRepeatInterval, tracker);
                 break;
             case MSG_LONGPRESS_KEY:
                 keyboardView.openMiniKeyboardIfRequired(tracker.getKey(), tracker);
@@ -213,7 +206,7 @@
         }
     }
 
-    private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
+    class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
         private boolean mProcessingShiftDoubleTapEvent = false;
 
         @Override
@@ -270,8 +263,10 @@
         mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this);
 
         final Resources res = getResources();
+        // TODO: This should be the attribute of LatinKeyboardView.
         mConfigShowMiniKeyboardAtTouchedPoint = res.getBoolean(
                 R.bool.config_show_mini_keyboard_at_touched_point);
+        // TODO: This should be the attribute of LatinKeyboardView.
         final float keyHysteresisDistance = res.getDimension(R.dimen.key_hysteresis_distance);
         mKeyDetector = new KeyDetector(keyHysteresisDistance);
 
@@ -282,10 +277,10 @@
 
         mHasDistinctMultitouch = context.getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
-        mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
 
         PointerTracker.init(mHasDistinctMultitouch, getContext());
 
+        // TODO: This should be the attribute of LatinKeyboardView.
         final int longPressSpaceKeyTimeout =
                 res.getInteger(R.integer.config_long_press_space_key_timeout);
         mIsSpacebarTriggeringPopupByLongPress = (longPressSpaceKeyTimeout > 0);
@@ -362,7 +357,6 @@
         final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
         mSpacebarTextSize = keyHeight * mSpacebarTextRatio;
         mSpacebarLocale = keyboard.mId.mLocale;
-        clearSpacebarDrawableCache();
     }
 
     /**
@@ -759,14 +753,12 @@
     public void updateSpacebar(float fadeFactor, boolean needsToDisplayLanguage) {
         mSpacebarTextFadeFactor = fadeFactor;
         mNeedsToDisplayLanguage = needsToDisplayLanguage;
-        updateSpacebarIcon();
         invalidateKey(mSpaceKey);
     }
 
     public void updateAutoCorrectionState(boolean isAutoCorrection) {
         if (!mAutoCorrectionSpacebarLedEnabled) return;
         mAutoCorrectionSpacebarLedOn = isAutoCorrection;
-        updateSpacebarIcon();
         invalidateKey(mSpaceKey);
     }
 
@@ -775,27 +767,16 @@
         super.onDrawKeyTopVisuals(key, canvas, paint, params);
 
         if (key.mCode == Keyboard.CODE_SPACE) {
+            drawSpacebar(key, canvas, paint);
+
             // Whether space key needs to show the "..." popup hint for special purposes
             if (mIsSpacebarTriggeringPopupByLongPress
                     && Utils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
-                super.drawKeyPopupHint(key, canvas, paint, params);
+                drawKeyPopupHint(key, canvas, paint, params);
             }
         }
     }
 
-    // TODO: Get rid of this method and draw spacebar locale and auto correction spacebar LED
-    // in onDrawKeyTopVisuals.
-    private void updateSpacebarIcon() {
-        if (mSpaceKey == null) return;
-        if (mNeedsToDisplayLanguage) {
-            mSpaceKey.setIcon(getSpaceDrawable(mSpacebarLocale));
-        } else if (mAutoCorrectionSpacebarLedOn) {
-            mSpaceKey.setIcon(getSpaceDrawable(null));
-        } else {
-            mSpaceKey.setIcon(mSpaceIcon);
-        }
-    }
-
     private static int getSpacebarTextColor(int color, float fadeFactor) {
         final int newColor = Color.argb((int)(Color.alpha(color) * fadeFactor),
                 Color.red(color), Color.green(color), Color.blue(color));
@@ -803,24 +784,23 @@
     }
 
     // Compute width of text with specified text size using paint.
-    private static int getTextWidth(Paint paint, String text, float textSize, Rect bounds) {
+    private int getTextWidth(Paint paint, String text, float textSize) {
         paint.setTextSize(textSize);
-        paint.getTextBounds(text, 0, text.length(), bounds);
-        return bounds.width();
+        return (int)getLabelWidth(text, paint);
     }
 
     // Layout locale language name on spacebar.
-    private static String layoutSpacebar(Paint paint, Locale locale, int width,
+    private String layoutLanguageOnSpacebar(Paint paint, Locale locale, int width,
             float origTextSize) {
-        final Rect bounds = new Rect();
-
+        paint.setTextAlign(Align.CENTER);
+        paint.setTypeface(Typeface.DEFAULT);
         // Estimate appropriate language name text size to fit in maxTextWidth.
         String language = Utils.getFullDisplayName(locale, true);
-        int textWidth = getTextWidth(paint, language, origTextSize, bounds);
+        int textWidth = getTextWidth(paint, language, origTextSize);
         // Assuming text width and text size are proportional to each other.
         float textSize = origTextSize * Math.min(width / textWidth, 1.0f);
         // allow variable text size
-        textWidth = getTextWidth(paint, language, textSize, bounds);
+        textWidth = getTextWidth(paint, language, textSize);
         // If text size goes too small or text does not fit, use middle or short name
         final boolean useMiddleName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
                 || (textWidth > width);
@@ -828,7 +808,7 @@
         final boolean useShortName;
         if (useMiddleName) {
             language = Utils.getMiddleDisplayLanguage(locale);
-            textWidth = getTextWidth(paint, language, origTextSize, bounds);
+            textWidth = getTextWidth(paint, language, origTextSize);
             textSize = origTextSize * Math.min(width / textWidth, 1.0f);
             useShortName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
                     || (textWidth > width);
@@ -838,7 +818,7 @@
 
         if (useShortName) {
             language = Utils.getShortDisplayLanguage(locale);
-            textWidth = getTextWidth(paint, language, origTextSize, bounds);
+            textWidth = getTextWidth(paint, language, origTextSize);
             textSize = origTextSize * Math.min(width / textWidth, 1.0f);
         }
         paint.setTextSize(textSize);
@@ -846,50 +826,14 @@
         return language;
     }
 
-    private Integer getSpaceDrawableKey(Locale locale) {
-        return Arrays.hashCode(new Object[] {
-                locale,
-                mAutoCorrectionSpacebarLedOn,
-                mSpacebarTextFadeFactor
-        });
-    }
-
-    private void clearSpacebarDrawableCache() {
-        for (final BitmapDrawable drawable : mSpacebarDrawableCache.values()) {
-            final Bitmap bitmap = drawable.getBitmap();
-            bitmap.recycle();
-        }
-        mSpacebarDrawableCache.clear();
-    }
-
-    private BitmapDrawable getSpaceDrawable(Locale locale) {
-        final Integer hashCode = getSpaceDrawableKey(locale);
-        final BitmapDrawable cached = mSpacebarDrawableCache.get(hashCode);
-        if (cached != null) {
-            return cached;
-        }
-        final BitmapDrawable drawable = new BitmapDrawable(getResources(), drawSpacebar(
-                locale, mAutoCorrectionSpacebarLedOn, mSpacebarTextFadeFactor));
-        mSpacebarDrawableCache.put(hashCode, drawable);
-        return drawable;
-    }
-
-    private Bitmap drawSpacebar(Locale inputLocale, boolean isAutoCorrection,
-            float textFadeFactor) {
-        final int width = mSpaceKey.mWidth;
-        final int height = mSpaceIcon != null ? mSpaceIcon.getIntrinsicHeight() : mSpaceKey.mHeight;
-        final Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        final Canvas canvas = new Canvas(buffer);
-        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
+    private void drawSpacebar(Key key, Canvas canvas, Paint paint) {
+        final int width = key.mWidth;
+        final int height = mSpaceIcon != null ? mSpaceIcon.getIntrinsicHeight() : key.mHeight;
 
         // If application locales are explicitly selected.
-        if (inputLocale != null) {
-            final Paint paint = new Paint();
-            paint.setAntiAlias(true);
-            paint.setTextAlign(Align.CENTER);
-
-            final String language = layoutSpacebar(paint, inputLocale, width, mSpacebarTextSize);
-
+        if (mNeedsToDisplayLanguage) {
+            final String language = layoutLanguageOnSpacebar(paint, mSpacebarLocale, width,
+                    mSpacebarTextSize);
             // Draw language text with shadow
             // In case there is no space icon, we will place the language text at the center of
             // spacebar.
@@ -897,28 +841,25 @@
             final float textHeight = -paint.ascent() + descent;
             final float baseline = (mSpaceIcon != null) ? height * SPACEBAR_LANGUAGE_BASELINE
                     : height / 2 + textHeight / 2;
-            paint.setColor(getSpacebarTextColor(mSpacebarTextShadowColor, textFadeFactor));
+            paint.setColor(getSpacebarTextColor(mSpacebarTextShadowColor, mSpacebarTextFadeFactor));
             canvas.drawText(language, width / 2, baseline - descent - 1, paint);
-            paint.setColor(getSpacebarTextColor(mSpacebarTextColor, textFadeFactor));
+            paint.setColor(getSpacebarTextColor(mSpacebarTextColor, mSpacebarTextFadeFactor));
             canvas.drawText(language, width / 2, baseline - descent, paint);
         }
 
         // Draw the spacebar icon at the bottom
-        if (isAutoCorrection) {
+        if (mAutoCorrectionSpacebarLedOn) {
             final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100;
             final int iconHeight = mAutoCorrectionSpacebarLedIcon.getIntrinsicHeight();
             int x = (width - iconWidth) / 2;
             int y = height - iconHeight;
-            mAutoCorrectionSpacebarLedIcon.setBounds(x, y, x + iconWidth, y + iconHeight);
-            mAutoCorrectionSpacebarLedIcon.draw(canvas);
+            drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight);
         } else if (mSpaceIcon != null) {
             final int iconWidth = mSpaceIcon.getIntrinsicWidth();
             final int iconHeight = mSpaceIcon.getIntrinsicHeight();
             int x = (width - iconWidth) / 2;
             int y = height - iconHeight;
-            mSpaceIcon.setBounds(x, y, x + iconWidth, y + iconHeight);
-            mSpaceIcon.draw(canvas);
+            drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight);
         }
-        return buffer;
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 09ecbca..bec6ae1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -31,7 +31,7 @@
     private static final String TAG = KeyboardIconsSet.class.getSimpleName();
 
     public static final int ICON_UNDEFINED = 0;
-    private static final int ATTR_UNDEFINED = 0;
+    public static final int ATTR_UNDEFINED = 0;
 
     private final Map<Integer, Drawable> mIcons = new HashMap<Integer, Drawable>();
 
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 3692ac1..b824000 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -162,7 +162,7 @@
             }
             if (len > 0) {
                 callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j],
-                        mDicTypeId, DataType.BIGRAM);
+                        mDicTypeId, Dictionary.BIGRAM);
             }
         }
     }
@@ -182,7 +182,7 @@
             }
             if (len > 0) {
                 callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId,
-                        DataType.UNIGRAM);
+                        Dictionary.UNIGRAM);
             }
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/ComposingStateManager.java b/java/src/com/android/inputmethod/latin/ComposingStateManager.java
index 8811f20..27f509a 100644
--- a/java/src/com/android/inputmethod/latin/ComposingStateManager.java
+++ b/java/src/com/android/inputmethod/latin/ComposingStateManager.java
@@ -53,6 +53,13 @@
         }
     }
 
+    public synchronized boolean isComposing() {
+        // TODO: use the composing flag in WordComposer instead of maintaining it
+        // here separately. Even better, do away with this class and manage the auto
+        // correction indicator in the same place as the suggestions.
+        return mIsComposing;
+    }
+
     public synchronized boolean isAutoCorrectionIndicatorOn() {
         return mAutoCorrectionIndicatorOn;
     }
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index c35b428..79bf338 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -33,9 +33,8 @@
      */
     protected static final int FULL_WORD_SCORE_MULTIPLIER = 2;
 
-    public static enum DataType {
-        UNIGRAM, BIGRAM
-    }
+    public static final int UNIGRAM = 0;
+    public static final int BIGRAM = 1;
 
     /**
      * Interface to be implemented by classes requesting words to be fetched from the dictionary.
@@ -51,11 +50,11 @@
          * @param score the score of occurrence. This is normalized between 1 and 255, but
          * can exceed those limits
          * @param dicTypeId of the dictionary where word was from
-         * @param dataType tells type of this data
+         * @param dataType tells type of this data, either UNIGRAM or BIGRAM
          * @return true if the word was added, false if no more words are required
          */
         boolean addWord(char[] word, int wordOffset, int wordLength, int score, int dicTypeId,
-                DataType dataType);
+                int dataType);
     }
 
     /**
@@ -64,7 +63,7 @@
      * @param composer the key sequence to match
      * @param callback the callback object to send matched words to as possible candidates
      * @param proximityInfo the object for key proximity. May be ignored by some implementations.
-     * @see WordCallback#addWord(char[], int, int, int, int, DataType)
+     * @see WordCallback#addWord(char[], int, int, int, int, int)
      */
     abstract public void getWords(final WordComposer composer, final WordCallback callback,
             final ProximityInfo proximityInfo);
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 7eec8e2..8e8adc1 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -301,7 +301,7 @@
                         finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength);
                     }
                     if (!callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
-                            DataType.UNIGRAM)) {
+                            Dictionary.UNIGRAM)) {
                         return;
                     }
                 }
@@ -342,7 +342,7 @@
                                                 snr * addedAttenuation, mInputLength);
                                     }
                                     callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
-                                            DataType.UNIGRAM);
+                                            Dictionary.UNIGRAM);
                                 }
                             }
                             if (children != null) {
@@ -506,7 +506,7 @@
             } while (node != null);
 
             callback.addWord(mLookedUpString, index, MAX_WORD_LENGTH - index, freq, mDicTypeId,
-                    DataType.BIGRAM);
+                    Dictionary.BIGRAM);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 31cbc4e..327950a 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1622,7 +1622,7 @@
     private CharSequence getTextWithUnderline(final CharSequence text) {
         return mComposingStateManager.isAutoCorrectionIndicatorOn()
                 ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(this, text)
-                : mWordComposer.getTypedWord();
+                : text;
     }
 
     private void handleClose() {
@@ -1702,10 +1702,9 @@
                 if (DEBUG) {
                     Log.d(TAG, "Flip the indicator. " + oldAutoCorrectionIndicator
                             + " -> " + newAutoCorrectionIndicator);
-                    if (newAutoCorrectionIndicator
+                    if (mComposingStateManager.isComposing() && newAutoCorrectionIndicator
                             != mComposingStateManager.isAutoCorrectionIndicatorOn()) {
-                        throw new RuntimeException("Couldn't flip the indicator! We are not "
-                                + "composing a word right now.");
+                        throw new RuntimeException("Couldn't flip the indicator!");
                     }
                 }
                 final CharSequence textWithUnderline =
@@ -2230,10 +2229,14 @@
         final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0);
         // NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to
         // enter surrogate pairs this code will have been removed.
-        if (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1)) {
-            // We should not have come here if the text before the cursor is not a space.
-            throw new RuntimeException("Tried to revert a swap of punctuation but we didn't "
+        if (TextUtils.isEmpty(textBeforeCursor)
+                || (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1))) {
+            // We may only come here if the application is changing the text while we are typing.
+            // This is quite a broken case, but not logically impossible, so we shouldn't crash,
+            // but some debugging log may be in order.
+            Log.d(TAG, "Tried to revert a swap of punctuation but we didn't "
                     + "find a space just before the cursor.");
+            return false;
         }
         ic.beginBatchEdit();
         ic.deleteSurroundingText(2, 0);
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index 6f1adfe..e3dadf2 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -21,7 +21,6 @@
 import android.view.inputmethod.EditorInfo;
 
 import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.latin.Dictionary.DataType;
 
 import java.util.List;
 
@@ -75,7 +74,7 @@
     public static void onStartSuggestion(CharSequence previousWords) {
     }
 
-    public static void onAddSuggestedWord(String word, int typeId, DataType dataType) {
+    public static void onAddSuggestedWord(String word, int typeId, int dataType) {
     }
 
     public static void onSetKeyboard(Keyboard kb) {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 8e0d031..f6e177a 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -298,7 +298,7 @@
         if (typedWord != null) {
             // Treating USER_TYPED as UNIGRAM suggestion for logging now.
             LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED,
-                    Dictionary.DataType.UNIGRAM);
+                    Dictionary.UNIGRAM);
         }
         mConsideredWord = consideredWord;
 
@@ -417,12 +417,12 @@
 
     @Override
     public boolean addWord(final char[] word, final int offset, final int length, int score,
-            final int dicTypeId, final Dictionary.DataType dataType) {
-        Dictionary.DataType dataTypeForLog = dataType;
+            final int dicTypeId, final int dataType) {
+        int dataTypeForLog = dataType;
         final ArrayList<CharSequence> suggestions;
         final int[] sortedScores;
         final int prefMaxSuggestions;
-        if(dataType == Dictionary.DataType.BIGRAM) {
+        if (dataType == Dictionary.BIGRAM) {
             suggestions = mBigramSuggestions;
             sortedScores = mBigramScores;
             prefMaxSuggestions = PREF_MAX_BIGRAMS;
@@ -450,11 +450,11 @@
                 }
             }
         } else {
-            if (dataType == Dictionary.DataType.UNIGRAM) {
+            if (dataType == Dictionary.UNIGRAM) {
                 // Check if the word was already added before (by bigram data)
                 int bigramSuggestion = searchBigramSuggestion(word,offset,length);
                 if(bigramSuggestion >= 0) {
-                    dataTypeForLog = Dictionary.DataType.BIGRAM;
+                    dataTypeForLog = Dictionary.BIGRAM;
                     // turn freq from bigram into multiplier specified above
                     double multiplier = (((double) mBigramScores[bigramSuggestion])
                             / MAXIMUM_BIGRAM_FREQUENCY)
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 39e47f6..88ac043 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -31,7 +31,6 @@
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.BinaryDictionary;
 import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.Dictionary.DataType;
 import com.android.inputmethod.latin.Dictionary.WordCallback;
 import com.android.inputmethod.latin.DictionaryCollection;
 import com.android.inputmethod.latin.DictionaryFactory;
@@ -237,7 +236,7 @@
 
         @Override
         synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score,
-                int dicTypeId, DataType dataType) {
+                int dicTypeId, int dataType) {
             final int positionIndex = ArraysCompatUtils.binarySearch(mScores, 0, mLength, score);
             // binarySearch returns the index if the element exists, and -<insertion index> - 1
             // if it doesn't. See documentation for binarySearch.
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index 2bc2cfd..db35449 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -22,7 +22,8 @@
 import java.util.TreeMap;
 
 public class SpellCheckerProximityInfo {
-    final private static int NUL = KeyDetector.NOT_A_CODE;
+    /* public for test */
+    final public static int NUL = KeyDetector.NOT_A_CODE;
 
     // This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
     // native code - this value is passed at creation of the binary object and reused
diff --git a/native/src/defines.h b/native/src/defines.h
index 220309b..9c2d087 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -169,6 +169,7 @@
 #define NOT_VALID_WORD -99
 #define NOT_A_CHARACTER -1
 #define NOT_A_DISTANCE -1
+#define NOT_A_COORDINATE -1
 #define EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO -2
 #define PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO -3
 #define NOT_A_INDEX -1
diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp
index b91957c..e0e9380 100644
--- a/native/src/proximity_info.cpp
+++ b/native/src/proximity_info.cpp
@@ -165,6 +165,9 @@
     if (!hasSweetSpotData(keyIndex)) {
         return NOT_A_DISTANCE_FLOAT;
     }
+    if (NOT_A_COORDINATE == mInputXCoordinates[inputIndex]) {
+        return NOT_A_DISTANCE_FLOAT;
+    }
     const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(keyIndex, inputIndex);
     const float squaredRadius = square(mSweetSpotRadii[keyIndex]);
     return squaredDistance / squaredRadius;
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index 0d5e42b..7b87be6 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -34,6 +34,11 @@
 
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
+import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService; // for proximity info
+import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo;
+
+import java.util.Arrays;
+import java.util.HashMap;
 
 public class InputLogicTests extends ServiceTestCase<LatinIME> {
 
@@ -42,9 +47,29 @@
     private LatinIME mLatinIME;
     private TextView mTextView;
     private InputConnection mInputConnection;
+    private HashMap<Integer, int[]> mProximity;
 
     public InputLogicTests() {
         super(LatinIME.class);
+        mProximity = createProximity();
+    }
+
+    private static HashMap<Integer, int[]> createProximity() {
+        final HashMap<Integer, int[]> proximity = new HashMap<Integer, int[]>();
+        final int[] testProximity = SpellCheckerProximityInfo.getProximityForScript(
+                AndroidSpellCheckerService.SCRIPT_LATIN);
+        final int ROW_SIZE = SpellCheckerProximityInfo.ROW_SIZE;
+        final int NUL = SpellCheckerProximityInfo.NUL;
+        for (int row = 0; row * ROW_SIZE < testProximity.length; ++row) {
+            final int rowBase = row * ROW_SIZE;
+            int column;
+            for (column = 1; NUL != testProximity[rowBase + column]; ++column) {
+                // Do nothing, just search for a NUL element
+            }
+            proximity.put(testProximity[row * ROW_SIZE],
+                    Arrays.copyOfRange(testProximity, rowBase, rowBase + column));
+        }
+        return proximity;
     }
 
     // returns the previous setting value
@@ -73,7 +98,9 @@
         mLatinIME.onCreate();
         setDebugMode(previousDebugSetting);
         final EditorInfo ei = new EditorInfo();
+        ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
         final InputConnection ic = mTextView.onCreateInputConnection(ei);
+        ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
         final LayoutInflater inflater =
                 (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         final ViewGroup vg = new FrameLayout(getContext());
@@ -95,7 +122,11 @@
         // to keep these tests as pinpoint as possible and avoid bringing it too many dependencies,
         // but keep them in mind if something breaks. Commenting them out as is should work.
         //mLatinIME.onPressKey(codePoint);
-        mLatinIME.onCodeInput(codePoint, new int[] { codePoint },
+        int[] proximityKeys = mProximity.get(codePoint);
+        if (null == proximityKeys) {
+            proximityKeys = new int[] { codePoint };
+        }
+        mLatinIME.onCodeInput(codePoint, proximityKeys,
                 KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
                 KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
         //mLatinIME.onReleaseKey(codePoint, false);
@@ -139,4 +170,26 @@
         type(Keyboard.CODE_DELETE);
         assertEquals("delete selection", EXPECTED_RESULT, mTextView.getText().toString());
     }
+
+    public void testAutoCorrect() {
+        final String STRING_TO_TYPE = "tgis ";
+        final String EXPECTED_RESULT = "this ";
+        type(STRING_TO_TYPE);
+        assertEquals("simple auto-correct", EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testDoubleSpace() {
+        final String STRING_TO_TYPE = "this  ";
+        final String EXPECTED_RESULT = "this. ";
+        type(STRING_TO_TYPE);
+        assertEquals("double space make a period", EXPECTED_RESULT, mTextView.getText().toString());
+    }
+
+    public void testCancelDoubleSpace() {
+        final String STRING_TO_TYPE = "tgis  ";
+        final String EXPECTED_RESULT = "this  ";
+        type(STRING_TO_TYPE);
+        type(Keyboard.CODE_DELETE);
+        assertEquals("double space make a period", EXPECTED_RESULT, mTextView.getText().toString());
+    }
 }