diff options
18 files changed, 221 insertions, 103 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 0aa34e82e..60ac1ba48 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -28,9 +28,10 @@ import com.android.inputmethod.latin.makedict.FormatSpec; import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.makedict.WordProperty; -import com.android.inputmethod.latin.personalization.PersonalizationHelper; import com.android.inputmethod.latin.settings.NativeSuggestOptions; +import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.FileUtils; import com.android.inputmethod.latin.utils.JniUtils; import com.android.inputmethod.latin.utils.LanguageModelParam; import com.android.inputmethod.latin.utils.StringUtils; @@ -81,6 +82,8 @@ public final class BinaryDictionary extends Dictionary { public static final int FORMAT_WORD_PROPERTY_LEVEL_INDEX = 2; public static final int FORMAT_WORD_PROPERTY_COUNT_INDEX = 3; + public static final String DICT_FILE_NAME_SUFFIX_FOR_MIGRATION = ".migrate"; + private long mNativeDict; private final Locale mLocale; private final long mDictSize; @@ -244,7 +247,7 @@ public final class BinaryDictionary extends Dictionary { // TODO: toLowerCase in the native code final int[] prevWordCodePointArray = (null == prevWord) ? null : StringUtils.toCodePointArray(prevWord); - final int composerSize = composer.size(); + final int composerSize = composer.sizeWithoutTrailingSingleQuotes(); final boolean isGesture = composer.isBatchMode(); if (composerSize <= 1 || !isGesture) { @@ -458,6 +461,24 @@ public final class BinaryDictionary extends Dictionary { return needsToRunGCNative(mNativeDict, mindsBlockByGC); } + public boolean migrateTo(final int newFormatVersion) { + if (!isValidDictionary()) { + return false; + } + final String tmpDictFilePath = mDictFilePath + DICT_FILE_NAME_SUFFIX_FOR_MIGRATION; + // TODO: Implement migrateNative(tmpDictFilePath, newFormatVersion). + close(); + final File dictFile = new File(mDictFilePath); + final File tmpDictFile = new File(tmpDictFilePath); + FileUtils.deleteRecursively(dictFile); + if (!BinaryDictionaryUtils.renameDict(tmpDictFile, dictFile)) { + return false; + } + loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */, + dictFile.length(), mIsUpdatable); + return true; + } + @UsedForTesting public int calculateProbability(final int unigramProbability, final int bigramProbability) { if (!isValidDictionary()) return NOT_A_PROBABILITY; diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java index c2941e424..4e17f8389 100644 --- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java @@ -262,11 +262,6 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { } @Override - protected boolean needsToReloadAfterCreation() { - return true; - } - - @Override protected boolean haveContentsChanged() { final long startTime = SystemClock.uptimeMillis(); final int contactCount = getContactCount(); diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index 3c1015926..92b535458 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -137,6 +137,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return formatVersion == FormatSpec.VERSION4; } + private boolean needsToMigrateDictionary(final int formatVersion) { + // TODO: Check version. + return false; + } + public boolean isValidDictionaryLocked() { return mBinaryDictionary.isValidDictionary(); } @@ -477,15 +482,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { if (oldBinaryDictionary != null) { oldBinaryDictionary.close(); } + if (mBinaryDictionary.isValidDictionary() + && needsToMigrateDictionary(mBinaryDictionary.getFormatVersion())) { + mBinaryDictionary.migrateTo(DICTIONARY_FORMAT_VERSION); + } } /** - * Abstract method for checking if it is required to reload the dictionary before writing - * a binary dictionary. - */ - abstract protected boolean needsToReloadAfterCreation(); - - /** * Create a new binary dictionary and load initial contents. */ private void createNewDictionaryLocked() { diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 6985d9a84..db0a8a81c 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -101,19 +101,6 @@ public final class Suggest { : typedWord; LatinImeLogger.onAddSuggestedWord(typedWord, Dictionary.TYPE_USER_TYPED); - final WordComposer wordComposerForLookup; - if (trailingSingleQuotesCount > 0) { - wordComposerForLookup = new WordComposer(wordComposer); - for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) { - // TODO: do not create a fake event for this. Ideally the word composer should know - // how to give out the word without trailing quotes and we can remove this entirely - wordComposerForLookup.deleteLast(Event.createSoftwareKeypressEvent( - Event.NOT_A_CODE_POINT, Constants.CODE_DELETE, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE)); - } - } else { - wordComposerForLookup = wordComposer; - } final ArrayList<SuggestedWordInfo> rawSuggestions; if (ProductionFlag.INCLUDE_RAW_SUGGESTIONS) { rawSuggestions = CollectionUtils.newArrayList(); @@ -121,7 +108,7 @@ public final class Suggest { rawSuggestions = null; } final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults( - wordComposerForLookup, prevWordForBigram, proximityInfo, blockOffensiveWords, + wordComposer, prevWordForBigram, proximityInfo, blockOffensiveWords, additionalFeaturesOptions, SESSION_TYPING, rawSuggestions); final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java index 8078ab541..8838e27c4 100644 --- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java @@ -269,9 +269,4 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { protected boolean haveContentsChanged() { return true; } - - @Override - protected boolean needsToReloadAfterCreation() { - return true; - } } diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index a955f375b..324683c86 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -104,25 +104,6 @@ public final class WordComposer { refreshSize(); } - public WordComposer(final WordComposer source) { - mCombinerChain = source.mCombinerChain; - mPrimaryKeyCodes = Arrays.copyOf(source.mPrimaryKeyCodes, source.mPrimaryKeyCodes.length); - mEvents = new ArrayList<Event>(source.mEvents); - mTypedWord = new StringBuilder(source.mTypedWord); - mInputPointers.copy(source.mInputPointers); - mCapsCount = source.mCapsCount; - mDigitsCount = source.mDigitsCount; - mIsFirstCharCapitalized = source.mIsFirstCharCapitalized; - mCapitalizedMode = source.mCapitalizedMode; - mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount; - mIsResumed = source.mIsResumed; - mIsBatchMode = source.mIsBatchMode; - mCursorPositionWithinWord = source.mCursorPositionWithinWord; - mRejectedBatchModeSuggestion = source.mRejectedBatchModeSuggestion; - mPreviousWordForSuggestion = source.mPreviousWordForSuggestion; - refreshSize(); - } - /** * Clear out the keys registered so far. */ @@ -155,6 +136,13 @@ public final class WordComposer { return mCodePointSize; } + // When the composition contains trailing quotes, we don't pass them to the suggestion engine. + // This is because "'tgis'" should be corrected to "'this'", but we can't afford to consider + // single quotes as separators because of their very common use as apostrophes. + public int sizeWithoutTrailingSingleQuotes() { + return size() - mTrailingSingleQuotesCount; + } + public final boolean isComposingWord() { return size() > 0; } diff --git a/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java b/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java index 9dcd63f0c..5fcbb6357 100644 --- a/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java +++ b/java/src/com/android/inputmethod/latin/makedict/ProbabilityInfo.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin.makedict; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.BinaryDictionary; import com.android.inputmethod.latin.utils.CombinedFormatUtils; @@ -30,6 +31,7 @@ public final class ProbabilityInfo { public final int mLevel; public final int mCount; + @UsedForTesting public static ProbabilityInfo max(final ProbabilityInfo probabilityInfo1, final ProbabilityInfo probabilityInfo2) { if (probabilityInfo1 == null) { diff --git a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java index d94cec424..853392200 100644 --- a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java +++ b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java @@ -42,6 +42,7 @@ public final class WordProperty implements Comparable<WordProperty> { private int mHashCode = 0; + @UsedForTesting public WordProperty(final String word, final ProbabilityInfo probabilityInfo, final ArrayList<WeightedString> shortcutTargets, final ArrayList<WeightedString> bigrams, diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java index 074ec4074..6f84e1f10 100644 --- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java +++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java @@ -91,11 +91,6 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB return false; } - @Override - protected boolean needsToReloadAfterCreation() { - return false; - } - public void addMultipleDictionaryEntriesToDictionary( final ArrayList<LanguageModelParam> languageModelParams, final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) { diff --git a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java index 638830046..b4658b531 100644 --- a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java @@ -26,6 +26,8 @@ import java.io.File; import java.io.IOException; import java.util.Locale; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public final class BinaryDictionaryUtils { private static final String TAG = BinaryDictionaryUtils.class.getSimpleName(); @@ -64,6 +66,31 @@ public final class BinaryDictionaryUtils { return header; } + public static boolean renameDict(final File dictFile, final File newDictFile) { + if (dictFile.isFile()) { + return dictFile.renameTo(newDictFile); + } else if (dictFile.isDirectory()) { + final String dictName = dictFile.getName(); + final String newDictName = newDictFile.getName(); + if (newDictFile.exists()) { + return false; + } + for (final File file : dictFile.listFiles()) { + if (!file.isFile()) { + continue; + } + final String fileName = file.getName(); + final String newFileName = fileName.replaceFirst( + Pattern.quote(dictName), Matcher.quoteReplacement(newDictName)); + if (!file.renameTo(new File(dictFile, newFileName))) { + return false; + } + } + return dictFile.renameTo(newDictFile); + } + return false; + } + public static boolean createEmptyDictFile(final String filePath, final long dictVersion, final Locale locale, final Map<String, String> attributeMap) { final String[] keyArray = new String[attributeMap.size()]; diff --git a/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h index 3118cdfa3..258aa9ce3 100644 --- a/native/jni/src/suggest/core/dicnode/dic_node.h +++ b/native/jni/src/suggest/core/dicnode/dic_node.h @@ -83,14 +83,6 @@ class DicNode { #if DEBUG_DICT DicNodeProfiler mProfiler; #endif - ////////////////// - // Memory utils // - ////////////////// - AK_FORCE_INLINE static void managedDelete(DicNode *node) { - node->remove(); - } - // end - ///////////////// AK_FORCE_INLINE DicNode() : @@ -158,7 +150,7 @@ class DicNode { PROF_NODE_COPY(&dicNode->mProfiler, mProfiler); } - AK_FORCE_INLINE void remove() { + AK_FORCE_INLINE void finalize() { mIsUsed = false; if (mReleaseListener) { mReleaseListener->onReleased(this); @@ -478,17 +470,7 @@ class DicNode { mReleaseListener = releaseListener; } - AK_FORCE_INLINE bool compare(const DicNode *right) { - if (!isUsed() && !right->isUsed()) { - // Compare pointer values here for stable comparison - return this > right; - } - if (!isUsed()) { - return true; - } - if (!right->isUsed()) { - return false; - } + AK_FORCE_INLINE bool compare(const DicNode *right) const { // Promote exact matches to prevent them from being pruned. const bool leftExactMatch = ErrorTypeUtils::isExactMatch(getContainedErrorTypes()); const bool rightExactMatch = ErrorTypeUtils::isExactMatch(right->getContainedErrorTypes()); diff --git a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h index 1f02731a5..213b1b968 100644 --- a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h +++ b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h @@ -68,15 +68,15 @@ class DicNodePriorityQueue : public DicNodeReleaseListener { } setMaxSize(maxSize); for (int i = 0; i < mCapacity + 1; ++i) { - mDicNodesBuf[i].remove(); + mDicNodesBuf[i].finalize(); mDicNodesBuf[i].setReleaseListener(this); - mUnusedNodeIndices[i] = i == mCapacity ? NOT_A_NODE_ID : static_cast<int>(i) + 1; + mUnusedNodeIndices[i] = (i == mCapacity) ? NOT_A_NODE_ID : (i + 1); } mNextUnusedNodeId = 0; } // Copy - AK_FORCE_INLINE DicNode *copyPush(DicNode *dicNode) { + AK_FORCE_INLINE DicNode *copyPush(const DicNode *const dicNode) { return copyPush(dicNode, mMaxSize); } @@ -89,11 +89,11 @@ class DicNodePriorityQueue : public DicNodeReleaseListener { if (dest) { DicNodeUtils::initByCopy(node, dest); } - node->remove(); + node->finalize(); mDicNodesQueue.pop(); } - void onReleased(DicNode *dicNode) { + void onReleased(const DicNode *dicNode) { const int index = static_cast<int>(dicNode - &mDicNodesBuf[0]); if (mUnusedNodeIndices[index] != NOT_A_NODE_ID) { // it's already released @@ -118,7 +118,8 @@ class DicNodePriorityQueue : public DicNodeReleaseListener { DISALLOW_IMPLICIT_CONSTRUCTORS(DicNodePriorityQueue); static const int NOT_A_NODE_ID = -1; - AK_FORCE_INLINE static bool compareDicNode(DicNode *left, DicNode *right) { + AK_FORCE_INLINE static bool compareDicNode(const DicNode *const left, + const DicNode *const right) { return left->compare(right); } @@ -141,10 +142,10 @@ class DicNodePriorityQueue : public DicNodeReleaseListener { } AK_FORCE_INLINE void pop() { - copyPop(0); + copyPop(nullptr); } - AK_FORCE_INLINE bool betterThanWorstDicNode(DicNode *dicNode) const { + AK_FORCE_INLINE bool betterThanWorstDicNode(const DicNode *const dicNode) const { DicNode *worstNode = mDicNodesQueue.top(); if (!worstNode) { return true; @@ -154,7 +155,7 @@ class DicNodePriorityQueue : public DicNodeReleaseListener { AK_FORCE_INLINE DicNode *searchEmptyDicNode() { if (mCapacity == 0) { - return 0; + return nullptr; } if (mNextUnusedNodeId == NOT_A_NODE_ID) { AKLOGI("No unused node found."); @@ -163,7 +164,7 @@ class DicNodePriorityQueue : public DicNodeReleaseListener { i, mDicNodesBuf[i].isUsed(), mUnusedNodeIndices[i]); } ASSERT(false); - return 0; + return nullptr; } DicNode *dicNode = &mDicNodesBuf[mNextUnusedNodeId]; markNodeAsUsed(dicNode); @@ -179,7 +180,7 @@ class DicNodePriorityQueue : public DicNodeReleaseListener { AK_FORCE_INLINE DicNode *pushPoolNodeWithMaxSize(DicNode *dicNode, const int maxSize) { if (!dicNode) { - return 0; + return nullptr; } if (!isFull(maxSize)) { mDicNodesQueue.push(dicNode); @@ -190,16 +191,16 @@ class DicNodePriorityQueue : public DicNodeReleaseListener { mDicNodesQueue.push(dicNode); return dicNode; } - dicNode->remove(); - return 0; + dicNode->finalize(); + return nullptr; } // Copy - AK_FORCE_INLINE DicNode *copyPush(DicNode *dicNode, const int maxSize) { + AK_FORCE_INLINE DicNode *copyPush(const DicNode *const dicNode, const int maxSize) { return pushPoolNodeWithMaxSize(newDicNode(dicNode), maxSize); } - AK_FORCE_INLINE DicNode *newDicNode(DicNode *dicNode) { + AK_FORCE_INLINE DicNode *newDicNode(const DicNode *const dicNode) { DicNode *newNode = searchEmptyDicNode(); if (newNode) { DicNodeUtils::initByCopy(dicNode, newNode); diff --git a/native/jni/src/suggest/core/dicnode/dic_node_release_listener.h b/native/jni/src/suggest/core/dicnode/dic_node_release_listener.h index 2ca4f21bd..c3f432951 100644 --- a/native/jni/src/suggest/core/dicnode/dic_node_release_listener.h +++ b/native/jni/src/suggest/core/dicnode/dic_node_release_listener.h @@ -27,7 +27,7 @@ class DicNodeReleaseListener { public: DicNodeReleaseListener() {} virtual ~DicNodeReleaseListener() {} - virtual void onReleased(DicNode *dicNode) = 0; + virtual void onReleased(const DicNode *dicNode) = 0; private: DISALLOW_COPY_AND_ASSIGN(DicNodeReleaseListener); }; diff --git a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h index d4769e739..6b8dc8c96 100644 --- a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h +++ b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h @@ -100,14 +100,7 @@ class DicNodesCache { } AK_FORCE_INLINE void copyPushNextActive(DicNode *dicNode) { - DicNode *pushedDicNode = mNextActiveDicNodes->copyPush(dicNode); - if (!pushedDicNode) { - if (dicNode->isCached()) { - dicNode->remove(); - } - // We simply drop any dic node that was not cached, ignoring the slim chance - // that one of its children represents what the user really wanted. - } + mNextActiveDicNodes->copyPush(dicNode); } void popTerminal(DicNode *dest) { diff --git a/native/jni/src/suggest/core/result/suggestions_output_utils.cpp b/native/jni/src/suggest/core/result/suggestions_output_utils.cpp index b40f3226f..d07f5ca41 100644 --- a/native/jni/src/suggest/core/result/suggestions_output_utils.cpp +++ b/native/jni/src/suggest/core/result/suggestions_output_utils.cpp @@ -131,7 +131,6 @@ const int SuggestionsOutputUtils::MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT = 16; true /* forceCommit */, boostExactMatches) : finalScore; outputShortcuts(&shortcutIt, shortcutBaseScore, sameAsTyped, outSuggestionResults); } - DicNode::managedDelete(terminalDicNode); } scoringPolicy->getMostProbableString(traverseSession, languageWeight, outSuggestionResults); } diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp index 2ea6452af..303182cf4 100644 --- a/native/jni/src/suggest/core/suggest.cpp +++ b/native/jni/src/suggest/core/suggest.cpp @@ -265,7 +265,6 @@ void Suggest::processExpandedDicNode( traverseSession->getDicTraverseCache()->copyPushNextActive(dicNode); } } - DicNode::managedDelete(dicNode); } void Suggest::processDicNodeAsMatch(DicTraverseSession *traverseSession, @@ -388,7 +387,6 @@ void Suggest::processDicNodeAsTransposition(DicTraverseSession *traverseSession, processExpandedDicNode(traverseSession, childDicNode2); } } - DicNode::managedDelete(childDicNodes1[i]); } } diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java index d4e6ad87a..b36645289 100644 --- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java +++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java @@ -474,4 +474,43 @@ public class InputLogicTests extends InputTestsBase { WORD_TO_TYPE.length() * TIMES_TO_TYPE - TIMES_TO_BACKSPACE, mEditText.getText().length()); } + + public void testManySingleQuotes() { + final String WORD_TO_AUTOCORRECT = "i"; + final String WORD_AUTOCORRECTED = "I"; + final String QUOTES = "''''''''''''''''''''"; + final String WORD_TO_TYPE = WORD_TO_AUTOCORRECT + QUOTES + " "; + final String EXPECTED_RESULT = WORD_AUTOCORRECTED + QUOTES + " "; + type(WORD_TO_TYPE); + assertEquals("auto-correct with many trailing single quotes", EXPECTED_RESULT, + mEditText.getText().toString()); + } + + public void testManySingleQuotesOneByOne() { + final String WORD_TO_AUTOCORRECT = "i"; + final String WORD_AUTOCORRECTED = "I"; + final String QUOTES = "''''''''''''''''''''"; + final String WORD_TO_TYPE = WORD_TO_AUTOCORRECT + QUOTES + " "; + final String EXPECTED_RESULT = WORD_AUTOCORRECTED + QUOTES + " "; + + for (int i = 0; i < WORD_TO_TYPE.length(); ++i) { + type(WORD_TO_TYPE.substring(i, i+1)); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + } + assertEquals("type many trailing single quotes one by one", EXPECTED_RESULT, + mEditText.getText().toString()); + } + + public void testTypingSingleQuotesOneByOne() { + final String WORD_TO_TYPE = "it's "; + final String EXPECTED_RESULT = WORD_TO_TYPE; + for (int i = 0; i < WORD_TO_TYPE.length(); ++i) { + type(WORD_TO_TYPE.substring(i, i+1)); + sleep(DELAY_TO_WAIT_FOR_PREDICTIONS); + runMessages(); + } + assertEquals("type words letter by letter", EXPECTED_RESULT, + mEditText.getText().toString()); + } } diff --git a/tests/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtilsTests.java new file mode 100644 index 000000000..d86639101 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtilsTests.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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.utils; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; + +import com.android.inputmethod.latin.BinaryDictionary; +import com.android.inputmethod.latin.makedict.DictionaryHeader; +import com.android.inputmethod.latin.makedict.FormatSpec; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@LargeTest +public class BinaryDictionaryUtilsTests extends AndroidTestCase { + private static final String TEST_DICT_FILE_EXTENSION = ".testDict"; + private static final String TEST_LOCALE = "test"; + + private File createEmptyDictionaryAndGetFile(final String dictId, + final int formatVersion) throws IOException { + if (formatVersion == FormatSpec.VERSION4) { + return createEmptyVer4DictionaryAndGetFile(dictId); + } else { + throw new IOException("Dictionary format version " + formatVersion + + " is not supported."); + } + } + + private File createEmptyVer4DictionaryAndGetFile(final String dictId) throws IOException { + final File file = getDictFile(dictId); + FileUtils.deleteRecursively(file); + Map<String, String> attributeMap = new HashMap<String, String>(); + attributeMap.put(DictionaryHeader.DICTIONARY_ID_KEY, dictId); + attributeMap.put(DictionaryHeader.DICTIONARY_VERSION_KEY, + String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()))); + attributeMap.put(DictionaryHeader.USES_FORGETTING_CURVE_KEY, + DictionaryHeader.ATTRIBUTE_VALUE_TRUE); + attributeMap.put(DictionaryHeader.HAS_HISTORICAL_INFO_KEY, + DictionaryHeader.ATTRIBUTE_VALUE_TRUE); + if (BinaryDictionaryUtils.createEmptyDictFile(file.getAbsolutePath(), FormatSpec.VERSION4, + LocaleUtils.constructLocaleFromString(TEST_LOCALE), attributeMap)) { + return file; + } else { + throw new IOException("Empty dictionary " + file.getAbsolutePath() + + " cannot be created."); + } + } + + private File getDictFile(final String dictId) { + return new File(getContext().getCacheDir(), dictId + TEST_DICT_FILE_EXTENSION); + } + + public void testRenameDictionary() { + final int formatVersion = FormatSpec.VERSION4; + File dictFile0 = null; + try { + dictFile0 = createEmptyDictionaryAndGetFile("MoveFromDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + final File dictFile1 = getDictFile("MoveToDictionary"); + FileUtils.deleteRecursively(dictFile1); + assertTrue(BinaryDictionaryUtils.renameDict(dictFile0, dictFile1)); + assertFalse(dictFile0.exists()); + assertTrue(dictFile1.exists()); + BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile1.getAbsolutePath(), + 0 /* offset */, dictFile1.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + assertTrue(binaryDictionary.isValidDictionary()); + assertTrue(binaryDictionary.getFormatVersion() == formatVersion); + binaryDictionary.close(); + } +} |