- * When a sudden jump is detected, an UP event is simulated at the last position and when + * 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 + * @return true if the event was consumed, so that it doesn't continue to be handled by * KeyboardView. */ private boolean handleSuddenJump(MotionEvent me) { @@ -226,11 +225,10 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { invalidate(); } - // If an extension keyboard is visible or this is an extension keyboard, don't look - // for sudden jumps. Otherwise, if there was a sudden jump, return without processing the - // actual motion event. - if (!mExtensionVisible && !mIsExtensionType - && handleSuddenJump(me)) return true; + // If there was a sudden jump, return without processing the actual motion event. + if (handleSuddenJump(me)) + return true; + // Reset any bounding box controls in the keyboard if (me.getAction() == MotionEvent.ACTION_DOWN) { keyboard.keyReleased(); @@ -248,154 +246,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { } } - // If we don't have an extension keyboard, don't go any further. - if (keyboard.getExtension() == 0) { - return super.onTouchEvent(me); - } - // If the motion event is above the keyboard and it's not an UP event coming - // even before the first MOVE event into the extension area - if (me.getY() < 0 && (mExtensionVisible || me.getAction() != MotionEvent.ACTION_UP)) { - if (mExtensionVisible) { - int action = me.getAction(); - if (mFirstEvent) action = MotionEvent.ACTION_DOWN; - mFirstEvent = false; - MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(), - action, - me.getX(), me.getY() + mExtension.getHeight(), me.getMetaState()); - boolean result = mExtension.onTouchEvent(translated); - translated.recycle(); - if (me.getAction() == MotionEvent.ACTION_UP - || me.getAction() == MotionEvent.ACTION_CANCEL) { - closeExtension(); - } - return result; - } else { - if (openExtension()) { - MotionEvent cancel = MotionEvent.obtain(me.getDownTime(), me.getEventTime(), - MotionEvent.ACTION_CANCEL, me.getX() - 100, me.getY() - 100, 0); - super.onTouchEvent(cancel); - cancel.recycle(); - if (mExtension.getHeight() > 0) { - MotionEvent translated = MotionEvent.obtain(me.getEventTime(), - me.getEventTime(), - MotionEvent.ACTION_DOWN, - me.getX(), me.getY() + mExtension.getHeight(), - me.getMetaState()); - mExtension.onTouchEvent(translated); - translated.recycle(); - } else { - mFirstEvent = true; - } - // Stop processing multi-touch errors - mDisableDisambiguation = true; - } - return true; - } - } else if (mExtensionVisible) { - closeExtension(); - // Send a down event into the main keyboard first - MotionEvent down = MotionEvent.obtain(me.getEventTime(), me.getEventTime(), - MotionEvent.ACTION_DOWN, - me.getX(), me.getY(), me.getMetaState()); - super.onTouchEvent(down); - down.recycle(); - // Send the actual event - return super.onTouchEvent(me); - } else { - return super.onTouchEvent(me); - } - } - - private void setExtensionType(boolean isExtensionType) { - mIsExtensionType = isExtensionType; - } - - private boolean openExtension() { - // If the current keyboard is not visible, don't show the popup - if (!isShown()) { - return false; - } - if (((LatinKeyboard) getKeyboard()).getExtension() == 0) return false; - makePopupWindow(); - mExtensionVisible = true; - return true; - } - - private void makePopupWindow() { - if (mExtensionPopup == null) { - int[] windowLocation = new int[2]; - mExtensionPopup = new PopupWindow(getContext()); - mExtensionPopup.setBackgroundDrawable(null); - LayoutInflater li = (LayoutInflater) getContext().getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - mExtension = (LatinKeyboardView) li.inflate(mExtensionLayoutResId == 0 ? - R.layout.input_trans : mExtensionLayoutResId, null); - mExtension.setExtensionType(true); - mExtension.setOnKeyboardActionListener( - new ExtensionKeyboardListener(getOnKeyboardActionListener())); - mExtension.setPopupParent(this); - mExtension.setPopupOffset(0, -windowLocation[1]); - Keyboard keyboard; - mExtension.setKeyboard(keyboard = new LatinKeyboard(getContext(), - ((LatinKeyboard) getKeyboard()).getExtension())); - mExtensionPopup.setContentView(mExtension); - mExtensionPopup.setWidth(getWidth()); - mExtensionPopup.setHeight(keyboard.getHeight()); - mExtensionPopup.setAnimationStyle(-1); - getLocationInWindow(windowLocation); - // TODO: Fix the "- 30". - mExtension.setPopupOffset(0, -windowLocation[1] - 30); - mExtensionPopup.showAtLocation(this, 0, 0, -keyboard.getHeight() - + windowLocation[1]); - } else { - mExtension.setVisibility(VISIBLE); - } - } - - @Override - public void closing() { - super.closing(); - if (mExtensionPopup != null && mExtensionPopup.isShowing()) { - mExtensionPopup.dismiss(); - mExtensionPopup = null; - } - } - - private void closeExtension() { - mExtension.closing(); - mExtension.setVisibility(INVISIBLE); - mExtensionVisible = false; - } - - private static class ExtensionKeyboardListener implements OnKeyboardActionListener { - private OnKeyboardActionListener mTarget; - ExtensionKeyboardListener(OnKeyboardActionListener target) { - mTarget = target; - } - public void onKey(int primaryCode, int[] keyCodes, int x, int y) { - mTarget.onKey(primaryCode, keyCodes, x, y); - } - public void onPress(int primaryCode) { - mTarget.onPress(primaryCode); - } - public void onRelease(int primaryCode) { - mTarget.onRelease(primaryCode); - } - public void onText(CharSequence text) { - mTarget.onText(text); - } - public void swipeDown() { - // Don't pass through - } - public void swipeLeft() { - // Don't pass through - } - public void swipeRight() { - // Don't pass through - } - public void swipeUp() { - // Don't pass through - } + return super.onTouchEvent(me); } /**************************** INSTRUMENTATION *******************************/ @@ -404,9 +255,9 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { static final boolean DEBUG_LINE = false; private static final int MSG_TOUCH_DOWN = 1; private static final int MSG_TOUCH_UP = 2; - + Handler mHandler2; - + private String mStringToPlay; private int mStringIndex; private boolean mDownDelivered; @@ -426,7 +277,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { removeMessages(MSG_TOUCH_DOWN); removeMessages(MSG_TOUCH_UP); if (mPlaying == false) return; - + switch (msg.what) { case MSG_TOUCH_DOWN: if (mStringIndex >= mStringToPlay.length()) { @@ -434,7 +285,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { return; } char c = mStringToPlay.charAt(mStringIndex); - while (c > 255 || mAsciiKeys[(int) c] == null) { + while (c > 255 || mAsciiKeys[c] == null) { mStringIndex++; if (mStringIndex >= mStringToPlay.length()) { mPlaying = false; @@ -444,8 +295,8 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { } int x = mAsciiKeys[c].x + 10; int y = mAsciiKeys[c].y + 26; - MotionEvent me = MotionEvent.obtain(SystemClock.uptimeMillis(), - SystemClock.uptimeMillis(), + MotionEvent me = MotionEvent.obtain(SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, x, y, 0); LatinKeyboardView.this.dispatchTouchEvent(me); me.recycle(); @@ -458,9 +309,9 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { int x2 = mAsciiKeys[cUp].x + 10; int y2 = mAsciiKeys[cUp].y + 26; mStringIndex++; - - MotionEvent me2 = MotionEvent.obtain(SystemClock.uptimeMillis(), - SystemClock.uptimeMillis(), + + MotionEvent me2 = MotionEvent.obtain(SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x2, y2, 0); LatinKeyboardView.this.dispatchTouchEvent(me2); me2.recycle(); @@ -481,7 +332,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { // Get the keys on this keyboard for (int i = 0; i < keys.size(); i++) { int code = keys.get(i).codes[0]; - if (code >= 0 && code <= 255) { + if (code >= 0 && code <= 255) { mAsciiKeys[code] = keys.get(i); } } 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 new file mode 100644 index 000000000..2685e87c6 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/PointerTracker.java @@ -0,0 +1,459 @@ +/* + * 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; + +import com.android.inputmethod.latin.LatinKeyboardBaseView.OnKeyboardActionListener; +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; + private static final boolean DEBUG_MOVE = DEBUG && true; + + public interface UIProxy { + public void invalidateKey(Key key); + public void showPreview(int keyIndex, PointerTracker tracker); + } + + public final int mPointerId; + + // Timing constants + private static final int REPEAT_START_DELAY = 400; + /* package */ static final int REPEAT_INTERVAL = 50; // ~20 keys per second + private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); + private static final int MULTITAP_INTERVAL = 800; // milliseconds + private static final int KEY_DEBOUNCE_TIME = 70; + + // Miscellaneous constants + private static final int NOT_A_KEY = LatinKeyboardBaseView.NOT_A_KEY; + private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE }; + + private final UIProxy mProxy; + private final UIHandler mHandler; + private final KeyDetector mKeyDetector; + private OnKeyboardActionListener mListener; + + private Key[] mKeys; + private int mKeyDebounceThresholdSquared = -1; + + private int mCurrentKey = NOT_A_KEY; + private int mStartX; + private int mStartY; + private long mDownTime; + + // true if event is already translated to a key action (long press or mini-keyboard) + private boolean mKeyAlreadyProcessed; + + // for move de-bouncing + private int mLastCodeX; + private int mLastCodeY; + private int mLastX; + private int mLastY; + + // for time de-bouncing + private int mLastKey; + private long mLastKeyTime; + private long mLastMoveTime; + private long mCurrentKeyTime; + + // For multi-tap + private int mLastSentIndex; + private int mTapCount; + private long mLastTapTime; + private boolean mInMultiTap; + private final StringBuilder mPreviewLabel = new StringBuilder(1); + + // pressed key + private int mPreviousKey = NOT_A_KEY; + + public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy) { + if (proxy == null || handler == null || keyDetector == null) + throw new NullPointerException(); + mPointerId = id; + mProxy = proxy; + mHandler = handler; + mKeyDetector = keyDetector; + resetMultiTap(); + } + + public void setOnKeyboardActionListener(OnKeyboardActionListener listener) { + mListener = listener; + } + + public void setKeyboard(Key[] keys, float hysteresisPixel) { + if (keys == null || hysteresisPixel < 1.0f) + throw new IllegalArgumentException(); + mKeys = keys; + mKeyDebounceThresholdSquared = (int)(hysteresisPixel * hysteresisPixel); + } + + private boolean isValidKeyIndex(int keyIndex) { + return keyIndex >= 0 && keyIndex < mKeys.length; + } + + public Key getKey(int keyIndex) { + 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) { + if (mKeyAlreadyProcessed) + return; + int oldKeyIndex = mPreviousKey; + mPreviousKey = keyIndex; + if (keyIndex != oldKeyIndex) { + if (isValidKeyIndex(oldKeyIndex)) { + // if new key index is not a key, old key was just released inside of the key. + final boolean inside = (keyIndex == NOT_A_KEY); + mKeys[oldKeyIndex].onReleased(inside); + mProxy.invalidateKey(mKeys[oldKeyIndex]); + } + if (isValidKeyIndex(keyIndex)) { + mKeys[keyIndex].onPressed(); + mProxy.invalidateKey(mKeys[keyIndex]); + } + } + } + + public void setAlreadyProcessed() { + mKeyAlreadyProcessed = true; + } + + public void onDownEvent(int x, int y, long eventTime) { + int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); + mCurrentKey = keyIndex; + mStartX = x; + mStartY = y; + mDownTime = eventTime; + mKeyAlreadyProcessed = false; + startMoveDebouncing(x, y); + startTimeDebouncing(eventTime); + checkMultiTap(eventTime, keyIndex); + if (mListener != null) { + int primaryCode = isValidKeyIndex(keyIndex) ? mKeys[keyIndex].codes[0] : 0; + mListener.onPress(primaryCode); + } + if (isValidKeyIndex(keyIndex)) { + if (mKeys[keyIndex].repeatable) { + repeatKey(keyIndex); + mHandler.startKeyRepeatTimer(REPEAT_START_DELAY, keyIndex, this); + } + mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this); + } + showKeyPreviewAndUpdateKey(keyIndex); + updateMoveDebouncing(x, y); + if (DEBUG) + debugLog("onDownEvent:", x, y); + } + + public void onMoveEvent(int x, int y, long eventTime) { + if (mKeyAlreadyProcessed) + return; + int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); + if (isValidKeyIndex(keyIndex)) { + if (mCurrentKey == NOT_A_KEY) { + updateTimeDebouncing(eventTime); + mCurrentKey = keyIndex; + mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this); + } else if (isMinorMoveBounce(x, y, keyIndex, mCurrentKey)) { + updateTimeDebouncing(eventTime); + } else { + resetMultiTap(); + resetTimeDebouncing(eventTime, mCurrentKey); + resetMoveDebouncing(); + mCurrentKey = keyIndex; + mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this); + } + } else { + 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 + * holds the last key. At ACTION_UP event if time debouncing will be in effect + * eventually, the last key should be sent as the result. In such case mCurrentKey + * should not be showed as popup preview. + */ + showKeyPreviewAndUpdateKey(isMinorTimeBounce() ? mLastKey : mCurrentKey); + updateMoveDebouncing(x, y); + if (DEBUG_MOVE) + debugLog("onMoveEvent:", x, y); + } + + public void onUpEvent(int x, int y, long eventTime) { + if (mKeyAlreadyProcessed) + return; + if (DEBUG) + debugLog("onUpEvent :", x, y); + int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); + boolean wasInKeyRepeat = mHandler.isInKeyRepeat(); + mHandler.cancelKeyTimers(); + mHandler.cancelPopupPreview(); + if (isMinorMoveBounce(x, y, keyIndex, mCurrentKey)) { + updateTimeDebouncing(eventTime); + } else { + resetMultiTap(); + resetTimeDebouncing(eventTime, mCurrentKey); + mCurrentKey = keyIndex; + } + if (isMinorTimeBounce()) { + mCurrentKey = mLastKey; + x = mLastCodeX; + y = mLastCodeY; + } + showKeyPreviewAndUpdateKey(NOT_A_KEY); + // If we're not on a repeating key (which sends on a DOWN event) + if (!wasInKeyRepeat) { + detectAndSendKey(mCurrentKey, (int)x, (int)y, eventTime); + } + if (isValidKeyIndex(keyIndex)) + mProxy.invalidateKey(mKeys[keyIndex]); + } + + public void onCancelEvent(int x, int y, long eventTime) { + if (DEBUG) + debugLog("onCancelEvt:", x, y); + mHandler.cancelKeyTimers(); + mHandler.cancelPopupPreview(); + showKeyPreviewAndUpdateKey(NOT_A_KEY); + int keyIndex = mCurrentKey; + if (isValidKeyIndex(keyIndex)) + mProxy.invalidateKey(mKeys[keyIndex]); + } + + public void repeatKey(int keyIndex) { + Key key = getKey(keyIndex); + if (key != null) { + // While key is repeating, because there is no need to handle multi-tap key, we can + // pass -1 as eventTime argument. + detectAndSendKey(keyIndex, key.x, key.y, -1); + } + } + + public int getLastX() { + return mLastX; + } + + public int getLastY() { + return mLastY; + } + + public long getDownTime() { + return mDownTime; + } + + // These package scope methods are only for debugging purpose. + /* package */ int getStartX() { + return mStartX; + } + + /* package */ int getStartY() { + return mStartY; + } + + private void startMoveDebouncing(int x, int y) { + mLastCodeX = x; + mLastCodeY = y; + } + + private void updateMoveDebouncing(int x, int y) { + mLastX = x; + mLastY = y; + } + + private void resetMoveDebouncing() { + mLastCodeX = mLastX; + mLastCodeY = mLastY; + } + + private boolean isMinorMoveBounce(int x, int y, int newKey, int curKey) { + if (mKeys == null || mKeyDebounceThresholdSquared < 0) + throw new IllegalStateException("keyboard and/or hysteresis not set"); + if (newKey == curKey) { + return true; + } else if (isValidKeyIndex(curKey)) { + return getSquareDistanceToKeyEdge(x, y, mKeys[curKey]) + < mKeyDebounceThresholdSquared; + } else { + return false; + } + } + + private static int getSquareDistanceToKeyEdge(int x, int y, Key key) { + final int left = key.x; + final int right = key.x + key.width; + final int top = key.y; + final int bottom = key.y + key.height; + final int edgeX = x < left ? left : (x > right ? right : x); + final int edgeY = y < top ? top : (y > bottom ? bottom : y); + final int dx = x - edgeX; + final int dy = y - edgeY; + return dx * dx + dy * dy; + } + + private void startTimeDebouncing(long eventTime) { + mLastKey = NOT_A_KEY; + mLastKeyTime = 0; + mCurrentKeyTime = 0; + mLastMoveTime = eventTime; + } + + private void updateTimeDebouncing(long eventTime) { + mCurrentKeyTime += eventTime - mLastMoveTime; + mLastMoveTime = eventTime; + } + + private void resetTimeDebouncing(long eventTime, int currentKey) { + mLastKey = currentKey; + mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime; + mCurrentKeyTime = 0; + mLastMoveTime = eventTime; + } + + private boolean isMinorTimeBounce() { + return mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < KEY_DEBOUNCE_TIME + && mLastKey != NOT_A_KEY; + } + + private void showKeyPreviewAndUpdateKey(int keyIndex) { + updateKey(keyIndex); + if (!isModifier()) + mProxy.showPreview(keyIndex, this); + } + + private void detectAndSendKey(int index, int x, int y, long eventTime) { + if (isValidKeyIndex(index)) { + final Key key = mKeys[index]; + OnKeyboardActionListener listener = mListener; + if (key.text != null) { + if (listener != null) { + listener.onText(key.text); + listener.onRelease(NOT_A_KEY); + } + } else { + int code = key.codes[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.KEYCODE_DELETE, KEY_DELETE, x, y); + } else { + mTapCount = 0; + } + code = key.codes[mTapCount]; + } + /* + * 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; + } + } + + /** + * Handle multi-tap keys by producing the key label for the current multi-tap state. + */ + public CharSequence getPreviewText(Key key) { + if (mInMultiTap) { + // Multi-tap + mPreviewLabel.setLength(0); + mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]); + return mPreviewLabel; + } else { + return key.label; + } + } + + private void resetMultiTap() { + mLastSentIndex = NOT_A_KEY; + mTapCount = 0; + mLastTapTime = -1; + mInMultiTap = false; + } + + private void checkMultiTap(long eventTime, int keyIndex) { + Key key = getKey(keyIndex); + if (key == null) + return; + + final boolean isMultiTap = + (eventTime < mLastTapTime + MULTITAP_INTERVAL && keyIndex == mLastSentIndex); + if (key.codes.length > 1) { + mInMultiTap = true; + if (isMultiTap) { + mTapCount = (mTapCount + 1) % key.codes.length; + return; + } else { + mTapCount = -1; + return; + } + } + if (!isMultiTap) { + resetMultiTap(); + } + } + + private void debugLog(String title, int x, int y) { + Key key = getKey(mCurrentKey); + final String code; + if (key == null) { + code = "----"; + } else { + int primaryCode = key.codes[0]; + code = String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode); + } + Log.d(TAG, String.format("%s [%d] %3d,%3d %s %s", title, mPointerId, x, y, code, + isModifier() ? "modifier" : "")); + } +} \ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java b/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java index eae2d7f08..d17bedb56 100644 --- a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java +++ b/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java @@ -16,70 +16,43 @@ package com.android.inputmethod.latin; -import android.inputmethodservice.Keyboard; import android.inputmethodservice.Keyboard.Key; import java.util.Arrays; -class ProximityKeyDetector { +class ProximityKeyDetector extends KeyDetector { private static final int MAX_NEARBY_KEYS = 12; - private Keyboard mKeyboard; - private Key[] mKeys; - - private boolean mProximityCorrectOn; - private int mProximityThresholdSquare; - // working area private int[] mDistances = new int[MAX_NEARBY_KEYS]; - public void setKeyboard(Keyboard keyboard, Key[] keys) { - if (keyboard == null || keys == null) - throw new NullPointerException(); - mKeyboard = keyboard; - mKeys = keys; - } - - public void setProximityCorrectionEnabled(boolean enabled) { - mProximityCorrectOn = enabled; - } - - public boolean isProximityCorrectionEnabled() { - return mProximityCorrectOn; - } - - public void setProximityThreshold(int threshold) { - mProximityThresholdSquare = threshold * threshold; - } - - public int[] newCodeArray() { - int[] codes = new int[MAX_NEARBY_KEYS]; - Arrays.fill(codes, LatinKeyboardBaseView.NOT_A_KEY); - return codes; + @Override + protected int getMaxNearbyKeys() { + return MAX_NEARBY_KEYS; } + @Override public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) { - final Key[] keys = mKeys; - if (keys == null) - throw new IllegalStateException("keyboard isn't set"); - // mKeyboard is guaranteed not null at setKeybaord() method + final Key[] keys = getKeys(); + final int touchX = getTouchX(x); + final int touchY = getTouchY(y); int primaryIndex = LatinKeyboardBaseView.NOT_A_KEY; int closestKey = LatinKeyboardBaseView.NOT_A_KEY; int closestKeyDist = mProximityThresholdSquare + 1; int[] distances = mDistances; Arrays.fill(distances, Integer.MAX_VALUE); - int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y); + int [] nearestKeyIndices = mKeyboard.getNearestKeys(touchX, touchY); final int keyCount = nearestKeyIndices.length; for (int i = 0; i < keyCount; i++) { final Key key = keys[nearestKeyIndices[i]]; int dist = 0; - boolean isInside = key.isInside(x,y); + boolean isInside = key.isInside(touchX, touchY); if (isInside) { primaryIndex = nearestKeyIndices[i]; } if (((mProximityCorrectOn - && (dist = key.squaredDistanceFrom(x, y)) < mProximityThresholdSquare) + && (dist = key.squaredDistanceFrom(touchX, touchY)) < mProximityThresholdSquare) || isInside) && key.codes[0] > 32) { // Find insertion point diff --git a/java/src/com/android/inputmethod/voice/VoiceInput.java b/java/src/com/android/inputmethod/voice/VoiceInput.java index f24c180d0..4c54dd3c5 100644 --- a/java/src/com/android/inputmethod/voice/VoiceInput.java +++ b/java/src/com/android/inputmethod/voice/VoiceInput.java @@ -16,6 +16,7 @@ package com.android.inputmethod.voice; +import com.android.inputmethod.latin.EditingUtil; import com.android.inputmethod.latin.R; import android.content.ContentResolver; @@ -30,6 +31,7 @@ import android.speech.RecognitionListener; import android.speech.SpeechRecognizer; import android.speech.RecognizerIntent; import android.util.Log; +import android.view.inputmethod.InputConnection; import android.view.View; import android.view.View.OnClickListener; @@ -423,8 +425,14 @@ public class VoiceInput implements OnClickListener { mLogger.textModifiedByTypingDeletion(length); } - public void logTextModifiedByChooseSuggestion(int length) { - mLogger.textModifiedByChooseSuggestion(length); + public void logTextModifiedByChooseSuggestion(String suggestion, int index, + String wordSeparators, InputConnection ic) { + EditingUtil.Range range = new EditingUtil.Range(); + String wordToBeReplaced = EditingUtil.getWordAtCursor(ic, wordSeparators, range); + // If we enable phrase-based alternatives, only send up the first word + // in suggestion and wordToBeReplaced. + mLogger.textModifiedByChooseSuggestion(suggestion.length(), wordToBeReplaced.length(), + index, wordToBeReplaced, suggestion); } public void logKeyboardWarningDialogShown() { @@ -455,10 +463,6 @@ public class VoiceInput implements OnClickListener { mLogger.voiceInputDelivered(length); } - public void logNBestChoose(int index) { - mLogger.nBestChoose(index); - } - public void logInputEnded() { mLogger.inputEnded(); } diff --git a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java b/java/src/com/android/inputmethod/voice/VoiceInputLogger.java index 9d3a92037..4d50f5ee8 100644 --- a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java +++ b/java/src/com/android/inputmethod/voice/VoiceInputLogger.java @@ -178,20 +178,19 @@ public class VoiceInputLogger { mContext.sendBroadcast(i); } - public void textModifiedByChooseSuggestion(int length) { + public void textModifiedByChooseSuggestion(int suggestionLength, int replacedPhraseLength, + int index, String before, String after) { Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, suggestionLength); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_REPLACED_LENGTH, replacedPhraseLength); i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE, LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_CHOOSE_SUGGESTION); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_N_BEST_CHOOSE_INDEX, index); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_BEFORE_N_BEST_CHOOSE, before); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_AFTER_N_BEST_CHOOSE, after); mContext.sendBroadcast(i); } - public void nBestChoose(int index) { - Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.N_BEST_CHOOSE); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_N_BEST_CHOOSE_INDEX, index); - mContext.sendBroadcast(i); - } - public void inputEnded() { mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.INPUT_ENDED)); }