diff --git a/dictionaries/de_wordlist.combined.gz b/dictionaries/de_wordlist.combined.gz index 5db1aa4f3..207597a1b 100644 Binary files a/dictionaries/de_wordlist.combined.gz and b/dictionaries/de_wordlist.combined.gz differ diff --git a/dictionaries/en_GB_wordlist.combined.gz b/dictionaries/en_GB_wordlist.combined.gz index b5909c2da..62a3d5587 100644 Binary files a/dictionaries/en_GB_wordlist.combined.gz and b/dictionaries/en_GB_wordlist.combined.gz differ diff --git a/dictionaries/en_wordlist.combined.gz b/dictionaries/en_wordlist.combined.gz index 62c454049..d550e0711 100644 Binary files a/dictionaries/en_wordlist.combined.gz and b/dictionaries/en_wordlist.combined.gz differ diff --git a/dictionaries/fr_wordlist.combined.gz b/dictionaries/fr_wordlist.combined.gz index 95a87e630..864c0409b 100644 Binary files a/dictionaries/fr_wordlist.combined.gz and b/dictionaries/fr_wordlist.combined.gz differ diff --git a/dictionaries/ru_wordlist.combined.gz b/dictionaries/ru_wordlist.combined.gz index 62ae12e90..3f17affd6 100644 Binary files a/dictionaries/ru_wordlist.combined.gz and b/dictionaries/ru_wordlist.combined.gz differ diff --git a/java/res/raw/main_de.dict b/java/res/raw/main_de.dict index 6122cd3f0..59df8b829 100644 Binary files a/java/res/raw/main_de.dict and b/java/res/raw/main_de.dict differ diff --git a/java/res/raw/main_en.dict b/java/res/raw/main_en.dict index de1170afd..85ee809de 100644 Binary files a/java/res/raw/main_en.dict and b/java/res/raw/main_en.dict differ diff --git a/java/res/raw/main_es.dict b/java/res/raw/main_es.dict index 7a4daf1f2..cb17926e0 100644 Binary files a/java/res/raw/main_es.dict and b/java/res/raw/main_es.dict differ diff --git a/java/res/raw/main_fr.dict b/java/res/raw/main_fr.dict index c607d0e36..53fd8d139 100644 Binary files a/java/res/raw/main_fr.dict and b/java/res/raw/main_fr.dict differ diff --git a/java/res/raw/main_it.dict b/java/res/raw/main_it.dict index b93a55c93..59b78d643 100644 Binary files a/java/res/raw/main_it.dict and b/java/res/raw/main_it.dict differ diff --git a/java/res/raw/main_pt_br.dict b/java/res/raw/main_pt_br.dict index 66ac3f953..2a8af6f6e 100644 Binary files a/java/res/raw/main_pt_br.dict and b/java/res/raw/main_pt_br.dict differ diff --git a/java/res/raw/main_ru.dict b/java/res/raw/main_ru.dict index 050b0b8c4..dd3b7b3ab 100644 Binary files a/java/res/raw/main_ru.dict and b/java/res/raw/main_ru.dict differ diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java index b612f0927..14da9ebe6 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java @@ -80,6 +80,8 @@ public interface KeyboardActionListener { */ public void onEndBatchInput(InputPointers batchPointers); + public void onCancelBatchInput(); + /** * Called when user released a finger outside any key. */ @@ -107,6 +109,8 @@ public interface KeyboardActionListener { @Override public void onEndBatchInput(InputPointers batchPointers) {} @Override + public void onCancelBatchInput() {} + @Override public void onCancelInput() {} @Override public boolean onCustomRequest(int requestCode) { diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index c2e31ee1e..c1c105e8d 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -125,7 +125,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } private void setContextThemeWrapper(Context context, KeyboardTheme keyboardTheme) { - if (mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) { + if (mThemeContext == null || mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) { mKeyboardTheme = keyboardTheme; mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId); KeyboardLayoutSet.clearKeyboardCache(); diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index c86805232..c8052af6a 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -305,8 +305,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // true if keyboard layout has been changed. private boolean mKeyboardLayoutHasBeenChanged; - // true if event is already translated to a key action. - private boolean mKeyAlreadyProcessed; + // true if this pointer is no longer tracking touch event. + private boolean mIsTrackingCanceled; // true if this pointer has been long-pressed and is showing a more keys panel. private boolean mIsShowingMoreKeysPanel; @@ -517,7 +517,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { mKeyboard = keyDetector.getKeyboard(); final int keyWidth = mKeyboard.mMostCommonKeyWidth; final int keyHeight = mKeyboard.mMostCommonKeyHeight; - mGestureStrokeWithPreviewPoints.setKeyboardGeometry(keyWidth); + mGestureStrokeWithPreviewPoints.setKeyboardGeometry(keyWidth, mKeyboard.mOccupiedHeight); final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); if (newKey != mCurrentKey) { if (mDrawingProxy != null) { @@ -730,13 +730,15 @@ public final class PointerTracker implements PointerTrackerQueue.Element { synchronized (sAggregratedPointers) { mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregratedPointers); if (getActivePointerTrackerCount() == 1) { - if (DEBUG_LISTENER) { - Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d", - mPointerId, sAggregratedPointers.getPointerSize())); - } sInGesture = false; sTimeRecorder.onEndBatchInput(eventTime); - mListener.onEndBatchInput(sAggregratedPointers); + if (!mIsTrackingCanceled) { + if (DEBUG_LISTENER) { + Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d", + mPointerId, sAggregratedPointers.getPointerSize())); + } + mListener.onEndBatchInput(sAggregratedPointers); + } } } mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this)); @@ -784,7 +786,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.pointerTracker_onDownEvent(deltaT, distance * distance); } - mKeyAlreadyProcessed = true; + cancelTracking(); return; } } @@ -821,7 +823,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { || (key != null && key.isModifier()) || mKeyDetector.alwaysAllowsSlidingInput(); mKeyboardLayoutHasBeenChanged = false; - mKeyAlreadyProcessed = false; + mIsTrackingCanceled = false; resetSlidingKeyInput(); if (key != null) { // This onPress call may have changed keyboard layout. Those cases are detected at @@ -853,7 +855,17 @@ public final class PointerTracker implements PointerTrackerQueue.Element { final boolean isMajorEvent, final Key key) { final int gestureTime = (int)(eventTime - sGestureFirstDownTime); if (mIsDetectingGesture) { - mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isMajorEvent); + final boolean onValidArea = mGestureStrokeWithPreviewPoints.addPointOnKeyboard( + x, y, gestureTime, isMajorEvent); + if (!onValidArea) { + sPointerTrackerQueue.cancelAllPointerTracker(); + if (DEBUG_LISTENER) { + Log.d(TAG, String.format("[%d] onCancelBatchInput: batchPoints=%d", + mPointerId, sAggregratedPointers.getPointerSize())); + } + mListener.onCancelBatchInput(); + return; + } mayStartBatchInput(key); if (sInGesture) { mayUpdateBatchInput(eventTime, key); @@ -865,7 +877,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (DEBUG_MOVE_EVENT) { printTouchEvent("onMoveEvent:", x, y, eventTime); } - if (mKeyAlreadyProcessed) { + if (mIsTrackingCanceled) { return; } @@ -979,11 +991,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element { + " detected sliding finger while multi touching", mPointerId)); } onUpEvent(x, y, eventTime); - mKeyAlreadyProcessed = true; + cancelTracking(); setReleasedKeyGraphics(oldKey); } else { if (!mIsDetectingGesture) { - mKeyAlreadyProcessed = true; + cancelTracking(); } setReleasedKeyGraphics(oldKey); } @@ -997,7 +1009,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { onMoveToNewKey(null, x, y); } else { if (!mIsDetectingGesture) { - mKeyAlreadyProcessed = true; + cancelTracking(); } } } @@ -1060,7 +1072,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime); } onUpEventInternal(eventTime); - mKeyAlreadyProcessed = true; + cancelTracking(); } private void onUpEventInternal(final long eventTime) { @@ -1084,7 +1096,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { return; } - if (mKeyAlreadyProcessed) { + if (mIsTrackingCanceled) { return; } if (currentKey != null && !currentKey.isRepeatable()) { @@ -1098,8 +1110,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element { onDownEvent(x, y, SystemClock.uptimeMillis(), handler); } + @Override + public void cancelTracking() { + mIsTrackingCanceled = true; + } + public void onLongPressed() { - mKeyAlreadyProcessed = true; + cancelTracking(); setReleasedKeyGraphics(mCurrentKey); sPointerTrackerQueue.remove(this); } @@ -1202,6 +1219,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element { final Key key = mKeyDetector.detectHitKey(x, y); final String code = KeyDetector.printableCode(key); Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId, - (mKeyAlreadyProcessed ? "-" : " "), title, x, y, eventTime, code)); + (mIsTrackingCanceled ? "-" : " "), title, x, y, eventTime, code)); } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java index f8244dd5b..aab14e968 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java @@ -27,6 +27,10 @@ public class GestureStroke { private static final boolean DEBUG = false; private static final boolean DEBUG_SPEED = false; + // The height of extra area above the keyboard to draw gesture trails. + // Proportional to the keyboard height. + public static final float EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO = 0.25f; + public static final int DEFAULT_CAPACITY = 128; private final int mPointerId; @@ -37,6 +41,8 @@ public class GestureStroke { private final GestureStrokeParams mParams; private int mKeyWidth; // pixel + private int mMinYCoordinate; // pixel + private int mMaxYCoordinate; // pixel // Static threshold for starting gesture detection private int mDetectFastMoveSpeedThreshold; // pixel /sec private int mDetectFastMoveTime; @@ -135,8 +141,10 @@ public class GestureStroke { mParams = params; } - public void setKeyboardGeometry(final int keyWidth) { + public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) { mKeyWidth = keyWidth; + mMinYCoordinate = -(int)(keyboardHeight * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); + mMaxYCoordinate = keyboardHeight - 1; // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key? mDetectFastMoveSpeedThreshold = (int)(keyWidth * mParams.mDetectFastMoveSpeedThreshold); mGestureDynamicDistanceThresholdFrom = @@ -167,7 +175,7 @@ public class GestureStroke { elapsedTimeAfterTyping, mAfterFastTyping ? " afterFastTyping" : "")); } final int elapsedTimeFromFirstDown = (int)(downTime - gestureFirstDownTime); - addPoint(x, y, elapsedTimeFromFirstDown, true /* isMajorEvent */); + addPointOnKeyboard(x, y, elapsedTimeFromFirstDown, true /* isMajorEvent */); } private int getGestureDynamicDistanceThreshold(final int deltaTime) { @@ -277,7 +285,17 @@ public class GestureStroke { return dist; } - public void addPoint(final int x, final int y, final int time, final boolean isMajorEvent) { + /** + * Add a touch event as a gesture point. Returns true if the touch event is on the valid + * gesture area. + * @param x the x-coordinate of the touch event + * @param y the y-coordinate of the touch event + * @param time the elapsed time in millisecond from the first gesture down + * @param isMajorEvent false if this is a historical move event + * @return true if the touch event is on the valid gesture area + */ + public boolean addPointOnKeyboard(final int x, final int y, final int time, + final boolean isMajorEvent) { final int size = mEventTimes.getLength(); if (size <= 0) { // Down event @@ -293,6 +311,7 @@ public class GestureStroke { updateIncrementalRecognitionSize(x, y, time); updateMajorEvent(x, y, time); } + return y >= mMinYCoordinate && y < mMaxYCoordinate; } private void updateIncrementalRecognitionSize(final int x, final int y, final int time) { diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java index 8192c9076..7ab7e9aad 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java @@ -56,8 +56,8 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { } @Override - public void setKeyboardGeometry(final int keyWidth) { - super.setKeyboardGeometry(keyWidth); + public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) { + super.setKeyboardGeometry(keyWidth, keyboardHeight); final float sampleLength = keyWidth * MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH; mMinPreviewSampleLengthSquare = (int)(sampleLength * sampleLength); } @@ -69,8 +69,9 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { } @Override - public void addPoint(final int x, final int y, final int time, final boolean isMajorEvent) { - super.addPoint(x, y, time, isMajorEvent); + public boolean addPointOnKeyboard(final int x, final int y, final int time, + final boolean isMajorEvent) { + final boolean onValidArea = super.addPointOnKeyboard(x, y, time, isMajorEvent); if (isMajorEvent || needsSampling(x, y)) { mPreviewEventTimes.add(time); mPreviewXCoordinates.add(x); @@ -78,6 +79,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { mLastX = x; mLastY = y; } + return onValidArea; } public void appendPreviewStroke(final ResizableIntArray eventTimes, diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java index a52f202aa..00fc885e8 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java @@ -30,6 +30,7 @@ public final class PointerTrackerQueue { public boolean isModifier(); public boolean isInSlidingKeyInput(); public void onPhantomUpEvent(long eventTime); + public void cancelTracking(); } private static final int INITIAL_CAPACITY = 10; @@ -182,6 +183,15 @@ public final class PointerTrackerQueue { return false; } + public synchronized void cancelAllPointerTracker() { + final ArrayList expandableArray = mExpandableArrayOfActivePointers; + final int arraySize = mArraySize; + for (int index = 0; index < arraySize; index++) { + final Element element = expandableArray.get(index); + element.cancelTracking(); + } + } + @Override public synchronized String toString() { final StringBuilder sb = new StringBuilder(); diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java index 776ac0204..0d44ecdff 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java @@ -40,10 +40,6 @@ import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; public final class PreviewPlacerView extends RelativeLayout { - // The height of extra area above the keyboard to draw gesture trails. - // Proportional to the keyboard height. - private static final float EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO = 0.25f; - private final int mGestureFloatingPreviewTextColor; private final int mGestureFloatingPreviewTextOffset; private final int mGestureFloatingPreviewColor; @@ -175,7 +171,7 @@ public final class PreviewPlacerView extends RelativeLayout { public void setKeyboardViewGeometry(final int x, final int y, final int w, final int h) { mKeyboardViewOriginX = x; mKeyboardViewOriginY = y; - mOffscreenOffsetY = (int)(h * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); + mOffscreenOffsetY = (int)(h * GestureStroke.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); mOffscreenWidth = w; mOffscreenHeight = mOffscreenOffsetY + h; } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index e3508ac44..dc3ad4ff0 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1528,6 +1528,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction .sendToTarget(); } + public void onCancelBatchInput(final LatinIME latinIme) { + mInBatchInput = false; + latinIme.mHandler.showGesturePreviewAndSuggestionStrip( + SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */); + } + // Run in the UI thread. public synchronized SuggestedWords onEndBatchInput(final InputPointers batchPointers, final LatinIME latinIme) { @@ -1613,6 +1619,11 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mKeyboardSwitcher.onCancelInput(); } + @Override + public void onCancelBatchInput() { + BatchInputUpdater.getInstance().onCancelBatchInput(this); + } + private void handleBackspace(final int spaceState) { // In many cases, we may have to put the keyboard in auto-shift state again. However // we want to wait a few milliseconds before doing it to avoid the keyboard flashing diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h index bbef7329d..894e5f1c9 100644 --- a/native/jni/src/defines.h +++ b/native/jni/src/defines.h @@ -210,6 +210,7 @@ static inline void prof_out(void) { #define DEBUG_WORDS_PRIORITY_QUEUE false #define DEBUG_SAMPLING_POINTS true #define DEBUG_POINTS_PROBABILITY true +#define DEBUG_DOUBLE_LETTER true #ifdef FLAG_FULL_DBG #define DEBUG_GEO_FULL true @@ -232,6 +233,7 @@ static inline void prof_out(void) { #define DEBUG_WORDS_PRIORITY_QUEUE false #define DEBUG_SAMPLING_POINTS false #define DEBUG_POINTS_PROBABILITY false +#define DEBUG_DOUBLE_LETTER false #define DEBUG_GEO_FULL false diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp index e64d46d01..740e1a24e 100644 --- a/native/jni/src/proximity_info_state.cpp +++ b/native/jni/src/proximity_info_state.cpp @@ -21,7 +21,6 @@ #define LOG_TAG "LatinIME: proximity_info_state.cpp" #include "defines.h" -#include "geometry_utils.h" #include "proximity_info.h" #include "proximity_info_state.h" @@ -37,7 +36,6 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi const ProximityInfo *proximityInfo, const int *const inputCodes, const int inputSize, const int *const xCoordinates, const int *const yCoordinates, const int *const times, const int *const pointerIds, const bool isGeometric) { - if (isGeometric) { mIsContinuationPossible = checkAndReturnIsContinuationPossible( inputSize, xCoordinates, yCoordinates, times); @@ -106,7 +104,8 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi mDistanceCache.clear(); mNearKeysVector.clear(); mSearchKeysVector.clear(); - mRelativeSpeeds.clear(); + mSpeedRates.clear(); + mBeelineSpeedRates.clear(); mCharProbabilities.clear(); mDirections.clear(); } @@ -117,6 +116,14 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi mSampledInputSize = 0; if (xCoordinates && yCoordinates) { + if (DEBUG_SAMPLING_POINTS) { + if (isGeometric) { + for (int i = 0; i < inputSize; ++i) { + AKLOGI("(%d) x %d, y %d, time %d", + i, xCoordinates[i], yCoordinates[i], times[i]); + } + } + } const bool proximityOnly = !isGeometric && (xCoordinates[0] < 0 || yCoordinates[0] < 0); int lastInputIndex = pushTouchPointStartIndex; for (int i = lastInputIndex; i < inputSize; ++i) { @@ -179,7 +186,8 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi } if (mSampledInputSize > 0 && isGeometric) { - refreshRelativeSpeed(inputSize, xCoordinates, yCoordinates, times, lastSavedInputSize); + refreshSpeedRates(inputSize, xCoordinates, yCoordinates, times, lastSavedInputSize); + refreshBeelineSpeedRates(inputSize, xCoordinates, yCoordinates, times); } if (DEBUG_GEO_FULL) { @@ -242,7 +250,13 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi originalY << ";"; } } + AKLOGI("===== sampled points ====="); for (int i = 0; i < mSampledInputSize; ++i) { + if (isGeometric) { + AKLOGI("%d: x = %d, y = %d, time = %d, relative speed = %.4f, beeline speed = %.4f", + i, mSampledInputXs[i], mSampledInputYs[i], mTimes[i], mSpeedRates[i], + getBeelineSpeedRate(i)); + } sampledX << mSampledInputXs[i]; sampledY << mSampledInputYs[i]; if (i != mSampledInputSize - 1) { @@ -303,13 +317,13 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi } } -void ProximityInfoState::refreshRelativeSpeed(const int inputSize, const int *const xCoordinates, +void ProximityInfoState::refreshSpeedRates(const int inputSize, const int *const xCoordinates, const int *const yCoordinates, const int *const times, const int lastSavedInputSize) { // Relative speed calculation. const int sumDuration = mTimes.back() - mTimes.front(); const int sumLength = mLengthCache.back() - mLengthCache.front(); - const float averageSpeed = static_cast(sumLength) / static_cast(sumDuration); - mRelativeSpeeds.resize(mSampledInputSize); + mAverageSpeed = static_cast(sumLength) / static_cast(sumDuration); + mSpeedRates.resize(mSampledInputSize); for (int i = lastSavedInputSize; i < mSampledInputSize; ++i) { const int index = mInputIndice[i]; int length = 0; @@ -331,16 +345,17 @@ void ProximityInfoState::refreshRelativeSpeed(const int inputSize, const int *co if (i > 0 && j < mInputIndice[i - 1]) { break; } + // TODO: use mLengthCache instead? length += getDistanceInt(xCoordinates[j], yCoordinates[j], xCoordinates[j + 1], yCoordinates[j + 1]); duration += times[j + 1] - times[j]; } if (duration == 0 || sumDuration == 0) { // Cannot calculate speed; thus, it gives an average value (1.0); - mRelativeSpeeds[i] = 1.0f; + mSpeedRates[i] = 1.0f; } else { const float speed = static_cast(length) / static_cast(duration); - mRelativeSpeeds[i] = speed / averageSpeed; + mSpeedRates[i] = speed / mAverageSpeed; } } @@ -351,6 +366,69 @@ void ProximityInfoState::refreshRelativeSpeed(const int inputSize, const int *co } } +void ProximityInfoState::refreshBeelineSpeedRates(const int inputSize, + const int *const xCoordinates, const int *const yCoordinates, const int * times) { + mBeelineSpeedRates.resize(mSampledInputSize); + for (int i = 0; i < mSampledInputSize; ++i) { + mBeelineSpeedRates[i] = calculateBeelineSpeedRate( + i, inputSize, xCoordinates, yCoordinates, times); + } +} + +float ProximityInfoState::calculateBeelineSpeedRate( + const int id, const int inputSize, const int *const xCoordinates, + const int *const yCoordinates, const int * times) const { + static const int MAX_PERCENTILE = 100; + static const int LOOKUP_TIME_PERCENTILE = 30; + static const int LOOKUP_RADIUS_PERCENTILE = 50; + if (mSampledInputSize <= 0 || mAverageSpeed < 0.1f) { + return 1.0f; + } + const int lookupRadius = + mProximityInfo->getMostCommonKeyWidth() * LOOKUP_RADIUS_PERCENTILE / MAX_PERCENTILE; + const int x0 = mSampledInputXs[id]; + const int y0 = mSampledInputYs[id]; + const int lookupTime = + (mTimes.back() - mTimes.front()) * LOOKUP_TIME_PERCENTILE / MAX_PERCENTILE; + if (lookupTime <= 0) { + return 1.0f; + } + int tempTime = 0; + int tempBeelineDistance = 0; + int start = mInputIndice[id]; + // lookup forward + while (start > 0 && tempTime < lookupTime && tempBeelineDistance < lookupRadius) { + tempTime += times[start] - times[start - 1]; + --start; + tempBeelineDistance = getDistanceInt(x0, y0, xCoordinates[start], yCoordinates[start]); + } + tempTime= 0; + tempBeelineDistance = 0; + int end = mInputIndice[id]; + // lookup backward + while (end < static_cast(inputSize - 1) && tempTime < lookupTime + && tempBeelineDistance < lookupRadius) { + tempTime += times[end + 1] - times[end]; + ++end; + tempBeelineDistance = getDistanceInt(x0, y0, xCoordinates[start], yCoordinates[start]); + } + + if (start == end) { + return 1.0f; + } + + const int x2 = xCoordinates[start]; + const int y2 = yCoordinates[start]; + const int x3 = xCoordinates[end]; + const int y3 = yCoordinates[end]; + const int beelineDistance = getDistanceInt(x2, y2, x3, y3); + const int time = times[end] - times[start]; + if (time <= 0) { + return 1.0f; + } + return (static_cast(beelineDistance) / static_cast(time)) / mAverageSpeed; +} + bool ProximityInfoState::checkAndReturnIsContinuationPossible(const int inputSize, const int *const xCoordinates, const int *const yCoordinates, const int *const times) { for (int i = 0; i < mSampledInputSize; ++i) { @@ -777,7 +855,7 @@ void ProximityInfoState::updateAlignPointProbabilities(const int start) { float skipProbability = MAX_SKIP_PROBABILITY; const float currentAngle = getPointAngle(i); - const float relativeSpeed = getRelativeSpeed(i); + const float speedRate = getSpeedRate(i); float nearestKeyDistance = static_cast(MAX_POINT_TO_KEY_LENGTH); for (int j = 0; j < keyCount; ++j) { @@ -801,19 +879,19 @@ void ProximityInfoState::updateAlignPointProbabilities(const int start) { skipProbability *= SKIP_LAST_POINT_PROBABILITY; } else { // If the current speed is relatively slower than adjacent keys, we promote this point. - if (getRelativeSpeed(i - 1) - SPEED_MARGIN > relativeSpeed - && relativeSpeed < getRelativeSpeed(i + 1) - SPEED_MARGIN) { + if (getSpeedRate(i - 1) - SPEED_MARGIN > speedRate + && speedRate < getSpeedRate(i + 1) - SPEED_MARGIN) { if (currentAngle < CORNER_ANGLE_THRESHOLD) { - skipProbability *= min(1.0f, relativeSpeed + skipProbability *= min(1.0f, speedRate * SLOW_STRAIGHT_WEIGHT_FOR_SKIP_PROBABILITY); } else { // If the angle is small enough, we promote this point more. (e.g. pit vs put) - skipProbability *= min(1.0f, relativeSpeed * SPEED_WEIGHT_FOR_SKIP_PROBABILITY + skipProbability *= min(1.0f, speedRate * SPEED_WEIGHT_FOR_SKIP_PROBABILITY + MIN_SPEED_RATE_FOR_SKIP_PROBABILITY); } } - skipProbability *= min(1.0f, relativeSpeed * nearestKeyDistance * + skipProbability *= min(1.0f, speedRate * nearestKeyDistance * NEAREST_DISTANCE_WEIGHT + NEAREST_DISTANCE_BIAS); // Adjusts skip probability by a rate depending on angle. @@ -850,10 +928,10 @@ void ProximityInfoState::updateAlignPointProbabilities(const int start) { static const float MAX_SPEEDxNEAREST_RATE_FOR_STANDERD_DIVIATION = 0.15f; static const float MIN_STANDERD_DIVIATION = 0.37f; - const float speedxAngleRate = min(relativeSpeed * currentAngle / M_PI_F + const float speedxAngleRate = min(speedRate * currentAngle / M_PI_F * SPEEDxANGLE_WEIGHT_FOR_STANDARD_DIVIATION, MAX_SPEEDxANGLE_RATE_FOR_STANDERD_DIVIATION); - const float speedxNearestKeyDistanceRate = min(relativeSpeed * nearestKeyDistance + const float speedxNearestKeyDistanceRate = min(speedRate * nearestKeyDistance * SPEEDxNEAREST_WEIGHT_FOR_STANDARD_DIVIATION, MAX_SPEEDxNEAREST_RATE_FOR_STANDERD_DIVIATION); const float sigma = speedxAngleRate + speedxNearestKeyDistanceRate + MIN_STANDERD_DIVIATION; @@ -932,7 +1010,7 @@ void ProximityInfoState::updateAlignPointProbabilities(const int start) { std::stringstream sstream; sstream << i << ", "; sstream << "(" << mSampledInputXs[i] << ", " << mSampledInputYs[i] << "), "; - sstream << "Speed: "<< getRelativeSpeed(i) << ", "; + sstream << "Speed: "<< getSpeedRate(i) << ", "; sstream << "Angle: "<< getPointAngle(i) << ", \n"; for (hash_map_compat::iterator it = mCharProbabilities[i].begin(); @@ -1066,5 +1144,4 @@ float ProximityInfoState::getProbability(const int index, const int keyIndex) co } return static_cast(MAX_POINT_TO_KEY_LENGTH); } - } // namespace latinime diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h index 79dd5be53..97281af1c 100644 --- a/native/jni/src/proximity_info_state.h +++ b/native/jni/src/proximity_info_state.h @@ -25,6 +25,7 @@ #include "char_utils.h" #include "defines.h" +#include "geometry_utils.h" #include "hash_map_compat.h" namespace latinime { @@ -51,13 +52,13 @@ class ProximityInfoState { // Defined here // ///////////////////////////////////////// AK_FORCE_INLINE ProximityInfoState() - : mProximityInfo(0), mMaxPointToKeyLength(0), + : mProximityInfo(0), mMaxPointToKeyLength(0.0f), mAverageSpeed(0.0f), mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0), mLocaleStr(), mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0), mIsContinuationPossible(false), mSampledInputXs(), mSampledInputYs(), mTimes(), - mInputIndice(), mDistanceCache(), mLengthCache(), mRelativeSpeeds(), mDirections(), - mCharProbabilities(), mNearKeysVector(), mSearchKeysVector(), - mTouchPositionCorrectionEnabled(false), mSampledInputSize(0) { + mInputIndice(), mLengthCache(), mDistanceCache(), mSpeedRates(), + mDirections(), mBeelineSpeedRates(), mCharProbabilities(), mNearKeysVector(), + mSearchKeysVector(), mTouchPositionCorrectionEnabled(false), mSampledInputSize(0) { memset(mInputCodes, 0, sizeof(mInputCodes)); memset(mNormalizedSquaredDistances, 0, sizeof(mNormalizedSquaredDistances)); memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord)); @@ -162,8 +163,12 @@ class ProximityInfoState { int32_t getAllPossibleChars( const size_t startIndex, int32_t *const filter, const int32_t filterSize) const; - float getRelativeSpeed(const int index) const { - return mRelativeSpeeds[index]; + float getSpeedRate(const int index) const { + return mSpeedRates[index]; + } + + AK_FORCE_INLINE float getBeelineSpeedRate(const int id) const { + return mBeelineSpeedRates[id]; } float getDirection(const int index) const { @@ -228,12 +233,17 @@ class ProximityInfoState { void popInputData(); void updateAlignPointProbabilities(const int start); bool suppressCharProbabilities(const int index1, const int index2); - void refreshRelativeSpeed(const int inputSize, const int *const xCoordinates, + void refreshSpeedRates(const int inputSize, const int *const xCoordinates, const int *const yCoordinates, const int *const times, const int lastSavedInputSize); + void refreshBeelineSpeedRates(const int inputSize, + const int *const xCoordinates, const int *const yCoordinates, const int * times); + float calculateBeelineSpeedRate(const int id, const int inputSize, + const int *const xCoordinates, const int *const yCoordinates, const int * times) const; // const const ProximityInfo *mProximityInfo; float mMaxPointToKeyLength; + float mAverageSpeed; bool mHasTouchPositionCorrectionData; int mMostCommonKeyWidthSquare; std::string mLocaleStr; @@ -248,10 +258,11 @@ class ProximityInfoState { std::vector mSampledInputYs; std::vector mTimes; std::vector mInputIndice; + std::vector mLengthCache; std::vector mDistanceCache; - std::vector mLengthCache; - std::vector mRelativeSpeeds; + std::vector mSpeedRates; std::vector mDirections; + std::vector mBeelineSpeedRates; // probabilities of skipping or mapping to a key for each point. std::vector > mCharProbabilities; // The vector for the key code set which holds nearby keys for each sampled input point diff --git a/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java b/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java index 8fed28f9e..2c3e3a514 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java @@ -48,6 +48,9 @@ public class PointerTrackerQueueTests extends AndroidTestCase { mPhantomUpEventTime = eventTime + sPhantomUpCount; } + @Override + public void cancelTracking() {} + @Override public String toString() { return Integer.toString(mId);