Merge "KeyboardState unit tests for non-distinct multitouch device"
diff --git a/native/src/correction.cpp b/native/src/correction.cpp
index c40f94b..75831b6 100644
--- a/native/src/correction.cpp
+++ b/native/src/correction.cpp
@@ -168,8 +168,8 @@
     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 @@
 }
 
 // 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 @@
             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::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 @@
         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 @@
                 AKLOGI("UNRELATED(0): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
                         mTransposedCount, mExcessiveCount, c);
             }
-            return UNRELATED;
+            return processUnrelatedCorrectionType();
         }
     }
 
@@ -484,7 +480,7 @@
                 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 @@
         }
         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 4012e7e..a0fd55f 100644
--- a/native/src/correction.h
+++ b/native/src/correction.h
@@ -48,7 +48,6 @@
     void checkState();
     bool initProcessState(const int index);
 
-    int getOutputIndex();
     int getInputIndex();
 
     virtual ~Correction();
@@ -115,11 +114,11 @@
  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 01ef656..ce3f85a 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 @@
 #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 @@
 #define DEBUG_CORRECTION_FREQ false
 #define DEBUG_WORDS_PRIORITY_QUEUE false
 
-#define DUMP_WORD(word, length)
 
 #endif // FLAG_DBG
 
@@ -171,6 +172,7 @@
 #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 @@
 
 // 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 ca7f0be..69e3200 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -186,7 +186,7 @@
 
     PROF_OPEN;
     PROF_START(0);
-    // Note: This line is intentionally left blank
+    queuePool->clearAll();
     PROF_END(0);
 
     PROF_START(1);
@@ -241,18 +241,17 @@
         }
     }
     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 @@
         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 @@
             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 @@
 
 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 @@
             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 @@
     }
 
     // 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 @@
 // 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 @@
     } 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 2358142..5e7a758 100644
--- a/native/src/unigram_dictionary.h
+++ b/native/src/unigram_dictionary.h
@@ -93,14 +93,13 @@
         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 @@
             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 ce5d2ce..54bf27a 100644
--- a/native/src/words_priority_queue.h
+++ b/native/src/words_priority_queue.h
@@ -128,6 +128,13 @@
         }
     }
 
+    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 bf9619e..5fa2548 100644
--- a/native/src/words_priority_queue_pool.h
+++ b/native/src/words_priority_queue_pool.h
@@ -58,6 +58,21 @@
         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 d9e8199..89c0894 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTests.java
@@ -74,46 +74,143 @@
 
     // 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.
+        // Press/release shift key, back to normal state.
         mSwitcher.onPressShift(NOT_SLIDING);
         assertAlphabetManualShifted();
         mSwitcher.onReleaseShift(NOT_SLIDING);
         assertAlphabetNormal();
 
-        // TODO: Sliding test
+        // 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();
+
+        // Release shift key, snap back to normal state.
+        mSwitcher.onCodeInput(Keyboard.CODE_SHIFT, SINGLE);
+        mSwitcher.onReleaseShift(NOT_SLIDING);
+        mSwitcher.updateShiftState();
+        assertAlphabetNormal();
+    }
+
+    // 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() {
-        // Press/release "?123" key.
+        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();
 
-        // Press/release "ABC" key.
-        mSwitcher.onPressSymbol();
+        // Enter into letter key.
+        mSwitcher.onOtherKeyPressed();
+        assertSymbolsNormal();
+        // Release letter key, snap back to alphabet.
+        mSwitcher.onCodeInput('z', SINGLE);
         assertAlphabetNormal();
-        mSwitcher.onReleaseSymbol();
-        assertAlphabetNormal();
-
-        // TODO: Sliding test
-        // TODO: Snap back test
     }
 
     // Switching between symbols and symbols shifted.
     public void testSymbolsAndSymbolsShifted() {
-        // Press/release "?123" key.
-        mSwitcher.onPressSymbol();
-        assertSymbolsNormal();
-        mSwitcher.onReleaseSymbol();
-        assertSymbolsNormal();
+        enterSymbolsMode();
 
         // Press/release "=\<" key.
         mSwitcher.onPressShift(NOT_SLIDING);
@@ -121,14 +218,94 @@
         mSwitcher.onReleaseShift(NOT_SLIDING);
         assertSymbolsShifted();
 
-        // Press/release "ABC" key.
-        mSwitcher.onPressSymbol();
-        assertAlphabetNormal();
-        mSwitcher.onReleaseSymbol();
-        assertAlphabetNormal();
+        // Press/release "?123" key.
+        mSwitcher.onPressShift(NOT_SLIDING);
+        assertSymbolsNormal();
+        mSwitcher.onReleaseShift(NOT_SLIDING);
+        assertSymbolsNormal();
 
-        // TODO: Sliding test
-        // TODO: Snap back test
+        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 "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();
+
+        // 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 @@
         // Release shift key.
         mSwitcher.onReleaseShift(NOT_SLIDING);
         assertAlphabetNormal();
-
-        // TODO: Chording test.
     }
 
-    // TODO: UpdateShiftState with shift locked, etc.
+    // 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: Multitouch 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: Change focus test.
+    // 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: Change orientation 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();
+    }
 
-    // Long press shift key.
-    // TODO: Move long press recognizing timer/logic into KeyboardState.
-    public void testLongPressShift() {
+    // 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();
+
+        // 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();
+    }
+
+    // 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 @@
         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 @@
         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 @@
         // 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 fcbb645..7aadc67 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 @@
 
     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 @@
     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 @@
     }
 
     /**
+     * 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 @@
      * @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 @@
      * @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 @@
         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 @@
             }
             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 @@
     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 @@
                     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 @@
                     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 @@
     }
 
     /**
+     * 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 @@
             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 @@
             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 3ab206d..918b1ca 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 @@
     }
 
     /**
+     * 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 @@
             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 @@
     }
 
     /**
+     * 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 @@
      * @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 @@
             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 @@
                     } 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 @@
                     // 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 @@
                         }
                         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 @@
                     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 @@
                     }
                     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 561b21b..cf6116f 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 @@
 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 d6c03ed..77c5366 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 @@
     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 @@
                 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 @@
         }
         // 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) {