merge in jb-ub-latinimegoogle-azuki history after reset to jb-ub-latinimegoogle
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index 2273394..4a8b955 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -20,14 +20,16 @@
 
     <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17" />
 
-    <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
-    <uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
+    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.READ_PROFILE" />
+    <uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
 
     <application android:label="@string/english_ime_name"
             android:icon="@mipmap/ic_ime_settings"
diff --git a/java/res/layout/dictionary_line.xml b/java/res/layout/dictionary_line.xml
index 26924a5..7268cd4 100644
--- a/java/res/layout/dictionary_line.xml
+++ b/java/res/layout/dictionary_line.xml
@@ -55,7 +55,8 @@
     <FrameLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginStart="5dip">
+        android:layout_marginStart="5dip"
+        android:layout_marginLeft="5dip">
 
       <TextView
           android:id="@+android:id/summary"
diff --git a/java/res/layout/setup_welcome_video.xml b/java/res/layout/setup_welcome_video.xml
index 3cc5f21..8c04e63 100644
--- a/java/res/layout/setup_welcome_video.xml
+++ b/java/res/layout/setup_welcome_video.xml
@@ -35,6 +35,12 @@
             android:layout_weight="@integer/setup_welcome_video_weight_in_screen"
             android:layout_width="0dp"
             android:layout_height="wrap_content" />
+        <ImageView
+            android:id="@+id/setup_welcome_image"
+            android:visibility="gone"
+            android:layout_weight="@integer/setup_welcome_video_weight_in_screen"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content" />
         <View
             android:layout_weight="@integer/setup_welcome_video_right_padding_weight_in_screen"
             android:layout_width="0dp"
diff --git a/java/res/raw/setup_welcome_image.png b/java/res/raw/setup_welcome_image.png
new file mode 100644
index 0000000..17e3111
--- /dev/null
+++ b/java/res/raw/setup_welcome_image.png
Binary files differ
diff --git a/java/res/values-sw600dp/config.xml b/java/res/values-sw600dp/config.xml
index 9527fd6..8265651 100644
--- a/java/res/values-sw600dp/config.xml
+++ b/java/res/values-sw600dp/config.xml
@@ -19,8 +19,6 @@
 -->
 
 <resources>
-    <!-- Device form factor. This value must be aligned with {@link KeyboardId.FORM_FACTOR_TABLET7} -->
-    <integer name="config_device_form_factor">1</integer>
     <bool name="config_enable_show_voice_key_option">false</bool>
     <bool name="config_enable_show_option_of_key_preview_popup">false</bool>
     <bool name="config_enable_bigram_suggestions_option">false</bool>
diff --git a/java/res/values-sw768dp/config.xml b/java/res/values-sw768dp/config.xml
index 3c2c198..97f11cb 100644
--- a/java/res/values-sw768dp/config.xml
+++ b/java/res/values-sw768dp/config.xml
@@ -19,8 +19,6 @@
 -->
 
 <resources>
-    <!-- Device form factor. This value must be aligned with {@link KeyboardId.FORM_FACTOR_TABLET10} -->
-    <integer name="config_device_form_factor">2</integer>
     <bool name="config_enable_show_voice_key_option">false</bool>
     <bool name="config_enable_show_option_of_key_preview_popup">false</bool>
     <bool name="config_enable_bigram_suggestions_option">false</bool>
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 5b11e07..23b5794 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -19,8 +19,6 @@
 -->
 
 <resources>
-    <!-- Device form factor. This value must be aligned with {@link KeyboardId.FORM_FACTOR_PHONE} -->
-    <integer name="config_device_form_factor">0</integer>
     <bool name="config_use_fullscreen_mode">false</bool>
     <bool name="config_enable_show_voice_key_option">true</bool>
     <bool name="config_enable_show_option_of_key_preview_popup">true</bool>
@@ -119,9 +117,6 @@
     <!-- Threshold of the normalized score of the best suggestion for the spell checker to declare
          a word to be "recommended" -->
     <string name="spellchecker_recommended_threshold_value" translatable="false">0.11</string>
-    <!-- Threshold of the normalized score of any dictionary lookup to be offered as a suggestion
-         by the spell checker -->
-    <string name="spellchecker_suggestion_threshold_value" translatable="false">0.03</string>
     <!--  Screen metrics for logging.
             0 = "mdpi phone screen"
             1 = "hdpi phone screen"
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index c76acd1..60d09d6 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -94,6 +94,8 @@
     public boolean onCustomRequest(int requestCode);
 
     public static class Adapter implements KeyboardActionListener {
+        public static final Adapter EMPTY_LISTENER = new Adapter();
+
         @Override
         public void onPressKey(int primaryCode) {}
         @Override
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index ee8ee9a..aa27067 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -56,13 +56,8 @@
     public static final int ELEMENT_PHONE_SYMBOLS = 8;
     public static final int ELEMENT_NUMBER = 9;
 
-    public static final int FORM_FACTOR_PHONE = 0;
-    public static final int FORM_FACTOR_TABLET7 = 1;
-    public static final int FORM_FACTOR_TABLET10 = 2;
-
     public final InputMethodSubtype mSubtype;
     public final Locale mLocale;
-    public final int mDeviceFormFactor;
     // TODO: Remove this member. It is used only for logging purpose.
     public final int mOrientation;
     public final int mWidth;
@@ -82,7 +77,6 @@
     public KeyboardId(final int elementId, final KeyboardLayoutSet.Params params) {
         mSubtype = params.mSubtype;
         mLocale = SubtypeLocale.getSubtypeLocale(mSubtype);
-        mDeviceFormFactor = params.mDeviceFormFactor;
         mOrientation = params.mOrientation;
         mWidth = params.mKeyboardWidth;
         mHeight = params.mKeyboardHeight;
@@ -107,7 +101,6 @@
 
     private static int computeHashCode(final KeyboardId id) {
         return Arrays.hashCode(new Object[] {
-                id.mDeviceFormFactor,
                 id.mOrientation,
                 id.mElementId,
                 id.mMode,
@@ -130,8 +123,7 @@
     private boolean equals(final KeyboardId other) {
         if (other == this)
             return true;
-        return other.mDeviceFormFactor == mDeviceFormFactor
-                && other.mOrientation == mOrientation
+        return other.mOrientation == mOrientation
                 && other.mElementId == mElementId
                 && other.mMode == mMode
                 && other.mWidth == mWidth
@@ -195,11 +187,11 @@
     public String toString() {
         final String orientation = (mOrientation == Configuration.ORIENTATION_PORTRAIT)
                 ? "port" : "land";
-        return String.format("[%s %s:%s %s-%s:%dx%d %s %s %s%s%s%s%s%s%s%s%s]",
+        return String.format("[%s %s:%s %s:%dx%d %s %s %s%s%s%s%s%s%s%s%s]",
                 elementIdToName(mElementId),
                 mLocale,
                 mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
-                deviceFormFactor(mDeviceFormFactor), orientation, mWidth, mHeight,
+                orientation, mWidth, mHeight,
                 modeName(mMode),
                 imeAction(),
                 (navigateNext() ? "navigateNext" : ""),
@@ -238,15 +230,6 @@
         }
     }
 
-    public static String deviceFormFactor(final int deviceFormFactor) {
-        switch (deviceFormFactor) {
-        case FORM_FACTOR_PHONE: return "phone";
-        case FORM_FACTOR_TABLET7: return "tablet7";
-        case FORM_FACTOR_TABLET10: return "tablet10";
-        default: return null;
-        }
-    }
-
     public static String modeName(final int mode) {
         switch (mode) {
         case MODE_TEXT: return "text";
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 5e68c70..bc9e8cd 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -106,7 +106,6 @@
         boolean mNoSettingsKey;
         boolean mLanguageSwitchKeyEnabled;
         InputMethodSubtype mSubtype;
-        int mDeviceFormFactor;
         int mOrientation;
         int mKeyboardWidth;
         int mKeyboardHeight;
@@ -217,10 +216,8 @@
                     mPackageName, NO_SETTINGS_KEY, mEditorInfo);
         }
 
-        public Builder setScreenGeometry(final int deviceFormFactor, final int widthPixels,
-                final int heightPixels) {
+        public Builder setScreenGeometry(final int widthPixels, final int heightPixels) {
             final Params params = mParams;
-            params.mDeviceFormFactor = deviceFormFactor;
             params.mOrientation = (heightPixels > widthPixels)
                     ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
             setDefaultKeyboardSize(widthPixels, heightPixels);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 4e41b77..39afe90 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -142,8 +142,7 @@
                 mThemeContext, editorInfo);
         final Resources res = mThemeContext.getResources();
         final DisplayMetrics dm = res.getDisplayMetrics();
-        builder.setScreenGeometry(res.getInteger(R.integer.config_device_form_factor),
-                dm.widthPixels, dm.heightPixels);
+        builder.setScreenGeometry(dm.widthPixels, dm.heightPixels);
         builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype());
         builder.setOptions(
                 settingsValues.isVoiceKeyEnabled(editorInfo),
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index c1b148d..0556fdd 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -176,7 +176,7 @@
     private DrawingProxy mDrawingProxy;
     private TimerProxy mTimerProxy;
     private KeyDetector mKeyDetector;
-    private KeyboardActionListener mListener = EMPTY_LISTENER;
+    private KeyboardActionListener mListener = KeyboardActionListener.Adapter.EMPTY_LISTENER;
 
     private Keyboard mKeyboard;
     private int mPhantonSuddenMoveThreshold;
@@ -333,10 +333,6 @@
     // true if a sliding key input is allowed.
     private boolean mIsAllowedSlidingKeyInput;
 
-    // Empty {@link KeyboardActionListener}
-    private static final KeyboardActionListener EMPTY_LISTENER =
-            new KeyboardActionListener.Adapter();
-
     private final GestureStrokeWithPreviewPoints mGestureStrokeWithPreviewPoints;
 
     public static void init(final boolean needsPhantomSuddenMoveEventHack) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index 53da47c..93ff264 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -83,9 +83,8 @@
         public final int mRecognitionMinimumTime; // msec
         public final float mRecognitionSpeedThreshold; // keyWidth/sec
 
-        // Default GestureStroke parameters for test.
-        public static final GestureStrokeParams FOR_TEST = new GestureStrokeParams();
-        public static final GestureStrokeParams DEFAULT = FOR_TEST;
+        // Default GestureStroke parameters.
+        public static final GestureStrokeParams DEFAULT = new GestureStrokeParams();
 
         private GestureStrokeParams() {
             // These parameter values are default and intended for testing.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
index 3315954..7a51e25 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
@@ -21,8 +21,6 @@
 public final class GestureStrokeWithPreviewPoints extends GestureStroke {
     public static final int PREVIEW_CAPACITY = 256;
 
-    private static final boolean ENABLE_INTERPOLATION = true;
-
     private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY);
     private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
     private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
@@ -32,21 +30,22 @@
     private final HermiteInterpolator mInterpolator = new HermiteInterpolator();
     private int mLastInterpolatedPreviewIndex;
 
-    private int mMinPreviewSamplingDistanceSquared;
     private int mLastX;
     private int mLastY;
     private double mMinPreviewSamplingDistance;
     private double mDistanceFromLastSample;
+    private double mInterpolationDistanceThreshold;
 
     // TODO: Move these constants to resource.
-    // The minimum linear distance between sample points for preview in keyWidth unit.
-    private static final float MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH = 0.1f;
+    // TODO: Use "dp" instead of ratio to the keyWidth because table has rather large keys.
     // The minimum trail distance between sample points for preview in keyWidth unit when using
     // interpolation.
-    private static final float MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH_WITH_INTERPOLATION = 0.2f;
+    private static final float MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH = 0.2f;
     // The angular threshold to use interpolation in radian. PI/12 is 15 degree.
     private static final double INTERPOLATION_ANGULAR_THRESHOLD = Math.PI / 12.0d;
-    private static final int MAX_INTERPOLATION_PARTITION = 4;
+    // The distance threshold to use interpolation in keyWidth unit.
+    private static final float INTERPOLATION_DISTANCE_THRESHOLD_TO_KEY_WIDTH = 0.5f;
+    private static final int MAX_INTERPOLATION_PARTITIONS = 6;
 
     public GestureStrokeWithPreviewPoints(final int pointerId, final GestureStrokeParams params) {
         super(pointerId, params);
@@ -70,31 +69,17 @@
     @Override
     public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) {
         super.setKeyboardGeometry(keyWidth, keyboardHeight);
-        final float samplingRatioToKeyWidth = ENABLE_INTERPOLATION
-                ? MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH_WITH_INTERPOLATION
-                : MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH;
-        mMinPreviewSamplingDistance = keyWidth * samplingRatioToKeyWidth;
-        mMinPreviewSamplingDistanceSquared = (int)(
-                mMinPreviewSamplingDistance * mMinPreviewSamplingDistance);
+        mMinPreviewSamplingDistance = keyWidth * MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH;
+        mInterpolationDistanceThreshold = keyWidth * INTERPOLATION_DISTANCE_THRESHOLD_TO_KEY_WIDTH;
     }
 
-    private boolean needsSampling(final int x, final int y, final boolean isMajorEvent) {
-        if (ENABLE_INTERPOLATION) {
-            mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY);
-            mLastX = x;
-            mLastY = y;
-            if (mDistanceFromLastSample >= mMinPreviewSamplingDistance) {
-                mDistanceFromLastSample = 0.0d;
-                return true;
-            }
-            return false;
-        }
-
-        final int dx = x - mLastX;
-        final int dy = y - mLastY;
-        if (isMajorEvent || dx * dx + dy * dy >= mMinPreviewSamplingDistanceSquared) {
-            mLastX = x;
-            mLastY = y;
+    private boolean needsSampling(final int x, final int y) {
+        mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY);
+        mLastX = x;
+        mLastY = y;
+        final boolean isDownEvent = (mPreviewEventTimes.getLength() == 0);
+        if (mDistanceFromLastSample >= mMinPreviewSamplingDistance || isDownEvent) {
+            mDistanceFromLastSample = 0.0d;
             return true;
         }
         return false;
@@ -103,7 +88,7 @@
     @Override
     public boolean addPointOnKeyboard(final int x, final int y, final int time,
             final boolean isMajorEvent) {
-        if (needsSampling(x, y, isMajorEvent)) {
+        if (needsSampling(x, y)) {
             mPreviewEventTimes.add(time);
             mPreviewXCoordinates.add(x);
             mPreviewYCoordinates.add(y);
@@ -140,9 +125,6 @@
     public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex,
             final ResizableIntArray eventTimes, final ResizableIntArray xCoords,
             final ResizableIntArray yCoords) {
-        if (!ENABLE_INTERPOLATION) {
-            return lastInterpolatedIndex;
-        }
         final int size = mPreviewEventTimes.getLength();
         final int[] pt = mPreviewEventTimes.getPrimitiveArray();
         final int[] px = mPreviewXCoordinates.getPrimitiveArray();
@@ -161,14 +143,20 @@
             mInterpolator.setInterval(p0, p1, p2, p3);
             final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X);
             final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X);
-            final double dm = Math.abs(angularDiff(m2, m1));
-            final int partition = Math.min((int)Math.ceil(dm / INTERPOLATION_ANGULAR_THRESHOLD),
-                    MAX_INTERPOLATION_PARTITION);
+            final double deltaAngle = Math.abs(angularDiff(m2, m1));
+            final int partitionsByAngle = (int)Math.ceil(
+                    deltaAngle / INTERPOLATION_ANGULAR_THRESHOLD);
+            final double deltaDistance = Math.hypot(mInterpolator.mP1X - mInterpolator.mP2X,
+                    mInterpolator.mP1Y - mInterpolator.mP2Y);
+            final int partitionsByDistance = (int)Math.ceil(deltaDistance
+                    / mInterpolationDistanceThreshold);
+            final int partitions = Math.min(MAX_INTERPOLATION_PARTITIONS,
+                    Math.max(partitionsByAngle, partitionsByDistance));
             final int t1 = eventTimes.get(d1);
             final int dt = pt[p2] - pt[p1];
             d1++;
-            for (int i = 1; i < partition; i++) {
-                final float t = i / (float)partition;
+            for (int i = 1; i < partitions; i++) {
+                final float t = i / (float)partitions;
                 mInterpolator.interpolate(t);
                 eventTimes.add(d1, (int)(dt * t) + t1);
                 xCoords.add(d1, (int)mInterpolator.mInterpolatedX);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index b1d4997..962bde9 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -96,16 +96,16 @@
         public boolean mIsValid;
         public boolean mIsAlphabetMode;
         public boolean mIsAlphabetShiftLocked;
-        public boolean mIsShifted;
+        public int mShiftMode;
 
         @Override
         public String toString() {
             if (!mIsValid) return "INVALID";
             if (mIsAlphabetMode) {
                 if (mIsAlphabetShiftLocked) return "ALPHABET_SHIFT_LOCKED";
-                return mIsShifted ? "ALPHABET_SHIFTED" : "ALPHABET";
+                return "ALPHABET_" + shiftModeToString(mShiftMode);
             } else {
-                return mIsShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS";
+                return "SYMBOLS_" + shiftModeToString(mShiftMode);
             }
         }
     }
@@ -128,16 +128,21 @@
         onRestoreKeyboardState();
     }
 
+    private static final int UNSHIFT = 0;
+    private static final int MANUAL_SHIFT = 1;
+    private static final int AUTOMATIC_SHIFT = 2;
+    private static final int SHIFT_LOCK_SHIFTED = 3;
+
     public void onSaveKeyboardState() {
         final SavedKeyboardState state = mSavedKeyboardState;
         state.mIsAlphabetMode = mIsAlphabetMode;
         if (mIsAlphabetMode) {
             state.mIsAlphabetShiftLocked = mAlphabetShiftState.isShiftLocked();
-            state.mIsShifted = !state.mIsAlphabetShiftLocked
-                    && mAlphabetShiftState.isShiftedOrShiftLocked();
+            state.mShiftMode = mAlphabetShiftState.isAutomaticShifted() ? AUTOMATIC_SHIFT
+                    : (mAlphabetShiftState.isShiftedOrShiftLocked() ? MANUAL_SHIFT : UNSHIFT);
         } else {
             state.mIsAlphabetShiftLocked = mPrevMainKeyboardWasShiftLocked;
-            state.mIsShifted = mIsSymbolShifted;
+            state.mShiftMode = mIsSymbolShifted ? MANUAL_SHIFT : UNSHIFT;
         }
         state.mIsValid = true;
         if (DEBUG_EVENT) {
@@ -153,7 +158,7 @@
         if (!state.mIsValid || state.mIsAlphabetMode) {
             setAlphabetKeyboard();
         } else {
-            if (state.mIsShifted) {
+            if (state.mShiftMode == MANUAL_SHIFT) {
                 setSymbolsShiftedKeyboard();
             } else {
                 setSymbolsKeyboard();
@@ -166,18 +171,13 @@
         if (state.mIsAlphabetMode) {
             setShiftLocked(state.mIsAlphabetShiftLocked);
             if (!state.mIsAlphabetShiftLocked) {
-                setShifted(state.mIsShifted ? MANUAL_SHIFT : UNSHIFT);
+                setShifted(state.mShiftMode);
             }
         } else {
             mPrevMainKeyboardWasShiftLocked = state.mIsAlphabetShiftLocked;
         }
     }
 
-    private static final int UNSHIFT = 0;
-    private static final int MANUAL_SHIFT = 1;
-    private static final int AUTOMATIC_SHIFT = 2;
-    private static final int SHIFT_LOCK_SHIFTED = 3;
-
     private void setShifted(final int shiftMode) {
         if (DEBUG_ACTION) {
             Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode) + " " + this);
@@ -634,7 +634,7 @@
         }
     }
 
-    private static String shiftModeToString(final int shiftMode) {
+    static String shiftModeToString(final int shiftMode) {
         switch (shiftMode) {
         case UNSHIFT: return "UNSHIFT";
         case MANUAL_SHIFT: return "MANUAL";
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index c8c7bb4..4fc1919 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -107,13 +107,16 @@
 
     @Override
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
-            final String prevWord, final ProximityInfo proximityInfo) {
-        return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, 0);
+            final String prevWord, final ProximityInfo proximityInfo,
+            final boolean blockOffensiveWords) {
+        return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
+                0 /* sessionId */);
     }
 
     @Override
     public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
-            final String prevWord, final ProximityInfo proximityInfo, int sessionId) {
+            final String prevWord, final ProximityInfo proximityInfo,
+            final boolean blockOffensiveWords, final int sessionId) {
         if (!isValidDictionary()) return null;
 
         Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
@@ -139,8 +142,6 @@
                 inputSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
                 mUseFullEditDistance, mOutputCodePoints, mOutputScores, mSpaceIndices,
                 mOutputTypes);
-        final boolean blockPotentiallyOffensive =
-                Settings.getInstance().getBlockPotentiallyOffensive();
         final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
         for (int j = 0; j < count; ++j) {
             final int start = j * MAX_WORD_LENGTH;
@@ -150,7 +151,7 @@
             }
             if (len > 0) {
                 final int flags = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_FLAGS;
-                if (blockPotentiallyOffensive
+                if (blockOffensiveWords
                         && 0 != (flags & SuggestedWordInfo.KIND_FLAG_POSSIBLY_OFFENSIVE)
                         && 0 == (flags & SuggestedWordInfo.KIND_FLAG_EXACT_MATCH)) {
                     // If we block potentially offensive words, and if the word is possibly
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 9691fa2..acd7c2a 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -51,18 +51,21 @@
      * @param composer the key sequence to match with coordinate info, as a WordComposer
      * @param prevWord the previous word, or null if none
      * @param proximityInfo the object for key proximity. May be ignored by some implementations.
+     * @param blockOffensiveWords whether to block potentially offensive words
      * @return the list of suggestions (possibly null if none)
      */
     // TODO: pass more context than just the previous word, to enable better suggestions (n-gram
     // and more)
     abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
-            final String prevWord, final ProximityInfo proximityInfo);
+            final String prevWord, final ProximityInfo proximityInfo,
+            final boolean blockOffensiveWords);
 
     // The default implementation of this method ignores sessionId.
     // Subclasses that want to use sessionId need to override this method.
     public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
-            final String prevWord, final ProximityInfo proximityInfo, final int sessionId) {
-        return getSuggestions(composer, prevWord, proximityInfo);
+            final String prevWord, final ProximityInfo proximityInfo,
+            final boolean blockOffensiveWords, final int sessionId) {
+        return getSuggestions(composer, prevWord, proximityInfo, blockOffensiveWords);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 2832ad4..ed2b442 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -56,18 +56,19 @@
 
     @Override
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
-            final String prevWord, final ProximityInfo proximityInfo) {
+            final String prevWord, final ProximityInfo proximityInfo,
+            final boolean blockOffensiveWords) {
         final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries;
         if (dictionaries.isEmpty()) return null;
         // To avoid creating unnecessary objects, we get the list out of the first
         // dictionary and add the rest to it if not null, hence the get(0)
         ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
-                prevWord, proximityInfo);
+                prevWord, proximityInfo, blockOffensiveWords);
         if (null == suggestions) suggestions = CollectionUtils.newArrayList();
         final int length = dictionaries.size();
         for (int i = 1; i < length; ++ i) {
             final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
-                    prevWord, proximityInfo);
+                    prevWord, proximityInfo, blockOffensiveWords);
             if (null != sugg) suggestions.addAll(sugg);
         }
         return suggestions;
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 4b1975a..887d657 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -200,12 +200,14 @@
 
     @Override
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
-            final String prevWord, final ProximityInfo proximityInfo) {
+            final String prevWord, final ProximityInfo proximityInfo,
+            final boolean blockOffensiveWords) {
         asyncReloadDictionaryIfRequired();
         if (mLocalDictionaryController.tryLock()) {
             try {
                 if (mBinaryDictionary != null) {
-                    return mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo);
+                    return mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
+                            blockOffensiveWords);
                 }
             } finally {
                 mLocalDictionaryController.unlock();
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index fd81d13..0dabdb8 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -253,7 +253,8 @@
 
     @Override
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
-            final String prevWord, final ProximityInfo proximityInfo) {
+            final String prevWord, final ProximityInfo proximityInfo,
+            final boolean blockOffensiveWords) {
         if (reloadDictionaryIfRequired()) return null;
         if (composer.size() > 1) {
             if (composer.size() >= Constants.Dictionary.MAX_WORD_LENGTH) {
diff --git a/java/src/com/android/inputmethod/latin/JniUtils.java b/java/src/com/android/inputmethod/latin/JniUtils.java
index f930599..8aedee5 100644
--- a/java/src/com/android/inputmethod/latin/JniUtils.java
+++ b/java/src/com/android/inputmethod/latin/JniUtils.java
@@ -23,15 +23,19 @@
 public final class JniUtils {
     private static final String TAG = JniUtils.class.getSimpleName();
 
-    private JniUtils() {
-        // This utility class is not publicly instantiable.
-    }
-
-    public static void loadNativeLibrary() {
+    static {
         try {
             System.loadLibrary(JniLibName.JNI_LIB_NAME);
         } catch (UnsatisfiedLinkError ule) {
             Log.e(TAG, "Could not load native library " + JniLibName.JNI_LIB_NAME, ule);
         }
     }
+
+    private JniUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static void loadNativeLibrary() {
+        // Ensures the static initializer is called
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 16eab4b..84c7529 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -929,8 +929,11 @@
                 resetEntireInputState(newSelStart);
             }
 
-            // We moved the cursor. If we are touching a word, we need to resume suggestion.
-            mHandler.postResumeSuggestions();
+            // We moved the cursor. If we are touching a word, we need to resume suggestion,
+            // unless suggestions are off.
+            if (isSuggestionsStripVisible()) {
+                mHandler.postResumeSuggestions();
+            }
             // Reset the last recapitalization.
             mRecapitalizeStatus.deactivate();
             mKeyboardSwitcher.updateShiftState();
@@ -1574,21 +1577,11 @@
                 commitTyped(LastComposedWord.NOT_A_SEPARATOR);
             }
             mExpectingUpdateSelection = true;
-            // The following is necessary for the case where the user typed something but didn't
-            // manual pick it and didn't input any separator: we want to put a space between what
-            // has been entered and the coming gesture input result, so we go into phantom space
-            // state, which will be promoted to a space when the gesture result is committed. But if
-            // the current input ends in a word connector on the other hand, then we want to have
-            // the next input stick to the current input so we don't switch to phantom space state.
-            if (!mSettings.getCurrent().isWordConnector(lastChar)) {
-                mSpaceState = SPACE_STATE_PHANTOM;
-            }
-        } else {
-            final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
-            if (Character.isLetter(codePointBeforeCursor)
-                    || mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
-                mSpaceState = SPACE_STATE_PHANTOM;
-            }
+        }
+        final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
+        if (Character.isLetterOrDigit(codePointBeforeCursor)
+                || mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
+            mSpaceState = SPACE_STATE_PHANTOM;
         }
         mConnection.endBatchEdit();
         mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
@@ -1902,6 +1895,8 @@
             final int y, final int spaceState) {
         boolean isComposingWord = mWordComposer.isComposingWord();
 
+        // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead.
+        // See onStartBatchInput() to see how to do it.
         if (SPACE_STATE_PHANTOM == spaceState &&
                 !mSettings.getCurrent().isWordConnector(primaryCode)) {
             if (isComposingWord) {
@@ -2108,6 +2103,8 @@
             return false;
         if (mSuggestionStripView.isShowingAddToDictionaryHint())
             return true;
+        if (null == mSettings.getCurrent())
+            return false;
         if (!mSettings.getCurrent().isSuggestionStripVisibleInOrientation(mDisplayOrientation))
             return false;
         if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn())
@@ -2180,6 +2177,7 @@
                 mConnection.getNthPreviousWord(mSettings.getCurrent().mWordSeparators,
                 mWordComposer.isComposingWord() ? 2 : 1);
         return mSuggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(),
+                mSettings.getBlockPotentiallyOffensive(),
                 mSettings.getCurrent().mCorrectionEnabled, sessionId);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 59d0207..dc9bef2 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -174,21 +174,22 @@
 
     public SuggestedWords getSuggestedWords(final WordComposer wordComposer,
             final String prevWordForBigram, final ProximityInfo proximityInfo,
-            final boolean isCorrectionEnabled, final int sessionId) {
+            final boolean blockOffensiveWords, final boolean isCorrectionEnabled,
+            final int sessionId) {
         LatinImeLogger.onStartSuggestion(prevWordForBigram);
         if (wordComposer.isBatchMode()) {
             return getSuggestedWordsForBatchInput(
-                    wordComposer, prevWordForBigram, proximityInfo, sessionId);
+                    wordComposer, prevWordForBigram, proximityInfo, blockOffensiveWords, sessionId);
         } else {
             return getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
-                    isCorrectionEnabled);
+                    blockOffensiveWords, isCorrectionEnabled);
         }
     }
 
     // Retrieves suggestions for the typing input.
     private SuggestedWords getSuggestedWordsForTypingInput(final WordComposer wordComposer,
             final String prevWordForBigram, final ProximityInfo proximityInfo,
-            final boolean isCorrectionEnabled) {
+            final boolean blockOffensiveWords, final boolean isCorrectionEnabled) {
         final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
         final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
                 MAX_SUGGESTIONS);
@@ -212,7 +213,7 @@
         for (final String key : mDictionaries.keySet()) {
             final Dictionary dictionary = mDictionaries.get(key);
             suggestionsSet.addAll(dictionary.getSuggestions(
-                    wordComposerForLookup, prevWordForBigram, proximityInfo));
+                    wordComposerForLookup, prevWordForBigram, proximityInfo, blockOffensiveWords));
         }
 
         final String whitelistedWord;
@@ -301,7 +302,7 @@
     // Retrieves suggestions for the batch input.
     private SuggestedWords getSuggestedWordsForBatchInput(final WordComposer wordComposer,
             final String prevWordForBigram, final ProximityInfo proximityInfo,
-            final int sessionId) {
+            final boolean blockOffensiveWords, final int sessionId) {
         final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
                 MAX_SUGGESTIONS);
 
@@ -314,8 +315,8 @@
                 continue;
             }
             final Dictionary dictionary = mDictionaries.get(key);
-            suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(
-                    wordComposer, prevWordForBigram, proximityInfo, sessionId));
+            suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(wordComposer,
+                    prevWordForBigram, proximityInfo, blockOffensiveWords, sessionId));
         }
 
         for (SuggestedWordInfo wordInfo : suggestionsSet) {
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
index ec4dc14..92f96c0 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
@@ -33,9 +33,10 @@
 
     @Override
     public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
-            final String prevWordForBigrams, final ProximityInfo proximityInfo) {
+            final String prevWordForBigrams, final ProximityInfo proximityInfo,
+            final boolean blockOffensiveWords) {
         syncReloadDictionaryIfRequired();
-        return super.getSuggestions(codes, prevWordForBigrams, proximityInfo);
+        return super.getSuggestions(codes, prevWordForBigrams, proximityInfo, blockOffensiveWords);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
index 4bdaf20..33fe896 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
@@ -36,9 +36,10 @@
 
     @Override
     public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
-            final String prevWordForBigrams, final ProximityInfo proximityInfo) {
+            final String prevWordForBigrams, final ProximityInfo proximityInfo,
+            final boolean blockOffensiveWords) {
         syncReloadDictionaryIfRequired();
-        return super.getSuggestions(codes, prevWordForBigrams, proximityInfo);
+        return super.getSuggestions(codes, prevWordForBigrams, proximityInfo, blockOffensiveWords);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
index 044180b..affe3a3 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
@@ -30,6 +30,7 @@
 import android.view.View;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
+import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.VideoView;
 
@@ -58,7 +59,7 @@
     private SetupStepGroup mSetupStepGroup;
     private static final String STATE_STEP = "step";
     private int mStepNumber;
-    private static final int STEP_0 = 0;
+    private static final int STEP_WELCOME = 0;
     private static final int STEP_1 = 1;
     private static final int STEP_2 = 2;
     private static final int STEP_3 = 3;
@@ -112,18 +113,20 @@
 
         if (savedInstanceState == null) {
             mStepNumber = determineSetupStepNumber();
+            if (mStepNumber == STEP_1 && !mWasLanguageAndInputSettingsInvoked) {
+                mStepNumber = STEP_WELCOME;
+            }
+            if (mStepNumber == STEP_3) {
+                // This IME already has been enabled and set as current IME.
+                // TODO: Implement tutorial.
+                invokeSettingsOfThisIme();
+                finish();
+                return;
+            }
         } else {
             mStepNumber = savedInstanceState.getInt(STATE_STEP);
         }
 
-        if (mStepNumber == STEP_3) {
-            // This IME already has been enabled and set as current IME.
-            // TODO: Implement tutorial.
-            invokeSettingsOfThisIme();
-            finish();
-            return;
-        }
-
         final String applicationName = getResources().getString(getApplicationInfo().labelRes);
         mWelcomeScreen = findViewById(R.id.setup_welcome_screen);
         final TextView welcomeTitle = (TextView)findViewById(R.id.setup_welcome_title);
@@ -201,11 +204,14 @@
                 mWelcomeVideoView.setBackgroundResource(0);
             }
         });
+        final ImageView welcomeImageView = (ImageView)findViewById(R.id.setup_welcome_image);
         mWelcomeVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
             @Override
             public boolean onError(final MediaPlayer mp, final int what, final int extra) {
                 Log.e(TAG, "Playing welcome video causes error: what=" + what + " extra=" + extra);
                 mWelcomeVideoView.setVisibility(View.GONE);
+                welcomeImageView.setImageResource(R.raw.setup_welcome_image);
+                welcomeImageView.setVisibility(View.VISIBLE);
                 return true;
             }
         });
@@ -226,13 +232,13 @@
             finish();
             return;
         }
-        final int stepState = determineSetupState();
+        final int currentStep = determineSetupStepNumber();
         final int nextStep;
         if (v == mActionStart) {
             nextStep = STEP_1;
         } else if (v == mActionNext) {
             nextStep = mStepNumber + 1;
-        } else if (v == mStep1Bullet && stepState == STEP_2) {
+        } else if (v == mStep1Bullet && currentStep == STEP_2) {
             nextStep = STEP_1;
         } else {
             nextStep = mStepNumber;
@@ -312,7 +318,7 @@
         return myImi.getId().equals(currentImeId);
     }
 
-    private int determineSetupState() {
+    private int determineSetupStepNumber() {
         mHandler.cancelPollingImeSettings();
         if (!isThisImeEnabled(this)) {
             return STEP_1;
@@ -323,14 +329,6 @@
         return STEP_3;
     }
 
-    private int determineSetupStepNumber() {
-        final int stepState = determineSetupState();
-        if (stepState == STEP_1) {
-            return mWasLanguageAndInputSettingsInvoked ? STEP_1 : STEP_0;
-        }
-        return stepState;
-    }
-
     @Override
     protected void onSaveInstanceState(final Bundle outState) {
         super.onSaveInstanceState(outState);
@@ -344,15 +342,11 @@
     }
 
     @Override
-    protected void onStart() {
-        super.onStart();
-        mStepNumber = determineSetupStepNumber();
-    }
-
-    @Override
     protected void onRestart() {
         super.onRestart();
-        mStepNumber = determineSetupStepNumber();
+        if (mStepNumber != STEP_WELCOME) {
+            mStepNumber = determineSetupStepNumber();
+        }
     }
 
     @Override
@@ -364,7 +358,7 @@
     @Override
     public void onBackPressed() {
         if (mStepNumber == STEP_1) {
-            mStepNumber = STEP_0;
+            mStepNumber = STEP_WELCOME;
             updateSetupStepView();
             return;
         }
@@ -380,15 +374,14 @@
     @Override
     public void onWindowFocusChanged(final boolean hasFocus) {
         super.onWindowFocusChanged(hasFocus);
-        if (!hasFocus) {
-            return;
+        if (hasFocus && mStepNumber != STEP_WELCOME) {
+            mStepNumber = determineSetupStepNumber();
+            updateSetupStepView();
         }
-        mStepNumber = determineSetupStepNumber();
-        updateSetupStepView();
     }
 
     private void updateSetupStepView() {
-        final boolean welcomeScreen = (mStepNumber == STEP_0);
+        final boolean welcomeScreen = (mStepNumber == STEP_WELCOME);
         mWelcomeScreen.setVisibility(welcomeScreen ? View.VISIBLE : View.GONE);
         mSetupScreen.setVisibility(welcomeScreen ? View.GONE: View.VISIBLE);
         if (welcomeScreen) {
@@ -397,7 +390,7 @@
             return;
         }
         mWelcomeVideoView.stopPlayback();
-        final boolean isStepActionAlreadyDone = mStepNumber < determineSetupState();
+        final boolean isStepActionAlreadyDone = mStepNumber < determineSetupStepNumber();
         mSetupStepGroup.enableStep(mStepNumber, isStepActionAlreadyDone);
         mActionNext.setVisibility(isStepActionAlreadyDone ? View.VISIBLE : View.GONE);
         mActionFinish.setVisibility((mStepNumber == STEP_3) ? View.VISIBLE : View.GONE);
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java b/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java
index ca974f6..974dfdd 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java
@@ -51,11 +51,32 @@
             mIndicatorView = indicatorView;
         }
 
+        // TODO: Once we stop supporting ICS, uncomment {@link #setPressed(boolean)} method and
+        // remove this method.
         @Override
-        public void setPressed(final boolean pressed) {
-            super.setPressed(pressed);
+        protected void drawableStateChanged() {
+            super.drawableStateChanged();
+            for (final int state : getDrawableState()) {
+                if (state == android.R.attr.state_pressed) {
+                    updateIndicatorView(true /* pressed */);
+                    return;
+                }
+            }
+            updateIndicatorView(false /* pressed */);
+        }
+
+        // TODO: Once we stop supporting ICS, uncomment this method and remove
+        // {@link #drawableStateChanged()} method.
+//        @Override
+//        public void setPressed(final boolean pressed) {
+//            super.setPressed(pressed);
+//            updateIndicatorView(pressed);
+//        }
+
+        private void updateIndicatorView(final boolean pressed) {
             if (mIndicatorView != null) {
                 mIndicatorView.setPressed(pressed);
+                mIndicatorView.invalidate();
             }
         }
     }
@@ -73,12 +94,6 @@
         }
 
         @Override
-        public void setPressed(final boolean pressed) {
-            super.setPressed(pressed);
-            invalidate();
-        }
-
-        @Override
         protected void onDraw(final Canvas canvas) {
             super.onDraw(canvas);
             final int layoutDirection = ViewCompatUtils.getLayoutDirection(this);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 2d0a89b..aa60496 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -64,8 +64,6 @@
             CollectionUtils.newSynchronizedTreeMap();
     private ContactsBinaryDictionary mContactsDictionary;
 
-    // The threshold for a candidate to be offered as a suggestion.
-    private float mSuggestionThreshold;
     // The threshold for a suggestion to be considered "recommended".
     private float mRecommendedThreshold;
     // Whether to use the contacts dictionary
@@ -112,8 +110,6 @@
 
     @Override public void onCreate() {
         super.onCreate();
-        mSuggestionThreshold =
-                Float.parseFloat(getString(R.string.spellchecker_suggestion_threshold_value));
         mRecommendedThreshold =
                 Float.parseFloat(getString(R.string.spellchecker_recommended_threshold_value));
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
@@ -198,8 +194,7 @@
     }
 
     public SuggestionsGatherer newSuggestionsGatherer(final String text, int maxLength) {
-        return new SuggestionsGatherer(
-                text, mSuggestionThreshold, mRecommendedThreshold, maxLength);
+        return new SuggestionsGatherer(text, mRecommendedThreshold, maxLength);
     }
 
     // TODO: remove this class and replace it by storage local to the session.
@@ -217,7 +212,6 @@
         private final ArrayList<String> mSuggestions;
         private final int[] mScores;
         private final String mOriginalText;
-        private final float mSuggestionThreshold;
         private final float mRecommendedThreshold;
         private final int mMaxLength;
         private int mLength = 0;
@@ -227,10 +221,9 @@
         private String mBestSuggestion = null;
         private int mBestScore = Integer.MIN_VALUE; // As small as possible
 
-        SuggestionsGatherer(final String originalText, final float suggestionThreshold,
-                final float recommendedThreshold, final int maxLength) {
+        SuggestionsGatherer(final String originalText, final float recommendedThreshold,
+                final int maxLength) {
             mOriginalText = originalText;
-            mSuggestionThreshold = suggestionThreshold;
             mRecommendedThreshold = recommendedThreshold;
             mMaxLength = maxLength;
             mSuggestions = CollectionUtils.newArrayList(maxLength + 1);
@@ -267,16 +260,7 @@
                 return true;
             }
 
-            // Compute the normalized score and skip this word if it's normalized score does not
-            // make the threshold.
             final String wordString = new String(word, wordOffset, wordLength);
-            final float normalizedScore =
-                    BinaryDictionary.calcNormalizedScore(mOriginalText, wordString, score);
-            if (normalizedScore < mSuggestionThreshold) {
-                if (DBG) Log.i(TAG, wordString + " does not make the score threshold");
-                return true;
-            }
-
             if (mLength < mMaxLength) {
                 final int copyLen = mLength - insertIndex;
                 ++mLength;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index da86572..61850e4 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -283,20 +283,6 @@
             //suggestionsLimit);
             final SuggestionsGatherer suggestionsGatherer = mService.newSuggestionsGatherer(
                     text, suggestionsLimit);
-            final WordComposer composer = new WordComposer();
-            final int length = text.length();
-            for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
-                final int codePoint = text.codePointAt(i);
-                // The getXYForCodePointAndScript method returns (Y << 16) + X
-                final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
-                        codePoint, mScript);
-                if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
-                    composer.add(codePoint,
-                            Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
-                } else {
-                    composer.add(codePoint, xy & 0xFFFF, xy >> 16);
-                }
-            }
 
             final int capitalizeType = StringUtils.getCapitalizationType(text);
             boolean isInDict = true;
@@ -306,9 +292,24 @@
                 if (!DictionaryPool.isAValidDictionary(dictInfo)) {
                     return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
                 }
+                final WordComposer composer = new WordComposer();
+                final int length = text.length();
+                for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
+                    final int codePoint = text.codePointAt(i);
+                    // The getXYForCodePointAndScript method returns (Y << 16) + X
+                    final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
+                            codePoint, mScript);
+                    if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
+                        composer.add(codePoint,
+                                Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+                    } else {
+                        composer.add(codePoint, xy & 0xFFFF, xy >> 16);
+                    }
+                }
+                // TODO: make a spell checker option to block offensive words or not
                 final ArrayList<SuggestedWordInfo> suggestions =
                         dictInfo.mDictionary.getSuggestions(composer, prevWord,
-                                dictInfo.mProximityInfo);
+                                dictInfo.mProximityInfo, true /* blockOffensiveWords */);
                 for (final SuggestedWordInfo suggestion : suggestions) {
                     final String suggestionStr = suggestion.mWord;
                     suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0,
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index 81dd92d..27964b3 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -51,7 +51,8 @@
             new Dictionary(Dictionary.TYPE_MAIN) {
                 @Override
                 public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
-                        final String prevWord, final ProximityInfo proximityInfo) {
+                        final String prevWord, final ProximityInfo proximityInfo,
+                        final boolean blockOffensiveWords) {
                     return noSuggestions;
                 }
                 @Override
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 3037669..09f81d4 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -23,19 +23,28 @@
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardActionListener;
 import com.android.inputmethod.keyboard.TypefaceUtils;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.Utils;
 
 public final class MoreSuggestions extends Keyboard {
     public static final int SUGGESTION_CODE_BASE = 1024;
 
-    MoreSuggestions(final MoreSuggestionsParam params) {
+    public final SuggestedWords mSuggestedWords;
+
+    public static abstract class MoreSuggestionsListener extends KeyboardActionListener.Adapter {
+        public abstract void onSuggestionSelected(final int index, final SuggestedWordInfo info);
+    }
+
+    MoreSuggestions(final MoreSuggestionsParam params, final SuggestedWords suggestedWords) {
         super(params);
+        mSuggestedWords = suggestedWords;
     }
 
     private static final class MoreSuggestionsParam extends KeyboardParams {
@@ -52,8 +61,9 @@
             super();
         }
 
-        public int layout(final SuggestedWords suggestions, final int fromPos, final int maxWidth,
-                final int minWidth, final int maxRow, final Paint paint, final Resources res) {
+        public int layout(final SuggestedWords suggestedWords, final int fromPos,
+                final int maxWidth, final int minWidth, final int maxRow, final Paint paint,
+                final Resources res) {
             clearKeys();
             mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
             mDividerWidth = mDivider.getIntrinsicWidth();
@@ -61,9 +71,9 @@
 
             int row = 0;
             int pos = fromPos, rowStartPos = fromPos;
-            final int size = Math.min(suggestions.size(), SuggestionStripView.MAX_SUGGESTIONS);
+            final int size = Math.min(suggestedWords.size(), SuggestionStripView.MAX_SUGGESTIONS);
             while (pos < size) {
-                final String word = suggestions.getWord(pos);
+                final String word = suggestedWords.getWord(pos);
                 // TODO: Should take care of text x-scaling.
                 mWidths[pos] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding);
                 final int numColumn = pos - rowStartPos + 1;
@@ -163,7 +173,7 @@
 
     public static final class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
         private final MoreSuggestionsView mPaneView;
-        private SuggestedWords mSuggestions;
+        private SuggestedWords mSuggestedWords;
         private int mFromPos;
         private int mToPos;
 
@@ -172,7 +182,7 @@
             mPaneView = paneView;
         }
 
-        public Builder layout(final SuggestedWords suggestions, final int fromPos,
+        public Builder layout(final SuggestedWords suggestedWords, final int fromPos,
                 final int maxWidth, final int minWidth, final int maxRow,
                 final Keyboard parentKeyboard) {
             final int xmlId = R.xml.kbd_suggestions_pane_template;
@@ -180,11 +190,11 @@
             mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
 
             mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight);
-            final int count = mParams.layout(suggestions, fromPos, maxWidth, minWidth, maxRow,
+            final int count = mParams.layout(suggestedWords, fromPos, maxWidth, minWidth, maxRow,
                     mPaneView.newLabelPaint(null /* key */), mResources);
             mFromPos = fromPos;
             mToPos = fromPos + count;
-            mSuggestions = suggestions;
+            mSuggestedWords = suggestedWords;
             return this;
         }
 
@@ -195,8 +205,8 @@
                 final int x = params.getX(pos);
                 final int y = params.getY(pos);
                 final int width = params.getWidth(pos);
-                final String word = mSuggestions.getWord(pos).toString();
-                final String info = Utils.getDebugInfo(mSuggestions, pos);
+                final String word = mSuggestedWords.getWord(pos);
+                final String info = Utils.getDebugInfo(mSuggestedWords, pos);
                 final int index = pos + SUGGESTION_CODE_BASE;
                 final Key key = new Key(
                         params, word, info, KeyboardIconsSet.ICON_UNDEFINED, index, null, x, y,
@@ -211,7 +221,7 @@
                     params.onAddKey(divider);
                 }
             }
-            return new MoreSuggestions(params);
+            return new MoreSuggestions(params, mSuggestedWords);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 6509f39..d585b5c 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -18,15 +18,21 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.util.Log;
 
+import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.MoreKeysKeyboardView;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener;
 
 /**
  * A view that renders a virtual {@link MoreSuggestions}. It handles rendering of keys and detecting
  * key presses and touch movements.
  */
 public final class MoreSuggestionsView extends MoreKeysKeyboardView {
+    private static final String TAG = MoreSuggestionsView.class.getSimpleName();
+
     public MoreSuggestionsView(final Context context, final AttributeSet attrs) {
         this(context, attrs, R.attr.moreSuggestionsViewStyle);
     }
@@ -54,9 +60,24 @@
 
     @Override
     public void onCodeInput(final int code, final int x, final int y) {
-        final int index = code - MoreSuggestions.SUGGESTION_CODE_BASE;
-        if (index >= 0 && index < SuggestionStripView.MAX_SUGGESTIONS) {
-            mListener.onCustomRequest(index);
+        final Keyboard keyboard = getKeyboard();
+        if (!(keyboard instanceof MoreSuggestions)) {
+            Log.e(TAG, "Expected keyboard is MoreSuggestions, but found "
+                    + keyboard.getClass().getName());
+            return;
         }
+        final SuggestedWords suggestedWords = ((MoreSuggestions)keyboard).mSuggestedWords;
+        final int index = code - MoreSuggestions.SUGGESTION_CODE_BASE;
+        if (index < 0 || index >= suggestedWords.size()) {
+            Log.e(TAG, "Selected suggestion has an illegal index: " + index);
+            return;
+        }
+        if (!(mListener instanceof MoreSuggestionsListener)) {
+            Log.e(TAG, "Expected mListener is MoreSuggestionsListener, but found "
+                    + mListener.getClass().getName());
+            return;
+        }
+        ((MoreSuggestionsListener)mListener).onSuggestionSelected(
+                index, suggestedWords.getInfo(index));
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 2a21ec2..ad350a0 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -50,7 +50,6 @@
 import android.widget.TextView;
 
 import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardActionListener;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.keyboard.MoreKeysPanel;
@@ -65,6 +64,7 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener;
 import com.android.inputmethod.research.ResearchLogger;
 
 import java.util.ArrayList;
@@ -93,7 +93,7 @@
     private final ArrayList<View> mDividers = CollectionUtils.newArrayList();
 
     Listener mListener;
-    SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
+    private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
 
     private final SuggestionStripViewParams mParams;
     private static final float MIN_TEXT_XSCALE = 0.70f;
@@ -652,15 +652,11 @@
         dismissMoreSuggestions();
     }
 
-    private final KeyboardActionListener mMoreSuggestionsListener =
-            new KeyboardActionListener.Adapter() {
+    private final MoreSuggestionsListener mMoreSuggestionsListener = new MoreSuggestionsListener() {
         @Override
-        public boolean onCustomRequest(final int requestCode) {
-            final int index = requestCode;
-            final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index);
+        public void onSuggestionSelected(final int index, final SuggestedWordInfo wordInfo) {
             mListener.pickSuggestionManually(index, wordInfo);
             dismissMoreSuggestions();
-            return true;
         }
 
         @Override
diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp
index 0c65939..61bf3f6 100644
--- a/native/jni/src/correction.cpp
+++ b/native/jni/src/correction.cpp
@@ -23,6 +23,8 @@
 #include "defines.h"
 #include "proximity_info_state.h"
 #include "suggest_utils.h"
+#include "suggest/policyimpl/utils/edit_distance.h"
+#include "suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h"
 
 namespace latinime {
 
@@ -906,50 +908,11 @@
     return totalFreq;
 }
 
-/* Damerau-Levenshtein distance */
-inline static int editDistanceInternal(int *editDistanceTable, const int *before,
-        const int beforeLength, const int *after, const int afterLength) {
-    // dp[li][lo] dp[a][b] = dp[ a * lo + b]
-    int *dp = editDistanceTable;
-    const int li = beforeLength + 1;
-    const int lo = afterLength + 1;
-    for (int i = 0; i < li; ++i) {
-        dp[lo * i] = i;
-    }
-    for (int i = 0; i < lo; ++i) {
-        dp[i] = i;
-    }
-
-    for (int i = 0; i < li - 1; ++i) {
-        for (int j = 0; j < lo - 1; ++j) {
-            const int ci = toBaseLowerCase(before[i]);
-            const int co = toBaseLowerCase(after[j]);
-            const int cost = (ci == co) ? 0 : 1;
-            dp[(i + 1) * lo + (j + 1)] = min(dp[i * lo + (j + 1)] + 1,
-                    min(dp[(i + 1) * lo + j] + 1, dp[i * lo + j] + cost));
-            if (i > 0 && j > 0 && ci == toBaseLowerCase(after[j - 1])
-                    && co == toBaseLowerCase(before[i - 1])) {
-                dp[(i + 1) * lo + (j + 1)] = min(
-                        dp[(i + 1) * lo + (j + 1)], dp[(i - 1) * lo + (j - 1)] + cost);
-            }
-        }
-    }
-
-    if (DEBUG_EDIT_DISTANCE) {
-        AKLOGI("IN = %d, OUT = %d", beforeLength, afterLength);
-        for (int i = 0; i < li; ++i) {
-            for (int j = 0; j < lo; ++j) {
-                AKLOGI("EDIT[%d][%d], %d", i, j, dp[i * lo + j]);
-            }
-        }
-    }
-    return dp[li * lo - 1];
-}
-
 /* static */ int Correction::RankingAlgorithm::editDistance(const int *before,
         const int beforeLength, const int *after, const int afterLength) {
-    int table[(beforeLength + 1) * (afterLength + 1)];
-    return editDistanceInternal(table, before, beforeLength, after, afterLength);
+    const DamerauLevenshteinEditDistancePolicy daemaruLevenshtein(
+            before, beforeLength, after, afterLength);
+    return static_cast<int>(EditDistance::getEditDistance(&daemaruLevenshtein));
 }
 
 
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index 3221dee..a187948 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -163,9 +163,14 @@
                 terminalDicNode->getFlags(), terminalDicNode->getAttributesPos());
         const bool isPossiblyOffensiveWord = terminalDicNode->getProbability() <= 0;
         const bool isExactMatch = terminalDicNode->isExactMatch();
+        const bool isFirstCharUppercase = terminalDicNode->isFirstCharUppercase();
+        // Heuristic: We exclude freq=0 first-char-uppercase words from exact match.
+        // (e.g. "AMD" and "and")
+        const bool isSafeExactMatch = isExactMatch
+                && !(isPossiblyOffensiveWord && isFirstCharUppercase);
         const int outputTypeFlags =
-                isPossiblyOffensiveWord ? Dictionary::KIND_FLAG_POSSIBLY_OFFENSIVE : 0
-                | isExactMatch ? Dictionary::KIND_FLAG_EXACT_MATCH : 0;
+                (isPossiblyOffensiveWord ? Dictionary::KIND_FLAG_POSSIBLY_OFFENSIVE : 0)
+                | (isSafeExactMatch ? Dictionary::KIND_FLAG_EXACT_MATCH : 0);
 
         // Entries that are blacklisted or do not represent a word should not be output.
         const bool isValidWord = !terminalAttributes.isBlacklistedOrNotAWord();
diff --git a/native/jni/src/suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h b/native/jni/src/suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h
new file mode 100644
index 0000000..ec14574
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#ifndef LATINIME_DAEMARU_LEVENSHTEIN_EDIT_DISTANCE_POLICY_H
+#define LATINIME_DAEMARU_LEVENSHTEIN_EDIT_DISTANCE_POLICY_H
+
+#include "char_utils.h"
+#include "suggest/policyimpl/utils/edit_distance_policy.h"
+
+namespace latinime {
+
+class DamerauLevenshteinEditDistancePolicy : public EditDistancePolicy {
+ public:
+    DamerauLevenshteinEditDistancePolicy(const int *const string0, const int length0,
+            const int *const string1, const int length1)
+            : mString0(string0), mString0Length(length0), mString1(string1),
+              mString1Length(length1) {}
+    ~DamerauLevenshteinEditDistancePolicy() {}
+
+    AK_FORCE_INLINE float getSubstitutionCost(const int index0, const int index1) const {
+        const int c0 = toBaseLowerCase(mString0[index0]);
+        const int c1 = toBaseLowerCase(mString1[index1]);
+        return (c0 == c1) ? 0.0f : 1.0f;
+    }
+
+    AK_FORCE_INLINE float getDeletionCost(const int index0, const int index1) const {
+        return 1.0f;
+    }
+
+    AK_FORCE_INLINE float getInsertionCost(const int index0, const int index1) const {
+        return 1.0f;
+    }
+
+    AK_FORCE_INLINE bool allowTransposition(const int index0, const int index1) const {
+        const int c0 = toBaseLowerCase(mString0[index0]);
+        const int c1 = toBaseLowerCase(mString1[index1]);
+        if (index0 > 0 && index1 > 0 && c0 == toBaseLowerCase(mString1[index1 - 1])
+                && c1 == toBaseLowerCase(mString0[index0 - 1])) {
+            return true;
+        }
+        return false;
+    }
+
+    AK_FORCE_INLINE float getTranspositionCost(const int index0, const int index1) const {
+        return getSubstitutionCost(index0, index1);
+    }
+
+    AK_FORCE_INLINE int getString0Length() const {
+        return mString0Length;
+    }
+
+    AK_FORCE_INLINE int getString1Length() const {
+        return mString1Length;
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN (DamerauLevenshteinEditDistancePolicy);
+
+    const int *const mString0;
+    const int mString0Length;
+    const int *const mString1;
+    const int mString1Length;
+};
+} // namespace latinime
+
+#endif  // LATINIME_DAEMARU_LEVENSHTEIN_EDIT_DISTANCE_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/utils/edit_distance.h b/native/jni/src/suggest/policyimpl/utils/edit_distance.h
new file mode 100644
index 0000000..cbbd668
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/utils/edit_distance.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#ifndef LATINIME_EDIT_DISTANCE_H
+#define LATINIME_EDIT_DISTANCE_H
+
+#include "defines.h"
+#include "suggest/policyimpl/utils/edit_distance_policy.h"
+
+namespace latinime {
+
+class EditDistance {
+ public:
+    // CAVEAT: There may be performance penalty if you need the edit distance as an integer value.
+    AK_FORCE_INLINE static float getEditDistance(const EditDistancePolicy *const policy) {
+        const int beforeLength = policy->getString0Length();
+        const int afterLength = policy->getString1Length();
+        float dp[(beforeLength + 1) * (afterLength + 1)];
+        for (int i = 0; i <= beforeLength; ++i) {
+            dp[(afterLength + 1) * i] = i * policy->getInsertionCost(i - 1, -1);
+        }
+        for (int i = 0; i <= afterLength; ++i) {
+            dp[i] = i * policy->getDeletionCost(-1, i - 1);
+        }
+
+        for (int i = 0; i < beforeLength; ++i) {
+            for (int j = 0; j < afterLength; ++j) {
+                dp[(afterLength + 1) * (i + 1) + (j + 1)] = min(
+                        dp[(afterLength + 1) * i + (j + 1)] + policy->getInsertionCost(i, j),
+                        min(dp[(afterLength + 1) * (i + 1) + j] + policy->getDeletionCost(i, j),
+                                dp[(afterLength + 1) * i + j]
+                                        + policy->getSubstitutionCost(i, j)));
+                if (policy->allowTransposition(i, j)) {
+                    dp[(afterLength + 1) * (i + 1) + (j + 1)] = min(
+                            dp[(afterLength + 1) * (i + 1) + (j + 1)],
+                            dp[(afterLength + 1) * (i - 1) + (j - 1)]
+                                    + policy->getTranspositionCost(i, j));
+                }
+            }
+        }
+        if (DEBUG_EDIT_DISTANCE) {
+            AKLOGI("IN = %d, OUT = %d", beforeLength, afterLength);
+            for (int i = 0; i < beforeLength + 1; ++i) {
+                for (int j = 0; j < afterLength + 1; ++j) {
+                    AKLOGI("EDIT[%d][%d], %f", i, j, dp[(afterLength + 1) * i + j]);
+                }
+            }
+        }
+        return dp[(beforeLength + 1) * (afterLength + 1) - 1];
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(EditDistance);
+};
+} // namespace latinime
+
+#endif  // LATINIME_EDIT_DISTANCE_H
diff --git a/native/jni/src/suggest/policyimpl/utils/edit_distance_policy.h b/native/jni/src/suggest/policyimpl/utils/edit_distance_policy.h
new file mode 100644
index 0000000..e3d1792
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/utils/edit_distance_policy.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#ifndef LATINIME_EDIT_DISTANCE_POLICY_H
+#define LATINIME_EDIT_DISTANCE_POLICY_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class EditDistancePolicy {
+ public:
+    virtual float getSubstitutionCost(const int index0, const int index1) const = 0;
+    virtual float getDeletionCost(const int index0, const int index1) const = 0;
+    virtual float getInsertionCost(const int index0, const int index1) const = 0;
+    virtual bool allowTransposition(const int index0, const int index1) const = 0;
+    virtual float getTranspositionCost(const int index0, const int index1) const = 0;
+    virtual int getString0Length() const = 0;
+    virtual int getString1Length() const = 0;
+
+ protected:
+    EditDistancePolicy() {}
+    virtual ~EditDistancePolicy() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(EditDistancePolicy);
+};
+} // namespace latinime
+
+#endif  // LATINIME_EDIT_DISTANCE_POLICY_H
diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
index 6bb5ada..01814ae 100644
--- a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
@@ -17,11 +17,11 @@
 package com.android.inputmethod.keyboard;
 
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.inputmethod.keyboard.MoreKeysKeyboard.MoreKeysKeyboardParams;
 
-@SmallTest
+@MediumTest
 public class MoreKeysKeyboardBuilderFixedOrderTests extends AndroidTestCase {
     private static final int WIDTH = 10;
     private static final int HEIGHT = 10;
diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
index 99da481..ce5573d 100644
--- a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
@@ -17,11 +17,11 @@
 package com.android.inputmethod.keyboard;
 
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.inputmethod.keyboard.MoreKeysKeyboard.MoreKeysKeyboardParams;
 
-@SmallTest
+@MediumTest
 public class MoreKeysKeyboardBuilderTests extends AndroidTestCase {
     private static final int WIDTH = 10;
     private static final int HEIGHT = 10;
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
index d05aabf..9014e7c 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
@@ -18,7 +18,7 @@
 
 import android.app.Instrumentation;
 import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.StringUtils;
@@ -28,7 +28,7 @@
 import java.util.Arrays;
 import java.util.Locale;
 
-@SmallTest
+@MediumTest
 public class KeySpecParserCsvTests extends InstrumentationTestCase {
     private final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
 
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
index df9ce5e..a3f9dbd 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
@@ -597,6 +597,17 @@
         // Rotate device, remain in alphabet.
         rotateDevice(ALPHABET_UNSHIFTED);
 
+        // Alphabet automatic shifted -> rotate -> automatic shifted.
+        // Set capitalize the first character of all words mode.
+        setAutoCapsMode(CAP_MODE_WORDS);
+        // Press/release auto caps trigger letter to enter alphabet automatic shifted.
+        pressAndReleaseKey(CODE_AUTO_CAPS_TRIGGER, ALPHABET_UNSHIFTED, ALPHABET_AUTOMATIC_SHIFTED);
+        // Rotate device, remain in alphabet.
+        rotateDevice(ALPHABET_AUTOMATIC_SHIFTED);
+        setAutoCapsMode(CAP_MODE_OFF);
+        // Press/release auto caps trigger letter to reset shift state.
+        pressAndReleaseKey(CODE_AUTO_CAPS_TRIGGER, ALPHABET_AUTOMATIC_SHIFTED, ALPHABET_UNSHIFTED);
+
         // Alphabet shifted -> rotate -> alphabet shifted.
         // Press/release shift key, enter alphabet shifted.
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
diff --git a/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java b/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java
index 7275d3a..c4fd5a0 100644
--- a/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java
+++ b/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java
@@ -30,7 +30,7 @@
         type(STRING_TO_TYPE);
         sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
         runMessages();
-        final SpanGetter span = new SpanGetter(mTextView.getText(), SuggestionSpan.class);
+        final SpanGetter span = new SpanGetter(mEditText.getText(), SuggestionSpan.class);
         assertEquals("show blue underline, span start", EXPECTED_SPAN_START, span.mStart);
         assertEquals("show blue underline, span end", EXPECTED_SPAN_END, span.mEnd);
         assertEquals("show blue underline, span color", true, span.isAutoCorrectionIndicator());
@@ -47,7 +47,7 @@
         type(STRING_2_TO_TYPE);
         // We haven't have time to look into the dictionary yet, so the line should still be
         // blue to avoid any flicker.
-        final SpanGetter spanBefore = new SpanGetter(mTextView.getText(), SuggestionSpan.class);
+        final SpanGetter spanBefore = new SpanGetter(mEditText.getText(), SuggestionSpan.class);
         assertEquals("extend blue underline, span start", EXPECTED_SPAN_START, spanBefore.mStart);
         assertEquals("extend blue underline, span end", EXPECTED_SPAN_END, spanBefore.mEnd);
         assertEquals("extend blue underline, span color", true,
@@ -55,7 +55,7 @@
         sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
         runMessages();
         // Now we have been able to re-evaluate the word, there shouldn't be an auto-correction span
-        final SpanGetter spanAfter = new SpanGetter(mTextView.getText(), SuggestionSpan.class);
+        final SpanGetter spanAfter = new SpanGetter(mEditText.getText(), SuggestionSpan.class);
         assertNull("hide blue underline", spanAfter.mSpan);
     }
 
@@ -76,10 +76,10 @@
         type(Constants.CODE_DELETE);
         sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
         runMessages();
-        final SpanGetter suggestionSpan = new SpanGetter(mTextView.getText(), SuggestionSpan.class);
+        final SpanGetter suggestionSpan = new SpanGetter(mEditText.getText(), SuggestionSpan.class);
         assertEquals("show no blue underline after backspace, span start should be -1",
                 EXPECTED_SUGGESTION_SPAN_START, suggestionSpan.mStart);
-        final SpanGetter underlineSpan = new SpanGetter(mTextView.getText(), UnderlineSpan.class);
+        final SpanGetter underlineSpan = new SpanGetter(mEditText.getText(), UnderlineSpan.class);
         assertEquals("should be composing, so should have an underline span",
                 EXPECTED_UNDERLINE_SPAN_START, underlineSpan.mStart);
         assertEquals("should be composing, so should have an underline span",
@@ -103,7 +103,7 @@
                 NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1);
         sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
         runMessages();
-        final SpanGetter span = new SpanGetter(mTextView.getText(), SuggestionSpan.class);
+        final SpanGetter span = new SpanGetter(mEditText.getText(), SuggestionSpan.class);
         assertNull("blue underline removed when cursor is moved", span.mSpan);
     }
 
@@ -117,7 +117,7 @@
         // Here the blue underline has been set. testBlueUnderline() is testing for this already,
         // so let's not test it here again.
         // Now simulate the user moving the cursor.
-        SpanGetter span = new SpanGetter(mTextView.getText(), UnderlineSpan.class);
+        SpanGetter span = new SpanGetter(mEditText.getText(), UnderlineSpan.class);
         assertNull("should not be composing, so should not have an underline span", span.mSpan);
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index 72c8d9c..9140197 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -24,7 +24,7 @@
     public void testTypeWord() {
         final String WORD_TO_TYPE = "abcd";
         type(WORD_TO_TYPE);
-        assertEquals("type word", WORD_TO_TYPE, mTextView.getText().toString());
+        assertEquals("type word", WORD_TO_TYPE, mEditText.getText().toString());
     }
 
     public void testPickSuggestionThenBackspace() {
@@ -35,7 +35,7 @@
         mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1);
         type(Constants.CODE_DELETE);
         assertEquals("press suggestion then backspace", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testPickAutoCorrectionThenBackspace() {
@@ -48,10 +48,10 @@
         pickSuggestionManually(0, WORD_TO_PICK);
         mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1);
         assertEquals("pick typed word over auto-correction then backspace", WORD_TO_PICK,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
         type(Constants.CODE_DELETE);
         assertEquals("pick typed word over auto-correction then backspace", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testPickTypedWordOverAutoCorrectionThenBackspace() {
@@ -63,10 +63,10 @@
         pickSuggestionManually(1, WORD_TO_TYPE);
         mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1);
         assertEquals("pick typed word over auto-correction then backspace", WORD_TO_TYPE,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
         type(Constants.CODE_DELETE);
         assertEquals("pick typed word over auto-correction then backspace", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testPickDifferentSuggestionThenBackspace() {
@@ -79,10 +79,10 @@
         pickSuggestionManually(2, WORD_TO_PICK);
         mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1);
         assertEquals("pick different suggestion then backspace", WORD_TO_PICK,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
         type(Constants.CODE_DELETE);
         assertEquals("pick different suggestion then backspace", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testDeleteSelection() {
@@ -102,7 +102,7 @@
         mLatinIME.onUpdateSelection(typedLength, typedLength,
                 SELECTION_START, SELECTION_END, -1, -1);
         type(Constants.CODE_DELETE);
-        assertEquals("delete selection", EXPECTED_RESULT, mTextView.getText().toString());
+        assertEquals("delete selection", EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testDeleteSelectionTwice() {
@@ -123,21 +123,21 @@
                 SELECTION_START, SELECTION_END, -1, -1);
         type(Constants.CODE_DELETE);
         type(Constants.CODE_DELETE);
-        assertEquals("delete selection twice", EXPECTED_RESULT, mTextView.getText().toString());
+        assertEquals("delete selection twice", EXPECTED_RESULT, mEditText.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());
+        assertEquals("simple auto-correct", EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testAutoCorrectWithPeriod() {
         final String STRING_TO_TYPE = "tgis.";
         final String EXPECTED_RESULT = "this.";
         type(STRING_TO_TYPE);
-        assertEquals("auto-correct with period", EXPECTED_RESULT, mTextView.getText().toString());
+        assertEquals("auto-correct with period", EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testAutoCorrectWithPeriodThenRevert() {
@@ -147,7 +147,7 @@
         mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1);
         type(Constants.CODE_DELETE);
         assertEquals("auto-correct with period then revert", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testAutoCorrectWithSpaceThenRevert() {
@@ -157,7 +157,7 @@
         mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1);
         type(Constants.CODE_DELETE);
         assertEquals("auto-correct with space then revert", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testAutoCorrectToSelfDoesNotRevert() {
@@ -167,14 +167,14 @@
         mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1);
         type(Constants.CODE_DELETE);
         assertEquals("auto-correct with space does not revert", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.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());
+        assertEquals("double space make a period", EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testCancelDoubleSpace() {
@@ -182,7 +182,7 @@
         final String EXPECTED_RESULT = "this  ";
         type(STRING_TO_TYPE);
         type(Constants.CODE_DELETE);
-        assertEquals("double space make a period", EXPECTED_RESULT, mTextView.getText().toString());
+        assertEquals("double space make a period", EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testBackspaceAtStartAfterAutocorrect() {
@@ -197,7 +197,7 @@
                 NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1);
         type(Constants.CODE_DELETE);
         assertEquals("auto correct then move cursor to start of line then backspace",
-                EXPECTED_RESULT, mTextView.getText().toString());
+                EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testAutoCorrectThenMoveCursorThenBackspace() {
@@ -212,7 +212,7 @@
                 NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1);
         type(Constants.CODE_DELETE);
         assertEquals("auto correct then move cursor then backspace",
-                EXPECTED_RESULT, mTextView.getText().toString());
+                EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testNoSpaceAfterManualPick() {
@@ -221,7 +221,7 @@
         type(WORD_TO_TYPE);
         pickSuggestionManually(0, WORD_TO_TYPE);
         assertEquals("no space after manual pick", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testManualPickThenType() {
@@ -231,7 +231,7 @@
         type(WORD1_TO_TYPE);
         pickSuggestionManually(0, WORD1_TO_TYPE);
         type(WORD2_TO_TYPE);
-        assertEquals("manual pick then type", EXPECTED_RESULT, mTextView.getText().toString());
+        assertEquals("manual pick then type", EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testManualPickThenSeparator() {
@@ -241,7 +241,7 @@
         type(WORD1_TO_TYPE);
         pickSuggestionManually(0, WORD1_TO_TYPE);
         type(WORD2_TO_TYPE);
-        assertEquals("manual pick then separator", EXPECTED_RESULT, mTextView.getText().toString());
+        assertEquals("manual pick then separator", EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testManualPickThenStripperThenPick() {
@@ -254,7 +254,7 @@
         type(WORD_TO_TYPE);
         pickSuggestionManually(0, WORD_TO_TYPE);
         assertEquals("manual pick then \\n then manual pick", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testManualPickThenSpaceThenType() {
@@ -265,7 +265,7 @@
         pickSuggestionManually(0, WORD1_TO_TYPE);
         type(WORD2_TO_TYPE);
         assertEquals("manual pick then space then type", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testManualPickThenManualPick() {
@@ -279,7 +279,7 @@
         // to actually pass the right string.
         pickSuggestionManually(1, WORD2_TO_PICK);
         assertEquals("manual pick then manual pick", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testDeleteWholeComposingWord() {
@@ -288,7 +288,7 @@
         for (int i = 0; i < WORD_TO_TYPE.length(); ++i) {
             type(Constants.CODE_DELETE);
         }
-        assertEquals("delete whole composing word", "", mTextView.getText().toString());
+        assertEquals("delete whole composing word", "", mEditText.getText().toString());
     }
     // TODO: Add some tests for non-BMP characters
 }
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java b/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
index 333b602..2d736e3 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
@@ -30,7 +30,7 @@
         changeLanguage("fr");
         type(STRING_TO_TYPE);
         assertEquals("simple auto-correct for French", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testManualPickThenSeparatorForFrench() {
@@ -42,7 +42,7 @@
         pickSuggestionManually(0, WORD1_TO_TYPE);
         type(WORD2_TO_TYPE);
         assertEquals("manual pick then separator for French", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testWordThenSpaceThenPunctuationFromStripTwiceForFrench() {
@@ -64,7 +64,7 @@
             pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
             pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
             assertEquals("type word then type space then punctuation from strip twice for French",
-                    EXPECTED_RESULT, mTextView.getText().toString());
+                    EXPECTED_RESULT, mEditText.getText().toString());
         } finally {
             setBooleanPreference(NEXT_WORD_PREDICTION_OPTION, previousNextWordPredictionOption,
                     defaultNextWordPredictionOption);
@@ -98,7 +98,7 @@
         changeLanguage("de");
         type(STRING_TO_TYPE);
         assertEquals("simple auto-correct for German", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testAutoCorrectWithUmlautForGerman() {
@@ -107,6 +107,6 @@
         changeLanguage("de");
         type(STRING_TO_TYPE);
         assertEquals("auto-correct with umlaut for German", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index 9e107a4..807c9f3 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.Looper;
-import android.os.MessageQueue;
 import android.preference.PreferenceManager;
 import android.test.ServiceTestCase;
 import android.text.InputType;
@@ -31,8 +30,8 @@
 import android.view.ViewGroup;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
+import android.widget.EditText;
 import android.widget.FrameLayout;
-import android.widget.TextView;
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
@@ -49,7 +48,7 @@
 
     protected LatinIME mLatinIME;
     protected Keyboard mKeyboard;
-    protected MyTextView mTextView;
+    protected MyEditText mEditText;
     protected View mInputView;
     protected InputConnection mInputConnection;
 
@@ -88,22 +87,37 @@
         }
     }
 
-    // A helper class to increase control over the TextView
-    public static class MyTextView extends TextView {
+    // A helper class to increase control over the EditText
+    public static class MyEditText extends EditText {
         public Locale mCurrentLocale;
-        public MyTextView(final Context c) {
+        public MyEditText(final Context c) {
             super(c);
         }
+
+        @Override
         public void onAttachedToWindow() {
+            // Make onAttachedToWindow "public"
             super.onAttachedToWindow();
         }
+
+        // overriding hidden API in EditText
         public Locale getTextServicesLocale() {
-            // This method is necessary because TextView is asking this method for the language
+            // This method is necessary because EditText is asking this method for the language
             // to check the spell in. If we don't override this, the spell checker will run in
             // whatever language the keyboard is currently set on the test device, ignoring any
             // settings we do inside the tests.
             return mCurrentLocale;
         }
+
+        // overriding hidden API in EditText
+        public Locale getSpellCheckerLocale() {
+            // This method is necessary because EditText is asking this method for the language
+            // to check the spell in. If we don't override this, the spell checker will run in
+            // whatever language the keyboard is currently set on the test device, ignoring any
+            // settings we do inside the tests.
+            return mCurrentLocale;
+        }
+
     }
 
     public InputTestsBase() {
@@ -130,18 +144,18 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mTextView = new MyTextView(getContext());
+        mEditText = new MyEditText(getContext());
         final int inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
                 | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
-        mTextView.setInputType(inputType);
-        mTextView.setEnabled(true);
+        mEditText.setInputType(inputType);
+        mEditText.setEnabled(true);
         setupService();
         mLatinIME = getService();
         final boolean previousDebugSetting = setDebugMode(true);
         mLatinIME.onCreate();
         setDebugMode(previousDebugSetting);
         final EditorInfo ei = new EditorInfo();
-        final InputConnection ic = mTextView.onCreateInputConnection(ei);
+        final InputConnection ic = mEditText.onCreateInputConnection(ei);
         final LayoutInflater inflater =
                 (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         final ViewGroup vg = new FrameLayout(getContext());
@@ -225,8 +239,8 @@
     }
 
     protected void changeLanguage(final String locale) {
-        mTextView.mCurrentLocale = LocaleUtils.constructLocaleFromString(locale);
-        SubtypeSwitcher.getInstance().forceLocale(mTextView.mCurrentLocale);
+        mEditText.mCurrentLocale = LocaleUtils.constructLocaleFromString(locale);
+        SubtypeSwitcher.getInstance().forceLocale(mEditText.mCurrentLocale);
         mLatinIME.loadKeyboard();
         mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard();
         waitForDictionaryToBeLoaded();
diff --git a/tests/src/com/android/inputmethod/latin/PunctuationTests.java b/tests/src/com/android/inputmethod/latin/PunctuationTests.java
index 1b2f0e6..84ff6b30 100644
--- a/tests/src/com/android/inputmethod/latin/PunctuationTests.java
+++ b/tests/src/com/android/inputmethod/latin/PunctuationTests.java
@@ -44,7 +44,7 @@
             pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
             pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
             assertEquals("type word then type space then punctuation from strip twice",
-                    EXPECTED_RESULT, mTextView.getText().toString());
+                    EXPECTED_RESULT, mEditText.getText().toString());
         } finally {
             setBooleanPreference(NEXT_WORD_PREDICTION_OPTION, previousNextWordPredictionOption,
                     defaultNextWordPredictionOption);
@@ -56,7 +56,7 @@
         final String EXPECTED_RESULT = "this !!";
         type(WORD_TO_TYPE);
         assertEquals("manual pick then space then punctuation from keyboard twice", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testManualPickThenPunctuationFromStripTwiceThenType() {
@@ -70,7 +70,7 @@
         pickSuggestionManually(0, PUNCTUATION_FROM_STRIP);
         type(WORD2_TO_TYPE);
         assertEquals("pick word then pick punctuation twice then type", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testManualPickThenManualPickWithPunctAtStart() {
@@ -81,7 +81,7 @@
         pickSuggestionManually(0, WORD1_TO_TYPE);
         pickSuggestionManually(1, WORD2_TO_PICK);
         assertEquals("manual pick then manual pick a word with punct at start", EXPECTED_RESULT,
-                mTextView.getText().toString());
+                mEditText.getText().toString());
     }
 
     public void testManuallyPickedWordThenColon() {
@@ -92,7 +92,7 @@
         pickSuggestionManually(0, WORD_TO_TYPE);
         type(PUNCTUATION);
         assertEquals("manually pick word then colon",
-                EXPECTED_RESULT, mTextView.getText().toString());
+                EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testManuallyPickedWordThenOpenParen() {
@@ -103,7 +103,7 @@
         pickSuggestionManually(0, WORD_TO_TYPE);
         type(PUNCTUATION);
         assertEquals("manually pick word then open paren",
-                EXPECTED_RESULT, mTextView.getText().toString());
+                EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testManuallyPickedWordThenCloseParen() {
@@ -114,7 +114,7 @@
         pickSuggestionManually(0, WORD_TO_TYPE);
         type(PUNCTUATION);
         assertEquals("manually pick word then close paren",
-                EXPECTED_RESULT, mTextView.getText().toString());
+                EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testManuallyPickedWordThenSmiley() {
@@ -125,7 +125,7 @@
         pickSuggestionManually(0, WORD_TO_TYPE);
         mLatinIME.onTextInput(SPECIAL_KEY);
         assertEquals("manually pick word then press the smiley key",
-                EXPECTED_RESULT, mTextView.getText().toString());
+                EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testManuallyPickedWordThenDotCom() {
@@ -136,7 +136,7 @@
         pickSuggestionManually(0, WORD_TO_TYPE);
         mLatinIME.onTextInput(SPECIAL_KEY);
         assertEquals("manually pick word then press the .com key",
-                EXPECTED_RESULT, mTextView.getText().toString());
+                EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testTypeWordTypeDotThenPressDotCom() {
@@ -146,7 +146,7 @@
         type(WORD_TO_TYPE);
         mLatinIME.onTextInput(SPECIAL_KEY);
         assertEquals("type word type dot then press the .com key",
-                EXPECTED_RESULT, mTextView.getText().toString());
+                EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testAutoCorrectionWithSingleQuoteInside() {
@@ -154,7 +154,7 @@
         final String EXPECTED_RESULT = "you'd ";
         type(WORD_TO_TYPE);
         assertEquals("auto-correction with single quote inside",
-                EXPECTED_RESULT, mTextView.getText().toString());
+                EXPECTED_RESULT, mEditText.getText().toString());
     }
 
     public void testAutoCorrectionWithSingleQuotesAround() {
@@ -162,6 +162,6 @@
         final String EXPECTED_RESULT = "'this' ";
         type(WORD_TO_TYPE);
         assertEquals("auto-correction with single quotes around",
-                EXPECTED_RESULT, mTextView.getText().toString());
+                EXPECTED_RESULT, mEditText.getText().toString());
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerServiceTest.java b/tests/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerServiceTest.java
index 879cc46..995d7f0 100644
--- a/tests/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerServiceTest.java
+++ b/tests/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerServiceTest.java
@@ -24,14 +24,15 @@
 @LargeTest
 public class AndroidSpellCheckerServiceTest extends InputTestsBase {
     public void testSpellchecker() {
-        mTextView.onAttachedToWindow();
-        mTextView.setText("tgis");
-        type(" ");
+        changeLanguage("en_US");
+        mEditText.setText("tgis ");
+        mEditText.setSelection(mEditText.getText().length());
+        mEditText.onAttachedToWindow();
         sleep(1000);
         runMessages();
         sleep(1000);
 
-        final SpanGetter span = new SpanGetter(mTextView.getText(), SuggestionSpan.class);
+        final SpanGetter span = new SpanGetter(mEditText.getText(), SuggestionSpan.class);
         // If no span, the following will crash
         final String[] suggestions = span.getSuggestions();
         // For this test we consider "tgis" should yield at least 2 suggestions (at this moment
@@ -43,14 +44,15 @@
 
     public void testRussianSpellchecker() {
         changeLanguage("ru");
-        mTextView.onAttachedToWindow();
-        mTextView.setText("годп");
-        type(" ");
+        mEditText.onAttachedToWindow();
+        mEditText.setText("годп ");
+        mEditText.setSelection(mEditText.getText().length());
+        mEditText.onAttachedToWindow();
         sleep(1000);
         runMessages();
         sleep(1000);
 
-        final SpanGetter span = new SpanGetter(mTextView.getText(), SuggestionSpan.class);
+        final SpanGetter span = new SpanGetter(mEditText.getText(), SuggestionSpan.class);
         // If no span, the following will crash
         final String[] suggestions = span.getSuggestions();
         // For this test we consider "годп" should yield at least 2 suggestions (at this moment