diff options
-rw-r--r-- | native/src/correction.cpp | 36 | ||||
-rw-r--r-- | native/src/correction.h | 3 | ||||
-rw-r--r-- | native/src/defines.h | 31 | ||||
-rw-r--r-- | native/src/unigram_dictionary.cpp | 70 | ||||
-rw-r--r-- | native/src/unigram_dictionary.h | 9 | ||||
-rw-r--r-- | native/src/words_priority_queue.h | 7 | ||||
-rw-r--r-- | native/src/words_priority_queue_pool.h | 15 | ||||
-rw-r--r-- | tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTests.java | 342 | ||||
-rw-r--r-- | tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java | 88 | ||||
-rw-r--r-- | tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java | 75 | ||||
-rw-r--r-- | tools/makedict/src/com/android/inputmethod/latin/Word.java | 4 | ||||
-rw-r--r-- | tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java | 16 |
12 files changed, 558 insertions, 138 deletions
diff --git a/native/src/correction.cpp b/native/src/correction.cpp index c40f94b3f..75831b69d 100644 --- a/native/src/correction.cpp +++ b/native/src/correction.cpp @@ -168,8 +168,8 @@ int Correction::getFinalFreq(const int freq, unsigned short **word, int *wordLen const int outputIndex = mTerminalOutputIndex; const int inputIndex = mTerminalInputIndex; *wordLength = outputIndex + 1; - if (mProximityInfo->sameAsTyped(mWord, outputIndex + 1) || outputIndex < MIN_SUGGEST_DEPTH) { - return -1; + if (outputIndex < MIN_SUGGEST_DEPTH) { + return NOT_A_FREQUENCY; } *word = mWord; @@ -215,20 +215,10 @@ int Correction::goDownTree( } // TODO: remove -int Correction::getOutputIndex() { - return mOutputIndex; -} - -// TODO: remove int Correction::getInputIndex() { return mInputIndex; } -// TODO: remove -bool Correction::needsToTraverseAllNodes() { - return mNeedsToTraverseAllNodes; -} - void Correction::incrementInputIndex() { ++mInputIndex; } @@ -278,13 +268,12 @@ void Correction::addCharToCurrentWord(const int32_t c) { mWord, mOutputIndex + 1); } -// TODO: inline? Correction::CorrectionType Correction::processSkipChar( const int32_t c, const bool isTerminal, const bool inputIndexIncremented) { addCharToCurrentWord(c); - if (needsToTraverseAllNodes() && isTerminal) { - mTerminalInputIndex = mInputIndex - (inputIndexIncremented ? 1 : 0); - mTerminalOutputIndex = mOutputIndex; + mTerminalInputIndex = mInputIndex - (inputIndexIncremented ? 1 : 0); + mTerminalOutputIndex = mOutputIndex; + if (mNeedsToTraverseAllNodes && isTerminal) { incrementOutputIndex(); return TRAVERSE_ALL_ON_TERMINAL; } else { @@ -293,6 +282,13 @@ Correction::CorrectionType Correction::processSkipChar( } } +Correction::CorrectionType Correction::processUnrelatedCorrectionType() { + // Needs to set mTerminalInputIndex and mTerminalOutputIndex before returning any CorrectionType + mTerminalInputIndex = mInputIndex; + mTerminalOutputIndex = mOutputIndex; + return UNRELATED; +} + inline bool isEquivalentChar(ProximityInfo::ProximityType type) { return type == ProximityInfo::EQUIVALENT_CHAR; } @@ -301,7 +297,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( const int32_t c, const bool isTerminal) { const int correctionCount = (mSkippedCount + mExcessiveCount + mTransposedCount); if (correctionCount > mMaxErrors) { - return UNRELATED; + return processUnrelatedCorrectionType(); } // TODO: Change the limit if we'll allow two or more corrections @@ -381,7 +377,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( AKLOGI("UNRELATED(0): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, mTransposedCount, mExcessiveCount, c); } - return UNRELATED; + return processUnrelatedCorrectionType(); } } @@ -484,7 +480,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( AKLOGI("UNRELATED(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, mTransposedCount, mExcessiveCount, c); } - return UNRELATED; + return processUnrelatedCorrectionType(); } } else if (secondTransposing) { // If inputIndex is greater than mInputLength, that means there is no @@ -539,6 +535,8 @@ Correction::CorrectionType Correction::processCharAndCalcState( } return ON_TERMINAL; } else { + mTerminalInputIndex = mInputIndex - 1; + mTerminalOutputIndex = mOutputIndex - 1; return NOT_ON_TERMINAL; } } diff --git a/native/src/correction.h b/native/src/correction.h index 4012e7e82..a0fd55fd9 100644 --- a/native/src/correction.h +++ b/native/src/correction.h @@ -48,7 +48,6 @@ class Correction { void checkState(); bool initProcessState(const int index); - int getOutputIndex(); int getInputIndex(); virtual ~Correction(); @@ -115,11 +114,11 @@ class Correction { private: inline void incrementInputIndex(); inline void incrementOutputIndex(); - inline bool needsToTraverseAllNodes(); inline void startToTraverseAllNodes(); inline bool isQuote(const unsigned short c); inline CorrectionType processSkipChar( const int32_t c, const bool isTerminal, const bool inputIndexIncremented); + inline CorrectionType processUnrelatedCorrectionType(); inline void addCharToCurrentWord(const int32_t c); const int TYPED_LETTER_MULTIPLIER; diff --git a/native/src/defines.h b/native/src/defines.h index 01ef65678..ce3f85acd 100644 --- a/native/src/defines.h +++ b/native/src/defines.h @@ -22,9 +22,23 @@ #include <cutils/log.h> #define AKLOGE ALOGE #define AKLOGI ALOGI + +#define DUMP_WORD(word, length) do { dumpWord(word, length); } while(0) + +static char charBuf[50]; + +static void dumpWord(const unsigned short* word, const int length) { + for (int i = 0; i < length; ++i) { + charBuf[i] = word[i]; + } + charBuf[length] = 0; + AKLOGI("[ %s ]", charBuf); +} + #else #define AKLOGE(fmt, ...) #define AKLOGI(fmt, ...) +#define DUMP_WORD(word, length) #endif #ifdef FLAG_DO_PROFILE @@ -106,18 +120,6 @@ static void prof_out(void) { #define DEBUG_CORRECTION_FREQ true #define DEBUG_WORDS_PRIORITY_QUEUE true -#define DUMP_WORD(word, length) do { dumpWord(word, length); } while(0) - -static char charBuf[50]; - -static void dumpWord(const unsigned short* word, const int length) { - for (int i = 0; i < length; ++i) { - charBuf[i] = word[i]; - } - charBuf[length] = 0; - AKLOGI("[ %s ]", charBuf); -} - #else // FLAG_DBG #define DEBUG_DICT false @@ -131,7 +133,6 @@ static void dumpWord(const unsigned short* word, const int length) { #define DEBUG_CORRECTION_FREQ false #define DEBUG_WORDS_PRIORITY_QUEUE false -#define DUMP_WORD(word, length) #endif // FLAG_DBG @@ -171,6 +172,7 @@ static void dumpWord(const unsigned short* word, const int length) { #define EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO -2 #define PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO -3 #define NOT_A_INDEX -1 +#define NOT_A_FREQUENCY -1 #define KEYCODE_SPACE ' ' @@ -207,7 +209,8 @@ static void dumpWord(const unsigned short* word, const int length) { // Word limit for sub queues used in WordsPriorityQueuePool. Sub queues are temporary queues used // for better performance. -#define SUB_QUEUE_MAX_WORDS 5 +// Holds up to 1 candidate for each word +#define SUB_QUEUE_MAX_WORDS 1 #define SUB_QUEUE_MAX_COUNT 10 #define MAX_DEPTH_MULTIPLIER 3 diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp index ca7f0be0c..69e3200fc 100644 --- a/native/src/unigram_dictionary.cpp +++ b/native/src/unigram_dictionary.cpp @@ -186,7 +186,7 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, PROF_OPEN; PROF_START(0); - // Note: This line is intentionally left blank + queuePool->clearAll(); PROF_END(0); PROF_START(1); @@ -241,18 +241,17 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, } } PROF_END(6); + if (DEBUG_WORDS_PRIORITY_QUEUE) { + queuePool->dumpSubQueue1TopSuggestions(); + } } void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int *xCoordinates, - const int *yCoordinates, const int *codes, const int inputLength, - WordsPriorityQueue *queue, Correction *correction) { + const int *yCoordinates, const int *codes, const int inputLength, Correction *correction) { if (DEBUG_DICT) { AKLOGI("initSuggest"); } proximityInfo->setInputParams(codes, inputLength, xCoordinates, yCoordinates); - if (queue) { - queue->clear(); - } const int maxDepth = min(inputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH); correction->initCorrection(proximityInfo, inputLength, maxDepth); } @@ -264,15 +263,13 @@ void UnigramDictionary::getOneWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const bool useFullEditDistance, const int inputLength, Correction *correction, WordsPriorityQueuePool *queuePool) { - WordsPriorityQueue *masterQueue = queuePool->getMasterQueue(); - initSuggestions( - proximityInfo, xcoordinates, ycoordinates, codes, inputLength, masterQueue, correction); - getSuggestionCandidates(useFullEditDistance, inputLength, correction, masterQueue, + initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, correction); + getSuggestionCandidates(useFullEditDistance, inputLength, correction, queuePool, true /* doAutoCompletion */, DEFAULT_MAX_ERRORS); } void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance, - const int inputLength, Correction *correction, WordsPriorityQueue *queue, + const int inputLength, Correction *correction, WordsPriorityQueuePool *queuePool, const bool doAutoCompletion, const int maxErrors) { // TODO: Remove setCorrectionParams correction->setCorrectionParams(0, 0, 0, @@ -292,7 +289,7 @@ void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance, int firstChildPos; const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos, - correction, &childCount, &firstChildPos, &siblingPos, queue); + correction, &childCount, &firstChildPos, &siblingPos, queuePool); // Update next sibling pos correction->setTreeSiblingPos(outputIndex, siblingPos); @@ -327,14 +324,34 @@ void UnigramDictionary::getMistypedSpaceWords(ProximityInfo *proximityInfo, cons inline void UnigramDictionary::onTerminal(const int freq, const TerminalAttributes& terminalAttributes, Correction *correction, - WordsPriorityQueue *queue) { + WordsPriorityQueuePool *queuePool, const bool addToMasterQueue) { + const int inputIndex = correction->getInputIndex(); + const bool addToSubQueue = inputIndex < SUB_QUEUE_MAX_COUNT; + if (!addToMasterQueue && !addToSubQueue) { + return; + } + WordsPriorityQueue *masterQueue = queuePool->getMasterQueue(); + WordsPriorityQueue *subQueue = queuePool->getSubQueue1(inputIndex); int wordLength; unsigned short* wordPointer; const int finalFreq = correction->getFinalFreq(freq, &wordPointer, &wordLength); - if (finalFreq >= 0) { + if (finalFreq != NOT_A_FREQUENCY) { if (!terminalAttributes.isShortcutOnly()) { - addWord(wordPointer, wordLength, finalFreq, queue); + if (addToMasterQueue) { + addWord(wordPointer, wordLength, finalFreq, masterQueue); + } + // TODO: Check the validity of "inputIndex == wordLength" + //if (addToSubQueue && inputIndex == wordLength) { + if (addToSubQueue) { + addWord(wordPointer, wordLength, finalFreq, subQueue); + } } + // Please note that the shortcut candidates will be added to the master queue only. + if (!addToMasterQueue) { + return; + } + + // From here, below is the code to add shortcut candidates. TerminalAttributes::ShortcutIterator iterator = terminalAttributes.getShortcutIterator(); while (iterator.hasNextShortcutTarget()) { // TODO: addWord only supports weak ordering, meaning we have no means to control the @@ -345,7 +362,7 @@ inline void UnigramDictionary::onTerminal(const int freq, uint16_t shortcutTarget[MAX_WORD_LENGTH_INTERNAL]; const int shortcutTargetStringLength = iterator.getNextShortcutTarget( MAX_WORD_LENGTH_INTERNAL, shortcutTarget); - addWord(shortcutTarget, shortcutTargetStringLength, finalFreq, queue); + addWord(shortcutTarget, shortcutTargetStringLength, finalFreq, masterQueue); } } } @@ -411,8 +428,7 @@ void UnigramDictionary::getSplitTwoWordsSuggestions(ProximityInfo *proximityInfo } // TODO: Remove initSuggestions and correction->setCorrectionParams - initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, - 0 /* do not clear queue */, correction); + initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, correction); correction->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */, -1 /* transposedPos */, spaceProximityPos, missingSpacePos, @@ -584,7 +600,7 @@ int UnigramDictionary::getBigramPosition(int pos, unsigned short *word, int offs // given level, as output into newCount when traversing this level's parent. inline bool UnigramDictionary::processCurrentNode(const int initialPos, Correction *correction, int *newCount, - int *newChildrenPosition, int *nextSiblingPosition, WordsPriorityQueue *queue) { + int *newChildrenPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool) { if (DEBUG_DICT) { correction->checkState(); } @@ -659,15 +675,13 @@ inline bool UnigramDictionary::processCurrentNode(const int initialPos, } while (NOT_A_CHARACTER != c); if (isTerminalNode) { - if (needsToInvokeOnTerminal) { - // The frequency should be here, because we come here only if this is actually - // a terminal node, and we are on its last char. - const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos); - const int childrenAddressPos = BinaryFormat::skipFrequency(flags, pos); - const int attributesPos = BinaryFormat::skipChildrenPosition(flags, childrenAddressPos); - TerminalAttributes terminalAttributes(DICT_ROOT, flags, attributesPos); - onTerminal(freq, terminalAttributes, correction, queue); - } + // The frequency should be here, because we come here only if this is actually + // a terminal node, and we are on its last char. + const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos); + const int childrenAddressPos = BinaryFormat::skipFrequency(flags, pos); + const int attributesPos = BinaryFormat::skipChildrenPosition(flags, childrenAddressPos); + TerminalAttributes terminalAttributes(DICT_ROOT, flags, attributesPos); + onTerminal(freq, terminalAttributes, correction, queuePool, needsToInvokeOnTerminal); // If there are more chars in this node, then this virtual node has children. // If we are on the last char, this virtual node has children if this node has. diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h index 23581425a..5e7a7580f 100644 --- a/native/src/unigram_dictionary.h +++ b/native/src/unigram_dictionary.h @@ -93,14 +93,13 @@ class UnigramDictionary { const int codesRemain, const int currentDepth, int* codesDest, Correction *correction, WordsPriorityQueuePool* queuePool); void initSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, - const int *ycoordinates, const int *codes, const int codesSize, - WordsPriorityQueue *queue, Correction *correction); + const int *ycoordinates, const int *codes, const int codesSize, Correction *correction); void getOneWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const bool useFullEditDistance, const int inputLength, Correction *correction, WordsPriorityQueuePool* queuePool); void getSuggestionCandidates( const bool useFullEditDistance, const int inputLength, Correction *correction, - WordsPriorityQueue* queue, const bool doAutoCompletion, const int maxErrors); + WordsPriorityQueuePool* queuePool, const bool doAutoCompletion, const int maxErrors); void getSplitTwoWordsSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const bool useFullEditDistance, const int inputLength, const int spaceProximityPos, @@ -114,12 +113,12 @@ class UnigramDictionary { const int inputLength, const int spaceProximityPos, Correction *correction, WordsPriorityQueuePool* queuePool); void onTerminal(const int freq, const TerminalAttributes& terminalAttributes, - Correction *correction, WordsPriorityQueue *queue); + Correction *correction, WordsPriorityQueuePool *queuePool, const bool addToMasterQueue); bool needsToSkipCurrentNode(const unsigned short c, const int inputIndex, const int skipPos, const int depth); // Process a node by considering proximity, missing and excessive character bool processCurrentNode(const int initialPos, Correction *correction, int *newCount, - int *newChildPosition, int *nextSiblingPosition, WordsPriorityQueue *queue); + int *newChildPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool); int getMostFrequentWordLike(const int startInputIndex, const int inputLength, ProximityInfo *proximityInfo, unsigned short *word); int getMostFrequentWordLikeInner(const uint16_t* const inWord, const int length, diff --git a/native/src/words_priority_queue.h b/native/src/words_priority_queue.h index ce5d2ce51..54bf27a59 100644 --- a/native/src/words_priority_queue.h +++ b/native/src/words_priority_queue.h @@ -128,6 +128,13 @@ class WordsPriorityQueue { } } + void dumpTopWord() { + if (size() <= 0) { + return; + } + DUMP_WORD(mSuggestions.top()->mWord, mSuggestions.top()->mWordLength); + } + private: struct wordComparator { bool operator ()(SuggestedWord * left, SuggestedWord * right) { diff --git a/native/src/words_priority_queue_pool.h b/native/src/words_priority_queue_pool.h index bf9619e19..5fa254852 100644 --- a/native/src/words_priority_queue_pool.h +++ b/native/src/words_priority_queue_pool.h @@ -58,6 +58,21 @@ class WordsPriorityQueuePool { return mSubQueues2[id]; } + inline void clearAll() { + mMasterQueue->clear(); + for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) { + mSubQueues1[i]->clear(); + mSubQueues2[i]->clear(); + } + } + + void dumpSubQueue1TopSuggestions() { + AKLOGI("DUMP SUBQUEUE1 TOP SUGGESTIONS"); + for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) { + mSubQueues1[i]->dumpTopWord(); + } + } + private: WordsPriorityQueue* mMasterQueue; WordsPriorityQueue* mSubQueues1[SUB_QUEUE_MAX_COUNT]; diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTests.java index d9e8199b6..89c089444 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTests.java @@ -74,61 +74,238 @@ public class KeyboardStateTests extends AndroidTestCase { // Shift key in alphabet mode. public void testShift() { - // Press/release shift key. + // Press/release shift key, enter into shift state. mSwitcher.onPressShift(NOT_SLIDING); assertAlphabetManualShifted(); mSwitcher.onReleaseShift(NOT_SLIDING); assertAlphabetManualShifted(); + // Press/release shift key, back to normal state. + mSwitcher.onPressShift(NOT_SLIDING); + assertAlphabetManualShifted(); + mSwitcher.onReleaseShift(NOT_SLIDING); + assertAlphabetNormal(); - // Press/release shift key. + // Press/release shift key, enter into shift state. mSwitcher.onPressShift(NOT_SLIDING); assertAlphabetManualShifted(); mSwitcher.onReleaseShift(NOT_SLIDING); + assertAlphabetManualShifted(); + // Press/release letter key, snap back to normal state. + mSwitcher.onOtherKeyPressed(); + mSwitcher.onCodeInput('Z', SINGLE); assertAlphabetNormal(); + } + + // Shift key chording input. + public void testShiftChording() { + // Press shift key and hold, enter into choring shift state. + mSwitcher.onPressShift(NOT_SLIDING); + assertAlphabetManualShifted(); + + // Press/release letter keys. + mSwitcher.onOtherKeyPressed(); + mSwitcher.onCodeInput('Z', MULTI); + assertAlphabetManualShifted(); + mSwitcher.onOtherKeyPressed(); + mSwitcher.onCodeInput('X', MULTI); + assertAlphabetManualShifted(); - // TODO: Sliding test + // Release shift key, snap back to normal state. + mSwitcher.onCodeInput(Keyboard.CODE_SHIFT, SINGLE); + mSwitcher.onReleaseShift(NOT_SLIDING); + mSwitcher.updateShiftState(); + assertAlphabetNormal(); } - // Switching between alphabet and symbols. - public void testAlphabetAndSymbols() { + // Shift key sliding input. + public void testShiftSliding() { + // Press shift key. + mSwitcher.onPressShift(NOT_SLIDING); + assertAlphabetManualShifted(); + // Slide out shift key. + mSwitcher.onReleaseShift(SLIDING); + assertAlphabetManualShifted(); + + // Enter into letter key. + mSwitcher.onOtherKeyPressed(); + assertAlphabetManualShifted(); + // Release letter key, snap back to alphabet. + mSwitcher.onCodeInput('Z', SINGLE); + assertAlphabetNormal(); + } + + private void enterSymbolsMode() { // Press/release "?123" key. mSwitcher.onPressSymbol(); assertSymbolsNormal(); + mSwitcher.onCodeInput(Keyboard.CODE_SWITCH_ALPHA_SYMBOL, SINGLE); mSwitcher.onReleaseSymbol(); assertSymbolsNormal(); + } + private void leaveSymbolsMode() { // Press/release "ABC" key. mSwitcher.onPressSymbol(); assertAlphabetNormal(); + mSwitcher.onCodeInput(Keyboard.CODE_SWITCH_ALPHA_SYMBOL, SINGLE); + assertAlphabetNormal(); + } + + // Switching between alphabet and symbols. + public void testAlphabetAndSymbols() { + enterSymbolsMode(); + leaveSymbolsMode(); + } + + // Switching between alphabet shift locked and symbols. + public void testAlphabetShiftLockedAndSymbols() { + enterShiftLockWithLongPressShift(); + enterSymbolsMode(); + + // Press/release "ABC" key, switch back to shift locked mode. + mSwitcher.onPressSymbol(); + assertAlphabetShiftLocked(); + mSwitcher.onCodeInput(Keyboard.CODE_SWITCH_ALPHA_SYMBOL, SINGLE); + mSwitcher.onReleaseSymbol(); + assertAlphabetShiftLocked(); + } + + // Symbols key chording input. + public void testSymbolsChording() { + // Press symbols key and hold, enter into choring shift state. + mSwitcher.onPressSymbol(); + assertSymbolsNormal(); + + // Press/release symbol letter keys. + mSwitcher.onOtherKeyPressed(); + mSwitcher.onCodeInput('1', MULTI); + assertSymbolsNormal(); + mSwitcher.onOtherKeyPressed(); + mSwitcher.onCodeInput('2', MULTI); + assertSymbolsNormal(); + + // Release shift key, snap back to normal state. + mSwitcher.onCodeInput(Keyboard.CODE_SWITCH_ALPHA_SYMBOL, SINGLE); mSwitcher.onReleaseSymbol(); + mSwitcher.updateShiftState(); assertAlphabetNormal(); + } + + // Symbols key sliding input. + public void testSymbolsSliding() { + // Press "123?" key. + mSwitcher.onPressSymbol(); + assertSymbolsNormal(); + // Slide out from "123?" key. + mSwitcher.onReleaseSymbol(); + assertSymbolsNormal(); - // TODO: Sliding test - // TODO: Snap back test + // Enter into letter key. + mSwitcher.onOtherKeyPressed(); + assertSymbolsNormal(); + // Release letter key, snap back to alphabet. + mSwitcher.onCodeInput('z', SINGLE); + assertAlphabetNormal(); } // Switching between symbols and symbols shifted. public void testSymbolsAndSymbolsShifted() { + enterSymbolsMode(); + + // Press/release "=\<" key. + mSwitcher.onPressShift(NOT_SLIDING); + assertSymbolsShifted(); + mSwitcher.onReleaseShift(NOT_SLIDING); + assertSymbolsShifted(); + // Press/release "?123" key. - mSwitcher.onPressSymbol(); + mSwitcher.onPressShift(NOT_SLIDING); assertSymbolsNormal(); - mSwitcher.onReleaseSymbol(); + mSwitcher.onReleaseShift(NOT_SLIDING); assertSymbolsNormal(); + leaveSymbolsMode(); + } + + // Symbols shift sliding input + public void testSymbolsShiftSliding() { + enterSymbolsMode(); + + // Press "=\<" key. + mSwitcher.onPressShift(NOT_SLIDING); + assertSymbolsShifted(); + // Slide out "=\<" key. + mSwitcher.onReleaseShift(SLIDING); + assertSymbolsShifted(); + + // Enter into symbol shifted letter key. + mSwitcher.onOtherKeyPressed(); + assertSymbolsShifted(); + // Release symbol shifted letter key, snap back to symbols. + mSwitcher.onCodeInput('~', SINGLE); + assertSymbolsNormal(); + } + + // Symbols shift sliding input from symbols shifted. + public void testSymbolsShiftSliding2() { + enterSymbolsMode(); + // Press/release "=\<" key. mSwitcher.onPressShift(NOT_SLIDING); assertSymbolsShifted(); mSwitcher.onReleaseShift(NOT_SLIDING); assertSymbolsShifted(); - // Press/release "ABC" key. - mSwitcher.onPressSymbol(); - assertAlphabetNormal(); - mSwitcher.onReleaseSymbol(); + // Press "123?" key. + mSwitcher.onPressShift(NOT_SLIDING); + assertSymbolsNormal(); + // Slide out "123?" key. + mSwitcher.onReleaseShift(SLIDING); + assertSymbolsNormal(); + + // Enter into symbol letter key. + mSwitcher.onOtherKeyPressed(); + assertSymbolsNormal(); + // Release symbol letter key, snap back to symbols shift. + mSwitcher.onCodeInput('1', SINGLE); + assertSymbolsShifted(); + } + + // Automatic snap back to alphabet from symbols by space key. + public void testSnapBackBySpace() { + enterSymbolsMode(); + + // Enter a symbol letter. + mSwitcher.onOtherKeyPressed(); + assertSymbolsNormal(); + mSwitcher.onCodeInput('1', SINGLE); + assertSymbolsNormal(); + // Enter space, snap back to alphabet. + mSwitcher.onOtherKeyPressed(); + assertSymbolsNormal(); + mSwitcher.onCodeInput(Keyboard.CODE_SPACE, SINGLE); assertAlphabetNormal(); + } + + // Automatic snap back to alphabet from symbols by registered letters. + public void testSnapBack() { + final String snapBackChars = "'"; + final int snapBackCode = snapBackChars.codePointAt(0); + final boolean hasDistinctMultitouch = true; + mSwitcher.loadKeyboard(snapBackChars, hasDistinctMultitouch); + + enterSymbolsMode(); - // TODO: Sliding test - // TODO: Snap back test + // Enter a symbol letter. + mSwitcher.onOtherKeyPressed(); + assertSymbolsNormal(); + mSwitcher.onCodeInput('1', SINGLE); + assertSymbolsNormal(); + // Enter snap back letter, snap back to alphabet. + mSwitcher.onOtherKeyPressed(); + assertSymbolsNormal(); + mSwitcher.onCodeInput(snapBackCode, SINGLE); + assertAlphabetNormal(); } // Automatic upper case test @@ -144,21 +321,92 @@ public class KeyboardStateTests extends AndroidTestCase { // Release shift key. mSwitcher.onReleaseShift(NOT_SLIDING); assertAlphabetNormal(); + } + + // Chording shift key in automatic upper case. + public void testAutomaticUpperCaseChording() { + mSwitcher.setAutoCapsMode(AUTO_CAPS); + // Update shift state with auto caps enabled. + mSwitcher.updateShiftState(); + assertAlphabetAutomaticShifted(); - // TODO: Chording test. + // Press shift key. + mSwitcher.onPressShift(NOT_SLIDING); + assertAlphabetManualShifted(); + // Press/release letter keys. + mSwitcher.onOtherKeyPressed(); + mSwitcher.onCodeInput('Z', MULTI); + assertAlphabetManualShifted(); + // Release shift key, snap back to alphabet. + mSwitcher.onCodeInput(Keyboard.CODE_SHIFT, SINGLE); + mSwitcher.onReleaseShift(NOT_SLIDING); + assertAlphabetNormal(); } - // TODO: UpdateShiftState with shift locked, etc. + // Chording symbol key in automatic upper case. + public void testAutomaticUpperCaseChrding2() { + mSwitcher.setAutoCapsMode(AUTO_CAPS); + // Update shift state with auto caps enabled. + mSwitcher.updateShiftState(); + assertAlphabetAutomaticShifted(); - // TODO: Multitouch test + // Press "123?" key. + mSwitcher.onPressSymbol(); + assertSymbolsNormal(); + // Press/release symbol letter keys. + mSwitcher.onOtherKeyPressed(); + assertSymbolsNormal(); + mSwitcher.onCodeInput('1', MULTI); + assertSymbolsNormal(); + // Release "123?" key, snap back to alphabet. + mSwitcher.onCodeInput(Keyboard.CODE_SWITCH_ALPHA_SYMBOL, SINGLE); + mSwitcher.onReleaseSymbol(); + assertAlphabetNormal(); + } - // TODO: Change focus test. + // Sliding from shift key in automatic upper case. + public void testAutomaticUpperCaseSliding() { + mSwitcher.setAutoCapsMode(AUTO_CAPS); + // Update shift state with auto caps enabled. + mSwitcher.updateShiftState(); + assertAlphabetAutomaticShifted(); - // TODO: Change orientation test. + // Press shift key. + mSwitcher.onPressShift(NOT_SLIDING); + assertAlphabetManualShifted(); + // Slide out shift key. + mSwitcher.onReleaseShift(SLIDING); + assertAlphabetManualShifted(); + // Enter into letter key. + mSwitcher.onOtherKeyPressed(); + assertAlphabetManualShifted(); + // Release letter key, snap back to alphabet. + mSwitcher.onCodeInput('Z', SINGLE); + assertAlphabetNormal(); + } - // Long press shift key. - // TODO: Move long press recognizing timer/logic into KeyboardState. - public void testLongPressShift() { + // Sliding from symbol key in automatic upper case. + public void testAutomaticUpperCaseSliding2() { + mSwitcher.setAutoCapsMode(AUTO_CAPS); + // Update shift state with auto caps enabled. + mSwitcher.updateShiftState(); + assertAlphabetAutomaticShifted(); + + // Press "123?" key. + mSwitcher.onPressSymbol(); + assertSymbolsNormal(); + // Slide out "123?" key. + mSwitcher.onReleaseSymbol(); + assertSymbolsNormal(); + // Enter into symbol letter keys. + mSwitcher.onOtherKeyPressed(); + assertSymbolsNormal(); + // Release symbol letter key, snap back to alphabet. + mSwitcher.onCodeInput('1', SINGLE); + assertAlphabetNormal(); + } + + private void enterShiftLockWithLongPressShift() { // Long press shift key mSwitcher.onPressShift(NOT_SLIDING); assertAlphabetManualShifted(); @@ -169,8 +417,9 @@ public class KeyboardStateTests extends AndroidTestCase { assertAlphabetShiftLocked(); mSwitcher.onReleaseShift(NOT_SLIDING); assertAlphabetShiftLocked(); + } - // Long press shift key. + private void leaveShiftLockWithLongPressShift() { mSwitcher.onPressShift(NOT_SLIDING); assertAlphabetManualShifted(); // Long press recognized in LatinKeyboardView.KeyTimerHandler. @@ -182,6 +431,27 @@ public class KeyboardStateTests extends AndroidTestCase { assertAlphabetNormal(); } + // Long press shift key. + // TODO: Move long press recognizing timer/logic into KeyboardState. + public void testLongPressShift() { + enterShiftLockWithLongPressShift(); + leaveShiftLockWithLongPressShift(); + } + + // Leave shift lock with single tap shift key. + public void testShiftInShiftLock() { + enterShiftLockWithLongPressShift(); + assertAlphabetShiftLocked(); + + // Tap shift key. + mSwitcher.onPressShift(NOT_SLIDING); + assertAlphabetManualShifted(); + mSwitcher.onCodeInput(Keyboard.CODE_SHIFT, SINGLE); + assertAlphabetManualShifted(); + mSwitcher.onReleaseShift(NOT_SLIDING); + assertAlphabetNormal(); + } + // Double tap shift key. // TODO: Move double tap recognizing timer/logic into KeyboardState. public void testDoubleTapShift() { @@ -209,4 +479,28 @@ public class KeyboardStateTests extends AndroidTestCase { // Second shift key tap. // Second tap is ignored in LatinKeyboardView.KeyTimerHandler. } + + // Update shift state. + public void testUpdateShiftState() { + mSwitcher.setAutoCapsMode(AUTO_CAPS); + // Update shift state. + mSwitcher.updateShiftState(); + assertAlphabetAutomaticShifted(); + } + + // Update shift state when shift locked. + public void testUpdateShiftStateInShiftLocked() { + mSwitcher.setAutoCapsMode(AUTO_CAPS); + enterShiftLockWithLongPressShift(); + assertAlphabetShiftLocked(); + // Update shift state when shift locked + mSwitcher.updateShiftState(); + assertAlphabetShiftLocked(); + } + + // TODO: Multitouch test + + // TODO: Change focus test. + + // TODO: Change orientation test. } diff --git a/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java b/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java index fcbb645f5..7aadc677b 100644 --- a/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java +++ b/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java @@ -144,7 +144,6 @@ public class BinaryDictInputOutput { private static final int GROUP_CHARACTERS_TERMINATOR = 0x1F; - private static final int GROUP_COUNT_SIZE = 1; private static final int GROUP_TERMINATOR_SIZE = 1; private static final int GROUP_FLAGS_SIZE = 1; private static final int GROUP_FREQUENCY_SIZE = 1; @@ -155,9 +154,8 @@ public class BinaryDictInputOutput { private static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE; private static final int INVALID_CHARACTER = -1; - // Limiting to 127 for upward compatibility - // TODO: implement a scheme to be able to shoot 256 chargroups in a node - private static final int MAX_CHARGROUPS_IN_A_NODE = 127; + private static final int MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT = 0x7F; // 127 + private static final int MAX_CHARGROUPS_IN_A_NODE = 0x7FFF; // 32767 private static final int MAX_TERMINAL_FREQUENCY = 255; @@ -267,6 +265,31 @@ public class BinaryDictInputOutput { } /** + * Compute the binary size of the group count + * @param count the group count + * @return the size of the group count, either 1 or 2 bytes. + */ + private static int getGroupCountSize(final int count) { + if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= count) { + return 1; + } else if (MAX_CHARGROUPS_IN_A_NODE >= count) { + return 2; + } else { + throw new RuntimeException("Can't have more than " + MAX_CHARGROUPS_IN_A_NODE + + " groups in a node (found " + count +")"); + } + } + + /** + * Compute the binary size of the group count for a node + * @param node the node + * @return the size of the group count, either 1 or 2 bytes. + */ + private static int getGroupCountSize(final Node node) { + return getGroupCountSize(node.mData.size()); + } + + /** * Compute the maximum size of a CharGroup, assuming 3-byte addresses for everything. * * @param group the CharGroup to compute the size of. @@ -295,7 +318,7 @@ public class BinaryDictInputOutput { * @param node the node to compute the maximum size of. */ private static void setNodeMaximumSize(Node node) { - int size = GROUP_COUNT_SIZE; + int size = getGroupCountSize(node); for (CharGroup g : node.mData) { final int groupSize = getCharGroupMaximumSize(g); g.mCachedSize = groupSize; @@ -394,7 +417,7 @@ public class BinaryDictInputOutput { * @param dict the dictionary in which the word/attributes are to be found. */ private static void computeActualNodeSize(Node node, FusionDictionary dict) { - int size = GROUP_COUNT_SIZE; + int size = getGroupCountSize(node); for (CharGroup group : node.mData) { int groupSize = GROUP_FLAGS_SIZE + getGroupCharactersSize(group); if (group.isTerminal()) groupSize += GROUP_FREQUENCY_SIZE; @@ -437,12 +460,13 @@ public class BinaryDictInputOutput { int nodeOffset = 0; for (Node n : flatNodes) { n.mCachedAddress = nodeOffset; + int groupCountSize = getGroupCountSize(n); int groupOffset = 0; for (CharGroup g : n.mData) { - g.mCachedAddress = GROUP_COUNT_SIZE + nodeOffset + groupOffset; + g.mCachedAddress = groupCountSize + nodeOffset + groupOffset; groupOffset += g.mCachedSize; } - if (groupOffset + GROUP_COUNT_SIZE != n.mCachedSize) { + if (groupOffset + groupCountSize != n.mCachedSize) { throw new RuntimeException("Bug : Stored and computed node size differ"); } nodeOffset += n.mCachedSize; @@ -582,7 +606,9 @@ public class BinaryDictInputOutput { } flags |= FLAG_HAS_BIGRAMS; } - // TODO: fill in the FLAG_IS_SHORTCUT_ONLY + if (group.mIsShortcutOnly) { + flags |= FLAG_IS_SHORTCUT_ONLY; + } return flags; } @@ -629,13 +655,20 @@ public class BinaryDictInputOutput { private static int writePlacedNode(FusionDictionary dict, byte[] buffer, Node node) { int index = node.mCachedAddress; - final int size = node.mData.size(); - if (size > MAX_CHARGROUPS_IN_A_NODE) - throw new RuntimeException("A node has a group count over 127 (" + size + ")."); - - buffer[index++] = (byte)size; + final int groupCount = node.mData.size(); + final int countSize = getGroupCountSize(node); + if (1 == countSize) { + buffer[index++] = (byte)groupCount; + } else if (2 == countSize) { + // We need to signal 2-byte size by setting the top bit of the MSB to 1, so + // we | 0x80 to do this. + buffer[index++] = (byte)((groupCount >> 8) | 0x80); + buffer[index++] = (byte)(groupCount & 0xFF); + } else { + throw new RuntimeException("Strange size from getGroupCountSize : " + countSize); + } int groupAddress = index; - for (int i = 0; i < size; ++i) { + for (int i = 0; i < groupCount; ++i) { CharGroup group = node.mData.get(i); if (index != group.mCachedAddress) throw new RuntimeException("Bug: write index is not " + "the same as the cached address of the group"); @@ -891,7 +924,7 @@ public class BinaryDictInputOutput { addressPointer += 3; break; default: - throw new RuntimeException("Has attribute with no address"); + throw new RuntimeException("Has shortcut targets with no address"); } shortcutTargets.add(new PendingAttribute(targetFlags & FLAG_ATTRIBUTE_FREQUENCY, targetAddress)); @@ -922,7 +955,7 @@ public class BinaryDictInputOutput { addressPointer += 3; break; default: - throw new RuntimeException("Has attribute with no address"); + throw new RuntimeException("Has bigrams with no address"); } bigrams.add(new PendingAttribute(bigramFlags & FLAG_ATTRIBUTE_FREQUENCY, bigramAddress)); @@ -934,6 +967,19 @@ public class BinaryDictInputOutput { } /** + * Reads and returns the char group count out of a file and forwards the pointer. + */ + private static int readCharGroupCount(RandomAccessFile source) throws IOException { + final int msb = source.readUnsignedByte(); + if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) { + return msb; + } else { + return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8) + + source.readUnsignedByte(); + } + } + + /** * Finds, as a string, the word at the address passed as an argument. * * @param source the file to read from. @@ -946,8 +992,8 @@ public class BinaryDictInputOutput { int address) throws IOException { final long originalPointer = source.getFilePointer(); source.seek(headerSize); - final int count = source.readUnsignedByte(); - int groupOffset = 1; // 1 for the group count + final int count = readCharGroupCount(source); + int groupOffset = getGroupCountSize(count); final StringBuilder builder = new StringBuilder(); String result = null; @@ -1003,9 +1049,9 @@ public class BinaryDictInputOutput { Map<Integer, Node> reverseNodeMap, Map<Integer, CharGroup> reverseGroupMap) throws IOException { final int nodeOrigin = (int)(source.getFilePointer() - headerSize); - final int count = source.readUnsignedByte(); + final int count = readCharGroupCount(source); final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>(); - int groupOffset = nodeOrigin + 1; // 1 byte for the group count + int groupOffset = nodeOrigin + getGroupCountSize(count); for (int i = count; i > 0; --i) { CharGroupInfo info = readCharGroup(source, groupOffset); ArrayList<WeightedString> shortcutTargets = null; diff --git a/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java b/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java index 3ab206d80..918b1ca4b 100644 --- a/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java +++ b/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java @@ -171,6 +171,24 @@ public class FusionDictionary implements Iterable<Word> { } /** + * Helper method to add all words in a list as 0-frequency entries + * + * These words are added when shortcuts targets or bigrams are not found in the dictionary + * yet. The same words may be added later with an actual frequency - this is handled by + * the private version of add(). + */ + private void addNeutralWords(final ArrayList<WeightedString> words) { + if (null != words) { + for (WeightedString word : words) { + final CharGroup t = findWordInTree(mRoot, word.mWord); + if (null == t) { + add(getCodePoints(word.mWord), 0, null, null, false /* isShortcutOnly */); + } + } + } + } + + /** * Helper method to add a word as a string. * * This method adds a word to the dictionary with the given frequency. Optional @@ -186,22 +204,12 @@ public class FusionDictionary implements Iterable<Word> { final ArrayList<WeightedString> shortcutTargets, final ArrayList<WeightedString> bigrams) { if (null != shortcutTargets) { - for (WeightedString target : shortcutTargets) { - final CharGroup t = findWordInTree(mRoot, target.mWord); - if (null == t) { - add(getCodePoints(target.mWord), 0, null, null); - } - } + addNeutralWords(shortcutTargets); } if (null != bigrams) { - for (WeightedString bigram : bigrams) { - final CharGroup t = findWordInTree(mRoot, bigram.mWord); - if (null == t) { - add(getCodePoints(bigram.mWord), 0, null, null); - } - } + addNeutralWords(bigrams); } - add(getCodePoints(word), frequency, shortcutTargets, bigrams); + add(getCodePoints(word), frequency, shortcutTargets, bigrams, false /* isShortcutOnly */); } /** @@ -223,6 +231,22 @@ public class FusionDictionary implements Iterable<Word> { } /** + * Helper method to add a shortcut that should not be a dictionary word. + * + * @param word the word to add. + * @param frequency the frequency of the word, in the range [0..255]. + * @param shortcutTargets a list of shortcut targets. May not be null. + */ + public void addShortcutOnly(final String word, final int frequency, + final ArrayList<WeightedString> shortcutTargets) { + if (null == shortcutTargets) { + throw new RuntimeException("Can't add a shortcut without targets"); + } + addNeutralWords(shortcutTargets); + add(getCodePoints(word), frequency, shortcutTargets, null, true /* isShortcutOnly */); + } + + /** * Add a word to this dictionary. * * The shortcuts and bigrams, if any, have to be in the dictionary already. If they aren't, @@ -232,10 +256,12 @@ public class FusionDictionary implements Iterable<Word> { * @param frequency the frequency of the word, in the range [0..255]. * @param shortcutTargets an optional list of shortcut targets for this word (null if none). * @param bigrams an optional list of bigrams for this word (null if none). + * @param isShortcutOnly whether this should be a shortcut only. */ private void add(final int[] word, final int frequency, final ArrayList<WeightedString> shortcutTargets, - final ArrayList<WeightedString> bigrams) { + final ArrayList<WeightedString> bigrams, + final boolean isShortcutOnly) { assert(frequency >= 0 && frequency <= 255); Node currentNode = mRoot; int charIndex = 0; @@ -260,7 +286,7 @@ public class FusionDictionary implements Iterable<Word> { final int insertionIndex = findInsertionIndex(currentNode, word[charIndex]); final CharGroup newGroup = new CharGroup( Arrays.copyOfRange(word, charIndex, word.length), - shortcutTargets, bigrams, frequency, false /* isShortcutOnly */); + shortcutTargets, bigrams, frequency, isShortcutOnly); currentNode.mData.add(insertionIndex, newGroup); checkStack(currentNode); } else { @@ -275,7 +301,7 @@ public class FusionDictionary implements Iterable<Word> { } else { final CharGroup newNode = new CharGroup(currentGroup.mChars, shortcutTargets, bigrams, frequency, currentGroup.mChildren, - false /* isShortcutOnly */); + isShortcutOnly); currentNode.mData.set(nodeIndex, newNode); checkStack(currentNode); } @@ -284,8 +310,7 @@ public class FusionDictionary implements Iterable<Word> { // We only have to create a new node and add it to the end of this. final CharGroup newNode = new CharGroup( Arrays.copyOfRange(word, charIndex + differentCharIndex, word.length), - shortcutTargets, bigrams, frequency, - false /* isShortcutOnly */); + shortcutTargets, bigrams, frequency, isShortcutOnly); currentGroup.mChildren = new Node(); currentGroup.mChildren.mData.add(newNode); } @@ -300,7 +325,8 @@ public class FusionDictionary implements Iterable<Word> { } final CharGroup newGroup = new CharGroup(word, currentGroup.mShortcutTargets, currentGroup.mBigrams, - frequency, currentGroup.mChildren, false /* isShortcutOnly */); + frequency, currentGroup.mChildren, + currentGroup.mIsShortcutOnly && isShortcutOnly); currentNode.mData.set(nodeIndex, newGroup); } } else { @@ -318,16 +344,18 @@ public class FusionDictionary implements Iterable<Word> { if (charIndex + differentCharIndex >= word.length) { newParent = new CharGroup( Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex), - shortcutTargets, bigrams, frequency, newChildren, - false /* isShortcutOnly */); + shortcutTargets, bigrams, frequency, newChildren, isShortcutOnly); } else { + // isShortcutOnly makes no sense for non-terminal nodes. The following node + // is non-terminal (frequency 0 in FusionDictionary representation) so we + // pass false for isShortcutOnly newParent = new CharGroup( Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex), null, null, -1, newChildren, false /* isShortcutOnly */); final CharGroup newWord = new CharGroup( Arrays.copyOfRange(word, charIndex + differentCharIndex, word.length), shortcutTargets, bigrams, frequency, - false /* isShortcutOnly */); + isShortcutOnly); final int addIndex = word[charIndex + differentCharIndex] > currentGroup.mChars[differentCharIndex] ? 1 : 0; newChildren.mData.add(addIndex, newWord); @@ -619,7 +647,8 @@ public class FusionDictionary implements Iterable<Word> { } if (currentGroup.mFrequency >= 0) return new Word(mCurrentString.toString(), currentGroup.mFrequency, - currentGroup.mShortcutTargets, currentGroup.mBigrams); + currentGroup.mShortcutTargets, currentGroup.mBigrams, + currentGroup.mIsShortcutOnly); } else { mPositions.removeLast(); currentPos = mPositions.getLast(); diff --git a/tools/makedict/src/com/android/inputmethod/latin/Word.java b/tools/makedict/src/com/android/inputmethod/latin/Word.java index 561b21bb3..cf6116f91 100644 --- a/tools/makedict/src/com/android/inputmethod/latin/Word.java +++ b/tools/makedict/src/com/android/inputmethod/latin/Word.java @@ -28,16 +28,18 @@ import java.util.ArrayList; public class Word implements Comparable<Word> { final String mWord; final int mFrequency; + final boolean mIsShortcutOnly; final ArrayList<WeightedString> mShortcutTargets; final ArrayList<WeightedString> mBigrams; public Word(final String word, final int frequency, final ArrayList<WeightedString> shortcutTargets, - final ArrayList<WeightedString> bigrams) { + final ArrayList<WeightedString> bigrams, final boolean isShortcutOnly) { mWord = word; mFrequency = frequency; mShortcutTargets = shortcutTargets; mBigrams = bigrams; + mIsShortcutOnly = isShortcutOnly; } /** diff --git a/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java b/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java index d6c03ed70..77c536668 100644 --- a/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java +++ b/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java @@ -45,6 +45,9 @@ public class XmlDictInputOutput { private static final String SHORTCUT_TAG = "shortcut"; private static final String FREQUENCY_ATTR = "f"; private static final String WORD_ATTR = "word"; + private static final String SHORTCUT_ONLY_ATTR = "shortcutOnly"; + + private static final int SHORTCUT_ONLY_DEFAULT_FREQ = 1; /** * SAX handler for a unigram XML file. @@ -232,6 +235,15 @@ public class XmlDictInputOutput { new UnigramHandler(dict, shortcutHandler.getShortcutMap(), bigramHandler.getBigramMap()); parser.parse(unigrams, unigramHandler); + + final HashMap<String, ArrayList<WeightedString>> shortcutMap = + shortcutHandler.getShortcutMap(); + for (final String shortcut : shortcutMap.keySet()) { + if (dict.hasWord(shortcut)) continue; + // TODO: list a frequency in the shortcut file and use it here, instead of + // a constant freq + dict.addShortcutOnly(shortcut, SHORTCUT_ONLY_DEFAULT_FREQ, shortcutMap.get(shortcut)); + } return dict; } @@ -264,9 +276,11 @@ public class XmlDictInputOutput { } // TODO: use an XMLSerializer if this gets big destination.write("<wordlist format=\"2\">\n"); + destination.write("<!-- Warning: there is no code to read this format yet. -->\n"); for (Word word : set) { destination.write(" <" + WORD_TAG + " " + WORD_ATTR + "=\"" + word.mWord + "\" " - + FREQUENCY_ATTR + "=\"" + word.mFrequency + "\">"); + + FREQUENCY_ATTR + "=\"" + word.mFrequency + "\" " + SHORTCUT_ONLY_ATTR + + "=\"" + word.mIsShortcutOnly + "\">"); if (null != word.mShortcutTargets) { destination.write("\n"); for (WeightedString target : word.mShortcutTargets) { |