From ff082d081f3ea18ff0b9b22126ee4a86504cf83c Mon Sep 17 00:00:00 2001
From: "Tadashi G. Takaoka" <takaoka@google.com>
Date: Wed, 27 Apr 2011 14:14:45 +0900
Subject: [PATCH] Refactor KeyboardView and create MiniKeyboardView

Change-Id: I8d68b944762ccde05020978f20b3742eb6ab945b
---
 java/res/layout/input_basic.xml               |   2 +-
 java/res/layout/input_basic_highcontrast.xml  |   2 +-
 java/res/layout/input_gingerbread.xml         |   2 +-
 java/res/layout/input_honeycomb.xml           |   2 +-
 java/res/layout/input_stone_bold.xml          |   2 +-
 java/res/layout/input_stone_normal.xml        |   2 +-
 java/res/layout/keyboard_popup.xml            |   4 +-
 java/res/layout/keyboard_popup_honeycomb.xml  |   4 +-
 java/res/layout/keyboard_popup_stone.xml      |   4 +-
 java/res/values/styles.xml                    |   2 +-
 .../inputmethod/keyboard/KeyboardView.java    | 288 +++++++-----------
 .../inputmethod/keyboard/MiniKeyboard.java    |  17 ++
 .../keyboard/PopupMiniKeyboardView.java       | 124 ++++++++
 .../inputmethod/keyboard/PopupPanel.java      |  46 +++
 14 files changed, 302 insertions(+), 199 deletions(-)
 create mode 100644 java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
 create mode 100644 java/src/com/android/inputmethod/keyboard/PopupPanel.java

diff --git a/java/res/layout/input_basic.xml b/java/res/layout/input_basic.xml
index 86ae82d511..8666daece8 100644
--- a/java/res/layout/input_basic.xml
+++ b/java/res/layout/input_basic.xml
@@ -21,7 +21,7 @@
 <com.android.inputmethod.keyboard.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-        android:id="@+id/LatinkeyboardBaseView"
+        android:id="@+id/latin_keyboard_view"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/java/res/layout/input_basic_highcontrast.xml b/java/res/layout/input_basic_highcontrast.xml
index 03b22ef076..4829c7d7c4 100644
--- a/java/res/layout/input_basic_highcontrast.xml
+++ b/java/res/layout/input_basic_highcontrast.xml
@@ -21,7 +21,7 @@
 <com.android.inputmethod.keyboard.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-        android:id="@+id/LatinkeyboardBaseView"
+        android:id="@+id/latin_keyboard_view"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/java/res/layout/input_gingerbread.xml b/java/res/layout/input_gingerbread.xml
index 9332f9ddf4..ccca501af9 100644
--- a/java/res/layout/input_gingerbread.xml
+++ b/java/res/layout/input_gingerbread.xml
@@ -21,7 +21,7 @@
 <com.android.inputmethod.keyboard.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-        android:id="@+id/LatinkeyboardBaseView"
+        android:id="@+id/latin_keyboard_view"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/java/res/layout/input_honeycomb.xml b/java/res/layout/input_honeycomb.xml
index 42bedb43c8..8dadafd812 100644
--- a/java/res/layout/input_honeycomb.xml
+++ b/java/res/layout/input_honeycomb.xml
@@ -21,7 +21,7 @@
 <com.android.inputmethod.keyboard.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-        android:id="@+id/LatinkeyboardBaseView"
+        android:id="@+id/latin_keyboard_view"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/java/res/layout/input_stone_bold.xml b/java/res/layout/input_stone_bold.xml
index 22ac14eb12..a0b40684f0 100644
--- a/java/res/layout/input_stone_bold.xml
+++ b/java/res/layout/input_stone_bold.xml
@@ -21,7 +21,7 @@
 <com.android.inputmethod.keyboard.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-        android:id="@+id/LatinkeyboardBaseView"
+        android:id="@+id/latin_keyboard_view"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/java/res/layout/input_stone_normal.xml b/java/res/layout/input_stone_normal.xml
index 8baecabdbd..41cbc16faa 100644
--- a/java/res/layout/input_stone_normal.xml
+++ b/java/res/layout/input_stone_normal.xml
@@ -21,7 +21,7 @@
 <com.android.inputmethod.keyboard.LatinKeyboardView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-        android:id="@+id/LatinkeyboardBaseView"
+        android:id="@+id/latin_keyboard_view"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/java/res/layout/keyboard_popup.xml b/java/res/layout/keyboard_popup.xml
index 013662240f..0317d8deb2 100644
--- a/java/res/layout/keyboard_popup.xml
+++ b/java/res/layout/keyboard_popup.xml
@@ -26,9 +26,9 @@
         android:paddingLeft="@dimen/mini_keyboard_horizontal_padding"
         android:paddingRight="@dimen/mini_keyboard_horizontal_padding"
         >
-    <com.android.inputmethod.keyboard.KeyboardView
+    <com.android.inputmethod.keyboard.PopupMiniKeyboardView
             xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-            android:id="@+id/KeyboardView"
+            android:id="@+id/mini_keyboard_view"
             android:layout_alignParentBottom="true"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
diff --git a/java/res/layout/keyboard_popup_honeycomb.xml b/java/res/layout/keyboard_popup_honeycomb.xml
index 96c519dd41..2ddcbdc0c0 100644
--- a/java/res/layout/keyboard_popup_honeycomb.xml
+++ b/java/res/layout/keyboard_popup_honeycomb.xml
@@ -26,9 +26,9 @@
         android:paddingLeft="@dimen/mini_keyboard_horizontal_padding_holo"
         android:paddingRight="@dimen/mini_keyboard_horizontal_padding_holo"
         >
-    <com.android.inputmethod.keyboard.KeyboardView
+    <com.android.inputmethod.keyboard.PopupMiniKeyboardView
             xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-            android:id="@+id/KeyboardView"
+            android:id="@+id/mini_keyboard_view"
             android:layout_alignParentBottom="true"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
diff --git a/java/res/layout/keyboard_popup_stone.xml b/java/res/layout/keyboard_popup_stone.xml
index e6acee374b..94176b244d 100644
--- a/java/res/layout/keyboard_popup_stone.xml
+++ b/java/res/layout/keyboard_popup_stone.xml
@@ -25,9 +25,9 @@
         android:orientation="horizontal"
         android:background="@drawable/keyboard_popup_panel_background"
         >
-    <com.android.inputmethod.keyboard.KeyboardView
+    <com.android.inputmethod.keyboard.PopupMiniKeyboardView
             xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
-            android:id="@+id/KeyboardView"
+            android:id="@+id/mini_keyboard_view"
             android:layout_alignParentBottom="true"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index ef48a4bd2e..8a4b16d7af 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -35,7 +35,7 @@
         <item name="backgroundDimAmount">0.5</item>
         <item name="colorScheme">white</item>
     </style>
-    <style name="MiniKeyboardAnimation">
+    <style name="PopupMiniKeyboardAnimation">
         <item name="android:windowEnterAnimation">@anim/mini_keyboard_fadein</item>
         <item name="android:windowExitAnimation">@anim/mini_keyboard_fadeout</item>
     </style>
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 11476e0693..08e739d5a9 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -35,17 +35,16 @@ import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Message;
-import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.GestureDetector;
-import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
@@ -53,7 +52,6 @@ import android.widget.TextView;
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.WeakHashMap;
 
 /**
@@ -111,24 +109,18 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
     private boolean mInForeground;
     private TextView mPreviewText;
     private int mPreviewTextSizeLarge;
-    private final int[] mOffsetInWindow = new int[2];
     private boolean mShowKeyPreview = true;
     private int mKeyPreviewDisplayedY;
     private final int mDelayBeforePreview;
     private final int mDelayAfterPreview;
     private ViewGroup mPreviewPlacer;
+    private final int[] mCoordinates = new int[2];
 
     // Mini keyboard
-    private PopupWindow mMiniKeyboardWindow;
-    private KeyboardView mMiniKeyboardView;
-    private final WeakHashMap<Key, View> mMiniKeyboardCache = new WeakHashMap<Key, View>();
-    private int mMiniKeyboardOriginX;
-    private int mMiniKeyboardOriginY;
-    private long mMiniKeyboardDisplayedTime;
-    private int[] mWindowOffset;
-    private final float mMiniKeyboardSlideAllowance;
-    private int mMiniKeyboardTrackerId;
-    private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
+    private PopupWindow mPopupWindow;
+    private PopupPanel mPopupMiniKeyboardPanel;
+    private final WeakHashMap<Key, PopupPanel> mPopupPanelCache =
+            new WeakHashMap<Key, PopupPanel>();
 
     /** Listener for {@link KeyboardActionListener}. */
     private KeyboardActionListener mKeyboardActionListener;
@@ -148,7 +140,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
     protected KeyDetector mKeyDetector = new KeyDetector();
 
     // Swipe gesture detector
-    private GestureDetector mGestureDetector;
+    protected GestureDetector mGestureDetector;
     private final SwipeTracker mSwipeTracker = new SwipeTracker();
     private final int mSwipeThreshold;
     private final boolean mDisambiguateSwipe;
@@ -196,29 +188,24 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
 
         @Override
         public void handleMessage(Message msg) {
+            final PointerTracker tracker = (PointerTracker) msg.obj;
             switch (msg.what) {
-                case MSG_SHOW_KEY_PREVIEW:
-                    showKey(msg.arg1, (PointerTracker)msg.obj);
-                    break;
-                case MSG_DISMISS_KEY_PREVIEW:
-                    mPreviewText.setVisibility(View.INVISIBLE);
-                    break;
-                case MSG_REPEAT_KEY: {
-                    final PointerTracker tracker = (PointerTracker)msg.obj;
-                    tracker.onRepeatKey(msg.arg1);
-                    startKeyRepeatTimer(mKeyRepeatInterval, msg.arg1, tracker);
-                    break;
-                }
-                case MSG_LONGPRESS_KEY: {
-                    final PointerTracker tracker = (PointerTracker)msg.obj;
-                    openMiniKeyboardIfRequired(msg.arg1, tracker);
-                    break;
-                }
-                case MSG_LONGPRESS_SHIFT_KEY: {
-                    final PointerTracker tracker = (PointerTracker)msg.obj;
-                    onLongPressShiftKey(tracker);
-                    break;
-                }
+            case MSG_SHOW_KEY_PREVIEW:
+                showKey(msg.arg1, tracker);
+                break;
+            case MSG_DISMISS_KEY_PREVIEW:
+                mPreviewText.setVisibility(View.INVISIBLE);
+                break;
+            case MSG_REPEAT_KEY:
+                tracker.onRepeatKey(msg.arg1);
+                startKeyRepeatTimer(mKeyRepeatInterval, msg.arg1, tracker);
+                break;
+            case MSG_LONGPRESS_KEY:
+                openMiniKeyboardIfRequired(msg.arg1, tracker);
+                break;
+            case MSG_LONGPRESS_SHIFT_KEY:
+                onLongPressShiftKey(tracker);
+                break;
             }
         }
 
@@ -390,12 +377,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         mKeyLabelHorizontalPadding = (int)res.getDimension(
                 R.dimen.key_label_horizontal_alignment_padding);
 
-        mMiniKeyboardWindow = new PopupWindow(context);
-        mMiniKeyboardWindow.setBackgroundDrawable(null);
-        mMiniKeyboardWindow.setAnimationStyle(R.style.MiniKeyboardAnimation);
-        // Allow popup window to be drawn off the screen.
-        mMiniKeyboardWindow.setClippingEnabled(false);
-
         mPaint = new Paint();
         mPaint.setAntiAlias(true);
         mPaint.setTextSize(keyTextSize);
@@ -406,11 +387,8 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         mKeyBackground.getPadding(mPadding);
 
         mSwipeThreshold = (int) (500 * res.getDisplayMetrics().density);
-        // TODO: Refer frameworks/base/core/res/res/values/config.xml
+        // TODO: Refer to frameworks/base/core/res/res/values/config.xml
         mDisambiguateSwipe = res.getBoolean(R.bool.config_swipeDisambiguation);
-        mMiniKeyboardSlideAllowance = res.getDimension(R.dimen.mini_keyboard_slide_allowance);
-        mConfigShowMiniKeyboardAtTouchedPoint = res.getBoolean(
-                R.bool.config_show_mini_keyboard_at_touched_point);
 
         GestureDetector.SimpleOnGestureListener listener =
                 new GestureDetector.SimpleOnGestureListener() {
@@ -530,7 +508,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         mKeyboardChanged = true;
         invalidateAllKeys();
         mKeyDetector.setProximityThreshold(keyboard.getMostCommonKeyWidth());
-        mMiniKeyboardCache.clear();
+        mPopupPanelCache.clear();
     }
 
     /**
@@ -694,7 +672,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         }
 
         // Overlay a dark rectangle to dim the keyboard
-        if (mMiniKeyboardView != null) {
+        if (mPopupMiniKeyboardPanel != null) {
             mPaint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
             canvas.drawRect(0, 0, width, height, mPaint);
         }
@@ -947,8 +925,10 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
             mPreviewPlacer = placer;
         }
         if (placer instanceof FrameLayout) {
+            // Honeycomb or later.
             placer.addView(keyPreview, new FrameLayout.LayoutParams(0, 0));
         } else {
+            // Gingerbread or ealier.
             placer.addView(keyPreview, new LinearLayout.LayoutParams(0, 0));
         }
     }
@@ -956,10 +936,11 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
     // TODO: Introduce minimum duration for displaying key previews
     // TODO: Display up to two key previews when the user presses two keys at the same time
     private void showKey(final int keyIndex, PointerTracker tracker) {
+        final TextView previewText = mPreviewText;
         // If the key preview has no parent view yet, add it to the ViewGroup which can place
         // key preview absolutely in SoftInputWindow.
-        if (mPreviewText.getParent() == null) {
-            addKeyPreview(mPreviewText);
+        if (previewText.getParent() == null) {
+            addKeyPreview(previewText);
         }
 
         final Key key = tracker.getKey(keyIndex);
@@ -968,56 +949,51 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         // WindowManager.BadTokenException.
         if (key == null || !mInForeground)
             return;
+
+        mHandler.cancelAllDismissKeyPreviews();
+
         final int keyDrawX = key.mX + key.mVisualInsetsLeft;
         final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
         // What we show as preview should match what we show on key top in onBufferDraw(). 
         if (key.mLabel != null) {
             // TODO Should take care of temporaryShiftLabel here.
-            mPreviewText.setCompoundDrawables(null, null, null, null);
-            mPreviewText.setText(adjustCase(tracker.getPreviewText(key)));
+            previewText.setCompoundDrawables(null, null, null, null);
+            previewText.setText(adjustCase(tracker.getPreviewText(key)));
             if (key.mLabel.length() > 1) {
-                mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyLetterSize);
-                mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
+                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyLetterSize);
+                previewText.setTypeface(Typeface.DEFAULT_BOLD);
             } else {
-                mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
-                mPreviewText.setTypeface(mKeyLetterStyle);
+                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
+                previewText.setTypeface(mKeyLetterStyle);
             }
         } else {
             final Drawable previewIcon = key.getPreviewIcon();
-            mPreviewText.setCompoundDrawables(null, null, null,
+            previewText.setCompoundDrawables(null, null, null,
                    previewIcon != null ? previewIcon : key.getIcon());
-            mPreviewText.setText(null);
+            previewText.setText(null);
         }
         // Set the preview background state
-        mPreviewText.getBackground().setState(
+        previewText.getBackground().setState(
                 key.mPopupCharacters != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
 
-        mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+        previewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
-        int previewWidth = Math.max(mPreviewText.getMeasuredWidth(), keyDrawWidth
-                + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
+        final int previewWidth = Math.max(previewText.getMeasuredWidth(), keyDrawWidth
+                + previewText.getPaddingLeft() + previewText.getPaddingRight());
         final int previewHeight = mPreviewHeight;
-        final ViewGroup.LayoutParams lp = mPreviewText.getLayoutParams();
-        lp.width = previewWidth;
-        lp.height = previewHeight;
-
-        int previewX = keyDrawX - (previewWidth - keyDrawWidth) / 2;
-        int previewY = key.mY - previewHeight + mPreviewOffset;
-
-        mHandler.cancelAllDismissKeyPreviews();
-        getLocationInWindow(mOffsetInWindow);
-        previewX += mOffsetInWindow[0];
-        previewY += mOffsetInWindow[1];
+        getLocationInWindow(mCoordinates);
+        final int previewX = keyDrawX - (previewWidth - keyDrawWidth) / 2 + mCoordinates[0];
+        final int previewY = key.mY - previewHeight + mCoordinates[1] + mPreviewOffset;
+        // Record key preview position to display mini-keyboard later at the same position
+        mKeyPreviewDisplayedY = previewY;
 
         // Place the key preview.
         // TODO: Adjust position of key previews which touch screen edges
-        if (lp instanceof ViewGroup.MarginLayoutParams) {
-            ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)lp;
-            mlp.setMargins(previewX, previewY, 0, 0);
-        }
-        // Record key preview position to display mini-keyboard later at the same position
-        mKeyPreviewDisplayedY = previewY;
-        mPreviewText.setVisibility(VISIBLE);
+        final MarginLayoutParams lp = (MarginLayoutParams)previewText.getLayoutParams();
+        lp.width = previewWidth;
+        lp.height = previewHeight;
+        lp.setMargins(previewX, previewY, 0, 0);
+        previewText.setVisibility(VISIBLE);
     }
 
     /**
@@ -1044,8 +1020,9 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         if (key == null)
             return;
         mInvalidatedKey = key;
-        mInvalidatedKeyRect.set(0, 0, key.mWidth, key.mHeight);
-        mInvalidatedKeyRect.offset(key.mX + getPaddingLeft(), key.mY + getPaddingTop());
+        final int x = key.mX + getPaddingLeft();
+        final int y = key.mY + getPaddingTop();
+        mInvalidatedKeyRect.set(x, y, x + key.mWidth, y + key.mHeight);
         mDirtyRect.union(mInvalidatedKeyRect);
         onBufferDraw();
         invalidate(mInvalidatedKeyRect);
@@ -1057,13 +1034,12 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
             return false;
         }
 
-        Key parentKey = tracker.getKey(keyIndex);
+        final Key parentKey = tracker.getKey(keyIndex);
         if (parentKey == null)
             return false;
         boolean result = onLongPress(parentKey, tracker);
         if (result) {
             dismissAllKeyPreviews();
-            mMiniKeyboardTrackerId = tracker.mPointerId;
             tracker.onLongPressed(mPointerQueue);
         }
         return result;
@@ -1081,13 +1057,18 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
     }
 
-    private View inflateMiniKeyboardContainer(Key parentKey) {
+    // This default implementation returns a popup mini keyboard panel.
+    // A derived class may return a language switcher popup panel, for instance.
+    protected PopupPanel onCreatePopupPanel(Key parentKey) {
+        if (parentKey.mPopupCharacters == null)
+            return null;
+
         final View container = LayoutInflater.from(getContext()).inflate(mPopupLayout, null);
         if (container == null)
             throw new NullPointerException();
 
-        final KeyboardView miniKeyboardView =
-                (KeyboardView)container.findViewById(R.id.KeyboardView);
+        final PopupMiniKeyboardView miniKeyboardView =
+                (PopupMiniKeyboardView)container.findViewById(R.id.mini_keyboard_view);
         miniKeyboardView.setOnKeyboardActionListener(new KeyboardActionListener() {
             @Override
             public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
@@ -1120,10 +1101,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
                 mKeyboardActionListener.onRelease(primaryCode, withSliding);
             }
         });
-        // Override default ProximityKeyDetector.
-        miniKeyboardView.mKeyDetector = new MiniKeyboardKeyDetector(mMiniKeyboardSlideAllowance);
-        // Remove gesture detector on mini-keyboard
-        miniKeyboardView.mGestureDetector = null;
 
         final Keyboard keyboard = new MiniKeyboardBuilder(this, mKeyboard.getPopupKeyboardResId(),
                 parentKey).build();
@@ -1132,87 +1109,39 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
                 MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
 
-        return container;
-    }
-
-    private static boolean isOneRowKeys(List<Key> keys) {
-        if (keys.size() == 0) return false;
-        final int edgeFlags = keys.get(0).mEdgeFlags;
-        // HACK: The first key of mini keyboard which was inflated from xml and has multiple rows,
-        // does not have both top and bottom edge flags on at the same time.  On the other hand,
-        // the first key of mini keyboard that was created with popupCharacters must have both top
-        // and bottom edge flags on.
-        // When you want to use one row mini-keyboard from xml file, make sure that the row has
-        // both top and bottom edge flags set.
-        return (edgeFlags & Keyboard.EDGE_TOP) != 0
-                && (edgeFlags & Keyboard.EDGE_BOTTOM) != 0;
+        return miniKeyboardView;
     }
 
     /**
-     * Called when a key is long pressed. By default this will open any mini keyboard associated
-     * with this key through the attributes popupLayout and popupCharacters.
+     * 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) {
-        if (parentKey.mPopupCharacters == null)
-            return false;
-
-        View container = mMiniKeyboardCache.get(parentKey);
-        if (container == null) {
-            container = inflateMiniKeyboardContainer(parentKey);
-            mMiniKeyboardCache.put(parentKey, container);
-        }
-        mMiniKeyboardView = (KeyboardView)container.findViewById(R.id.KeyboardView);
-        final MiniKeyboard miniKeyboard = (MiniKeyboard)mMiniKeyboardView.getKeyboard();
-
-        if (mWindowOffset == null) {
-            mWindowOffset = new int[2];
-            getLocationInWindow(mWindowOffset);
+        PopupPanel popupPanel = mPopupPanelCache.get(parentKey);
+        if (popupPanel == null) {
+            popupPanel = onCreatePopupPanel(parentKey);
+            if (popupPanel == null)
+                return false;
+            mPopupPanelCache.put(parentKey, popupPanel);
         }
-        final int pointX = (mConfigShowMiniKeyboardAtTouchedPoint) ? tracker.getLastX()
-                : parentKey.mX + parentKey.mWidth / 2;
-        final int miniKeyboardX = pointX - miniKeyboard.getDefaultCoordX()
-                - container.getPaddingLeft()
-                + getPaddingLeft() + mWindowOffset[0];
-        final int miniKeyboardY = parentKey.mY - mKeyboard.getVerticalGap()
-                - (container.getMeasuredHeight() - container.getPaddingBottom())
-                + getPaddingTop() + mWindowOffset[1];
-        final int x = miniKeyboardX;
-        final int y = mShowKeyPreview && isOneRowKeys(miniKeyboard.getKeys())
-                ? mKeyPreviewDisplayedY : miniKeyboardY;
-
-        mMiniKeyboardOriginX = x + container.getPaddingLeft() - mWindowOffset[0];
-        mMiniKeyboardOriginY = y + container.getPaddingTop() - mWindowOffset[1];
-        if (miniKeyboard.setShifted(
-                mKeyboard == null ? false : mKeyboard.isShiftedOrShiftLocked())) {
-            mMiniKeyboardView.invalidateAllKeys();
+        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);
         }
-        // Mini keyboard needs no pop-up key preview displayed.
-        mMiniKeyboardView.setKeyPreviewEnabled(false);
-        mMiniKeyboardWindow.setContentView(container);
-        mMiniKeyboardWindow.setWidth(container.getMeasuredWidth());
-        mMiniKeyboardWindow.setHeight(container.getMeasuredHeight());
-        mMiniKeyboardWindow.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
-
-        // Inject down event on the key to mini keyboard.
-        final long eventTime = SystemClock.uptimeMillis();
-        mMiniKeyboardDisplayedTime = eventTime;
-        final MotionEvent downEvent = generateMiniKeyboardMotionEvent(MotionEvent.ACTION_DOWN,
-                pointX, parentKey.mY + parentKey.mHeight / 2, eventTime);
-        mMiniKeyboardView.onTouchEvent(downEvent);
-        downEvent.recycle();
+        mPopupMiniKeyboardPanel = popupPanel;
+        popupPanel.showPanel(this, parentKey, tracker, mKeyPreviewDisplayedY, mPopupWindow);
 
         invalidateAllKeys();
         return true;
     }
 
-    private MotionEvent generateMiniKeyboardMotionEvent(int action, int x, int y, long eventTime) {
-        return MotionEvent.obtain(mMiniKeyboardDisplayedTime, eventTime, action,
-                    x - mMiniKeyboardOriginX, y - mMiniKeyboardOriginY, 0);
-    }
-
     private PointerTracker getPointerTracker(final int id) {
         final ArrayList<PointerTracker> pointers = mPointerTrackers;
         final KeyboardActionListener listener = mKeyboardActionListener;
@@ -1232,8 +1161,8 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
     }
 
     public boolean isInSlidingKeyInput() {
-        if (mMiniKeyboardView != null) {
-            return mMiniKeyboardView.isInSlidingKeyInput();
+        if (mPopupMiniKeyboardPanel != null) {
+            return mPopupMiniKeyboardPanel.isInSlidingKeyInput();
         } else {
             return mPointerQueue.isInSlidingKeyInput();
         }
@@ -1264,7 +1193,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         // Gesture detector must be enabled only when mini-keyboard is not on the screen and
         // accessibility is not enabled.
         // TODO: Reconcile gesture detection and accessibility features.
-        if (mMiniKeyboardView == null && !mIsAccessibilityEnabled
+        if (mPopupMiniKeyboardPanel == null && !mIsAccessibilityEnabled
                 && mGestureDetector != null && mGestureDetector.onTouchEvent(me)) {
             dismissAllKeyPreviews();
             mHandler.cancelKeyTimers();
@@ -1277,19 +1206,10 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         final int x = (int)me.getX(index);
         final int y = (int)me.getY(index);
 
-        // Needs to be called after the gesture detector gets a turn, as it may have
-        // displayed the mini keyboard
-        if (mMiniKeyboardView != null) {
-            final int miniKeyboardPointerIndex = me.findPointerIndex(mMiniKeyboardTrackerId);
-            if (miniKeyboardPointerIndex >= 0 && miniKeyboardPointerIndex < pointerCount) {
-                final int miniKeyboardX = (int)me.getX(miniKeyboardPointerIndex);
-                final int miniKeyboardY = (int)me.getY(miniKeyboardPointerIndex);
-                MotionEvent translated = generateMiniKeyboardMotionEvent(action,
-                        miniKeyboardX, miniKeyboardY, eventTime);
-                mMiniKeyboardView.onTouchEvent(translated);
-                translated.recycle();
-            }
-            return true;
+        // Needs to be called after the gesture detector gets a turn, as it may have displayed the
+        // mini keyboard
+        if (mPopupMiniKeyboardPanel != null) {
+            return mPopupMiniKeyboardPanel.onTouchEvent(me);
         }
 
         if (mHandler.isInKeyRepeat()) {
@@ -1370,7 +1290,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
 
         dismissMiniKeyboard();
         mDirtyRect.union(0, 0, getWidth(), getHeight());
-        mMiniKeyboardCache.clear();
+        mPopupPanelCache.clear();
         requestLayout();
     }
 
@@ -1385,21 +1305,17 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         closing();
     }
 
-    private void dismissMiniKeyboard() {
-        if (mMiniKeyboardWindow.isShowing()) {
-            mMiniKeyboardWindow.dismiss();
-            mMiniKeyboardView = null;
-            mMiniKeyboardOriginX = 0;
-            mMiniKeyboardOriginY = 0;
+    private boolean dismissMiniKeyboard() {
+        if (mPopupWindow != null && mPopupWindow.isShowing()) {
+            mPopupWindow.dismiss();
+            mPopupMiniKeyboardPanel = null;
             invalidateAllKeys();
+            return true;
         }
+        return false;
     }
 
     public boolean handleBack() {
-        if (mMiniKeyboardWindow.isShowing()) {
-            dismissMiniKeyboard();
-            return true;
-        }
-        return false;
+        return dismissMiniKeyboard();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
index 3b1408ccf1..5dde15e940 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
@@ -18,6 +18,8 @@ package com.android.inputmethod.keyboard;
 
 import android.content.Context;
 
+import java.util.List;
+
 public class MiniKeyboard extends Keyboard {
     private int mDefaultKeyCoordX;
 
@@ -32,4 +34,19 @@ public class MiniKeyboard extends Keyboard {
     public int getDefaultCoordX() {
         return mDefaultKeyCoordX;
     }
+
+    public boolean isOneRowKeyboard() {
+        final List<Key> keys = getKeys();
+        if (keys.size() == 0) return false;
+        final int edgeFlags = keys.get(0).mEdgeFlags;
+        // HACK: The first key of mini keyboard which was inflated from xml and has multiple rows,
+        // does not have both top and bottom edge flags on at the same time.  On the other hand,
+        // the first key of mini keyboard that was created with popupCharacters must have both top
+        // and bottom edge flags on.
+        // When you want to use one row mini-keyboard from xml file, make sure that the row has
+        // both top and bottom edge flags set.
+        return (edgeFlags & Keyboard.EDGE_TOP) != 0
+                && (edgeFlags & Keyboard.EDGE_BOTTOM) != 0;
+
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
new file mode 100644
index 0000000000..12031f1eaf
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
@@ -0,0 +1,124 @@
+/*
+ * 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 com.android.inputmethod.latin.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.PopupWindow;
+
+/**
+ * A view that renders a virtual {@link MiniKeyboard}. It handles rendering of keys and detecting
+ * key presses and touch movements.
+ */
+public class PopupMiniKeyboardView extends KeyboardView implements PopupPanel {
+    private final int[] mCoordinates = new int[2];
+    private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
+
+    private int mOriginX;
+    private int mOriginY;
+    private int mTrackerId;
+    private long mDownTime;
+
+    public PopupMiniKeyboardView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.keyboardViewStyle);
+    }
+
+    public PopupMiniKeyboardView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        final Resources res = context.getResources();
+        mConfigShowMiniKeyboardAtTouchedPoint = res.getBoolean(
+                R.bool.config_show_mini_keyboard_at_touched_point);
+        // Override default ProximityKeyDetector.
+        mKeyDetector = new MiniKeyboardKeyDetector(res.getDimension(
+                R.dimen.mini_keyboard_slide_allowance));
+        // Remove gesture detector on mini-keyboard
+        mGestureDetector = null;
+        setKeyPreviewEnabled(false);
+    }
+
+    @Override
+    public void setKeyPreviewEnabled(boolean previewEnabled) {
+        // Mini keyboard needs no pop-up key preview displayed.
+        super.setKeyPreviewEnabled(false);
+    }
+
+    @Override
+    public void showPanel(KeyboardView parentKeyboardView, Key parentKey,
+            PointerTracker tracker, int keyPreviewY, PopupWindow window) {
+        final View container = (View)getParent();
+        final MiniKeyboard miniKeyboard = (MiniKeyboard)getKeyboard();
+        final Keyboard parentKeyboard = parentKeyboardView.getKeyboard();
+
+        parentKeyboardView.getLocationInWindow(mCoordinates);
+        final int pointX = (mConfigShowMiniKeyboardAtTouchedPoint) ? tracker.getLastX()
+                : parentKey.mX + parentKey.mWidth / 2;
+        final int pointY = parentKey.mY;
+        final int miniKeyboardX = pointX - miniKeyboard.getDefaultCoordX()
+                - container.getPaddingLeft()
+                + parentKeyboardView.getPaddingLeft() + mCoordinates[0];
+        final int miniKeyboardY = pointY - parentKeyboard.getVerticalGap()
+                - (container.getMeasuredHeight() - container.getPaddingBottom())
+                + parentKeyboardView.getPaddingTop() + mCoordinates[1];
+        final int x = miniKeyboardX;
+        final int y = parentKeyboardView.isKeyPreviewEnabled() && miniKeyboard.isOneRowKeyboard()
+                ? keyPreviewY : miniKeyboardY;
+
+        if (miniKeyboard.setShifted(parentKeyboard.isShiftedOrShiftLocked())) {
+            invalidateAllKeys();
+        }
+        window.setContentView(container);
+        window.setWidth(container.getMeasuredWidth());
+        window.setHeight(container.getMeasuredHeight());
+        window.showAtLocation(parentKeyboardView, Gravity.NO_GRAVITY, x, y);
+
+        mOriginX = x + container.getPaddingLeft() - mCoordinates[0];
+        mOriginY = y + container.getPaddingTop() - mCoordinates[1];
+        mTrackerId = tracker.mPointerId;
+        mDownTime = SystemClock.uptimeMillis();
+
+        // Inject down event on the key to mini keyboard.
+        final MotionEvent downEvent = translateMotionEvent(MotionEvent.ACTION_DOWN, pointX,
+                pointY + parentKey.mHeight / 2, mDownTime);
+        onTouchEvent(downEvent);
+        downEvent.recycle();
+    }
+
+    private MotionEvent translateMotionEvent(int action, float x, float y, long eventTime) {
+        return MotionEvent.obtain(mDownTime, eventTime, action, x - mOriginX, y - mOriginY, 0);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent me) {
+        final int index = me.getActionIndex();
+        final int id = me.getPointerId(index);
+        if (id == mTrackerId) {
+            final MotionEvent translated = translateMotionEvent(me.getAction(), me.getX(index),
+                    me.getY(index), me.getEventTime());
+            super.onTouchEvent(translated);
+            translated.recycle();
+        }
+        return true;
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/PopupPanel.java b/java/src/com/android/inputmethod/keyboard/PopupPanel.java
new file mode 100644
index 0000000000..6f2b161481
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/PopupPanel.java
@@ -0,0 +1,46 @@
+/*
+ * 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.view.MotionEvent;
+import android.widget.PopupWindow;
+
+public interface PopupPanel {
+    /**
+     * Show popup panel.
+     * @param parentKeyboardView the parent KeyboardView that has the parent key.
+     * @param parentKey the parent key that is the source of this popup panel
+     * @param tracker the pointer tracker that pressesd the parent key
+     * @param keyPreviewY the Y-coordinate of key preview
+     * @param window PopupWindow to be used to show this popup panel
+     */
+    public void showPanel(KeyboardView parentKeyboardView, Key parentKey,
+            PointerTracker tracker, int keyPreviewY, PopupWindow window);
+
+    /**
+     * Check if the pointer is in siding key input mode.
+     * @return true if the pointer is sliding key input mode.
+     */
+    public boolean isInSlidingKeyInput();
+
+    /**
+     * The motion event handler.
+     * @param me the MotionEvent to be processed.
+     * @return true if the motion event is processed and should be consumed.
+     */
+    public boolean onTouchEvent(MotionEvent me);
+}
-- 
GitLab