Merge "The processNextKey becomes to use actual average duration." into jb-mr1-dev
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index e601397..7e8c77e 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -90,7 +90,8 @@
         <!-- Interval of updating gesture preview trail in millisecond. -->
         <attr name="gesturePreviewTrailUpdateInterval" format="integer" />
         <attr name="gesturePreviewTrailColor" format="color" />
-        <attr name="gesturePreviewTrailWidth" format="dimension" />
+        <attr name="gesturePreviewTrailStartWidth" format="dimension" />
+        <attr name="gesturePreviewTrailEndWidth" format="dimension" />
     </declare-styleable>
 
     <declare-styleable name="MainKeyboardView">
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index 9ccaaaf..d92a715 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -100,7 +100,8 @@
     <fraction name="center_suggestion_percentile">36%</fraction>
 
     <!-- Gesture preview trail parameters -->
-    <dimen name="gesture_preview_trail_width">2.5dp</dimen>
+    <dimen name="gesture_preview_trail_start_width">18.0dp</dimen>
+    <dimen name="gesture_preview_trail_end_width">2.5dp</dimen>
     <!-- Gesture floating preview text parameters -->
     <dimen name="gesture_floating_preview_text_size">24dp</dimen>
     <dimen name="gesture_floating_preview_text_offset">73dp</dimen>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 94fdbeb..ed92440 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -77,7 +77,8 @@
         <item name="gesturePreviewTrailFadeoutDuration">@integer/config_gesture_preview_trail_fadeout_duration</item>
         <item name="gesturePreviewTrailUpdateInterval">@integer/config_gesture_preview_trail_update_interval</item>
         <item name="gesturePreviewTrailColor">@android:color/holo_blue_light</item>
-        <item name="gesturePreviewTrailWidth">@dimen/gesture_preview_trail_width</item>
+        <item name="gesturePreviewTrailStartWidth">@dimen/gesture_preview_trail_start_width</item>
+        <item name="gesturePreviewTrailEndWidth">@dimen/gesture_preview_trail_end_width</item>
         <!-- Common attributes of MainKeyboardView -->
         <item name="keyHysteresisDistance">@dimen/config_key_hysteresis_distance</item>
         <item name="touchNoiseThresholdTime">@integer/config_touch_noise_threshold_time</item>
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
index e814d80..7442b7f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -17,16 +17,18 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Xfermode;
 import android.os.SystemClock;
 
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.ResizableIntArray;
 
-class GesturePreviewTrail {
+final class GesturePreviewTrail {
     private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewTrail.PREVIEW_CAPACITY;
 
-    private final GesturePreviewTrailParams mPreviewParams;
     private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
@@ -34,28 +36,39 @@
     private long mCurrentDownTime;
     private int mTrailStartIndex;
 
+    private final static Xfermode PORTER_DUFF_MODE_SRC =
+            new PorterDuffXfermode(PorterDuff.Mode.SRC);
+
     // Use this value as imaginary zero because x-coordinates may be zero.
     private static final int DOWN_EVENT_MARKER = -128;
 
-    static class GesturePreviewTrailParams {
+    static final class Params {
+        public final int mTrailColor;
+        public final float mTrailStartWidth;
+        public final float mTrailEndWidth;
         public final int mFadeoutStartDelay;
         public final int mFadeoutDuration;
         public final int mUpdateInterval;
 
-        public GesturePreviewTrailParams(final TypedArray keyboardViewAttr) {
+        public final int mTrailLingerDuration;
+
+        public Params(final TypedArray keyboardViewAttr) {
+            mTrailColor = keyboardViewAttr.getColor(
+                    R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
+            mTrailStartWidth = keyboardViewAttr.getDimension(
+                    R.styleable.KeyboardView_gesturePreviewTrailStartWidth, 0.0f);
+            mTrailEndWidth = keyboardViewAttr.getDimension(
+                    R.styleable.KeyboardView_gesturePreviewTrailEndWidth, 0.0f);
             mFadeoutStartDelay = keyboardViewAttr.getInt(
                     R.styleable.KeyboardView_gesturePreviewTrailFadeoutStartDelay, 0);
             mFadeoutDuration = keyboardViewAttr.getInt(
                     R.styleable.KeyboardView_gesturePreviewTrailFadeoutDuration, 0);
+            mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
             mUpdateInterval = keyboardViewAttr.getInt(
                     R.styleable.KeyboardView_gesturePreviewTrailUpdateInterval, 0);
         }
     }
 
-    public GesturePreviewTrail(final GesturePreviewTrailParams params) {
-        mPreviewParams = params;
-    }
-
     private static int markAsDownEvent(final int xCoord) {
         return DOWN_EVENT_MARKER - xCoord;
     }
@@ -94,23 +107,30 @@
         }
     }
 
-    private int getAlpha(final int elapsedTime) {
-        if (elapsedTime < mPreviewParams.mFadeoutStartDelay) {
+    private static int getAlpha(final int elapsedTime, final Params params) {
+        if (elapsedTime < params.mFadeoutStartDelay) {
             return Constants.Color.ALPHA_OPAQUE;
         }
         final int decreasingAlpha = Constants.Color.ALPHA_OPAQUE
-                * (elapsedTime - mPreviewParams.mFadeoutStartDelay)
-                / mPreviewParams.mFadeoutDuration;
+                * (elapsedTime - params.mFadeoutStartDelay)
+                / params.mFadeoutDuration;
         return Constants.Color.ALPHA_OPAQUE - decreasingAlpha;
     }
 
+    private static float getWidth(final int elapsedTime, final Params params) {
+        return Math.max((params.mTrailLingerDuration - elapsedTime)
+                * (params.mTrailStartWidth - params.mTrailEndWidth)
+                / params.mTrailLingerDuration, 0.0f);
+    }
+
     /**
      * Draw gesture preview trail
      * @param canvas The canvas to draw the gesture preview trail
      * @param paint The paint object to be used to draw the gesture preview trail
+     * @param params The drawing parameters of gesture preview trail
      * @return true if some gesture preview trails remain to be drawn
      */
-    public boolean drawGestureTrail(final Canvas canvas, final Paint paint) {
+    public boolean drawGestureTrail(final Canvas canvas, final Paint paint, final Params params) {
         final int trailSize = mEventTimes.getLength();
         if (trailSize == 0) {
             return false;
@@ -120,13 +140,11 @@
         final int[] xCoords = mXCoordinates.getPrimitiveArray();
         final int[] yCoords = mYCoordinates.getPrimitiveArray();
         final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentDownTime);
-        final int lingeringDuration = mPreviewParams.mFadeoutStartDelay
-                + mPreviewParams.mFadeoutDuration;
         int startIndex;
         for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) {
             final int elapsedTime = sinceDown - eventTimes[startIndex];
             // Skip too old trail points.
-            if (elapsedTime < lingeringDuration) {
+            if (elapsedTime < params.mTrailLingerDuration) {
                 break;
             }
         }
@@ -135,13 +153,20 @@
         if (startIndex < trailSize) {
             int lastX = getXCoordValue(xCoords[startIndex]);
             int lastY = yCoords[startIndex];
+            paint.setColor(params.mTrailColor);
+            paint.setStyle(Paint.Style.STROKE);
+            paint.setStrokeCap(Paint.Cap.ROUND);
+            paint.setXfermode(PORTER_DUFF_MODE_SRC);
             for (int i = startIndex + 1; i < trailSize - 1; i++) {
                 final int x = xCoords[i];
                 final int y = yCoords[i];
                 final int elapsedTime = sinceDown - eventTimes[i];
                 // Draw trail line only when the current point isn't a down point.
                 if (!isDownEventXCoord(x)) {
-                    paint.setAlpha(getAlpha(elapsedTime));
+                    final int alpha = getAlpha(elapsedTime, params);
+                    paint.setAlpha(alpha);
+                    final float width = getWidth(elapsedTime, params);
+                    paint.setStrokeWidth(width);
                     canvas.drawLine(lastX, lastY, x, y, paint);
                 }
                 lastX = getXCoordValue(x);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index 641fadf..8dde4d6 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -21,6 +21,8 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Paint.Align;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Message;
@@ -30,14 +32,12 @@
 import android.widget.RelativeLayout;
 
 import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.GesturePreviewTrailParams;
+import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.Params;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
 
 public class PreviewPlacerView extends RelativeLayout {
-    private final Paint mGesturePaint;
-    private final Paint mTextPaint;
     private final int mGestureFloatingPreviewTextColor;
     private final int mGestureFloatingPreviewTextOffset;
     private final int mGestureFloatingPreviewColor;
@@ -51,8 +51,11 @@
 
     private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
             CollectionUtils.newSparseArray();
-    private final GesturePreviewTrailParams mGesturePreviewTrailParams;
+    private final Params mGesturePreviewTrailParams;
+    private final Paint mGesturePaint;
+    private boolean mDrawsGesturePreviewTrail;
 
+    private final Paint mTextPaint;
     private String mGestureFloatingPreviewText;
     private final int mGestureFloatingPreviewTextHeight;
     // {@link RectF} is needed for {@link Canvas#drawRoundRect(RectF, float, float, Paint)}.
@@ -60,8 +63,6 @@
     private int mLastPointerX;
     private int mLastPointerY;
     private static final char[] TEXT_HEIGHT_REFERENCE_CHAR = { 'M' };
-
-    private boolean mDrawsGesturePreviewTrail;
     private boolean mDrawsGestureFloatingPreviewText;
 
     private final DrawingHandler mDrawingHandler;
@@ -70,10 +71,10 @@
         private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0;
         private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1;
 
-        private final GesturePreviewTrailParams mGesturePreviewTrailParams;
+        private final Params mGesturePreviewTrailParams;
 
         public DrawingHandler(final PreviewPlacerView outerInstance,
-                final GesturePreviewTrailParams gesturePreviewTrailParams) {
+                final Params gesturePreviewTrailParams) {
             super(outerInstance);
             mGesturePreviewTrailParams = gesturePreviewTrailParams;
         }
@@ -146,21 +147,13 @@
                 R.styleable.KeyboardView_gestureFloatingPreviewRoundRadius, 0.0f);
         mGestureFloatingPreviewTextLingerTimeout = keyboardViewAttr.getInt(
                 R.styleable.KeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
-        final int gesturePreviewTrailColor = keyboardViewAttr.getColor(
-                R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
-        final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize(
-                R.styleable.KeyboardView_gesturePreviewTrailWidth, 0);
-        mGesturePreviewTrailParams = new GesturePreviewTrailParams(keyboardViewAttr);
+        mGesturePreviewTrailParams = new Params(keyboardViewAttr);
         keyboardViewAttr.recycle();
 
         mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
 
         final Paint gesturePaint = new Paint();
         gesturePaint.setAntiAlias(true);
-        gesturePaint.setStyle(Paint.Style.STROKE);
-        gesturePaint.setStrokeJoin(Paint.Join.ROUND);
-        gesturePaint.setColor(gesturePreviewTrailColor);
-        gesturePaint.setStrokeWidth(gesturePreviewTrailWidth);
         mGesturePaint = gesturePaint;
 
         final Paint textPaint = new Paint();
@@ -172,6 +165,10 @@
         final Rect textRect = new Rect();
         textPaint.getTextBounds(TEXT_HEIGHT_REFERENCE_CHAR, 0, 1, textRect);
         mGestureFloatingPreviewTextHeight = textRect.height();
+
+        final Paint layerPaint = new Paint();
+        layerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
+        setLayerType(LAYER_TYPE_HARDWARE, layerPaint);
     }
 
     public void setOrigin(final int x, final int y) {
@@ -190,7 +187,7 @@
         synchronized (mGesturePreviewTrails) {
             trail = mGesturePreviewTrails.get(tracker.mPointerId);
             if (trail == null) {
-                trail = new GesturePreviewTrail(mGesturePreviewTrailParams);
+                trail = new GesturePreviewTrail();
                 mGesturePreviewTrails.put(tracker.mPointerId, trail);
             }
         }
@@ -214,7 +211,8 @@
                 for (int index = 0; index < trailsCount; index++) {
                     final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
                     needsUpdatingGesturePreviewTrail |=
-                            trail.drawGestureTrail(canvas, mGesturePaint);
+                            trail.drawGestureTrail(canvas, mGesturePaint,
+                                    mGesturePreviewTrailParams);
                 }
             }
             if (needsUpdatingGesturePreviewTrail) {
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
index 81d61e9..d6fa661 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
@@ -54,7 +54,7 @@
         private byte[] mBuffer;
         private int mPosition;
 
-        ByteArrayWrapper(final byte[] buffer) {
+        public ByteArrayWrapper(final byte[] buffer) {
             mBuffer = buffer;
             mPosition = 0;
         }
diff --git a/native/jni/src/binary_format.h b/native/jni/src/binary_format.h
index 5d8b2a0..eec52e3 100644
--- a/native/jni/src/binary_format.h
+++ b/native/jni/src/binary_format.h
@@ -360,7 +360,7 @@
     while (true) {
         // If we already traversed the tree further than the word is long, there means
         // there was no match (or we would have found it).
-        if (wordPos > length) return NOT_VALID_WORD;
+        if (wordPos >= length) return NOT_VALID_WORD;
         int charGroupCount = BinaryFormat::getGroupCountAndForwardPointer(root, &pos);
         const int32_t wChar = forceLowerCaseSearch ? toLowerCase(inWord[wordPos]) : inWord[wordPos];
         while (true) {
@@ -383,7 +383,7 @@
                         // character that does not match, as explained above, it means the word is
                         // not in the dictionary (by virtue of this chargroup being the only one to
                         // match the word on the first character, but not matching the whole word).
-                        if (wordPos > length) return NOT_VALID_WORD;
+                        if (wordPos >= length) return NOT_VALID_WORD;
                         if (inWord[wordPos] != character) return NOT_VALID_WORD;
                         character = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
                     }
diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp
index b7e245a..cf806c1 100644
--- a/native/jni/src/unigram_dictionary.cpp
+++ b/native/jni/src/unigram_dictionary.cpp
@@ -451,7 +451,7 @@
         const bool hasAutoCorrectionCandidate, const int currentWordIndex,
         const int inputWordStartPos, const int inputWordLength,
         const int outputWordStartPos, const bool isSpaceProximity, int *freqArray,
-        int*wordLengthArray, unsigned short *outputWord, int *outputWordLength) const {
+        int *wordLengthArray, unsigned short *outputWord, int *outputWordLength) const {
     if (inputWordLength > MULTIPLE_WORDS_SUGGESTION_MAX_WORD_LENGTH) {
         return FLAG_MULTIPLE_SUGGEST_ABORT;
     }
@@ -546,9 +546,9 @@
         freq = score >> (nextWordLength + TWO_WORDS_PLUS_OTHER_ERROR_CORRECTION_DEMOTION_DIVIDER);
     }
     if (DEBUG_DICT) {
-        AKLOGI("Freq(%d): %d, length: %d, input length: %d, input start: %d (%d)"
-                , currentWordIndex, freq, nextWordLength, inputWordLength, inputWordStartPos,
-                wordLengthArray[0]);
+        AKLOGI("Freq(%d): %d, length: %d, input length: %d, input start: %d (%d)",
+                currentWordIndex, freq, nextWordLength, inputWordLength, inputWordStartPos,
+                (currentWordIndex > 0) ? wordLengthArray[0] : 0);
     }
     if (freq <= 0 || nextWordLength <= 0
             || MAX_WORD_LENGTH <= (outputWordStartPos + nextWordLength)) {
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
index 6036562..523287b 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
@@ -17,23 +17,22 @@
 package com.android.inputmethod.latin.makedict;
 
 import com.android.inputmethod.latin.CollectionUtils;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
-import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.UserHistoryDictIOUtils;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
 import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
 import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
-import com.android.inputmethod.latin.makedict.PendingAttribute;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 
 import android.test.AndroidTestCase;
 import android.util.Log;
 import android.util.SparseArray;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -53,16 +52,76 @@
     private static final int BIGRAM_FREQ = 50;
     private static final int TOLERANCE_OF_BIGRAM_FREQ = 5;
 
+    private static final int USE_BYTE_ARRAY = 1;
+    private static final int USE_BYTE_BUFFER = 2;
+
+    private static final List<String> sWords = CollectionUtils.newArrayList();
+    private static final SparseArray<List<Integer>> sEmptyBigrams =
+            CollectionUtils.newSparseArray();
+    private static final SparseArray<List<Integer>> sStarBigrams = CollectionUtils.newSparseArray();
+    private static final SparseArray<List<Integer>> sChainBigrams =
+            CollectionUtils.newSparseArray();
+
     private static final BinaryDictInputOutput.FormatOptions VERSION2 =
             new BinaryDictInputOutput.FormatOptions(2);
 
-    private static final String[] CHARACTERS =
-        {
+    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"
-        };
+    };
+
+    public BinaryDictIOTests() {
+        super();
+
+        final Random random = new Random(123456);
+        sWords.clear();
+        generateWords(MAX_UNIGRAMS, random);
+
+        for (int i = 0; i < sWords.size(); ++i) {
+            sChainBigrams.put(i, new ArrayList<Integer>());
+            if (i > 0) {
+                sChainBigrams.get(i-1).add(i);
+            }
+        }
+
+        sStarBigrams.put(0, new ArrayList<Integer>());
+        for (int i = 1; i < sWords.size(); ++i) {
+            sStarBigrams.get(0).add(i);
+        }
+    }
 
     // Utilities for test
+
+    /**
+     * Makes new buffer according to BUFFER_TYPE.
+     */
+    private FusionDictionaryBufferInterface getBuffer(final File file,final int bufferType) {
+        FileInputStream inStream = null;
+        try {
+            inStream = new FileInputStream(file);
+            if (bufferType == USE_BYTE_ARRAY) {
+                final byte[] array = new byte[(int)file.length()];
+                inStream.read(array);
+                return new UserHistoryDictIOUtils.ByteArrayWrapper(array);
+            } else if (bufferType == USE_BYTE_BUFFER){
+                final ByteBuffer buffer = inStream.getChannel().map(
+                        FileChannel.MapMode.READ_ONLY, 0, file.length());
+                return new BinaryDictInputOutput.ByteBufferWrapper(buffer);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "IOException while making buffer: " + e);
+        } finally {
+            if (inStream != null) {
+                try {
+                    inStream.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "IOException while closing stream: " + e);
+                }
+            }
+        }
+        return null;
+    }
+
     /**
      * Generates a random word.
      */
@@ -77,23 +136,29 @@
         return builder.toString();
     }
 
-    private List<String> generateWords(final int number, final Random random) {
+    private void generateWords(final int number, final Random random) {
         final Set<String> wordSet = CollectionUtils.newHashSet();
         while (wordSet.size() < number) {
             wordSet.add(generateWord(random.nextInt()));
         }
-        return new ArrayList<String>(wordSet);
+        sWords.addAll(wordSet);
     }
 
     /**
      * Adds unigrams to the dictionary.
      */
-    private void addUnigrams(final int number,
-            final FusionDictionary dict,
-            final List<String> words) {
+    private void addUnigrams(final int number, final FusionDictionary dict,
+            final List<String> words, final Map<String, List<String>> shortcutMap) {
         for (int i = 0; i < number; ++i) {
             final String word = words.get(i);
-            dict.add(word, UNIGRAM_FREQ, null, false /* isNotAWord */);
+            final ArrayList<WeightedString> shortcuts = CollectionUtils.newArrayList();
+            if (shortcutMap != null && shortcutMap.containsKey(word)) {
+                for (final String shortcut : shortcutMap.get(word)) {
+                    shortcuts.add(new WeightedString(shortcut, UNIGRAM_FREQ));
+                }
+            }
+            dict.add(word, UNIGRAM_FREQ, (shortcutMap == null) ? null : shortcuts,
+                    false /* isNotAWord */);
         }
     }
 
@@ -130,9 +195,8 @@
         return diff;
     }
 
-    private void checkDictionary(final FusionDictionary dict,
-            final List<String> words,
-            final SparseArray<List<Integer>> bigrams) {
+    private void checkDictionary(final FusionDictionary dict, final List<String> words,
+            final SparseArray<List<Integer>> bigrams, final Map<String, List<String>> shortcutMap) {
         assertNotNull(dict);
 
         // check unigram
@@ -149,94 +213,93 @@
                 assertNotNull(words.get(w1) + "," + words.get(w2), cg.getBigram(words.get(w2)));
             }
         }
+
+        // check shortcut
+        if (shortcutMap != null) {
+            for (final Map.Entry<String, List<String>> entry : shortcutMap.entrySet()) {
+                final CharGroup group = FusionDictionary.findWordInTree(dict.mRoot, entry.getKey());
+                for (final String word : entry.getValue()) {
+                    assertNotNull("shortcut not found: " + entry.getKey() + ", " + word,
+                            group.getShortcut(word));
+                }
+            }
+        }
     }
 
     // Tests for readDictionaryBinary and writeDictionaryBinary
 
     private long timeReadingAndCheckDict(final File file, final List<String> words,
-            final SparseArray<List<Integer>> bigrams) {
-
+            final SparseArray<List<Integer>> bigrams, final Map<String, List<String>> shortcutMap,
+            final int bufferType) {
         long now, diff = -1;
+        final FusionDictionaryBufferInterface buffer = getBuffer(file, bufferType);
+        assertNotNull(buffer);
 
-        FileInputStream inStream = null;
+        FusionDictionary dict = null;
         try {
-            inStream = new FileInputStream(file);
-            final ByteBuffer buffer = inStream.getChannel().map(
-                    FileChannel.MapMode.READ_ONLY, 0, file.length());
-
             now = System.currentTimeMillis();
-
-            final FusionDictionary dict =
-                    BinaryDictInputOutput.readDictionaryBinary(buffer, null);
-
-            diff = System.currentTimeMillis() - now;
-
-            checkDictionary(dict, words, bigrams);
-            return diff;
-
+            dict = BinaryDictInputOutput.readDictionaryBinary(buffer, null);
+            diff  = System.currentTimeMillis() - now;
         } catch (IOException e) {
-            Log.e(TAG, "raise IOException while reading file " + e);
+            Log.e(TAG, "IOException while reading dictionary: " + e);
         } catch (UnsupportedFormatException e) {
-            Log.e(TAG, "Unsupported format: " + e);
-        } finally {
-            if (inStream != null) {
-                try {
-                    inStream.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
+            Log.e(TAG, "Unsupported format: "+ e);
         }
 
+        checkDictionary(dict, words, bigrams, shortcutMap);
         return diff;
     }
 
+    // Tests for readDictionaryBinary and writeDictionaryBinary
     private String runReadAndWrite(final List<String> words,
-            final SparseArray<List<Integer>> bigrams,
-            final String message) {
-        final FusionDictionary dict = new FusionDictionary(new Node(),
-                new FusionDictionary.DictionaryOptions(
-                        new HashMap<String,String>(), false, false));
-
+            final SparseArray<List<Integer>> bigrams, final Map<String, List<String>> shortcuts,
+            final int bufferType, final String message) {
         File file = null;
         try {
             file = File.createTempFile("runReadAndWrite", ".dict");
         } catch (IOException e) {
             Log.e(TAG, "IOException: " + e);
         }
-
         assertNotNull(file);
 
-        addUnigrams(words.size(), dict, words);
+        final FusionDictionary dict = new FusionDictionary(new Node(),
+                new FusionDictionary.DictionaryOptions(
+                        new HashMap<String,String>(), false, false));
+        addUnigrams(words.size(), dict, words, shortcuts);
         addBigrams(dict, words, bigrams);
-        // check original dictionary
-        checkDictionary(dict, words, bigrams);
+        checkDictionary(dict, words, bigrams, shortcuts);
 
         final long write = timeWritingDictToFile(file, dict);
-        final long read = timeReadingAndCheckDict(file, words, bigrams);
+        final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType);
 
-        return "PROF: read=" + read + "ms, write=" + write + "ms    :" + message;
+        return "PROF: read=" + read + "ms, write=" + write + "ms    :" + message +
+                " : buffer type = " + bufferType;
     }
 
-    public void testReadAndWrite() {
-        final List<String> results = new ArrayList<String>();
+    public void testReadAndWriteWithByteBuffer() {
+        final List<String> results = CollectionUtils.newArrayList();
 
-        final Random random = new Random(123456);
-        final List<String> words = generateWords(MAX_UNIGRAMS, random);
-        final SparseArray<List<Integer>> emptyArray = CollectionUtils.newSparseArray();
+        results.add(runReadAndWrite(sWords, sEmptyBigrams, null /* shortcuts */, USE_BYTE_BUFFER,
+                "unigram"));
+        results.add(runReadAndWrite(sWords, sChainBigrams, null /* shortcuts */, USE_BYTE_BUFFER,
+                "chain"));
+        results.add(runReadAndWrite(sWords, sStarBigrams, null /* shortcuts */, USE_BYTE_BUFFER,
+                "star"));
 
-        final SparseArray<List<Integer>> chain = CollectionUtils.newSparseArray();
-        for (int i = 0; i < words.size(); ++i) chain.put(i, new ArrayList<Integer>());
-        for (int i = 1; i < words.size(); ++i) chain.get(i-1).add(i);
+        for (final String result : results) {
+            Log.d(TAG, result);
+        }
+    }
 
-        final SparseArray<List<Integer>> star = CollectionUtils.newSparseArray();
-        final List<Integer> list0 = CollectionUtils.newArrayList();
-        star.put(0, list0);
-        for (int i = 1; i < words.size(); ++i) star.get(0).add(i);
+    public void testReadAndWriteWithByteArray() {
+        final List<String> results = CollectionUtils.newArrayList();
 
-        results.add(runReadAndWrite(words, emptyArray, "only unigram"));
-        results.add(runReadAndWrite(words, chain, "chain"));
-        results.add(runReadAndWrite(words, star, "star"));
+        results.add(runReadAndWrite(sWords, sEmptyBigrams, null /* shortcuts */, USE_BYTE_ARRAY,
+                "unigram"));
+        results.add(runReadAndWrite(sWords, sChainBigrams, null /* shortcuts */, USE_BYTE_ARRAY,
+                "chain"));
+        results.add(runReadAndWrite(sWords, sStarBigrams, null /* shortcuts */, USE_BYTE_ARRAY,
+                "star"));
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -292,7 +355,7 @@
     }
 
     private long timeAndCheckReadUnigramsAndBigramsBinary(final File file, final List<String> words,
-            final SparseArray<List<Integer>> bigrams) {
+            final SparseArray<List<Integer>> bigrams, final int bufferType) {
         FileInputStream inStream = null;
 
         final Map<Integer, String> resultWords = CollectionUtils.newTreeMap();
@@ -301,17 +364,13 @@
         final Map<Integer, Integer> resultFreqs = CollectionUtils.newTreeMap();
 
         long now = -1, diff = -1;
+        final FusionDictionaryBufferInterface buffer = getBuffer(file, bufferType);
+        assertNotNull("Can't get buffer.", buffer);
         try {
-            inStream = new FileInputStream(file);
-            final ByteBuffer buffer = inStream.getChannel().map(
-                    FileChannel.MapMode.READ_ONLY, 0, file.length());
-
             now = System.currentTimeMillis();
-            BinaryDictInputOutput.readUnigramsAndBigramsBinary(
-                    new BinaryDictInputOutput.ByteBufferWrapper(buffer), resultWords, resultFreqs,
+            BinaryDictInputOutput.readUnigramsAndBigramsBinary(buffer, resultWords, resultFreqs,
                     resultBigrams);
             diff = System.currentTimeMillis() - now;
-            checkWordMap(words, bigrams, resultWords, resultFreqs, resultBigrams);
         } catch (IOException e) {
             Log.e(TAG, "IOException " + e);
         } catch (UnsupportedFormatException e) {
@@ -326,50 +385,64 @@
             }
         }
 
+        checkWordMap(words, bigrams, resultWords, resultFreqs, resultBigrams);
         return diff;
     }
 
-    private void runReadUnigramsAndBigramsBinary(final List<String> words,
-            final SparseArray<List<Integer>> bigrams) {
-
-        // making the dictionary from lists of words.
-        final FusionDictionary dict = new FusionDictionary(new Node(),
-                new FusionDictionary.DictionaryOptions(
-                        new HashMap<String, String>(), false, false));
-
+    private String runReadUnigramsAndBigramsBinary(final List<String> words,
+            final SparseArray<List<Integer>> bigrams, final int bufferType,
+            final String message) {
         File file = null;
         try {
             file = File.createTempFile("runReadUnigrams", ".dict");
         } catch (IOException e) {
             Log.e(TAG, "IOException: " + e);
         }
-
         assertNotNull(file);
 
-        addUnigrams(words.size(), dict, words);
+        // making the dictionary from lists of words.
+        final FusionDictionary dict = new FusionDictionary(new Node(),
+                new FusionDictionary.DictionaryOptions(
+                        new HashMap<String, String>(), false, false));
+        addUnigrams(words.size(), dict, words, null /* shortcutMap */);
         addBigrams(dict, words, bigrams);
+
         timeWritingDictToFile(file, dict);
 
-        long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams);
-        long fullReading = timeReadingAndCheckDict(file, words, bigrams);
+        long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams, bufferType);
+        long fullReading = timeReadingAndCheckDict(file, words, bigrams, null /* shortcutMap */,
+                bufferType);
 
-        Log.d(TAG, "read=" + fullReading + ", bytearray=" + wordMap);
+        return "readDictionaryBinary=" + fullReading + ", readUnigramsAndBigramsBinary=" + wordMap
+                + " : " + message + " : buffer type = " + bufferType;
     }
 
-    public void testReadUnigramsAndBigramsBinary() {
-        final List<String> results = new ArrayList<String>();
+    public void testReadUnigramsAndBigramsBinaryWithByteBuffer() {
+        final List<String> results = CollectionUtils.newArrayList();
 
-        final Random random = new Random(123456);
-        final List<String> words = generateWords(MAX_UNIGRAMS, random);
-        final SparseArray<List<Integer>> emptyArray = CollectionUtils.newSparseArray();
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sEmptyBigrams, USE_BYTE_BUFFER,
+                "unigram"));
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sChainBigrams, USE_BYTE_BUFFER,
+                "chain"));
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sStarBigrams, USE_BYTE_BUFFER,
+                "star"));
 
-        runReadUnigramsAndBigramsBinary(words, emptyArray);
-
-        final SparseArray<List<Integer>> star = CollectionUtils.newSparseArray();
-        for (int i = 1; i < words.size(); ++i) {
-            star.put(i-1, new ArrayList<Integer>());
-            star.get(i-1).add(i);
+        for (final String result : results) {
+            Log.d(TAG, result);
         }
-        runReadUnigramsAndBigramsBinary(words, star);
+    }
+
+    public void testReadUnigramsAndBigramsBinaryWithByteArray() {
+        final List<String> results = CollectionUtils.newArrayList();
+
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sEmptyBigrams, USE_BYTE_ARRAY,
+                "unigram"));
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sChainBigrams, USE_BYTE_ARRAY,
+                "chain"));
+        results.add(runReadUnigramsAndBigramsBinary(sWords, sStarBigrams, USE_BYTE_ARRAY, "star"));
+
+        for (final String result : results) {
+            Log.d(TAG, result);
+        }
     }
 }