Merge "Combine the skipped and transposed correction"
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index f55e9bf..5cd995b 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -35,7 +35,7 @@
     <!-- Label for "switch to more symbol" modifier key.  Must be short to fit on key! -->
     <string name="label_to_more_symbol_key">= \\ &lt;</string>
     <!-- Label for "switch to more symbol" modifier key on tablets.  Must be short to fit on key! -->
-    <string name="label_to_more_symbol_for_tablet_key">\\ ^ [ {</string>
+    <string name="label_to_more_symbol_for_tablet_key">~ \\ {</string>
 
     <!-- Label for "Tab" key.  Must be short to fit on key! -->
     <string name="label_tab_key">Tab</string>
diff --git a/java/res/values/sudden-jumping-touch-event-device-list.xml b/java/res/values/sudden-jumping-touch-event-device-list.xml
new file mode 100644
index 0000000..275a262
--- /dev/null
+++ b/java/res/values/sudden-jumping-touch-event-device-list.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+    <string-array name="sudden_jumping_touch_event_device_list">
+        <!-- Nexus One -->
+        <item>passion</item>
+        <!-- Droid -->
+        <item>sholes</item>
+    </string-array>
+</resources>
diff --git a/java/res/xml-sw600dp/kbd_key_styles.xml b/java/res/xml-sw600dp/kbd_key_styles.xml
index 15a3d1d..b93d300 100644
--- a/java/res/xml-sw600dp/kbd_key_styles.xml
+++ b/java/res/xml-sw600dp/kbd_key_styles.xml
@@ -118,19 +118,4 @@
         latin:keyLabelOption="fontNormal|hasPopupHint"
         latin:keyOutputText="@string/keylabel_for_popular_domain"
         latin:popupCharacters="@string/alternates_for_popular_domain" />
-    <switch>
-        <case
-            latin:passwordInput="true"
-        >
-            <key-style
-                latin:styleName="nonPasswordSymbolKeyStyle"
-                latin:enabled="false" />
-        </case>
-        <!-- latin:passwordInput="false" -->
-        <default>
-            <key-style
-                latin:styleName="nonPasswordSymbolKeyStyle"
-                latin:enabled="true" />
-        </default>
-    </switch>
 </merge>
diff --git a/java/res/xml-sw600dp/kbd_rows_symbols_shift.xml b/java/res/xml-sw600dp/kbd_rows_symbols_shift.xml
index 2909acb..c5143d9 100644
--- a/java/res/xml-sw600dp/kbd_rows_symbols_shift.xml
+++ b/java/res/xml-sw600dp/kbd_rows_symbols_shift.xml
@@ -33,28 +33,21 @@
         <Key
             latin:keyLabel="|" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="•"
             latin:popupCharacters="♪,♥,♠,♦,♣" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="√" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="π"
             latin:popupCharacters="Π" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="÷" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="×" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="§"
             latin:popupCharacters="¶" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="Δ" />
         <Key
             latin:keyStyle="deleteKeyStyle"
@@ -65,27 +58,21 @@
         latin:keyWidth="9.0%p"
     >
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="£"
             latin:keyXPos="4.5%p" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="¢" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="€" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="¥" />
         <Key
             latin:keyLabel="^"
             latin:popupCharacters="↑,↓,←,→" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="°"
             latin:popupCharacters="′,″" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="±"
             latin:popupCharacters="∞" />
         <Key
@@ -106,26 +93,20 @@
         <Key
             latin:keyLabel="\\" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="©" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="®" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="™" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="℅" />
         <Key
             latin:keyLabel="[" />
         <Key
             latin:keyLabel="]" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="¡" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="¿" />
     </Row>
     <Row
diff --git a/java/res/xml-sw768dp/kbd_key_styles.xml b/java/res/xml-sw768dp/kbd_key_styles.xml
index 1711c42..7c74bb5 100644
--- a/java/res/xml-sw768dp/kbd_key_styles.xml
+++ b/java/res/xml-sw768dp/kbd_key_styles.xml
@@ -104,19 +104,4 @@
         latin:keyLabelOption="fontNormal|hasPopupHint"
         latin:keyOutputText="@string/keylabel_for_popular_domain"
         latin:popupCharacters="@string/alternates_for_popular_domain" />
-    <switch>
-        <case
-            latin:passwordInput="true"
-        >
-            <key-style
-                latin:styleName="nonPasswordSymbolKeyStyle"
-                latin:enabled="false" />
-        </case>
-        <!-- latin:passwordInput="false" -->
-        <default>
-            <key-style
-                latin:styleName="nonPasswordSymbolKeyStyle"
-                latin:enabled="true" />
-        </default>
-    </switch>
 </merge>
diff --git a/java/res/xml-sw768dp/kbd_rows_symbols_shift.xml b/java/res/xml-sw768dp/kbd_rows_symbols_shift.xml
index 1f43a0f..82bc4b2 100644
--- a/java/res/xml-sw768dp/kbd_rows_symbols_shift.xml
+++ b/java/res/xml-sw768dp/kbd_rows_symbols_shift.xml
@@ -37,28 +37,21 @@
         <Key
             latin:keyLabel="|" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="•"
             latin:popupCharacters="♪,♥,♠,♦,♣" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="√" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="π"
             latin:popupCharacters="Π" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="÷" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="×" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="§"
             latin:popupCharacters="¶" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="Δ" />
         <Key
             latin:keyStyle="deleteKeyStyle"
@@ -73,26 +66,20 @@
             latin:keyLabelOption="alignLeft"
             latin:keyWidth="11.172%p" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="£" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="¢" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="€" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="¥" />
         <Key
             latin:keyLabel="^"
             latin:popupCharacters="↑,↓,←,→" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="°"
             latin:popupCharacters="′,″" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="±"
             latin:popupCharacters="∞" />
         <Key
@@ -113,26 +100,20 @@
         <Key
             latin:keyLabel="\\" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="©" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="®" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="™" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="℅" />
         <Key
             latin:keyLabel="[" />
         <Key
             latin:keyLabel="]" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="¡" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="¿" />
         <Key
             latin:keyStyle="backFromMoreSymbolKeyStyle"
diff --git a/java/res/xml/kbd_key_styles.xml b/java/res/xml/kbd_key_styles.xml
index 5612251..4846898 100644
--- a/java/res/xml/kbd_key_styles.xml
+++ b/java/res/xml/kbd_key_styles.xml
@@ -215,29 +215,6 @@
         latin:code="@integer/key_shift"
         latin:keyLabel="@string/label_to_symbol_key"
         latin:parentStyle="functionalKeyStyle" />
-    <switch>
-        <case
-            latin:passwordInput="true"
-        >
-            <key-style
-                latin:styleName="nonPasswordSymbolKeyStyle"
-                latin:enabled="false" />
-            <key-style
-                latin:styleName="nonPasswordFunctionalKeyStyle"
-                latin:enabled="false"
-                latin:parentStyle="functionalKeyStyle" />
-        </case>
-        <!-- latin:passwordInput="false" -->
-        <default>
-            <key-style
-                latin:styleName="nonPasswordSymbolKeyStyle"
-                latin:enabled="true" />
-            <key-style
-                latin:styleName="nonPasswordFunctionalKeyStyle"
-                latin:enabled="true"
-                latin:parentStyle="functionalKeyStyle" />
-        </default>
-    </switch>
     <key-style
         latin:styleName="punctuationKeyStyle"
         latin:keyLabel="."
diff --git a/java/res/xml/kbd_rows_symbols_shift.xml b/java/res/xml/kbd_rows_symbols_shift.xml
index d523415..ff272f1 100644
--- a/java/res/xml/kbd_rows_symbols_shift.xml
+++ b/java/res/xml/kbd_rows_symbols_shift.xml
@@ -33,21 +33,16 @@
         <Key
             latin:keyLabel="|" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="•"
             latin:popupCharacters="♪,♥,♠,♦,♣" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="√" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="π"
             latin:popupCharacters="Π" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="÷" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="×" />
         <Key
             latin:keyLabel="{" />
@@ -61,16 +56,12 @@
         <Key
             latin:keyStyle="nonSpecialBackgroundTabKeyStyle" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="£" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="¢" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="€" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="°"
             latin:popupCharacters="′,″" />
         <Key
@@ -95,16 +86,12 @@
             latin:keyWidth="15%p"
             latin:visualInsetsRight="1%p" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="™" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="®" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="©" />
         <Key
-            latin:keyStyle="nonPasswordSymbolKeyStyle"
             latin:keyLabel="¶"
             latin:popupCharacters="§" />
         <Key
diff --git a/java/res/xml/kbd_symbols_shift_row4.xml b/java/res/xml/kbd_symbols_shift_row4.xml
index 9cb453f..bcab19b 100644
--- a/java/res/xml/kbd_symbols_shift_row4.xml
+++ b/java/res/xml/kbd_symbols_shift_row4.xml
@@ -35,13 +35,13 @@
                 <Key
                     latin:keyLabel="„"
                     latin:popupCharacters="“,”,„,‟,«,»,‘,’,‚,‛"
-                    latin:keyStyle="nonPasswordFunctionalKeyStyle" />
+                    latin:keyStyle="functionalKeyStyle" />
                 <Key
                     latin:keyStyle="spaceKeyStyle"
                     latin:keyWidth="50%p" />
                 <Key
                     latin:keyLabel="…"
-                    latin:keyStyle="nonPasswordFunctionalKeyStyle" />
+                    latin:keyStyle="functionalKeyStyle" />
                 <Key
                     latin:keyStyle="returnKeyStyle"
                     latin:keyWidth="fillRight" />
@@ -57,14 +57,14 @@
                     latin:keyLabel="„"
                     latin:popupCharacters="“,”,„,‟,«,»,‘,’,‚,‛"
                     latin:keyWidth="9.2%p"
-                    latin:keyStyle="nonPasswordFunctionalKeyStyle" />
+                    latin:keyStyle="functionalKeyStyle" />
                 <Key
                     latin:keyStyle="spaceKeyStyle"
                     latin:keyWidth="35.83%p" />
                 <Key
                     latin:keyLabel="…"
                     latin:keyWidth="9.2%p"
-                    latin:keyStyle="nonPasswordFunctionalKeyStyle" />
+                    latin:keyStyle="functionalKeyStyle" />
                 <Key
                     latin:keyStyle="returnKeyStyle"
                     latin:keyWidth="fillRight" />
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index 3dca9aa..8185619 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -35,7 +35,7 @@
 import com.android.inputmethod.compat.MotionEventCompatUtils;
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.KeyDetector;
-import com.android.inputmethod.keyboard.LatinKeyboardBaseView;
+import com.android.inputmethod.keyboard.LatinKeyboardView;
 import com.android.inputmethod.keyboard.PointerTracker;
 
 public class AccessibleKeyboardViewProxy {
@@ -47,7 +47,7 @@
 
     private InputMethodService mInputMethod;
     private FlickGestureDetector mGestureDetector;
-    private LatinKeyboardBaseView mView;
+    private LatinKeyboardView mView;
     private AccessibleKeyboardActionListener mListener;
     private AudioManagerCompatWrapper mAudioManager;
 
@@ -65,7 +65,7 @@
         return sInstance;
     }
 
-    public static void setView(LatinKeyboardBaseView view) {
+    public static void setView(LatinKeyboardView view) {
         sInstance.mView = view;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 397b7b1..f56b523 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -295,12 +295,8 @@
             mY = y;
             mWidth = keyWidth - mHorizontalGap;
 
-            CharSequence[] popupCharacters = style.getTextArray(
+            final CharSequence[] popupCharacters = style.getTextArray(
                     keyAttr, R.styleable.Keyboard_Key_popupCharacters);
-            if (params.mId.mPasswordInput) {
-                popupCharacters = PopupCharactersParser.filterOut(
-                        res, popupCharacters, PopupCharactersParser.NON_ASCII_FILTER);
-            }
             // In Arabic symbol layouts, we'd like to keep digits in popup characters regardless of
             // config_digit_popup_characters_enabled.
             if (params.mId.isAlphabetKeyboard() && !res.getBoolean(
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 0a3acb4..3298c41 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -91,6 +91,10 @@
         mProximityThresholdSquare = threshold * threshold;
     }
 
+    public boolean alwaysAllowsSlidingInput() {
+        return false;
+    }
+
     /**
      * Computes maximum size of the array that can contain all nearby key indices returned by
      * {@link #getKeyIndexAndNearbyCodes}.
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
index 3b3e1f8..1b6f57b 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
@@ -141,7 +141,7 @@
         }
     }
 
-    public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboardBaseView view) {
+    public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboardView view) {
         mSpacebarTextFadeFactor = fadeFactor;
         updateSpacebarForLocale(false);
         if (view != null)
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java
deleted file mode 100644
index 4a7b2bd..0000000
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java
+++ /dev/null
@@ -1,681 +0,0 @@
-/*
- * 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.graphics.Canvas;
-import android.os.Message;
-import android.os.SystemClock;
-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.deprecated.VoiceProxy;
-import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
-import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
-import com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder;
-import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
-import com.android.inputmethod.latin.Utils;
-
-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 implements PointerTracker.KeyEventHandler {
-    private static final String TAG = LatinKeyboardBaseView.class.getSimpleName();
-
-    private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true;
-
-    // Timing constants
-    private final int mKeyRepeatInterval;
-
-    // XML attribute
-    private final float mVerticalCorrection;
-    private final int mPopupLayout;
-
-    // Mini keyboard
-    private PopupWindow mPopupWindow;
-    private PopupPanel mPopupPanel;
-    private int mPopupPanelPointerTrackerId;
-    private final WeakHashMap<Key, PopupPanel> mPopupPanelCache =
-            new WeakHashMap<Key, PopupPanel>();
-
-    /** Listener for {@link KeyboardActionListener}. */
-    private KeyboardActionListener mKeyboardActionListener;
-
-    private final boolean mHasDistinctMultitouch;
-    private int mOldPointerCount = 1;
-    private int mOldKeyIndex;
-
-    protected KeyDetector mKeyDetector;
-
-    // To detect double tap.
-    protected GestureDetector mGestureDetector;
-
-    private final KeyTimerHandler mKeyTimerHandler = new KeyTimerHandler(this);
-
-    private static class KeyTimerHandler extends StaticInnerHandlerWrapper<LatinKeyboardBaseView>
-            implements TimerProxy {
-        private static final int MSG_REPEAT_KEY = 1;
-        private static final int MSG_LONGPRESS_KEY = 2;
-        private static final int MSG_IGNORE_DOUBLE_TAP = 3;
-
-        private boolean mInKeyRepeat;
-
-        public KeyTimerHandler(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;
-            }
-        }
-
-        @Override
-        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;
-        }
-
-        @Override
-        public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {
-            cancelLongPressTimer();
-            sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay);
-        }
-
-        @Override
-        public void cancelLongPressTimer() {
-            removeMessages(MSG_LONGPRESS_KEY);
-        }
-
-        @Override
-        public void cancelKeyTimers() {
-            cancelKeyRepeatTimer();
-            cancelLongPressTimer();
-            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();
-        }
-    }
-
-    private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
-        private boolean mProcessingShiftDoubleTapEvent = 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 = mKeyTimerHandler.isIgnoringDoubleTap();
-                    if (!ignoringDoubleTap)
-                        onDoubleTapShiftKey(tracker);
-                    return true;
-                }
-                // Otherwise these events should not be handled as double tap.
-                mProcessingShiftDoubleTapEvent = false;
-            }
-            return mProcessingShiftDoubleTapEvent;
-        }
-    }
-
-    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);
-        mVerticalCorrection = a.getDimensionPixelOffset(
-                R.styleable.KeyboardView_verticalCorrection, 0);
-        mPopupLayout = a.getResourceId(R.styleable.KeyboardView_popupLayout, 0);
-        a.recycle();
-
-        final Resources res = getResources();
-        final float keyHysteresisDistance = res.getDimension(R.dimen.key_hysteresis_distance);
-        mKeyDetector = new KeyDetector(keyHysteresisDistance);
-
-        final boolean ignoreMultitouch = true;
-        mGestureDetector = new GestureDetector(
-                getContext(), new DoubleTapListener(), null, ignoreMultitouch);
-        mGestureDetector.setIsLongpressEnabled(false);
-
-        mHasDistinctMultitouch = context.getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
-        mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
-
-        PointerTracker.init(mHasDistinctMultitouch, getContext());
-    }
-
-    public void startIgnoringDoubleTap() {
-        if (ENABLE_CAPSLOCK_BY_DOUBLETAP)
-            mKeyTimerHandler.startIgnoringDoubleTap();
-    }
-
-    public void setKeyboardActionListener(KeyboardActionListener listener) {
-        mKeyboardActionListener = listener;
-        PointerTracker.setKeyboardActionListener(listener);
-    }
-
-    /**
-     * Returns the {@link KeyboardActionListener} object.
-     * @return the listener attached to this keyboard
-     */
-    @Override
-    public KeyboardActionListener getKeyboardActionListener() {
-        return mKeyboardActionListener;
-    }
-
-    @Override
-    public KeyDetector getKeyDetector() {
-        return mKeyDetector;
-    }
-
-    @Override
-    public DrawingProxy getDrawingProxy() {
-        return this;
-    }
-
-    @Override
-    public TimerProxy getTimerProxy() {
-        return mKeyTimerHandler;
-    }
-
-    @Override
-    public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
-        final Keyboard keyboard = getKeyboard();
-        if (keyboard instanceof LatinKeyboard) {
-            final LatinKeyboard latinKeyboard = (LatinKeyboard)keyboard;
-            if (latinKeyboard.isPhoneKeyboard() || latinKeyboard.isNumberKeyboard()) {
-                // Phone and number keyboard never shows popup preview.
-                super.setKeyPreviewPopupEnabled(false, delay);
-                return;
-            }
-        }
-        super.setKeyPreviewPopupEnabled(previewEnabled, delay);
-    }
-
-    /**
-     * 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) {
-        // Remove any pending messages, except dismissing preview
-        mKeyTimerHandler.cancelKeyTimers();
-        super.setKeyboard(keyboard);
-        mKeyDetector.setKeyboard(
-                keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
-        mKeyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth);
-        PointerTracker.setKeyDetector(mKeyDetector);
-        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();
-    }
-
-    @Override
-    public void cancelAllMessages() {
-        mKeyTimerHandler.cancelAllMessages();
-        super.cancelAllMessages();
-    }
-
-    private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) {
-        // Check if we have a popup layout specified first.
-        if (mPopupLayout == 0) {
-            return false;
-        }
-
-        // Check if we are already displaying popup panel.
-        if (mPopupPanel != null)
-            return false;
-        final Key parentKey = tracker.getKey(keyIndex);
-        if (parentKey == null)
-            return false;
-        return onLongPress(parentKey, tracker);
-    }
-
-    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 mPointerQueue.
-        mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
-    }
-
-    // This default implementation returns a popup mini keyboard panel.
-    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);
-        final Keyboard parentKeyboard = getKeyboard();
-        final Keyboard miniKeyboard = new MiniKeyboardBuilder(
-                this, parentKeyboard.mPopupKeyboardResId, 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 mPopupPanel != null;
-    }
-
-    public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboard oldKeyboard) {
-        final Keyboard keyboard = getKeyboard();
-        // We should not set text fade factor to the keyboard which does not display the language on
-        // its spacebar.
-        if (keyboard instanceof LatinKeyboard && keyboard == oldKeyboard) {
-            ((LatinKeyboard)keyboard).setSpacebarTextFadeFactor(fadeFactor, this);
-        }
-    }
-
-    /**
-     * 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) {
-        final int primaryCode = parentKey.mCode;
-        final Keyboard keyboard = getKeyboard();
-        if (keyboard instanceof LatinKeyboard) {
-            final LatinKeyboard latinKeyboard = (LatinKeyboard) keyboard;
-            if (primaryCode == Keyboard.CODE_DIGIT0 && latinKeyboard.isPhoneKeyboard()) {
-                tracker.onLongPressed();
-                // Long pressing on 0 in phone number keypad gives you a '+'.
-                return invokeOnKey(Keyboard.CODE_PLUS);
-            }
-            if (primaryCode == Keyboard.CODE_SHIFT && latinKeyboard.isAlphaKeyboard()) {
-                tracker.onLongPressed();
-                return invokeOnKey(Keyboard.CODE_CAPSLOCK);
-            }
-        }
-        if (primaryCode == Keyboard.CODE_SETTINGS || primaryCode == Keyboard.CODE_SPACE) {
-            // Both long pressing settings key and space key invoke IME switcher dialog.
-            if (getKeyboardActionListener().onCustomRequest(
-                    LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) {
-                tracker.onLongPressed();
-                return true;
-            } else {
-                return openPopupPanel(parentKey, tracker);
-            }
-        } else {
-            return openPopupPanel(parentKey, tracker);
-        }
-    }
-
-    private boolean invokeOnKey(int primaryCode) {
-        getKeyboardActionListener().onCodeInput(primaryCode, null,
-                KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
-                KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
-        return true;
-    }
-
-    private boolean openPopupPanel(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);
-        }
-        mPopupPanel = popupPanel;
-        mPopupPanelPointerTrackerId = tracker.mPointerId;
-
-        popupPanel.showPopupPanel(this, parentKey, tracker, mPopupWindow);
-        final int translatedX = popupPanel.translateX(tracker.getLastX());
-        final int translatedY = popupPanel.translateY(tracker.getLastY());
-        tracker.onShowPopupPanel(translatedX, translatedY, SystemClock.uptimeMillis(), popupPanel);
-
-        invalidateAllKeys();
-        return true;
-    }
-
-    private PointerTracker getPointerTracker(final int id) {
-        return PointerTracker.getPointerTracker(id, this);
-    }
-
-    public boolean isInSlidingKeyInput() {
-        if (mPopupPanel != null) {
-            return true;
-        } else {
-            return PointerTracker.isAnyInSlidingKeyInput();
-        }
-    }
-
-    public int getPointerCount() {
-        return mOldPointerCount;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent me) {
-        final boolean nonDistinctMultitouch = !mHasDistinctMultitouch;
-        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 (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
-            return true;
-        }
-
-        // Gesture detector must be enabled only when mini-keyboard is not on the screen.
-        if (mPopupPanel == null && mGestureDetector != null
-                && mGestureDetector.onTouchEvent(me)) {
-            PointerTracker.dismissAllKeyPreviews();
-            mKeyTimerHandler.cancelKeyTimers();
-            return true;
-        }
-
-        final long eventTime = me.getEventTime();
-        final int index = me.getActionIndex();
-        final int id = me.getPointerId(index);
-        final int x, y;
-        if (mPopupPanel != null && id == mPopupPanelPointerTrackerId) {
-            x = mPopupPanel.translateX((int)me.getX(index));
-            y = mPopupPanel.translateY((int)me.getY(index));
-        } else {
-            x = (int)me.getX(index);
-            y = (int)me.getY(index);
-        }
-
-        if (mKeyTimerHandler.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()) {
-                mKeyTimerHandler.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 (nonDistinctMultitouch) {
-            // 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, this);
-                    if (action == MotionEvent.ACTION_UP)
-                        tracker.onUpEvent(x, y, eventTime);
-                }
-            } 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);
-            } else if (pointerCount == 1 && oldPointerCount == 1) {
-                processMotionEvent(tracker, action, x, y, eventTime, this);
-            } else {
-                Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
-                        + " (old " + oldPointerCount + ")");
-            }
-            return true;
-        }
-
-        if (action == MotionEvent.ACTION_MOVE) {
-            for (int i = 0; i < pointerCount; i++) {
-                final PointerTracker tracker = getPointerTracker(me.getPointerId(i));
-                final int px, py;
-                if (mPopupPanel != null && tracker.mPointerId == mPopupPanelPointerTrackerId) {
-                    px = mPopupPanel.translateX((int)me.getX(i));
-                    py = mPopupPanel.translateY((int)me.getY(i));
-                } else {
-                    px = (int)me.getX(i);
-                    py = (int)me.getY(i);
-                }
-                tracker.onMoveEvent(px, py, eventTime);
-            }
-        } else {
-            processMotionEvent(getPointerTracker(id), action, x, y, eventTime, this);
-        }
-
-        return true;
-    }
-
-    private static void processMotionEvent(PointerTracker tracker, int action, int x, int y,
-            long eventTime, PointerTracker.KeyEventHandler handler) {
-        switch (action) {
-        case MotionEvent.ACTION_DOWN:
-        case MotionEvent.ACTION_POINTER_DOWN:
-            tracker.onDownEvent(x, y, eventTime, handler);
-            break;
-        case MotionEvent.ACTION_UP:
-        case MotionEvent.ACTION_POINTER_UP:
-            tracker.onUpEvent(x, y, eventTime);
-            break;
-        case MotionEvent.ACTION_MOVE:
-            tracker.onMoveEvent(x, y, eventTime);
-            break;
-        case MotionEvent.ACTION_CANCEL:
-            tracker.onCancelEvent(x, y, eventTime);
-            break;
-        }
-    }
-
-    @Override
-    public void closing() {
-        super.closing();
-        dismissPopupPanel();
-        mPopupPanelCache.clear();
-    }
-
-    @Override
-    public boolean dismissPopupPanel() {
-        if (mPopupWindow != null && mPopupWindow.isShowing()) {
-            mPopupWindow.dismiss();
-            mPopupPanel = null;
-            mPopupPanelPointerTrackerId = -1;
-            invalidateAllKeys();
-            return true;
-        }
-        return false;
-    }
-
-    public boolean handleBack() {
-        return dismissPopupPanel();
-    }
-
-    @Override
-    public void draw(Canvas c) {
-        Utils.GCUtils.getInstance().reset();
-        boolean tryGC = true;
-        for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
-            try {
-                super.draw(c);
-                tryGC = false;
-            } catch (OutOfMemoryError e) {
-                tryGC = Utils.GCUtils.getInstance().tryGCOrWait("LatinKeyboardView", e);
-            }
-        }
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        // Token is available from here.
-        VoiceProxy.getInstance().onAttachedToWindow();
-    }
-
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent event) {
-        // Drop non-hover touch events when touch exploration is enabled.
-        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
-            return false;
-        }
-
-        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);
-    }
-
-    /**
-     * Receives hover events from the input framework. This method overrides
-     * View.dispatchHoverEvent(MotionEvent) on SDK version ICS or higher. On
-     * lower SDK versions, this method is never called.
-     *
-     * @param event The motion event to be dispatched.
-     * @return {@code true} if the event was handled by the view, {@code false}
-     *         otherwise
-     */
-    public boolean dispatchHoverEvent(MotionEvent event) {
-        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
-            final PointerTracker tracker = getPointerTracker(0);
-            return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker);
-        }
-
-        // Reflection doesn't support calling superclass methods.
-        return false;
-    }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 42ce7c4..be04b5a 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -1,152 +1,691 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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
+ * 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
+ *      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.
+ * 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.graphics.Canvas;
+import android.os.Message;
+import android.os.SystemClock;
 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.latin.LatinImeLogger;
+import com.android.inputmethod.accessibility.AccessibilityUtils;
+import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
+import com.android.inputmethod.deprecated.VoiceProxy;
+import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
+import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
 import com.android.inputmethod.latin.Utils;
 
-// TODO: We should remove this class
-public class LatinKeyboardView extends LatinKeyboardBaseView {
-    private static final String TAG = LatinKeyboardView.class.getSimpleName();
-    private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
+import java.util.WeakHashMap;
 
-    /** Whether we've started dropping move events because we found a big jump */
-    private boolean mDroppingEvents;
-    /**
-     * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has
-     * occured
-     */
-    private boolean mDisableDisambiguation;
-    /** The distance threshold at which we start treating the touch session as a multi-touch */
-    private int mJumpThresholdSquare = Integer.MAX_VALUE;
-    private int mLastX;
-    private int mLastY;
+/**
+ * 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 LatinKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
+        SuddenJumpingTouchEventHandler.ProcessMotionEvent {
+    private static final String TAG = LatinKeyboardView.class.getSimpleName();
+
+    private static final boolean ENABLE_CAPSLOCK_BY_DOUBLETAP = true;
+
+    private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
+
+    // Timing constants
+    private final int mKeyRepeatInterval;
+
+    // XML attribute
+    private final float mVerticalCorrection;
+    private final int mPopupLayout;
+
+    // Mini keyboard
+    private PopupWindow mPopupWindow;
+    private PopupPanel mPopupPanel;
+    private int mPopupPanelPointerTrackerId;
+    private final WeakHashMap<Key, PopupPanel> mPopupPanelCache =
+            new WeakHashMap<Key, PopupPanel>();
+
+    /** Listener for {@link KeyboardActionListener}. */
+    private KeyboardActionListener mKeyboardActionListener;
+
+    private final boolean mHasDistinctMultitouch;
+    private int mOldPointerCount = 1;
+    private int mOldKeyIndex;
+
+    protected KeyDetector mKeyDetector;
+
+    // To detect double tap.
+    protected GestureDetector mGestureDetector;
+
+    private final KeyTimerHandler mKeyTimerHandler = new KeyTimerHandler(this);
+
+    private static class KeyTimerHandler extends StaticInnerHandlerWrapper<LatinKeyboardView>
+            implements TimerProxy {
+        private static final int MSG_REPEAT_KEY = 1;
+        private static final int MSG_LONGPRESS_KEY = 2;
+        private static final int MSG_IGNORE_DOUBLE_TAP = 3;
+
+        private boolean mInKeyRepeat;
+
+        public KeyTimerHandler(LatinKeyboardView outerInstance) {
+            super(outerInstance);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final LatinKeyboardView 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;
+            }
+        }
+
+        @Override
+        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;
+        }
+
+        @Override
+        public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {
+            cancelLongPressTimer();
+            sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay);
+        }
+
+        @Override
+        public void cancelLongPressTimer() {
+            removeMessages(MSG_LONGPRESS_KEY);
+        }
+
+        @Override
+        public void cancelKeyTimers() {
+            cancelKeyRepeatTimer();
+            cancelLongPressTimer();
+            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();
+        }
+    }
+
+    private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
+        private boolean mProcessingShiftDoubleTapEvent = 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 = mKeyTimerHandler.isIgnoringDoubleTap();
+                    if (!ignoringDoubleTap)
+                        onDoubleTapShiftKey(tracker);
+                    return true;
+                }
+                // Otherwise these events should not be handled as double tap.
+                mProcessingShiftDoubleTapEvent = false;
+            }
+            return mProcessingShiftDoubleTapEvent;
+        }
+    }
 
     public LatinKeyboardView(Context context, AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, R.attr.keyboardViewStyle);
     }
 
     public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+
+        mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this);
+
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
+        mVerticalCorrection = a.getDimensionPixelOffset(
+                R.styleable.KeyboardView_verticalCorrection, 0);
+        mPopupLayout = a.getResourceId(R.styleable.KeyboardView_popupLayout, 0);
+        a.recycle();
+
+        final Resources res = getResources();
+        final float keyHysteresisDistance = res.getDimension(R.dimen.key_hysteresis_distance);
+        mKeyDetector = new KeyDetector(keyHysteresisDistance);
+
+        final boolean ignoreMultitouch = true;
+        mGestureDetector = new GestureDetector(
+                getContext(), new DoubleTapListener(), null, ignoreMultitouch);
+        mGestureDetector.setIsLongpressEnabled(false);
+
+        mHasDistinctMultitouch = context.getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
+        mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval);
+
+        PointerTracker.init(mHasDistinctMultitouch, getContext());
     }
 
-    @Override
-    public void setKeyboard(Keyboard newKeyboard) {
-        super.setKeyboard(newKeyboard);
-        // One-seventh of the keyboard width seems like a reasonable threshold
-        final int jumpThreshold = newKeyboard.mOccupiedWidth / 7;
-        mJumpThresholdSquare = jumpThreshold * jumpThreshold;
+    public void startIgnoringDoubleTap() {
+        if (ENABLE_CAPSLOCK_BY_DOUBLETAP)
+            mKeyTimerHandler.startIgnoringDoubleTap();
+    }
+
+    public void setKeyboardActionListener(KeyboardActionListener listener) {
+        mKeyboardActionListener = listener;
+        PointerTracker.setKeyboardActionListener(listener);
     }
 
     /**
-     * This function checks to see if we need to handle any sudden jumps in the pointer location
-     * that could be due to a multi-touch being treated as a move by the firmware or hardware.
-     * Once a sudden jump is detected, all subsequent move events are discarded
-     * until an UP is received.<P>
-     * When a sudden jump is detected, an UP event is simulated at the last position and when
-     * the sudden moves subside, a DOWN event is simulated for the second key.
-     * @param me the motion event
-     * @return true if the event was consumed, so that it doesn't continue to be handled by
-     * {@link LatinKeyboardBaseView}.
+     * Returns the {@link KeyboardActionListener} object.
+     * @return the listener attached to this keyboard
      */
-    private boolean handleSuddenJump(MotionEvent me) {
-        // If device has distinct multi touch panel, there is no need to check sudden jump.
-        if (hasDistinctMultitouch())
-            return false;
-        final int action = me.getAction();
-        final int x = (int) me.getX();
-        final int y = (int) me.getY();
-        boolean result = false;
+    @Override
+    public KeyboardActionListener getKeyboardActionListener() {
+        return mKeyboardActionListener;
+    }
 
-        // Real multi-touch event? Stop looking for sudden jumps
-        if (me.getPointerCount() > 1) {
-            mDisableDisambiguation = true;
+    @Override
+    public KeyDetector getKeyDetector() {
+        return mKeyDetector;
+    }
+
+    @Override
+    public DrawingProxy getDrawingProxy() {
+        return this;
+    }
+
+    @Override
+    public TimerProxy getTimerProxy() {
+        return mKeyTimerHandler;
+    }
+
+    @Override
+    public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
+        final Keyboard keyboard = getKeyboard();
+        if (keyboard instanceof LatinKeyboard) {
+            final LatinKeyboard latinKeyboard = (LatinKeyboard)keyboard;
+            if (latinKeyboard.isPhoneKeyboard() || latinKeyboard.isNumberKeyboard()) {
+                // Phone and number keyboard never shows popup preview.
+                super.setKeyPreviewPopupEnabled(false, delay);
+                return;
+            }
         }
-        if (mDisableDisambiguation) {
-            // If UP, reset the multi-touch flag
-            if (action == MotionEvent.ACTION_UP) mDisableDisambiguation = false;
+        super.setKeyPreviewPopupEnabled(previewEnabled, delay);
+    }
+
+    /**
+     * 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) {
+        // Remove any pending messages, except dismissing preview
+        mKeyTimerHandler.cancelKeyTimers();
+        super.setKeyboard(keyboard);
+        mKeyDetector.setKeyboard(
+                keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
+        mKeyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth);
+        PointerTracker.setKeyDetector(mKeyDetector);
+        mTouchScreenRegulator.setKeyboard(keyboard);
+        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();
+    }
+
+    @Override
+    public void cancelAllMessages() {
+        mKeyTimerHandler.cancelAllMessages();
+        super.cancelAllMessages();
+    }
+
+    private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) {
+        // Check if we have a popup layout specified first.
+        if (mPopupLayout == 0) {
             return false;
         }
 
-        switch (action) {
-        case MotionEvent.ACTION_DOWN:
-            // Reset the "session"
-            mDroppingEvents = false;
-            mDisableDisambiguation = false;
-            break;
-        case MotionEvent.ACTION_MOVE:
-            // Is this a big jump?
-            final int distanceSquare = (mLastX - x) * (mLastX - x) + (mLastY - y) * (mLastY - y);
-            // Check the distance.
-            if (distanceSquare > mJumpThresholdSquare) {
-                // If we're not yet dropping events, start dropping and send an UP event
-                if (!mDroppingEvents) {
-                    mDroppingEvents = true;
-                    // Send an up event
-                    MotionEvent translated = MotionEvent.obtain(
-                            me.getEventTime(), me.getEventTime(),
-                            MotionEvent.ACTION_UP,
-                            mLastX, mLastY, me.getMetaState());
-                    super.onTouchEvent(translated);
-                    translated.recycle();
-                }
-                result = true;
-            } else if (mDroppingEvents) {
-                // If moves are small and we're already dropping events, continue dropping
-                result = true;
-            }
-            break;
-        case MotionEvent.ACTION_UP:
-            if (mDroppingEvents) {
-                // Send a down event first, as we dropped a bunch of sudden jumps and assume that
-                // the user is releasing the touch on the second key.
-                MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
-                        MotionEvent.ACTION_DOWN,
-                        x, y, me.getMetaState());
-                super.onTouchEvent(translated);
-                translated.recycle();
-                mDroppingEvents = false;
-                // Let the up event get processed as well, result = false
-            }
-            break;
+        // Check if we are already displaying popup panel.
+        if (mPopupPanel != null)
+            return false;
+        final Key parentKey = tracker.getKey(keyIndex);
+        if (parentKey == null)
+            return false;
+        return onLongPress(parentKey, tracker);
+    }
+
+    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 mPointerQueue.
+        mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
+    }
+
+    // This default implementation returns a popup mini keyboard panel.
+    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);
+        final Keyboard parentKeyboard = getKeyboard();
+        final Keyboard miniKeyboard = new MiniKeyboard.Builder(
+                this, parentKeyboard.mPopupKeyboardResId, 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 mPopupPanel != null;
+    }
+
+    public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboard oldKeyboard) {
+        final Keyboard keyboard = getKeyboard();
+        // We should not set text fade factor to the keyboard which does not display the language on
+        // its spacebar.
+        if (keyboard instanceof LatinKeyboard && keyboard == oldKeyboard) {
+            ((LatinKeyboard)keyboard).setSpacebarTextFadeFactor(fadeFactor, this);
         }
-        // Track the previous coordinate
-        mLastX = x;
-        mLastY = y;
-        return result;
+    }
+
+    /**
+     * 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) {
+        final int primaryCode = parentKey.mCode;
+        final Keyboard keyboard = getKeyboard();
+        if (keyboard instanceof LatinKeyboard) {
+            final LatinKeyboard latinKeyboard = (LatinKeyboard) keyboard;
+            if (primaryCode == Keyboard.CODE_DIGIT0 && latinKeyboard.isPhoneKeyboard()) {
+                tracker.onLongPressed();
+                // Long pressing on 0 in phone number keypad gives you a '+'.
+                return invokeOnKey(Keyboard.CODE_PLUS);
+            }
+            if (primaryCode == Keyboard.CODE_SHIFT && latinKeyboard.isAlphaKeyboard()) {
+                tracker.onLongPressed();
+                return invokeOnKey(Keyboard.CODE_CAPSLOCK);
+            }
+        }
+        if (primaryCode == Keyboard.CODE_SETTINGS || primaryCode == Keyboard.CODE_SPACE) {
+            // Both long pressing settings key and space key invoke IME switcher dialog.
+            if (getKeyboardActionListener().onCustomRequest(
+                    LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) {
+                tracker.onLongPressed();
+                return true;
+            } else {
+                return openPopupPanel(parentKey, tracker);
+            }
+        } else {
+            return openPopupPanel(parentKey, tracker);
+        }
+    }
+
+    private boolean invokeOnKey(int primaryCode) {
+        getKeyboardActionListener().onCodeInput(primaryCode, null,
+                KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
+                KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+        return true;
+    }
+
+    private boolean openPopupPanel(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);
+        }
+        mPopupPanel = popupPanel;
+        mPopupPanelPointerTrackerId = tracker.mPointerId;
+
+        popupPanel.showPopupPanel(this, parentKey, tracker, mPopupWindow);
+        final int translatedX = popupPanel.translateX(tracker.getLastX());
+        final int translatedY = popupPanel.translateY(tracker.getLastY());
+        tracker.onShowPopupPanel(translatedX, translatedY, SystemClock.uptimeMillis(), popupPanel);
+
+        invalidateAllKeys();
+        return true;
+    }
+
+    private PointerTracker getPointerTracker(final int id) {
+        return PointerTracker.getPointerTracker(id, this);
+    }
+
+    public boolean isInSlidingKeyInput() {
+        if (mPopupPanel != null) {
+            return true;
+        } else {
+            return PointerTracker.isAnyInSlidingKeyInput();
+        }
+    }
+
+    public int getPointerCount() {
+        return mOldPointerCount;
     }
 
     @Override
     public boolean onTouchEvent(MotionEvent me) {
-        if (getKeyboard() == null) return true;
+        return mTouchScreenRegulator.onTouchEvent(me);
+    }
 
-        // If there was a sudden jump, return without processing the actual motion event.
-        if (handleSuddenJump(me)) {
-            if (DEBUG_MODE)
-                Log.w(TAG, "onTouchEvent: ignore sudden jump " + me);
+    @Override
+    public boolean processMotionEvent(MotionEvent me) {
+        final boolean nonDistinctMultitouch = !mHasDistinctMultitouch;
+        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 (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
             return true;
         }
 
-        return super.onTouchEvent(me);
+        // Gesture detector must be enabled only when mini-keyboard is not on the screen.
+        if (mPopupPanel == null && mGestureDetector != null
+                && mGestureDetector.onTouchEvent(me)) {
+            PointerTracker.dismissAllKeyPreviews();
+            mKeyTimerHandler.cancelKeyTimers();
+            return true;
+        }
+
+        final long eventTime = me.getEventTime();
+        final int index = me.getActionIndex();
+        final int id = me.getPointerId(index);
+        final int x, y;
+        if (mPopupPanel != null && id == mPopupPanelPointerTrackerId) {
+            x = mPopupPanel.translateX((int)me.getX(index));
+            y = mPopupPanel.translateY((int)me.getY(index));
+        } else {
+            x = (int)me.getX(index);
+            y = (int)me.getY(index);
+        }
+
+        if (mKeyTimerHandler.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()) {
+                mKeyTimerHandler.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 (nonDistinctMultitouch) {
+            // 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, this);
+                    if (action == MotionEvent.ACTION_UP)
+                        tracker.onUpEvent(x, y, eventTime);
+                }
+            } 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);
+            } else if (pointerCount == 1 && oldPointerCount == 1) {
+                processMotionEvent(tracker, action, x, y, eventTime, this);
+            } else {
+                Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
+                        + " (old " + oldPointerCount + ")");
+            }
+            return true;
+        }
+
+        if (action == MotionEvent.ACTION_MOVE) {
+            for (int i = 0; i < pointerCount; i++) {
+                final PointerTracker tracker = getPointerTracker(me.getPointerId(i));
+                final int px, py;
+                if (mPopupPanel != null && tracker.mPointerId == mPopupPanelPointerTrackerId) {
+                    px = mPopupPanel.translateX((int)me.getX(i));
+                    py = mPopupPanel.translateY((int)me.getY(i));
+                } else {
+                    px = (int)me.getX(i);
+                    py = (int)me.getY(i);
+                }
+                tracker.onMoveEvent(px, py, eventTime);
+            }
+        } else {
+            processMotionEvent(getPointerTracker(id), action, x, y, eventTime, this);
+        }
+
+        return true;
+    }
+
+    private static void processMotionEvent(PointerTracker tracker, int action, int x, int y,
+            long eventTime, PointerTracker.KeyEventHandler handler) {
+        switch (action) {
+        case MotionEvent.ACTION_DOWN:
+        case MotionEvent.ACTION_POINTER_DOWN:
+            tracker.onDownEvent(x, y, eventTime, handler);
+            break;
+        case MotionEvent.ACTION_UP:
+        case MotionEvent.ACTION_POINTER_UP:
+            tracker.onUpEvent(x, y, eventTime);
+            break;
+        case MotionEvent.ACTION_MOVE:
+            tracker.onMoveEvent(x, y, eventTime);
+            break;
+        case MotionEvent.ACTION_CANCEL:
+            tracker.onCancelEvent(x, y, eventTime);
+            break;
+        }
+    }
+
+    @Override
+    public void closing() {
+        super.closing();
+        dismissPopupPanel();
+        mPopupPanelCache.clear();
+    }
+
+    @Override
+    public boolean dismissPopupPanel() {
+        if (mPopupWindow != null && mPopupWindow.isShowing()) {
+            mPopupWindow.dismiss();
+            mPopupPanel = null;
+            mPopupPanelPointerTrackerId = -1;
+            invalidateAllKeys();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean handleBack() {
+        return dismissPopupPanel();
+    }
+
+    @Override
+    public void draw(Canvas c) {
+        Utils.GCUtils.getInstance().reset();
+        boolean tryGC = true;
+        for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
+            try {
+                super.draw(c);
+                tryGC = false;
+            } catch (OutOfMemoryError e) {
+                tryGC = Utils.GCUtils.getInstance().tryGCOrWait("LatinKeyboardView", e);
+            }
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        // Token is available from here.
+        VoiceProxy.getInstance().onAttachedToWindow();
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        // Drop non-hover touch events when touch exploration is enabled.
+        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+            return false;
+        }
+
+        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);
+    }
+
+    /**
+     * Receives hover events from the input framework. This method overrides
+     * View.dispatchHoverEvent(MotionEvent) on SDK version ICS or higher. On
+     * lower SDK versions, this method is never called.
+     *
+     * @param event The motion event to be dispatched.
+     * @return {@code true} if the event was handled by the view, {@code false}
+     *         otherwise
+     */
+    public boolean dispatchHoverEvent(MotionEvent event) {
+        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+            final PointerTracker tracker = getPointerTracker(0);
+            return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker);
+        }
+
+        // Reflection doesn't support calling superclass methods.
+        return false;
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
index 08e7d7e..17c2539 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
@@ -16,12 +16,18 @@
 
 package com.android.inputmethod.keyboard;
 
-import com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder.MiniKeyboardParams;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.keyboard.internal.PopupCharactersParser;
+import com.android.inputmethod.latin.R;
 
 public class MiniKeyboard extends Keyboard {
     private final int mDefaultKeyCoordX;
 
-    public MiniKeyboard(MiniKeyboardParams params) {
+    private MiniKeyboard(Builder.MiniKeyboardParams params) {
         super(params);
         mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
     }
@@ -29,4 +35,244 @@
     public int getDefaultCoordX() {
         return mDefaultKeyCoordX;
     }
+
+    public static class Builder extends KeyboardBuilder<Builder.MiniKeyboardParams> {
+        private final CharSequence[] mPopupCharacters;
+
+        public static class MiniKeyboardParams extends KeyboardParams {
+            /* package */int mTopRowAdjustment;
+            public int mNumRows;
+            public int mNumColumns;
+            public int mLeftKeys;
+            public int mRightKeys; // includes default key.
+
+            public MiniKeyboardParams() {
+                super();
+            }
+
+            /* package for test */MiniKeyboardParams(int numKeys, int maxColumns, int keyWidth,
+                    int rowHeight, int coordXInParent, int parentKeyboardWidth) {
+                super();
+                setParameters(numKeys, maxColumns, keyWidth, rowHeight, coordXInParent,
+                        parentKeyboardWidth);
+            }
+
+            /**
+             * Set keyboard parameters of mini keyboard.
+             *
+             * @param numKeys number of keys in this mini keyboard.
+             * @param maxColumns number of maximum columns of this mini keyboard.
+             * @param keyWidth mini keyboard key width in pixel, including horizontal gap.
+             * @param rowHeight mini keyboard row height in pixel, including vertical gap.
+             * @param coordXInParent coordinate x of the popup key in parent keyboard.
+             * @param parentKeyboardWidth parent keyboard width in pixel.
+             */
+            public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight,
+                    int coordXInParent, int parentKeyboardWidth) {
+                if (parentKeyboardWidth / keyWidth < maxColumns) {
+                    throw new IllegalArgumentException(
+                            "Keyboard is too small to hold mini keyboard: " + parentKeyboardWidth
+                                    + " " + keyWidth + " " + maxColumns);
+                }
+                mDefaultKeyWidth = keyWidth;
+                mDefaultRowHeight = rowHeight;
+
+                final int numRows = (numKeys + maxColumns - 1) / maxColumns;
+                mNumRows = numRows;
+                final int numColumns = getOptimizedColumns(numKeys, maxColumns);
+                mNumColumns = numColumns;
+
+                final int numLeftKeys = (numColumns - 1) / 2;
+                final int numRightKeys = numColumns - numLeftKeys; // including default key.
+                final int maxLeftKeys = coordXInParent / keyWidth;
+                final int maxRightKeys = Math.max(1, (parentKeyboardWidth - coordXInParent)
+                        / keyWidth);
+                int leftKeys, rightKeys;
+                if (numLeftKeys > maxLeftKeys) {
+                    leftKeys = maxLeftKeys;
+                    rightKeys = numColumns - maxLeftKeys;
+                } else if (numRightKeys > maxRightKeys) {
+                    leftKeys = numColumns - maxRightKeys;
+                    rightKeys = maxRightKeys;
+                } else {
+                    leftKeys = numLeftKeys;
+                    rightKeys = numRightKeys;
+                }
+                // Shift right if the left edge of mini keyboard is on the edge of parent keyboard
+                // unless the parent key is on the left edge.
+                if (leftKeys * keyWidth >= coordXInParent && leftKeys > 0) {
+                    leftKeys--;
+                    rightKeys++;
+                }
+                // Shift left if the right edge of mini keyboard is on the edge of parent keyboard
+                // unless the parent key is on the right edge.
+                if (rightKeys * keyWidth + coordXInParent >= parentKeyboardWidth && rightKeys > 1) {
+                    leftKeys++;
+                    rightKeys--;
+                }
+                mLeftKeys = leftKeys;
+                mRightKeys = rightKeys;
+
+                // Centering of the top row.
+                final boolean onEdge = (leftKeys == 0 || rightKeys == 1);
+                if (numRows < 2 || onEdge || getTopRowEmptySlots(numKeys, numColumns) % 2 == 0) {
+                    mTopRowAdjustment = 0;
+                } else if (mLeftKeys < mRightKeys - 1) {
+                    mTopRowAdjustment = 1;
+                } else {
+                    mTopRowAdjustment = -1;
+                }
+
+                mWidth = mOccupiedWidth = mNumColumns * mDefaultKeyWidth;
+                mHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
+            }
+
+            // Return key position according to column count (0 is default).
+            /* package */int getColumnPos(int n) {
+                final int col = n % mNumColumns;
+                if (col == 0) {
+                    // default position.
+                    return 0;
+                }
+                int pos = 0;
+                int right = 1; // include default position key.
+                int left = 0;
+                int i = 0;
+                while (true) {
+                    // Assign right key if available.
+                    if (right < mRightKeys) {
+                        pos = right;
+                        right++;
+                        i++;
+                    }
+                    if (i >= col)
+                        break;
+                    // Assign left key if available.
+                    if (left < mLeftKeys) {
+                        left++;
+                        pos = -left;
+                        i++;
+                    }
+                    if (i >= col)
+                        break;
+                }
+                return pos;
+            }
+
+            private static int getTopRowEmptySlots(int numKeys, int numColumns) {
+                final int remainingKeys = numKeys % numColumns;
+                if (remainingKeys == 0) {
+                    return 0;
+                } else {
+                    return numColumns - remainingKeys;
+                }
+            }
+
+            private int getOptimizedColumns(int numKeys, int maxColumns) {
+                int numColumns = Math.min(numKeys, maxColumns);
+                while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
+                    numColumns--;
+                }
+                return numColumns;
+            }
+
+            public int getDefaultKeyCoordX() {
+                return mLeftKeys * mDefaultKeyWidth;
+            }
+
+            public int getX(int n, int row) {
+                final int x = getColumnPos(n) * mDefaultKeyWidth + getDefaultKeyCoordX();
+                if (isTopRow(row)) {
+                    return x + mTopRowAdjustment * (mDefaultKeyWidth / 2);
+                }
+                return x;
+            }
+
+            public int getY(int row) {
+                return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
+            }
+
+            public int getRowFlags(int row) {
+                int rowFlags = 0;
+                if (row == 0)
+                    rowFlags |= Keyboard.EDGE_TOP;
+                if (isTopRow(row))
+                    rowFlags |= Keyboard.EDGE_BOTTOM;
+                return rowFlags;
+            }
+
+            private boolean isTopRow(int rowCount) {
+                return rowCount == mNumRows - 1;
+            }
+        }
+
+        public Builder(KeyboardView view, int xmlId, Key parentKey, Keyboard parentKeyboard) {
+            super(view.getContext(), new MiniKeyboardParams());
+            load(parentKeyboard.mId.cloneWithNewXml(mResources.getResourceEntryName(xmlId), xmlId));
+
+            // HACK: Current mini keyboard design totally relies on the 9-patch
+            // padding about horizontal
+            // and vertical key spacing. To keep the visual of mini keyboard as
+            // is, these hacks are
+            // needed to keep having the same horizontal and vertical key
+            // spacing.
+            mParams.mHorizontalGap = 0;
+            mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
+            // TODO: When we have correctly padded key background 9-patch
+            // drawables for mini keyboard,
+            // revert the above hacks and uncomment the following lines.
+            // mParams.mHorizontalGap = parentKeyboard.mHorizontalGap;
+            // mParams.mVerticalGap = parentKeyboard.mVerticalGap;
+
+            mParams.mIsRtlKeyboard = parentKeyboard.mIsRtlKeyboard;
+            mPopupCharacters = parentKey.mPopupCharacters;
+
+            final int keyWidth = getMaxKeyWidth(view, mPopupCharacters, mParams.mDefaultKeyWidth);
+            mParams.setParameters(mPopupCharacters.length, parentKey.mMaxPopupColumn, keyWidth,
+                    parentKeyboard.mDefaultRowHeight, parentKey.mX
+                            + (mParams.mDefaultKeyWidth - keyWidth) / 2, view.getMeasuredWidth());
+        }
+
+        private static int getMaxKeyWidth(KeyboardView view, CharSequence[] popupCharacters,
+                int minKeyWidth) {
+            Paint paint = null;
+            Rect bounds = null;
+            int maxWidth = 0;
+            for (CharSequence popupSpec : popupCharacters) {
+                final CharSequence label = PopupCharactersParser.getLabel(popupSpec.toString());
+                // If the label is single letter, minKeyWidth is enough to hold
+                // the label.
+                if (label != null && label.length() > 1) {
+                    if (paint == null) {
+                        paint = new Paint();
+                        paint.setAntiAlias(true);
+                    }
+                    final int labelSize = view.getDefaultLabelSizeAndSetPaint(paint);
+                    paint.setTextSize(labelSize);
+                    if (bounds == null)
+                        bounds = new Rect();
+                    paint.getTextBounds(label.toString(), 0, label.length(), bounds);
+                    if (maxWidth < bounds.width())
+                        maxWidth = bounds.width();
+                }
+            }
+            final int horizontalPadding = (int) view.getContext().getResources()
+                    .getDimension(R.dimen.mini_keyboard_key_horizontal_padding);
+            return Math.max(minKeyWidth, maxWidth + horizontalPadding);
+        }
+
+        @Override
+        public MiniKeyboard build() {
+            final MiniKeyboardParams params = mParams;
+            for (int n = 0; n < mPopupCharacters.length; n++) {
+                final CharSequence label = mPopupCharacters[n];
+                final int row = n / params.mNumColumns;
+                final Key key = new Key(mResources, params, label, params.getX(n, row),
+                        params.getY(row), params.mDefaultKeyWidth, params.mDefaultRowHeight,
+                        params.getRowFlags(row));
+                params.onAddKey(key);
+            }
+            return new MiniKeyboard(params);
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
deleted file mode 100644
index 84bd44c..0000000
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2010 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 java.util.List;
-
-public class MiniKeyboardKeyDetector extends KeyDetector {
-    private final int mSlideAllowanceSquare;
-    private final int mSlideAllowanceSquareTop;
-
-    public MiniKeyboardKeyDetector(float slideAllowance) {
-        super(/* keyHysteresisDistance */0);
-        mSlideAllowanceSquare = (int)(slideAllowance * slideAllowance);
-        // Top slide allowance is slightly longer (sqrt(2) times) than other edges.
-        mSlideAllowanceSquareTop = mSlideAllowanceSquare * 2;
-    }
-
-    @Override
-    protected int getMaxNearbyKeys() {
-        // No nearby key will be returned.
-        return 1;
-    }
-
-    @Override
-    public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
-        final List<Key> keys = getKeyboard().mKeys;
-        final int touchX = getTouchX(x);
-        final int touchY = getTouchY(y);
-
-        int nearestIndex = NOT_A_KEY;
-        int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
-        final int keyCount = keys.size();
-        for (int index = 0; index < keyCount; index++) {
-            final int dist = keys.get(index).squaredDistanceToEdge(touchX, touchY);
-            if (dist < nearestDist) {
-                nearestIndex = index;
-                nearestDist = dist;
-            }
-        }
-
-        if (allCodes != null && nearestIndex != NOT_A_KEY)
-            allCodes[0] = keys.get(nearestIndex).mCode;
-        return nearestIndex;
-    }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 1f8119a..d33cb44 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -438,9 +438,9 @@
     private void onDownEventInternal(int x, int y, long eventTime) {
         int keyIndex = onDownKey(x, y, eventTime);
         // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
-        // from modifier key, or 3) this pointer is on mini-keyboard.
+        // from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
         mIsAllowedSlidingKeyInput = sConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex)
-                || mKeyDetector instanceof MiniKeyboardKeyDetector;
+                || mKeyDetector.alwaysAllowsSlidingInput();
         mKeyboardLayoutHasBeenChanged = false;
         mKeyAlreadyProcessed = false;
         mIsRepeatableKey = false;
diff --git a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
index fb932e3..2396222 100644
--- a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
@@ -28,6 +28,8 @@
 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
 import com.android.inputmethod.latin.R;
 
+import java.util.List;
+
 /**
  * A view that renders a virtual {@link MiniKeyboard}. It handles rendering of keys and detecting
  * key presses and touch movements.
@@ -39,10 +41,55 @@
     private final KeyDetector mKeyDetector;
     private final int mVerticalCorrection;
 
-    private LatinKeyboardBaseView mParentKeyboardView;
+    private LatinKeyboardView mParentKeyboardView;
     private int mOriginX;
     private int mOriginY;
 
+    private static class MiniKeyboardKeyDetector extends KeyDetector {
+        private final int mSlideAllowanceSquare;
+        private final int mSlideAllowanceSquareTop;
+
+        public MiniKeyboardKeyDetector(float slideAllowance) {
+            super(/* keyHysteresisDistance */0);
+            mSlideAllowanceSquare = (int)(slideAllowance * slideAllowance);
+            // Top slide allowance is slightly longer (sqrt(2) times) than other edges.
+            mSlideAllowanceSquareTop = mSlideAllowanceSquare * 2;
+        }
+
+        @Override
+        public boolean alwaysAllowsSlidingInput() {
+            return true;
+        }
+
+        @Override
+        protected int getMaxNearbyKeys() {
+            // No nearby key will be returned.
+            return 1;
+        }
+
+        @Override
+        public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
+            final List<Key> keys = getKeyboard().mKeys;
+            final int touchX = getTouchX(x);
+            final int touchY = getTouchY(y);
+
+            int nearestIndex = NOT_A_KEY;
+            int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
+            final int keyCount = keys.size();
+            for (int index = 0; index < keyCount; index++) {
+                final int dist = keys.get(index).squaredDistanceToEdge(touchX, touchY);
+                if (dist < nearestDist) {
+                    nearestIndex = index;
+                    nearestDist = dist;
+                }
+            }
+
+            if (allCodes != null && nearestIndex != NOT_A_KEY)
+                allCodes[0] = keys.get(nearestIndex).mCode;
+            return nearestIndex;
+        }
+    }
+
     private static final TimerProxy EMPTY_TIMER_PROXY = new TimerProxy() {
         @Override
         public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {}
@@ -146,11 +193,6 @@
     }
 
     @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        // Do nothing for the mini keyboard.
-    }
-
-    @Override
     public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
         // Mini keyboard needs no pop-up key preview displayed, so we pass always false with a
         // delay of 0. The delay does not matter actually since the popup is not shown anyway.
@@ -158,7 +200,7 @@
     }
 
     @Override
-    public void showPopupPanel(LatinKeyboardBaseView parentKeyboardView, Key parentKey,
+    public void showPopupPanel(LatinKeyboardView parentKeyboardView, Key parentKey,
             PointerTracker tracker, PopupWindow window) {
         mParentKeyboardView = parentKeyboardView;
         final View container = (View)getParent();
diff --git a/java/src/com/android/inputmethod/keyboard/PopupPanel.java b/java/src/com/android/inputmethod/keyboard/PopupPanel.java
index dc526e7..db637c5 100644
--- a/java/src/com/android/inputmethod/keyboard/PopupPanel.java
+++ b/java/src/com/android/inputmethod/keyboard/PopupPanel.java
@@ -26,7 +26,7 @@
      * @param tracker the pointer tracker that pressesd the parent key
      * @param window PopupWindow to be used to show this popup panel
      */
-    public void showPopupPanel(LatinKeyboardBaseView parentKeyboardView, Key parentKey,
+    public void showPopupPanel(LatinKeyboardView parentKeyboardView, Key parentKey,
             PointerTracker tracker, PopupWindow window);
 
     /**
diff --git a/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java b/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java
new file mode 100644
index 0000000..c4251cc
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java
@@ -0,0 +1,158 @@
+/*
+ * 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.os.Build;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+
+public class SuddenJumpingTouchEventHandler {
+    private static final String TAG = SuddenJumpingTouchEventHandler.class.getSimpleName();
+    private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
+
+    public interface ProcessMotionEvent {
+        public boolean processMotionEvent(MotionEvent me);
+    }
+
+    private final ProcessMotionEvent mView;
+    private final boolean mNeedsSuddenJumpingHack;
+
+    /** Whether we've started dropping move events because we found a big jump */
+    private boolean mDroppingEvents;
+    /**
+     * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has
+     * occured
+     */
+    private boolean mDisableDisambiguation;
+    /** The distance threshold at which we start treating the touch session as a multi-touch */
+    private int mJumpThresholdSquare = Integer.MAX_VALUE;
+    private int mLastX;
+    private int mLastY;
+
+    public SuddenJumpingTouchEventHandler(Context context, ProcessMotionEvent view) {
+        mView = view;
+        final String[] deviceList = context.getResources().getStringArray(
+                R.array.sudden_jumping_touch_event_device_list);
+        mNeedsSuddenJumpingHack = needsSuddenJumpingHack(Build.DEVICE, deviceList);
+    }
+
+    private static boolean needsSuddenJumpingHack(String deviceName, String[] deviceList) {
+        for (String device : deviceList) {
+            if (device.equalsIgnoreCase(deviceName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void setKeyboard(Keyboard newKeyboard) {
+        // One-seventh of the keyboard width seems like a reasonable threshold
+        final int jumpThreshold = newKeyboard.mOccupiedWidth / 7;
+        mJumpThresholdSquare = jumpThreshold * jumpThreshold;
+    }
+
+    /**
+     * This function checks to see if we need to handle any sudden jumps in the pointer location
+     * that could be due to a multi-touch being treated as a move by the firmware or hardware.
+     * Once a sudden jump is detected, all subsequent move events are discarded
+     * until an UP is received.<P>
+     * When a sudden jump is detected, an UP event is simulated at the last position and when
+     * the sudden moves subside, a DOWN event is simulated for the second key.
+     * @param me the motion event
+     * @return true if the event was consumed, so that it doesn't continue to be handled by
+     * {@link LatinKeyboardView}.
+     */
+    private boolean handleSuddenJumping(MotionEvent me) {
+        if (!mNeedsSuddenJumpingHack)
+            return false;
+        final int action = me.getAction();
+        final int x = (int) me.getX();
+        final int y = (int) me.getY();
+        boolean result = false;
+
+        // Real multi-touch event? Stop looking for sudden jumps
+        if (me.getPointerCount() > 1) {
+            mDisableDisambiguation = true;
+        }
+        if (mDisableDisambiguation) {
+            // If UP, reset the multi-touch flag
+            if (action == MotionEvent.ACTION_UP) mDisableDisambiguation = false;
+            return false;
+        }
+
+        switch (action) {
+        case MotionEvent.ACTION_DOWN:
+            // Reset the "session"
+            mDroppingEvents = false;
+            mDisableDisambiguation = false;
+            break;
+        case MotionEvent.ACTION_MOVE:
+            // Is this a big jump?
+            final int distanceSquare = (mLastX - x) * (mLastX - x) + (mLastY - y) * (mLastY - y);
+            // Check the distance.
+            if (distanceSquare > mJumpThresholdSquare) {
+                // If we're not yet dropping events, start dropping and send an UP event
+                if (!mDroppingEvents) {
+                    mDroppingEvents = true;
+                    // Send an up event
+                    MotionEvent translated = MotionEvent.obtain(
+                            me.getEventTime(), me.getEventTime(),
+                            MotionEvent.ACTION_UP,
+                            mLastX, mLastY, me.getMetaState());
+                    mView.processMotionEvent(translated);
+                    translated.recycle();
+                }
+                result = true;
+            } else if (mDroppingEvents) {
+                // If moves are small and we're already dropping events, continue dropping
+                result = true;
+            }
+            break;
+        case MotionEvent.ACTION_UP:
+            if (mDroppingEvents) {
+                // Send a down event first, as we dropped a bunch of sudden jumps and assume that
+                // the user is releasing the touch on the second key.
+                MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
+                        MotionEvent.ACTION_DOWN,
+                        x, y, me.getMetaState());
+                mView.processMotionEvent(translated);
+                translated.recycle();
+                mDroppingEvents = false;
+                // Let the up event get processed as well, result = false
+            }
+            break;
+        }
+        // Track the previous coordinate
+        mLastX = x;
+        mLastY = y;
+        return result;
+    }
+
+    public boolean onTouchEvent(MotionEvent me) {
+        // If there was a sudden jump, return without processing the actual motion event.
+        if (handleSuddenJumping(me)) {
+            if (DEBUG_MODE)
+                Log.w(TAG, "onTouchEvent: ignore sudden jump " + me);
+            return true;
+        }
+        return mView.processMotionEvent(me);
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java
deleted file mode 100644
index 31a291c..0000000
--- a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * Copyright (C) 2010 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.internal;
-
-import android.graphics.Paint;
-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.MiniKeyboard;
-import com.android.inputmethod.latin.R;
-
-public class MiniKeyboardBuilder extends
-        KeyboardBuilder<MiniKeyboardBuilder.MiniKeyboardParams> {
-    private final CharSequence[] mPopupCharacters;
-
-    public static class MiniKeyboardParams extends KeyboardParams {
-        /* package */ int mTopRowAdjustment;
-        public int mNumRows;
-        public int mNumColumns;
-        public int mLeftKeys;
-        public int mRightKeys; // includes default key.
-
-        public MiniKeyboardParams() {
-            super();
-        }
-
-        /* package for test */ MiniKeyboardParams(int numKeys, int maxColumns, int keyWidth,
-                int rowHeight, int coordXInParent, int parentKeyboardWidth) {
-            super();
-            setParameters(
-                    numKeys, maxColumns, keyWidth, rowHeight, coordXInParent, parentKeyboardWidth);
-        }
-
-        /**
-         * Set keyboard parameters of mini keyboard.
-         *
-         * @param numKeys number of keys in this mini keyboard.
-         * @param maxColumns number of maximum columns of this mini keyboard.
-         * @param keyWidth mini keyboard key width in pixel, including horizontal gap.
-         * @param rowHeight mini keyboard row height in pixel, including vertical gap.
-         * @param coordXInParent coordinate x of the popup key in parent keyboard.
-         * @param parentKeyboardWidth parent keyboard width in pixel.
-         */
-        public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight,
-                int coordXInParent, int parentKeyboardWidth) {
-            if (parentKeyboardWidth / keyWidth < maxColumns) {
-                throw new IllegalArgumentException("Keyboard is too small to hold mini keyboard: "
-                        + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
-            }
-            mDefaultKeyWidth = keyWidth;
-            mDefaultRowHeight = rowHeight;
-
-            final int numRows = (numKeys + maxColumns - 1) / maxColumns;
-            mNumRows = numRows;
-            final int numColumns = getOptimizedColumns(numKeys, maxColumns);
-            mNumColumns = numColumns;
-
-            final int numLeftKeys = (numColumns - 1) / 2;
-            final int numRightKeys = numColumns - numLeftKeys; // including default key.
-            final int maxLeftKeys = coordXInParent / keyWidth;
-            final int maxRightKeys = Math.max(1, (parentKeyboardWidth - coordXInParent) / keyWidth);
-            int leftKeys, rightKeys;
-            if (numLeftKeys > maxLeftKeys) {
-                leftKeys = maxLeftKeys;
-                rightKeys = numColumns - maxLeftKeys;
-            } else if (numRightKeys > maxRightKeys) {
-                leftKeys = numColumns - maxRightKeys;
-                rightKeys = maxRightKeys;
-            } else {
-                leftKeys = numLeftKeys;
-                rightKeys = numRightKeys;
-            }
-            // Shift right if the left edge of mini keyboard is on the edge of parent keyboard
-            // unless the parent key is on the left edge.
-            if (leftKeys * keyWidth >= coordXInParent && leftKeys > 0) {
-                leftKeys--;
-                rightKeys++;
-            }
-            // Shift left if the right edge of mini keyboard is on the edge of parent keyboard
-            // unless the parent key is on the right edge.
-            if (rightKeys * keyWidth + coordXInParent >= parentKeyboardWidth && rightKeys > 1) {
-                leftKeys++;
-                rightKeys--;
-            }
-            mLeftKeys = leftKeys;
-            mRightKeys = rightKeys;
-
-            // Centering of the top row.
-            final boolean onEdge = (leftKeys == 0 || rightKeys == 1);
-            if (numRows < 2 || onEdge || getTopRowEmptySlots(numKeys, numColumns) % 2 == 0) {
-                mTopRowAdjustment = 0;
-            } else if (mLeftKeys < mRightKeys - 1) {
-                mTopRowAdjustment = 1;
-            } else {
-                mTopRowAdjustment = -1;
-            }
-
-            mWidth = mOccupiedWidth = mNumColumns * mDefaultKeyWidth;
-            mHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
-        }
-
-        // Return key position according to column count (0 is default).
-        /* package */ int getColumnPos(int n) {
-            final int col = n % mNumColumns;
-            if (col == 0) {
-                // default position.
-                return 0;
-            }
-            int pos = 0;
-            int right = 1; // include default position key.
-            int left = 0;
-            int i = 0;
-            while (true) {
-                // Assign right key if available.
-                if (right < mRightKeys) {
-                    pos = right;
-                    right++;
-                    i++;
-                }
-                if (i >= col)
-                    break;
-                // Assign left key if available.
-                if (left < mLeftKeys) {
-                    left++;
-                    pos = -left;
-                    i++;
-                }
-                if (i >= col)
-                    break;
-            }
-            return pos;
-        }
-
-        private static int getTopRowEmptySlots(int numKeys, int numColumns) {
-            final int remainingKeys = numKeys % numColumns;
-            if (remainingKeys == 0) {
-                return 0;
-            } else {
-                return numColumns - remainingKeys;
-            }
-        }
-
-        private int getOptimizedColumns(int numKeys, int maxColumns) {
-            int numColumns = Math.min(numKeys, maxColumns);
-            while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
-                numColumns--;
-            }
-            return numColumns;
-        }
-
-        public int getDefaultKeyCoordX() {
-            return mLeftKeys * mDefaultKeyWidth;
-        }
-
-        public int getX(int n, int row) {
-            final int x = getColumnPos(n) * mDefaultKeyWidth + getDefaultKeyCoordX();
-            if (isTopRow(row)) {
-                return x + mTopRowAdjustment * (mDefaultKeyWidth / 2);
-            }
-            return x;
-        }
-
-        public int getY(int row) {
-            return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
-        }
-
-        public int getRowFlags(int row) {
-            int rowFlags = 0;
-            if (row == 0) rowFlags |= Keyboard.EDGE_TOP;
-            if (isTopRow(row)) rowFlags |= Keyboard.EDGE_BOTTOM;
-            return rowFlags;
-        }
-
-        private boolean isTopRow(int rowCount) {
-            return rowCount == mNumRows - 1;
-        }
-    }
-
-    public MiniKeyboardBuilder(KeyboardView view, int xmlId, Key parentKey,
-            Keyboard parentKeyboard) {
-        super(view.getContext(), new MiniKeyboardParams());
-        load(parentKeyboard.mId.cloneWithNewXml(mResources.getResourceEntryName(xmlId), xmlId));
-
-        // HACK: Current mini keyboard design totally relies on the 9-patch padding about horizontal
-        // and vertical key spacing. To keep the visual of mini keyboard as is, these hacks are
-        // needed to keep having the same horizontal and vertical key spacing.
-        mParams.mHorizontalGap = 0;
-        mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
-        // TODO: When we have correctly padded key background 9-patch drawables for mini keyboard,
-        // revert the above hacks and uncomment the following lines.
-        //mParams.mHorizontalGap = parentKeyboard.mHorizontalGap;
-        //mParams.mVerticalGap = parentKeyboard.mVerticalGap;
-
-        mParams.mIsRtlKeyboard = parentKeyboard.mIsRtlKeyboard;
-        mPopupCharacters = parentKey.mPopupCharacters;
-
-        final int keyWidth = getMaxKeyWidth(view, mPopupCharacters, mParams.mDefaultKeyWidth);
-        mParams.setParameters(
-                mPopupCharacters.length, parentKey.mMaxPopupColumn,
-                keyWidth, parentKeyboard.mDefaultRowHeight,
-                parentKey.mX + (mParams.mDefaultKeyWidth - keyWidth) / 2,
-                view.getMeasuredWidth());
-    }
-
-    private static int getMaxKeyWidth(KeyboardView view, CharSequence[] popupCharacters,
-            int minKeyWidth) {
-        Paint paint = null;
-        Rect bounds = null;
-        int maxWidth = 0;
-        for (CharSequence popupSpec : popupCharacters) {
-            final CharSequence label = PopupCharactersParser.getLabel(popupSpec.toString());
-            // If the label is single letter, minKeyWidth is enough to hold the label.
-            if (label != null && label.length() > 1) {
-                if (paint == null) {
-                    paint = new Paint();
-                    paint.setAntiAlias(true);
-                }
-                final int labelSize = view.getDefaultLabelSizeAndSetPaint(paint);
-                paint.setTextSize(labelSize);
-                if (bounds == null) bounds = new Rect();
-                paint.getTextBounds(label.toString(), 0, label.length(), bounds);
-                if (maxWidth < bounds.width())
-                    maxWidth = bounds.width();
-            }
-        }
-        final int horizontalPadding = (int)view.getContext().getResources().getDimension(
-                R.dimen.mini_keyboard_key_horizontal_padding);
-        return Math.max(minKeyWidth, maxWidth + horizontalPadding);
-    }
-
-    @Override
-    public MiniKeyboard build() {
-        final MiniKeyboardParams params = mParams;
-        for (int n = 0; n < mPopupCharacters.length; n++) {
-            final CharSequence label = mPopupCharacters[n];
-            final int row = n / params.mNumColumns;
-            final Key key = new Key(mResources, params, label, params.getX(n, row), params.getY(row),
-                    params.mDefaultKeyWidth, params.mDefaultRowHeight, params.getRowFlags(row));
-            params.onAddKey(key);
-        }
-        return new MiniKeyboard(params);
-    }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PopupCharactersParser.java b/java/src/com/android/inputmethod/keyboard/internal/PopupCharactersParser.java
index 032489e..7c5abe3 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PopupCharactersParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PopupCharactersParser.java
@@ -196,13 +196,6 @@
         }
     };
 
-    public static final CodeFilter NON_ASCII_FILTER = new CodeFilter() {
-        @Override
-        public boolean shouldFilterOut(int code) {
-            return code < 0x20 || code > 0x7e;
-        }
-    };
-
     public static CharSequence[] filterOut(Resources res, CharSequence[] popupCharacters,
             CodeFilter filter) {
         if (popupCharacters == null || popupCharacters.length < 1) {
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index ffd204d..9642151 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -64,24 +64,10 @@
             }
         }
 
-        // null == dictList is not supposed to be possible, but better safe than sorry and it's
-        // safer for future extension. In this case, rather than returning null, it should be safer
-        // to return an empty DictionaryCollection.
-        if (null == dictList) {
-            return new DictionaryCollection();
-        } else {
-            if (dictList.isEmpty()) {
-                // The list may be empty if no dictionaries have been added. The getter should not
-                // return an empty list, but if it does we end up here. Likewise, if the files
-                // we found could not be opened by the native code for any reason (format mismatch,
-                // file too big to fit in memory, etc) then we could have an empty list. In this
-                // case we want to fall back on the resource.
-                return new DictionaryCollection(createBinaryDictionary(context, fallbackResId,
-                        locale));
-            } else {
-                return new DictionaryCollection(dictList);
-            }
-        }
+        // If the list is empty, that means we should not use any dictionary (for example, the user
+        // explicitly disabled the main dictionary), so the following is okay. dictList is never
+        // null, but if for some reason it is, DictionaryCollection handles it gracefully.
+        return new DictionaryCollection(dictList);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index afbdd36..394414d 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -113,7 +113,7 @@
     // Key events coming any faster than this are long-presses.
     private static final int QUICK_PRESS = 200;
 
-    private static final int SCREEN_ORIENTATION_CHANGE_DETECTION_DELAY = 2;
+    private static final int START_INPUT_VIEW_DELAY_WHEN_SCREEN_ORIENTATION_STARTED = 10;
     private static final int ACCUMULATE_START_INPUT_VIEW_DELAY = 20;
     private static final int RESTORE_KEYBOARD_STATE_DELAY = 500;
 
@@ -198,8 +198,6 @@
 
     // Member variables for remembering the current device orientation.
     private int mDisplayOrientation;
-    private int mDisplayWidth;
-    private int mDisplayHeight;
 
     // Object for reacting to adding/removing a dictionary pack.
     private BroadcastReceiver mDictionaryPackInstallReceiver =
@@ -219,31 +217,10 @@
         private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 5;
         private static final int MSG_SPACE_TYPED = 6;
         private static final int MSG_SET_BIGRAM_PREDICTIONS = 7;
-        private static final int MSG_CONFIRM_ORIENTATION_CHANGE = 8;
+        private static final int MSG_START_ORIENTATION_CHANGE = 8;
         private static final int MSG_START_INPUT_VIEW = 9;
         private static final int MSG_RESTORE_KEYBOARD_LAYOUT = 10;
 
-        private static class OrientationChangeArgs {
-            public final int mOldWidth;
-            public final int mOldHeight;
-            private int mRetryCount;
-
-            public OrientationChangeArgs(int oldw, int oldh) {
-                mOldWidth = oldw;
-                mOldHeight = oldh;
-                mRetryCount = 0;
-            }
-
-            public boolean hasTimedOut() {
-                mRetryCount++;
-                return mRetryCount >= 10;
-            }
-
-            public boolean hasOrientationChangeFinished(DisplayMetrics dm) {
-                return dm.widthPixels != mOldWidth && dm.heightPixels != mOldHeight;
-            }
-        }
-
         public UIHandler(LatinIME outerInstance) {
             super(outerInstance);
         }
@@ -291,18 +268,6 @@
                             (LatinKeyboard)msg.obj);
                 }
                 break;
-            case MSG_CONFIRM_ORIENTATION_CHANGE: {
-                final OrientationChangeArgs args = (OrientationChangeArgs)msg.obj;
-                final Resources res = latinIme.mResources;
-                final DisplayMetrics dm = res.getDisplayMetrics();
-                if (args.hasTimedOut() || args.hasOrientationChangeFinished(dm)) {
-                    latinIme.setDisplayGeometry(res.getConfiguration(), dm);
-                } else {
-                    // It seems orientation changing is on going.
-                    postConfirmOrientationChange(args);
-                }
-                break;
-            }
             case MSG_START_INPUT_VIEW:
                 latinIme.onStartInputView((EditorInfo)msg.obj, false);
                 break;
@@ -411,22 +376,16 @@
             }
         }
 
-        private void postConfirmOrientationChange(OrientationChangeArgs args) {
-            removeMessages(MSG_CONFIRM_ORIENTATION_CHANGE);
-            // Will confirm whether orientation change has finished or not again.
-            sendMessageDelayed(obtainMessage(MSG_CONFIRM_ORIENTATION_CHANGE, args),
-                    SCREEN_ORIENTATION_CHANGE_DETECTION_DELAY);
-        }
-
-        public void startOrientationChanging(int oldw, int oldh) {
-            postConfirmOrientationChange(new OrientationChangeArgs(oldw, oldh));
+        public void startOrientationChanging() {
+            sendMessageDelayed(obtainMessage(MSG_START_ORIENTATION_CHANGE),
+                    START_INPUT_VIEW_DELAY_WHEN_SCREEN_ORIENTATION_STARTED);
             final LatinIME latinIme = getOuterInstance();
             latinIme.mKeyboardSwitcher.getKeyboardState().save();
             postRestoreKeyboardLayout();
         }
 
         public boolean postStartInputView(EditorInfo attribute) {
-            if (hasMessages(MSG_CONFIRM_ORIENTATION_CHANGE) || hasMessages(MSG_START_INPUT_VIEW)) {
+            if (hasMessages(MSG_START_ORIENTATION_CHANGE) || hasMessages(MSG_START_INPUT_VIEW)) {
                 removeMessages(MSG_START_INPUT_VIEW);
                 // Postpone onStartInputView by ACCUMULATE_START_INPUT_VIEW_DELAY and see if
                 // orientation change has finished.
@@ -438,12 +397,6 @@
         }
     }
 
-    private void setDisplayGeometry(Configuration conf, DisplayMetrics metric) {
-        mDisplayOrientation = conf.orientation;
-        mDisplayWidth = metric.widthPixels;
-        mDisplayHeight = metric.heightPixels;
-    }
-
     @Override
     public void onCreate() {
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
@@ -481,7 +434,7 @@
             }
         }
 
-        setDisplayGeometry(res.getConfiguration(), res.getDisplayMetrics());
+        mDisplayOrientation = res.getConfiguration().orientation;
 
         // Register to receive ringer mode change and network state change.
         // Also receive installation and removal of a dictionary pack.
@@ -609,8 +562,9 @@
     public void onConfigurationChanged(Configuration conf) {
         mSubtypeSwitcher.onConfigurationChanged(conf);
         // If orientation changed while predicting, commit the change
-        if (conf.orientation != mDisplayOrientation) {
-            mHandler.startOrientationChanging(mDisplayWidth, mDisplayHeight);
+        if (mDisplayOrientation != conf.orientation) {
+            mDisplayOrientation = conf.orientation;
+            mHandler.startOrientationChanging();
             final InputConnection ic = getCurrentInputConnection();
             commitTyped(ic);
             if (ic != null) ic.finishComposingText(); // For voice input
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilderTests.java b/tests/src/com/android/inputmethod/keyboard/MiniKeyboardBuilderTests.java
similarity index 99%
rename from tests/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilderTests.java
rename to tests/src/com/android/inputmethod/keyboard/MiniKeyboardBuilderTests.java
index 1c5661b..a143bba 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilderTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/MiniKeyboardBuilderTests.java
@@ -14,9 +14,9 @@
  * the License.
  */
 
-package com.android.inputmethod.keyboard.internal;
+package com.android.inputmethod.keyboard;
 
-import com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder.MiniKeyboardParams;
+import com.android.inputmethod.keyboard.MiniKeyboard.Builder.MiniKeyboardParams;
 
 import android.test.AndroidTestCase;