aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--native/src/correction.cpp36
-rw-r--r--native/src/correction.h3
-rw-r--r--native/src/defines.h31
-rw-r--r--native/src/unigram_dictionary.cpp70
-rw-r--r--native/src/unigram_dictionary.h9
-rw-r--r--native/src/words_priority_queue.h7
-rw-r--r--native/src/words_priority_queue_pool.h15
-rw-r--r--tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTests.java342
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java88
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java75
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/Word.java4
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java16
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) {