Merge "Import revised translations."
diff --git a/java/res/values-et/donottranslate-more-keys.xml b/java/res/values-et/donottranslate-more-keys.xml
new file mode 100644
index 0000000..d6b3099
--- /dev/null
+++ b/java/res/values-et/donottranslate-more-keys.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="more_keys_for_a">ä,ā,à,á,â,ã,å,æ,ą</string>
+    <string name="more_keys_for_e">3,ē,è,ė,é,ê,ë,ę,ě</string>
+    <string name="more_keys_for_i">8,ī,ì,į,í,î,ï,ı</string>
+    <string name="more_keys_for_o">9,ö,õ,ò,ó,ô,œ,ő,ø</string>
+    <string name="more_keys_for_u">7,ü,ū,ų,ù,ú,û,ů,ű</string>
+    <string name="more_keys_for_s">š,ß,ś,ş</string>
+    <string name="more_keys_for_n">ņ,ñ,ń,ń</string>
+    <string name="more_keys_for_c">č,ç,ć</string>
+    <string name="more_keys_for_y">6,ý,ÿ</string>
+    <string name="more_keys_for_d">ď</string>
+    <string name="more_keys_for_r">4,ŗ,ř,ŕ</string>
+    <string name="more_keys_for_t">5,ţ,ť</string>
+    <string name="more_keys_for_z">ž,ż,ź</string>
+    <string name="more_keys_for_k">ķ</string>
+    <string name="more_keys_for_l">ļ,ł,ĺ,ľ</string>
+    <string name="more_keys_for_g">ģ,ğ</string>
+</resources>
diff --git a/java/res/values-lt/donottranslate-more-keys.xml b/java/res/values-lt/donottranslate-more-keys.xml
index 6b81e45..e36ce6a 100644
--- a/java/res/values-lt/donottranslate-more-keys.xml
+++ b/java/res/values-lt/donottranslate-more-keys.xml
@@ -18,11 +18,20 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">ą,à,á,â,ä,æ,ã,å,ā</string>
-    <string name="more_keys_for_e">3,ė,ę,è,é,ê,ë,ē</string>
-    <string name="more_keys_for_i">8,į,î,ï,ì,í,ī</string>
-    <string name="more_keys_for_u">7,ų,ū,û,ü,ù,ú</string>
-    <string name="more_keys_for_s">š,ß,ś</string>
+    <string name="more_keys_for_a">ą,ä,ā,à,á,â,ã,å,æ</string>
+    <string name="more_keys_for_e">3,ė,ę,ē,è,é,ê,ë,ě</string>
+    <string name="more_keys_for_i">8,į,ī,ì,í,î,ï,ı</string>
+    <string name="more_keys_for_o">9,ö,õ,ò,ó,ô,œ,ő,ø</string>
+    <string name="more_keys_for_u">7,ū,ų,ü,ū,ù,ú,û,ů,ű</string>
+    <string name="more_keys_for_s">š,ß,ś,ş</string>
+    <string name="more_keys_for_n">ņ,ñ,ń,ń</string>
     <string name="more_keys_for_c">č,ç,ć</string>
-    <string name="more_keys_for_z">ž,ź,ż</string>
+    <string name="more_keys_for_y">6,ý,ÿ</string>
+    <string name="more_keys_for_d">ď</string>
+    <string name="more_keys_for_r">4,ŗ,ř,ŕ</string>
+    <string name="more_keys_for_t">5,ţ,ť</string>
+    <string name="more_keys_for_z">ž,ż,ź</string>
+    <string name="more_keys_for_k">ķ</string>
+    <string name="more_keys_for_l">ļ,ł,ĺ,ľ</string>
+    <string name="more_keys_for_g">ģ,ğ</string>
 </resources>
diff --git a/java/res/values-lv/donottranslate-more-keys.xml b/java/res/values-lv/donottranslate-more-keys.xml
index 77e1c26..8514e73 100644
--- a/java/res/values-lv/donottranslate-more-keys.xml
+++ b/java/res/values-lv/donottranslate-more-keys.xml
@@ -18,16 +18,20 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_keys_for_a">ā,à,á,â,ä,æ,ã,å</string>
-    <string name="more_keys_for_e">3,ē,è,é,ê,ë,ę,ė</string>
-    <string name="more_keys_for_i">8,ī,î,ï,ì,í,į</string>
-    <string name="more_keys_for_u">7,ū,û,ü,ù,ú</string>
-    <string name="more_keys_for_s">š,ß,ś</string>
-    <string name="more_keys_for_n">ņ,ñ,ń</string>
+    <string name="more_keys_for_a">ā,à,á,â,ã,ä,å,æ,ą</string>
+    <string name="more_keys_for_e">3,ē,ė,è,é,ê,ë,ę,ě</string>
+    <string name="more_keys_for_i">8,ī,į,ì,í,î,ï,ı</string>
+    <string name="more_keys_for_o">9,ò,ó,ô,õ,ö,œ,ő,ø</string>
+    <string name="more_keys_for_u">7,ū,ų,ù,ú,û,ü,ů,ű</string>
+    <string name="more_keys_for_s">š,ß,ś,ş</string>
+    <string name="more_keys_for_n">ņ,ñ,ń,ń</string>
     <string name="more_keys_for_c">č,ç,ć</string>
-    <string name="more_keys_for_r">4,ŗ</string>
-    <string name="more_keys_for_z">ž,ź,ż</string>
+    <string name="more_keys_for_y">6,ý,ÿ</string>
+    <string name="more_keys_for_d">ď</string>
+    <string name="more_keys_for_r">4,ŗ,ř,ŕ</string>
+    <string name="more_keys_for_t">5,ţ,ť</string>
+    <string name="more_keys_for_z">ž,ż,ź</string>
     <string name="more_keys_for_k">ķ</string>
-    <string name="more_keys_for_l">ļ,ł</string>
-    <string name="more_keys_for_g">ģ</string>
+    <string name="more_keys_for_l">ļ,ł,ĺ,ľ</string>
+    <string name="more_keys_for_g">ģ,ğ</string>
 </resources>
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 6184add..2b2c00c 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -20,7 +20,7 @@
 <!-- The attributes in this XML file provide configuration information -->
 <!-- for the Input Method Manager. -->
 
-<!-- Keyboard: en_US, en_GB, ar, cs, da, de, de(QWERTY), es, es_US, fi, fr, fr_CA, fr_CH, hr, hu, it, iw, nb, nl, pl, pt, ru, sr, sv, tr -->
+<!-- Keyboard: en_US, en_GB, ar, cs, da, de, de(QWERTY), es, es_US, et, fi, fr, fr_CA, fr_CH, hr, hu, it, iw, lt, lv, nb, nl, pl, pt, ru, sr, sv, tr -->
 <!-- TODO: use <lang>_keyboard icon instead of a common keyboard icon. -->
 <!-- If IME doesn't have an applicable subtype, the first subtype will be used as a default
      subtype.-->
@@ -77,6 +77,12 @@
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="et"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
             android:imeSubtypeLocale="fi"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
@@ -126,6 +132,18 @@
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="lt"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="lv"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
             android:imeSubtypeLocale="nb"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index cef8226..4cb2f20 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -29,7 +29,6 @@
 import com.android.inputmethod.compat.AccessibilityEventCompatUtils;
 import com.android.inputmethod.compat.MotionEventCompatUtils;
 import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.LatinKeyboardView;
 import com.android.inputmethod.keyboard.PointerTracker;
 
@@ -42,7 +41,7 @@
     private LatinKeyboardView mView;
     private AccessibleKeyboardActionListener mListener;
 
-    private int mLastHoverKeyIndex = KeyDetector.NOT_A_KEY;
+    private Key mLastHoverKey = null;
 
     public static void init(InputMethodService inputMethod, SharedPreferences prefs) {
         sInstance.initInternal(inputMethod, prefs);
@@ -81,7 +80,7 @@
 
         switch (event.getEventType()) {
         case AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER:
-            final Key key = tracker.getKey(mLastHoverKeyIndex);
+            final Key key = mLastHoverKey;
 
             if (key == null)
                 break;
@@ -130,12 +129,12 @@
         switch (event.getAction()) {
         case MotionEventCompatUtils.ACTION_HOVER_ENTER:
         case MotionEventCompatUtils.ACTION_HOVER_MOVE:
-            final int keyIndex = tracker.getKeyIndexOn(x, y);
+            final Key key = tracker.getKeyOn(x, y);
 
-            if (keyIndex != mLastHoverKeyIndex) {
-                fireKeyHoverEvent(tracker, mLastHoverKeyIndex, false);
-                mLastHoverKeyIndex = keyIndex;
-                fireKeyHoverEvent(tracker, mLastHoverKeyIndex, true);
+            if (key != mLastHoverKey) {
+                fireKeyHoverEvent(tracker, mLastHoverKey, false);
+                mLastHoverKey = key;
+                fireKeyHoverEvent(tracker, mLastHoverKey, true);
             }
 
             return true;
@@ -144,7 +143,7 @@
         return false;
     }
 
-    private void fireKeyHoverEvent(PointerTracker tracker, int keyIndex, boolean entering) {
+    private void fireKeyHoverEvent(PointerTracker tracker, Key key, boolean entering) {
         if (mListener == null) {
             Log.e(TAG, "No accessible keyboard action listener set!");
             return;
@@ -155,11 +154,6 @@
             return;
         }
 
-        if (keyIndex == KeyDetector.NOT_A_KEY)
-            return;
-
-        final Key key = tracker.getKey(keyIndex);
-
         if (key == null)
             return;
 
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index ce5f29d..84f2be5 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -318,6 +318,14 @@
         return false;
     }
 
+    public boolean isShift() {
+        return mCode == Keyboard.CODE_SHIFT;
+    }
+
+    public boolean isModifier() {
+        return mCode == Keyboard.CODE_SHIFT || mCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL;
+    }
+
     public boolean isRepeatable() {
         return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0;
     }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 3298c41..4b708a7 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -26,7 +26,7 @@
     private static final boolean DEBUG = false;
 
     public static final int NOT_A_CODE = -1;
-    public static final int NOT_A_KEY = -1;
+    private static final int NOT_A_KEY = -1;
 
     private final int mKeyHysteresisDistanceSquared;
 
@@ -96,22 +96,22 @@
     }
 
     /**
-     * Computes maximum size of the array that can contain all nearby key indices returned by
-     * {@link #getKeyIndexAndNearbyCodes}.
+     * Computes maximum size of the array that can contain all nearby key codes returned by
+     * {@link #getKeyAndNearbyCodes}.
      *
-     * @return Returns maximum size of the array that can contain all nearby key indices returned
-     *         by {@link #getKeyIndexAndNearbyCodes}.
+     * @return Returns maximum size of the array that can contain all nearby key codes returned
+     *         by {@link #getKeyAndNearbyCodes}.
      */
     protected int getMaxNearbyKeys() {
         return MAX_NEARBY_KEYS;
     }
 
     /**
-     * Allocates array that can hold all key indices returned by {@link #getKeyIndexAndNearbyCodes}
+     * Allocates array that can hold all key codes returned by {@link #getKeyAndNearbyCodes}
      * method. The maximum size of the array should be computed by {@link #getMaxNearbyKeys}.
      *
-     * @return Allocates and returns an array that can hold all key indices returned by
-     *         {@link #getKeyIndexAndNearbyCodes} method. All elements in the returned array are
+     * @return Allocates and returns an array that can hold all key codes returned by
+     *         {@link #getKeyAndNearbyCodes} method. All elements in the returned array are
      *         initialized by {@link #NOT_A_CODE} value.
      */
     public int[] newCodeArray() {
@@ -180,31 +180,32 @@
     }
 
     /**
-     * Finds all possible nearby key indices around a touch event point and returns the nearest key
-     * index. The algorithm to determine the nearby keys depends on the threshold set by
+     * Finds all possible nearby key codes around a touch event point and returns the nearest key.
+     * The algorithm to determine the nearby keys depends on the threshold set by
      * {@link #setProximityThreshold(int)} and the mode set by
      * {@link #setProximityCorrectionEnabled(boolean)}.
      *
      * @param x The x-coordinate of a touch point
      * @param y The y-coordinate of a touch point
-     * @param allCodes All nearby key code except functional key are returned in this array
-     * @return The nearest key index
+     * @param allCodes All nearby key codes except functional key are returned in this array
+     * @return The nearest key
      */
-    public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
+    public Key getKeyAndNearbyCodes(int x, int y, final int[] allCodes) {
         final List<Key> keys = getKeyboard().mKeys;
         final int touchX = getTouchX(x);
         final int touchY = getTouchY(y);
 
         initializeNearbyKeys();
-        int primaryIndex = NOT_A_KEY;
+        Key primaryKey = null;
         for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) {
             final Key key = keys.get(index);
             final boolean isOnKey = key.isOnKey(touchX, touchY);
             final int distance = key.squaredDistanceToEdge(touchX, touchY);
             if (isOnKey || (mProximityCorrectOn && distance < mProximityThresholdSquare)) {
                 final int insertedPosition = sortNearbyKeys(index, distance, isOnKey);
-                if (insertedPosition == 0 && isOnKey)
-                    primaryIndex = index;
+                if (insertedPosition == 0 && isOnKey) {
+                    primaryKey = key;
+                }
             }
         }
 
@@ -213,11 +214,11 @@
             if (DEBUG) {
                 Log.d(TAG, "x=" + x + " y=" + y
                         + " primary="
-                        + (primaryIndex == NOT_A_KEY ? "none" : keys.get(primaryIndex).mCode)
+                        + (primaryKey == null ? "none" : primaryKey.mCode)
                         + " codes=" + Arrays.toString(allCodes));
             }
         }
 
-        return primaryIndex;
+        return primaryKey;
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 3ce1849..d2741ed 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -148,7 +148,7 @@
             final PointerTracker tracker = (PointerTracker) msg.obj;
             switch (msg.what) {
             case MSG_SHOW_KEY_PREVIEW:
-                keyboardView.showKey(msg.arg1, tracker);
+                keyboardView.showKey(tracker);
                 break;
             case MSG_DISMISS_KEY_PREVIEW:
                 tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
@@ -156,16 +156,15 @@
             }
         }
 
-        public void showKeyPreview(long delay, int keyIndex, PointerTracker tracker) {
+        public void showKeyPreview(long delay, PointerTracker tracker) {
             removeMessages(MSG_SHOW_KEY_PREVIEW);
             final KeyboardView keyboardView = getOuterInstance();
             if (keyboardView == null) return;
             if (tracker.getKeyPreviewText().getVisibility() == VISIBLE || delay == 0) {
                 // Show right away, if it's already visible and finger is moving around
-                keyboardView.showKey(keyIndex, tracker);
+                keyboardView.showKey(tracker);
             } else {
-                sendMessageDelayed(
-                        obtainMessage(MSG_SHOW_KEY_PREVIEW, keyIndex, 0, tracker), delay);
+                sendMessageDelayed(obtainMessage(MSG_SHOW_KEY_PREVIEW, tracker), delay);
             }
         }
 
@@ -830,9 +829,9 @@
     }
 
     @Override
-    public void showKeyPreview(int keyIndex, PointerTracker tracker) {
+    public void showKeyPreview(PointerTracker tracker) {
         if (mShowKeyPreviewPopup) {
-            mDrawingHandler.showKeyPreview(mDelayBeforePreview, keyIndex, tracker);
+            mDrawingHandler.showKeyPreview(mDelayBeforePreview, tracker);
         }
     }
 
@@ -858,7 +857,7 @@
                 keyPreview, FrameLayoutCompatUtils.newLayoutParam(mPreviewPlacer, 0, 0));
     }
 
-    private void showKey(final int keyIndex, PointerTracker tracker) {
+    private void showKey(PointerTracker tracker) {
         final TextView previewText = tracker.getKeyPreviewText();
         // If the key preview has no parent view yet, add it to the ViewGroup which can place
         // key preview absolutely in SoftInputWindow.
@@ -867,8 +866,8 @@
         }
 
         mDrawingHandler.cancelDismissKeyPreview(tracker);
-        final Key key = tracker.getKey(keyIndex);
-        // If keyIndex is invalid or IME is already closed, we must not show key preview.
+        final Key key = tracker.getKey();
+        // If key is invalid or IME is already closed, we must not show key preview.
         // Trying to show key preview while root window is closed causes
         // WindowManager.BadTokenException.
         if (key == null)
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 60631e8..9ddf401 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -74,7 +74,7 @@
 
     private final boolean mHasDistinctMultitouch;
     private int mOldPointerCount = 1;
-    private int mOldKeyIndex;
+    private Key mOldKey;
 
     private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
     protected KeyDetector mKeyDetector;
@@ -103,19 +103,19 @@
             final PointerTracker tracker = (PointerTracker) msg.obj;
             switch (msg.what) {
             case MSG_REPEAT_KEY:
-                tracker.onRepeatKey(msg.arg1);
-                startKeyRepeatTimer(keyboardView.mKeyRepeatInterval, msg.arg1, tracker);
+                tracker.onRepeatKey(tracker.getKey());
+                startKeyRepeatTimer(keyboardView.mKeyRepeatInterval, tracker);
                 break;
             case MSG_LONGPRESS_KEY:
-                keyboardView.openMiniKeyboardIfRequired(msg.arg1, tracker);
+                keyboardView.openMiniKeyboardIfRequired(tracker.getKey(), tracker);
                 break;
             }
         }
 
         @Override
-        public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {
+        public void startKeyRepeatTimer(long delay, PointerTracker tracker) {
             mInKeyRepeat = true;
-            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay);
+            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, tracker), delay);
         }
 
         public void cancelKeyRepeatTimer() {
@@ -128,9 +128,9 @@
         }
 
         @Override
-        public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {
+        public void startLongPressTimer(long delay, PointerTracker tracker) {
             cancelLongPressTimer();
-            sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay);
+            sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
         }
 
         @Override
@@ -181,8 +181,9 @@
                 final int pointerIndex = firstDown.getActionIndex();
                 final int id = firstDown.getPointerId(pointerIndex);
                 final PointerTracker tracker = getPointerTracker(id);
+                final Key key = tracker.getKeyOn((int)firstDown.getX(), (int)firstDown.getY());
                 // If the first down event is on shift key.
-                if (tracker.isOnShiftKey((int) firstDown.getX(), (int) firstDown.getY())) {
+                if (key != null && key.isShift()) {
                     mProcessingShiftDoubleTapEvent = true;
                     return true;
                 }
@@ -199,8 +200,9 @@
                 final int pointerIndex = secondDown.getActionIndex();
                 final int id = secondDown.getPointerId(pointerIndex);
                 final PointerTracker tracker = getPointerTracker(id);
+                final Key key = tracker.getKeyOn((int)secondDown.getX(), (int)secondDown.getY());
                 // If the second down event is also on shift key.
-                if (tracker.isOnShiftKey((int) secondDown.getX(), (int) secondDown.getY())) {
+                if (key != null && key.isShift()) {
                     // Detected a double tap on shift key. If we are in the ignoring double tap
                     // mode, it means we have already turned off caps lock in
                     // {@link KeyboardSwitcher#onReleaseShift} .
@@ -326,7 +328,7 @@
         super.cancelAllMessages();
     }
 
-    private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) {
+    private boolean openMiniKeyboardIfRequired(Key parentKey, PointerTracker tracker) {
         // Check if we have a popup layout specified first.
         if (mMoreKeysLayout == 0) {
             return false;
@@ -335,7 +337,6 @@
         // Check if we are already displaying popup panel.
         if (mMoreKeysPanel != null)
             return false;
-        final Key parentKey = tracker.getKey(keyIndex);
         if (parentKey == null)
             return false;
         return onLongPress(parentKey, tracker);
@@ -546,8 +547,8 @@
                 // Multi-touch to single touch transition.
                 // Send a down event for the latest pointer if the key is different from the
                 // previous key.
-                final int newKeyIndex = tracker.getKeyIndexOn(x, y);
-                if (mOldKeyIndex != newKeyIndex) {
+                final Key newKey = tracker.getKeyOn(x, y);
+                if (mOldKey != newKey) {
                     tracker.onDownEvent(x, y, eventTime, this);
                     if (action == MotionEvent.ACTION_UP)
                         tracker.onUpEvent(x, y, eventTime);
@@ -557,7 +558,7 @@
                 // Send an up event for the last pointer.
                 final int lastX = tracker.getLastX();
                 final int lastY = tracker.getLastY();
-                mOldKeyIndex = tracker.getKeyIndexOn(lastX, lastY);
+                mOldKey = tracker.getKeyOn(lastX, lastY);
                 tracker.onUpEvent(lastX, lastY, eventTime);
             } else if (pointerCount == 1 && oldPointerCount == 1) {
                 tracker.processMotionEvent(action, x, y, eventTime, this);
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
index d202046..742ee98 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
@@ -16,8 +16,6 @@
 
 package com.android.inputmethod.keyboard;
 
-import java.util.List;
-
 public class MoreKeysDetector extends KeyDetector {
     private final int mSlideAllowanceSquare;
     private final int mSlideAllowanceSquareTop;
@@ -41,24 +39,23 @@
     }
 
     @Override
-    public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
-        final List<Key> keys = getKeyboard().mKeys;
+    public Key getKeyAndNearbyCodes(int x, int y, final int[] allCodes) {
         final int touchX = getTouchX(x);
         final int touchY = getTouchY(y);
 
-        int nearestIndex = NOT_A_KEY;
+        Key nearestKey = null;
         int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
-        final int keyCount = keys.size();
-        for (int index = 0; index < keyCount; index++) {
-            final int dist = keys.get(index).squaredDistanceToEdge(touchX, touchY);
+        for (final Key key : getKeyboard().mKeys) {
+            final int dist = key.squaredDistanceToEdge(touchX, touchY);
             if (dist < nearestDist) {
-                nearestIndex = index;
+                nearestKey = key;
                 nearestDist = dist;
             }
         }
 
-        if (allCodes != null && nearestIndex != NOT_A_KEY)
-            allCodes[0] = keys.get(nearestIndex).mCode;
-        return nearestIndex;
+        if (allCodes != null && nearestKey != null) {
+            allCodes[0] = nearestKey.mCode;
+        }
+        return nearestKey;
     }
-}
\ No newline at end of file
+}
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index f7a0d97..da60af3 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -68,7 +68,7 @@
     public interface DrawingProxy extends MoreKeysPanel.Controller {
         public void invalidateKey(Key key);
         public TextView inflateKeyPreviewText();
-        public void showKeyPreview(int keyIndex, PointerTracker tracker);
+        public void showKeyPreview(PointerTracker tracker);
         public void cancelShowKeyPreview(PointerTracker tracker);
         public void dismissKeyPreview(PointerTracker tracker);
     }
@@ -76,8 +76,8 @@
     public interface TimerProxy {
         public void startKeyTypedTimer(long delay);
         public boolean isTyping();
-        public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker);
-        public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker);
+        public void startKeyRepeatTimer(long delay, PointerTracker tracker);
+        public void startLongPressTimer(long delay, PointerTracker tracker);
         public void cancelLongPressTimer();
         public void cancelKeyTimers();
 
@@ -87,9 +87,9 @@
             @Override
             public boolean isTyping() { return false; }
             @Override
-            public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {}
+            public void startKeyRepeatTimer(long delay, PointerTracker tracker) {}
             @Override
-            public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {}
+            public void startLongPressTimer(long delay, PointerTracker tracker) {}
             @Override
             public void cancelLongPressTimer() {}
             @Override
@@ -127,9 +127,9 @@
     private long mDownTime;
     private long mUpTime;
 
-    // The current key index where this pointer is.
-    private int mKeyIndex = KeyDetector.NOT_A_KEY;
-    // The position where mKeyIndex was recognized for the first time.
+    // The current key where this pointer is.
+    private Key mCurrentKey = null;
+    // The position where the current key was recognized for the first time.
     private int mKeyX;
     private int mKeyY;
 
@@ -217,7 +217,7 @@
 
     public static void dismissAllKeyPreviews() {
         for (final PointerTracker tracker : sTrackers) {
-            tracker.setReleasedKeyGraphics(tracker.mKeyIndex);
+            tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
         }
     }
 
@@ -238,7 +238,7 @@
 
     // Returns true if keyboard has been changed by this callback.
     private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key, boolean withSliding) {
-        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
+        final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
         if (DEBUG_LISTENER)
             Log.d(TAG, "onPress    : " + keyCodePrintable(key.mCode) + " sliding=" + withSliding
                     + " ignoreModifier=" + ignoreModifierKey);
@@ -257,13 +257,14 @@
     // Note that we need primaryCode argument because the keyboard may in shifted state and the
     // primaryCode is different from {@link Key#mCode}.
     private void callListenerOnCodeInput(Key key, int primaryCode, int[] keyCodes, int x, int y) {
-        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
+        final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
         if (DEBUG_LISTENER)
             Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode)
                     + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y
                     + " ignoreModifier=" + ignoreModifierKey);
-        if (ignoreModifierKey)
+        if (ignoreModifierKey) {
             return;
+        }
         if (key.isEnabled()) {
             mListener.onCodeInput(primaryCode, keyCodes, x, y);
         }
@@ -280,12 +281,13 @@
     // Note that we need primaryCode argument because the keyboard may in shifted state and the
     // primaryCode is different from {@link Key#mCode}.
     private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) {
-        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
+        final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
         if (DEBUG_LISTENER)
             Log.d(TAG, "onRelease  : " + keyCodePrintable(primaryCode) + " sliding="
                     + withSliding + " ignoreModifier=" + ignoreModifierKey);
-        if (ignoreModifierKey)
+        if (ignoreModifierKey) {
             return;
+        }
         if (key.isEnabled()) {
             mListener.onRelease(primaryCode, withSliding);
         }
@@ -309,55 +311,30 @@
         return mIsInSlidingKeyInput;
     }
 
-    private boolean isValidKeyIndex(int keyIndex) {
-        return keyIndex >= 0 && keyIndex < mKeys.size();
-    }
-
-    public Key getKey(int keyIndex) {
-        return isValidKeyIndex(keyIndex) ? mKeys.get(keyIndex) : null;
-    }
-
-    private static boolean isModifierCode(int primaryCode) {
-        return primaryCode == Keyboard.CODE_SHIFT
-                || primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL;
-    }
-
-    private boolean isModifierInternal(int keyIndex) {
-        final Key key = getKey(keyIndex);
-        return key == null ? false : isModifierCode(key.mCode);
+    public Key getKey() {
+        return mCurrentKey;
     }
 
     public boolean isModifier() {
-        return isModifierInternal(mKeyIndex);
+        return mCurrentKey != null && mCurrentKey.isModifier();
     }
 
-    private boolean isOnModifierKey(int x, int y) {
-        return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
+    public Key getKeyOn(int x, int y) {
+        return mKeyDetector.getKeyAndNearbyCodes(x, y, null);
     }
 
-    public boolean isOnShiftKey(int x, int y) {
-        final Key key = getKey(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
-        return key != null && key.mCode == Keyboard.CODE_SHIFT;
-    }
-
-    public int getKeyIndexOn(int x, int y) {
-        return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
-    }
-
-    private void setReleasedKeyGraphics(int keyIndex) {
+    private void setReleasedKeyGraphics(Key key) {
         mDrawingProxy.dismissKeyPreview(this);
-        final Key key = getKey(keyIndex);
         if (key != null && key.isEnabled()) {
             key.onReleased();
             mDrawingProxy.invalidateKey(key);
         }
     }
 
-    private void setPressedKeyGraphics(int keyIndex) {
-        final Key key = getKey(keyIndex);
+    private void setPressedKeyGraphics(Key key) {
         if (key != null && key.isEnabled()) {
             if (!key.noKeyPreview()) {
-                mDrawingProxy.showKeyPreview(keyIndex, this);
+                mDrawingProxy.showKeyPreview(this);
             }
             key.onPressed();
             mDrawingProxy.invalidateKey(key);
@@ -376,31 +353,31 @@
         return mDownTime;
     }
 
-    private int onDownKey(int x, int y, long eventTime) {
+    private Key onDownKey(int x, int y, long eventTime) {
         mDownTime = eventTime;
         return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
     }
 
-    private int onMoveKeyInternal(int x, int y) {
+    private Key onMoveKeyInternal(int x, int y) {
         mLastX = x;
         mLastY = y;
-        return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
+        return mKeyDetector.getKeyAndNearbyCodes(x, y, null);
     }
 
-    private int onMoveKey(int x, int y) {
+    private Key onMoveKey(int x, int y) {
         return onMoveKeyInternal(x, y);
     }
 
-    private int onMoveToNewKey(int keyIndex, int x, int y) {
-        mKeyIndex = keyIndex;
+    private Key onMoveToNewKey(Key newKey, int x, int y) {
+        mCurrentKey = newKey;
         mKeyX = x;
         mKeyY = y;
-        return keyIndex;
+        return newKey;
     }
 
-    private int onUpKey(int x, int y, long eventTime) {
+    private Key onUpKey(int x, int y, long eventTime) {
         mUpTime = eventTime;
-        mKeyIndex = KeyDetector.NOT_A_KEY;
+        mCurrentKey = null;
         return onMoveKeyInternal(x, y);
     }
 
@@ -449,7 +426,8 @@
 
         final PointerTrackerQueue queue = sPointerTrackerQueue;
         if (queue != null) {
-            if (isOnModifierKey(x, y)) {
+            final Key key = getKeyOn(x, y);
+            if (key != null && key.isModifier()) {
                 // Before processing a down event of modifier key, all pointers already being
                 // tracked should be released.
                 queue.releaseAllPointers(eventTime);
@@ -460,32 +438,34 @@
     }
 
     private void onDownEventInternal(int x, int y, long eventTime) {
-        int keyIndex = onDownKey(x, y, eventTime);
+        Key key = onDownKey(x, y, eventTime);
         // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
         // from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
-        mIsAllowedSlidingKeyInput = sConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex)
+        mIsAllowedSlidingKeyInput = sConfigSlidingKeyInputEnabled || key.isModifier()
                 || mKeyDetector.alwaysAllowsSlidingInput();
         mKeyboardLayoutHasBeenChanged = false;
         mKeyAlreadyProcessed = false;
         mIsRepeatableKey = false;
         mIsInSlidingKeyInput = false;
         mIgnoreModifierKey = false;
-        if (isValidKeyIndex(keyIndex)) {
+        if (key != null) {
             // This onPress call may have changed keyboard layout. Those cases are detected at
-            // {@link #setKeyboard}. In those cases, we should update keyIndex according to the new
+            // {@link #setKeyboard}. In those cases, we should update key according to the new
             // keyboard layout.
-            if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), false))
-                keyIndex = onDownKey(x, y, eventTime);
+            if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false)) {
+                key = onDownKey(x, y, eventTime);
+            }
 
-            startRepeatKey(keyIndex);
-            startLongPressTimer(keyIndex);
-            setPressedKeyGraphics(keyIndex);
+            startRepeatKey(key);
+            startLongPressTimer(key);
+            setPressedKeyGraphics(key);
         }
     }
 
     private void startSlidingKeyInput(Key key) {
-        if (!mIsInSlidingKeyInput)
-            mIgnoreModifierKey = isModifierCode(key.mCode);
+        if (!mIsInSlidingKeyInput) {
+            mIgnoreModifierKey = key.isModifier();
+        }
         mIsInSlidingKeyInput = true;
     }
 
@@ -497,39 +477,40 @@
 
         final int lastX = mLastX;
         final int lastY = mLastY;
-        final int oldKeyIndex = mKeyIndex;
-        final Key oldKey = getKey(oldKeyIndex);
-        int keyIndex = onMoveKey(x, y);
-        if (isValidKeyIndex(keyIndex)) {
+        final Key oldKey = mCurrentKey;
+        Key key = onMoveKey(x, y);
+        if (key != null) {
             if (oldKey == null) {
                 // The pointer has been slid in to the new key, but the finger was not on any keys.
                 // In this case, we must call onPress() to notify that the new key is being pressed.
                 // This onPress call may have changed keyboard layout. Those cases are detected at
-                // {@link #setKeyboard}. In those cases, we should update keyIndex according to the
+                // {@link #setKeyboard}. In those cases, we should update key according to the
                 // new keyboard layout.
-                if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true))
-                    keyIndex = onMoveKey(x, y);
-                onMoveToNewKey(keyIndex, x, y);
-                startLongPressTimer(keyIndex);
-                setPressedKeyGraphics(keyIndex);
-            } else if (isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) {
+                if (callListenerOnPressAndCheckKeyboardLayoutChange(key, true)) {
+                    key = onMoveKey(x, y);
+                }
+                onMoveToNewKey(key, x, y);
+                startLongPressTimer(key);
+                setPressedKeyGraphics(key);
+            } else if (isMajorEnoughMoveToBeOnNewKey(x, y, key)) {
                 // The pointer has been slid in to the new key from the previous key, we must call
                 // onRelease() first to notify that the previous key has been released, then call
                 // onPress() to notify that the new key is being pressed.
-                setReleasedKeyGraphics(oldKeyIndex);
+                setReleasedKeyGraphics(oldKey);
                 callListenerOnRelease(oldKey, oldKey.mCode, true);
                 startSlidingKeyInput(oldKey);
                 mTimerProxy.cancelKeyTimers();
-                startRepeatKey(keyIndex);
+                startRepeatKey(key);
                 if (mIsAllowedSlidingKeyInput) {
                     // This onPress call may have changed keyboard layout. Those cases are detected
-                    // at {@link #setKeyboard}. In those cases, we should update keyIndex according
+                    // at {@link #setKeyboard}. In those cases, we should update key according
                     // to the new keyboard layout.
-                    if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true))
-                        keyIndex = onMoveKey(x, y);
-                    onMoveToNewKey(keyIndex, x, y);
-                    startLongPressTimer(keyIndex);
-                    setPressedKeyGraphics(keyIndex);
+                    if (callListenerOnPressAndCheckKeyboardLayoutChange(key, true)) {
+                        key = onMoveKey(x, y);
+                    }
+                    onMoveToNewKey(key, x, y);
+                    startLongPressTimer(key);
+                    setPressedKeyGraphics(key);
                 } else {
                     // HACK: On some devices, quick successive touches may be translated to sudden
                     // move by touch panel firmware. This hack detects the case and translates the
@@ -545,20 +526,20 @@
                         onDownEventInternal(x, y, eventTime);
                     } else {
                         mKeyAlreadyProcessed = true;
-                        setReleasedKeyGraphics(oldKeyIndex);
+                        setReleasedKeyGraphics(oldKey);
                     }
                 }
             }
         } else {
-            if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) {
+            if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, key)) {
                 // The pointer has been slid out from the previous key, we must call onRelease() to
                 // notify that the previous key has been released.
-                setReleasedKeyGraphics(oldKeyIndex);
+                setReleasedKeyGraphics(oldKey);
                 callListenerOnRelease(oldKey, oldKey.mCode, true);
                 startSlidingKeyInput(oldKey);
                 mTimerProxy.cancelLongPressTimer();
                 if (mIsAllowedSlidingKeyInput) {
-                    onMoveToNewKey(keyIndex, x, y);
+                    onMoveToNewKey(key, x, y);
                 } else {
                     mKeyAlreadyProcessed = true;
                 }
@@ -572,7 +553,7 @@
 
         final PointerTrackerQueue queue = sPointerTrackerQueue;
         if (queue != null) {
-            if (isModifier()) {
+            if (mCurrentKey != null && mCurrentKey.isModifier()) {
                 // Before processing an up event of modifier key, all pointers already being
                 // tracked should be released.
                 queue.releaseAllPointersExcept(this, eventTime);
@@ -607,8 +588,8 @@
             keyX = mKeyX;
             keyY = mKeyY;
         }
-        final int keyIndex = onUpKey(keyX, keyY, eventTime);
-        setReleasedKeyGraphics(keyIndex);
+        final Key key = onUpKey(keyX, keyY, eventTime);
+        setReleasedKeyGraphics(key);
         if (mIsShowingMoreKeysPanel) {
             mDrawingProxy.dismissMoreKeysPanel();
             mIsShowingMoreKeysPanel = false;
@@ -616,7 +597,7 @@
         if (mKeyAlreadyProcessed)
             return;
         if (!mIsRepeatableKey) {
-            detectAndSendKey(keyIndex, keyX, keyY);
+            detectAndSendKey(key, keyX, keyY);
         }
     }
 
@@ -628,7 +609,7 @@
 
     public void onLongPressed() {
         mKeyAlreadyProcessed = true;
-        setReleasedKeyGraphics(mKeyIndex);
+        setReleasedKeyGraphics(mCurrentKey);
         final PointerTrackerQueue queue = sPointerTrackerQueue;
         if (queue != null) {
             queue.remove(this);
@@ -650,7 +631,7 @@
     private void onCancelEventInternal() {
         mTimerProxy.cancelKeyTimers();
         mDrawingProxy.cancelShowKeyPreview(this);
-        setReleasedKeyGraphics(mKeyIndex);
+        setReleasedKeyGraphics(mCurrentKey);
         mIsInSlidingKeyInput = false;
         if (mIsShowingMoreKeysPanel) {
             mDrawingProxy.dismissMoreKeysPanel();
@@ -658,48 +639,45 @@
         }
     }
 
-    private void startRepeatKey(int keyIndex) {
-        final Key key = getKey(keyIndex);
+    private void startRepeatKey(Key key) {
         if (key != null && key.isRepeatable()) {
-            onRepeatKey(keyIndex);
-            mTimerProxy.startKeyRepeatTimer(sDelayBeforeKeyRepeatStart, keyIndex, this);
+            onRepeatKey(key);
+            mTimerProxy.startKeyRepeatTimer(sDelayBeforeKeyRepeatStart, this);
             mIsRepeatableKey = true;
         } else {
             mIsRepeatableKey = false;
         }
     }
 
-    public void onRepeatKey(int keyIndex) {
-        Key key = getKey(keyIndex);
+    public void onRepeatKey(Key key) {
         if (key != null) {
-            detectAndSendKey(keyIndex, key.mX, key.mY);
+            detectAndSendKey(key, key.mX, key.mY);
         }
     }
 
-    private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, int newKey) {
+    private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) {
         if (mKeys == null || mKeyDetector == null)
             throw new NullPointerException("keyboard and/or key detector not set");
-        int curKey = mKeyIndex;
+        Key curKey = mCurrentKey;
         if (newKey == curKey) {
             return false;
-        } else if (isValidKeyIndex(curKey)) {
-            return mKeys.get(curKey).squaredDistanceToEdge(x, y)
+        } else if (curKey != null) {
+            return curKey.squaredDistanceToEdge(x, y)
                     >= mKeyDetector.getKeyHysteresisDistanceSquared();
         } else {
             return true;
         }
     }
 
-    private void startLongPressTimer(int keyIndex) {
-        Key key = getKey(keyIndex);
+    private void startLongPressTimer(Key key) {
         if (key == null) return;
         if (key.mCode == Keyboard.CODE_SHIFT) {
             if (sLongPressShiftKeyTimeout > 0) {
-                mTimerProxy.startLongPressTimer(sLongPressShiftKeyTimeout, keyIndex, this);
+                mTimerProxy.startLongPressTimer(sLongPressShiftKeyTimeout, this);
             }
         } else if (key.mCode == Keyboard.CODE_SPACE) {
             if (sLongPressSpaceKeyTimeout > 0) {
-                mTimerProxy.startLongPressTimer(sLongPressSpaceKeyTimeout, keyIndex, this);
+                mTimerProxy.startLongPressTimer(sLongPressSpaceKeyTimeout, this);
             }
         } else if (key.hasUppercaseLetter() && mKeyboard.isManualTemporaryUpperCase()) {
             // We need not start long press timer on the key which has manual temporary upper case
@@ -707,14 +685,13 @@
             return;
         } else if (sKeyboardSwitcher.isInMomentarySwitchState()) {
             // We use longer timeout for sliding finger input started from the symbols mode key.
-            mTimerProxy.startLongPressTimer(sLongPressKeyTimeout * 3, keyIndex, this);
+            mTimerProxy.startLongPressTimer(sLongPressKeyTimeout * 3, this);
         } else {
-            mTimerProxy.startLongPressTimer(sLongPressKeyTimeout, keyIndex, this);
+            mTimerProxy.startLongPressTimer(sLongPressKeyTimeout, this);
         }
     }
 
-    private void detectAndSendKey(int keyIndex, int x, int y) {
-        final Key key = getKey(keyIndex);
+    private void detectAndSendKey(Key key, int x, int y) {
         if (key == null) {
             callListenerOnCancelInput();
             return;
@@ -731,7 +708,7 @@
         } else {
             int code = key.mCode;
             final int[] codes = mKeyDetector.newCodeArray();
-            mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
+            mKeyDetector.getKeyAndNearbyCodes(x, y, codes);
 
             // If keyboard is in manual temporary upper case state and key has manual temporary
             // uppercase letter as key hint letter, alternate character code should be sent.
@@ -754,7 +731,7 @@
                 callListenerOnCodeInput(key, code, codes, x, y);
             }
             callListenerOnRelease(key, code, false);
-            if (!key.ignoreWhileTyping() && !isModifierCode(code)) {
+            if (!key.ignoreWhileTyping() && !key.isModifier()) {
                 mTimerProxy.startKeyTypedTimer(sIgnoreSpecialKeyTimeout);
             }
         }
@@ -763,17 +740,17 @@
     private long mPreviousEventTime;
 
     private void printTouchEvent(String title, int x, int y, long eventTime) {
-        final int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
-        final Key key = getKey(keyIndex);
+        final Key key = mKeyDetector.getKeyAndNearbyCodes(x, y, null);
         final String code = (key == null) ? "----" : keyCodePrintable(key.mCode);
         final long delta = eventTime - mPreviousEventTime;
-        Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %3d(%s)", title,
-                (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, keyIndex, code));
+        Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title,
+                (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, code));
         mPreviousEventTime = eventTime;
     }
 
     private static String keyCodePrintable(int primaryCode) {
-        final String modifier = isModifierCode(primaryCode) ? " modifier" : "";
-        return  String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode) + modifier;
+        if (primaryCode < 0) return String.format("%4d", primaryCode);
+        if (primaryCode < 0x100) return String.format("\\u%02x", primaryCode);
+        return String.format("\\u04x", primaryCode);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f7a77ca..60b436f 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -403,7 +403,9 @@
             resetPendingImsCallback();
             mIsOrientationChanging = true;
             final LatinIME latinIme = getOuterInstance();
-            latinIme.mKeyboardSwitcher.saveKeyboardState();
+            if (latinIme.isInputViewShown()) {
+                latinIme.mKeyboardSwitcher.saveKeyboardState();
+            }
         }
 
         private void resetPendingImsCallback() {
@@ -948,6 +950,10 @@
         }
         mExpectingUpdateSelection = false;
         mHandler.postUpdateShiftKeyState();
+        // TODO: Decide to call restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() or not
+        // here. It would probably be too expensive to call directly here but we may want to post a
+        // message to delay it. The point would be to unify behavior between backspace to the
+        // end of a word and manually put the pointer at the end of the word.
 
         // Make a note of the cursor position
         mLastSelectionStart = newSelStart;
@@ -1466,10 +1472,11 @@
                 // inconsistent with backspacing after selecting other suggestions.
                 revertLastWord(ic);
             } else {
-                sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+                ic.deleteSurroundingText(1, 0);
                 if (mDeleteCount > DELETE_ACCELERATE_AT) {
-                    sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+                    ic.deleteSurroundingText(1, 0);
                 }
+                restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(ic);
             }
         }
         ic.endBatchEdit();
@@ -1793,6 +1800,7 @@
         // The whitelist should be case-insensitive, so it's not possible to be consistent with
         // a boolean flag. Right now this is handled with a slight hack in
         // WhitelistDictionary#shouldForciblyAutoCorrectFrom.
+        final int quotesCount = wordComposer.trailingSingleQuotesCount();
         final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected(
                 mSuggest.getUnigramDictionaries(),
                 // If the typed string ends with a single quote, for dictionary lookup purposes
@@ -1800,8 +1808,8 @@
                 // typed string in the dictionary (to avoid autocorrecting from an existing
                 // word, so for consistency this lookup should be made WITHOUT the trailing
                 // single quote.
-                wordComposer.isLastCharASingleQuote()
-                        ? typedWord.subSequence(0, typedWord.length() - 1) : typedWord,
+                quotesCount > 0
+                        ? typedWord.subSequence(0, typedWord.length() - quotesCount) : typedWord,
                 preferCapitalization());
         if (mCorrectionMode == Suggest.CORRECTION_FULL
                 || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
@@ -2118,6 +2126,53 @@
     }
 
     // "ic" must not be null
+    /**
+     * Check if the cursor is actually at the end of a word. If so, restart suggestions on this
+     * word, else do nothing.
+     */
+    private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(
+            final InputConnection ic) {
+        // Bail out if the cursor is not at the end of a word (cursor must be preceded by
+        // non-whitespace, non-separator, non-start-of-text)
+        // Example ("|" is the cursor here) : <SOL>"|a" " |a" " | " all get rejected here.
+        final CharSequence textBeforeCursor = ic.getTextBeforeCursor(1, 0);
+        if (TextUtils.isEmpty(textBeforeCursor)
+                || mSettingsValues.isWordSeparator(textBeforeCursor.charAt(0))) return;
+
+        // Bail out if the cursor is in the middle of a word (cursor must be followed by whitespace,
+        // separator or end of line/text)
+        // Example: "test|"<EOL> "te|st" get rejected here
+        final CharSequence textAfterCursor = ic.getTextAfterCursor(1, 0);
+        if (!TextUtils.isEmpty(textAfterCursor)
+                && !mSettingsValues.isWordSeparator(textAfterCursor.charAt(0))) return;
+
+        // Bail out if word before cursor is 0-length or a single non letter (like an apostrophe)
+        // Example: " '|" gets rejected here but "I'|" and "I|" are okay
+        final CharSequence word = EditingUtils.getWordAtCursor(ic, mSettingsValues.mWordSeparators);
+        if (TextUtils.isEmpty(word)) return;
+        if (word.length() == 1 && !Character.isLetter(word.charAt(0))) return;
+
+        // Okay, we are at the end of a word. Restart suggestions.
+        restartSuggestionsOnWordBeforeCursor(ic, word);
+    }
+
+    // "ic" must not be null
+    private void restartSuggestionsOnWordBeforeCursor(final InputConnection ic,
+            final CharSequence word) {
+        mWordComposer.setComposingWord(word, mKeyboardSwitcher.getLatinKeyboard());
+        mComposingStringBuilder.setLength(0);
+        mComposingStringBuilder.append(word);
+        // mBestWord will be set appropriately by updateSuggestions() called by the handler
+        mBestWord = null;
+        mHasUncommittedTypedChars = true;
+        mComposingStateManager.onStartComposingText();
+        TextEntryState.restartSuggestionsOnWordBeforeCursor();
+        ic.deleteSurroundingText(word.length(), 0);
+        ic.setComposingText(word, 1);
+        mHandler.postUpdateSuggestions();
+    }
+
+    // "ic" must not be null
     private void revertLastWord(final InputConnection ic) {
         if (mHasUncommittedTypedChars || mComposingStringBuilder.length() <= 0) {
             sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
@@ -2143,6 +2198,10 @@
             // Clear composing text
             mComposingStringBuilder.setLength(0);
         } else {
+            // Note: this relies on the last word still being held in the WordComposer
+            // Note: in the interest of code simplicity, we may want to just call
+            // restartSuggestionsOnWordBeforeCursorIfAtEndOfWord instead, but retrieving
+            // the old WordComposer allows to reuse the actual typed coordinates.
             mHasUncommittedTypedChars = true;
             ic.setComposingText(mComposingStringBuilder, 1);
             TextEntryState.backspace();
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 5a3c348..2a36f82 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -82,8 +82,6 @@
     public static final String DICT_KEY_USER_BIGRAM = "user_bigram";
     public static final String DICT_KEY_WHITELIST ="whitelist";
 
-    private static String SINGLE_QUOTE_AS_STRING = String.valueOf((char)Keyboard.CODE_SINGLE_QUOTE);
-
     private static final boolean DBG = LatinImeLogger.sDBG;
 
     private AutoCorrection mAutoCorrection;
@@ -109,7 +107,7 @@
     // TODO: Remove these member variables by passing more context to addWord() callback method
     private boolean mIsFirstCharCapitalized;
     private boolean mIsAllUpperCase;
-    private boolean mIsLastCharASingleQuote;
+    private int mTrailingSingleQuotesCount;
 
     private int mCorrectionMode = CORRECTION_BASIC;
 
@@ -299,13 +297,14 @@
         mAutoCorrection.init();
         mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
         mIsAllUpperCase = wordComposer.isAllUpperCase();
-        mIsLastCharASingleQuote = wordComposer.isLastCharASingleQuote();
+        mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
         collectGarbage(mSuggestions, mPrefMaxSuggestions);
         Arrays.fill(mScores, 0);
 
         final String typedWord = wordComposer.getTypedWord();
-        final String consideredWord = mIsLastCharASingleQuote
-                ? typedWord.substring(0, typedWord.length() - 1) : typedWord;
+        final String consideredWord = mTrailingSingleQuotesCount > 0
+                ? typedWord.substring(0, typedWord.length() - mTrailingSingleQuotesCount)
+                : typedWord;
         if (typedWord != null) {
             // Treating USER_TYPED as UNIGRAM suggestion for logging now.
             LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED,
@@ -360,9 +359,11 @@
                 if (key.equals(DICT_KEY_USER_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
                     continue;
                 final Dictionary dictionary = mUnigramDictionaries.get(key);
-                if (mIsLastCharASingleQuote) {
+                if (mTrailingSingleQuotesCount > 0) {
                     final WordComposer tmpWordComposer = new WordComposer(wordComposer);
-                    tmpWordComposer.deleteLast();
+                    for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+                        tmpWordComposer.deleteLast();
+                    }
                     dictionary.getWords(tmpWordComposer, this, proximityInfo);
                 } else {
                     dictionary.getWords(wordComposer, this, proximityInfo);
@@ -380,8 +381,15 @@
                 whitelistedWord);
 
         if (whitelistedWord != null) {
-            mSuggestions.add(0, mIsLastCharASingleQuote
-                    ? whitelistedWord + SINGLE_QUOTE_AS_STRING : whitelistedWord);
+            if (mTrailingSingleQuotesCount > 0) {
+                final StringBuilder sb = new StringBuilder(whitelistedWord);
+                for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+                    sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
+                }
+                mSuggestions.add(0, sb.toString());
+            } else {
+                mSuggestions.add(0, whitelistedWord);
+            }
         }
 
         if (typedWord != null) {
@@ -500,7 +508,7 @@
         } else {
             sb.append(word, offset, length);
         }
-        if (mIsLastCharASingleQuote) {
+        for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
             sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
         }
         suggestions.add(pos, sb);
diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java
index 82242f8..a6041b3 100644
--- a/java/src/com/android/inputmethod/latin/TextEntryState.java
+++ b/java/src/com/android/inputmethod/latin/TextEntryState.java
@@ -146,9 +146,24 @@
         } else if (sState == UNDO_COMMIT) {
             setState(IN_WORD);
         }
+        // TODO: tidy up this logic. At the moment, for example, writing a word goes to
+        // ACCEPTED_DEFAULT, backspace will go to UNDO_COMMIT, another backspace will go to IN_WORD,
+        // and subsequent backspaces will leave the status at IN_WORD, even if the user backspaces
+        // past the end of the word. We are not in a word any more but the state is still IN_WORD.
         if (DEBUG) displayState("backspace");
     }
 
+    public static void restartSuggestionsOnWordBeforeCursor() {
+        if (UNKNOWN == sState || ACCEPTED_DEFAULT == sState) {
+            // Here we can come from pretty much any state, except the ones that we can't
+            // come from after backspace, so supposedly anything except UNKNOWN and
+            // ACCEPTED_DEFAULT. Note : we could be in UNDO_COMMIT if
+            // LatinIME#revertLastWord() was calling LatinIME#restartSuggestions...()
+            Log.e(TAG, "Strange state change : coming from state " + sState);
+        }
+        setState(IN_WORD);
+    }
+
     public static void reset() {
         setState(START);
         if (DEBUG) displayState("reset");
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 612b160..44c89f7 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -17,7 +17,9 @@
 package com.android.inputmethod.latin;
 
 import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.LatinKeyboard;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -44,7 +46,7 @@
 
     private boolean mAutoCapitalized;
     // Cache this value for performance
-    private boolean mIsLastCharASingleQuote;
+    private int mTrailingSingleQuotesCount;
 
     /**
      * Whether the user chose to capitalize the first char of the word.
@@ -57,7 +59,7 @@
         mTypedWord = new StringBuilder(N);
         mXCoordinates = new int[N];
         mYCoordinates = new int[N];
-        mIsLastCharASingleQuote = false;
+        mTrailingSingleQuotesCount = 0;
     }
 
     public WordComposer(WordComposer source) {
@@ -72,7 +74,7 @@
         mCapsCount = source.mCapsCount;
         mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
         mAutoCapitalized = source.mAutoCapitalized;
-        mIsLastCharASingleQuote = source.mIsLastCharASingleQuote;
+        mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
     }
 
     /**
@@ -83,7 +85,7 @@
         mTypedWord.setLength(0);
         mCapsCount = 0;
         mIsFirstCharCapitalized = false;
-        mIsLastCharASingleQuote = false;
+        mTrailingSingleQuotesCount = 0;
     }
 
     /**
@@ -133,7 +135,55 @@
         mIsFirstCharCapitalized = isFirstCharCapitalized(
                 newIndex, primaryCode, mIsFirstCharCapitalized);
         if (Character.isUpperCase(primaryCode)) mCapsCount++;
-        mIsLastCharASingleQuote = Keyboard.CODE_SINGLE_QUOTE == primaryCode;
+        if (Keyboard.CODE_SINGLE_QUOTE == primaryCode) {
+            ++mTrailingSingleQuotesCount;
+        } else {
+            mTrailingSingleQuotesCount = 0;
+        }
+    }
+
+    /**
+     * Internal method to retrieve reasonable proximity info for a character.
+     */
+    private void addKeyInfo(final int codePoint, final LatinKeyboard keyboard,
+            final KeyDetector keyDetector) {
+        for (final Key key : keyboard.mKeys) {
+            if (key.mCode == codePoint) {
+                final int x = key.mX + key.mWidth / 2;
+                final int y = key.mY + key.mHeight / 2;
+                final int[] codes = keyDetector.newCodeArray();
+                keyDetector.getKeyAndNearbyCodes(x, y, codes);
+                add(codePoint, codes, x, y);
+                return;
+            }
+        }
+        add(codePoint, new int[] { codePoint },
+                WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+    }
+
+    /**
+     * Set the currently composing word to the one passed as an argument.
+     * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
+     */
+    public void setComposingWord(final CharSequence word, final LatinKeyboard keyboard,
+            final KeyDetector keyDetector) {
+        reset();
+        final int length = word.length();
+        for (int i = 0; i < length; ++i) {
+            int codePoint = word.charAt(i);
+            addKeyInfo(codePoint, keyboard, keyDetector);
+        }
+    }
+
+    /**
+     * Shortcut for the above method, this will create a new KeyDetector for the passed keyboard.
+     */
+    public void setComposingWord(final CharSequence word, final LatinKeyboard keyboard) {
+        final KeyDetector keyDetector = new KeyDetector(0);
+        keyDetector.setKeyboard(keyboard, 0, 0);
+        keyDetector.setProximityCorrectionEnabled(true);
+        keyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth);
+        setComposingWord(word, keyboard, keyDetector);
     }
 
     /**
@@ -165,10 +215,14 @@
         }
         if (size() == 0) {
             mIsFirstCharCapitalized = false;
-            mIsLastCharASingleQuote = false;
+        }
+        if (mTrailingSingleQuotesCount > 0) {
+            --mTrailingSingleQuotesCount;
         } else {
-            mIsLastCharASingleQuote =
-                    Keyboard.CODE_SINGLE_QUOTE == mTypedWord.codePointAt(mTypedWord.length() - 1);
+            for (int i = mTypedWord.length() - 1; i >= 0; --i) {
+                if (Keyboard.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break;
+                ++mTrailingSingleQuotesCount;
+            }
         }
     }
 
@@ -191,8 +245,8 @@
         return mIsFirstCharCapitalized;
     }
 
-    public boolean isLastCharASingleQuote() {
-        return mIsLastCharASingleQuote;
+    public int trailingSingleQuotesCount() {
+        return mTrailingSingleQuotesCount;
     }
 
     /**
diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
index 464930f..0d90e0e 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.text.TextUtils;
 
-import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.LatinKeyboard;
@@ -65,26 +64,9 @@
         return mSuggest.hasMainDictionary();
     }
 
-    private void addKeyInfo(WordComposer word, char c) {
-        for (final Key key : mKeyboard.mKeys) {
-            if (key.mCode == c) {
-                final int x = key.mX + key.mWidth / 2;
-                final int y = key.mY + key.mHeight / 2;
-                final int[] codes = mKeyDetector.newCodeArray();
-                mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
-                word.add(c, codes, x, y);
-                return;
-            }
-        }
-        word.add(c, new int[] { c }, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
-    }
-
     protected WordComposer createWordComposer(CharSequence s) {
         WordComposer word = new WordComposer();
-        for (int i = 0; i < s.length(); i++) {
-            final char c = s.charAt(i);
-            addKeyInfo(word, c);
-        }
+        word.setComposingWord(s, mKeyboard, mKeyDetector);
         return word;
     }