From 40a05f62edc6cdedb4365a722b48a72826ef2bf6 Mon Sep 17 00:00:00 2001
From: "Tadashi G. Takaoka" <takaoka@google.com>
Date: Thu, 2 Sep 2010 01:35:24 +0900
Subject: [PATCH] Queuing PointerTracker to support n-key roll-over and shift
 modifier.

Bug: 2910379
Change-Id: I5cfae33e72a406585137842a2260310813cee07f
---
 .../android/inputmethod/latin/LatinIME.java   | 29 +++++++-
 .../latin/LatinKeyboardBaseView.java          | 67 ++++++++++++++++++-
 .../inputmethod/latin/ModifierKeyState.java   | 42 ++++++++++++
 .../inputmethod/latin/PointerTracker.java     | 22 +++++-
 4 files changed, 153 insertions(+), 7 deletions(-)
 create mode 100644 java/src/com/android/inputmethod/latin/ModifierKeyState.java

diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 76f774c96..7afee3fad 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -227,6 +227,9 @@ public class LatinIME extends InputMethodService
     private int mDeleteCount;
     private long mLastKeyTime;
 
+    // Shift modifier key state
+    private ModifierKeyState mShiftKeyState = new ModifierKeyState();
+
     private Tutorial mTutorial;
 
     private AudioManager mAudioManager;
@@ -976,7 +979,8 @@ public class LatinIME extends InputMethodService
     public void updateShiftKeyState(EditorInfo attr) {
         InputConnection ic = getCurrentInputConnection();
         if (ic != null && attr != null && mKeyboardSwitcher.isAlphabetMode()) {
-            mKeyboardSwitcher.setShifted(mCapsLock || getCursorCapsMode(ic, attr) != 0);
+            mKeyboardSwitcher.setShifted(mShiftKeyState.isMomentary() || mCapsLock
+                    || getCursorCapsMode(ic, attr) != 0);
         }
     }
 
@@ -1233,12 +1237,20 @@ public class LatinIME extends InputMethodService
         ic.endBatchEdit();
     }
 
+    private void resetShift() {
+        handleShiftInternal(true);
+    }
+
     private void handleShift() {
+        handleShiftInternal(false);
+    }
+
+    private void handleShiftInternal(boolean forceNormal) {
         mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE);
         KeyboardSwitcher switcher = mKeyboardSwitcher;
         LatinKeyboardView inputView = switcher.getInputView();
         if (switcher.isAlphabetMode()) {
-            if (mCapsLock) {
+            if (mCapsLock || forceNormal) {
                 mCapsLock = false;
                 switcher.setShifted(false);
             } else if (inputView != null) {
@@ -2146,15 +2158,26 @@ public class LatinIME extends InputMethodService
         vibrate();
         playKeyClick(primaryCode);
         if (primaryCode == Keyboard.KEYCODE_SHIFT) {
+            mShiftKeyState.onPress();
             handleShift();
+        } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE) {
+            // TODO: We should handle KEYCODE_MODE_CHANGE (symbol) here as well.
+        } else {
+            mShiftKeyState.onOtherKeyPressed();
         }
-        // TODO: We should handle KEYCODE_MODE_CHANGE (symbol) here as well.
     }
 
     public void onRelease(int primaryCode) {
         // Reset any drag flags in the keyboard
         ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).keyReleased();
         //vibrate();
+        if (primaryCode == Keyboard.KEYCODE_SHIFT) {
+            if (mShiftKeyState.isMomentary())
+                resetShift();
+            mShiftKeyState.onRelease();
+        } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE) {
+            // TODO: We should handle KEYCODE_MODE_CHANGE (symbol) here as well.
+        }
     }
 
     private FieldContext makeFieldContext() {
diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
index 4daf6515f..30042d8a1 100644
--- a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
+++ b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
@@ -33,6 +33,7 @@ import android.inputmethodservice.Keyboard.Key;
 import android.os.Handler;
 import android.os.Message;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.TypedValue;
 import android.view.GestureDetector;
 import android.view.Gravity;
@@ -45,6 +46,7 @@ import android.widget.TextView;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.Map;
 
 /**
@@ -62,6 +64,7 @@ import java.util.Map;
  */
 public class LatinKeyboardBaseView extends View implements View.OnClickListener,
         PointerTracker.UIProxy {
+    private static final String TAG = "LatinKeyboardBaseView";
     private static final boolean DEBUG = false;
 
     public static final int NOT_A_TOUCH_COORDINATE = -1;
@@ -199,6 +202,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener,
     private OnKeyboardActionListener mKeyboardActionListener;
 
     private final ArrayList<PointerTracker> mPointerTrackers = new ArrayList<PointerTracker>();
+    private final PointerQueue mPointerQueue = new PointerQueue();
     private final float mDebounceHysteresis;
 
     protected KeyDetector mKeyDetector = new ProximityKeyDetector();
@@ -316,6 +320,41 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener,
         }
     };
 
+    static class PointerQueue {
+        private LinkedList<PointerTracker> mQueue = new LinkedList<PointerTracker>();
+
+        public void add(PointerTracker tracker) {
+            mQueue.add(tracker);
+        }
+
+        public int lastIndexOf(PointerTracker tracker) {
+            LinkedList<PointerTracker> queue = mQueue;
+            for (int index = queue.size() - 1; index >= 0; index--) {
+                PointerTracker t = queue.get(index);
+                if (t == tracker)
+                    return index;
+            }
+            return -1;
+        }
+
+        public void releasePointersOlderThan(PointerTracker tracker, long eventTime) {
+            LinkedList<PointerTracker> queue = mQueue;
+            int oldestPos = 0;
+            for (PointerTracker t = queue.get(oldestPos); t != tracker; t = queue.get(oldestPos)) {
+                if (t.isModifier()) {
+                    oldestPos++;
+                } else {
+                    t.onUpEvent(t.getLastX(), t.getLastY(), eventTime);
+                    queue.remove(oldestPos);
+                }
+            }
+        }
+
+        public void remove(PointerTracker tracker) {
+            mQueue.remove(tracker);
+        }
+    }
+
     public LatinKeyboardBaseView(Context context, AttributeSet attrs) {
         this(context, attrs, R.attr.keyboardViewStyle);
     }
@@ -1107,14 +1146,14 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener,
             switch (action) {
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_POINTER_DOWN:
-                tracker.onDownEvent(touchX, touchY, eventTime);
+                onDownEvent(tracker, touchX, touchY, eventTime);
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_POINTER_UP:
-                tracker.onUpEvent(touchX, touchY, eventTime);
+                onUpEvent(tracker, touchX, touchY, eventTime);
                 break;
             case MotionEvent.ACTION_CANCEL:
-                tracker.onCancelEvent(touchX, touchY, eventTime);
+                onCancelEvent(tracker, touchX, touchY, eventTime);
                 break;
             }
         }
@@ -1122,6 +1161,28 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener,
         return true;
     }
 
+    private void onDownEvent(PointerTracker tracker, int touchX, int touchY, long eventTime) {
+        tracker.onDownEvent(touchX, touchY, eventTime);
+        mPointerQueue.add(tracker);
+    }
+
+    private void onUpEvent(PointerTracker tracker, int touchX, int touchY, long eventTime) {
+        int index = mPointerQueue.lastIndexOf(tracker);
+        if (index >= 0) {
+            mPointerQueue.releasePointersOlderThan(tracker, eventTime);
+        } else {
+            Log.w(TAG, "onUpEvent: corresponding down event not found for pointer "
+                    + tracker.mPointerId);
+        }
+        tracker.onUpEvent(touchX, touchY, eventTime);
+        mPointerQueue.remove(tracker);
+    }
+
+    private void onCancelEvent(PointerTracker tracker, int touchX, int touchY, long eventTime) {
+        tracker.onCancelEvent(touchX, touchY, eventTime);
+        mPointerQueue.remove(tracker);
+    }
+
     protected void swipeRight() {
         mKeyboardActionListener.swipeRight();
     }
diff --git a/java/src/com/android/inputmethod/latin/ModifierKeyState.java b/java/src/com/android/inputmethod/latin/ModifierKeyState.java
new file mode 100644
index 000000000..097e87abe
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ModifierKeyState.java
@@ -0,0 +1,42 @@
+/*
+ * 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.latin;
+
+class ModifierKeyState {
+    private static final int RELEASING = 0;
+    private static final int PRESSING = 1;
+    private static final int MOMENTARY = 2;
+
+    private int mState = RELEASING;
+
+    public void onPress() {
+        mState = PRESSING;
+    }
+
+    public void onRelease() {
+        mState = RELEASING;
+    }
+
+    public void onOtherKeyPressed() {
+        if (mState == PRESSING)
+            mState = MOMENTARY;
+    }
+
+    public boolean isMomentary() {
+        return mState == MOMENTARY;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/PointerTracker.java b/java/src/com/android/inputmethod/latin/PointerTracker.java
index c8976a372..10f51bd48 100644
--- a/java/src/com/android/inputmethod/latin/PointerTracker.java
+++ b/java/src/com/android/inputmethod/latin/PointerTracker.java
@@ -21,9 +21,13 @@ import com.android.inputmethod.latin.LatinKeyboardBaseView.UIHandler;
 
 import android.inputmethodservice.Keyboard;
 import android.inputmethodservice.Keyboard.Key;
+import android.util.Log;
 import android.view.ViewConfiguration;
 
 public class PointerTracker {
+    private static final String TAG = "PointerTracker";
+    private static final boolean DEBUG = false;
+
     public interface UIProxy {
         public void invalidateKey(Key key);
         public void showPreview(int keyIndex, PointerTracker tracker);
@@ -108,6 +112,15 @@ public class PointerTracker {
         return isValidKeyIndex(keyIndex) ? mKeys[keyIndex] : null;
     }
 
+    public boolean isModifier() {
+        Key key = getKey(mCurrentKey);
+        if (key == null)
+            return false;
+        int primaryCode = key.codes[0];
+        // TODO: KEYCODE_MODE_CHANGE (symbol) will be also a modifier key
+        return primaryCode == Keyboard.KEYCODE_SHIFT;
+    }
+
     public void updateKey(int keyIndex) {
         int oldKeyIndex = mPreviousKey;
         mPreviousKey = keyIndex;
@@ -146,6 +159,8 @@ public class PointerTracker {
         }
         showKeyPreviewAndUpdateKey(keyIndex);
         updateMoveDebouncing(touchX, touchY);
+        if (DEBUG)
+            Log.d(TAG, "onDownEvent: [" + mPointerId + "] modifier=" + isModifier());
     }
 
     public void onMoveEvent(int touchX, int touchY, long eventTime) {
@@ -178,6 +193,8 @@ public class PointerTracker {
     }
 
     public void onUpEvent(int touchX, int touchY, long eventTime) {
+        if (DEBUG)
+            Log.d(TAG, "onUpEvent: [" + mPointerId + "] modifier=" + isModifier());
         int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(touchX, touchY, null);
         boolean wasInKeyRepeat = mHandler.isInKeyRepeat();
         mHandler.cancelKeyTimers();
@@ -204,6 +221,8 @@ public class PointerTracker {
     }
 
     public void onCancelEvent(int touchX, int touchY, long eventTime) {
+        if (DEBUG)
+            Log.d(TAG, "onCancelEvent: [" + mPointerId + "]");
         mHandler.cancelKeyTimers();
         mHandler.cancelPopupPreview();
         mProxy.dismissPopupKeyboard();
@@ -305,7 +324,8 @@ public class PointerTracker {
 
     private void showKeyPreviewAndUpdateKey(int keyIndex) {
         updateKey(keyIndex);
-        mProxy.showPreview(keyIndex, this);
+        if (!isModifier())
+            mProxy.showPreview(keyIndex, this);
     }
 
     private void detectAndSendKey(int index, int x, int y, long eventTime) {