diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index a87ff989171fb78e33e47f4a3289f02a18a49a3e..38d904a94455536c004a172c83dd8bfbb1aad828 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -29,7 +29,7 @@ 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.KeyboardView;
+import com.android.inputmethod.keyboard.LatinKeyboardBaseView;
 import com.android.inputmethod.keyboard.PointerTracker;
 
 public class AccessibleKeyboardViewProxy {
@@ -40,7 +40,7 @@ public class AccessibleKeyboardViewProxy {
     private static final long DELAY_KEY_PRESS = 10;
 
     private int mScaledEdgeSlop;
-    private KeyboardView mView;
+    private LatinKeyboardBaseView mView;
     private AccessibleKeyboardActionListener mListener;
     private FlickGestureDetector mGestureDetector;
 
@@ -57,7 +57,7 @@ public class AccessibleKeyboardViewProxy {
         return sInstance;
     }
 
-    public static void setView(KeyboardView view) {
+    public static void setView(LatinKeyboardBaseView view) {
         sInstance.mView = view;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 752f87d6646d5813032b7c527025d51fc92ac57f..f3e023d3ebb3ab8c2257c59bd7a53e6e7f484a74 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.keyboard;
 
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
@@ -32,39 +31,24 @@ import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.os.Message;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.TypedValue;
-import android.view.GestureDetector;
 import android.view.LayoutInflater;
-import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.PopupWindow;
 import android.widget.TextView;
 
-import com.android.inputmethod.accessibility.AccessibilityUtils;
-import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
 import com.android.inputmethod.compat.FrameLayoutCompatUtils;
-import com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder;
-import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
-import com.android.inputmethod.keyboard.internal.SwipeTracker;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
 
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.WeakHashMap;
 
 /**
- * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and detecting key
- * presses and touch movements.
+ * A view that renders a virtual {@link Keyboard}.
  *
  * @attr ref R.styleable#KeyboardView_backgroundDimAmount
  * @attr ref R.styleable#KeyboardView_keyBackground
- * @attr ref R.styleable#KeyboardView_keyHysteresisDistance
  * @attr ref R.styleable#KeyboardView_keyLetterRatio
  * @attr ref R.styleable#KeyboardView_keyLargeLetterRatio
  * @attr ref R.styleable#KeyboardView_keyLabelRatio
@@ -85,29 +69,17 @@ import java.util.WeakHashMap;
  * @attr ref R.styleable#KeyboardView_keyHintLabelColor
  * @attr ref R.styleable#KeyboardView_keyUppercaseLetterInactivatedColor
  * @attr ref R.styleable#KeyboardView_keyUppercaseLetterActivatedColor
- * @attr ref R.styleable#KeyboardView_verticalCorrection
- * @attr ref R.styleable#KeyboardView_popupLayout
  * @attr ref R.styleable#KeyboardView_shadowColor
  * @attr ref R.styleable#KeyboardView_shadowRadius
  */
 public class KeyboardView extends View implements PointerTracker.UIProxy {
-    private static final String TAG = KeyboardView.class.getSimpleName();
     private static final boolean DEBUG_KEYBOARD_GRID = false;
 
-    private static final boolean ENABLE_CAPSLOCK_BY_LONGPRESS = true;
-    private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true;
-
-    // Timing constants
-    private final int mKeyRepeatInterval;
-
     // Miscellaneous constants
     private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
 
     // XML attribute
     private final float mBackgroundDimAmount;
-    private final float mKeyHysteresisDistance;
-    private final float mVerticalCorrection;
-    private final int mPopupLayout;
 
     // HORIZONTAL ELLIPSIS "...", character for popup hint.
     private static final String POPUP_HINT_CHAR = "\u2026";
@@ -124,32 +96,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
     private int mDelayAfterPreview;
     private ViewGroup mPreviewPlacer;
 
-    // Mini keyboard
-    private PopupWindow mPopupWindow;
-    private PopupPanel mPopupMiniKeyboardPanel;
-    private final WeakHashMap<Key, PopupPanel> mPopupPanelCache =
-            new WeakHashMap<Key, PopupPanel>();
-
-    /** Listener for {@link KeyboardActionListener}. */
-    private KeyboardActionListener mKeyboardActionListener;
-
-    private final ArrayList<PointerTracker> mPointerTrackers = new ArrayList<PointerTracker>();
-
-    // TODO: Let the PointerTracker class manage this pointer queue
-    private final PointerTrackerQueue mPointerQueue = new PointerTrackerQueue();
-
-    private final boolean mHasDistinctMultitouch;
-    private int mOldPointerCount = 1;
-    private int mOldKeyIndex;
-
-    protected KeyDetector mKeyDetector = new KeyDetector();
-
-    // Swipe gesture detector
-    protected GestureDetector mGestureDetector;
-    private final SwipeTracker mSwipeTracker = new SwipeTracker();
-    private final int mSwipeThreshold;
-    private final boolean mDisambiguateSwipe;
-
     // Drawing
     /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
     private boolean mDrawPending;
@@ -182,12 +128,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
     public static class UIHandler extends StaticInnerHandlerWrapper<KeyboardView> {
         private static final int MSG_SHOW_KEY_PREVIEW = 1;
         private static final int MSG_DISMISS_KEY_PREVIEW = 2;
-        private static final int MSG_REPEAT_KEY = 3;
-        private static final int MSG_LONGPRESS_KEY = 4;
-        private static final int MSG_LONGPRESS_SHIFT_KEY = 5;
-        private static final int MSG_IGNORE_DOUBLE_TAP = 6;
-
-        private boolean mInKeyRepeat;
 
         public UIHandler(KeyboardView outerInstance) {
             super(outerInstance);
@@ -204,16 +144,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
             case MSG_DISMISS_KEY_PREVIEW:
                 keyboardView.mPreviewText.setVisibility(View.INVISIBLE);
                 break;
-            case MSG_REPEAT_KEY:
-                tracker.onRepeatKey(msg.arg1);
-                startKeyRepeatTimer(keyboardView.mKeyRepeatInterval, msg.arg1, tracker);
-                break;
-            case MSG_LONGPRESS_KEY:
-                keyboardView.openMiniKeyboardIfRequired(msg.arg1, tracker);
-                break;
-            case MSG_LONGPRESS_SHIFT_KEY:
-                keyboardView.onLongPressShiftKey(tracker);
-                break;
             }
         }
 
@@ -249,55 +179,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
             removeMessages(MSG_DISMISS_KEY_PREVIEW);
         }
 
-        public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {
-            mInKeyRepeat = true;
-            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay);
-        }
-
-        public void cancelKeyRepeatTimer() {
-            mInKeyRepeat = false;
-            removeMessages(MSG_REPEAT_KEY);
-        }
-
-        public boolean isInKeyRepeat() {
-            return mInKeyRepeat;
-        }
-
-        public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {
-            cancelLongPressTimers();
-            sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay);
-        }
-
-        public void startLongPressShiftTimer(long delay, int keyIndex, PointerTracker tracker) {
-            cancelLongPressTimers();
-            if (ENABLE_CAPSLOCK_BY_LONGPRESS) {
-                sendMessageDelayed(
-                        obtainMessage(MSG_LONGPRESS_SHIFT_KEY, keyIndex, 0, tracker), delay);
-            }
-        }
-
-        public void cancelLongPressTimers() {
-            removeMessages(MSG_LONGPRESS_KEY);
-            removeMessages(MSG_LONGPRESS_SHIFT_KEY);
-        }
-
-        public void cancelKeyTimers() {
-            cancelKeyRepeatTimer();
-            cancelLongPressTimers();
-            removeMessages(MSG_IGNORE_DOUBLE_TAP);
-        }
-
-        public void startIgnoringDoubleTap() {
-            sendMessageDelayed(obtainMessage(MSG_IGNORE_DOUBLE_TAP),
-                    ViewConfiguration.getDoubleTapTimeout());
-        }
-
-        public boolean isIgnoringDoubleTap() {
-            return hasMessages(MSG_IGNORE_DOUBLE_TAP);
-        }
-
         public void cancelAllMessages() {
-            cancelKeyTimers();
             cancelAllShowKeyPreviews();
             cancelAllDismissKeyPreviews();
         }
@@ -442,10 +324,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
 
         mKeyDrawParams = new KeyDrawParams(a);
         mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams);
-        mKeyHysteresisDistance = a.getDimensionPixelOffset(
-                R.styleable.KeyboardView_keyHysteresisDistance, 0);
-        mVerticalCorrection = a.getDimensionPixelOffset(
-                R.styleable.KeyboardView_verticalCorrection, 0);
         final int previewLayout = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
         if (previewLayout != 0) {
             mPreviewText = (TextView) LayoutInflater.from(context).inflate(previewLayout, null);
@@ -453,7 +331,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
             mPreviewText = null;
             mShowKeyPreviewPopup = false;
         }
-        mPopupLayout = a.getResourceId(R.styleable.KeyboardView_popupLayout, 0);
         // TODO: Use Theme (android.R.styleable.Theme_backgroundDimAmount)
         mBackgroundDimAmount = a.getFloat(R.styleable.KeyboardView_backgroundDimAmount, 0.5f);
         a.recycle();
@@ -466,82 +343,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         mPaint.setAntiAlias(true);
         mPaint.setTextAlign(Align.CENTER);
         mPaint.setAlpha(255);
-
-        mSwipeThreshold = (int) (500 * res.getDisplayMetrics().density);
-        // TODO: Refer to frameworks/base/core/res/res/values/config.xml
-        mDisambiguateSwipe = res.getBoolean(R.bool.config_swipeDisambiguation);
-
-        GestureDetector.SimpleOnGestureListener listener =
-                new GestureDetector.SimpleOnGestureListener() {
-            private boolean mProcessingShiftDoubleTapEvent = false;
-
-            @Override
-            public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX,
-                    float velocityY) {
-                final float absX = Math.abs(velocityX);
-                final float absY = Math.abs(velocityY);
-                float deltaY = me2.getY() - me1.getY();
-                int travelY = getHeight() / 2; // Half the keyboard height
-                mSwipeTracker.computeCurrentVelocity(1000);
-                final float endingVelocityY = mSwipeTracker.getYVelocity();
-                if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
-                    if (mDisambiguateSwipe && endingVelocityY >= velocityY / 4) {
-                        onSwipeDown();
-                        return true;
-                    }
-                }
-                return false;
-            }
-
-            @Override
-            public boolean onDoubleTap(MotionEvent firstDown) {
-                if (ENABLE_CAPSLOCK_BY_DOUBLETAP && mKeyboard instanceof LatinKeyboard
-                        && ((LatinKeyboard) mKeyboard).isAlphaKeyboard()) {
-                    final int pointerIndex = firstDown.getActionIndex();
-                    final int id = firstDown.getPointerId(pointerIndex);
-                    final PointerTracker tracker = getPointerTracker(id);
-                    // If the first down event is on shift key.
-                    if (tracker.isOnShiftKey((int)firstDown.getX(), (int)firstDown.getY())) {
-                        mProcessingShiftDoubleTapEvent = true;
-                        return true;
-                    }
-                }
-                mProcessingShiftDoubleTapEvent = false;
-                return false;
-            }
-
-            @Override
-            public boolean onDoubleTapEvent(MotionEvent secondTap) {
-                if (mProcessingShiftDoubleTapEvent
-                        && secondTap.getAction() == MotionEvent.ACTION_DOWN) {
-                    final MotionEvent secondDown = secondTap;
-                    final int pointerIndex = secondDown.getActionIndex();
-                    final int id = secondDown.getPointerId(pointerIndex);
-                    final PointerTracker tracker = getPointerTracker(id);
-                    // If the second down event is also on shift key.
-                    if (tracker.isOnShiftKey((int)secondDown.getX(), (int)secondDown.getY())) {
-                        // 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} .
-                        final boolean ignoringDoubleTap = mHandler.isIgnoringDoubleTap();
-                        if (!ignoringDoubleTap)
-                            onDoubleTapShiftKey(tracker);
-                        return true;
-                    }
-                    // Otherwise these events should not be handled as double tap.
-                    mProcessingShiftDoubleTapEvent = false;
-                }
-                return mProcessingShiftDoubleTapEvent;
-            }
-        };
-
-        final boolean ignoreMultitouch = true;
-        mGestureDetector = new GestureDetector(getContext(), listener, null, ignoreMultitouch);
-        mGestureDetector.setIsLongpressEnabled(false);
-
-        mHasDistinctMultitouch = context.getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
-        mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
     }
 
     // Read fraction value in TypedArray as float.
@@ -549,26 +350,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
     }
 
-    public void startIgnoringDoubleTap() {
-        if (ENABLE_CAPSLOCK_BY_DOUBLETAP)
-            mHandler.startIgnoringDoubleTap();
-    }
-
-    public void setOnKeyboardActionListener(KeyboardActionListener listener) {
-        mKeyboardActionListener = listener;
-        for (PointerTracker tracker : mPointerTrackers) {
-            tracker.setOnKeyboardActionListener(listener);
-        }
-    }
-
-    /**
-     * Returns the {@link KeyboardActionListener} object.
-     * @return the listener attached to this keyboard
-     */
-    protected KeyboardActionListener getOnKeyboardActionListener() {
-        return mKeyboardActionListener;
-    }
-
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         // TODO: Should notify InputMethodService instead?
@@ -583,24 +364,13 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
      * @param keyboard the keyboard to display in this view
      */
     public void setKeyboard(Keyboard keyboard) {
-        if (mKeyboard != null) {
-            dismissAllKeyPreviews();
-        }
         // Remove any pending messages, except dismissing preview
-        mHandler.cancelKeyTimers();
         mHandler.cancelAllShowKeyPreviews();
         mKeyboard = keyboard;
         LatinImeLogger.onSetKeyboard(keyboard);
-        mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
-                -getPaddingTop() + mVerticalCorrection);
-        for (PointerTracker tracker : mPointerTrackers) {
-            tracker.setKeyboard(keyboard, mKeyHysteresisDistance);
-        }
         requestLayout();
         mKeyboardChanged = true;
         invalidateAllKeys();
-        mKeyDetector.setProximityThreshold(keyboard.getMostCommonKeyWidth());
-        mPopupPanelCache.clear();
         final int keyHeight = keyboard.getRowHeight() - keyboard.getVerticalGap();
         mKeyDrawParams.updateKeyHeight(keyHeight);
         mKeyPreviewDrawParams.updateKeyHeight(keyHeight);
@@ -615,15 +385,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         return mKeyboard;
     }
 
-    /**
-     * Returns whether the device has distinct multi-touch panel.
-     * @return true if the device has distinct multi-touch panel.
-     */
-    @Override
-    public boolean hasDistinctMultitouch() {
-        return mHasDistinctMultitouch;
-    }
-
     /**
      * Enables or disables the key feedback popup. This is a popup that shows a magnified
      * version of the depressed key. By default the preview is enabled.
@@ -645,23 +406,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         return mShowKeyPreviewPopup;
     }
 
-    /**
-     * When enabled, calls to {@link KeyboardActionListener#onCodeInput} will include key
-     * codes for adjacent keys.  When disabled, only the primary key code will be
-     * reported.
-     * @param enabled whether or not the proximity correction is enabled
-     */
-    public void setProximityCorrectionEnabled(boolean enabled) {
-        mKeyDetector.setProximityCorrectionEnabled(enabled);
-    }
-
-    /**
-     * Returns true if proximity correction is enabled.
-     */
-    public boolean isProximityCorrectionEnabled() {
-        return mKeyDetector.isProximityCorrectionEnabled();
-    }
-
     @Override
     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // Round up a little
@@ -752,7 +496,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         }
 
         // Overlay a dark rectangle to dim the keyboard
-        if (mPopupMiniKeyboardPanel != null) {
+        if (needsToDimKeyboard()) {
             mPaint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
             canvas.drawRect(0, 0, width, height, mPaint);
         }
@@ -762,6 +506,10 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         mDirtyRect.setEmpty();
     }
 
+    protected boolean needsToDimKeyboard() {
+        return false;
+    }
+
     private static void onBufferDrawKey(final Key key, final Canvas canvas, Paint paint,
             KeyDrawParams params, boolean isManualTemporaryUpperCase) {
         final boolean debugShowAlign = LatinImeLogger.sVISUALDEBUG;
@@ -1017,15 +765,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         canvas.translate(-x, -y);
     }
 
-    // TODO: clean up this method.
-    private void dismissAllKeyPreviews() {
-        for (PointerTracker tracker : mPointerTrackers) {
-            tracker.setReleasedKeyGraphics();
-            dismissKeyPreview(tracker);
-        }
-    }
-
-    public void cancelAllMessage() {
+    public void cancelAllMessages() {
         mHandler.cancelAllMessages();
     }
 
@@ -1039,6 +779,11 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         }
     }
 
+    @Override
+    public void cancelShowKeyPreview(PointerTracker tracker) {
+        mHandler.cancelShowKeyPreview(tracker);
+    }
+
     @Override
     public void dismissKeyPreview(PointerTracker tracker) {
         if (mShowKeyPreviewPopup) {
@@ -1161,266 +906,11 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         invalidate(mInvalidatedKeyRect);
     }
 
-    private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) {
-        // Check if we have a popup layout specified first.
-        if (mPopupLayout == 0) {
-            return false;
-        }
-
-        final Key parentKey = tracker.getKey(keyIndex);
-        if (parentKey == null)
-            return false;
-        boolean result = onLongPress(parentKey, tracker);
-        if (result) {
-            dismissAllKeyPreviews();
-            tracker.onLongPressed(mPointerQueue);
-        }
-        return result;
-    }
-
-    private void onLongPressShiftKey(PointerTracker tracker) {
-        tracker.onLongPressed(mPointerQueue);
-        mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
-    }
-
-    private void onDoubleTapShiftKey(@SuppressWarnings("unused") PointerTracker tracker) {
-        // When shift key is double tapped, the first tap is correctly processed as usual tap. And
-        // the second tap is treated as this double tap event, so that we need not mark tracker
-        // calling setAlreadyProcessed() nor remove the tracker from mPointerQueueueue.
-        mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
-    }
-
-    // This default implementation returns a popup mini keyboard panel.
-    // A derived class may return a language switcher popup panel, for instance.
-    protected PopupPanel onCreatePopupPanel(Key parentKey) {
-        if (parentKey.mPopupCharacters == null)
-            return null;
-
-        final View container = LayoutInflater.from(getContext()).inflate(mPopupLayout, null);
-        if (container == null)
-            throw new NullPointerException();
-
-        final PopupMiniKeyboardView miniKeyboardView =
-                (PopupMiniKeyboardView)container.findViewById(R.id.mini_keyboard_view);
-        miniKeyboardView.setOnKeyboardActionListener(new KeyboardActionListener() {
-            @Override
-            public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
-                mKeyboardActionListener.onCodeInput(primaryCode, keyCodes, x, y);
-                dismissMiniKeyboard();
-            }
-
-            @Override
-            public void onTextInput(CharSequence text) {
-                mKeyboardActionListener.onTextInput(text);
-                dismissMiniKeyboard();
-            }
-
-            @Override
-            public void onCancelInput() {
-                mKeyboardActionListener.onCancelInput();
-                dismissMiniKeyboard();
-            }
-
-            @Override
-            public void onSwipeDown() {
-                // Nothing to do.
-            }
-            @Override
-            public void onPress(int primaryCode, boolean withSliding) {
-                mKeyboardActionListener.onPress(primaryCode, withSliding);
-            }
-            @Override
-            public void onRelease(int primaryCode, boolean withSliding) {
-                mKeyboardActionListener.onRelease(primaryCode, withSliding);
-            }
-        });
-
-        final Keyboard keyboard = new MiniKeyboardBuilder(this, mKeyboard.getPopupKeyboardResId(),
-                parentKey, mKeyboard).build();
-        miniKeyboardView.setKeyboard(keyboard);
-
-        container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
-                MEASURESPEC_UNSPECIFIED);
-
-        return miniKeyboardView;
-    }
-
-    /**
-     * Called when a key is long pressed. By default this will open mini keyboard associated
-     * with this key.
-     * @param parentKey the key that was long pressed
-     * @param tracker the pointer tracker which pressed the parent key
-     * @return true if the long press is handled, false otherwise. Subclasses should call the
-     * method on the base class if the subclass doesn't wish to handle the call.
-     */
-    protected boolean onLongPress(Key parentKey, PointerTracker tracker) {
-        PopupPanel popupPanel = mPopupPanelCache.get(parentKey);
-        if (popupPanel == null) {
-            popupPanel = onCreatePopupPanel(parentKey);
-            if (popupPanel == null)
-                return false;
-            mPopupPanelCache.put(parentKey, popupPanel);
-        }
-        if (mPopupWindow == null) {
-            mPopupWindow = new PopupWindow(getContext());
-            mPopupWindow.setBackgroundDrawable(null);
-            mPopupWindow.setAnimationStyle(R.style.PopupMiniKeyboardAnimation);
-            // Allow popup window to be drawn off the screen.
-            mPopupWindow.setClippingEnabled(false);
-        }
-        mPopupMiniKeyboardPanel = popupPanel;
-        popupPanel.showPanel(this, parentKey, tracker, mPopupWindow);
-
-        invalidateAllKeys();
-        return true;
-    }
-
-    private PointerTracker getPointerTracker(final int id) {
-        final ArrayList<PointerTracker> pointers = mPointerTrackers;
-        final KeyboardActionListener listener = mKeyboardActionListener;
-
-        // Create pointer trackers until we can get 'id+1'-th tracker, if needed.
-        for (int i = pointers.size(); i <= id; i++) {
-            final PointerTracker tracker =
-                new PointerTracker(i, this, mHandler, mKeyDetector, this);
-            if (mKeyboard != null)
-                tracker.setKeyboard(mKeyboard, mKeyHysteresisDistance);
-            if (listener != null)
-                tracker.setOnKeyboardActionListener(listener);
-            pointers.add(tracker);
-        }
-
-        return pointers.get(id);
-    }
-
-    public boolean isInSlidingKeyInput() {
-        if (mPopupMiniKeyboardPanel != null) {
-            return mPopupMiniKeyboardPanel.isInSlidingKeyInput();
-        } else {
-            return mPointerQueue.isInSlidingKeyInput();
-        }
-    }
-
-    public int getPointerCount() {
-        return mOldPointerCount;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent me) {
-        final int action = me.getActionMasked();
-        final int pointerCount = me.getPointerCount();
-        final int oldPointerCount = mOldPointerCount;
-        mOldPointerCount = pointerCount;
-
-        // TODO: cleanup this code into a multi-touch to single-touch event converter class?
-        // If the device does not have distinct multi-touch support panel, ignore all multi-touch
-        // events except a transition from/to single-touch.
-        if (!mHasDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
-            return true;
-        }
-
-        // Track the last few movements to look for spurious swipes.
-        mSwipeTracker.addMovement(me);
-
-        // Gesture detector must be enabled only when mini-keyboard is not on the screen.
-        if (mPopupMiniKeyboardPanel == null && mGestureDetector != null
-                && mGestureDetector.onTouchEvent(me)) {
-            dismissAllKeyPreviews();
-            mHandler.cancelKeyTimers();
-            return true;
-        }
-
-        final long eventTime = me.getEventTime();
-        final int index = me.getActionIndex();
-        final int id = me.getPointerId(index);
-        final int x = (int)me.getX(index);
-        final int y = (int)me.getY(index);
-
-        // Needs to be called after the gesture detector gets a turn, as it may have displayed the
-        // mini keyboard
-        if (mPopupMiniKeyboardPanel != null) {
-            return mPopupMiniKeyboardPanel.onTouchEvent(me);
-        }
-
-        if (mHandler.isInKeyRepeat()) {
-            final PointerTracker tracker = getPointerTracker(id);
-            // Key repeating timer will be canceled if 2 or more keys are in action, and current
-            // event (UP or DOWN) is non-modifier key.
-            if (pointerCount > 1 && !tracker.isModifier()) {
-                mHandler.cancelKeyRepeatTimer();
-            }
-            // Up event will pass through.
-        }
-
-        // TODO: cleanup this code into a multi-touch to single-touch event converter class?
-        // Translate mutli-touch event to single-touch events on the device that has no distinct
-        // multi-touch panel.
-        if (!mHasDistinctMultitouch) {
-            // Use only main (id=0) pointer tracker.
-            PointerTracker tracker = getPointerTracker(0);
-            if (pointerCount == 1 && oldPointerCount == 2) {
-                // 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) {
-                    tracker.onDownEvent(x, y, eventTime, null);
-                    if (action == MotionEvent.ACTION_UP)
-                        tracker.onUpEvent(x, y, eventTime, null);
-                }
-            } else if (pointerCount == 2 && oldPointerCount == 1) {
-                // Single-touch to multi-touch transition.
-                // Send an up event for the last pointer.
-                final int lastX = tracker.getLastX();
-                final int lastY = tracker.getLastY();
-                mOldKeyIndex = tracker.getKeyIndexOn(lastX, lastY);
-                tracker.onUpEvent(lastX, lastY, eventTime, null);
-            } else if (pointerCount == 1 && oldPointerCount == 1) {
-                tracker.onTouchEvent(action, x, y, eventTime, null);
-            } else {
-                Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
-                        + " (old " + oldPointerCount + ")");
-            }
-            return true;
-        }
-
-        final PointerTrackerQueue queue = mPointerQueue;
-        if (action == MotionEvent.ACTION_MOVE) {
-            for (int i = 0; i < pointerCount; i++) {
-                final PointerTracker tracker = getPointerTracker(me.getPointerId(i));
-                tracker.onMoveEvent((int)me.getX(i), (int)me.getY(i), eventTime, queue);
-            }
-        } else {
-            final PointerTracker tracker = getPointerTracker(id);
-            switch (action) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_POINTER_DOWN:
-                tracker.onDownEvent(x, y, eventTime, queue);
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_POINTER_UP:
-                tracker.onUpEvent(x, y, eventTime, queue);
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                tracker.onCancelEvent(x, y, eventTime, queue);
-                break;
-            }
-        }
-
-        return true;
-    }
-
-    protected void onSwipeDown() {
-        mKeyboardActionListener.onSwipeDown();
-    }
-
     public void closing() {
         mPreviewText.setVisibility(View.GONE);
-        mHandler.cancelAllMessages();
+        cancelAllMessages();
 
-        dismissMiniKeyboard();
         mDirtyRect.union(0, 0, getWidth(), getHeight());
-        mPopupPanelCache.clear();
         requestLayout();
     }
 
@@ -1434,51 +924,4 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         super.onDetachedFromWindow();
         closing();
     }
-
-    private boolean dismissMiniKeyboard() {
-        if (mPopupWindow != null && mPopupWindow.isShowing()) {
-            mPopupWindow.dismiss();
-            mPopupMiniKeyboardPanel = null;
-            invalidateAllKeys();
-            return true;
-        }
-        return false;
-    }
-
-    public boolean handleBack() {
-        return dismissMiniKeyboard();
-    }
-
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent event) {
-        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
-            return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event)
-                    || super.dispatchTouchEvent(event);
-        }
-
-        return super.dispatchTouchEvent(event);
-    }
-
-    @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
-            final PointerTracker tracker = getPointerTracker(0);
-            return AccessibleKeyboardViewProxy.getInstance().dispatchPopulateAccessibilityEvent(
-                    event, tracker) || super.dispatchPopulateAccessibilityEvent(event);
-        }
-
-        return super.dispatchPopulateAccessibilityEvent(event);
-    }
-
-    public boolean onHoverEvent(MotionEvent event) {
-        // Since reflection doesn't support calling superclass methods, this
-        // method checks for the existence of onHoverEvent() in the View class
-        // before returning a value.
-        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
-            final PointerTracker tracker = getPointerTracker(0);
-            return AccessibleKeyboardViewProxy.getInstance().onHoverEvent(event, tracker);
-        }
-
-        return false;
-    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java
new file mode 100644
index 0000000000000000000000000000000000000000..0a7ba05c867a6ea1472b3ca367baa91939c0e0ec
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java
@@ -0,0 +1,670 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.PopupWindow;
+
+import com.android.inputmethod.accessibility.AccessibilityUtils;
+import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
+import com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder;
+import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
+import com.android.inputmethod.keyboard.internal.SwipeTracker;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+
+import java.util.ArrayList;
+import java.util.WeakHashMap;
+
+/**
+ * A view that is responsible for detecting key presses and touch movements.
+ *
+ * @attr ref R.styleable#KeyboardView_keyHysteresisDistance
+ * @attr ref R.styleable#KeyboardView_verticalCorrection
+ * @attr ref R.styleable#KeyboardView_popupLayout
+ */
+public class LatinKeyboardBaseView extends KeyboardView {
+    private static final String TAG = LatinKeyboardBaseView.class.getSimpleName();
+
+    private static final boolean ENABLE_CAPSLOCK_BY_LONGPRESS = true;
+    private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true;
+
+    // Timing constants
+    private final int mKeyRepeatInterval;
+
+    // XML attribute
+    private final float mKeyHysteresisDistance;
+    private final float mVerticalCorrection;
+    private final int mPopupLayout;
+
+    // Mini keyboard
+    private PopupWindow mPopupWindow;
+    private PopupPanel mPopupMiniKeyboardPanel;
+    private final WeakHashMap<Key, PopupPanel> mPopupPanelCache =
+            new WeakHashMap<Key, PopupPanel>();
+
+    /** Listener for {@link KeyboardActionListener}. */
+    private KeyboardActionListener mKeyboardActionListener;
+
+    private final ArrayList<PointerTracker> mPointerTrackers = new ArrayList<PointerTracker>();
+
+    // TODO: Let the PointerTracker class manage this pointer queue
+    private final PointerTrackerQueue mPointerQueue = new PointerTrackerQueue();
+
+    private final boolean mHasDistinctMultitouch;
+    private int mOldPointerCount = 1;
+    private int mOldKeyIndex;
+
+    protected KeyDetector mKeyDetector = new KeyDetector();
+
+    // Swipe gesture detector
+    protected GestureDetector mGestureDetector;
+    private final SwipeTracker mSwipeTracker = new SwipeTracker();
+    private final int mSwipeThreshold;
+    private final boolean mDisambiguateSwipe;
+
+    private final UIHandler mHandler = new UIHandler(this);
+
+    public static class UIHandler extends StaticInnerHandlerWrapper<LatinKeyboardBaseView> {
+        private static final int MSG_REPEAT_KEY = 3;
+        private static final int MSG_LONGPRESS_KEY = 4;
+        private static final int MSG_LONGPRESS_SHIFT_KEY = 5;
+        private static final int MSG_IGNORE_DOUBLE_TAP = 6;
+
+        private boolean mInKeyRepeat;
+
+        public UIHandler(LatinKeyboardBaseView outerInstance) {
+            super(outerInstance);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final LatinKeyboardBaseView keyboardView = getOuterInstance();
+            final PointerTracker tracker = (PointerTracker) msg.obj;
+            switch (msg.what) {
+            case MSG_REPEAT_KEY:
+                tracker.onRepeatKey(msg.arg1);
+                startKeyRepeatTimer(keyboardView.mKeyRepeatInterval, msg.arg1, tracker);
+                break;
+            case MSG_LONGPRESS_KEY:
+                keyboardView.openMiniKeyboardIfRequired(msg.arg1, tracker);
+                break;
+            case MSG_LONGPRESS_SHIFT_KEY:
+                keyboardView.onLongPressShiftKey(tracker);
+                break;
+            }
+        }
+
+        public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {
+            mInKeyRepeat = true;
+            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay);
+        }
+
+        public void cancelKeyRepeatTimer() {
+            mInKeyRepeat = false;
+            removeMessages(MSG_REPEAT_KEY);
+        }
+
+        public boolean isInKeyRepeat() {
+            return mInKeyRepeat;
+        }
+
+        public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {
+            cancelLongPressTimers();
+            sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay);
+        }
+
+        public void startLongPressShiftTimer(long delay, int keyIndex, PointerTracker tracker) {
+            cancelLongPressTimers();
+            if (ENABLE_CAPSLOCK_BY_LONGPRESS) {
+                sendMessageDelayed(
+                        obtainMessage(MSG_LONGPRESS_SHIFT_KEY, keyIndex, 0, tracker), delay);
+            }
+        }
+
+        public void cancelLongPressTimers() {
+            removeMessages(MSG_LONGPRESS_KEY);
+            removeMessages(MSG_LONGPRESS_SHIFT_KEY);
+        }
+
+        public void cancelKeyTimers() {
+            cancelKeyRepeatTimer();
+            cancelLongPressTimers();
+            removeMessages(MSG_IGNORE_DOUBLE_TAP);
+        }
+
+        public void startIgnoringDoubleTap() {
+            sendMessageDelayed(obtainMessage(MSG_IGNORE_DOUBLE_TAP),
+                    ViewConfiguration.getDoubleTapTimeout());
+        }
+
+        public boolean isIgnoringDoubleTap() {
+            return hasMessages(MSG_IGNORE_DOUBLE_TAP);
+        }
+
+        public void cancelAllMessages() {
+            cancelKeyTimers();
+        }
+    }
+
+    public LatinKeyboardBaseView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.keyboardViewStyle);
+    }
+
+    public LatinKeyboardBaseView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
+        mKeyHysteresisDistance = a.getDimensionPixelOffset(
+                R.styleable.KeyboardView_keyHysteresisDistance, 0);
+        mVerticalCorrection = a.getDimensionPixelOffset(
+                R.styleable.KeyboardView_verticalCorrection, 0);
+
+        mPopupLayout = a.getResourceId(R.styleable.KeyboardView_popupLayout, 0);
+        // TODO: Use Theme (android.R.styleable.Theme_backgroundDimAmount)
+        a.recycle();
+
+        final Resources res = getResources();
+
+        mSwipeThreshold = (int) (500 * res.getDisplayMetrics().density);
+        // TODO: Refer to frameworks/base/core/res/res/values/config.xml
+        mDisambiguateSwipe = res.getBoolean(R.bool.config_swipeDisambiguation);
+
+        GestureDetector.SimpleOnGestureListener listener =
+                new GestureDetector.SimpleOnGestureListener() {
+            private boolean mProcessingShiftDoubleTapEvent = false;
+
+            @Override
+            public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX,
+                    float velocityY) {
+                final float absX = Math.abs(velocityX);
+                final float absY = Math.abs(velocityY);
+                float deltaY = me2.getY() - me1.getY();
+                int travelY = getHeight() / 2; // Half the keyboard height
+                mSwipeTracker.computeCurrentVelocity(1000);
+                final float endingVelocityY = mSwipeTracker.getYVelocity();
+                if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
+                    if (mDisambiguateSwipe && endingVelocityY >= velocityY / 4) {
+                        onSwipeDown();
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+            @Override
+            public boolean onDoubleTap(MotionEvent firstDown) {
+                final Keyboard keyboard = getKeyboard();
+                if (ENABLE_CAPSLOCK_BY_DOUBLETAP && keyboard instanceof LatinKeyboard
+                        && ((LatinKeyboard) keyboard).isAlphaKeyboard()) {
+                    final int pointerIndex = firstDown.getActionIndex();
+                    final int id = firstDown.getPointerId(pointerIndex);
+                    final PointerTracker tracker = getPointerTracker(id);
+                    // If the first down event is on shift key.
+                    if (tracker.isOnShiftKey((int)firstDown.getX(), (int)firstDown.getY())) {
+                        mProcessingShiftDoubleTapEvent = true;
+                        return true;
+                    }
+                }
+                mProcessingShiftDoubleTapEvent = false;
+                return false;
+            }
+
+            @Override
+            public boolean onDoubleTapEvent(MotionEvent secondTap) {
+                if (mProcessingShiftDoubleTapEvent
+                        && secondTap.getAction() == MotionEvent.ACTION_DOWN) {
+                    final MotionEvent secondDown = secondTap;
+                    final int pointerIndex = secondDown.getActionIndex();
+                    final int id = secondDown.getPointerId(pointerIndex);
+                    final PointerTracker tracker = getPointerTracker(id);
+                    // If the second down event is also on shift key.
+                    if (tracker.isOnShiftKey((int)secondDown.getX(), (int)secondDown.getY())) {
+                        // 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} .
+                        final boolean ignoringDoubleTap = mHandler.isIgnoringDoubleTap();
+                        if (!ignoringDoubleTap)
+                            onDoubleTapShiftKey(tracker);
+                        return true;
+                    }
+                    // Otherwise these events should not be handled as double tap.
+                    mProcessingShiftDoubleTapEvent = false;
+                }
+                return mProcessingShiftDoubleTapEvent;
+            }
+        };
+
+        final boolean ignoreMultitouch = true;
+        mGestureDetector = new GestureDetector(getContext(), listener, null, ignoreMultitouch);
+        mGestureDetector.setIsLongpressEnabled(false);
+
+        mHasDistinctMultitouch = context.getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
+        mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
+    }
+
+    public void startIgnoringDoubleTap() {
+        if (ENABLE_CAPSLOCK_BY_DOUBLETAP)
+            mHandler.startIgnoringDoubleTap();
+    }
+
+    public void setOnKeyboardActionListener(KeyboardActionListener listener) {
+        mKeyboardActionListener = listener;
+        for (PointerTracker tracker : mPointerTrackers) {
+            tracker.setOnKeyboardActionListener(listener);
+        }
+    }
+
+    /**
+     * Returns the {@link KeyboardActionListener} object.
+     * @return the listener attached to this keyboard
+     */
+    protected KeyboardActionListener getOnKeyboardActionListener() {
+        return mKeyboardActionListener;
+    }
+
+    /**
+     * Attaches a keyboard to this view. The keyboard can be switched at any time and the
+     * view will re-layout itself to accommodate the keyboard.
+     * @see Keyboard
+     * @see #getKeyboard()
+     * @param keyboard the keyboard to display in this view
+     */
+    @Override
+    public void setKeyboard(Keyboard keyboard) {
+        if (getKeyboard() != null) {
+            dismissAllKeyPreviews();
+        }
+        // Remove any pending messages, except dismissing preview
+        mHandler.cancelKeyTimers();
+        super.setKeyboard(keyboard);
+        mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
+                -getPaddingTop() + mVerticalCorrection);
+        for (PointerTracker tracker : mPointerTrackers) {
+            tracker.setKeyboard(keyboard, mKeyHysteresisDistance);
+        }
+        mKeyDetector.setProximityThreshold(keyboard.getMostCommonKeyWidth());
+        mPopupPanelCache.clear();
+    }
+
+    /**
+     * Returns whether the device has distinct multi-touch panel.
+     * @return true if the device has distinct multi-touch panel.
+     */
+    public boolean hasDistinctMultitouch() {
+        return mHasDistinctMultitouch;
+    }
+
+    /**
+     * When enabled, calls to {@link KeyboardActionListener#onCodeInput} will include key
+     * codes for adjacent keys.  When disabled, only the primary key code will be
+     * reported.
+     * @param enabled whether or not the proximity correction is enabled
+     */
+    public void setProximityCorrectionEnabled(boolean enabled) {
+        mKeyDetector.setProximityCorrectionEnabled(enabled);
+    }
+
+    /**
+     * Returns true if proximity correction is enabled.
+     */
+    public boolean isProximityCorrectionEnabled() {
+        return mKeyDetector.isProximityCorrectionEnabled();
+    }
+
+    // TODO: clean up this method.
+    private void dismissAllKeyPreviews() {
+        for (PointerTracker tracker : mPointerTrackers) {
+            tracker.setReleasedKeyGraphics();
+            dismissKeyPreview(tracker);
+        }
+    }
+
+    @Override
+    public void cancelAllMessages() {
+        mHandler.cancelAllMessages();
+        super.cancelAllMessages();
+    }
+
+    private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) {
+        // Check if we have a popup layout specified first.
+        if (mPopupLayout == 0) {
+            return false;
+        }
+
+        final Key parentKey = tracker.getKey(keyIndex);
+        if (parentKey == null)
+            return false;
+        boolean result = onLongPress(parentKey, tracker);
+        if (result) {
+            dismissAllKeyPreviews();
+            tracker.onLongPressed(mPointerQueue);
+        }
+        return result;
+    }
+
+    private void onLongPressShiftKey(PointerTracker tracker) {
+        tracker.onLongPressed(mPointerQueue);
+        mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
+    }
+
+    private void onDoubleTapShiftKey(@SuppressWarnings("unused") PointerTracker tracker) {
+        // When shift key is double tapped, the first tap is correctly processed as usual tap. And
+        // the second tap is treated as this double tap event, so that we need not mark tracker
+        // calling setAlreadyProcessed() nor remove the tracker from mPointerQueueueue.
+        mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
+    }
+
+    // This default implementation returns a popup mini keyboard panel.
+    // A derived class may return a language switcher popup panel, for instance.
+    protected PopupPanel onCreatePopupPanel(Key parentKey) {
+        if (parentKey.mPopupCharacters == null)
+            return null;
+
+        final View container = LayoutInflater.from(getContext()).inflate(mPopupLayout, null);
+        if (container == null)
+            throw new NullPointerException();
+
+        final PopupMiniKeyboardView miniKeyboardView =
+                (PopupMiniKeyboardView)container.findViewById(R.id.mini_keyboard_view);
+        miniKeyboardView.setOnKeyboardActionListener(new KeyboardActionListener() {
+            @Override
+            public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
+                mKeyboardActionListener.onCodeInput(primaryCode, keyCodes, x, y);
+                dismissMiniKeyboard();
+            }
+
+            @Override
+            public void onTextInput(CharSequence text) {
+                mKeyboardActionListener.onTextInput(text);
+                dismissMiniKeyboard();
+            }
+
+            @Override
+            public void onCancelInput() {
+                mKeyboardActionListener.onCancelInput();
+                dismissMiniKeyboard();
+            }
+
+            @Override
+            public void onSwipeDown() {
+                // Nothing to do.
+            }
+            @Override
+            public void onPress(int primaryCode, boolean withSliding) {
+                mKeyboardActionListener.onPress(primaryCode, withSliding);
+            }
+            @Override
+            public void onRelease(int primaryCode, boolean withSliding) {
+                mKeyboardActionListener.onRelease(primaryCode, withSliding);
+            }
+        });
+
+        final Keyboard parentKeyboard = getKeyboard();
+        final Keyboard miniKeyboard = new MiniKeyboardBuilder(
+                this, parentKeyboard.getPopupKeyboardResId(), parentKey, parentKeyboard).build();
+        miniKeyboardView.setKeyboard(miniKeyboard);
+
+        container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
+                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+
+        return miniKeyboardView;
+    }
+
+    @Override
+    protected boolean needsToDimKeyboard() {
+        return mPopupMiniKeyboardPanel != null;
+    }
+
+    /**
+     * Called when a key is long pressed. By default this will open mini keyboard associated
+     * with this key.
+     * @param parentKey the key that was long pressed
+     * @param tracker the pointer tracker which pressed the parent key
+     * @return true if the long press is handled, false otherwise. Subclasses should call the
+     * method on the base class if the subclass doesn't wish to handle the call.
+     */
+    protected boolean onLongPress(Key parentKey, PointerTracker tracker) {
+        PopupPanel popupPanel = mPopupPanelCache.get(parentKey);
+        if (popupPanel == null) {
+            popupPanel = onCreatePopupPanel(parentKey);
+            if (popupPanel == null)
+                return false;
+            mPopupPanelCache.put(parentKey, popupPanel);
+        }
+        if (mPopupWindow == null) {
+            mPopupWindow = new PopupWindow(getContext());
+            mPopupWindow.setBackgroundDrawable(null);
+            mPopupWindow.setAnimationStyle(R.style.PopupMiniKeyboardAnimation);
+            // Allow popup window to be drawn off the screen.
+            mPopupWindow.setClippingEnabled(false);
+        }
+        mPopupMiniKeyboardPanel = popupPanel;
+        popupPanel.showPanel(this, parentKey, tracker, mPopupWindow);
+
+        invalidateAllKeys();
+        return true;
+    }
+
+    private PointerTracker getPointerTracker(final int id) {
+        final ArrayList<PointerTracker> pointers = mPointerTrackers;
+        final KeyboardActionListener listener = mKeyboardActionListener;
+        final Keyboard keyboard = getKeyboard();
+
+        // Create pointer trackers until we can get 'id+1'-th tracker, if needed.
+        for (int i = pointers.size(); i <= id; i++) {
+            final PointerTracker tracker =
+                new PointerTracker(i, this, mHandler, mKeyDetector, this);
+            if (keyboard != null)
+                tracker.setKeyboard(keyboard, mKeyHysteresisDistance);
+            if (listener != null)
+                tracker.setOnKeyboardActionListener(listener);
+            pointers.add(tracker);
+        }
+
+        return pointers.get(id);
+    }
+
+    public boolean isInSlidingKeyInput() {
+        if (mPopupMiniKeyboardPanel != null) {
+            return mPopupMiniKeyboardPanel.isInSlidingKeyInput();
+        } else {
+            return mPointerQueue.isInSlidingKeyInput();
+        }
+    }
+
+    public int getPointerCount() {
+        return mOldPointerCount;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent me) {
+        final int action = me.getActionMasked();
+        final int pointerCount = me.getPointerCount();
+        final int oldPointerCount = mOldPointerCount;
+        mOldPointerCount = pointerCount;
+
+        // TODO: cleanup this code into a multi-touch to single-touch event converter class?
+        // If the device does not have distinct multi-touch support panel, ignore all multi-touch
+        // events except a transition from/to single-touch.
+        if (!mHasDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
+            return true;
+        }
+
+        // Track the last few movements to look for spurious swipes.
+        mSwipeTracker.addMovement(me);
+
+        // Gesture detector must be enabled only when mini-keyboard is not on the screen.
+        if (mPopupMiniKeyboardPanel == null && mGestureDetector != null
+                && mGestureDetector.onTouchEvent(me)) {
+            dismissAllKeyPreviews();
+            mHandler.cancelKeyTimers();
+            return true;
+        }
+
+        final long eventTime = me.getEventTime();
+        final int index = me.getActionIndex();
+        final int id = me.getPointerId(index);
+        final int x = (int)me.getX(index);
+        final int y = (int)me.getY(index);
+
+        // Needs to be called after the gesture detector gets a turn, as it may have displayed the
+        // mini keyboard
+        if (mPopupMiniKeyboardPanel != null) {
+            return mPopupMiniKeyboardPanel.onTouchEvent(me);
+        }
+
+        if (mHandler.isInKeyRepeat()) {
+            final PointerTracker tracker = getPointerTracker(id);
+            // Key repeating timer will be canceled if 2 or more keys are in action, and current
+            // event (UP or DOWN) is non-modifier key.
+            if (pointerCount > 1 && !tracker.isModifier()) {
+                mHandler.cancelKeyRepeatTimer();
+            }
+            // Up event will pass through.
+        }
+
+        // TODO: cleanup this code into a multi-touch to single-touch event converter class?
+        // Translate mutli-touch event to single-touch events on the device that has no distinct
+        // multi-touch panel.
+        if (!mHasDistinctMultitouch) {
+            // Use only main (id=0) pointer tracker.
+            PointerTracker tracker = getPointerTracker(0);
+            if (pointerCount == 1 && oldPointerCount == 2) {
+                // 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) {
+                    tracker.onDownEvent(x, y, eventTime, null);
+                    if (action == MotionEvent.ACTION_UP)
+                        tracker.onUpEvent(x, y, eventTime, null);
+                }
+            } else if (pointerCount == 2 && oldPointerCount == 1) {
+                // Single-touch to multi-touch transition.
+                // Send an up event for the last pointer.
+                final int lastX = tracker.getLastX();
+                final int lastY = tracker.getLastY();
+                mOldKeyIndex = tracker.getKeyIndexOn(lastX, lastY);
+                tracker.onUpEvent(lastX, lastY, eventTime, null);
+            } else if (pointerCount == 1 && oldPointerCount == 1) {
+                tracker.onTouchEvent(action, x, y, eventTime, null);
+            } else {
+                Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
+                        + " (old " + oldPointerCount + ")");
+            }
+            return true;
+        }
+
+        final PointerTrackerQueue queue = mPointerQueue;
+        if (action == MotionEvent.ACTION_MOVE) {
+            for (int i = 0; i < pointerCount; i++) {
+                final PointerTracker tracker = getPointerTracker(me.getPointerId(i));
+                tracker.onMoveEvent((int)me.getX(i), (int)me.getY(i), eventTime, queue);
+            }
+        } else {
+            final PointerTracker tracker = getPointerTracker(id);
+            switch (action) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_POINTER_DOWN:
+                tracker.onDownEvent(x, y, eventTime, queue);
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_POINTER_UP:
+                tracker.onUpEvent(x, y, eventTime, queue);
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                tracker.onCancelEvent(x, y, eventTime, queue);
+                break;
+            }
+        }
+
+        return true;
+    }
+
+    protected void onSwipeDown() {
+        mKeyboardActionListener.onSwipeDown();
+    }
+
+    @Override
+    public void closing() {
+        super.closing();
+        dismissMiniKeyboard();
+        mPopupPanelCache.clear();
+    }
+
+    private boolean dismissMiniKeyboard() {
+        if (mPopupWindow != null && mPopupWindow.isShowing()) {
+            mPopupWindow.dismiss();
+            mPopupMiniKeyboardPanel = null;
+            invalidateAllKeys();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean handleBack() {
+        return dismissMiniKeyboard();
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+            return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event)
+                    || super.dispatchTouchEvent(event);
+        }
+
+        return super.dispatchTouchEvent(event);
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+            final PointerTracker tracker = getPointerTracker(0);
+            return AccessibleKeyboardViewProxy.getInstance().dispatchPopulateAccessibilityEvent(
+                    event, tracker) || super.dispatchPopulateAccessibilityEvent(event);
+        }
+
+        return super.dispatchPopulateAccessibilityEvent(event);
+    }
+
+    public boolean onHoverEvent(MotionEvent event) {
+        // Since reflection doesn't support calling superclass methods, this
+        // method checks for the existence of onHoverEvent() in the View class
+        // before returning a value.
+        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+            final PointerTracker tracker = getPointerTracker(0);
+            return AccessibleKeyboardViewProxy.getInstance().onHoverEvent(event, tracker);
+        }
+
+        return false;
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 75d022b520fbc1c264f885f69f716c124b7fe00a..510bc16b48f997157e5888ccd93a91b6bfad06b7 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -27,7 +27,7 @@ import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.Utils;
 
 // TODO: We should remove this class
-public class LatinKeyboardView extends KeyboardView {
+public class LatinKeyboardView extends LatinKeyboardBaseView {
     private static final String TAG = LatinKeyboardView.class.getSimpleName();
     private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
 
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 6228cc8b2cb73abb2e0d1bf76a78e1ea566471a6..68284cd1b3555bb972cfc6c69c8c321343b6ded2 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -21,7 +21,7 @@ import android.os.SystemClock;
 import android.util.Log;
 import android.view.MotionEvent;
 
-import com.android.inputmethod.keyboard.KeyboardView.UIHandler;
+import com.android.inputmethod.keyboard.LatinKeyboardBaseView.UIHandler;
 import com.android.inputmethod.keyboard.internal.PointerTrackerKeyState;
 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
 import com.android.inputmethod.latin.LatinImeLogger;
@@ -42,8 +42,8 @@ public class PointerTracker {
     public interface UIProxy {
         public void invalidateKey(Key key);
         public void showKeyPreview(int keyIndex, PointerTracker tracker);
+        public void cancelShowKeyPreview(PointerTracker tracker);
         public void dismissKeyPreview(PointerTracker tracker);
-        public boolean hasDistinctMultitouch();
     }
 
     public final int mPointerId;
@@ -53,7 +53,7 @@ public class PointerTracker {
     private final int mLongPressKeyTimeout;
     private final int mLongPressShiftKeyTimeout;
 
-    private final KeyboardView mKeyboardView;
+    private final LatinKeyboardBaseView mKeyboardView;
     private final UIProxy mProxy;
     private final UIHandler mHandler;
     private final KeyDetector mKeyDetector;
@@ -112,7 +112,7 @@ public class PointerTracker {
         public void onSwipeDown() {}
     };
 
-    public PointerTracker(int id, KeyboardView keyboardView, UIHandler handler,
+    public PointerTracker(int id, LatinKeyboardBaseView keyboardView, UIHandler handler,
             KeyDetector keyDetector, UIProxy proxy) {
         if (proxy == null || handler == null || keyDetector == null)
             throw new NullPointerException();
@@ -123,7 +123,7 @@ public class PointerTracker {
         mKeyDetector = keyDetector;
         mKeyboardSwitcher = KeyboardSwitcher.getInstance();
         mKeyState = new PointerTrackerKeyState(keyDetector);
-        mHasDistinctMultitouch = proxy.hasDistinctMultitouch();
+        mHasDistinctMultitouch = keyboardView.hasDistinctMultitouch();
         final Resources res = mKeyboardView.getResources();
         mConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled);
         mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start);
@@ -504,7 +504,7 @@ public class PointerTracker {
     private void onUpEventInternal(int x, int y, long eventTime,
             boolean updateReleasedKeyGraphics) {
         mHandler.cancelKeyTimers();
-        mHandler.cancelShowKeyPreview(this);
+        mProxy.cancelShowKeyPreview(this);
         mIsInSlidingKeyInput = false;
         final PointerTrackerKeyState keyState = mKeyState;
         final int keyX, keyY;
@@ -564,7 +564,7 @@ public class PointerTracker {
 
     private void onCancelEventInternal() {
         mHandler.cancelKeyTimers();
-        mHandler.cancelShowKeyPreview(this);
+        mProxy.cancelShowKeyPreview(this);
         dismissKeyPreview();
         setReleasedKeyGraphics(mKeyState.getKeyIndex());
         mIsInSlidingKeyInput = false;
diff --git a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
index 3b8c364877a5b597ae980f322b682a488339916e..959427aad2e2eef5c58da1547e7f38ed98327299 100644
--- a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
@@ -31,7 +31,7 @@ import com.android.inputmethod.latin.R;
  * A view that renders a virtual {@link MiniKeyboard}. It handles rendering of keys and detecting
  * key presses and touch movements.
  */
-public class PopupMiniKeyboardView extends KeyboardView implements PopupPanel {
+public class PopupMiniKeyboardView extends LatinKeyboardBaseView implements PopupPanel {
     private final int[] mCoordinates = new int[2];
     private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java
index cc89579bb9b977b099eccbe7e48024d8bbe8eb33..1885ea14ee47c11a7195178998c4e0366758cdec 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java
@@ -23,7 +23,7 @@ import android.graphics.Rect;
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.keyboard.LatinKeyboardBaseView;
 import com.android.inputmethod.keyboard.MiniKeyboard;
 import com.android.inputmethod.latin.R;
 
@@ -199,7 +199,7 @@ public class MiniKeyboardBuilder {
         }
     }
 
-    public MiniKeyboardBuilder(KeyboardView view, int layoutTemplateResId, Key parentKey,
+    public MiniKeyboardBuilder(LatinKeyboardBaseView view, int layoutTemplateResId, Key parentKey,
             Keyboard parentKeyboard) {
         final Context context = view.getContext();
         mRes = context.getResources();
@@ -223,7 +223,7 @@ public class MiniKeyboardBuilder {
         keyboard.setDefaultCoordX(params.getDefaultKeyCoordX() + params.mKeyWidth / 2);
     }
 
-    private static int getMaxKeyWidth(KeyboardView view, CharSequence[] popupCharacters,
+    private static int getMaxKeyWidth(LatinKeyboardBaseView view, CharSequence[] popupCharacters,
             int minKeyWidth) {
         Paint paint = null;
         Rect bounds = null;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 929fdac313acfec3cef0301616d5000b8aaa85ed..4a813541d4c7556627a4ca57c89dae368943b439 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -66,7 +66,7 @@ import com.android.inputmethod.deprecated.recorrection.Recorrection;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.keyboard.LatinKeyboardBaseView;
 import com.android.inputmethod.keyboard.LatinKeyboard;
 import com.android.inputmethod.keyboard.LatinKeyboardView;
 
@@ -655,7 +655,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
     @Override
     public void onWindowHidden() {
         super.onWindowHidden();
-        KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
+        LatinKeyboardBaseView inputView = mKeyboardSwitcher.getKeyboardView();
         if (inputView != null) inputView.closing();
     }
 
@@ -668,7 +668,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
 
         mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging);
 
-        KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
+        LatinKeyboardBaseView inputView = mKeyboardSwitcher.getKeyboardView();
         if (inputView != null) inputView.closing();
         if (mAutoDictionary != null) mAutoDictionary.flushPendingWrites();
         if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites();
@@ -677,8 +677,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
     @Override
     public void onFinishInputView(boolean finishingInput) {
         super.onFinishInputView(finishingInput);
-        KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
-        if (inputView != null) inputView.cancelAllMessage();
+        LatinKeyboardBaseView inputView = mKeyboardSwitcher.getKeyboardView();
+        if (inputView != null) inputView.cancelAllMessages();
         // Remove pending messages related to update suggestions
         mHandler.cancelUpdateSuggestions();
         mHandler.cancelUpdateOldSuggestions();
@@ -866,7 +866,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
     @Override
     public void onComputeInsets(InputMethodService.Insets outInsets) {
         super.onComputeInsets(outInsets);
-        final KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
+        final LatinKeyboardBaseView inputView = mKeyboardSwitcher.getKeyboardView();
         if (inputView == null || mCandidateViewContainer == null)
             return;
         final int containerHeight = mCandidateViewContainer.getHeight();