diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
7 files changed, 607 insertions, 401 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index b081242a9..d0e143dd0 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -61,9 +61,9 @@ public class BinaryDictionary extends Dictionary { static { try { - System.loadLibrary("jni_latinime2"); + System.loadLibrary("jni_latinime"); } catch (UnsatisfiedLinkError ule) { - Log.e("BinaryDictionary", "Could not load native library jni_latinime2"); + Log.e("BinaryDictionary", "Could not load native library jni_latinime"); } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 2406eb8c3..2753bb8b9 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -768,9 +768,6 @@ public class LatinIME extends InputMethodService // Check if we should go in or out of correction mode. - // TODO: Uncomment this block when we enable re-editing feature - // If a word is selected - /* if (isPredictionOn() && mJustRevertedSeparator == null && (candidatesStart == candidatesEnd || newSelStart != oldSelStart || TextEntryState.isCorrecting()) @@ -782,7 +779,6 @@ public class LatinIME extends InputMethodService abortCorrection(false); } } - */ } @Override @@ -1077,7 +1073,7 @@ public class LatinIME extends InputMethodService // Implementation of KeyboardViewListener - public void onKey(int primaryCode, int[] keyCodes) { + public void onKey(int primaryCode, int[] keyCodes, int x, int y) { long when = SystemClock.uptimeMillis(); if (primaryCode != Keyboard.KEYCODE_DELETE || when > mLastKeyTime + QUICK_PRESS) { @@ -1125,7 +1121,7 @@ public class LatinIME extends InputMethodService if (primaryCode != KEYCODE_ENTER) { mJustAddedAutoSpace = false; } - LatinImeLogger.logOnInputChar((char)primaryCode); + LatinImeLogger.logOnInputChar((char)primaryCode, x, y); if (isWordSeparator(primaryCode)) { handleSeparator(primaryCode); } else { @@ -1772,7 +1768,8 @@ public class LatinIME extends InputMethodService // So, LatinImeLogger logs "" as a user's input. LatinImeLogger.logOnManualSuggestion( "", suggestion.toString(), index, suggestions); - onKey(suggestion.charAt(0), null); + onKey(suggestion.charAt(0), null, LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE, + LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE); if (ic != null) { ic.endBatchEdit(); } diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java index 19eead0a0..716f7207f 100644 --- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java +++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.inputmethodservice.Keyboard; import android.os.AsyncTask; import android.os.DropBoxManager; import android.preference.PreferenceManager; @@ -54,11 +55,12 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang private static final char SEPARATER = ';'; private static final char NULL_CHAR = '\uFFFC'; private static final int EXCEPTION_MAX_LENGTH = 400; + private static final int INVALID_COORDINATE = -2; // ID_MANUALSUGGESTION has been replaced by ID_MANUALSUGGESTION_WITH_DATATYPE // private static final int ID_MANUALSUGGESTION = 0; - private static final int ID_AUTOSUGGESTIONCANCELLED = 1; - private static final int ID_AUTOSUGGESTION = 2; + // private static final int ID_AUTOSUGGESTIONCANCELLED = 1; + // private static final int ID_AUTOSUGGESTION = 2; private static final int ID_INPUT_COUNT = 3; private static final int ID_DELETE_COUNT = 4; private static final int ID_WORD_COUNT = 5; @@ -72,6 +74,8 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang private static final int ID_AUTOSUGGESTIONCOUNT = 13; private static final int ID_LANGUAGES = 14; private static final int ID_MANUALSUGGESTION_WITH_DATATYPE = 15; + private static final int ID_AUTOSUGGESTIONCANCELLED_WITH_COORDINATES = 16; + private static final int ID_AUTOSUGGESTION_WITH_COORDINATES = 17; private static final String PREF_ENABLE_LOG = "enable_logging"; private static final String PREF_DEBUG_MODE = "debug_mode"; @@ -84,6 +88,8 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang /* package */ static String sLastAutoSuggestBefore; /* package */ static String sLastAutoSuggestAfter; /* package */ static String sLastAutoSuggestSeparator; + private static int[] sLastAutoSuggestXCoordinates; + private static int[] sLastAutoSuggestYCoordinates; // This value holds MAIN, USER, AUTO, etc... private static int sLastAutoSuggestDicTypeId; // This value holds 0 (= unigram), 1 (= bigram) etc... @@ -92,6 +98,8 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang = new HashMap<String, Pair<Integer, Integer>>(); private static String[] sPreviousWords; private static DebugKeyEnabler sDebugKeyEnabler = new DebugKeyEnabler(); + private static int sKeyboardWidth = 0; + private static int sKeyboardHeight = 0; private ArrayList<LogEntry> mLogBuffer = null; private ArrayList<LogEntry> mPrivacyLogBuffer = null; @@ -201,7 +209,6 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang Arrays.fill(mAutoCancelledCountPerDic, 0); mLogBuffer.clear(); mPrivacyLogBuffer.clear(); - mRingCharBuffer.reset(); } public void destroy() { @@ -362,7 +369,7 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang mInputCount += (Integer)data; break; case ID_MANUALSUGGESTION_WITH_DATATYPE: - case ID_AUTOSUGGESTION: + case ID_AUTOSUGGESTION_WITH_COORDINATES: ++mWordCount; String[] dataStrings = (String[]) data; if (dataStrings.length < 2) { @@ -381,7 +388,7 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang } } break; - case ID_AUTOSUGGESTIONCANCELLED: + case ID_AUTOSUGGESTIONCANCELLED_WITH_COORDINATES: --mWordCount; dataStrings = (String[]) data; if (dataStrings.length < 2) { @@ -461,7 +468,7 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang } else if (s instanceof Integer) { out += (Integer) s; } - Log.d(TAG, "SendLog: " + tag + ";" + out + ", will be sent after " + Log.d(TAG, "SendLog: " + tag + ";" + out + " -> will be sent after " + (- (now - mLastTimeSend - MINIMUMSENDINTERVAL) / 1000) + " sec."); } if (now - mLastTimeActive > MINIMUMSENDINTERVAL) { @@ -625,7 +632,8 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang sLatinImeLogger.mAutoSuggestCountPerDic[sLastAutoSuggestDicTypeId]++; if (sLastAutoSuggestDicTypeId != Suggest.DIC_MAIN) { if (sDBG) { - Log.d(TAG, "logOnAutoSuggestion was cancelled: not from main dic."); + Log.d(TAG, "logOnAutoSuggestion was cancelled: not from main dic.:" + + sLastAutoSuggestDicTypeId); } before = ""; after = ""; @@ -637,23 +645,46 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang before = ""; after = ""; } - int previousWordsLength = (sPreviousWords == null) ? 0 : sPreviousWords.length; final int COLUMN_BEFORE_ID = 0; final int COLUMN_AFTER_ID = 1; final int COLUMN_SEPARATOR_ID = 2; final int COLUMN_DATA_TYPE_ID = 3; - final int BASE_COLUMN_SIZE = 4; + final int COLUMN_KEYBOARD_SIZE_WIDTH = 4; + final int COLUMN_KEYBOARD_SIZE_HEIGHT = 5; + final int BASE_COLUMN_SIZE = 6; + + final int userTypedWordLength = before.length(); + final int previousWordsLength = (sPreviousWords == null) ? 0 + : sPreviousWords.length; + String[] strings = new String[BASE_COLUMN_SIZE + userTypedWordLength * 2 + + previousWordsLength]; + sLastAutoSuggestXCoordinates = new int[userTypedWordLength]; + sLastAutoSuggestXCoordinates = new int[userTypedWordLength]; - String[] strings = new String[4 + previousWordsLength]; strings[COLUMN_BEFORE_ID] = before; strings[COLUMN_AFTER_ID] = after; strings[COLUMN_SEPARATOR_ID] = separator; strings[COLUMN_DATA_TYPE_ID] = String.valueOf(sLastAutoSuggestDataType); + strings[COLUMN_KEYBOARD_SIZE_WIDTH] = String.valueOf(sKeyboardWidth); + strings[COLUMN_KEYBOARD_SIZE_HEIGHT] = String.valueOf(sKeyboardHeight); + + for (int i = 0; i < userTypedWordLength; ++i) { + int x = sLatinImeLogger.mRingCharBuffer.getPreviousX(before.charAt(i), + userTypedWordLength - i - 1); + int y = sLatinImeLogger.mRingCharBuffer.getPreviousY(before.charAt(i), + userTypedWordLength - i - 1); + strings[BASE_COLUMN_SIZE + i * 2] = String.valueOf(x); + strings[BASE_COLUMN_SIZE + i * 2 + 1] = String.valueOf(y); + sLastAutoSuggestXCoordinates[i] = x; + sLastAutoSuggestXCoordinates[i] = y; + } + for (int i = 0; i < previousWordsLength; ++i) { - strings[BASE_COLUMN_SIZE + i] = sPreviousWords[i]; + strings[BASE_COLUMN_SIZE + userTypedWordLength * 2 + i] = sPreviousWords[i]; } - sLatinImeLogger.sendLogToDropBox(ID_AUTOSUGGESTION, strings); + + sLatinImeLogger.sendLogToDropBox(ID_AUTOSUGGESTION_WITH_COORDINATES, strings); } synchronized (LatinImeLogger.class) { sLastAutoSuggestBefore = before; @@ -669,9 +700,29 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang if (sLogEnabled) { sLatinImeLogger.mAutoCancelledCountPerDic[sLastAutoSuggestDicTypeId]++; if (sLastAutoSuggestBefore != null && sLastAutoSuggestAfter != null) { - String[] strings = new String[] { - sLastAutoSuggestBefore, sLastAutoSuggestAfter, sLastAutoSuggestSeparator}; - sLatinImeLogger.sendLogToDropBox(ID_AUTOSUGGESTIONCANCELLED, strings); + final int COLUMN_BEFORE_ID = 0; + final int COLUMN_AFTER_ID = 1; + final int COLUMN_SEPARATOR_ID = 2; + final int COLUMN_KEYBOARD_SIZE_WIDTH = 3; + final int COLUMN_KEYBOARD_SIZE_HEIGHT = 4; + final int BASE_COLUMN_SIZE = 5; + + final int userTypedWordLength = sLastAutoSuggestBefore.length(); + + String[] strings = new String[BASE_COLUMN_SIZE + userTypedWordLength * 2]; + strings[COLUMN_BEFORE_ID] = sLastAutoSuggestBefore; + strings[COLUMN_AFTER_ID] = sLastAutoSuggestAfter; + strings[COLUMN_SEPARATOR_ID] = sLastAutoSuggestSeparator; + strings[COLUMN_KEYBOARD_SIZE_WIDTH] = String.valueOf(sKeyboardWidth); + strings[COLUMN_KEYBOARD_SIZE_HEIGHT] = String.valueOf(sKeyboardHeight); + for (int i = 0; i < userTypedWordLength; ++i) { + strings[BASE_COLUMN_SIZE + i * 2] = String.valueOf( + sLastAutoSuggestXCoordinates); + strings[BASE_COLUMN_SIZE + i * 2 + 1] = String.valueOf( + sLastAutoSuggestYCoordinates); + } + sLatinImeLogger.sendLogToDropBox( + ID_AUTOSUGGESTIONCANCELLED_WITH_COORDINATES, strings); } synchronized (LatinImeLogger.class) { sLastAutoSuggestBefore = ""; @@ -693,9 +744,9 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang } } - public static void logOnInputChar(char c) { + public static void logOnInputChar(char c, int x, int y) { if (sLogEnabled) { - sLatinImeLogger.mRingCharBuffer.push(c); + sLatinImeLogger.mRingCharBuffer.push(c, x, y); sLatinImeLogger.sendLogToDropBox(ID_INPUT_COUNT, 1); } } @@ -747,6 +798,13 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang } } + public static void onSetKeyboard(Keyboard kb) { + if (sLogEnabled) { + sKeyboardWidth = kb.getMinWidth(); + sKeyboardHeight = kb.getHeight(); + } + } + private static class LogSerializer { private static void appendWithLength(StringBuffer sb, String data) { sb.append(data.length()); @@ -780,43 +838,64 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang final int BUFSIZE = 20; private Context mContext; private int mEnd = 0; - /* package */ int length = 0; + /* package */ int mLength = 0; private char[] mCharBuf = new char[BUFSIZE]; + private int[] mXBuf = new int[BUFSIZE]; + private int[] mYBuf = new int[BUFSIZE]; public RingCharBuffer(Context context) { mContext = context; } - private int normalize(int in) { int ret = in % BUFSIZE; return ret < 0 ? ret + BUFSIZE : ret; } - public void push(char c) { + public void push(char c, int x, int y) { mCharBuf[mEnd] = c; + mXBuf[mEnd] = x; + mYBuf[mEnd] = y; mEnd = normalize(mEnd + 1); - if (length < BUFSIZE) { - ++length; + if (mLength < BUFSIZE) { + ++mLength; } } public char pop() { - if (length < 1) { + if (mLength < 1) { return NULL_CHAR; } else { mEnd = normalize(mEnd - 1); - --length; + --mLength; return mCharBuf[mEnd]; } } public char getLastChar() { - if (length < 1) { + if (mLength < 1) { return NULL_CHAR; } else { return mCharBuf[normalize(mEnd - 1)]; } } + public int getPreviousX(char c, int back) { + int index = normalize(mEnd - 2 - back); + if (mLength <= back + || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) { + return INVALID_COORDINATE; + } else { + return mXBuf[index]; + } + } + public int getPreviousY(char c, int back) { + int index = normalize(mEnd - 2 - back); + if (mLength <= back + || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) { + return INVALID_COORDINATE; + } else { + return mYBuf[index]; + } + } public String getLastString() { StringBuffer sb = new StringBuffer(); - for (int i = 0; i < length; ++i) { + for (int i = 0; i < mLength; ++i) { char c = mCharBuf[normalize(mEnd - 1 - i)]; if (!((LatinIME)mContext).isWordSeparator(c)) { sb.append(c); @@ -827,7 +906,7 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang return sb.reverse().toString(); } public void reset() { - length = 0; + mLength = 0; } } diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java index 665c641c2..4007c2b55 100644 --- a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java +++ b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java @@ -21,11 +21,11 @@ import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.Paint.Align; import android.graphics.PorterDuff; import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.Paint.Align; import android.graphics.Region.Op; +import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.inputmethodservice.Keyboard; import android.inputmethodservice.Keyboard.Key; @@ -43,7 +43,6 @@ import android.view.ViewGroup.LayoutParams; import android.widget.PopupWindow; import android.widget.TextView; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -100,8 +99,14 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener * keys. These codes are useful to correct for * accidental presses of a key adjacent to the intended * key. + * @param x + * x-coordinate pixel of touched event. If onKey is not called by onTouchEvent, + * the value should be NOT_A_TOUCH_COORDINATE. + * @param y + * y-coordinate pixel of touched event. If onKey is not called by onTouchEvent, + * the value should be NOT_A_TOUCH_COORDINATE. */ - void onKey(int primaryCode, int[] keyCodes); + void onKey(int primaryCode, int[] keyCodes, int x, int y); /** * Sends a sequence of characters to the listener. @@ -134,8 +139,10 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener void swipeUp(); } + public static final int NOT_A_TOUCH_COORDINATE = -1; + private static final boolean DEBUG = false; - private static final int NOT_A_KEY = -1; + static final int NOT_A_KEY = -1; private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE }; private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable }; @@ -176,7 +183,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener private static final int DEBOUNCE_TIME = 70; private int mVerticalCorrection; - private int mProximityThreshold; + private ProximityKeyDetector mProximityKeyDetector = new ProximityKeyDetector(); private boolean mPreviewCentered = false; private boolean mShowPreview = true; @@ -185,13 +192,10 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener private int mPopupPreviewY; private int mWindowY; - private boolean mProximityCorrectOn; - private Paint mPaint; private Rect mPadding; private int mCurrentKey = NOT_A_KEY; - private int mDownKey = NOT_A_KEY; private int mStartX; private int mStartY; @@ -200,20 +204,18 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener private GestureDetector mGestureDetector; private int mPopupX; private int mPopupY; - private int mRepeatKeyIndex = NOT_A_KEY; private int mPopupLayout; private boolean mAbortKey; private Key mInvalidatedKey; private Rect mClipRegion = new Rect(0, 0, 0, 0); - private boolean mPossiblePoly; private SwipeTracker mSwipeTracker = new SwipeTracker(); private int mSwipeThreshold; private boolean mDisambiguateSwipe; // Variables for dealing with multiple pointers private int mOldPointerCount = 1; - private float mOldPointerX; - private float mOldPointerY; + private int mOldPointerX; + private int mOldPointerY; private Drawable mKeyBackground; @@ -221,9 +223,6 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener private static final int REPEAT_START_DELAY = 400; private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); - private static int MAX_NEARBY_KEYS = 12; - private int[] mDistances = new int[MAX_NEARBY_KEYS]; - // For multi-tap private int mLastSentIndex; private int mTapCount; @@ -251,6 +250,8 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener private static final int MSG_REPEAT_KEY = 3; private static final int MSG_LOGPRESS_KEY = 4; + private boolean mInKeyRepeat; + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -261,12 +262,11 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener mPreviewText.setVisibility(INVISIBLE); break; case MSG_REPEAT_KEY: - if (repeatKey()) { - startKeyRepeatTimer(REPEAT_INTERVAL); - } + repeatKey(msg.arg1); + startKeyRepeatTimer(REPEAT_INTERVAL, msg.arg1); break; case MSG_LOGPRESS_KEY: - openPopupIfRequired((MotionEvent) msg.obj); + openPopupIfRequired(msg.arg1); break; } } @@ -288,34 +288,38 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener removeMessages(MSG_DISMISS_PREVIEW); } - public void startKeyRepeatTimer(long delay) { - sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY), delay); + public void startKeyRepeatTimer(long delay, int keyIndex) { + mInKeyRepeat = true; + sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0), delay); } - public void startLongPressTimer(MotionEvent me, long delay) { - sendMessageDelayed(obtainMessage(MSG_LOGPRESS_KEY, me), delay); + public void cancelKeyRepeatTimer() { + mInKeyRepeat = false; + removeMessages(MSG_REPEAT_KEY); } - public void cancelLongPressTimer() { - removeMessages(MSG_LOGPRESS_KEY); + public boolean isInKeyRepeat() { + return mInKeyRepeat; } - public void cancelKeyTimers() { - removeMessages(MSG_REPEAT_KEY); + public void startLongPressTimer(int keyIndex, long delay) { removeMessages(MSG_LOGPRESS_KEY); + sendMessageDelayed(obtainMessage(MSG_LOGPRESS_KEY, keyIndex, 0), delay); } - public void cancelKeyTimersAndPopupPreview() { - removeMessages(MSG_REPEAT_KEY); + public void cancelLongPressTimer() { removeMessages(MSG_LOGPRESS_KEY); - removeMessages(MSG_POPUP_PREVIEW); + } + + public void cancelKeyTimers() { + cancelKeyRepeatTimer(); + cancelLongPressTimer(); } public void cancelAllMessages() { - removeMessages(MSG_REPEAT_KEY); - removeMessages(MSG_LOGPRESS_KEY); - removeMessages(MSG_POPUP_PREVIEW); - removeMessages(MSG_DISMISS_PREVIEW); + cancelKeyTimers(); + cancelPopupPreview(); + cancelDismissPreview(); } }; @@ -540,12 +544,11 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener } private void initGestureDetector() { - mGestureDetector = new GestureDetector( - getContext(), new GestureDetector.SimpleOnGestureListener() { + GestureDetector.SimpleOnGestureListener listener = + new GestureDetector.SimpleOnGestureListener() { @Override public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) { - if (mPossiblePoly) return false; final float absX = Math.abs(velocityX); final float absY = Math.abs(velocityY); float deltaX = me2.getX() - me1.getX(); @@ -555,44 +558,33 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener mSwipeTracker.computeCurrentVelocity(1000); final float endingVelocityX = mSwipeTracker.getXVelocity(); final float endingVelocityY = mSwipeTracker.getYVelocity(); - boolean sendDownKey = false; if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) { - if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) { - sendDownKey = true; - } else { + if (mDisambiguateSwipe && endingVelocityX >= velocityX / 4) { swipeRight(); return true; } } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) { - if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) { - sendDownKey = true; - } else { + if (mDisambiguateSwipe && endingVelocityX <= velocityX / 4) { swipeLeft(); return true; } } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) { - if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) { - sendDownKey = true; - } else { + if (mDisambiguateSwipe && endingVelocityY <= velocityY / 4) { swipeUp(); return true; } } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) { - if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) { - sendDownKey = true; - } else { + if (mDisambiguateSwipe && endingVelocityY >= velocityY / 4) { swipeDown(); return true; } } - - if (sendDownKey) { - detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime()); - } return false; } - }); + }; + final boolean ignoreMultitouch = true; + mGestureDetector = new GestureDetector(getContext(), listener, null, ignoreMultitouch); mGestureDetector.setIsLongpressEnabled(false); } @@ -620,10 +612,13 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener showPreview(NOT_A_KEY); } // Remove any pending messages, except dismissing preview - mHandler.cancelKeyTimersAndPopupPreview(); + mHandler.cancelKeyTimers(); + mHandler.cancelPopupPreview(); mKeyboard = keyboard; + LatinImeLogger.onSetKeyboard(mKeyboard); List<Key> keys = mKeyboard.getKeys(); mKeys = keys.toArray(new Key[keys.size()]); + mProximityKeyDetector.setKeyboard(keyboard, mKeys); requestLayout(); // Hint to reallocate the buffer if the size changed mKeyboardChanged = true; @@ -718,14 +713,14 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener * @param enabled whether or not the proximity correction is enabled */ public void setProximityCorrectionEnabled(boolean enabled) { - mProximityCorrectOn = enabled; + mProximityKeyDetector.setProximityCorrectionEnabled(enabled); } /** * Returns true if proximity correction is enabled. */ public boolean isProximityCorrectionEnabled() { - return mProximityCorrectOn; + return mProximityKeyDetector.isProximityCorrectionEnabled(); } /** @@ -777,8 +772,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener dimensionSum += Math.min(key.width, key.height) + key.gap; } if (dimensionSum < 0 || length == 0) return; - mProximityThreshold = (int) (dimensionSum * 1.4f / length); - mProximityThreshold *= mProximityThreshold; // Square it + mProximityKeyDetector.setProximityThreshold((int) (dimensionSum * 1.4f / length)); final float hysteresisPixel = getContext().getResources() .getDimension(R.dimen.key_debounce_hysteresis_distance); @@ -919,54 +913,6 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener mDirtyRect.setEmpty(); } - private int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) { - final Key[] keys = mKeys; - int primaryIndex = NOT_A_KEY; - int closestKey = NOT_A_KEY; - int closestKeyDist = mProximityThreshold + 1; - Arrays.fill(mDistances, Integer.MAX_VALUE); - int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y); - 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); - if (isInside) { - primaryIndex = nearestKeyIndices[i]; - } - - if (((mProximityCorrectOn - && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold) - || isInside) - && key.codes[0] > 32) { - // Find insertion point - final int nCodes = key.codes.length; - if (dist < closestKeyDist) { - closestKeyDist = dist; - closestKey = nearestKeyIndices[i]; - } - - if (allKeys == null) continue; - - for (int j = 0; j < mDistances.length; j++) { - if (mDistances[j] > dist) { - // Make space for nCodes codes - System.arraycopy(mDistances, j, mDistances, j + nCodes, - mDistances.length - j - nCodes); - System.arraycopy(allKeys, j, allKeys, j + nCodes, - allKeys.length - j - nCodes); - System.arraycopy(key.codes, 0, allKeys, j, nCodes); - Arrays.fill(mDistances, j, j + nCodes, dist); - break; - } - } - } - } - if (primaryIndex == NOT_A_KEY) { - primaryIndex = closestKey; - } - return primaryIndex; - } private void detectAndSendKey(int index, int x, int y, long eventTime) { if (index != NOT_A_KEY && index < mKeys.length) { @@ -977,13 +923,12 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener } else { int code = key.codes[0]; //TextEntryState.keyPressedAt(key, x, y); - int[] codes = new int[MAX_NEARBY_KEYS]; - Arrays.fill(codes, NOT_A_KEY); - getKeyIndexAndNearbyCodes(x, y, codes); + int[] codes = mProximityKeyDetector.newCodeArray(); + mProximityKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes); // Multi-tap if (mInMultiTap) { if (mTapCount != -1) { - mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE); + mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE, x, y); } else { mTapCount = 0; } @@ -998,7 +943,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener codes[1] = codes[0]; codes[0] = code; } - mKeyboardActionListener.onKey(code, codes); + mKeyboardActionListener.onKey(code, codes, x, y); mKeyboardActionListener.onRelease(code); } mLastSentIndex = index; @@ -1166,16 +1111,16 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop()); } - private boolean openPopupIfRequired(MotionEvent me) { + private boolean openPopupIfRequired(int keyIndex) { // Check if we have a popup layout specified first. if (mPopupLayout == 0) { return false; } - if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) { + if (keyIndex < 0 || keyIndex >= mKeys.length) { return false; } - Key popupKey = mKeys[mCurrentKey]; + Key popupKey = mKeys[keyIndex]; boolean result = onLongPress(popupKey); if (result) { mAbortKey = true; @@ -1206,8 +1151,8 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener R.id.closeButton); if (closeButton != null) closeButton.setOnClickListener(this); mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() { - public void onKey(int primaryCode, int[] keyCodes) { - mKeyboardActionListener.onKey(primaryCode, keyCodes); + public void onKey(int primaryCode, int[] keyCodes, int x, int y) { + mKeyboardActionListener.onKey(primaryCode, keyCodes, x, y); dismissPopupKeyboard(); } @@ -1270,58 +1215,28 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener return false; } + private int getTouchX(float x) { + return (int)x - getPaddingLeft(); + } + + private int getTouchY(float y) { + return (int)y + mVerticalCorrection - getPaddingTop(); + } + @Override public boolean onTouchEvent(MotionEvent me) { // Convert multi-pointer up/down events to single up/down events to // deal with the typical multi-pointer behavior of two-thumb typing final int pointerCount = me.getPointerCount(); final int action = me.getAction(); - boolean result = false; - final long now = me.getEventTime(); + final long eventTime = me.getEventTime(); - if (pointerCount != mOldPointerCount) { - if (pointerCount == 1) { - // Send a down event for the latest pointer - MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, - me.getX(), me.getY(), me.getMetaState()); - result = onModifiedTouchEvent(down, false); - down.recycle(); - // If it's an up action, then deliver the up as well. - if (action == MotionEvent.ACTION_UP) { - result = onModifiedTouchEvent(me, true); - } - } else { - // Send an up event for the last pointer - MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, - mOldPointerX, mOldPointerY, me.getMetaState()); - result = onModifiedTouchEvent(up, true); - up.recycle(); - } - } else { - if (pointerCount == 1) { - result = onModifiedTouchEvent(me, false); - mOldPointerX = me.getX(); - mOldPointerY = me.getY(); - } else { - // Don't do anything when 2 pointers are down and moving. - result = true; - } + if (pointerCount > 1 && mOldPointerCount > 1) { + // Don't do anything when 2 or more pointers are down and moving. + return true; } - mOldPointerCount = pointerCount; - - return result; - } - - private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) { - int touchX = (int) me.getX() - getPaddingLeft(); - int touchY = (int) me.getY() + mVerticalCorrection - getPaddingTop(); - final int action = me.getAction(); - final long eventTime = me.getEventTime(); - int keyIndex = getKeyIndexAndNearbyCodes(touchX, touchY, null); - mPossiblePoly = possiblePoly; // Track the last few movements to look for spurious swipes. - if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear(); mSwipeTracker.addMovement(me); // Ignore all motion events until a DOWN. @@ -1342,107 +1257,156 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener return true; } + if (mHandler.isInKeyRepeat()) { + // It'll be canceled if 2 or more keys are in action. Otherwise it will keep being in + // the key repeating mode while the key is being pressed. + if (pointerCount > 1) { + mHandler.cancelKeyRepeatTimer(); + } else if (action == MotionEvent.ACTION_MOVE) { + return true; + } + // Up event will pass through. + } + + int touchX = getTouchX(me.getX()); + int touchY = getTouchY(me.getY()); + if (pointerCount != mOldPointerCount) { + if (pointerCount == 1) { + // Send a down event for the latest pointer + onDownEvent(touchX, touchY, eventTime); + // If it's an up action, then deliver the up as well. + if (action == MotionEvent.ACTION_UP) { + onUpEvent(touchX, touchY, eventTime); + } + } else { + // Send an up event for the last pointer + onUpEvent(mOldPointerX, mOldPointerY, eventTime); + } + mOldPointerCount = pointerCount; + return true; + } else { + if (pointerCount == 1) { + onModifiedTouchEvent(action, touchX, touchY, eventTime); + mOldPointerX = touchX; + mOldPointerY = touchY; + return true; + } + } + + return false; + } + + private void onModifiedTouchEvent(int action, int touchX, int touchY, long eventTime) { switch (action) { case MotionEvent.ACTION_DOWN: - mAbortKey = false; - mCurrentKey = keyIndex; - mDownKey = keyIndex; - mStartX = touchX; - mStartY = touchY; - mDebouncer.startMoveDebouncing(touchX, touchY); - mDebouncer.startTimeDebouncing(eventTime); - checkMultiTap(eventTime, keyIndex); - mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ? - mKeys[keyIndex].codes[0] : 0); - if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) { - mRepeatKeyIndex = mCurrentKey; - mHandler.startKeyRepeatTimer(REPEAT_START_DELAY); - repeatKey(); - // Delivering the key could have caused an abort - if (mAbortKey) { - mRepeatKeyIndex = NOT_A_KEY; - break; - } - } - if (mCurrentKey != NOT_A_KEY) { - mHandler.startLongPressTimer(me, LONGPRESS_TIMEOUT); - } - showPreview(keyIndex); + onDownEvent(touchX, touchY, eventTime); break; - case MotionEvent.ACTION_MOVE: - boolean continueLongPress = false; - if (keyIndex != NOT_A_KEY) { - if (mCurrentKey == NOT_A_KEY) { - mCurrentKey = keyIndex; - mDebouncer.updateTimeDebouncing(eventTime); - } else if (mDebouncer.isMinorMoveBounce(touchX, touchY, keyIndex, - mCurrentKey)) { - mDebouncer.updateTimeDebouncing(eventTime); - continueLongPress = true; - } else if (mRepeatKeyIndex == NOT_A_KEY) { - resetMultiTap(); - mDebouncer.resetTimeDebouncing(eventTime, mCurrentKey); - mDebouncer.resetMoveDebouncing(); - mCurrentKey = keyIndex; - } - } - if (!continueLongPress) { - // Cancel old longpress - mHandler.cancelLongPressTimer(); - // Start new longpress if key has changed - if (keyIndex != NOT_A_KEY) { - mHandler.startLongPressTimer(me, LONGPRESS_TIMEOUT); - } - } - /* - * While time debouncing is in effect, mCurrentKey holds the new key and mDebouncer - * 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. - */ - showPreview(mDebouncer.isMinorTimeBounce() ? mDebouncer.getLastKey() : mCurrentKey); + onMoveEvent(touchX, touchY, eventTime); break; - case MotionEvent.ACTION_UP: - mHandler.cancelKeyTimersAndPopupPreview(); - if (mDebouncer.isMinorMoveBounce(touchX, touchY, keyIndex, mCurrentKey)) { - mDebouncer.updateTimeDebouncing(eventTime); - } else { - resetMultiTap(); - mDebouncer.resetTimeDebouncing(eventTime, mCurrentKey); - mCurrentKey = keyIndex; - } - if (mDebouncer.isMinorTimeBounce()) { - mCurrentKey = mDebouncer.getLastKey(); - touchX = mDebouncer.getLastCodeX(); - touchY = mDebouncer.getLastCodeY(); - } - showPreview(NOT_A_KEY); - // If we're not on a repeating key (which sends on a DOWN event) - if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) { - detectAndSendKey(mCurrentKey, touchX, touchY, eventTime); - } - invalidateKey(keyIndex); - mRepeatKeyIndex = NOT_A_KEY; + onUpEvent(touchX, touchY, eventTime); break; - case MotionEvent.ACTION_CANCEL: - mHandler.cancelKeyTimersAndPopupPreview(); - dismissPopupKeyboard(); - mAbortKey = true; - showPreview(NOT_A_KEY); - invalidateKey(mCurrentKey); + onCancelEvent(touchX, touchY, eventTime); break; } + } + + private void onDownEvent(int touchX, int touchY, long eventTime) { + int keyIndex = mProximityKeyDetector.getKeyIndexAndNearbyCodes(touchX, touchY, null); + mAbortKey = false; + mCurrentKey = keyIndex; + mStartX = touchX; + mStartY = touchY; + mDebouncer.startMoveDebouncing(touchX, touchY); + mDebouncer.startTimeDebouncing(eventTime); + checkMultiTap(eventTime, keyIndex); + mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ? mKeys[keyIndex].codes[0] : 0); + if (keyIndex >= 0 && mKeys[keyIndex].repeatable) { + repeatKey(keyIndex); + mHandler.startKeyRepeatTimer(REPEAT_START_DELAY, keyIndex); + // Delivering the key could have caused an abort + if (mAbortKey) { + mHandler.cancelKeyRepeatTimer(); + return; + } + } + if (keyIndex != NOT_A_KEY) { + mHandler.startLongPressTimer(keyIndex, LONGPRESS_TIMEOUT); + } + showPreview(keyIndex); mDebouncer.updateMoveDebouncing(touchX, touchY); - return true; } - private boolean repeatKey() { - Key key = mKeys[mRepeatKeyIndex]; - detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime); - return true; + private void onMoveEvent(int touchX, int touchY, long eventTime) { + int keyIndex = mProximityKeyDetector.getKeyIndexAndNearbyCodes(touchX, touchY, null); + if (keyIndex != NOT_A_KEY) { + if (mCurrentKey == NOT_A_KEY) { + mDebouncer.updateTimeDebouncing(eventTime); + mCurrentKey = keyIndex; + mHandler.startLongPressTimer(keyIndex, LONGPRESS_TIMEOUT); + } else if (mDebouncer.isMinorMoveBounce(touchX, touchY, keyIndex, mCurrentKey)) { + mDebouncer.updateTimeDebouncing(eventTime); + } else { + resetMultiTap(); + mDebouncer.resetTimeDebouncing(eventTime, mCurrentKey); + mDebouncer.resetMoveDebouncing(); + mCurrentKey = keyIndex; + mHandler.startLongPressTimer(keyIndex, LONGPRESS_TIMEOUT); + } + } else { + mHandler.cancelLongPressTimer(); + } + /* + * While time debouncing is in effect, mCurrentKey holds the new key and mDebouncer + * 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. + */ + showPreview(mDebouncer.isMinorTimeBounce() ? mDebouncer.getLastKey() : mCurrentKey); + mDebouncer.updateMoveDebouncing(touchX, touchY); + } + + private void onUpEvent(int touchX, int touchY, long eventTime) { + int keyIndex = mProximityKeyDetector.getKeyIndexAndNearbyCodes(touchX, touchY, null); + boolean wasInKeyRepeat = mHandler.isInKeyRepeat(); + mHandler.cancelKeyTimers(); + mHandler.cancelPopupPreview(); + if (mDebouncer.isMinorMoveBounce(touchX, touchY, keyIndex, mCurrentKey)) { + mDebouncer.updateTimeDebouncing(eventTime); + } else { + resetMultiTap(); + mDebouncer.resetTimeDebouncing(eventTime, mCurrentKey); + mCurrentKey = keyIndex; + } + if (mDebouncer.isMinorTimeBounce()) { + mCurrentKey = mDebouncer.getLastKey(); + touchX = mDebouncer.getLastCodeX(); + touchY = mDebouncer.getLastCodeY(); + } + showPreview(NOT_A_KEY); + // If we're not on a repeating key (which sends on a DOWN event) + if (!wasInKeyRepeat && !mMiniKeyboardOnScreen && !mAbortKey) { + detectAndSendKey(mCurrentKey, touchX, touchY, eventTime); + } + invalidateKey(keyIndex); + } + + private void onCancelEvent(int touchX, int touchY, long eventTime) { + mHandler.cancelKeyTimers(); + mHandler.cancelPopupPreview(); + dismissPopupKeyboard(); + mAbortKey = true; + showPreview(NOT_A_KEY); + invalidateKey(mCurrentKey); + } + + private void repeatKey(int keyIndex) { + Key key = mKeys[keyIndex]; + // 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); } protected void swipeRight() { @@ -1520,114 +1484,4 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener resetMultiTap(); } } - - private static class SwipeTracker { - - static final int NUM_PAST = 4; - static final int LONGEST_PAST_TIME = 200; - - final float mPastX[] = new float[NUM_PAST]; - final float mPastY[] = new float[NUM_PAST]; - final long mPastTime[] = new long[NUM_PAST]; - - float mYVelocity; - float mXVelocity; - - public void clear() { - mPastTime[0] = 0; - } - - public void addMovement(MotionEvent ev) { - long time = ev.getEventTime(); - final int N = ev.getHistorySize(); - for (int i=0; i<N; i++) { - addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i), - ev.getHistoricalEventTime(i)); - } - addPoint(ev.getX(), ev.getY(), time); - } - - private void addPoint(float x, float y, long time) { - int drop = -1; - int i; - final long[] pastTime = mPastTime; - for (i=0; i<NUM_PAST; i++) { - if (pastTime[i] == 0) { - break; - } else if (pastTime[i] < time-LONGEST_PAST_TIME) { - drop = i; - } - } - if (i == NUM_PAST && drop < 0) { - drop = 0; - } - if (drop == i) drop--; - final float[] pastX = mPastX; - final float[] pastY = mPastY; - if (drop >= 0) { - final int start = drop+1; - final int count = NUM_PAST-drop-1; - System.arraycopy(pastX, start, pastX, 0, count); - System.arraycopy(pastY, start, pastY, 0, count); - System.arraycopy(pastTime, start, pastTime, 0, count); - i -= (drop+1); - } - pastX[i] = x; - pastY[i] = y; - pastTime[i] = time; - i++; - if (i < NUM_PAST) { - pastTime[i] = 0; - } - } - - public void computeCurrentVelocity(int units) { - computeCurrentVelocity(units, Float.MAX_VALUE); - } - - public void computeCurrentVelocity(int units, float maxVelocity) { - final float[] pastX = mPastX; - final float[] pastY = mPastY; - final long[] pastTime = mPastTime; - - final float oldestX = pastX[0]; - final float oldestY = pastY[0]; - final long oldestTime = pastTime[0]; - float accumX = 0; - float accumY = 0; - int N=0; - while (N < NUM_PAST) { - if (pastTime[N] == 0) { - break; - } - N++; - } - - for (int i=1; i < N; i++) { - final int dur = (int)(pastTime[i] - oldestTime); - if (dur == 0) continue; - float dist = pastX[i] - oldestX; - float vel = (dist/dur) * units; // pixels/frame. - if (accumX == 0) accumX = vel; - else accumX = (accumX + vel) * .5f; - - dist = pastY[i] - oldestY; - vel = (dist/dur) * units; // pixels/frame. - if (accumY == 0) accumY = vel; - else accumY = (accumY + vel) * .5f; - } - mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) - : Math.min(accumX, maxVelocity); - mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) - : Math.min(accumY, maxVelocity); - } - - public float getXVelocity() { - return mXVelocity; - } - - public float getYVelocity() { - return mYVelocity; - } - } } diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardView.java index 38d9cefb1..e57abd2c5 100644 --- a/java/src/com/android/inputmethod/latin/LatinKeyboardView.java +++ b/java/src/com/android/inputmethod/latin/LatinKeyboardView.java @@ -96,15 +96,21 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { @Override protected boolean onLongPress(Key key) { if (key.codes[0] == Keyboard.KEYCODE_MODE_CHANGE) { - getOnKeyboardActionListener().onKey(KEYCODE_OPTIONS, null); + getOnKeyboardActionListener().onKey(KEYCODE_OPTIONS, null, + LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE, + LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE); return true; } else if (key.codes[0] == Keyboard.KEYCODE_SHIFT) { - getOnKeyboardActionListener().onKey(KEYCODE_SHIFT_LONGPRESS, null); + getOnKeyboardActionListener().onKey(KEYCODE_SHIFT_LONGPRESS, null, + LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE, + LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE); invalidateAllKeys(); return true; } else if (key.codes[0] == '0' && getKeyboard() == mPhoneKeyboard) { // Long pressing on 0 in phone number keypad gives you a '+'. - getOnKeyboardActionListener().onKey('+', null); + getOnKeyboardActionListener().onKey( + '+', null, LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE, + LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE); return true; } else { return super.onLongPress(key); @@ -235,7 +241,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { if (languageDirection != 0) { getOnKeyboardActionListener().onKey( languageDirection == 1 ? KEYCODE_NEXT_LANGUAGE : KEYCODE_PREV_LANGUAGE, - null); + null, mLastX, mLastY); me.setAction(MotionEvent.ACTION_CANCEL); keyboard.keyReleased(); return super.onTouchEvent(me); @@ -366,8 +372,8 @@ public class LatinKeyboardView extends LatinKeyboardBaseView { ExtensionKeyboardListener(OnKeyboardActionListener target) { mTarget = target; } - public void onKey(int primaryCode, int[] keyCodes) { - mTarget.onKey(primaryCode, keyCodes); + 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); diff --git a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java b/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java new file mode 100644 index 000000000..eae2d7f08 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java @@ -0,0 +1,113 @@ +/* + * 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 android.inputmethodservice.Keyboard; +import android.inputmethodservice.Keyboard.Key; + +import java.util.Arrays; + +class ProximityKeyDetector { + 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; + } + + 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 + 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); + 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); + if (isInside) { + primaryIndex = nearestKeyIndices[i]; + } + + if (((mProximityCorrectOn + && (dist = key.squaredDistanceFrom(x, y)) < mProximityThresholdSquare) + || isInside) + && key.codes[0] > 32) { + // Find insertion point + final int nCodes = key.codes.length; + if (dist < closestKeyDist) { + closestKeyDist = dist; + closestKey = nearestKeyIndices[i]; + } + + if (allKeys == null) continue; + + for (int j = 0; j < distances.length; j++) { + if (distances[j] > dist) { + // Make space for nCodes codes + System.arraycopy(distances, j, distances, j + nCodes, + distances.length - j - nCodes); + System.arraycopy(allKeys, j, allKeys, j + nCodes, + allKeys.length - j - nCodes); + System.arraycopy(key.codes, 0, allKeys, j, nCodes); + Arrays.fill(distances, j, j + nCodes, dist); + break; + } + } + } + } + if (primaryIndex == LatinKeyboardBaseView.NOT_A_KEY) { + primaryIndex = closestKey; + } + return primaryIndex; + } +}
\ No newline at end of file diff --git a/java/src/com/android/inputmethod/latin/SwipeTracker.java b/java/src/com/android/inputmethod/latin/SwipeTracker.java new file mode 100644 index 000000000..970e91965 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/SwipeTracker.java @@ -0,0 +1,157 @@ +/* + * 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 android.view.MotionEvent; + +class SwipeTracker { + private static final int NUM_PAST = 4; + private static final int LONGEST_PAST_TIME = 200; + + final EventRingBuffer mBuffer = new EventRingBuffer(NUM_PAST); + + private float mYVelocity; + private float mXVelocity; + + public void addMovement(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mBuffer.clear(); + return; + } + long time = ev.getEventTime(); + final int count = ev.getHistorySize(); + for (int i = 0; i < count; i++) { + addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i), ev.getHistoricalEventTime(i)); + } + addPoint(ev.getX(), ev.getY(), time); + } + + private void addPoint(float x, float y, long time) { + final EventRingBuffer buffer = mBuffer; + while (buffer.size() > 0) { + long lastT = buffer.getTime(0); + if (lastT >= time - LONGEST_PAST_TIME) + break; + buffer.dropOldest(); + } + buffer.add(x, y, time); + } + + public void computeCurrentVelocity(int units) { + computeCurrentVelocity(units, Float.MAX_VALUE); + } + + public void computeCurrentVelocity(int units, float maxVelocity) { + final EventRingBuffer buffer = mBuffer; + final float oldestX = buffer.getX(0); + final float oldestY = buffer.getY(0); + final long oldestTime = buffer.getTime(0); + + float accumX = 0; + float accumY = 0; + final int count = buffer.size(); + for (int pos = 1; pos < count; pos++) { + final int dur = (int)(buffer.getTime(pos) - oldestTime); + if (dur == 0) continue; + float dist = buffer.getX(pos) - oldestX; + float vel = (dist / dur) * units; // pixels/frame. + if (accumX == 0) accumX = vel; + else accumX = (accumX + vel) * .5f; + + dist = buffer.getY(pos) - oldestY; + vel = (dist / dur) * units; // pixels/frame. + if (accumY == 0) accumY = vel; + else accumY = (accumY + vel) * .5f; + } + mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) + : Math.min(accumX, maxVelocity); + mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) + : Math.min(accumY, maxVelocity); + } + + public float getXVelocity() { + return mXVelocity; + } + + public float getYVelocity() { + return mYVelocity; + } + + static class EventRingBuffer { + private final int bufSize; + private final float xBuf[]; + private final float yBuf[]; + private final long timeBuf[]; + private int top; // points new event + private int end; // points oldest event + private int count; // the number of valid data + + public EventRingBuffer(int max) { + this.bufSize = max; + xBuf = new float[max]; + yBuf = new float[max]; + timeBuf = new long[max]; + clear(); + } + + public void clear() { + top = end = count = 0; + } + + public int size() { + return count; + } + + // Position 0 points oldest event + private int index(int pos) { + return (end + pos) % bufSize; + } + + private int advance(int index) { + return (index + 1) % bufSize; + } + + public void add(float x, float y, long time) { + xBuf[top] = x; + yBuf[top] = y; + timeBuf[top] = time; + top = advance(top); + if (count < bufSize) { + count++; + } else { + end = advance(end); + } + } + + public float getX(int pos) { + return xBuf[index(pos)]; + } + + public float getY(int pos) { + return yBuf[index(pos)]; + } + + public long getTime(int pos) { + return timeBuf[index(pos)]; + } + + public void dropOldest() { + count--; + end = advance(end); + } + } +}
\ No newline at end of file |