diff options
-rw-r--r-- | java/src/com/android/inputmethod/latin/LatinIME.java | 3 | ||||
-rw-r--r-- | java/src/com/android/inputmethod/latin/RichInputConnection.java | 30 | ||||
-rw-r--r-- | native/jni/src/proximity_info.cpp | 33 | ||||
-rw-r--r-- | native/jni/src/proximity_info.h | 6 | ||||
-rw-r--r-- | native/jni/src/proximity_info_state.h | 2 | ||||
-rw-r--r-- | tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java | 96 |
6 files changed, 102 insertions, 68 deletions
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 78c65e0c7..b3f7e674d 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -825,7 +825,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // we know for sure the cursor moved while we were composing and we should reset // the state. final boolean noComposingSpan = composingSpanStart == -1 && composingSpanEnd == -1; - if (!mExpectingUpdateSelection) { + if (!mExpectingUpdateSelection + && !mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart)) { // TAKE CARE: there is a race condition when we enter this test even when the user // did not explicitly move the cursor. This happens when typing fast, where two keys // turn this flag on in succession and both onUpdateSelection() calls arrive after diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 37e1dbb69..ce7049f4f 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -629,4 +629,34 @@ public class RichInputConnection { commitText(" " + textBeforeCursor.subSequence(0, 1), 1); return true; } + + /** + * Heuristic to determine if this is an expected update of the cursor. + * + * Sometimes updates to the cursor position are late because of their asynchronous nature. + * This method tries to determine if this update is one, based on the values of the cursor + * position in the update, and the currently expected position of the cursor according to + * LatinIME's internal accounting. If this is not a belated expected update, then it should + * mean that the user moved the cursor explicitly. + * This is quite robust, but of course it's not perfect. In particular, it will fail in the + * case we get an update A, the user types in N characters so as to move the cursor to A+N but + * we don't get those, and then the user places the cursor between A and A+N, and we get only + * this update and not the ones in-between. This is almost impossible to achieve even trying + * very very hard. + * + * @param oldSelStart The value of the old cursor position in the update. + * @param newSelStart The value of the new cursor position in the update. + * @return whether this is a belated expected update or not. + */ + public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart) { + // If this is an update that arrives at our expected position, it's a belated update. + if (newSelStart == mCurrentCursorPosition) return true; + // If this is an update that moves the cursor from our expected position, it must be + // an explicit move. + if (oldSelStart == mCurrentCursorPosition) return false; + // The following returns true if newSelStart is between oldSelStart and + // mCurrentCursorPosition. We assume that if the updated position is between the old + // position and the expected position, then it must be a belated update. + return (newSelStart - oldSelStart) * (mCurrentCursorPosition - newSelStart) >= 0; + } } diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp index 693a9f2b1..c9f83b62c 100644 --- a/native/jni/src/proximity_info.cpp +++ b/native/jni/src/proximity_info.cpp @@ -67,7 +67,8 @@ ProximityInfo::ProximityInfo(JNIEnv *env, const jstring localeJStr, const int ma && keyWidths && keyHeights && keyCharCodes && sweetSpotCenterXs && sweetSpotCenterYs && sweetSpotRadii), mProximityCharsArray(new int32_t[GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE - /* proximityGridLength */]) { + /* proximityGridLength */]), + mCodeToKeyMap() { const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE; if (DEBUG_PROXIMITY_INFO) { AKLOGI("Create proximity info array %d", proximityGridLength); @@ -88,22 +89,9 @@ ProximityInfo::ProximityInfo(JNIEnv *env, const jstring localeJStr, const int ma safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterXs, KEY_COUNT, mSweetSpotCenterXs); safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterYs, KEY_COUNT, mSweetSpotCenterYs); safeGetOrFillZeroFloatArrayRegion(env, sweetSpotRadii, KEY_COUNT, mSweetSpotRadii); - initializeCodePointToKeyIndex(); initializeG(); } -// Build the reversed look up table from the char code to the index in mKeyXCoordinates, -// mKeyYCoordinates, mKeyWidths, mKeyHeights, mKeyCharCodes. -void ProximityInfo::initializeCodePointToKeyIndex() { - memset(mCodePointToKeyIndex, -1, sizeof(mCodePointToKeyIndex)); - for (int i = 0; i < KEY_COUNT; ++i) { - const int code = mKeyCodePoints[i]; - if (0 <= code && code <= MAX_CHAR_CODE) { - mCodePointToKeyIndex[code] = i; - } - } -} - ProximityInfo::~ProximityInfo() { delete[] mProximityCharsArray; } @@ -239,11 +227,12 @@ int ProximityInfo::getKeyIndexOf(const int c) const { // We do not have the coordinate data return NOT_AN_INDEX; } - const unsigned short baseLowerC = toBaseLowerCase(c); - if (baseLowerC > MAX_CHAR_CODE) { - return NOT_AN_INDEX; + const int baseLowerC = static_cast<int>(toBaseLowerCase(c)); + hash_map_compat<int, int>::const_iterator mapPos = mCodeToKeyMap.find(baseLowerC); + if (mapPos != mCodeToKeyMap.end()) { + return mapPos->second; } - return mCodePointToKeyIndex[baseLowerC]; + return NOT_AN_INDEX; } int ProximityInfo::getCodePointOf(const int keyIndex) const { @@ -260,12 +249,8 @@ void ProximityInfo::initializeG() { const int lowerCode = toBaseLowerCase(code); mCenterXsG[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2; mCenterYsG[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2; - if (code != lowerCode && lowerCode >= 0 && lowerCode <= MAX_CHAR_CODE) { - mCodePointToKeyIndex[lowerCode] = i; - mKeyIndexToCodePointG[i] = lowerCode; - } else { - mKeyIndexToCodePointG[i] = code; - } + mCodeToKeyMap[lowerCode] = i; + mKeyIndexToCodePointG[i] = lowerCode; } for (int i = 0; i < KEY_COUNT; i++) { mKeyKeyDistancesG[i][i] = 0; diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h index 45df6ff6a..2f258ef86 100644 --- a/native/jni/src/proximity_info.h +++ b/native/jni/src/proximity_info.h @@ -20,6 +20,7 @@ #include <stdint.h> #include "defines.h" +#include "hash_map_compat.h" #include "jni.h" namespace latinime { @@ -112,12 +113,9 @@ class ProximityInfo { private: DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfo); - // The upper limit of the char code in mCodePointToKeyIndex - static const int MAX_CHAR_CODE = 127; static const float NOT_A_DISTANCE_FLOAT; int getStartIndexFromCoordinates(const int x, const int y) const; - void initializeCodePointToKeyIndex(); void initializeG(); float calculateNormalizedSquaredDistance(const int keyIndex, const int inputIndex) const; float calculateSquaredDistanceFromSweetSpotCenter( @@ -154,7 +152,7 @@ class ProximityInfo { float mSweetSpotCenterXs[MAX_KEY_COUNT_IN_A_KEYBOARD]; float mSweetSpotCenterYs[MAX_KEY_COUNT_IN_A_KEYBOARD]; float mSweetSpotRadii[MAX_KEY_COUNT_IN_A_KEYBOARD]; - int mCodePointToKeyIndex[MAX_CHAR_CODE + 1]; + hash_map_compat<int, int> mCodeToKeyMap; int mKeyIndexToCodePointG[MAX_KEY_COUNT_IN_A_KEYBOARD]; int mCenterXsG[MAX_KEY_COUNT_IN_A_KEYBOARD]; diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h index fce4b5bdc..453b1de4d 100644 --- a/native/jni/src/proximity_info_state.h +++ b/native/jni/src/proximity_info_state.h @@ -37,8 +37,6 @@ class ProximityInfoState { static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2 = 10; static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR = 1 << NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2; - // The upper limit of the char code in mCodeToKeyIndex - static const int MAX_CHAR_CODE = 127; static const float NOT_A_DISTANCE_FLOAT = -1.0f; static const int NOT_A_CODE = -1; diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java index 5d9c229e3..328784b1a 100644 --- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java +++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java @@ -64,6 +64,10 @@ public class BinaryDictIOTests extends AndroidTestCase { CollectionUtils.newSparseArray(); private static final FormatSpec.FormatOptions VERSION2 = new FormatSpec.FormatOptions(2); + private static final FormatSpec.FormatOptions VERSION3_WITHOUT_PARENTADDRESS = + new FormatSpec.FormatOptions(3, false); + private static final FormatSpec.FormatOptions VERSION3_WITH_PARENTADDRESS = + new FormatSpec.FormatOptions(3, true); private static final String[] CHARACTERS = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", @@ -80,7 +84,7 @@ public class BinaryDictIOTests extends AndroidTestCase { for (int i = 0; i < sWords.size(); ++i) { sChainBigrams.put(i, new ArrayList<Integer>()); if (i > 0) { - sChainBigrams.get(i-1).add(i); + sChainBigrams.get(i - 1).add(i); } } @@ -95,7 +99,7 @@ public class BinaryDictIOTests extends AndroidTestCase { /** * Makes new buffer according to BUFFER_TYPE. */ - private FusionDictionaryBufferInterface getBuffer(final File file,final int bufferType) { + private FusionDictionaryBufferInterface getBuffer(final File file, final int bufferType) { FileInputStream inStream = null; try { inStream = new FileInputStream(file); @@ -173,7 +177,8 @@ public class BinaryDictIOTests extends AndroidTestCase { } } - private long timeWritingDictToFile(final File file, final FusionDictionary dict) { + private long timeWritingDictToFile(final File file, final FusionDictionary dict, + final FormatSpec.FormatOptions formatOptions) { long now = -1, diff = -1; @@ -181,7 +186,7 @@ public class BinaryDictIOTests extends AndroidTestCase { final FileOutputStream out = new FileOutputStream(file); now = System.currentTimeMillis(); - BinaryDictInputOutput.writeDictionaryBinary(out, dict, VERSION2); + BinaryDictInputOutput.writeDictionaryBinary(out, dict, formatOptions); diff = System.currentTimeMillis() - now; out.flush(); @@ -226,6 +231,14 @@ public class BinaryDictIOTests extends AndroidTestCase { } } + private String outputOptions(final int bufferType, + final FormatSpec.FormatOptions formatOptions) { + String result = " : buffer type = " + + ((bufferType == USE_BYTE_BUFFER) ? "byte buffer" : "byte array"); + result += " : version = " + formatOptions.mVersion; + return result + ", hasParentAddress = " + formatOptions.mHasParentAddress; + } + // Tests for readDictionaryBinary and writeDictionaryBinary private long timeReadingAndCheckDict(final File file, final List<String> words, @@ -243,7 +256,7 @@ public class BinaryDictIOTests extends AndroidTestCase { } catch (IOException e) { Log.e(TAG, "IOException while reading dictionary: " + e); } catch (UnsupportedFormatException e) { - Log.e(TAG, "Unsupported format: "+ e); + Log.e(TAG, "Unsupported format: " + e); } checkDictionary(dict, words, bigrams, shortcutMap); @@ -253,7 +266,8 @@ public class BinaryDictIOTests extends AndroidTestCase { // Tests for readDictionaryBinary and writeDictionaryBinary private String runReadAndWrite(final List<String> words, final SparseArray<List<Integer>> bigrams, final Map<String, List<String>> shortcuts, - final int bufferType, final String message) { + final int bufferType, final FormatSpec.FormatOptions formatOptions, + final String message) { File file = null; try { file = File.createTempFile("runReadAndWrite", ".dict"); @@ -263,28 +277,34 @@ public class BinaryDictIOTests extends AndroidTestCase { assertNotNull(file); final FusionDictionary dict = new FusionDictionary(new Node(), - new FusionDictionary.DictionaryOptions( - new HashMap<String,String>(), false, false)); + new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false)); addUnigrams(words.size(), dict, words, shortcuts); addBigrams(dict, words, bigrams); checkDictionary(dict, words, bigrams, shortcuts); - final long write = timeWritingDictToFile(file, dict); + final long write = timeWritingDictToFile(file, dict, formatOptions); final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType); - return "PROF: read=" + read + "ms, write=" + write + "ms :" + message + - " : buffer type = " + bufferType; + return "PROF: read=" + read + "ms, write=" + write + "ms :" + message + + " : " + outputOptions(bufferType, formatOptions); + } + + private void runReadAndWriteTests(final List<String> results, final int bufferType, + final FormatSpec.FormatOptions formatOptions) { + results.add(runReadAndWrite(sWords, sEmptyBigrams, null /* shortcuts */, bufferType, + formatOptions, "unigram")); + results.add(runReadAndWrite(sWords, sChainBigrams, null /* shortcuts */, bufferType, + formatOptions, "chain")); + results.add(runReadAndWrite(sWords, sStarBigrams, null /* shortcuts */, bufferType, + formatOptions, "star")); } public void testReadAndWriteWithByteBuffer() { final List<String> results = CollectionUtils.newArrayList(); - results.add(runReadAndWrite(sWords, sEmptyBigrams, null /* shortcuts */, USE_BYTE_BUFFER, - "unigram")); - results.add(runReadAndWrite(sWords, sChainBigrams, null /* shortcuts */, USE_BYTE_BUFFER, - "chain")); - results.add(runReadAndWrite(sWords, sStarBigrams, null /* shortcuts */, USE_BYTE_BUFFER, - "star")); + runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION2); + runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_PARENTADDRESS); + runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITH_PARENTADDRESS); for (final String result : results) { Log.d(TAG, result); @@ -294,12 +314,9 @@ public class BinaryDictIOTests extends AndroidTestCase { public void testReadAndWriteWithByteArray() { final List<String> results = CollectionUtils.newArrayList(); - results.add(runReadAndWrite(sWords, sEmptyBigrams, null /* shortcuts */, USE_BYTE_ARRAY, - "unigram")); - results.add(runReadAndWrite(sWords, sChainBigrams, null /* shortcuts */, USE_BYTE_ARRAY, - "chain")); - results.add(runReadAndWrite(sWords, sStarBigrams, null /* shortcuts */, USE_BYTE_ARRAY, - "star")); + runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION2); + runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_PARENTADDRESS); + runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITH_PARENTADDRESS); for (final String result : results) { Log.d(TAG, result); @@ -391,7 +408,7 @@ public class BinaryDictIOTests extends AndroidTestCase { private String runReadUnigramsAndBigramsBinary(final List<String> words, final SparseArray<List<Integer>> bigrams, final int bufferType, - final String message) { + final FormatSpec.FormatOptions formatOptions, final String message) { File file = null; try { file = File.createTempFile("runReadUnigrams", ".dict"); @@ -407,25 +424,32 @@ public class BinaryDictIOTests extends AndroidTestCase { addUnigrams(words.size(), dict, words, null /* shortcutMap */); addBigrams(dict, words, bigrams); - timeWritingDictToFile(file, dict); + timeWritingDictToFile(file, dict, formatOptions); long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams, bufferType); long fullReading = timeReadingAndCheckDict(file, words, bigrams, null /* shortcutMap */, bufferType); return "readDictionaryBinary=" + fullReading + ", readUnigramsAndBigramsBinary=" + wordMap - + " : " + message + " : buffer type = " + bufferType; + + " : " + message + " : " + outputOptions(bufferType, formatOptions); + } + + private void runReadUnigramsAndBigramsTests(final List<String> results, final int bufferType, + final FormatSpec.FormatOptions formatOptions) { + results.add(runReadUnigramsAndBigramsBinary(sWords, sEmptyBigrams, bufferType, + formatOptions, "unigram")); + results.add(runReadUnigramsAndBigramsBinary(sWords, sChainBigrams, bufferType, + formatOptions, "chain")); + results.add(runReadUnigramsAndBigramsBinary(sWords, sChainBigrams, bufferType, + formatOptions, "star")); } public void testReadUnigramsAndBigramsBinaryWithByteBuffer() { final List<String> results = CollectionUtils.newArrayList(); - results.add(runReadUnigramsAndBigramsBinary(sWords, sEmptyBigrams, USE_BYTE_BUFFER, - "unigram")); - results.add(runReadUnigramsAndBigramsBinary(sWords, sChainBigrams, USE_BYTE_BUFFER, - "chain")); - results.add(runReadUnigramsAndBigramsBinary(sWords, sStarBigrams, USE_BYTE_BUFFER, - "star")); + runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION2); + runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_PARENTADDRESS); + runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITH_PARENTADDRESS); for (final String result : results) { Log.d(TAG, result); @@ -435,11 +459,9 @@ public class BinaryDictIOTests extends AndroidTestCase { public void testReadUnigramsAndBigramsBinaryWithByteArray() { final List<String> results = CollectionUtils.newArrayList(); - results.add(runReadUnigramsAndBigramsBinary(sWords, sEmptyBigrams, USE_BYTE_ARRAY, - "unigram")); - results.add(runReadUnigramsAndBigramsBinary(sWords, sChainBigrams, USE_BYTE_ARRAY, - "chain")); - results.add(runReadUnigramsAndBigramsBinary(sWords, sStarBigrams, USE_BYTE_ARRAY, "star")); + runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION2); + runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_PARENTADDRESS); + runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITH_PARENTADDRESS); for (final String result : results) { Log.d(TAG, result); |