diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index b11e25bc3956fa3304aaa66d39d526c18c5b9ef3..15e00659338ca7d3a601745e5825165f3328f3d1 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -185,6 +185,8 @@
     <declare-styleable name="Keyboard_Key">
         <!-- The unicode value that this key outputs. -->
         <attr name="code" format="integer" />
+        <!-- The alternate unicode value that this key outputs while typing. -->
+        <attr name="altCode" format="integer" />
         <!-- The keys to display in the more keys keyboard. -->
         <attr name="moreKeys" format="string" />
         <!-- Maximum column of more keys keyboard -->
@@ -201,7 +203,7 @@
             <!-- This should be aligned with Key.ACTION_FLAGS_* -->
             <flag name="isRepeatable" value="0x01" />
             <flag name="noKeyPreview" value="0x02" />
-            <flag name="ignoreWhileTyping" value="0x04" />
+            <flag name="altCodeWhileTyping" value="0x04" />
         </attr>
         <!-- The string of characters to output when this key is pressed. -->
         <attr name="keyOutputText" format="string" />
diff --git a/java/res/xml-sw600dp/kbd_key_styles.xml b/java/res/xml-sw600dp/kbd_key_styles.xml
index 5e9a88dd717404e786903b36ccd6d126ed71c504..aba1a802930b72bf11a89b8dd70144c74044937e 100644
--- a/java/res/xml-sw600dp/kbd_key_styles.xml
+++ b/java/res/xml-sw600dp/kbd_key_styles.xml
@@ -77,13 +77,15 @@
         latin:styleName="shortcutKeyStyle"
         latin:code="@integer/key_shortcut"
         latin:keyIcon="iconShortcutKey"
-        latin:keyActionFlags="noKeyPreview|ignoreWhileTyping"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:altCode="@integer/key_space"
         latin:parentStyle="f2PopupStyle" />
     <key-style
         latin:styleName="settingsKeyStyle"
         latin:code="@integer/key_settings"
         latin:keyIcon="iconSettingsKey"
-        latin:keyActionFlags="noKeyPreview|ignoreWhileTyping"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:altCode="@integer/key_space"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="tabKeyStyle"
diff --git a/java/res/xml-sw768dp/kbd_key_styles.xml b/java/res/xml-sw768dp/kbd_key_styles.xml
index 499728d677ffd9bec2f47d5398fe3903e60b0cee..e6ec53dd1f81b01c9a06a9e1b8efe8e54ec5b096 100644
--- a/java/res/xml-sw768dp/kbd_key_styles.xml
+++ b/java/res/xml-sw768dp/kbd_key_styles.xml
@@ -59,13 +59,15 @@
         latin:styleName="shortcutKeyStyle"
         latin:code="@integer/key_shortcut"
         latin:keyIcon="iconShortcutKey"
-        latin:keyActionFlags="noKeyPreview|ignoreWhileTyping"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:altCode="@integer/key_space"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="settingsKeyStyle"
         latin:code="@integer/key_settings"
         latin:keyIcon="iconSettingsKey"
-        latin:keyActionFlags="noKeyPreview|ignoreWhileTyping"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:altCode="@integer/key_space"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="tabKeyStyle"
diff --git a/java/res/xml/kbd_key_styles.xml b/java/res/xml/kbd_key_styles.xml
index 835269a4ad246b42801139ea8bc95f205c3da8f0..5714e09c532c4341a74e771e1685b7d691df8f74 100644
--- a/java/res/xml/kbd_key_styles.xml
+++ b/java/res/xml/kbd_key_styles.xml
@@ -163,13 +163,15 @@
         latin:styleName="shortcutKeyStyle"
         latin:code="@integer/key_shortcut"
         latin:keyIcon="iconShortcutKey"
-        latin:keyActionFlags="noKeyPreview|ignoreWhileTyping"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:altCode="@integer/key_space"
         latin:parentStyle="f1PopupStyle" />
     <key-style
         latin:styleName="settingsKeyStyle"
         latin:code="@integer/key_settings"
         latin:keyIcon="iconSettingsKey"
-        latin:keyActionFlags="noKeyPreview|ignoreWhileTyping"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:altCode="@integer/key_space"
         latin:backgroundType="functional" />
     <key-style
         latin:styleName="tabKeyStyle"
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 84f2be585a2dddb506226b14c37c5cde74933097..b2b68f0ad06b0766730293bb30fccf6cd18cdf7d 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -46,6 +46,7 @@ public class Key {
      * The key code (unicode or custom code) that this key generates.
      */
     public final int mCode;
+    public final int mAltCode;
 
     /** Label to display */
     public final CharSequence mLabel;
@@ -108,7 +109,7 @@ public class Key {
     private final int mActionFlags;
     private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01;
     private static final int ACTION_FLAGS_NO_KEY_PREVIEW = 0x02;
-    private static final int ACTION_FLAGS_IGNORE_WHILE_TYPING = 0x04;
+    private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04;
 
     /** The current pressed state of this key */
     private boolean mPressed;
@@ -191,6 +192,7 @@ public class Key {
         mLabel = label;
         mOutputText = outputText;
         mCode = code;
+        mAltCode = Keyboard.CODE_DUMMY;
         mIcon = icon;
         // Horizontal gap is divided equally to both sides of the key.
         mX = x + mHorizontalGap / 2;
@@ -290,6 +292,8 @@ public class Key {
         } else {
             mCode = Keyboard.CODE_DUMMY;
         }
+        mAltCode = style.getInt(keyAttr,
+                R.styleable.Keyboard_Key_altCode, Keyboard.CODE_DUMMY);
 
         keyAttr.recycle();
     }
@@ -334,8 +338,8 @@ public class Key {
         return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0;
     }
 
-    public boolean ignoreWhileTyping() {
-        return (mActionFlags & ACTION_FLAGS_IGNORE_WHILE_TYPING) != 0;
+    public boolean altCodeWhileTyping() {
+        return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0;
     }
 
     public Typeface selectTypeface(Typeface defaultTypeface) {
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 4a28bf1de1c6b1d107f82fb4ae9b2fd4de2a7758..b3b20ca21d018bc71e3917a5260a92275b8870f0 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -24,6 +24,7 @@ import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.keyboard.internal.KeyboardShiftState;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -65,7 +66,6 @@ public class Keyboard {
     public static final int CODE_DIGIT0 = '0';
     public static final int CODE_PLUS = '+';
 
-
     /** Special keys code.  These should be aligned with values/keycodes.xml */
     public static final int CODE_DUMMY = 0;
     public static final int CODE_SHIFT = -1;
@@ -111,6 +111,7 @@ public class Keyboard {
     public final Map<Key, Drawable> mUnshiftedIcons;
     public final KeyboardIconsSet mIconsSet;
 
+    private final Map<Integer, Key> mKeyCache = new HashMap<Integer, Key>();
     private final KeyboardShiftState mShiftState = new KeyboardShiftState();
 
     private final ProximityInfo mProximityInfo;
@@ -145,6 +146,22 @@ public class Keyboard {
         return mProximityInfo;
     }
 
+    public Key getKey(int code) {
+        final Integer keyCode = code;
+        if (mKeyCache.containsKey(keyCode)) {
+            return mKeyCache.get(keyCode);
+        }
+
+        for (final Key key : mKeys) {
+            if (key.mCode == code) {
+                mKeyCache.put(keyCode, key);
+                return key;
+            }
+        }
+        mKeyCache.put(keyCode, null);
+        return null;
+    }
+
     public boolean hasShiftLockKey() {
         return !mShiftLockKeys.isEmpty();
     }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index da60af3be1925de11f3b916aac1a30a94ab72714..4e8765dcc289b367ea27f81d091b47638b6f2899 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -258,23 +258,27 @@ public class PointerTracker {
     // primaryCode is different from {@link Key#mCode}.
     private void callListenerOnCodeInput(Key key, int primaryCode, int[] keyCodes, int x, int y) {
         final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
-        if (DEBUG_LISTENER)
-            Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode)
+        final boolean alterCode = key.altCodeWhileTyping() && mTimerProxy.isTyping();
+        final int code = alterCode ? key.mAltCode : primaryCode;
+        // If code is CODE_DUMMY here, this key will be ignored or generate text.
+        final CharSequence text = (code != Keyboard.CODE_DUMMY) ? null : key.mOutputText;
+        if (DEBUG_LISTENER) {
+            Log.d(TAG, "onCodeInput: " + keyCodePrintable(code) + " text=" + text
                     + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y
-                    + " ignoreModifier=" + ignoreModifierKey);
+                    + " ignoreModifier=" + ignoreModifierKey + " alterCode=" + alterCode);
+        }
         if (ignoreModifierKey) {
             return;
         }
         if (key.isEnabled()) {
-            mListener.onCodeInput(primaryCode, keyCodes, x, y);
-        }
-    }
-
-    private void callListenerOnTextInput(Key key) {
-        if (DEBUG_LISTENER)
-            Log.d(TAG, "onTextInput: text=" + key.mOutputText);
-        if (key.isEnabled()) {
-            mListener.onTextInput(key.mOutputText);
+            if (code != Keyboard.CODE_DUMMY) {
+                mListener.onCodeInput(code, keyCodes, x, y);
+            } else if (text != null) {
+                mListener.onTextInput(text);
+            }
+            if (!key.altCodeWhileTyping() && !key.isModifier()) {
+                mTimerProxy.startKeyTypedTimer(sIgnoreSpecialKeyTimeout);
+            }
         }
     }
 
@@ -328,6 +332,23 @@ public class PointerTracker {
         if (key != null && key.isEnabled()) {
             key.onReleased();
             mDrawingProxy.invalidateKey(key);
+
+            if (key.isShift()) {
+                for (final Key shiftKey : mKeyboard.mShiftKeys) {
+                    if (shiftKey != key) {
+                        shiftKey.onReleased();
+                        mDrawingProxy.invalidateKey(shiftKey);
+                    }
+                }
+            }
+
+            if (key.altCodeWhileTyping()) {
+                final Key altKey = mKeyboard.getKey(key.mAltCode);
+                if (altKey != null) {
+                    altKey.onReleased();
+                    mDrawingProxy.invalidateKey(altKey);
+                }
+            }
         }
     }
 
@@ -338,6 +359,24 @@ public class PointerTracker {
             }
             key.onPressed();
             mDrawingProxy.invalidateKey(key);
+
+            if (key.isShift()) {
+                for (final Key shiftKey : mKeyboard.mShiftKeys) {
+                    if (shiftKey != key) {
+                        shiftKey.onPressed();
+                        mDrawingProxy.invalidateKey(shiftKey);
+                    }
+                }
+            }
+
+            if (key.altCodeWhileTyping() && mTimerProxy.isTyping()) {
+                final Key altKey = mKeyboard.getKey(key.mAltCode);
+                if (altKey != null) {
+                    // TODO: Show altKey's preview.
+                    altKey.onPressed();
+                    mDrawingProxy.invalidateKey(altKey);
+                }
+            }
         }
     }
 
@@ -696,45 +735,27 @@ public class PointerTracker {
             callListenerOnCancelInput();
             return;
         }
-        if (key.mOutputText != null) {
-            final boolean ignoreText = key.ignoreWhileTyping() && mTimerProxy.isTyping();
-            if (!ignoreText) {
-                callListenerOnTextInput(key);
-            }
-            callListenerOnRelease(key, key.mCode, false);
-            if (!ignoreText) {
-                mTimerProxy.startKeyTypedTimer(sIgnoreSpecialKeyTimeout);
-            }
-        } else {
-            int code = key.mCode;
-            final int[] codes = mKeyDetector.newCodeArray();
-            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.
-            if (mKeyboard.isManualTemporaryUpperCase() && key.hasUppercaseLetter()) {
-                code = key.mHintLabel.charAt(0);
-                codes[0] = code;
-            }
 
-            // Swap the first and second values in the codes array if the primary code is not the
-            // first value but the second value in the array. This happens when key debouncing is
-            // in effect.
-            if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
-                codes[1] = codes[0];
-                codes[0] = code;
-            }
-            final boolean ignoreCode = key.ignoreWhileTyping() && mTimerProxy.isTyping();
-            if (!ignoreCode) {
-                // TODO: It might be useful to register the nearest key code in codes[] instead of
-                // just ignoring.
-                callListenerOnCodeInput(key, code, codes, x, y);
-            }
-            callListenerOnRelease(key, code, false);
-            if (!key.ignoreWhileTyping() && !key.isModifier()) {
-                mTimerProxy.startKeyTypedTimer(sIgnoreSpecialKeyTimeout);
-            }
+        int code = key.mCode;
+        final int[] codes = mKeyDetector.newCodeArray();
+        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.
+        if (mKeyboard.isManualTemporaryUpperCase() && key.hasUppercaseLetter()) {
+            code = key.mHintLabel.charAt(0);
+            codes[0] = code;
+        }
+
+        // Swap the first and second values in the codes array if the primary code is not the
+        // first value but the second value in the array. This happens when key debouncing is
+        // in effect.
+        if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
+            codes[1] = codes[0];
+            codes[0] = code;
         }
+        callListenerOnCodeInput(key, code, codes, x, y);
+        callListenerOnRelease(key, code, false);
     }
 
     private long mPreviousEventTime;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
index 565edb90104b145005a9feff816d06ef8fd85b00..21879350090e465830098d9d85b5b96e942445d4 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
@@ -152,6 +152,7 @@ public class KeyStyles {
         private void parseKeyStyleAttributes(TypedArray keyAttr) {
             // TODO: Currently not all Key attributes can be declared as style.
             readInt(keyAttr, R.styleable.Keyboard_Key_code);
+            readInt(keyAttr, R.styleable.Keyboard_Key_altCode);
             readText(keyAttr, R.styleable.Keyboard_Key_keyLabel);
             readText(keyAttr, R.styleable.Keyboard_Key_keyOutputText);
             readText(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);