Refactor PointerTracker class

Change-Id: Id6f69e82119a9a3f618d95443a3ecc766abab08a
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 395783e..80af37f 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -23,10 +23,13 @@
 import android.util.Log;
 import android.view.MotionEvent;
 
+import java.util.Arrays;
+
 public class PointerTracker {
-    private static final String TAG = "PointerTracker";
-    private static final boolean DEBUG = false;
-    private static final boolean DEBUG_MOVE = false;
+    private static final String TAG = PointerTracker.class.getSimpleName();
+    private static final boolean DEBUG_EVENT = false;
+    private static final boolean DEBUG_MOVE_EVENT = false;
+    private static final boolean DEBUG_LISTENER = false;
 
     public interface UIProxy {
         public void invalidateKey(Key key);
@@ -49,7 +52,7 @@
     private final UIProxy mProxy;
     private final UIHandler mHandler;
     private final KeyDetector mKeyDetector;
-    private KeyboardActionListener mListener;
+    private KeyboardActionListener mListener = EMPTY_LISTENER;
     private final KeyboardSwitcher mKeyboardSwitcher;
     private final boolean mHasDistinctMultitouch;
     private final boolean mConfigSlidingKeyInputEnabled;
@@ -58,7 +61,7 @@
     private Key[] mKeys;
     private int mKeyHysteresisDistanceSquared = -1;
 
-    private final KeyState mKeyState;
+    private final PointerTrackerKeyState mKeyState;
 
     // true if event is already translated to a key action (long press or mini-keyboard)
     private boolean mKeyAlreadyProcessed;
@@ -79,94 +82,27 @@
     // pressed key
     private int mPreviousKey = NOT_A_KEY;
 
-    // This class keeps track of a key index and a position where this pointer is.
-    private static class KeyState {
-        private final KeyDetector mKeyDetector;
-
-        // The position and time at which first down event occurred.
-        private int mStartX;
-        private int mStartY;
-        private long mDownTime;
-
-        // The current key index where this pointer is.
-        private int mKeyIndex = NOT_A_KEY;
-        // The position where mKeyIndex was recognized for the first time.
-        private int mKeyX;
-        private int mKeyY;
-
-        // Last pointer position.
-        private int mLastX;
-        private int mLastY;
-
-        public KeyState(KeyDetector keyDetecor) {
-            mKeyDetector = keyDetecor;
-        }
-
-        public int getKeyIndex() {
-            return mKeyIndex;
-        }
-
-        public int getKeyX() {
-            return mKeyX;
-        }
-
-        public int getKeyY() {
-            return mKeyY;
-        }
-
-        public int getStartX() {
-            return mStartX;
-        }
-
-        public int getStartY() {
-            return mStartY;
-        }
-
-        public long getDownTime() {
-            return mDownTime;
-        }
-
-        public int getLastX() {
-            return mLastX;
-        }
-
-        public int getLastY() {
-            return mLastY;
-        }
-
-        public int onDownKey(int x, int y, long eventTime) {
-            mStartX = x;
-            mStartY = y;
-            mDownTime = eventTime;
-
-            return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
-        }
-
-        private int onMoveKeyInternal(int x, int y) {
-            mLastX = x;
-            mLastY = y;
-            return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
-        }
-
-        public int onMoveKey(int x, int y) {
-            return onMoveKeyInternal(x, y);
-        }
-
-        public int onMoveToNewKey(int keyIndex, int x, int y) {
-            mKeyIndex = keyIndex;
-            mKeyX = x;
-            mKeyY = y;
-            return keyIndex;
-        }
-
-        public int onUpKey(int x, int y) {
-            return onMoveKeyInternal(x, y);
-        }
-
-        public void onSetKeyboard() {
-            mKeyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(mKeyX, mKeyY, null);
-        }
-    }
+    // Empty {@link KeyboardActionListener}
+    private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener() {
+        @Override
+        public void onPress(int primaryCode) {}
+        @Override
+        public void onRelease(int primaryCode) {}
+        @Override
+        public void onKey(int primaryCode, int[] keyCodes, int x, int y) {}
+        @Override
+        public void onText(CharSequence text) {}
+        @Override
+        public void onCancel() {}
+        @Override
+        public void swipeLeft() {}
+        @Override
+        public void swipeRight() {}
+        @Override
+        public void swipeDown() {}
+        @Override
+        public void swipeUp() {}
+    };
 
     public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy,
             Resources res) {
@@ -177,7 +113,7 @@
         mHandler = handler;
         mKeyDetector = keyDetector;
         mKeyboardSwitcher = KeyboardSwitcher.getInstance();
-        mKeyState = new KeyState(keyDetector);
+        mKeyState = new PointerTrackerKeyState(keyDetector);
         mHasDistinctMultitouch = proxy.hasDistinctMultitouch();
         mConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled);
         mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start);
@@ -191,6 +127,37 @@
         mListener = listener;
     }
 
+    private void callListenerOnPress(int primaryCode) {
+        if (DEBUG_LISTENER)
+            Log.d(TAG, "onPress    : " + keyCodePrintable(primaryCode));
+        mListener.onPress(primaryCode);
+    }
+
+    private void callListenerOnKey(int primaryCode, int[] keyCodes, int x, int y) {
+        if (DEBUG_LISTENER)
+            Log.d(TAG, "onKey      : " + keyCodePrintable(primaryCode)
+                    + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y);
+        mListener.onKey(primaryCode, keyCodes, x, y);
+    }
+
+    private void callListenerOnText(CharSequence text) {
+        if (DEBUG_LISTENER)
+            Log.d(TAG, "onText     : text=" + text);
+        mListener.onText(text);
+    }
+
+    private void callListenerOnRelease(int primaryCode) {
+        if (DEBUG_LISTENER)
+            Log.d(TAG, "onRelease  : " + keyCodePrintable(primaryCode));
+        mListener.onRelease(primaryCode);
+    }
+
+    private void callListenerOnCancel() {
+        if (DEBUG_LISTENER)
+            Log.d(TAG, "onCancel");
+        mListener.onCancel();
+    }
+
     public void setKeyboard(Keyboard keyboard, Key[] keys, float keyHysteresisDistance) {
         if (keyboard == null || keys == null || keyHysteresisDistance < 0)
             throw new IllegalArgumentException();
@@ -209,15 +176,16 @@
         return isValidKeyIndex(keyIndex) ? mKeys[keyIndex] : null;
     }
 
-    private boolean isModifierInternal(int keyIndex) {
-        Key key = getKey(keyIndex);
-        if (key == null)
-            return false;
-        int primaryCode = key.mCodes[0];
+    private static boolean isModifierCode(int primaryCode) {
         return primaryCode == Keyboard.CODE_SHIFT
                 || primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL;
     }
 
+    private boolean isModifierInternal(int keyIndex) {
+        final Key key = getKey(keyIndex);
+        return key == null ? false : isModifierCode(key.mCodes[0]);
+    }
+
     public boolean isModifier() {
         return isModifierInternal(mKeyState.getKeyIndex());
     }
@@ -281,23 +249,21 @@
     }
 
     public void onDownEvent(int x, int y, long eventTime) {
-        if (DEBUG)
-            debugLog("onDownEvent:", x, y);
+        if (DEBUG_EVENT)
+            printTouchEvent("onDownEvent:", x, y, eventTime);
         int keyIndex = mKeyState.onDownKey(x, y, eventTime);
         // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
-        // form modifier key, or 3) this pointer is on mini-keyboard.
+        // from modifier key, or 3) this pointer is on mini-keyboard.
         mIsAllowedSlidingKeyInput = mConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex)
                 || mKeyDetector instanceof MiniKeyboardKeyDetector;
         mKeyAlreadyProcessed = false;
         mIsRepeatableKey = false;
         checkMultiTap(eventTime, keyIndex);
-        if (mListener != null) {
-            if (isValidKeyIndex(keyIndex)) {
-                mListener.onPress(mKeys[keyIndex].mCodes[0]);
-                // This onPress call may have changed keyboard layout and have updated mKeyIndex.
-                // If that's the case, mKeyIndex has been updated in setKeyboard().
-                keyIndex = mKeyState.getKeyIndex();
-            }
+        if (isValidKeyIndex(keyIndex)) {
+            callListenerOnPress(mKeys[keyIndex].mCodes[0]);
+            // This onPress call may have changed keyboard layout and have updated mKeyIndex.
+            // If that's the case, mKeyIndex has been updated in setKeyboard().
+            keyIndex = mKeyState.getKeyIndex();
         }
         if (isValidKeyIndex(keyIndex)) {
             if (mKeys[keyIndex].mRepeatable) {
@@ -310,32 +276,29 @@
         showKeyPreviewAndUpdateKeyGraphics(keyIndex);
     }
 
-    public void onMoveEvent(int x, int y, @SuppressWarnings("unused") long eventTime) {
-        if (DEBUG_MOVE)
-            debugLog("onMoveEvent:", x, y);
+    public void onMoveEvent(int x, int y, long eventTime) {
+        if (DEBUG_MOVE_EVENT)
+            printTouchEvent("onMoveEvent:", x, y, eventTime);
         if (mKeyAlreadyProcessed)
             return;
-        final KeyState keyState = mKeyState;
+        final PointerTrackerKeyState keyState = mKeyState;
         final int keyIndex = keyState.onMoveKey(x, y);
         final Key oldKey = getKey(keyState.getKeyIndex());
         if (isValidKeyIndex(keyIndex)) {
             if (oldKey == null) {
                 // The pointer has been slid in to the new key, but the finger was not on any keys.
                 // In this case, we must call onPress() to notify that the new key is being pressed.
-                if (mListener != null)
-                    mListener.onPress(getKey(keyIndex).mCodes[0]);
+                callListenerOnPress(getKey(keyIndex).mCodes[0]);
                 keyState.onMoveToNewKey(keyIndex, x, y);
                 startLongPressTimer(keyIndex);
             } else if (!isMinorMoveBounce(x, y, keyIndex)) {
                 // The pointer has been slid in to the new key from the previous key, we must call
                 // onRelease() first to notify that the previous key has been released, then call
                 // onPress() to notify that the new key is being pressed.
-                if (mListener != null)
-                    mListener.onRelease(oldKey.mCodes[0]);
+                callListenerOnRelease(oldKey.mCodes[0]);
                 if (mIsAllowedSlidingKeyInput) {
                     resetMultiTap();
-                    if (mListener != null)
-                        mListener.onPress(getKey(keyIndex).mCodes[0]);
+                    callListenerOnPress(getKey(keyIndex).mCodes[0]);
                     keyState.onMoveToNewKey(keyIndex, x, y);
                     startLongPressTimer(keyIndex);
                 } else {
@@ -345,12 +308,14 @@
                 }
             }
         } else {
-            // TODO: we should check isMinorMoveDebounce() first.
-            if (oldKey != null) {
+            if (!isMinorMoveBounce(x, y, keyIndex)) {
+                resetMultiTap();
+                keyState.onMoveToNewKey(keyIndex, x ,y);
+                mHandler.cancelLongPressTimers();
+            } else if (oldKey != null) {
                 // The pointer has been slid out from the previous key, we must call onRelease() to
                 // notify that the previous key has been released.
-                if (mListener != null)
-                    mListener.onRelease(oldKey.mCodes[0]);
+                callListenerOnRelease(oldKey.mCodes[0]);
                 if (mIsAllowedSlidingKeyInput) {
                     keyState.onMoveToNewKey(keyIndex, x ,y);
                     mHandler.cancelLongPressTimers();
@@ -359,10 +324,6 @@
                     showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
                     return;
                 }
-            } else if (!isMinorMoveBounce(x, y, keyIndex)) {
-                resetMultiTap();
-                keyState.onMoveToNewKey(keyIndex, x ,y);
-                mHandler.cancelLongPressTimers();
             }
         }
         showKeyPreviewAndUpdateKeyGraphics(mKeyState.getKeyIndex());
@@ -371,19 +332,20 @@
     public void onUpEvent(int pointX, int pointY, long eventTime) {
         int x = pointX;
         int y = pointY;
-        if (DEBUG)
-            debugLog("onUpEvent  :", x, y);
+        if (DEBUG_EVENT)
+            printTouchEvent("onUpEvent  :", x, y, eventTime);
         showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
         if (mKeyAlreadyProcessed)
             return;
         mHandler.cancelKeyTimers();
         mHandler.cancelPopupPreview();
-        int keyIndex = mKeyState.onUpKey(x, y);
+        final PointerTrackerKeyState keyState = mKeyState;
+        int keyIndex = keyState.onUpKey(x, y);
         if (isMinorMoveBounce(x, y, keyIndex)) {
             // Use previous fixed key index and coordinates.
-            keyIndex = mKeyState.getKeyIndex();
-            x = mKeyState.getKeyX();
-            y = mKeyState.getKeyY();
+            keyIndex = keyState.getKeyIndex();
+            x = keyState.getKeyX();
+            y = keyState.getKeyY();
         }
         if (!mIsRepeatableKey) {
             detectAndSendKey(keyIndex, x, y, eventTime);
@@ -393,9 +355,9 @@
             mProxy.invalidateKey(mKeys[keyIndex]);
     }
 
-    public void onCancelEvent(int x, int y, @SuppressWarnings("unused") long eventTime) {
-        if (DEBUG)
-            debugLog("onCancelEvt:", x, y);
+    public void onCancelEvent(int x, int y, long eventTime) {
+        if (DEBUG_EVENT)
+            printTouchEvent("onCancelEvt:", x, y, eventTime);
         mHandler.cancelKeyTimers();
         mHandler.cancelPopupPreview();
         showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
@@ -472,58 +434,47 @@
     }
 
     private void detectAndSendKey(int index, int x, int y, long eventTime) {
-        final KeyboardActionListener listener = mListener;
         final Key key = getKey(index);
-
         if (key == null) {
-            if (listener != null)
-                listener.onCancel();
-        } else {
-            if (key.mOutputText != null) {
-                if (listener != null) {
-                    listener.onText(key.mOutputText);
-                    listener.onRelease(NOT_A_KEY);
-                }
-            } else {
-                int code = key.mCodes[0];
-                //TextEntryState.keyPressedAt(key, x, y);
-                int[] codes = mKeyDetector.newCodeArray();
-                mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
-                // Multi-tap
-                if (mInMultiTap) {
-                    if (mTapCount != -1) {
-                        mListener.onKey(Keyboard.CODE_DELETE, KEY_DELETE, x, y);
-                    } else {
-                        mTapCount = 0;
-                    }
-                    code = key.mCodes[mTapCount];
-                }
-
-                // If keyboard is in manual temporary upper case state and key has manual temporary
-                // shift code, alternate character code should be sent.
-                if (mKeyboard.isManualTemporaryUpperCase()
-                        && key.mManualTemporaryUpperCaseCode != 0) {
-                    code = key.mManualTemporaryUpperCaseCode;
-                    codes[0] = code;
-                }
-
-                /*
-                 * Swap the first and second values in the codes array if the primary code is not
-                 * the first value but the second value in the array. This happens when key
-                 * debouncing is in effect.
-                 */
-                if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
-                    codes[1] = codes[0];
-                    codes[0] = code;
-                }
-                if (listener != null) {
-                    listener.onKey(code, codes, x, y);
-                    listener.onRelease(code);
-                }
-            }
-            mLastSentIndex = index;
-            mLastTapTime = eventTime;
+            callListenerOnCancel();
+            return;
         }
+        if (key.mOutputText != null) {
+            callListenerOnText(key.mOutputText);
+            callListenerOnRelease(NOT_A_KEY);
+        } else {
+            int code = key.mCodes[0];
+            final int[] codes = mKeyDetector.newCodeArray();
+            mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
+            // Multi-tap
+            if (mInMultiTap) {
+                if (mTapCount != -1) {
+                    callListenerOnKey(Keyboard.CODE_DELETE, KEY_DELETE, x, y);
+                } else {
+                    mTapCount = 0;
+                }
+                code = key.mCodes[mTapCount];
+            }
+
+            // If keyboard is in manual temporary upper case state and key has manual temporary
+            // shift code, alternate character code should be sent.
+            if (mKeyboard.isManualTemporaryUpperCase() && key.mManualTemporaryUpperCaseCode != 0) {
+                code = key.mManualTemporaryUpperCaseCode;
+                codes[0] = code;
+            }
+
+            // Swap the first and second values in the codes array if the primary code is not the
+            // first value but the second value in the array. This happens when key debouncing is
+            // in effect.
+            if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
+                codes[1] = codes[0];
+                codes[0] = code;
+            }
+            callListenerOnKey(code, codes, x, y);
+            callListenerOnRelease(code);
+        }
+        mLastSentIndex = index;
+        mLastTapTime = eventTime;
     }
 
     /**
@@ -569,18 +520,20 @@
         }
     }
 
-    private void debugLog(String title, int x, int y) {
-        int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
-        Key key = getKey(keyIndex);
-        final String code;
-        if (key == null) {
-            code = "----";
-        } else {
-            int primaryCode = key.mCodes[0];
-            code = String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode);
-        }
-        Log.d(TAG, String.format("%s%s[%d] %3d,%3d %3d(%s) %s", title,
-                (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, keyIndex, code,
-                (isModifier() ? "modifier" : "")));
+    private long mPreviousEventTime;
+
+    private void printTouchEvent(String title, int x, int y, long eventTime) {
+        final int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
+        final Key key = getKey(keyIndex);
+        final String code = (key == null) ? "----" : keyCodePrintable(key.mCodes[0]);
+        final long delta = eventTime - mPreviousEventTime;
+        Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %3d(%s)", title,
+                (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, keyIndex, code));
+        mPreviousEventTime = eventTime;
+    }
+
+    private static String keyCodePrintable(int primaryCode) {
+        final String modifier = isModifierCode(primaryCode) ? " modifier" : "";
+        return  String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode) + modifier;
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
new file mode 100644
index 0000000..b9ae03a
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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;
+
+/**
+ * This class keeps track of a key index and a position where {@link PointerTracker} is.
+ */
+/* package */ class PointerTrackerKeyState {
+    private final KeyDetector mKeyDetector;
+
+    // The position and time at which first down event occurred.
+    private int mStartX;
+    private int mStartY;
+    private long mDownTime;
+
+    // The current key index where this pointer is.
+    private int mKeyIndex = KeyDetector.NOT_A_KEY;
+    // The position where mKeyIndex was recognized for the first time.
+    private int mKeyX;
+    private int mKeyY;
+
+    // Last pointer position.
+    private int mLastX;
+    private int mLastY;
+
+    public PointerTrackerKeyState(KeyDetector keyDetecor) {
+        mKeyDetector = keyDetecor;
+    }
+
+    public int getKeyIndex() {
+        return mKeyIndex;
+    }
+
+    public int getKeyX() {
+        return mKeyX;
+    }
+
+    public int getKeyY() {
+        return mKeyY;
+    }
+
+    public int getStartX() {
+        return mStartX;
+    }
+
+    public int getStartY() {
+        return mStartY;
+    }
+
+    public long getDownTime() {
+        return mDownTime;
+    }
+
+    public int getLastX() {
+        return mLastX;
+    }
+
+    public int getLastY() {
+        return mLastY;
+    }
+
+    public int onDownKey(int x, int y, long eventTime) {
+        mStartX = x;
+        mStartY = y;
+        mDownTime = eventTime;
+
+        return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
+    }
+
+    private int onMoveKeyInternal(int x, int y) {
+        mLastX = x;
+        mLastY = y;
+        return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
+    }
+
+    public int onMoveKey(int x, int y) {
+        return onMoveKeyInternal(x, y);
+    }
+
+    public int onMoveToNewKey(int keyIndex, int x, int y) {
+        mKeyIndex = keyIndex;
+        mKeyX = x;
+        mKeyY = y;
+        return keyIndex;
+    }
+
+    public int onUpKey(int x, int y) {
+        return onMoveKeyInternal(x, y);
+    }
+
+    public void onSetKeyboard() {
+        mKeyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(mKeyX, mKeyY, null);
+    }
+}