Support sliding pop-up mini-keyboard input

TODO:
- Remove close button on mini-keyboard
- Dismiss mini-keyboard when finger leave the screen while no key is selected

This change also renames some instance variables to have more meaningful name.

Bug: 2959169
Change-Id: I9fd79116a647d7be82415c6e9e7cdaf6edcb2bf6
diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
index 48cccea..a58c6e5 100644
--- a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
+++ b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
@@ -185,17 +185,20 @@
     private boolean mShowTouchPoints = true;
     private int mPopupPreviewX;
     private int mPopupPreviewY;
+    private int mPopupPreviewOffsetX;
+    private int mPopupPreviewOffsetY;
     private int mWindowY;
 
     // Popup mini keyboard
-    private PopupWindow mPopupKeyboard;
+    private PopupWindow mMiniKeyboardPopup;
     private View mMiniKeyboardContainer;
     private LatinKeyboardBaseView mMiniKeyboard;
     private boolean mMiniKeyboardOnScreen;
-    private View mPopupParent;
-    private int mMiniKeyboardOffsetX;
-    private int mMiniKeyboardOffsetY;
+    private View mMiniKeyboardParent;
     private Map<Key,View> mMiniKeyboardCache;
+    private int mMiniKeyboardOriginX;
+    private int mMiniKeyboardOriginY;
+    private long mMiniKeyboardPopupTime;
     private int[] mWindowOffset;
 
     /** Listener for {@link OnKeyboardActionListener}. */
@@ -445,10 +448,10 @@
             mShowPreview = false;
         }
         mPreviewPopup.setTouchable(false);
-        mPopupParent = this;
+        mMiniKeyboardParent = this;
 
-        mPopupKeyboard = new PopupWindow(context);
-        mPopupKeyboard.setBackgroundDrawable(null);
+        mMiniKeyboardPopup = new PopupWindow(context);
+        mMiniKeyboardPopup.setBackgroundDrawable(null);
 
         mPaint = new Paint();
         mPaint.setAntiAlias(true);
@@ -615,12 +618,12 @@
     }
 
     public void setPopupParent(View v) {
-        mPopupParent = v;
+        mMiniKeyboardParent = v;
     }
 
     public void setPopupOffset(int x, int y) {
-        mMiniKeyboardOffsetX = x;
-        mMiniKeyboardOffsetY = y;
+        mPopupPreviewOffsetX = x;
+        mPopupPreviewOffsetY = y;
         if (mPreviewPopup.isShowing()) {
             mPreviewPopup.dismiss();
         }
@@ -896,8 +899,8 @@
         if (mOffsetInWindow == null) {
             mOffsetInWindow = new int[2];
             getLocationInWindow(mOffsetInWindow);
-            mOffsetInWindow[0] += mMiniKeyboardOffsetX; // Offset may be zero
-            mOffsetInWindow[1] += mMiniKeyboardOffsetY; // Offset may be zero
+            mOffsetInWindow[0] += mPopupPreviewOffsetX; // Offset may be zero
+            mOffsetInWindow[1] += mPopupPreviewOffsetY; // Offset may be zero
             int[] mWindowLocation = new int[2];
             getLocationOnScreen(mWindowLocation);
             mWindowY = mWindowLocation[1];
@@ -926,7 +929,7 @@
         } else {
             previewPopup.setWidth(popupWidth);
             previewPopup.setHeight(popupHeight);
-            previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
+            previewPopup.showAtLocation(mMiniKeyboardParent, Gravity.NO_GRAVITY,
                     mPopupPreviewX, mPopupPreviewY);
         }
         mPreviewText.setVisibility(VISIBLE);
@@ -974,6 +977,13 @@
         boolean result = onLongPress(popupKey);
         if (result) {
             dismissKeyPreview();
+
+            long eventTime = tracker.getDownTime();
+            mMiniKeyboardPopupTime = eventTime;
+            MotionEvent downEvent = generateMiniKeyboardMotionEvent(MotionEvent.ACTION_DOWN,
+                    tracker.getLastX(), tracker.getLastY(), eventTime);
+            mMiniKeyboard.onTouchEvent(downEvent);
+            downEvent.recycle();
         }
         return result;
     }
@@ -1020,7 +1030,7 @@
                         mKeyboardActionListener.onRelease(primaryCode);
                     }
                 });
-                //mInputView.setSuggest(mSuggest);
+
                 Keyboard keyboard;
                 if (popupKey.popupCharacters != null) {
                     keyboard = new Keyboard(getContext(), popupKeyboardId,
@@ -1043,20 +1053,25 @@
                 mWindowOffset = new int[2];
                 getLocationInWindow(mWindowOffset);
             }
-            int popupX = popupKey.x + getPaddingLeft();
+            int popupX = popupKey.x + popupKey.width + getPaddingLeft();
             int popupY = popupKey.y + getPaddingTop();
-            popupX = popupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
-            popupY = popupY - mMiniKeyboardContainer.getMeasuredHeight();
-            final int x = popupX + mMiniKeyboardContainer.getPaddingRight() + mWindowOffset[0];
-            final int y = popupY + mMiniKeyboardContainer.getPaddingBottom() + mWindowOffset[1];
-            mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
+            popupX -= mMiniKeyboardContainer.getMeasuredWidth();
+            popupY -= mMiniKeyboardContainer.getMeasuredHeight();
+            popupX += mWindowOffset[0];
+            popupY += mWindowOffset[1];
+            final int x = popupX + mMiniKeyboardContainer.getPaddingRight();
+            final int y = popupY + mMiniKeyboardContainer.getPaddingBottom();
+            mMiniKeyboardOriginX = (x < 0 ? 0 : x) + mMiniKeyboardContainer.getPaddingLeft();
+            mMiniKeyboardOriginY = y + mMiniKeyboardContainer.getPaddingTop();
+            mMiniKeyboard.setPopupOffset((x < 0) ? 0 : x, y);
             mMiniKeyboard.setShifted(isShifted());
-            mPopupKeyboard.setContentView(mMiniKeyboardContainer);
-            mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
-            mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
-            mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
+            mMiniKeyboardPopup.setContentView(mMiniKeyboardContainer);
+            mMiniKeyboardPopup.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
+            mMiniKeyboardPopup.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
+            mMiniKeyboardPopup.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
             mMiniKeyboardOnScreen = true;
-            //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
+
+            // TODO: down event?
             invalidateAllKeys();
             return true;
         }
@@ -1068,6 +1083,11 @@
         return mMiniKeyboardOnScreen;
     }
 
+    private MotionEvent generateMiniKeyboardMotionEvent(int action, int x, int y, long eventTime) {
+        return MotionEvent.obtain(mMiniKeyboardPopupTime, eventTime, action,
+                x - mMiniKeyboardOriginX, y - mMiniKeyboardOriginY, 0);
+    }
+
     private PointerTracker getPointerTracker(final int id) {
         final ArrayList<PointerTracker> pointers = mPointerTrackers;
         final Key[] keys = mKeys;
@@ -1105,7 +1125,11 @@
 
         // Needs to be called after the gesture detector gets a turn, as it may have
         // displayed the mini keyboard
-        if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
+        if (mMiniKeyboardOnScreen) {
+            MotionEvent translated = generateMiniKeyboardMotionEvent(action, (int)me.getX(),
+                    (int)me.getY(), eventTime);
+            mMiniKeyboard.onTouchEvent(translated);
+            translated.recycle();
             return true;
         }
 
@@ -1209,15 +1233,17 @@
     }
 
     public void dismissPopupKeyboard() {
-        if (mPopupKeyboard.isShowing()) {
-            mPopupKeyboard.dismiss();
+        if (mMiniKeyboardPopup.isShowing()) {
+            mMiniKeyboardPopup.dismiss();
             mMiniKeyboardOnScreen = false;
+            mMiniKeyboardOriginX = 0;
+            mMiniKeyboardOriginY = 0;
             invalidateAllKeys();
         }
     }
 
     public boolean handleBack() {
-        if (mPopupKeyboard.isShowing()) {
+        if (mMiniKeyboardPopup.isShowing()) {
             dismissPopupKeyboard();
             return true;
         }
diff --git a/java/src/com/android/inputmethod/latin/PointerTracker.java b/java/src/com/android/inputmethod/latin/PointerTracker.java
index 5b5cbe5..cb45e21 100644
--- a/java/src/com/android/inputmethod/latin/PointerTracker.java
+++ b/java/src/com/android/inputmethod/latin/PointerTracker.java
@@ -61,6 +61,7 @@
     private int mCurrentKey = NOT_A_KEY;
     private int mStartX;
     private int mStartY;
+    private long mDownTime;
 
     // for move de-bouncing
     private int mLastCodeX;
@@ -144,6 +145,7 @@
         mCurrentKey = keyIndex;
         mStartX = x;
         mStartY = y;
+        mDownTime = eventTime;
         startMoveDebouncing(x, y);
         startTimeDebouncing(eventTime);
         checkMultiTap(eventTime, keyIndex);
@@ -181,7 +183,19 @@
                 mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this);
             }
         } else {
-            mHandler.cancelLongPressTimer();
+            if (mCurrentKey != NOT_A_KEY) {
+                updateTimeDebouncing(eventTime);
+                mCurrentKey = keyIndex;
+                mHandler.cancelLongPressTimer();
+            } else if (isMinorMoveBounce(x, y, keyIndex, mCurrentKey)) {
+                updateTimeDebouncing(eventTime);
+            } else {
+                resetMultiTap();
+                resetTimeDebouncing(eventTime, mCurrentKey);
+                resetMoveDebouncing();
+                mCurrentKey = keyIndex;
+                mHandler.cancelLongPressTimer();
+            }
         }
         /*
          * While time debouncing is in effect, mCurrentKey holds the new key and this tracker
@@ -252,6 +266,10 @@
         return mLastY;
     }
 
+    public long getDownTime() {
+        return mDownTime;
+    }
+
     // These package scope methods are only for debugging purpose.
     /* package */ int getStartX() {
         return mStartX;