am 988d848b: (-s ours) am cb928e27: am 2e3f261d: Merge "Import translations. DO NOT MERGE" into jb-mr1-dev

* commit '988d848b08ef47b50f94cd2487f2c9add75c62c4':
  Import translations. DO NOT MERGE
diff --git a/java/res/xml/rows_number_normal.xml b/java/res/xml/rows_number_normal.xml
index c59e262..b77544b 100644
--- a/java/res/xml/rows_number_normal.xml
+++ b/java/res/xml/rows_number_normal.xml
@@ -33,6 +33,8 @@
             latin:keyStyle="numKeyStyle" />
         <Key
             latin:keyLabel="-"
+            latin:moreKeys="+"
+            latin:keyLabelFlags="hasPopupHint"
             latin:keyStyle="numFunctionalKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
diff --git a/java/res/xml/rows_phone.xml b/java/res/xml/rows_phone.xml
index 630b24e..9299c2a 100644
--- a/java/res/xml/rows_phone.xml
+++ b/java/res/xml/rows_phone.xml
@@ -34,6 +34,8 @@
             latin:keyStyle="num3KeyStyle" />
         <Key
             latin:keyLabel="-"
+            latin:moreKeys="+"
+            latin:keyLabelFlags="hasPopupHint"
             latin:keyStyle="numFunctionalKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
diff --git a/java/res/xml/rows_phone_symbols.xml b/java/res/xml/rows_phone_symbols.xml
index 7841c56..c13018e 100644
--- a/java/res/xml/rows_phone_symbols.xml
+++ b/java/res/xml/rows_phone_symbols.xml
@@ -37,6 +37,8 @@
             latin:keyStyle="numKeyStyle" />
         <Key
             latin:keyLabel="-"
+            latin:moreKeys="+"
+            latin:keyLabelFlags="hasPopupHint"
             latin:keyStyle="numFunctionalKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index 159f436..9a89eed 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -108,8 +108,8 @@
             CharSequence pickedWord, SuggestedWords suggestedWords, boolean dictionaryAvailable) {
         if (!dictionaryAvailable || TextUtils.isEmpty(pickedWord)
                 || CONSTRUCTOR_SuggestionSpan == null
-                || suggestedWords == null || suggestedWords.size() == 0
-                || suggestedWords.mIsPrediction || suggestedWords.mIsPunctuationSuggestions
+                || suggestedWords.isEmpty() || suggestedWords.mIsPrediction
+                || suggestedWords.mIsPunctuationSuggestions
                 || OBJ_SUGGESTIONS_MAX_SIZE == null) {
             return pickedWord;
         }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d98966e..5a6a502 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1518,8 +1518,8 @@
 
     private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
             final boolean dismissGestureFloatingPreviewText) {
-        final String batchInputText = (suggestedWords.size() > 0)
-                ? suggestedWords.getWord(0) : null;
+        final String batchInputText = suggestedWords.isEmpty()
+                ? null : suggestedWords.getWord(0);
         final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         mainKeyboardView.showGestureFloatingPreviewText(batchInputText);
         showSuggestionStrip(suggestedWords, null);
@@ -1537,8 +1537,8 @@
     public void onEndBatchInput(final InputPointers batchPointers) {
         final SuggestedWords suggestedWords = BatchInputUpdater.getInstance().onEndBatchInput(
                 batchPointers, this);
-        final String batchInputText = (suggestedWords.size() > 0)
-                ? suggestedWords.getWord(0) : null;
+        final String batchInputText = suggestedWords.isEmpty()
+                ? null : suggestedWords.getWord(0);
         if (TextUtils.isEmpty(batchInputText)) {
             return;
         }
@@ -1963,19 +1963,15 @@
 
     private void showSuggestionStrip(final SuggestedWords suggestedWords,
             final CharSequence typedWord) {
-        if (null == suggestedWords || suggestedWords.size() <= 0) {
+        if (suggestedWords.isEmpty()) {
             clearSuggestionStrip();
             return;
         }
         final CharSequence autoCorrection;
-        if (suggestedWords.size() > 0) {
-            if (suggestedWords.mWillAutoCorrect) {
-                autoCorrection = suggestedWords.getWord(1);
-            } else {
-                autoCorrection = typedWord;
-            }
+        if (suggestedWords.mWillAutoCorrect) {
+            autoCorrection = suggestedWords.getWord(1);
         } else {
-            autoCorrection = null;
+            autoCorrection = typedWord;
         }
         mWordComposer.setAutoCorrection(autoCorrection);
         final boolean isAutoCorrection = suggestedWords.willAutoCorrect();
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 52e292a..ad94aff 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -53,6 +53,10 @@
         mIsPrediction = isPrediction;
     }
 
+    public boolean isEmpty() {
+        return mSuggestedWordInfoList.isEmpty();
+    }
+
     public int size() {
         return mSuggestedWordInfoList.size();
     }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index 7b0231a..40e089f 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -17,18 +17,30 @@
 package com.android.inputmethod.latin.makedict;
 
 import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.CharEncoding;
 import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 
 import java.io.IOException;
+import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Stack;
 
 public final class BinaryDictIOUtils {
     private static final boolean DBG = false;
+    private static final int MSB24 = 0x800000;
+    private static final int SINT24_MAX = 0x7FFFFF;
+    private static final int MAX_JUMPS = 10000;
+
+    private BinaryDictIOUtils() {
+        // This utility class is not publicly instantiable.
+    }
 
     private static final class Position {
         public static final int NOT_READ_GROUPCOUNT = -1;
@@ -90,7 +102,9 @@
 
             final boolean isMovedGroup = BinaryDictInputOutput.isMovedGroup(info.mFlags,
                     formatOptions);
-            if (!isMovedGroup
+            final boolean isDeletedGroup = BinaryDictInputOutput.isDeletedGroup(info.mFlags,
+                    formatOptions);
+            if (!isMovedGroup && !isDeletedGroup
                     && info.mFrequency != FusionDictionary.CharGroup.NOT_A_TERMINAL) {// found word
                 words.put(info.mOriginalAddress, new String(pushedChars, 0, index));
                 frequencies.put(info.mOriginalAddress, info.mFrequency);
@@ -165,19 +179,19 @@
             if (wordPos >= wordLen) return FormatSpec.NOT_VALID_WORD;
 
             do {
-                int groupOffset = buffer.position() - header.mHeaderSize;
                 final int charGroupCount = BinaryDictInputOutput.readCharGroupCount(buffer);
-                groupOffset += BinaryDictInputOutput.getGroupCountSize(charGroupCount);
-
                 boolean foundNextCharGroup = false;
                 for (int i = 0; i < charGroupCount; ++i) {
                     final int charGroupPos = buffer.position();
                     final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer,
                             buffer.position(), header.mFormatOptions);
-                    if (BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags,
-                            header.mFormatOptions)) {
-                        continue;
-                    }
+                    final boolean isMovedGroup =
+                            BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags,
+                                    header.mFormatOptions);
+                    final boolean isDeletedGroup =
+                            BinaryDictInputOutput.isDeletedGroup(currentInfo.mFlags,
+                                    header.mFormatOptions);
+                    if (isMovedGroup) continue;
                     boolean same = true;
                     for (int p = 0, j = word.offsetByCodePoints(0, wordPos);
                             p < currentInfo.mCharacters.length;
@@ -192,7 +206,8 @@
                     if (same) {
                         // found the group matches the word.
                         if (wordPos + currentInfo.mCharacters.length == wordLen) {
-                            if (currentInfo.mFrequency == CharGroup.NOT_A_TERMINAL) {
+                            if (currentInfo.mFrequency == CharGroup.NOT_A_TERMINAL
+                                    || isDeletedGroup) {
                                 return FormatSpec.NOT_VALID_WORD;
                             } else {
                                 return charGroupPos;
@@ -206,7 +221,6 @@
                         buffer.position(currentInfo.mChildrenAddress);
                         break;
                     }
-                    groupOffset = currentInfo.mEndAddress;
                 }
 
                 // If we found the next char group, it is under the file pointer.
@@ -228,6 +242,10 @@
         return FormatSpec.NOT_VALID_WORD;
     }
 
+    private static int markAsDeleted(final int flags) {
+        return (flags & (~FormatSpec.MASK_GROUP_ADDRESS_TYPE)) | FormatSpec.FLAG_IS_DELETED;
+    }
+
     /**
      * Delete the word from the binary file.
      *
@@ -245,21 +263,58 @@
 
         buffer.position(wordPosition);
         final int flags = buffer.readUnsignedByte();
-        final int newFlags = flags ^ FormatSpec.FLAG_IS_TERMINAL;
         buffer.position(wordPosition);
-        buffer.put((byte)newFlags);
+        buffer.put((byte)markAsDeleted(flags));
     }
 
-    private static void putSInt24(final FusionDictionaryBufferInterface buffer,
+    /**
+     * @return the size written, in bytes. Always 3 bytes.
+     */
+    private static int writeSInt24ToBuffer(final FusionDictionaryBufferInterface buffer,
             final int value) {
         final int absValue = Math.abs(value);
         buffer.put((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF));
         buffer.put((byte)((absValue >> 8) & 0xFF));
         buffer.put((byte)(absValue & 0xFF));
+        return 3;
     }
 
     /**
-     * Update a parent address in a CharGroup that is addressed by groupOriginAddress.
+     * @return the size written, in bytes. Always 3 bytes.
+     */
+    private static int writeSInt24ToStream(final OutputStream destination, final int value)
+            throws IOException {
+        final int absValue = Math.abs(value);
+        destination.write((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF));
+        destination.write((byte)((absValue >> 8) & 0xFF));
+        destination.write((byte)(absValue & 0xFF));
+        return 3;
+    }
+
+    /**
+     * @return the size written, in bytes. 1, 2, or 3 bytes.
+     */
+    private static int writeVariableAddress(final OutputStream destination, final int value)
+            throws IOException {
+        switch (BinaryDictInputOutput.getByteSize(value)) {
+        case 1:
+            destination.write((byte)value);
+            break;
+        case 2:
+            destination.write((byte)(0xFF & (value >> 8)));
+            destination.write((byte)(0xFF & value));
+            break;
+        case 3:
+            destination.write((byte)(0xFF & (value >> 16)));
+            destination.write((byte)(0xFF & (value >> 8)));
+            destination.write((byte)(0xFF & value));
+            break;
+        }
+        return BinaryDictInputOutput.getByteSize(value);
+    }
+
+    /**
+     * Update a parent address in a CharGroup that is referred to by groupOriginAddress.
      *
      * @param buffer the buffer to write.
      * @param groupOriginAddress the address of the group.
@@ -275,8 +330,644 @@
             throw new RuntimeException("this file format does not support parent addresses");
         }
         final int flags = buffer.readUnsignedByte();
+        if (BinaryDictInputOutput.isMovedGroup(flags, formatOptions)) {
+            // if the group is moved, the parent address is stored in the destination group.
+            // We are guaranteed to process the destination group later, so there is no need to
+            // update anything here.
+            buffer.position(originalPosition);
+            return;
+        }
+        if (DBG) {
+            MakedictLog.d("update parent address flags=" + flags + ", " + groupOriginAddress);
+        }
         final int parentOffset = newParentAddress - groupOriginAddress;
-        putSInt24(buffer, parentOffset);
+        writeSInt24ToBuffer(buffer, parentOffset);
         buffer.position(originalPosition);
     }
+
+    private static void skipCharGroup(final FusionDictionaryBufferInterface buffer,
+            final FormatOptions formatOptions) {
+        final int flags = buffer.readUnsignedByte();
+        BinaryDictInputOutput.readParentAddress(buffer, formatOptions);
+        skipString(buffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
+        BinaryDictInputOutput.readChildrenAddress(buffer, flags, formatOptions);
+        if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) buffer.readUnsignedByte();
+        if ((flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS) != 0) {
+            final int shortcutsSize = buffer.readUnsignedShort();
+            buffer.position(buffer.position() + shortcutsSize
+                    - FormatSpec.GROUP_SHORTCUT_LIST_SIZE_SIZE);
+        }
+        if ((flags & FormatSpec.FLAG_HAS_BIGRAMS) != 0) {
+            int bigramCount = 0;
+            while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_GROUP) {
+                final int bigramFlags = buffer.readUnsignedByte();
+                switch (bigramFlags & FormatSpec.MASK_ATTRIBUTE_ADDRESS_TYPE) {
+                    case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
+                        buffer.readUnsignedByte();
+                        break;
+                    case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
+                        buffer.readUnsignedShort();
+                        break;
+                    case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
+                        buffer.readUnsignedInt24();
+                        break;
+                }
+                if ((bigramFlags & FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT) == 0) break;
+            }
+            if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_GROUP) {
+                throw new RuntimeException("Too many bigrams in a group.");
+            }
+        }
+    }
+
+    /**
+     * Update parent addresses in a Node that is referred to by nodeOriginAddress.
+     *
+     * @param buffer the buffer to be modified.
+     * @param nodeOriginAddress the address of a modified Node.
+     * @param newParentAddress the address to be written.
+     * @param formatOptions file format options.
+     */
+    public static void updateParentAddresses(final FusionDictionaryBufferInterface buffer,
+            final int nodeOriginAddress, final int newParentAddress,
+            final FormatOptions formatOptions) {
+        final int originalPosition = buffer.position();
+        buffer.position(nodeOriginAddress);
+        do {
+            final int count = BinaryDictInputOutput.readCharGroupCount(buffer);
+            for (int i = 0; i < count; ++i) {
+                updateParentAddress(buffer, buffer.position(), newParentAddress, formatOptions);
+                skipCharGroup(buffer, formatOptions);
+            }
+            final int forwardLinkAddress = buffer.readUnsignedInt24();
+            buffer.position(forwardLinkAddress);
+        } while (formatOptions.mSupportsDynamicUpdate
+                && buffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS);
+        buffer.position(originalPosition);
+    }
+
+    private static void skipString(final FusionDictionaryBufferInterface buffer,
+            final boolean hasMultipleChars) {
+        if (hasMultipleChars) {
+            int character = CharEncoding.readChar(buffer);
+            while (character != FormatSpec.INVALID_CHARACTER) {
+                character = CharEncoding.readChar(buffer);
+            }
+        } else {
+            CharEncoding.readChar(buffer);
+        }
+    }
+
+    /**
+     * Write a string to a stream.
+     *
+     * @param destination the stream to write.
+     * @param word the string to be written.
+     * @return the size written, in bytes.
+     * @throws IOException
+     */
+    private static int writeString(final OutputStream destination, final String word)
+            throws IOException {
+        int size = 0;
+        final int length = word.length();
+        for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
+            final int codePoint = word.codePointAt(i);
+            if (CharEncoding.getCharSize(codePoint) == 1) {
+                destination.write((byte)codePoint);
+                size++;
+            } else {
+                destination.write((byte)(0xFF & (codePoint >> 16)));
+                destination.write((byte)(0xFF & (codePoint >> 8)));
+                destination.write((byte)(0xFF & codePoint));
+                size += 3;
+            }
+        }
+        destination.write((byte)FormatSpec.GROUP_CHARACTERS_TERMINATOR);
+        size += FormatSpec.GROUP_TERMINATOR_SIZE;
+        return size;
+    }
+
+    /**
+     * Update a children address in a CharGroup that is addressed by groupOriginAddress.
+     *
+     * @param buffer the buffer to write.
+     * @param groupOriginAddress the address of the group.
+     * @param newChildrenAddress the absolute address of the child.
+     * @param formatOptions file format options.
+     */
+    public static void updateChildrenAddress(final FusionDictionaryBufferInterface buffer,
+            final int groupOriginAddress, final int newChildrenAddress,
+            final FormatOptions formatOptions) {
+        final int originalPosition = buffer.position();
+        buffer.position(groupOriginAddress);
+        final int flags = buffer.readUnsignedByte();
+        final int parentAddress = BinaryDictInputOutput.readParentAddress(buffer, formatOptions);
+        skipString(buffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
+        if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) buffer.readUnsignedByte();
+        final int childrenOffset = newChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS
+                ? FormatSpec.NO_CHILDREN_ADDRESS : newChildrenAddress - buffer.position();
+        writeSInt24ToBuffer(buffer, childrenOffset);
+        buffer.position(originalPosition);
+    }
+
+    /**
+     * Write a char group to an output stream.
+     * A char group is an in-memory representation of a node in trie.
+     * A char group info is an on-disk representation of a node.
+     *
+     * @param destination the stream to write.
+     * @param info the char group info to be written.
+     * @return the size written, in bytes.
+     */
+    public static int writeCharGroup(final OutputStream destination, final CharGroupInfo info)
+            throws IOException {
+        int size = FormatSpec.GROUP_FLAGS_SIZE;
+        destination.write((byte)info.mFlags);
+        final int parentOffset = info.mParentAddress == FormatSpec.NO_PARENT_ADDRESS ?
+                FormatSpec.NO_PARENT_ADDRESS : info.mParentAddress - info.mOriginalAddress;
+        size += writeSInt24ToStream(destination, parentOffset);
+
+        for (int i = 0; i < info.mCharacters.length; ++i) {
+            if (CharEncoding.getCharSize(info.mCharacters[i]) == 1) {
+                destination.write((byte)info.mCharacters[i]);
+                size++;
+            } else {
+                size += writeSInt24ToStream(destination, info.mCharacters[i]);
+            }
+        }
+        if (info.mCharacters.length > 1) {
+            destination.write((byte)FormatSpec.GROUP_CHARACTERS_TERMINATOR);
+            size++;
+        }
+
+        if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) {
+            destination.write((byte)info.mFrequency);
+            size++;
+        }
+
+        if (DBG) {
+            MakedictLog.d("writeCharGroup origin=" + info.mOriginalAddress + ", size=" + size
+                    + ", child=" + info.mChildrenAddress + ", characters ="
+                    + new String(info.mCharacters, 0, info.mCharacters.length));
+        }
+        final int childrenOffset = info.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS ?
+                0 : info.mChildrenAddress - (info.mOriginalAddress + size);
+        writeSInt24ToStream(destination, childrenOffset);
+        size += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
+
+        if (info.mShortcutTargets != null && info.mShortcutTargets.size() > 0) {
+            final int shortcutListSize =
+                    BinaryDictInputOutput.getShortcutListSize(info.mShortcutTargets);
+            destination.write((byte)(shortcutListSize >> 8));
+            destination.write((byte)(shortcutListSize & 0xFF));
+            size += 2;
+            final Iterator<WeightedString> shortcutIterator = info.mShortcutTargets.iterator();
+            while (shortcutIterator.hasNext()) {
+                final WeightedString target = shortcutIterator.next();
+                destination.write((byte)BinaryDictInputOutput.makeShortcutFlags(
+                        shortcutIterator.hasNext(), target.mFrequency));
+                size++;
+                size += writeString(destination, target.mWord);
+            }
+        }
+
+        if (info.mBigrams != null) {
+            // TODO: Consolidate this code with the code that computes the size of the bigram list
+            //        in BinaryDictionaryInputOutput#computeActualNodeSize
+            for (int i = 0; i < info.mBigrams.size(); ++i) {
+
+                final int bigramFrequency = info.mBigrams.get(i).mFrequency;
+                int bigramFlags = (i < info.mBigrams.size() - 1)
+                        ? FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT : 0;
+                size++;
+                final int bigramOffset = info.mBigrams.get(i).mAddress - (info.mOriginalAddress
+                        + size);
+                bigramFlags |= (bigramOffset < 0) ? FormatSpec.FLAG_ATTRIBUTE_OFFSET_NEGATIVE : 0;
+                switch (BinaryDictInputOutput.getByteSize(bigramOffset)) {
+                case 1:
+                    bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE;
+                    break;
+                case 2:
+                    bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES;
+                    break;
+                case 3:
+                    bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES;
+                    break;
+                }
+                bigramFlags |= bigramFrequency & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY;
+                destination.write((byte)bigramFlags);
+                size += writeVariableAddress(destination, Math.abs(bigramOffset));
+            }
+        }
+        return size;
+    }
+
+    private static void updateForwardLink(final FusionDictionaryBufferInterface buffer,
+            final int nodeOriginAddress, final int newNodeAddress,
+            final FormatOptions formatOptions) {
+        buffer.position(nodeOriginAddress);
+        int jumpCount = 0;
+        while (jumpCount++ < MAX_JUMPS) {
+            final int count = BinaryDictInputOutput.readCharGroupCount(buffer);
+            for (int i = 0; i < count; ++i) skipCharGroup(buffer, formatOptions);
+            final int forwardLinkAddress = buffer.readUnsignedInt24();
+            if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+                buffer.position(buffer.position() - FormatSpec.FORWARD_LINK_ADDRESS_SIZE);
+                writeSInt24ToBuffer(buffer, newNodeAddress);
+                return;
+            }
+            buffer.position(forwardLinkAddress);
+        }
+        if (DBG && jumpCount >= MAX_JUMPS) {
+            throw new RuntimeException("too many jumps, probably a bug.");
+        }
+    }
+
+    /**
+     * Helper method to move a char group to the tail of the file.
+     */
+    private static int moveCharGroup(final OutputStream destination,
+            final FusionDictionaryBufferInterface buffer, final CharGroupInfo info,
+            final int nodeOriginAddress, final int oldGroupAddress,
+            final FormatOptions formatOptions) throws IOException {
+        updateParentAddress(buffer, oldGroupAddress, buffer.limit() + 1, formatOptions);
+        buffer.position(oldGroupAddress);
+        final int currentFlags = buffer.readUnsignedByte();
+        buffer.position(oldGroupAddress);
+        buffer.put((byte)(FormatSpec.FLAG_IS_MOVED | (currentFlags
+                & (~FormatSpec.MASK_MOVE_AND_DELETE_FLAG))));
+        int size = FormatSpec.GROUP_FLAGS_SIZE;
+        updateForwardLink(buffer, nodeOriginAddress, buffer.limit(), formatOptions);
+        size += writeNode(destination, new CharGroupInfo[] { info });
+        return size;
+    }
+
+    /**
+     * Compute the size of the char group.
+     */
+    private static int computeGroupSize(final CharGroupInfo info,
+            final FormatOptions formatOptions) {
+        int size = FormatSpec.GROUP_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE
+                + BinaryDictInputOutput.getGroupCharactersSize(info.mCharacters)
+                + BinaryDictInputOutput.getChildrenAddressSize(info.mFlags, formatOptions);
+        if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) {
+            size += FormatSpec.GROUP_FREQUENCY_SIZE;
+        }
+        if (info.mShortcutTargets != null && !info.mShortcutTargets.isEmpty()) {
+            size += BinaryDictInputOutput.getShortcutListSize(info.mShortcutTargets);
+        }
+        if (info.mBigrams != null) {
+            for (final PendingAttribute attr : info.mBigrams) {
+                size += FormatSpec.GROUP_FLAGS_SIZE;
+                size += BinaryDictInputOutput.getByteSize(attr.mAddress);
+            }
+        }
+        return size;
+    }
+
+    /**
+     * Write a node to the stream.
+     *
+     * @param destination the stream to write.
+     * @param infos groups to be written.
+     * @return the size written, in bytes.
+     * @throws IOException
+     */
+    private static int writeNode(final OutputStream destination, final CharGroupInfo[] infos)
+            throws IOException {
+        int size = BinaryDictInputOutput.getGroupCountSize(infos.length);
+        switch (BinaryDictInputOutput.getGroupCountSize(infos.length)) {
+            case 1:
+                destination.write((byte)infos.length);
+                break;
+            case 2:
+                destination.write((byte)(infos.length >> 8));
+                destination.write((byte)(infos.length & 0xFF));
+                break;
+            default:
+                throw new RuntimeException("Invalid group count size.");
+        }
+        for (final CharGroupInfo info : infos) size += writeCharGroup(destination, info);
+        writeSInt24ToStream(destination, FormatSpec.NO_FORWARD_LINK_ADDRESS);
+        return size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+    }
+
+    /**
+     * Move a group that is referred to by oldGroupOrigin to the tail of the file.
+     * And set the children address to the byte after the group.
+     *
+     * @param nodeOrigin the address of the tail of the file.
+     * @param characters
+     * @param length
+     * @param flags
+     * @param frequency
+     * @param parentAddress
+     * @param shortcutTargets
+     * @param bigrams
+     * @param destination the stream representing the tail of the file.
+     * @param buffer the buffer representing the (constant-size) body of the file.
+     * @param oldNodeOrigin
+     * @param oldGroupOrigin
+     * @param formatOptions
+     * @return the size written, in bytes.
+     * @throws IOException
+     */
+    private static int moveGroup(final int nodeOrigin, final int[] characters, final int length,
+            final int flags, final int frequency, final int parentAddress,
+            final ArrayList<WeightedString> shortcutTargets,
+            final ArrayList<PendingAttribute> bigrams, final OutputStream destination,
+            final FusionDictionaryBufferInterface buffer, final int oldNodeOrigin,
+            final int oldGroupOrigin, final FormatOptions formatOptions) throws IOException {
+        int size = 0;
+        final int newGroupOrigin = nodeOrigin + 1;
+        final int[] writtenCharacters = Arrays.copyOfRange(characters, 0, length);
+        final CharGroupInfo tmpInfo = new CharGroupInfo(newGroupOrigin, -1 /* endAddress */,
+                flags, writtenCharacters, frequency, parentAddress, FormatSpec.NO_CHILDREN_ADDRESS,
+                shortcutTargets, bigrams);
+        size = computeGroupSize(tmpInfo, formatOptions);
+        final CharGroupInfo newInfo = new CharGroupInfo(newGroupOrigin, newGroupOrigin + size,
+                flags, writtenCharacters, frequency, parentAddress,
+                nodeOrigin + 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE, shortcutTargets,
+                bigrams);
+        moveCharGroup(destination, buffer, newInfo, oldNodeOrigin, oldGroupOrigin, formatOptions);
+        return 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+    }
+
+    /**
+     * Insert a word into a binary dictionary.
+     *
+     * @param buffer
+     * @param destination
+     * @param word
+     * @param frequency
+     * @param bigramStrings
+     * @param shortcuts
+     * @throws IOException
+     * @throws UnsupportedFormatException
+     */
+    // TODO: Support batch insertion.
+    public static void insertWord(final FusionDictionaryBufferInterface buffer,
+            final OutputStream destination, final String word, final int frequency,
+            final ArrayList<WeightedString> bigramStrings,
+            final ArrayList<WeightedString> shortcuts, final boolean isNotAWord,
+            final boolean isBlackListEntry)
+                    throws IOException, UnsupportedFormatException {
+        final ArrayList<PendingAttribute> bigrams = new ArrayList<PendingAttribute>();
+        if (bigramStrings != null) {
+            for (final WeightedString bigram : bigramStrings) {
+                int position = getTerminalPosition(buffer, bigram.mWord);
+                if (position == FormatSpec.NOT_VALID_WORD) {
+                    // TODO: figure out what is the correct thing to do here.
+                } else {
+                    bigrams.add(new PendingAttribute(bigram.mFrequency, position));
+                }
+            }
+        }
+
+        final boolean isTerminal = true;
+        final boolean hasBigrams = !bigrams.isEmpty();
+        final boolean hasShortcuts = shortcuts != null && !shortcuts.isEmpty();
+
+        // find the insert position of the word.
+        if (buffer.position() != 0) buffer.position(0);
+        final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
+
+        int wordPos = 0, address = buffer.position(), nodeOriginAddress = buffer.position();
+        final int[] codePoints = FusionDictionary.getCodePoints(word);
+        final int wordLen = codePoints.length;
+
+        for (int depth = 0; depth < Constants.Dictionary.MAX_WORD_LENGTH; ++depth) {
+            if (wordPos >= wordLen) break;
+            nodeOriginAddress = buffer.position();
+            int nodeParentAddress = -1;
+            final int charGroupCount = BinaryDictInputOutput.readCharGroupCount(buffer);
+            boolean foundNextGroup = false;
+
+            for (int i = 0; i < charGroupCount; ++i) {
+                address = buffer.position();
+                final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer,
+                        buffer.position(), header.mFormatOptions);
+                final boolean isMovedGroup = BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags,
+                        header.mFormatOptions);
+                if (isMovedGroup) continue;
+                nodeParentAddress = (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS)
+                        ? FormatSpec.NO_PARENT_ADDRESS : currentInfo.mParentAddress + address;
+                boolean matched = true;
+                for (int p = 0; p < currentInfo.mCharacters.length; ++p) {
+                    if (wordPos + p >= wordLen) {
+                        /*
+                         * splitting
+                         * before
+                         *  abcd - ef
+                         *
+                         * insert "abc"
+                         *
+                         * after
+                         *  abc - d - ef
+                         */
+                        final int newNodeAddress = buffer.limit();
+                        final int flags = BinaryDictInputOutput.makeCharGroupFlags(p > 1,
+                                isTerminal, 0, hasShortcuts, hasBigrams, false /* isNotAWord */,
+                                false /* isBlackListEntry */, header.mFormatOptions);
+                        int written = moveGroup(newNodeAddress, currentInfo.mCharacters, p, flags,
+                                frequency, nodeParentAddress, shortcuts, bigrams, destination,
+                                buffer, nodeOriginAddress, address, header.mFormatOptions);
+
+                        final int[] characters2 = Arrays.copyOfRange(currentInfo.mCharacters, p,
+                                currentInfo.mCharacters.length);
+                        if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
+                            updateParentAddresses(buffer, currentInfo.mChildrenAddress,
+                                    newNodeAddress + written + 1, header.mFormatOptions);
+                        }
+                        final CharGroupInfo newInfo2 = new CharGroupInfo(
+                                newNodeAddress + written + 1, -1 /* endAddress */,
+                                currentInfo.mFlags, characters2, currentInfo.mFrequency,
+                                newNodeAddress + 1, currentInfo.mChildrenAddress,
+                                currentInfo.mShortcutTargets, currentInfo.mBigrams);
+                        writeNode(destination, new CharGroupInfo[] { newInfo2 });
+                        return;
+                    } else if (codePoints[wordPos + p] != currentInfo.mCharacters[p]) {
+                        if (p > 0) {
+                            /*
+                             * splitting
+                             * before
+                             *   ab - cd
+                             *
+                             * insert "ac"
+                             *
+                             * after
+                             *   a - b - cd
+                             *     |
+                             *     - c
+                             */
+
+                            final int newNodeAddress = buffer.limit();
+                            final int childrenAddress = currentInfo.mChildrenAddress;
+
+                            // move prefix
+                            final int prefixFlags = BinaryDictInputOutput.makeCharGroupFlags(p > 1,
+                                    false /* isTerminal */, 0 /* childrenAddressSize*/,
+                                    false /* hasShortcut */, false /* hasBigrams */,
+                                    false /* isNotAWord */, false /* isBlackListEntry */,
+                                    header.mFormatOptions);
+                            int written = moveGroup(newNodeAddress, currentInfo.mCharacters, p,
+                                    prefixFlags, -1 /* frequency */, nodeParentAddress, null, null,
+                                    destination, buffer, nodeOriginAddress, address,
+                                    header.mFormatOptions);
+
+                            final int[] suffixCharacters = Arrays.copyOfRange(
+                                    currentInfo.mCharacters, p, currentInfo.mCharacters.length);
+                            if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
+                                updateParentAddresses(buffer, currentInfo.mChildrenAddress,
+                                        newNodeAddress + written + 1, header.mFormatOptions);
+                            }
+                            final int suffixFlags = BinaryDictInputOutput.makeCharGroupFlags(
+                                    suffixCharacters.length > 1,
+                                    (currentInfo.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0,
+                                    0 /* childrenAddressSize */,
+                                    (currentInfo.mFlags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)
+                                            != 0,
+                                    (currentInfo.mFlags & FormatSpec.FLAG_HAS_BIGRAMS) != 0,
+                                    isNotAWord, isBlackListEntry, header.mFormatOptions);
+                            final CharGroupInfo suffixInfo = new CharGroupInfo(
+                                    newNodeAddress + written + 1, -1 /* endAddress */, suffixFlags,
+                                    suffixCharacters, currentInfo.mFrequency, newNodeAddress + 1,
+                                    currentInfo.mChildrenAddress, currentInfo.mShortcutTargets,
+                                    currentInfo.mBigrams);
+                            written += computeGroupSize(suffixInfo, header.mFormatOptions) + 1;
+
+                            final int[] newCharacters = Arrays.copyOfRange(codePoints, wordPos + p,
+                                    codePoints.length);
+                            final int flags = BinaryDictInputOutput.makeCharGroupFlags(
+                                    newCharacters.length > 1, isTerminal,
+                                    0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
+                                    isNotAWord, isBlackListEntry, header.mFormatOptions);
+                            final CharGroupInfo newInfo = new CharGroupInfo(
+                                    newNodeAddress + written, -1 /* endAddress */, flags,
+                                    newCharacters, frequency, newNodeAddress + 1,
+                                    FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams);
+                            writeNode(destination, new CharGroupInfo[] { suffixInfo, newInfo });
+                            return;
+                        }
+                        matched = false;
+                        break;
+                    }
+                }
+
+                if (matched) {
+                    if (wordPos + currentInfo.mCharacters.length == wordLen) {
+                        // the word exists in the dictionary.
+                        // only update group.
+                        final int newNodeAddress = buffer.limit();
+                        final boolean hasMultipleChars = currentInfo.mCharacters.length > 1;
+                        final int flags = BinaryDictInputOutput.makeCharGroupFlags(hasMultipleChars,
+                                isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
+                                isNotAWord, isBlackListEntry, header.mFormatOptions);
+                        final CharGroupInfo newInfo = new CharGroupInfo(newNodeAddress + 1,
+                                -1 /* endAddress */, flags, currentInfo.mCharacters, frequency,
+                                nodeParentAddress, currentInfo.mChildrenAddress, shortcuts,
+                                bigrams);
+                        moveCharGroup(destination, buffer, newInfo, nodeOriginAddress, address,
+                                header.mFormatOptions);
+                        return;
+                    }
+                    wordPos += currentInfo.mCharacters.length;
+                    if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) {
+                        /*
+                         * found the prefix of the word.
+                         * make new node and link to the node from this group.
+                         *
+                         * before
+                         * ab - cd
+                         *
+                         * insert "abcde"
+                         *
+                         * after
+                         * ab - cd - e
+                         */
+                        final int newNodeAddress = buffer.limit();
+                        updateChildrenAddress(buffer, address, newNodeAddress,
+                                header.mFormatOptions);
+                        final int newGroupAddress = newNodeAddress + 1;
+                        final boolean hasMultipleChars = (wordLen - wordPos) > 1;
+                        final int flags = BinaryDictInputOutput.makeCharGroupFlags(hasMultipleChars,
+                                isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
+                                isNotAWord, isBlackListEntry, header.mFormatOptions);
+                        final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen);
+                        final CharGroupInfo newInfo = new CharGroupInfo(newGroupAddress, -1, flags,
+                                characters, frequency, address, FormatSpec.NO_CHILDREN_ADDRESS,
+                                shortcuts, bigrams);
+                        writeNode(destination, new CharGroupInfo[] { newInfo });
+                        return;
+                    }
+                    buffer.position(currentInfo.mChildrenAddress);
+                    foundNextGroup = true;
+                    break;
+                }
+            }
+
+            if (foundNextGroup) continue;
+
+            // reached the end of the array.
+            final int linkAddressPosition = buffer.position();
+            int nextLink = buffer.readUnsignedInt24();
+            if ((nextLink & MSB24) != 0) {
+                nextLink = -(nextLink & SINT24_MAX);
+            }
+            if (nextLink == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+                /*
+                 * expand this node.
+                 *
+                 * before
+                 * ab - cd
+                 *
+                 * insert "abef"
+                 *
+                 * after
+                 * ab - cd
+                 *    |
+                 *    - ef
+                 */
+
+                // change the forward link address.
+                final int newNodeAddress = buffer.limit();
+                buffer.position(linkAddressPosition);
+                writeSInt24ToBuffer(buffer, newNodeAddress);
+
+                final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen);
+                final int flags = BinaryDictInputOutput.makeCharGroupFlags(characters.length > 1,
+                        isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
+                        isNotAWord, isBlackListEntry, header.mFormatOptions);
+                final CharGroupInfo newInfo = new CharGroupInfo(newNodeAddress + 1,
+                        -1 /* endAddress */, flags, characters, frequency, nodeParentAddress,
+                        FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams);
+                writeNode(destination, new CharGroupInfo[]{ newInfo });
+                return;
+            } else {
+                depth--;
+                buffer.position(nextLink);
+            }
+        }
+    }
+
+    /**
+     * Find a word from the buffer.
+     *
+     * @param buffer the buffer representing the body of the dictionary file.
+     * @param word the word searched
+     * @return the found group
+     * @throws IOException
+     * @throws UnsupportedFormatException
+     */
+    public static CharGroupInfo findWordFromBuffer(final FusionDictionaryBufferInterface buffer,
+            final String word) throws IOException, UnsupportedFormatException {
+        int position = getTerminalPosition(buffer, word);
+        if (position != FormatSpec.NOT_VALID_WORD) {
+            buffer.position(0);
+            final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
+            buffer.position(position);
+            return BinaryDictInputOutput.readCharGroup(buffer, position, header.mFormatOptions);
+        }
+        return null;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index b431a4d..2d39094 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -124,8 +124,7 @@
     /**
      * A class grouping utility function for our specific character encoding.
      */
-    private static final class CharEncoding {
-
+    static final class CharEncoding {
         private static final int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
         private static final int MAXIMAL_ONE_BYTE_CHARACTER_VALUE = 0xFF;
 
@@ -154,7 +153,7 @@
          * @param character the character code.
          * @return the size in binary encoded-form, either 1 or 3 bytes.
          */
-        private static int getCharSize(final int character) {
+        static int getCharSize(final int character) {
             // See char encoding in FusionDictionary.java
             if (fitsOnOneByte(character)) return 1;
             if (FormatSpec.INVALID_CHARACTER == character) return 1;
@@ -263,7 +262,7 @@
          * @param buffer the buffer, positioned over an encoded character.
          * @return the character code.
          */
-        private static int readChar(final FusionDictionaryBufferInterface buffer) {
+        static int readChar(final FusionDictionaryBufferInterface buffer) {
             int character = buffer.readUnsignedByte();
             if (!fitsOnOneByte(character)) {
                 if (FormatSpec.GROUP_CHARACTERS_TERMINATOR == character) {
@@ -277,6 +276,21 @@
     }
 
     /**
+     * Compute the binary size of the character array.
+     *
+     * If only one character, this is the size of this character. If many, it's the sum of their
+     * sizes + 1 byte for the terminator.
+     *
+     * @param characters the character array
+     * @return the size of the char array, including the terminator if any
+     */
+    static int getGroupCharactersSize(final int[] characters) {
+        int size = CharEncoding.getCharArraySize(characters);
+        if (characters.length > 1) size += FormatSpec.GROUP_TERMINATOR_SIZE;
+        return size;
+    }
+
+    /**
      * Compute the binary size of the character array in a group
      *
      * If only one character, this is the size of this character. If many, it's the sum of their
@@ -286,9 +300,7 @@
      * @return the size of the char array, including the terminator if any
      */
     private static int getGroupCharactersSize(final CharGroup group) {
-        int size = CharEncoding.getCharArraySize(group.mChars);
-        if (group.hasSeveralChars()) size += FormatSpec.GROUP_TERMINATOR_SIZE;
-        return size;
+        return getGroupCharactersSize(group.mChars);
     }
 
     /**
@@ -338,7 +350,7 @@
      * This is known in advance and does not change according to position in the file
      * like address lists do.
      */
-    private static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) {
+    static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) {
         if (null == shortcutList) return 0;
         int size = FormatSpec.GROUP_SHORTCUT_LIST_SIZE_SIZE;
         for (final WeightedString shortcut : shortcutList) {
@@ -399,7 +411,16 @@
      * Helper method to check whether the group is moved.
      */
     public static boolean isMovedGroup(final int flags, final FormatOptions options) {
-        return options.mSupportsDynamicUpdate && ((flags & FormatSpec.FLAG_IS_MOVED) == 1);
+        return options.mSupportsDynamicUpdate
+                && ((flags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) == FormatSpec.FLAG_IS_MOVED);
+    }
+
+    /**
+     * Helper method to check whether the group is deleted.
+     */
+    public static boolean isDeletedGroup(final int flags, final FormatOptions formatOptions) {
+        return formatOptions.mSupportsDynamicUpdate
+                && ((flags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) == FormatSpec.FLAG_IS_DELETED);
     }
 
     /**
@@ -439,7 +460,7 @@
      * @param address the address
      * @return the byte size.
      */
-    private static int getByteSize(final int address) {
+    static int getByteSize(final int address) {
         assert(address <= UINT24_MAX);
         if (!hasChildrenAddress(address)) {
             return 0;
@@ -721,51 +742,58 @@
         return 3;
     }
 
+    /**
+     * Makes the flag value for a char group.
+     *
+     * @param hasMultipleChars whether the group has multiple chars.
+     * @param isTerminal whether the group is terminal.
+     * @param childrenAddressSize the size of a children address.
+     * @param hasShortcuts whether the group has shortcuts.
+     * @param hasBigrams whether the group has bigrams.
+     * @param isNotAWord whether the group is not a word.
+     * @param isBlackListEntry whether the group is a blacklist entry.
+     * @param formatOptions file format options.
+     * @return the flags
+     */
+    static int makeCharGroupFlags(final boolean hasMultipleChars, final boolean isTerminal,
+            final int childrenAddressSize, final boolean hasShortcuts, final boolean hasBigrams,
+            final boolean isNotAWord, final boolean isBlackListEntry,
+            final FormatOptions formatOptions) {
+        byte flags = 0;
+        if (hasMultipleChars) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS;
+        if (isTerminal) flags |= FormatSpec.FLAG_IS_TERMINAL;
+        if (formatOptions.mSupportsDynamicUpdate) {
+            flags |= FormatSpec.FLAG_IS_NOT_MOVED;
+        } else if (true) {
+            switch (childrenAddressSize) {
+                case 1:
+                    flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE;
+                    break;
+                case 2:
+                    flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES;
+                    break;
+                case 3:
+                    flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES;
+                    break;
+                case 0:
+                    flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_NOADDRESS;
+                    break;
+                default:
+                    throw new RuntimeException("Node with a strange address");
+            }
+        }
+        if (hasShortcuts) flags |= FormatSpec.FLAG_HAS_SHORTCUT_TARGETS;
+        if (hasBigrams) flags |= FormatSpec.FLAG_HAS_BIGRAMS;
+        if (isNotAWord) flags |= FormatSpec.FLAG_IS_NOT_A_WORD;
+        if (isBlackListEntry) flags |= FormatSpec.FLAG_IS_BLACKLISTED;
+        return flags;
+    }
+
     private static byte makeCharGroupFlags(final CharGroup group, final int groupAddress,
             final int childrenOffset, final FormatOptions formatOptions) {
-        byte flags = 0;
-        if (group.mChars.length > 1) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS;
-        if (group.mFrequency >= 0) {
-            flags |= FormatSpec.FLAG_IS_TERMINAL;
-        }
-        if (null != group.mChildren) {
-            final int byteSize = formatOptions.mSupportsDynamicUpdate
-                    ? FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE : getByteSize(childrenOffset);
-            switch (byteSize) {
-            case 1:
-                flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE;
-                break;
-            case 2:
-                flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES;
-                break;
-            case 3:
-                flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES;
-                break;
-            default:
-                throw new RuntimeException("Node with a strange address");
-            }
-        } else if (formatOptions.mSupportsDynamicUpdate) {
-            flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES;
-        }
-        if (null != group.mShortcutTargets) {
-            if (DBG && 0 == group.mShortcutTargets.size()) {
-                throw new RuntimeException("0-sized shortcut list must be null");
-            }
-            flags |= FormatSpec.FLAG_HAS_SHORTCUT_TARGETS;
-        }
-        if (null != group.mBigrams) {
-            if (DBG && 0 == group.mBigrams.size()) {
-                throw new RuntimeException("0-sized bigram list must be null");
-            }
-            flags |= FormatSpec.FLAG_HAS_BIGRAMS;
-        }
-        if (group.mIsNotAWord) {
-            flags |= FormatSpec.FLAG_IS_NOT_A_WORD;
-        }
-        if (group.mIsBlacklistEntry) {
-            flags |= FormatSpec.FLAG_IS_BLACKLISTED;
-        }
-        return flags;
+        return (byte) makeCharGroupFlags(group.mChars.length > 1, group.mFrequency >= 0,
+                getByteSize(childrenOffset), group.mShortcutTargets != null, group.mBigrams != null,
+                group.mIsNotAWord, group.mIsBlacklistEntry, formatOptions);
     }
 
     /**
@@ -859,7 +887,7 @@
      * @param frequency the frequency of the attribute, 0..15
      * @return the flags
      */
-    private static final int makeShortcutFlags(final boolean more, final int frequency) {
+    static final int makeShortcutFlags(final boolean more, final int frequency) {
         return (more ? FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT : 0)
                 + (frequency & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY);
     }
@@ -897,6 +925,7 @@
      */
     private static int writePlacedNode(final FusionDictionary dict, byte[] buffer,
             final Node node, final FormatOptions formatOptions) {
+        // TODO: Make the code in common with BinaryDictIOUtils#writeCharGroup
         int index = node.mCachedAddress;
 
         final int groupCount = node.mData.size();
@@ -1178,7 +1207,7 @@
     // Input methods: Read a binary dictionary to memory.
     // readDictionaryBinary is the public entry point for them.
 
-    private static int getChildrenAddressSize(final int optionFlags,
+    static int getChildrenAddressSize(final int optionFlags,
             final FormatOptions formatOptions) {
         if (formatOptions.mSupportsDynamicUpdate) return FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
         switch (optionFlags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) {
@@ -1194,7 +1223,7 @@
         }
     }
 
-    private static int readChildrenAddress(final FusionDictionaryBufferInterface buffer,
+    static int readChildrenAddress(final FusionDictionaryBufferInterface buffer,
             final int optionFlags, final FormatOptions options) {
         if (options.mSupportsDynamicUpdate) {
             final int address = buffer.readUnsignedInt24();
@@ -1219,7 +1248,7 @@
         }
     }
 
-    private static int readParentAddress(final FusionDictionaryBufferInterface buffer,
+    static int readParentAddress(final FusionDictionaryBufferInterface buffer,
             final FormatOptions formatOptions) {
         if (supportsDynamicUpdate(formatOptions)) {
             final int parentAddress = buffer.readUnsignedInt24();
@@ -1290,7 +1319,8 @@
         ArrayList<PendingAttribute> bigrams = null;
         if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
             bigrams = new ArrayList<PendingAttribute>();
-            while (true) {
+            int bigramCount = 0;
+            while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_GROUP) {
                 final int bigramFlags = buffer.readUnsignedByte();
                 ++addressPointer;
                 final int sign = 0 == (bigramFlags & FormatSpec.FLAG_ATTRIBUTE_OFFSET_NEGATIVE)
@@ -1318,6 +1348,9 @@
                         bigramAddress));
                 if (0 == (bigramFlags & FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT)) break;
             }
+            if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_GROUP) {
+                MakedictLog.d("too many bigrams in a group.");
+            }
         }
         return new CharGroupInfo(originalGroupAddress, addressPointer, flags, characters, frequency,
                 parentAddress, childrenAddress, shortcutTargets, bigrams);
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index b3fbb9f..e88a4ae 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -59,9 +59,11 @@
      * l |                                   10 = 2 bytes     : FLAG_GROUP_ADDRESS_TYPE_TWOBYTES
      * a |                                   11 = 3 bytes     : FLAG_GROUP_ADDRESS_TYPE_THREEBYTES
      * g | ELSE
-     * s |   is moved ?              2 bits, 11 = no
-     *   |                                   01 = yes
+     * s |   is moved ?              2 bits, 11 = no          : FLAG_IS_NOT_MOVED
+     *   |                              This must be the same as FLAG_GROUP_ADDRESS_TYPE_THREEBYTES
+     *   |                                   01 = yes         : FLAG_IS_MOVED
      *   |                        the new address is stored in the same place as the parent address
+     *   |   is deleted?                     10 = yes         : FLAG_IS_DELETED
      *   | has several chars ?         1 bit, 1 = yes, 0 = no   : FLAG_HAS_MULTIPLE_CHARS
      *   | has a terminal ?            1 bit, 1 = yes, 0 = no   : FLAG_IS_TERMINAL
      *   | has shortcut targets ?      1 bit, 1 = yes, 0 = no   : FLAG_HAS_SHORTCUT_TARGETS
@@ -170,6 +172,7 @@
     static final int PARENT_ADDRESS_SIZE = 3;
     static final int FORWARD_LINK_ADDRESS_SIZE = 3;
 
+    // These flags are used only in the static dictionary.
     static final int MASK_GROUP_ADDRESS_TYPE = 0xC0;
     static final int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00;
     static final int FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40;
@@ -183,7 +186,13 @@
     static final int FLAG_HAS_BIGRAMS = 0x04;
     static final int FLAG_IS_NOT_A_WORD = 0x02;
     static final int FLAG_IS_BLACKLISTED = 0x01;
-    static final int FLAG_IS_MOVED = 0x40;
+
+    // These flags are used only in the dynamic dictionary.
+    static final int MASK_MOVE_AND_DELETE_FLAG = 0xC0;
+    static final int FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE = 0x40;
+    static final int FLAG_IS_MOVED = 0x00 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE;
+    static final int FLAG_IS_NOT_MOVED = 0x80 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE;
+    static final int FLAG_IS_DELETED = 0x80;
 
     static final int FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
     static final int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
@@ -210,6 +219,7 @@
 
     static final int MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT = 0x7F; // 127
     static final int MAX_CHARGROUPS_IN_A_NODE = 0x7FFF; // 32767
+    static final int MAX_BIGRAMS_IN_A_GROUP = 10000;
 
     static final int MAX_TERMINAL_FREQUENCY = 255;
     static final int MAX_BIGRAM_FREQUENCY = 15;
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 3193ef4..6f1faa1 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -279,7 +279,7 @@
     /**
      * Helper method to convert a String to an int array.
      */
-    static private int[] getCodePoints(final String word) {
+    static int[] getCodePoints(final String word) {
         // TODO: this is a copy-paste of the contents of StringUtils.toCodePointArray,
         // which is not visible from the makedict package. Factor this code.
         final char[] characters = word.toCharArray();
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index e9bf0fa..4ad82ab 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -22,7 +22,6 @@
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
@@ -175,11 +174,11 @@
         }
 
         public Builder layout(final SuggestedWords suggestions, final int fromPos,
-                final int maxWidth, final int minWidth, final int maxRow) {
-            final Keyboard keyboard = KeyboardSwitcher.getInstance().getKeyboard();
+                final int maxWidth, final int minWidth, final int maxRow,
+                final Keyboard parentKeyboard) {
             final int xmlId = R.xml.kbd_suggestions_pane_template;
-            load(xmlId, keyboard.mId);
-            mParams.mVerticalGap = mParams.mTopPadding = keyboard.mVerticalGap / 2;
+            load(xmlId, parentKeyboard.mId);
+            mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
 
             final int count = mParams.layout(suggestions, fromPos, maxWidth, minWidth, maxRow,
                     mPaneView);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 9b9a354..740476f 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -56,17 +56,17 @@
     final KeyboardActionListener mSuggestionsPaneListener =
             new KeyboardActionListener.Adapter() {
         @Override
-        public void onPressKey(int primaryCode) {
+        public void onPressKey(final int primaryCode) {
             mListener.onPressKey(primaryCode);
         }
 
         @Override
-        public void onReleaseKey(int primaryCode, boolean withSliding) {
+        public void onReleaseKey(final int primaryCode, final boolean withSliding) {
             mListener.onReleaseKey(primaryCode, withSliding);
         }
 
         @Override
-        public void onCodeInput(int primaryCode, int x, int y) {
+        public void onCodeInput(final int primaryCode, final int x, final int y) {
             final int index = primaryCode - MoreSuggestions.SUGGESTION_CODE_BASE;
             if (index >= 0 && index < SuggestionStripView.MAX_SUGGESTIONS) {
                 mListener.onCustomRequest(index);
@@ -79,11 +79,12 @@
         }
     };
 
-    public MoreSuggestionsView(Context context, AttributeSet attrs) {
+    public MoreSuggestionsView(final Context context, final AttributeSet attrs) {
         this(context, attrs, R.attr.moreSuggestionsViewStyle);
     }
 
-    public MoreSuggestionsView(Context context, AttributeSet attrs, int defStyle) {
+    public MoreSuggestionsView(final Context context, final AttributeSet attrs,
+            final int defStyle) {
         super(context, attrs, defStyle);
 
         final Resources res = context.getResources();
@@ -94,7 +95,7 @@
     }
 
     @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
         final Keyboard keyboard = getKeyboard();
         if (keyboard != null) {
             final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
@@ -106,7 +107,7 @@
     }
 
     @Override
-    public void setKeyboard(Keyboard keyboard) {
+    public void setKeyboard(final Keyboard keyboard) {
         super.setKeyboard(keyboard);
         mModalPanelKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), -getPaddingTop());
         mSlidingPanelKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
@@ -134,15 +135,16 @@
     }
 
     @Override
-    public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
+    public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
         // Suggestions pane needs no pop-up key preview displayed, so we pass always false with a
         // delay of 0. The delay does not matter actually since the popup is not shown anyway.
         super.setKeyPreviewPopupEnabled(false, 0);
     }
 
     @Override
-    public void showMoreKeysPanel(View parentView, Controller controller, int pointX, int pointY,
-            PopupWindow window, KeyboardActionListener listener) {
+    public void showMoreKeysPanel(final View parentView, final Controller controller,
+            final int pointX, final int pointY, final PopupWindow window,
+            final KeyboardActionListener listener) {
         mController = controller;
         mListener = listener;
         final View container = (View)getParent();
@@ -175,12 +177,12 @@
     }
 
     @Override
-    public int translateX(int x) {
+    public int translateX(final int x) {
         return x - mOriginX;
     }
 
     @Override
-    public int translateY(int y) {
+    public int translateY(final int y) {
         return y - mOriginY;
     }
 
@@ -207,7 +209,7 @@
     };
 
     @Override
-    public boolean onTouchEvent(MotionEvent me) {
+    public boolean onTouchEvent(final MotionEvent me) {
         final int action = me.getAction();
         final long eventTime = me.getEventTime();
         final int index = me.getActionIndex();
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index e926fa2..6056af9 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -52,7 +52,9 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.KeyboardView;
 import com.android.inputmethod.keyboard.MoreKeysPanel;
 import com.android.inputmethod.keyboard.PointerTracker;
@@ -83,7 +85,7 @@
     static final boolean DBG = LatinImeLogger.sDBG;
 
     private final ViewGroup mSuggestionsStrip;
-    private KeyboardView mKeyboardView;
+    KeyboardView mKeyboardView;
 
     private final View mMoreSuggestionsContainer;
     private final MoreSuggestionsView mMoreSuggestionsView;
@@ -97,8 +99,8 @@
     private final PopupWindow mPreviewPopup;
     private final TextView mPreviewText;
 
-    private Listener mListener;
-    private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
+    Listener mListener;
+    SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
 
     private final SuggestionStripViewParams mParams;
     private static final float MIN_TEXT_XSCALE = 0.70f;
@@ -108,12 +110,12 @@
     private static final class UiHandler extends StaticInnerHandlerWrapper<SuggestionStripView> {
         private static final int MSG_HIDE_PREVIEW = 0;
 
-        public UiHandler(SuggestionStripView outerInstance) {
+        public UiHandler(final SuggestionStripView outerInstance) {
             super(outerInstance);
         }
 
         @Override
-        public void dispatchMessage(Message msg) {
+        public void dispatchMessage(final Message msg) {
             final SuggestionStripView suggestionStripView = getOuterInstance();
             switch (msg.what) {
             case MSG_HIDE_PREVIEW:
@@ -177,8 +179,9 @@
         private final TextView mLeftwardsArrowView;
         private final TextView mHintToSaveView;
 
-        public SuggestionStripViewParams(Context context, AttributeSet attrs, int defStyle,
-                ArrayList<TextView> words, ArrayList<View> dividers, ArrayList<TextView> infos) {
+        public SuggestionStripViewParams(final Context context, final AttributeSet attrs,
+                final int defStyle, final ArrayList<TextView> words, final ArrayList<View> dividers,
+                final ArrayList<TextView> infos) {
             mWords = words;
             mDividers = dividers;
             mInfos = infos;
@@ -250,7 +253,7 @@
             return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap;
         }
 
-        public int setMoreSuggestionsHeight(int remainingHeight) {
+        public int setMoreSuggestionsHeight(final int remainingHeight) {
             final int currentHeight = getMoreSuggestionsHeight();
             if (currentHeight <= remainingHeight) {
                 return currentHeight;
@@ -262,7 +265,8 @@
             return newHeight;
         }
 
-        private static Drawable getMoreSuggestionsHint(Resources res, float textSize, int color) {
+        private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize,
+                final int color) {
             final Paint paint = new Paint();
             paint.setAntiAlias(true);
             paint.setTextAlign(Align.CENTER);
@@ -279,7 +283,8 @@
             return new BitmapDrawable(res, buffer);
         }
 
-        private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) {
+        private CharSequence getStyledSuggestionWord(final SuggestedWords suggestedWords,
+                final int pos) {
             final CharSequence word = suggestedWords.getWord(pos);
             final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect();
             final boolean isTypedWordValid = pos == 0 && suggestedWords.mTypedWordValid;
@@ -299,7 +304,7 @@
             return spannedWord;
         }
 
-        private int getWordPosition(int index, SuggestedWords suggestedWords) {
+        private int getWordPosition(final int index, final SuggestedWords suggestedWords) {
             // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more
             // suggestions.
             final int centerPos = suggestedWords.willAutoCorrect() ? 1 : 0;
@@ -312,7 +317,8 @@
             }
         }
 
-        private int getSuggestionTextColor(int index, SuggestedWords suggestedWords, int pos) {
+        private int getSuggestionTextColor(final int index, final SuggestedWords suggestedWords,
+                final int pos) {
             // TODO: Need to revisit this logic with bigram suggestions
             final boolean isSuggested = (pos != 0);
 
@@ -355,8 +361,8 @@
             params.gravity = Gravity.CENTER;
         }
 
-        public void layout(SuggestedWords suggestedWords, ViewGroup stripView, ViewGroup placer,
-                int stripWidth) {
+        public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView,
+                final ViewGroup placer, final int stripWidth) {
             if (suggestedWords.mIsPunctuationSuggestions) {
                 layoutPunctuationSuggestions(suggestedWords, stripView);
                 return;
@@ -418,14 +424,14 @@
             }
         }
 
-        private int getSuggestionWidth(int index, int maxWidth) {
+        private int getSuggestionWidth(final int index, final int maxWidth) {
             final int paddings = mPadding * mSuggestionsCountInStrip;
             final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1);
             final int availableWidth = maxWidth - paddings - dividers;
             return (int)(availableWidth * getSuggestionWeight(index));
         }
 
-        private float getSuggestionWeight(int index) {
+        private float getSuggestionWeight(final int index) {
             if (index == mCenterSuggestionIndex) {
                 return mCenterSuggestionWeight;
             } else {
@@ -434,7 +440,7 @@
             }
         }
 
-        private void setupTexts(SuggestedWords suggestedWords, int countInStrip) {
+        private void setupTexts(final SuggestedWords suggestedWords, final int countInStrip) {
             mTexts.clear();
             final int count = Math.min(suggestedWords.size(), countInStrip);
             for (int pos = 0; pos < count; pos++) {
@@ -447,8 +453,8 @@
             }
         }
 
-        private void layoutPunctuationSuggestions(SuggestedWords suggestedWords,
-                ViewGroup stripView) {
+        private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords,
+                final ViewGroup stripView) {
             final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP);
             for (int index = 0; index < countInStrip; index++) {
                 if (index != 0) {
@@ -469,8 +475,8 @@
             mMoreSuggestionsAvailable = false;
         }
 
-        public void layoutAddToDictionaryHint(CharSequence word, ViewGroup stripView,
-                int stripWidth, CharSequence hintText, OnClickListener listener) {
+        public void layoutAddToDictionaryHint(final CharSequence word, final ViewGroup stripView,
+                final int stripWidth, final CharSequence hintText, final OnClickListener listener) {
             final int width = stripWidth - mDividerWidth - mPadding * 2;
 
             final TextView wordView = mWordToSaveView;
@@ -511,11 +517,11 @@
             return (CharSequence)mWordToSaveView.getTag();
         }
 
-        public boolean isAddToDictionaryShowing(View v) {
+        public boolean isAddToDictionaryShowing(final View v) {
             return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView;
         }
 
-        private static void setLayoutWeight(View v, float weight, int height) {
+        private static void setLayoutWeight(final View v, final float weight, final int height) {
             final ViewGroup.LayoutParams lp = v.getLayoutParams();
             if (lp instanceof LinearLayout.LayoutParams) {
                 final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
@@ -525,7 +531,8 @@
             }
         }
 
-        private static float getTextScaleX(CharSequence text, int maxWidth, TextPaint paint) {
+        private static float getTextScaleX(final CharSequence text, final int maxWidth,
+                final TextPaint paint) {
             paint.setTextScaleX(1.0f);
             final int width = getTextWidth(text, paint);
             if (width <= maxWidth) {
@@ -534,8 +541,8 @@
             return maxWidth / (float)width;
         }
 
-        private static CharSequence getEllipsizedText(CharSequence text, int maxWidth,
-                TextPaint paint) {
+        private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth,
+                final TextPaint paint) {
             if (text == null) return null;
             paint.setTextScaleX(1.0f);
             final int width = getTextWidth(text, paint);
@@ -556,7 +563,7 @@
             return ellipsized;
         }
 
-        private static int getTextWidth(CharSequence text, TextPaint paint) {
+        private static int getTextWidth(final CharSequence text, final TextPaint paint) {
             if (TextUtils.isEmpty(text)) return 0;
             final Typeface savedTypeface = paint.getTypeface();
             paint.setTypeface(getTextTypeface(text));
@@ -571,7 +578,7 @@
             return width;
         }
 
-        private static Typeface getTextTypeface(CharSequence text) {
+        private static Typeface getTextTypeface(final CharSequence text) {
             if (!(text instanceof SpannableString))
                 return Typeface.DEFAULT;
 
@@ -593,11 +600,12 @@
      * @param context
      * @param attrs
      */
-    public SuggestionStripView(Context context, AttributeSet attrs) {
+    public SuggestionStripView(final Context context, final AttributeSet attrs) {
         this(context, attrs, R.attr.suggestionStripViewStyle);
     }
 
-    public SuggestionStripView(Context context, AttributeSet attrs, int defStyle) {
+    public SuggestionStripView(final Context context, final AttributeSet attrs,
+            final int defStyle) {
         super(context, attrs, defStyle);
 
         final LayoutInflater inflater = LayoutInflater.from(context);
@@ -658,15 +666,12 @@
      * A connection back to the input method.
      * @param listener
      */
-    public void setListener(Listener listener, View inputView) {
+    public void setListener(final Listener listener, final View inputView) {
         mListener = listener;
         mKeyboardView = (KeyboardView)inputView.findViewById(R.id.keyboard_view);
     }
 
-    public void setSuggestions(SuggestedWords suggestedWords) {
-        if (suggestedWords == null)
-            return;
-
+    public void setSuggestions(final SuggestedWords suggestedWords) {
         clear();
         mSuggestedWords = suggestedWords;
         mParams.layout(mSuggestedWords, mSuggestionsStrip, this, getWidth());
@@ -675,7 +680,7 @@
         }
     }
 
-    public int setMoreSuggestionsHeight(int remainingHeight) {
+    public int setMoreSuggestionsHeight(final int remainingHeight) {
         return mParams.setMoreSuggestionsHeight(remainingHeight);
     }
 
@@ -684,7 +689,7 @@
                 && mParams.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0));
     }
 
-    public void showAddToDictionaryHint(CharSequence word, CharSequence hintText) {
+    public void showAddToDictionaryHint(final CharSequence word, final CharSequence hintText) {
         clear();
         mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText, this);
     }
@@ -708,14 +713,14 @@
         dismissMoreSuggestions();
     }
 
-    private void hidePreview() {
+    void hidePreview() {
         mPreviewPopup.dismiss();
     }
 
     private final KeyboardActionListener mMoreSuggestionsListener =
             new KeyboardActionListener.Adapter() {
         @Override
-        public boolean onCustomRequest(int requestCode) {
+        public boolean onCustomRequest(final int requestCode) {
             final int index = requestCode;
             final CharSequence word = mSuggestedWords.getWord(index);
             mListener.pickSuggestionManually(index, word);
@@ -737,7 +742,7 @@
         }
     };
 
-    private boolean dismissMoreSuggestions() {
+    boolean dismissMoreSuggestions() {
         if (mMoreSuggestionsWindow.isShowing()) {
             mMoreSuggestionsWindow.dismiss();
             return true;
@@ -746,41 +751,42 @@
     }
 
     @Override
-    public boolean onLongClick(View view) {
+    public boolean onLongClick(final View view) {
         return showMoreSuggestions();
     }
 
-    private boolean showMoreSuggestions() {
-        final SuggestionStripViewParams params = mParams;
-        if (params.mMoreSuggestionsAvailable) {
-            final int stripWidth = getWidth();
-            final View container = mMoreSuggestionsContainer;
-            final int maxWidth = stripWidth - container.getPaddingLeft()
-                    - container.getPaddingRight();
-            final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder;
-            builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth,
-                    (int)(maxWidth * params.mMinMoreSuggestionsWidth),
-                    params.getMaxMoreSuggestionsRow());
-            mMoreSuggestionsView.setKeyboard(builder.build());
-            container.measure(
-                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-
-            final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView;
-            final int pointX = stripWidth / 2;
-            final int pointY = -params.mMoreSuggestionsBottomGap;
-            moreKeysPanel.showMoreKeysPanel(
-                    this, mMoreSuggestionsController, pointX, pointY,
-                    mMoreSuggestionsWindow, mMoreSuggestionsListener);
-            mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING;
-            mOriginX = mLastX;
-            mOriginY = mLastY;
-            mKeyboardView.dimEntireKeyboard(true);
-            for (int i = 0; i < params.mSuggestionsCountInStrip; i++) {
-                mWords.get(i).setPressed(false);
-            }
-            return true;
+    boolean showMoreSuggestions() {
+        final Keyboard parentKeyboard = KeyboardSwitcher.getInstance().getKeyboard();
+        if (parentKeyboard == null) {
+            return false;
         }
-        return false;
+        final SuggestionStripViewParams params = mParams;
+        if (!params.mMoreSuggestionsAvailable) {
+            return false;
+        }
+        final int stripWidth = getWidth();
+        final View container = mMoreSuggestionsContainer;
+        final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight();
+        final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder;
+        builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth,
+                (int)(maxWidth * params.mMinMoreSuggestionsWidth),
+                params.getMaxMoreSuggestionsRow(), parentKeyboard);
+        mMoreSuggestionsView.setKeyboard(builder.build());
+        container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+        final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView;
+        final int pointX = stripWidth / 2;
+        final int pointY = -params.mMoreSuggestionsBottomGap;
+        moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY,
+                mMoreSuggestionsWindow, mMoreSuggestionsListener);
+        mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING;
+        mOriginX = mLastX;
+        mOriginY = mLastY;
+        mKeyboardView.dimEntireKeyboard(true);
+        for (int i = 0; i < params.mSuggestionsCountInStrip; i++) {
+            mWords.get(i).setPressed(false);
+        }
+        return true;
     }
 
     // Working variables for onLongClick and dispatchTouchEvent.
@@ -807,7 +813,7 @@
     };
 
     @Override
-    public boolean dispatchTouchEvent(MotionEvent me) {
+    public boolean dispatchTouchEvent(final MotionEvent me) {
         if (!mMoreSuggestionsWindow.isShowing()
                 || mMoreSuggestionsMode == MORE_SUGGESTIONS_IN_MODAL_MODE) {
             mLastX = (int)me.getX();
@@ -849,7 +855,7 @@
     }
 
     @Override
-    public void onClick(View view) {
+    public void onClick(final View view) {
         if (mParams.isAddToDictionaryShowing(view)) {
             mListener.addWordToUserDictionary(mParams.getAddToDictionaryWord().toString());
             clear();
diff --git a/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java
index 70f916c..66c9e18 100644
--- a/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java
@@ -188,7 +188,7 @@
 
         File file = null;
         try {
-            file = File.createTempFile("testReadAndWrite", ".dict");
+            file = File.createTempFile("testReadAndWrite", ".dict", getContext().getCacheDir());
         } catch (IOException e) {
             Log.d(TAG, "IOException while creating a temporary file: " + e);
         }
diff --git a/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java
index f2a17d2..7536f64 100644
--- a/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java
@@ -23,6 +23,8 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
@@ -76,34 +78,43 @@
     }
 
     public void testRandomWords() {
-        Log.d(TAG, "This test can be used for profiling.");
-        Log.d(TAG, "Usage: please set UserHisotoryDictionary.PROFILE_SAVE_RESTORE to true.");
-        final int numberOfWords = 1000;
-        final Random random = new Random(123456);
-        List<String> words = generateWords(numberOfWords, random);
-
-        final String locale = "testRandomWords";
-        final UserHistoryDictionary dict = UserHistoryDictionary.getInstance(getContext(),
-                locale, mPrefs);
-        dict.isTest = true;
-
-        addToDict(dict, words);
-
+        File dictFile = null;
         try {
-            Log.d(TAG, "waiting for adding the word ...");
-            Thread.sleep(2000);
-        } catch (InterruptedException e) {
-            Log.d(TAG, "InterruptedException: " + e);
-        }
+            Log.d(TAG, "This test can be used for profiling.");
+            Log.d(TAG, "Usage: please set UserHisotoryDictionary.PROFILE_SAVE_RESTORE to true.");
+            final int numberOfWords = 1000;
+            final Random random = new Random(123456);
+            List<String> words = generateWords(numberOfWords, random);
 
-        // write to file
-        dict.close();
+            final String locale = "testRandomWords";
+            final String fileName = "UserHistoryDictionary." + locale + ".dict";
+            dictFile = new File(getContext().getFilesDir(), fileName);
+            final UserHistoryDictionary dict = UserHistoryDictionary.getInstance(getContext(),
+                    locale, mPrefs);
+            dict.isTest = true;
 
-        try {
-            Log.d(TAG, "waiting for writing ...");
-            Thread.sleep(5000);
-        } catch (InterruptedException e) {
-            Log.d(TAG, "InterruptedException: " + e);
+            addToDict(dict, words);
+
+            try {
+                Log.d(TAG, "waiting for adding the word ...");
+                Thread.sleep(2000);
+            } catch (InterruptedException e) {
+                Log.d(TAG, "InterruptedException: " + e);
+            }
+
+            // write to file
+            dict.close();
+
+            try {
+                Log.d(TAG, "waiting for writing ...");
+                Thread.sleep(5000);
+            } catch (InterruptedException e) {
+                Log.d(TAG, "InterruptedException: " + e);
+            }
+        } finally {
+            if (dictFile != null) {
+                dictFile.delete();
+            }
         }
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
index 2f95431..b6f6fea 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
@@ -271,7 +271,7 @@
             final String message) {
         File file = null;
         try {
-            file = File.createTempFile("runReadAndWrite", ".dict");
+            file = File.createTempFile("runReadAndWrite", ".dict", getContext().getCacheDir());
         } catch (IOException e) {
             Log.e(TAG, "IOException: " + e);
         }
@@ -412,7 +412,7 @@
             final FormatSpec.FormatOptions formatOptions, final String message) {
         File file = null;
         try {
-            file = File.createTempFile("runReadUnigrams", ".dict");
+            file = File.createTempFile("runReadUnigrams", ".dict", getContext().getCacheDir());
         } catch (IOException e) {
             Log.e(TAG, "IOException: " + e);
         }
@@ -510,7 +510,8 @@
     public void testGetTerminalPosition() {
         File file = null;
         try {
-            file = File.createTempFile("testGetTerminalPosition", ".dict");
+            file = File.createTempFile("testGetTerminalPosition", ".dict",
+                    getContext().getCacheDir());
         } catch (IOException e) {
             // do nothing
         }
@@ -561,7 +562,7 @@
     public void testDeleteWord() {
         File file = null;
         try {
-            file = File.createTempFile("testDeleteWord", ".dict");
+            file = File.createTempFile("testDeleteWord", ".dict", getContext().getCacheDir());
         } catch (IOException e) {
             // do nothing
         }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
new file mode 100644
index 0000000..3185168
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.makedict;
+
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.ByteBufferWrapper;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.CharEncoding;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
+import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Random;
+
+public class BinaryDictIOUtilsTests  extends AndroidTestCase {
+    private static final String TAG = BinaryDictIOUtilsTests.class.getSimpleName();
+    private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
+            new FormatSpec.FormatOptions(3, true);
+    private static final int MAX_UNIGRAMS = 1500;
+
+    private static final ArrayList<String> sWords = CollectionUtils.newArrayList();
+
+    private static final String[] CHARACTERS = {
+        "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
+        "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
+        "\u00FC" /* ü */, "\u00E2" /* â */, "\u00F1" /* ñ */, // accented characters
+        "\u4E9C" /* 亜 */, "\u4F0A" /* 伊 */, "\u5B87" /* 宇 */, // kanji
+        "\uD841\uDE28" /* 𠘨 */, "\uD840\uDC0B" /* 𠀋 */, "\uD861\uDeD7" /* 𨛗 */ // surrogate pair
+    };
+
+    public BinaryDictIOUtilsTests() {
+        super();
+        final Random random = new Random(123456);
+        sWords.clear();
+        for (int i = 0; i < MAX_UNIGRAMS; ++i) {
+            sWords.add(generateWord(random.nextInt()));
+        }
+    }
+
+    // Utilities for test
+    private String generateWord(final int value) {
+        final int lengthOfChars = CHARACTERS.length;
+        StringBuilder builder = new StringBuilder("");
+        long lvalue = Math.abs((long)value);
+        while (lvalue > 0) {
+            builder.append(CHARACTERS[(int)(lvalue % lengthOfChars)]);
+            lvalue /= lengthOfChars;
+        }
+        if (builder.toString().equals("")) return "a";
+        return builder.toString();
+    }
+
+    private static void printCharGroup(final CharGroupInfo info) {
+        Log.d(TAG, "    CharGroup at " + info.mOriginalAddress);
+        Log.d(TAG, "        flags = " + info.mFlags);
+        Log.d(TAG, "        parentAddress = " + info.mParentAddress);
+        Log.d(TAG, "        characters = " + new String(info.mCharacters, 0,
+                info.mCharacters.length));
+        if (info.mFrequency != -1) Log.d(TAG, "        frequency = " + info.mFrequency);
+        if (info.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) {
+            Log.d(TAG, "        children address = no children address");
+        } else {
+            Log.d(TAG, "        children address = " + info.mChildrenAddress);
+        }
+        if (info.mShortcutTargets != null) {
+            for (final WeightedString ws : info.mShortcutTargets) {
+                Log.d(TAG, "        shortcuts = " + ws.mWord);
+            }
+        }
+        if (info.mBigrams != null) {
+            for (final PendingAttribute attr : info.mBigrams) {
+                Log.d(TAG, "        bigram = " + attr.mAddress);
+            }
+        }
+        Log.d(TAG, "    end address = " + info.mEndAddress);
+    }
+
+    private static void printNode(final FusionDictionaryBufferInterface buffer,
+            final FormatSpec.FormatOptions formatOptions) {
+        Log.d(TAG, "Node at " + buffer.position());
+        final int count = BinaryDictInputOutput.readCharGroupCount(buffer);
+        Log.d(TAG, "    charGroupCount = " + count);
+        for (int i = 0; i < count; ++i) {
+            final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer,
+                    buffer.position(), formatOptions);
+            printCharGroup(currentInfo);
+        }
+        if (formatOptions.mSupportsDynamicUpdate) {
+            final int forwardLinkAddress = buffer.readUnsignedInt24();
+            Log.d(TAG, "    forwardLinkAddress = " + forwardLinkAddress);
+        }
+    }
+
+    private static void printBinaryFile(final FusionDictionaryBufferInterface buffer)
+            throws IOException, UnsupportedFormatException {
+        FileHeader header = BinaryDictInputOutput.readHeader(buffer);
+        while (buffer.position() < buffer.limit()) {
+            printNode(buffer, header.mFormatOptions);
+        }
+    }
+
+    private int getWordPosition(final File file, final String word) {
+        int position = FormatSpec.NOT_VALID_WORD;
+        FileInputStream inStream = null;
+        try {
+            inStream = new FileInputStream(file);
+            final FusionDictionaryBufferInterface buffer = new ByteBufferWrapper(
+                    inStream.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length()));
+            position = BinaryDictIOUtils.getTerminalPosition(buffer, word);
+        } catch (IOException e) {
+        } catch (UnsupportedFormatException e) {
+        } finally {
+            if (inStream != null) {
+                try {
+                    inStream.close();
+                } catch (IOException e) {
+                    // do nothing
+                }
+            }
+        }
+        return position;
+    }
+
+    private CharGroupInfo findWordFromFile(final File file, final String word) {
+        FileInputStream inStream = null;
+        CharGroupInfo info = null;
+        try {
+            inStream = new FileInputStream(file);
+            final FusionDictionaryBufferInterface buffer = new ByteBufferWrapper(
+                    inStream.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length()));
+            info = BinaryDictIOUtils.findWordFromBuffer(buffer, word);
+        } catch (IOException e) {
+        } catch (UnsupportedFormatException e) {
+        } finally {
+            if (inStream != null) {
+                try {
+                    inStream.close();
+                } catch (IOException e) {
+                    // do nothing
+                }
+            }
+        }
+        return info;
+    }
+
+    // return amount of time to insert a word
+    private long insertAndCheckWord(final File file, final String word, final int frequency,
+            final boolean exist, final ArrayList<WeightedString> bigrams,
+            final ArrayList<WeightedString> shortcuts) {
+        RandomAccessFile raFile = null;
+        BufferedOutputStream outStream = null;
+        FusionDictionaryBufferInterface buffer = null;
+        long amountOfTime = -1;
+        try {
+            raFile = new RandomAccessFile(file, "rw");
+            buffer = new ByteBufferWrapper(raFile.getChannel().map(
+                    FileChannel.MapMode.READ_WRITE, 0, file.length()));
+            outStream = new BufferedOutputStream(new FileOutputStream(file, true));
+
+            if (!exist) {
+                assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word));
+            }
+            final long now = System.nanoTime();
+            BinaryDictIOUtils.insertWord(buffer, outStream, word, frequency, bigrams, shortcuts,
+                    false, false);
+            amountOfTime = System.nanoTime() - now;
+            outStream.flush();
+            MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word));
+            outStream.close();
+            raFile.close();
+        } catch (IOException e) {
+        } catch (UnsupportedFormatException e) {
+        } finally {
+            if (outStream != null) {
+                try {
+                    outStream.close();
+                } catch (IOException e) {
+                    // do nothing
+                }
+            }
+            if (raFile != null) {
+                try {
+                    raFile.close();
+                } catch (IOException e) {
+                    // do nothing
+                }
+            }
+        }
+        return amountOfTime;
+    }
+
+    private void deleteWord(final File file, final String word) {
+        RandomAccessFile raFile = null;
+        FusionDictionaryBufferInterface buffer = null;
+        try {
+            raFile = new RandomAccessFile(file, "rw");
+            buffer = new ByteBufferWrapper(raFile.getChannel().map(
+                    FileChannel.MapMode.READ_WRITE, 0, file.length()));
+            BinaryDictIOUtils.deleteWord(buffer, word);
+        } catch (IOException e) {
+        } catch (UnsupportedFormatException e) {
+        } finally {
+            if (raFile != null) {
+                try {
+                    raFile.close();
+                } catch (IOException e) {
+                    // do nothing
+                }
+            }
+        }
+    }
+
+    private void checkReverseLookup(final File file, final String word, final int position) {
+        FileInputStream inStream = null;
+        try {
+            inStream = new FileInputStream(file);
+            final FusionDictionaryBufferInterface buffer = new ByteBufferWrapper(
+                    inStream.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length()));
+            final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
+            assertEquals(word, BinaryDictInputOutput.getWordAtAddress(buffer, header.mHeaderSize,
+                    position - header.mHeaderSize, header.mFormatOptions));
+        } catch (IOException e) {
+        } catch (UnsupportedFormatException e) {
+        } finally {
+            if (inStream != null) {
+                try {
+                    inStream.close();
+                } catch (IOException e) {
+                    // do nothing
+                }
+            }
+        }
+    }
+
+    public void testInsertWord() {
+        File file = null;
+        try {
+            file = File.createTempFile("testInsertWord", ".dict", getContext().getCacheDir());
+        } catch (IOException e) {
+            fail("IOException while creating temporary file: " + e);
+        }
+
+        // set an initial dictionary.
+        final FusionDictionary dict = new FusionDictionary(new Node(),
+                new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false));
+        dict.add("abcd", 10, null, false);
+
+        try {
+            final FileOutputStream out = new FileOutputStream(file);
+            BinaryDictInputOutput.writeDictionaryBinary(out, dict, FORMAT_OPTIONS);
+            out.close();
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+
+        MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "abcd"));
+        insertAndCheckWord(file, "abcde", 10, false, null, null);
+
+        insertAndCheckWord(file, "abcdefghijklmn", 10, false, null, null);
+        checkReverseLookup(file, "abcdefghijklmn", getWordPosition(file, "abcdefghijklmn"));
+
+        insertAndCheckWord(file, "abcdabcd", 10, false, null, null);
+        checkReverseLookup(file, "abcdabcd", getWordPosition(file, "abcdabcd"));
+
+        // update the existing word.
+        insertAndCheckWord(file, "abcdabcd", 15, true, null, null);
+
+        // split 1
+        insertAndCheckWord(file, "ab", 20, false, null, null);
+
+        // split 2
+        insertAndCheckWord(file, "ami", 30, false, null, null);
+
+        deleteWord(file, "ami");
+        assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "ami"));
+
+        insertAndCheckWord(file, "abcdabfg", 30, false, null, null);
+
+        deleteWord(file, "abcd");
+        assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "abcd"));
+    }
+
+    public void testInsertWordWithBigrams() {
+        File file = null;
+        try {
+            file = File.createTempFile("testInsertWordWithBigrams", ".dict",
+                    getContext().getCacheDir());
+        } catch (IOException e) {
+            fail("IOException while creating temporary file: " + e);
+        }
+
+        // set an initial dictionary.
+        final FusionDictionary dict = new FusionDictionary(new Node(),
+                new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false));
+        dict.add("abcd", 10, null, false);
+        dict.add("efgh", 15, null, false);
+
+        try {
+            final FileOutputStream out = new FileOutputStream(file);
+            BinaryDictInputOutput.writeDictionaryBinary(out, dict, FORMAT_OPTIONS);
+            out.close();
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        } catch (UnsupportedFormatException e) {
+            fail("UnsupportedFormatException while writing an initial dictionary : " + e);
+        }
+
+        final ArrayList<WeightedString> banana = new ArrayList<WeightedString>();
+        banana.add(new WeightedString("banana", 10));
+
+        insertAndCheckWord(file, "banana", 0, false, null, null);
+        insertAndCheckWord(file, "recursive", 60, true, banana, null);
+
+        final CharGroupInfo info = findWordFromFile(file, "recursive");
+        int bananaPos = getWordPosition(file, "banana");
+        assertNotNull(info.mBigrams);
+        assertEquals(info.mBigrams.size(), 1);
+        assertEquals(info.mBigrams.get(0).mAddress, bananaPos);
+    }
+
+    public void testRandomWords() {
+        File file = null;
+        try {
+            file = File.createTempFile("testRandomWord", ".dict", getContext().getCacheDir());
+        } catch (IOException e) {
+        }
+        assertNotNull(file);
+
+        // set an initial dictionary.
+        final FusionDictionary dict = new FusionDictionary(new Node(),
+                new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
+                        false));
+        dict.add("initial", 10, null, false);
+
+        try {
+            final FileOutputStream out = new FileOutputStream(file);
+            BinaryDictInputOutput.writeDictionaryBinary(out, dict, FORMAT_OPTIONS);
+            out.close();
+        } catch (IOException e) {
+            assertTrue(false);
+        } catch (UnsupportedFormatException e) {
+            assertTrue(false);
+        }
+
+        long maxTimeToInsert = 0, sum = 0;
+        long minTimeToInsert = 100000000; // 1000000000 is an upper bound for minTimeToInsert.
+        int cnt = 0;
+        for (final String word : sWords) {
+            final long diff = insertAndCheckWord(file, word,
+                    cnt % FormatSpec.MAX_TERMINAL_FREQUENCY, false, null, null);
+            maxTimeToInsert = Math.max(maxTimeToInsert, diff);
+            minTimeToInsert = Math.min(minTimeToInsert, diff);
+            sum += diff;
+            cnt++;
+        }
+        cnt = 0;
+        for (final String word : sWords) {
+            MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word));
+        }
+
+        Log.d(TAG, "max = " + ((double)maxTimeToInsert/1000000) + " ms.");
+        Log.d(TAG, "min = " + ((double)minTimeToInsert/1000000) + " ms.");
+        Log.d(TAG, "avg = " + ((double)sum/MAX_UNIGRAMS/1000000) + " ms.");
+    }
+}