diff options
16 files changed, 201 insertions, 49 deletions
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java index 138a626a0..43d4ba421 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java @@ -237,6 +237,24 @@ public class DictionaryFacilitatorForSuggest { mLatchForWaitingLoadingMainDictionary.await(timeout, unit); } + @UsedForTesting + public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit) + throws InterruptedException { + waitForLoadingMainDictionary(timeout, unit); + if (mContactsDictionary != null) { + mContactsDictionary.waitAllTasksForTests(); + } + if (mUserDictionary != null) { + mUserDictionary.waitAllTasksForTests(); + } + if (mUserHistoryDictionary != null) { + mUserHistoryDictionary.waitAllTasksForTests(); + } + if (mPersonalizationDictionary != null) { + mPersonalizationDictionary.waitAllTasksForTests(); + } + } + private void setMainDictionary(final Dictionary mainDictionary) { mMainDictionary = mainDictionary; addOrReplaceDictionary(Dictionary.TYPE_MAIN, mainDictionary); diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index 3b9be4395..230739d6f 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -62,7 +62,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private static final boolean DBG_STRESS_TEST = false; private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100; - private static final int TIMEOUT_FOR_READ_OPS_FOR_TESTS_IN_MILLISECONDS = 1000; + private static final int TIMEOUT_FOR_READ_OPS_FOR_TESTS_IN_MILLISECONDS = 10000; /** * The maximum length of a word in this dictionary. @@ -750,7 +750,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { @UsedForTesting public boolean isInUnderlyingBinaryDictionaryForTests(final String word) { final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>(); - getExecutor(mDictName).executePrioritized(new Runnable() { + getExecutor(mDictName).execute(new Runnable() { @Override public void run() { if (mDictType == Dictionary.TYPE_USER_HISTORY) { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 94e3e7418..47137e7fb 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -541,7 +541,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen shouldKeepUserHistoryDictionaries = true; // TODO: Eliminate this restriction shouldKeepPersonalizationDictionaries = - mSubtypeSwitcher.isSystemLocaleSameAsLocaleOfAllEnabledSubtypes(); + mSubtypeSwitcher.isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes(); } else { shouldKeepUserHistoryDictionaries = false; shouldKeepPersonalizationDictionaries = false; @@ -799,19 +799,22 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen suggest = mInputLogic.mSuggest; } - // Sometimes, while rotating, for some reason the framework tells the app we are not - // connected to it and that means we can't refresh the cache. In this case, schedule a - // refresh later. // TODO[IL]: Can the following be moved to InputLogic#startInput? final boolean canReachInputConnection; if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess( editorInfo.initialSelStart, editorInfo.initialSelEnd, false /* shouldFinishComposition */)) { + // Sometimes, while rotating, for some reason the framework tells the app we are not + // connected to it and that means we can't refresh the cache. In this case, schedule a + // refresh later. // We try resetting the caches up to 5 times before giving up. mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */); // mLastSelection{Start,End} are reset later in this method, don't need to do it here canReachInputConnection = false; } else { + // When rotating, initialSelStart and initialSelEnd sometimes are lying. Make a best + // effort to work around this bug. + mInputLogic.mConnection.tryFixLyingCursorPosition(); if (isDifferentTextField) { mHandler.postResumeSuggestions(); } @@ -1717,9 +1720,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME. @UsedForTesting - /* package for test */ void waitForMainDictionary(final long timeout, final TimeUnit unit) + /* package for test */ void waitForLoadingDictionaries(final long timeout, final TimeUnit unit) throws InterruptedException { - mInputLogic.mSuggest.mDictionaryFacilitator.waitForLoadingMainDictionary(timeout, unit); + mInputLogic.mSuggest.mDictionaryFacilitator.waitForLoadingDictionariesForTesting( + timeout, unit); } // DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly. @@ -1733,6 +1737,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen resetSuggest(new Suggest(locale, dictionaryFacilitator)); } + // DO NOT USE THIS for any other purpose than testing. + @UsedForTesting + /* package for test */ void clearPersonalizedDictionariesForTest() { + mInputLogic.mSuggest.mDictionaryFacilitator.clearUserHistoryDictionary(); + mInputLogic.mSuggest.mDictionaryFacilitator.clearPersonalizationDictionary(); + } + public void dumpDictionaryForDebug(final String dictName) { if (mInputLogic.mSuggest == null) { initSuggest(); diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index cc2db4c93..0e85b3c77 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -172,20 +172,6 @@ public final class RichInputConnection { Log.d(TAG, "Will try to retrieve text later."); return false; } - final int lengthOfTextBeforeCursor = mCommittedTextBeforeComposingText.length(); - if (lengthOfTextBeforeCursor > newSelStart - || (newSelStart != lengthOfTextBeforeCursor - && lengthOfTextBeforeCursor < Constants.EDITOR_CONTENTS_CACHE_SIZE - && newSelStart < Constants.EDITOR_CONTENTS_CACHE_SIZE)) { - // newSelStart and newSelEnd may be lying -- when rotating the device (probably a - // framework bug). If the values don't agree and we have less chars than we asked - // for, then we know how many chars we have. If we got more than newSelStart says, then - // we also know it was lying. In both cases the length is more reliable. Note that we - // only have to check newSelStart (not newSelEnd) since if newSelEnd is wrong, then - // newSelStart will be wrong as well. - mExpectedSelStart = lengthOfTextBeforeCursor; - mExpectedSelEnd = lengthOfTextBeforeCursor; - } if (null != mIC && shouldFinishComposition) { mIC.finishComposingText(); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 860575a1f..935dd9667 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -37,9 +37,11 @@ import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.latin.utils.LocaleUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; public final class SubtypeSwitcher { private static boolean DBG = LatinImeLogger.sDBG; @@ -273,12 +275,26 @@ public final class SubtypeSwitcher { return mNeedsToDisplayLanguage.getValue(); } - public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypes() { + public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() { final Locale systemLocale = mResources.getConfiguration().locale; - final List<InputMethodSubtype> enabledSubtypesOfThisIme = - mRichImm.getMyEnabledInputMethodSubtypeList(true); - for (final InputMethodSubtype subtype : enabledSubtypesOfThisIme) { - if (!systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) { + final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = + new HashSet<InputMethodSubtype>(); + final InputMethodManager inputMethodManager = mRichImm.getInputMethodManager(); + final List<InputMethodInfo> enabledInputMethodInfoList = + inputMethodManager.getEnabledInputMethodList(); + for (final InputMethodInfo info : enabledInputMethodInfoList) { + final List<InputMethodSubtype> enabledSubtypes = + inputMethodManager.getEnabledInputMethodSubtypeList( + info, true /* allowsImplicitlySelectedSubtypes */); + if (enabledSubtypes.isEmpty()) { + // An IME with no subtypes is found. + return false; + } + enabledSubtypesOfEnabledImes.addAll(enabledSubtypes); + } + for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) { + if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty() + && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) { return false; } } diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index d3c1a2aec..eeb5bf536 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -77,9 +77,10 @@ public final class InputLogic { public int mSpaceState; // Never null public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; - public Suggest mSuggest; + // TODO: mSuggest should be touched by a single thread. + public volatile Suggest mSuggest; // The event interpreter should never be null. - public EventInterpreter mEventInterpreter; + public final EventInterpreter mEventInterpreter; public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; public final WordComposer mWordComposer; diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java index 23aa05d18..88fff38f2 100644 --- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java @@ -53,6 +53,10 @@ public class Ver4DictDecoder extends AbstractDictDecoder { @Override public DictionaryHeader readHeader() throws IOException, UnsupportedFormatException { + final DictionaryHeader header = mBinaryDictionary.getHeader(); + if (header == null) { + throw new IOException("Cannot read the dictionary header."); + } return mBinaryDictionary.getHeader(); } diff --git a/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h b/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h index 784419586..38e8ff183 100644 --- a/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h +++ b/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h @@ -100,6 +100,8 @@ class DictionaryStructureWithBufferPolicy { // starts iterating the dictionary. virtual int getNextWordAndNextToken(const int token, int *const outCodePoints) = 0; + virtual bool isCorrupted() const = 0; + protected: DictionaryStructureWithBufferPolicy() {} diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp index fa5993090..212f2ef39 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp @@ -36,6 +36,7 @@ void PatriciaTriePolicy::createAndGetAllChildDicNodes(const DicNode *const dicNo if (nextPos < 0 || nextPos >= mDictBufferSize) { AKLOGE("Children PtNode array position is invalid. pos: %d, dict size: %d", nextPos, mDictBufferSize); + mIsCorrupted = true; ASSERT(false); return; } @@ -45,6 +46,7 @@ void PatriciaTriePolicy::createAndGetAllChildDicNodes(const DicNode *const dicNo if (nextPos < 0 || nextPos >= mDictBufferSize) { AKLOGE("Child PtNode position is invalid. pos: %d, dict size: %d, childCount: %d / %d", nextPos, mDictBufferSize, i, childCount); + mIsCorrupted = true; ASSERT(false); return; } @@ -239,7 +241,13 @@ int PatriciaTriePolicy::getTerminalPtNodePositionOfWord(const int *const inWord, const int length, const bool forceLowerCaseSearch) const { DynamicPtReadingHelper readingHelper(&mPtNodeReader, &mPtNodeArrayReader); readingHelper.initWithPtNodeArrayPos(getRootPosition()); - return readingHelper.getTerminalPtNodePositionOfWord(inWord, length, forceLowerCaseSearch); + const int ptNodePos = + readingHelper.getTerminalPtNodePositionOfWord(inWord, length, forceLowerCaseSearch); + if (readingHelper.isError()) { + mIsCorrupted = true; + AKLOGE("Dictionary reading error in createAndGetAllChildDicNodes()."); + } + return ptNodePos; } int PatriciaTriePolicy::getProbability(const int unigramProbability, diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h index 8fbca2612..6a2345a05 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h @@ -46,7 +46,7 @@ class PatriciaTriePolicy : public DictionaryStructureWithBufferPolicy { mBigramListPolicy(mDictRoot), mShortcutListPolicy(mDictRoot), mPtNodeReader(mDictRoot, mDictBufferSize, &mBigramListPolicy, &mShortcutListPolicy), mPtNodeArrayReader(mDictRoot, mDictBufferSize), - mTerminalPtNodePositionsForIteratingWords() {} + mTerminalPtNodePositionsForIteratingWords(), mIsCorrupted(false) {} AK_FORCE_INLINE int getRootPosition() const { return 0; @@ -134,6 +134,10 @@ class PatriciaTriePolicy : public DictionaryStructureWithBufferPolicy { int getNextWordAndNextToken(const int token, int *const outCodePoints); + bool isCorrupted() const { + return mIsCorrupted; + } + private: DISALLOW_IMPLICIT_CONSTRUCTORS(PatriciaTriePolicy); @@ -146,6 +150,7 @@ class PatriciaTriePolicy : public DictionaryStructureWithBufferPolicy { const Ver2ParticiaTrieNodeReader mPtNodeReader; const Ver2PtNodeArrayReader mPtNodeArrayReader; std::vector<int> mTerminalPtNodePositionsForIteratingWords; + mutable bool mIsCorrupted; int createAndGetLeavingChildNode(const DicNode *const dicNode, const int ptNodePos, DicNodeVector *const childDicNodes) const; diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp index efc29a0c3..b5d80be1d 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp @@ -65,6 +65,10 @@ void Ver4PatriciaTriePolicy::createAndGetAllChildDicNodes(const DicNode *const d ptNodeParams.getCodePointCount(), ptNodeParams.getCodePoints()); readingHelper.readNextSiblingNode(ptNodeParams); } + if (readingHelper.isError()) { + mIsCorrupted = true; + AKLOGE("Dictionary reading error in createAndGetAllChildDicNodes()."); + } } int Ver4PatriciaTriePolicy::getCodePointsAndProbabilityAndReturnCodePointCount( @@ -72,15 +76,26 @@ int Ver4PatriciaTriePolicy::getCodePointsAndProbabilityAndReturnCodePointCount( int *const outUnigramProbability) const { DynamicPtReadingHelper readingHelper(&mNodeReader, &mPtNodeArrayReader); readingHelper.initWithPtNodePos(ptNodePos); - return readingHelper.getCodePointsAndProbabilityAndReturnCodePointCount( + const int codePointCount = readingHelper.getCodePointsAndProbabilityAndReturnCodePointCount( maxCodePointCount, outCodePoints, outUnigramProbability); + if (readingHelper.isError()) { + mIsCorrupted = true; + AKLOGE("Dictionary reading error in getCodePointsAndProbabilityAndReturnCodePointCount()."); + } + return codePointCount; } int Ver4PatriciaTriePolicy::getTerminalPtNodePositionOfWord(const int *const inWord, const int length, const bool forceLowerCaseSearch) const { DynamicPtReadingHelper readingHelper(&mNodeReader, &mPtNodeArrayReader); readingHelper.initWithPtNodeArrayPos(getRootPosition()); - return readingHelper.getTerminalPtNodePositionOfWord(inWord, length, forceLowerCaseSearch); + const int ptNodePos = + readingHelper.getTerminalPtNodePositionOfWord(inWord, length, forceLowerCaseSearch); + if (readingHelper.isError()) { + mIsCorrupted = true; + AKLOGE("Dictionary reading error in createAndGetAllChildDicNodes()."); + } + return ptNodePos; } int Ver4PatriciaTriePolicy::getProbability(const int unigramProbability, @@ -265,7 +280,10 @@ void Ver4PatriciaTriePolicy::flush(const char *const filePath) { AKLOGI("Warning: flush() is called for non-updatable dictionary. filePath: %s", filePath); return; } - mWritingHelper.writeToDictFile(filePath, mUnigramCount, mBigramCount); + if (!mWritingHelper.writeToDictFile(filePath, mUnigramCount, mBigramCount)) { + AKLOGE("Cannot flush the dictionary to file."); + mIsCorrupted = true; + } } void Ver4PatriciaTriePolicy::flushWithGC(const char *const filePath) { @@ -273,7 +291,10 @@ void Ver4PatriciaTriePolicy::flushWithGC(const char *const filePath) { AKLOGI("Warning: flushWithGC() is called for non-updatable dictionary."); return; } - mWritingHelper.writeToDictFileWithGC(getRootPosition(), filePath); + if (!mWritingHelper.writeToDictFileWithGC(getRootPosition(), filePath)) { + AKLOGE("Cannot flush the dictionary to file with GC."); + mIsCorrupted = true; + } } bool Ver4PatriciaTriePolicy::needsToRunGC(const bool mindsBlockByGC) const { diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h index 692163058..7796e2ddc 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h @@ -55,7 +55,7 @@ class Ver4PatriciaTriePolicy : public DictionaryStructureWithBufferPolicy { mWritingHelper(mBuffers.get()), mUnigramCount(mHeaderPolicy->getUnigramCount()), mBigramCount(mHeaderPolicy->getBigramCount()), - mTerminalPtNodePositionsForIteratingWords() {}; + mTerminalPtNodePositionsForIteratingWords(), mIsCorrupted(false) {}; AK_FORCE_INLINE int getRootPosition() const { return 0; @@ -116,6 +116,10 @@ class Ver4PatriciaTriePolicy : public DictionaryStructureWithBufferPolicy { int getNextWordAndNextToken(const int token, int *const outCodePoints); + bool isCorrupted() const { + return mIsCorrupted; + } + private: DISALLOW_IMPLICIT_CONSTRUCTORS(Ver4PatriciaTriePolicy); @@ -141,6 +145,7 @@ class Ver4PatriciaTriePolicy : public DictionaryStructureWithBufferPolicy { int mUnigramCount; int mBigramCount; std::vector<int> mTerminalPtNodePositionsForIteratingWords; + mutable bool mIsCorrupted; }; } // namespace latinime #endif // LATINIME_VER4_PATRICIA_TRIE_POLICY_H diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp index acf099122..93053c38d 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp @@ -33,7 +33,7 @@ namespace latinime { -void Ver4PatriciaTrieWritingHelper::writeToDictFile(const char *const dictDirPath, +bool Ver4PatriciaTrieWritingHelper::writeToDictFile(const char *const dictDirPath, const int unigramCount, const int bigramCount) const { const HeaderPolicy *const headerPolicy = mBuffers->getHeaderPolicy(); BufferWithExtendableBuffer headerBuffer( @@ -46,12 +46,12 @@ void Ver4PatriciaTrieWritingHelper::writeToDictFile(const char *const dictDirPat "updatesLastDecayedTime: %d, unigramCount: %d, bigramCount: %d, " "extendedRegionSize: %d", false, unigramCount, bigramCount, extendedRegionSize); - return; + return false; } - mBuffers->flushHeaderAndDictBuffers(dictDirPath, &headerBuffer); + return mBuffers->flushHeaderAndDictBuffers(dictDirPath, &headerBuffer); } -void Ver4PatriciaTrieWritingHelper::writeToDictFileWithGC(const int rootPtNodeArrayPos, +bool Ver4PatriciaTrieWritingHelper::writeToDictFileWithGC(const int rootPtNodeArrayPos, const char *const dictDirPath) { const HeaderPolicy *const headerPolicy = mBuffers->getHeaderPolicy(); Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers( @@ -59,15 +59,15 @@ void Ver4PatriciaTrieWritingHelper::writeToDictFileWithGC(const int rootPtNodeAr int unigramCount = 0; int bigramCount = 0; if (!runGC(rootPtNodeArrayPos, headerPolicy, dictBuffers.get(), &unigramCount, &bigramCount)) { - return; + return false; } BufferWithExtendableBuffer headerBuffer( BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE); if (!headerPolicy->fillInAndWriteHeaderToBuffer(true /* updatesLastDecayedTime */, unigramCount, bigramCount, 0 /* extendedRegionSize */, &headerBuffer)) { - return; + return false; } - dictBuffers.get()->flushHeaderAndDictBuffers(dictDirPath, &headerBuffer); + return dictBuffers.get()->flushHeaderAndDictBuffers(dictDirPath, &headerBuffer); } bool Ver4PatriciaTrieWritingHelper::runGC(const int rootPtNodeArrayPos, diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.h index c3a155e0e..bb464ad28 100644 --- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.h +++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.h @@ -33,10 +33,12 @@ class Ver4PatriciaTrieWritingHelper { Ver4PatriciaTrieWritingHelper(Ver4DictBuffers *const buffers) : mBuffers(buffers) {} - void writeToDictFile(const char *const dictDirPath, const int unigramCount, + bool writeToDictFile(const char *const dictDirPath, const int unigramCount, const int bigramCount) const; - void writeToDictFileWithGC(const int rootPtNodeArrayPos, const char *const dictDirPath); + // This method cannot be const because the original dictionary buffer will be updated to detect + // useless PtNodes during GC. + bool writeToDictFileWithGC(const int rootPtNodeArrayPos, const char *const dictDirPath); private: DISALLOW_IMPLICIT_CONSTRUCTORS(Ver4PatriciaTrieWritingHelper); diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java index 4b157e700..690c559e8 100644 --- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java +++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java @@ -275,9 +275,9 @@ public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> { } } - protected void waitForDictionaryToBeLoaded() { + protected void waitForDictionariesToBeLoaded() { try { - mLatinIME.waitForMainDictionary( + mLatinIME.waitForLoadingDictionaries( TIMEOUT_TO_WAIT_FOR_LOADING_MAIN_DICTIONARY_IN_SECONDS, TimeUnit.SECONDS); } catch (InterruptedException e) { Log.e(TAG, "Interrupted during waiting for loading main dictionary.", e); @@ -286,7 +286,7 @@ public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> { protected void changeLanguage(final String locale) { changeLanguageWithoutWait(locale); - waitForDictionaryToBeLoaded(); + waitForDictionariesToBeLoaded(); } protected void changeLanguageWithoutWait(final String locale) { @@ -314,6 +314,7 @@ public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> { mLatinIME.loadKeyboard(); runMessages(); mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard(); + mLatinIME.clearPersonalizedDictionariesForTest(); } protected void changeKeyboardLocaleAndDictLocale(final String keyboardLocale, @@ -322,7 +323,7 @@ public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> { if (!keyboardLocale.equals(dictLocale)) { mLatinIME.replaceDictionariesForTest(LocaleUtils.constructLocaleFromString(dictLocale)); } - waitForDictionaryToBeLoaded(); + waitForDictionariesToBeLoaded(); } protected void pickSuggestionManually(final int index, final String suggestion) { diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java index dff5a77d7..b1239f0af 100644 --- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java +++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java @@ -20,6 +20,7 @@ import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; +import com.android.inputmethod.latin.BinaryDictionary; import com.android.inputmethod.latin.ExpandableBinaryDictionary; import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.FileUtils; @@ -44,6 +45,47 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" }; + private int mCurrentTime = 0; + + @Override + protected void setUp() throws Exception { + super.setUp(); + resetCurrentTimeForTestMode(); + } + + @Override + protected void tearDown() throws Exception { + stopTestModeInNativeCode(); + super.tearDown(); + } + + private void resetCurrentTimeForTestMode() { + mCurrentTime = 0; + setCurrentTimeForTestMode(mCurrentTime); + } + + private void forcePassingShortTime() { + // 3 days. + final int timeToElapse = (int)TimeUnit.DAYS.toSeconds(3); + mCurrentTime += timeToElapse; + setCurrentTimeForTestMode(mCurrentTime); + } + + private void forcePassingLongTime() { + // 60 days. + final int timeToElapse = (int)TimeUnit.DAYS.toSeconds(60); + mCurrentTime += timeToElapse; + setCurrentTimeForTestMode(mCurrentTime); + } + + private static int setCurrentTimeForTestMode(final int currentTime) { + return BinaryDictionary.setCurrentTimeForTest(currentTime); + } + + private static int stopTestModeInNativeCode() { + return BinaryDictionary.setCurrentTimeForTest(-1); + } + /** * Generates a random word. */ @@ -207,4 +249,34 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { FileUtils.deleteRecursively(dictFile); } } + + public void testDecaying() { + final Locale dummyLocale = new Locale("test_decaying" + System.currentTimeMillis()); + final int numberOfWords = 5000; + final Random random = new Random(123456); + resetCurrentTimeForTestMode(); + clearHistory(dummyLocale); + final List<String> words = generateWords(numberOfWords, random); + final UserHistoryDictionary dict = + PersonalizationHelper.getUserHistoryDictionary(getContext(), dummyLocale); + dict.waitAllTasksForTests(); + String prevWord = null; + for (final String word : words) { + dict.addToDictionary(prevWord, word, true, mCurrentTime); + prevWord = word; + assertTrue(dict.isInUnderlyingBinaryDictionaryForTests(word)); + } + forcePassingShortTime(); + dict.decayIfNeeded(); + dict.waitAllTasksForTests(); + for (final String word : words) { + assertTrue(dict.isInUnderlyingBinaryDictionaryForTests(word)); + } + forcePassingLongTime(); + dict.decayIfNeeded(); + dict.waitAllTasksForTests(); + for (final String word : words) { + assertFalse(dict.isInUnderlyingBinaryDictionaryForTests(word)); + } + } } |