diff --git a/res/xml/kbd_extension.xml b/res/xml/kbd_extension.xml
new file mode 100755
index 0000000000000000000000000000000000000000..c64f666d085d545b34b03f1e6f7c1483015453e9
--- /dev/null
+++ b/res/xml/kbd_extension.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row android:rowEdgeFlags="top">
+        <Key android:keyLabel="!" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="\@"/>
+        <Key android:keyLabel="\#"/>        
+        <Key android:keyLabel="&amp;"/>
+        <Key android:keyLabel="-"/>
+        <Key android:keyLabel="\'"/>
+        <Key android:keyLabel=":"/>
+        <Key android:keyLabel="&quot;"/>
+        <Key android:keyLabel="/"/>
+        <Key android:keyLabel="\?" android:keyEdgeFlags="right"
+        />
+    </Row>
+
+    <Row android:rowEdgeFlags="bottom">
+        <Key android:codes="49" android:keyLabel="1" android:keyEdgeFlags="left"
+        />
+        <Key android:codes="50" android:keyLabel="2"
+        />
+        <Key android:codes="51" android:keyLabel="3"
+        />
+        <Key android:codes="52" android:keyLabel="4"
+        />
+        <Key android:codes="53" android:keyLabel="5"
+        />
+        <Key android:codes="54" android:keyLabel="6"/>
+        <Key android:codes="55" android:keyLabel="7"
+        />
+        <Key android:codes="56" android:keyLabel="8"/>
+        <Key android:codes="57" android:keyLabel="9"/>
+        <Key android:codes="48" android:keyLabel="0" 
+                android:keyEdgeFlags="right"/>
+    </Row>    
+</Keyboard>
diff --git a/src/com/android/inputmethod/latin/KeyboardSwitcher.java b/src/com/android/inputmethod/latin/KeyboardSwitcher.java
index c82587b719c4c08ead646e4a5d7df69502afdd8a..c7f90946dcbde974e2e3f3afef637c43246af872 100644
--- a/src/com/android/inputmethod/latin/KeyboardSwitcher.java
+++ b/src/com/android/inputmethod/latin/KeyboardSwitcher.java
@@ -147,6 +147,13 @@ public class KeyboardSwitcher {
         if (!mKeyboards.containsKey(id)) {
             LatinKeyboard keyboard = new LatinKeyboard(
                 mContext, id.mXml, id.mMode);
+            if (id.mMode == KEYBOARDMODE_NORMAL
+                    || id.mMode == KEYBOARDMODE_URL
+                    || id.mMode == KEYBOARDMODE_IM
+                    || id.mMode == KEYBOARDMODE_EMAIL) {
+                keyboard.setExtension(R.xml.kbd_extension);
+            }
+
             if (id.mEnableShiftLock) {
                 keyboard.enableShiftLock();
             }
diff --git a/src/com/android/inputmethod/latin/LatinKeyboard.java b/src/com/android/inputmethod/latin/LatinKeyboard.java
index 9b04aa2648316145017b64a0a188ee3c4ff46df5..e68e01dedf32acac19fbcd743013357506a82f14 100644
--- a/src/com/android/inputmethod/latin/LatinKeyboard.java
+++ b/src/com/android/inputmethod/latin/LatinKeyboard.java
@@ -31,6 +31,8 @@ public class LatinKeyboard extends Keyboard {
     private Drawable mOldShiftPreviewIcon;
     private Key mShiftKey;
     private Key mEnterKey;
+
+    private int mExtensionResId; 
     
     private static final int SHIFT_OFF = 0;
     private static final int SHIFT_ON = 1;
@@ -191,6 +193,14 @@ public class LatinKeyboard extends Keyboard {
         }
     }
 
+    public void setExtension(int resId) {
+        mExtensionResId = resId;
+    }
+
+    public int getExtension() {
+        return mExtensionResId;
+    }
+
     static class LatinKey extends Keyboard.Key {
         
         private boolean mShiftLockEnabled;
diff --git a/src/com/android/inputmethod/latin/LatinKeyboardView.java b/src/com/android/inputmethod/latin/LatinKeyboardView.java
index d9ff0aa8c7a2713f5ee8b630bb28bec52b7f4fbf..ecbd1adfb5166816eb325666dc5bef2c0da6dad9 100644
--- a/src/com/android/inputmethod/latin/LatinKeyboardView.java
+++ b/src/com/android/inputmethod/latin/LatinKeyboardView.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin;
 
+import java.util.List;
+
 import android.content.Context;
 import android.graphics.Canvas;
 import android.inputmethodservice.Keyboard;
@@ -25,9 +27,9 @@ import android.os.Handler;
 import android.os.Message;
 import android.os.SystemClock;
 import android.util.AttributeSet;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
-
-import java.util.List;
+import android.widget.PopupWindow;
 
 public class LatinKeyboardView extends KeyboardView {
 
@@ -66,7 +68,115 @@ public class LatinKeyboardView extends KeyboardView {
         }
     }
 
-    
+    private boolean mExtensionVisible;
+    private LatinKeyboardView mExtension;
+    private PopupWindow mExtensionPopup;
+    private boolean mFirstEvent;
+
+    @Override
+    public boolean onTouchEvent(MotionEvent me) {
+        if (((LatinKeyboard) getKeyboard()).getExtension() == 0) {
+            return super.onTouchEvent(me);
+        }
+        if (me.getY() < 0) {
+            if (mExtensionVisible) {
+                int action = me.getAction();
+                if (mFirstEvent) action = MotionEvent.ACTION_DOWN;
+                mFirstEvent = false;
+                MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
+                        action,
+                        me.getX(), me.getY() + mExtension.getHeight(), me.getMetaState());
+                boolean result = mExtension.onTouchEvent(translated);
+                translated.recycle();
+                if (me.getAction() == MotionEvent.ACTION_UP
+                        || me.getAction() == MotionEvent.ACTION_CANCEL) {
+                    closeExtension();
+                }
+                return result;
+            } else {
+                if (openExtension()) {
+                    MotionEvent cancel = MotionEvent.obtain(me.getDownTime(), me.getEventTime(),
+                            MotionEvent.ACTION_CANCEL, me.getX() - 100, me.getY() - 100, 0);
+                    super.onTouchEvent(cancel);
+                    cancel.recycle();
+                    if (mExtension.getHeight() > 0) {
+                        MotionEvent translated = MotionEvent.obtain(me.getEventTime(),
+                                me.getEventTime(),
+                                MotionEvent.ACTION_DOWN,
+                                me.getX(), me.getY() + mExtension.getHeight(),
+                                me.getMetaState());
+                        mExtension.onTouchEvent(translated);
+                        translated.recycle();
+                    } else {
+                        mFirstEvent = true;
+                    }
+                }
+                return true;
+            }
+        } else if (mExtensionVisible) {
+            closeExtension();
+            // Send a down event into the main keyboard first
+            MotionEvent down = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
+                    MotionEvent.ACTION_DOWN,
+                    me.getX(), me.getY(), me.getMetaState());
+            super.onTouchEvent(down);
+            down.recycle();
+            // Send the actual event
+            return super.onTouchEvent(me);
+        } else {
+            return super.onTouchEvent(me);
+        }
+    }
+
+    private boolean openExtension() {
+        if (((LatinKeyboard) getKeyboard()).getExtension() == 0) return false;
+        makePopupWindow();
+        mExtensionVisible = true;
+        return true;
+    }
+
+    private void makePopupWindow() {
+        if (mExtensionPopup == null) {
+            int[] windowLocation = new int[2];
+            mExtensionPopup = new PopupWindow(getContext());
+            mExtensionPopup.setBackgroundDrawable(null);
+            LayoutInflater li = (LayoutInflater) getContext().getSystemService(
+                    Context.LAYOUT_INFLATER_SERVICE);
+            mExtension = (LatinKeyboardView) li.inflate(R.layout.input, null);
+            mExtension.setOnKeyboardActionListener((LatinIME) getContext());
+            mExtension.setPopupParent(this);
+            mExtension.setPopupOffset(0, -windowLocation[1]);
+            Keyboard keyboard;
+            mExtension.setKeyboard(keyboard = new LatinKeyboard(getContext(),
+                    ((LatinKeyboard) getKeyboard()).getExtension()));
+            mExtensionPopup.setContentView(mExtension);
+            mExtensionPopup.setWidth(getWidth());
+            mExtensionPopup.setHeight(keyboard.getHeight());
+            getLocationInWindow(windowLocation);
+            // TODO: Fix the "- 30". 
+            mExtension.setPopupOffset(0, -windowLocation[1] - 30);
+            mExtensionPopup.showAtLocation(this, 0, 0, -keyboard.getHeight()
+                    + windowLocation[1]);
+        } else {
+            mExtension.setVisibility(VISIBLE);
+        }
+    }
+
+    @Override
+    public void closing() {
+        super.closing();
+        if (mExtensionPopup != null && mExtensionPopup.isShowing()) {
+            mExtensionPopup.dismiss();
+            mExtensionPopup = null;
+        }
+    }
+
+    private void closeExtension() {
+        mExtension.setVisibility(INVISIBLE);
+        mExtension.closing();
+        mExtensionVisible = false;
+    }
+
     /****************************  INSTRUMENTATION  *******************************/
 
     static final boolean DEBUG_AUTO_PLAY = false;