diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml index f2f3178de..6184add4d 100644 --- a/java/res/xml/method.xml +++ b/java/res/xml/method.xml @@ -31,141 +31,145 @@ android:label="@string/subtype_en_US" android:imeSubtypeLocale="en_US" android:imeSubtypeMode="keyboard" - android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable" + android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection" /> diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index d35b1a939..cc6feeb4a 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -19,6 +19,7 @@ package com.android.inputmethod.keyboard; import android.graphics.Rect; import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo; @@ -31,6 +32,8 @@ public class ProximityInfo { /** Number of key widths from current touch point to search for nearest keys. */ private static float SEARCH_DISTANCE = 1.2f; private static final int[] EMPTY_INT_ARRAY = new int[0]; + private static final String SUPPORT_TOUCH_POSITION_CORRECTION = + "SupportTouchPositionCorrection"; private final int mKeyHeight; private final int mGridWidth; @@ -120,8 +123,10 @@ public class ProximityInfo { keyCharCodes[i] = key.mCode; } + final SubtypeSwitcher switcher = SubtypeSwitcher.getInstance(); final boolean hasTouchPositionCorrectionData = - mTouchPositionCorrectionXs != null + switcher.currentSubtypeContainsExtraValueKey(SUPPORT_TOUCH_POSITION_CORRECTION) + && mTouchPositionCorrectionXs != null && mTouchPositionCorrectionYs != null && mTouchPositionCorrectionRadii != null && mTouchPositionCorrectionXs.length > 0 diff --git a/native/src/correction.cpp b/native/src/correction.cpp index d5bfed017..5d3bd71ad 100644 --- a/native/src/correction.cpp +++ b/native/src/correction.cpp @@ -113,6 +113,9 @@ bool Correction::initProcessState(const int outputIndex) { mInputIndex = mCorrectionStates[outputIndex].mInputIndex; mNeedsToTraverseAllNodes = mCorrectionStates[outputIndex].mNeedsToTraverseAllNodes; + mEquivalentCharStrongCount = mCorrectionStates[outputIndex].mEquivalentCharStrongCount; + mEquivalentCharNormalCount = mCorrectionStates[outputIndex].mEquivalentCharNormalCount; + mEquivalentCharWeakCount = mCorrectionStates[outputIndex].mEquivalentCharWeakCount; mProximityCount = mCorrectionStates[outputIndex].mProximityCount; mTransposedCount = mCorrectionStates[outputIndex].mTransposedCount; mExcessiveCount = mCorrectionStates[outputIndex].mExcessiveCount; @@ -167,6 +170,9 @@ void Correction::incrementOutputIndex() { mCorrectionStates[mOutputIndex].mInputIndex = mInputIndex; mCorrectionStates[mOutputIndex].mNeedsToTraverseAllNodes = mNeedsToTraverseAllNodes; + mCorrectionStates[mOutputIndex].mEquivalentCharStrongCount = mEquivalentCharStrongCount; + mCorrectionStates[mOutputIndex].mEquivalentCharNormalCount = mEquivalentCharNormalCount; + mCorrectionStates[mOutputIndex].mEquivalentCharWeakCount = mEquivalentCharWeakCount; mCorrectionStates[mOutputIndex].mProximityCount = mProximityCount; mCorrectionStates[mOutputIndex].mTransposedCount = mTransposedCount; mCorrectionStates[mOutputIndex].mExcessiveCount = mExcessiveCount; @@ -208,6 +214,12 @@ Correction::CorrectionType Correction::processSkipChar( } } +inline bool isEquivalentChar(ProximityInfo::ProximityType type) { + // 'type ProximityInfo::EQUIVALENT_CHAR_WEAK' means that + // type == ..._WEAK or type == ..._NORMAL or type == ..._STRONG. + return type <= ProximityInfo::EQUIVALENT_CHAR_WEAK; +} + Correction::CorrectionType Correction::processCharAndCalcState( const int32_t c, const bool isTerminal) { const int correctionCount = (mSkippedCount + mExcessiveCount + mTransposedCount); @@ -219,8 +231,9 @@ Correction::CorrectionType Correction::processCharAndCalcState( bool incremented = false; if (mLastCharExceeded && mInputIndex == mInputLength - 1) { // TODO: Do not check the proximity if EditDistance exceeds the threshold - const int matchId = mProximityInfo->getMatchedProximityId(mInputIndex, c, true); - if (matchId == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { + const ProximityInfo::ProximityType matchId = + mProximityInfo->getMatchedProximityId(mInputIndex, c, true); + if (isEquivalentChar(matchId)) { mLastCharExceeded = false; --mExcessiveCount; } else if (matchId == ProximityInfo::NEAR_PROXIMITY_CHAR) { @@ -264,8 +277,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( bool secondTransposing = false; if (mTransposedCount % 2 == 1) { - if (mProximityInfo->getMatchedProximityId(mInputIndex - 1, c, false) - == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { + if (isEquivalentChar(mProximityInfo->getMatchedProximityId(mInputIndex - 1, c, false))) { ++mTransposedCount; secondTransposing = true; } else if (mCorrectionStates[mOutputIndex].mExceeding) { @@ -286,8 +298,8 @@ Correction::CorrectionType Correction::processCharAndCalcState( // TODO: Change the limit if we'll allow two or more proximity chars with corrections const bool checkProximityChars = noCorrectionsHappenedSoFar || mProximityCount == 0; - const int matchedProximityCharId = secondTransposing - ? ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR + const ProximityInfo::ProximityType matchedProximityCharId = secondTransposing + ? ProximityInfo::EQUIVALENT_CHAR_NORMAL : mProximityInfo->getMatchedProximityId(mInputIndex, c, checkProximityChars); if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId) { @@ -297,19 +309,18 @@ Correction::CorrectionType Correction::processCharAndCalcState( // here refers to the previous state. if (canTryCorrection && mCorrectionStates[mOutputIndex].mProximityMatching && mCorrectionStates[mOutputIndex].mExceeding - && mProximityInfo->getMatchedProximityId(mInputIndex, mWord[mOutputIndex], false) - == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { + && isEquivalentChar(mProximityInfo->getMatchedProximityId( + mInputIndex, mWord[mOutputIndex], false))) { // Conversion p->e ++mExcessiveCount; --mProximityCount; } else if (mInputIndex < mInputLength - 1 && mOutputIndex > 0 && mTransposedCount > 0 && !mCorrectionStates[mOutputIndex].mTransposing && mCorrectionStates[mOutputIndex - 1].mTransposing - && mProximityInfo->getMatchedProximityId( - mInputIndex, mWord[mOutputIndex - 1], false) - == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR - && mProximityInfo->getMatchedProximityId(mInputIndex + 1, c, false) - == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { + && isEquivalentChar(mProximityInfo->getMatchedProximityId( + mInputIndex, mWord[mOutputIndex - 1], false)) + && isEquivalentChar( + mProximityInfo->getMatchedProximityId(mInputIndex + 1, c, false))) { // Conversion t->e // Example: // occaisional -> occa sional @@ -320,8 +331,8 @@ Correction::CorrectionType Correction::processCharAndCalcState( } else if (mOutputIndex > 0 && mInputIndex > 0 && mTransposedCount > 0 && !mCorrectionStates[mOutputIndex].mTransposing && mCorrectionStates[mOutputIndex - 1].mTransposing - && mProximityInfo->getMatchedProximityId(mInputIndex - 1, c, false) - == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { + && isEquivalentChar( + mProximityInfo->getMatchedProximityId(mInputIndex - 1, c, false))) { // Conversion t->s // Example: // chcolate -> chocolate @@ -332,8 +343,8 @@ Correction::CorrectionType Correction::processCharAndCalcState( } else if (canTryCorrection && mInputIndex > 0 && mCorrectionStates[mOutputIndex].mProximityMatching && mCorrectionStates[mOutputIndex].mSkipping - && mProximityInfo->getMatchedProximityId(mInputIndex - 1, c, false) - == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { + && isEquivalentChar( + mProximityInfo->getMatchedProximityId(mInputIndex - 1, c, false))) { // Conversion p->s // Note: This logic tries saving cases like contrst --> contrast -- "a" is one of // proximity chars of "s", but it should rather be handled as a skipped char. @@ -341,8 +352,8 @@ Correction::CorrectionType Correction::processCharAndCalcState( --mProximityCount; return processSkipChar(c, isTerminal, false); } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputLength - && mProximityInfo->getMatchedProximityId(mInputIndex + 1, c, false) - == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { + && isEquivalentChar( + mProximityInfo->getMatchedProximityId(mInputIndex + 1, c, false))) { // 1.2. Excessive or transpose correction if (mTransposing) { ++mTransposedCount; @@ -362,14 +373,28 @@ Correction::CorrectionType Correction::processCharAndCalcState( } return UNRELATED; } - } else if (secondTransposing - || ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR == matchedProximityCharId) { + } else if (secondTransposing) { // If inputIndex is greater than mInputLength, that means there is no // proximity chars. So, we don't need to check proximity. mMatching = true; + } else if (isEquivalentChar(matchedProximityCharId)) { + mMatching = true; + switch (matchedProximityCharId) { + case ProximityInfo::EQUIVALENT_CHAR_STRONG: + ++mEquivalentCharStrongCount; + break; + case ProximityInfo::EQUIVALENT_CHAR_NORMAL: + ++mEquivalentCharNormalCount; + break; + case ProximityInfo::EQUIVALENT_CHAR_WEAK: + ++mEquivalentCharWeakCount; + break; + default: + assert(false); + } } else if (ProximityInfo::NEAR_PROXIMITY_CHAR == matchedProximityCharId) { mProximityMatching = true; - incrementProximityCount(); + ++mProximityCount; } mWord[mOutputIndex] = c; diff --git a/native/src/correction.h b/native/src/correction.h index 41130ad77..7d73dfa88 100644 --- a/native/src/correction.h +++ b/native/src/correction.h @@ -102,11 +102,6 @@ private: inline CorrectionType processSkipChar( const int32_t c, const bool isTerminal, const bool inputIndexIncremented); - // TODO: remove - inline void incrementProximityCount() { - ++mProximityCount; - } - const int TYPED_LETTER_MULTIPLIER; const int FULL_WORD_MULTIPLIER; const ProximityInfo *mProximityInfo; @@ -130,6 +125,9 @@ private: int mOutputIndex; int mInputIndex; + int mEquivalentCharStrongCount; + int mEquivalentCharNormalCount; + int mEquivalentCharWeakCount; int mProximityCount; int mExcessiveCount; int mTransposedCount; diff --git a/native/src/correction_state.h b/native/src/correction_state.h index 93f8a8aab..a8ee82acd 100644 --- a/native/src/correction_state.h +++ b/native/src/correction_state.h @@ -29,6 +29,9 @@ struct CorrectionState { uint16_t mChildCount; uint8_t mInputIndex; + uint8_t mEquivalentCharStrongCount; + uint8_t mEquivalentCharNormalCount; + uint8_t mEquivalentCharWeakCount; uint8_t mProximityCount; uint8_t mTransposedCount; uint8_t mExcessiveCount; @@ -63,7 +66,9 @@ inline static void initCorrectionState(CorrectionState *state, const int rootPos state->mExcessivePos = -1; state->mSkipPos = -1; - + state->mEquivalentCharStrongCount = 0; + state->mEquivalentCharNormalCount = 0; + state->mEquivalentCharWeakCount = 0; state->mProximityCount = 0; state->mTransposedCount = 0; state->mExcessiveCount = 0; diff --git a/native/src/defines.h b/native/src/defines.h index 55469a788..6c619d1ab 100644 --- a/native/src/defines.h +++ b/native/src/defines.h @@ -165,6 +165,8 @@ static void dumpWord(const unsigned short* word, const int length) { #define KEYCODE_SPACE ' ' +#define CALIBRATE_SCORE_BY_TOUCH_COORDINATES false + #define SUGGEST_WORDS_WITH_MISSING_CHARACTER true #define SUGGEST_WORDS_WITH_MISSING_SPACE_CHARACTER true #define SUGGEST_WORDS_WITH_EXCESSIVE_CHARACTER true @@ -204,4 +206,7 @@ static void dumpWord(const unsigned short* word, const int length) { #define min(a,b) ((a)<(b)?(a):(b)) #define max(a,b) ((a)>(b)?(a):(b)) +// The ratio of neutral area radius to sweet spot radius. +#define NEUTRAL_AREA_RADIUS_RATIO 1.3f + #endif // LATINIME_DEFINES_H diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp index 4ff6e0ac0..081cb61c3 100644 --- a/native/src/proximity_info.cpp +++ b/native/src/proximity_info.cpp @@ -43,7 +43,8 @@ ProximityInfo::ProximityInfo(const int maxProximityCharsSize, const int keyboard KEYBOARD_HEIGHT(keyboardHeight), GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight), CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth), CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight), - KEY_COUNT(min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)) { + KEY_COUNT(min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)), + mInputXCoordinates(NULL), mInputYCoordinates(NULL) { const int len = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE; mProximityCharsArray = new uint32_t[len]; if (DEBUG_PROXIMITY_INFO) { @@ -103,8 +104,11 @@ bool ProximityInfo::hasSpaceProximity(const int x, const int y) const { } // TODO: Calculate nearby codes here. -void ProximityInfo::setInputParams(const int* inputCodes, const int inputLength) { +void ProximityInfo::setInputParams(const int* inputCodes, const int inputLength, + const int* xCoordinates, const int* yCoordinates) { mInputCodes = inputCodes; + mInputXCoordinates = xCoordinates; + mInputYCoordinates = yCoordinates; mInputLength = inputLength; for (int i = 0; i < inputLength; ++i) { mPrimaryInputWord[i] = getPrimaryCharAt(i); @@ -158,19 +162,37 @@ bool ProximityInfo::existsAdjacentProximityChars(const int index) const { ProximityInfo::ProximityType ProximityInfo::getMatchedProximityId( const int index, const unsigned short c, const bool checkProximityChars) const { const int *currentChars = getProximityCharsAt(index); + const int firstChar = currentChars[0]; const unsigned short baseLowerC = Dictionary::toBaseLowerCase(c); // The first char in the array is what user typed. If it matches right away, // that means the user typed that same char for this pos. - if (currentChars[0] == baseLowerC || currentChars[0] == c) - return SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR; + if (firstChar == baseLowerC || firstChar == c) { + if (CALIBRATE_SCORE_BY_TOUCH_COORDINATES) { + const SweetSpotType result = calculateSweetSpotType(index, baseLowerC); + switch (result) { + case UNKNOWN: + return EQUIVALENT_CHAR_NORMAL; + case IN_SWEET_SPOT: + return EQUIVALENT_CHAR_STRONG; + case IN_NEUTRAL_AREA: + return EQUIVALENT_CHAR_NORMAL; + case OUT_OF_NEUTRAL_AREA: + return EQUIVALENT_CHAR_WEAK; + default: + assert(false); + } + } else { + return EQUIVALENT_CHAR_NORMAL; + } + } if (!checkProximityChars) return UNRELATED_CHAR; // If the non-accented, lowercased version of that first character matches c, // then we have a non-accented version of the accented character the user // typed. Treat it as a close char. - if (Dictionary::toBaseLowerCase(currentChars[0]) == baseLowerC) + if (Dictionary::toBaseLowerCase(firstChar) == baseLowerC) return NEAR_PROXIMITY_CHAR; // Not an exact nor an accent-alike match: search the list of close keys @@ -185,6 +207,38 @@ ProximityInfo::ProximityType ProximityInfo::getMatchedProximityId( return UNRELATED_CHAR; } +inline float square(const float x) { return x * x; } + +ProximityInfo::SweetSpotType ProximityInfo::calculateSweetSpotType( + int index, unsigned short baseLowerC) const { + if (KEY_COUNT == 0 || !mInputXCoordinates || !mInputYCoordinates + || baseLowerC > MAX_CHAR_CODE) { + return UNKNOWN; + } + const int keyIndex = mCodeToKeyIndex[baseLowerC]; + if (keyIndex < 0) { + return UNKNOWN; + } + const float sweetSpotRadius = mSweetSpotRadii[keyIndex]; + if (sweetSpotRadius <= 0.0) { + return UNKNOWN; + } + const float sweetSpotCenterX = mSweetSpotCenterXs[keyIndex]; + const float sweetSpotCenterY = mSweetSpotCenterXs[keyIndex]; + const float inputX = (float)mInputXCoordinates[index]; + const float inputY = (float)mInputYCoordinates[index]; + const float squaredDistance = + square(inputX - sweetSpotCenterX) + square(inputY - sweetSpotCenterY); + const float squaredSweetSpotRadius = square(sweetSpotRadius); + if (squaredDistance <= squaredSweetSpotRadius) { + return IN_SWEET_SPOT; + } + if (squaredDistance <= square(NEUTRAL_AREA_RADIUS_RATIO) * squaredSweetSpotRadius) { + return IN_NEUTRAL_AREA; + } + return OUT_OF_NEUTRAL_AREA; +} + bool ProximityInfo::sameAsTyped(const unsigned short *word, int length) const { if (length != mInputLength) { return false; diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h index b1e8236d3..a705d0cf6 100644 --- a/native/src/proximity_info.h +++ b/native/src/proximity_info.h @@ -27,10 +27,18 @@ class Correction; class ProximityInfo { public: - typedef enum { // Used as a return value for character comparison - SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR, // Same char, possibly with different case or accent - NEAR_PROXIMITY_CHAR, // It is a char located nearby on the keyboard - UNRELATED_CHAR // It is an unrelated char + // Used as a return value for character comparison + typedef enum { + // Same char, possibly with different case or accent, and in the sweet spot of the char + EQUIVALENT_CHAR_STRONG, + // Same char, possibly with different case or accent, and in the outer sweet spot + EQUIVALENT_CHAR_NORMAL, + // Same char, possibly with different case or accent, and in the hit box of the char + EQUIVALENT_CHAR_WEAK, + // It is a char located nearby on the keyboard + NEAR_PROXIMITY_CHAR, + // It is an unrelated char + UNRELATED_CHAR } ProximityType; ProximityInfo(const int maxProximityCharsSize, const int keyboardWidth, @@ -41,7 +49,8 @@ public: const float *sweetSpotCenterYs, const float *sweetSpotRadii); ~ProximityInfo(); bool hasSpaceProximity(const int x, const int y) const; - void setInputParams(const int* inputCodes, const int inputLength); + void setInputParams(const int* inputCodes, const int inputLength, + const int *xCoordinates, const int *yCoordinates); const int* getProximityCharsAt(const int index) const; unsigned short getPrimaryCharAt(const int index) const; bool existsCharInProximityAt(const int index, const int c) const; @@ -59,8 +68,20 @@ private: // The upper limit of the char code in mCodeToKeyIndex static const int MAX_CHAR_CODE = 127; + typedef enum { + // cannot figure out the sweet spot type + UNKNOWN, + // touch position is out of neutral area of the given char + OUT_OF_NEUTRAL_AREA, + // touch position is in the neutral area of the given char + IN_NEUTRAL_AREA, + // touch position is in the sweet spot of the given char + IN_SWEET_SPOT + } SweetSpotType; + int getStartIndexFromCoordinates(const int x, const int y) const; void initializeCodeToKeyIndex(); + SweetSpotType calculateSweetSpotType(int index, unsigned short baseLowerC) const; const int MAX_PROXIMITY_CHARS_SIZE; const int KEYBOARD_WIDTH; const int KEYBOARD_HEIGHT; @@ -70,6 +91,8 @@ private: const int CELL_HEIGHT; const int KEY_COUNT; const int *mInputCodes; + const int *mInputXCoordinates; + const int *mInputYCoordinates; uint32_t *mProximityCharsArray; int32_t mKeyXCoordinates[MAX_KEY_COUNT_IN_A_KEYBOARD]; int32_t mKeyYCoordinates[MAX_KEY_COUNT_IN_A_KEYBOARD]; diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp index 517dc843e..cdd84aa33 100644 --- a/native/src/unigram_dictionary.cpp +++ b/native/src/unigram_dictionary.cpp @@ -238,8 +238,8 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, PROF_END(6); } -void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, - const int *ycoordinates, const int *codes, const int codesSize, +void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int *xCoordinates, + const int *yCoordinates, const int *codes, const int codesSize, unsigned short *outWords, int *frequencies) { if (DEBUG_DICT) { LOGI("initSuggest"); @@ -247,7 +247,7 @@ void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int mFrequencies = frequencies; mOutputChars = outWords; mInputLength = codesSize; - proximityInfo->setInputParams(codes, codesSize); + proximityInfo->setInputParams(codes, codesSize, xCoordinates, yCoordinates); mProximityInfo = proximityInfo; }