Merge "Fix a bug giving broken characters" into jb-mr1-dev
diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml
index 8f6b647..1d7e57b 100644
--- a/java/res/values/keypress-vibration-durations.xml
+++ b/java/res/values/keypress-vibration-durations.xml
@@ -23,5 +23,6 @@
         <item>herring,5</item>
         <item>tuna,5</item>
         <item>mako,20</item>
+        <item>manta,16</item>
     </string-array>
 </resources>
diff --git a/java/res/values/keypress-volumes.xml b/java/res/values/keypress-volumes.xml
index dd86d6c..d112069 100644
--- a/java/res/values/keypress-volumes.xml
+++ b/java/res/values/keypress-volumes.xml
@@ -25,5 +25,6 @@
         <item>stingray,0.4</item>
         <item>grouper,0.3</item>
         <item>mako,0.3</item>
+        <item>manta,0.2</item>
     </string-array>
 </resources>
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 87fb8ba..d18be4c 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -55,7 +55,6 @@
      * The key code (unicode or custom code) that this key generates.
      */
     public final int mCode;
-    public final int mAltCode;
 
     /** Label to display */
     public final String mLabel;
@@ -90,22 +89,11 @@
 
     /** Icon to display instead of a label. Icon takes precedence over a label */
     private final int mIconId;
-    /** Icon for disabled state */
-    private final int mDisabledIconId;
-    /** Preview version of the icon, for the preview popup */
-    private final int mPreviewIconId;
 
     /** Width of the key, not including the gap */
     public final int mWidth;
     /** Height of the key, not including the gap */
     public final int mHeight;
-    /** The horizontal gap around this key */
-    public final int mHorizontalGap;
-    /** The vertical gap below this key */
-    public final int mVerticalGap;
-    /** The visual insets */
-    public final int mVisualInsetsLeft;
-    public final int mVisualInsetsRight;
     /** X coordinate of the key in the keyboard layout */
     public final int mX;
     /** Y coordinate of the key in the keyboard layout */
@@ -113,8 +101,6 @@
     /** Hit bounding box of the key */
     public final Rect mHitBox = new Rect();
 
-    /** Text to output when pressed. This can be multiple characters, like ".com" */
-    public final CharSequence mOutputText;
     /** More keys */
     public final MoreKeySpec[] mMoreKeys;
     /** More keys column number and flags */
@@ -144,6 +130,32 @@
     private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04;
     private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08;
 
+    private final OptionalAttributes mOptionalAttributes;
+
+    private static class OptionalAttributes {
+        /** Text to output when pressed. This can be multiple characters, like ".com" */
+        public final String mOutputText;
+        public final int mAltCode;
+        /** Icon for disabled state */
+        public final int mDisabledIconId;
+        /** Preview version of the icon, for the preview popup */
+        public final int mPreviewIconId;
+        /** The visual insets */
+        public final int mVisualInsetsLeft;
+        public final int mVisualInsetsRight;
+
+        public OptionalAttributes(final String outputText, final int altCode,
+                final int disabledIconId, final int previewIconId,
+                final int visualInsetsLeft, final int visualInsetsRight) {
+            mOutputText = outputText;
+            mAltCode = altCode;
+            mDisabledIconId = disabledIconId;
+            mPreviewIconId = previewIconId;
+            mVisualInsetsLeft = visualInsetsLeft;
+            mVisualInsetsRight = visualInsetsRight;
+        }
+    }
+
     private final int mHashCode;
 
     /** The current pressed state of this key */
@@ -166,10 +178,7 @@
     public Key(Keyboard.Params params, String label, String hintLabel, int iconId,
             int code, String outputText, int x, int y, int width, int height, int labelFlags) {
         mHeight = height - params.mVerticalGap;
-        mHorizontalGap = params.mHorizontalGap;
-        mVerticalGap = params.mVerticalGap;
-        mVisualInsetsLeft = mVisualInsetsRight = 0;
-        mWidth = width - mHorizontalGap;
+        mWidth = width - params.mHorizontalGap;
         mHintLabel = hintLabel;
         mLabelFlags = labelFlags;
         mBackgroundType = BACKGROUND_TYPE_NORMAL;
@@ -177,15 +186,17 @@
         mMoreKeys = null;
         mMoreKeysColumnAndFlags = 0;
         mLabel = label;
-        mOutputText = outputText;
+        if (outputText == null) {
+            mOptionalAttributes = null;
+        } else {
+            mOptionalAttributes = new OptionalAttributes(outputText, CODE_UNSPECIFIED,
+                    ICON_UNDEFINED, ICON_UNDEFINED, 0, 0);
+        }
         mCode = code;
         mEnabled = (code != CODE_UNSPECIFIED);
-        mAltCode = CODE_UNSPECIFIED;
         mIconId = iconId;
-        mDisabledIconId = ICON_UNDEFINED;
-        mPreviewIconId = ICON_UNDEFINED;
         // Horizontal gap is divided equally to both sides of the key.
-        mX = x + mHorizontalGap / 2;
+        mX = x + params.mHorizontalGap / 2;
         mY = y;
         mHitBox.set(x, y, x + width + 1, y + height);
 
@@ -206,8 +217,7 @@
             XmlPullParser parser) throws XmlPullParserException {
         final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
         final int keyHeight = row.mRowHeight;
-        mVerticalGap = params.mVerticalGap;
-        mHeight = keyHeight - mVerticalGap;
+        mHeight = keyHeight - params.mVerticalGap;
 
         final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
                 R.styleable.Keyboard_Key);
@@ -221,7 +231,6 @@
         mX = Math.round(keyXPos + horizontalGap / 2);
         mY = keyYPos;
         mWidth = Math.round(keyWidth - horizontalGap);
-        mHorizontalGap = Math.round(horizontalGap);
         mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1,
                 keyYPos + keyHeight);
         // Update row to have current x coordinate.
@@ -230,15 +239,15 @@
         mBackgroundType = style.getInt(keyAttr,
                 R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType());
 
-        mVisualInsetsLeft = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr,
+        final int visualInsetsLeft = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr,
                 R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0));
-        mVisualInsetsRight = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr,
+        final int visualInsetsRight = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr,
                 R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0));
         mIconId = KeySpecParser.getIconId(style.getString(keyAttr,
                 R.styleable.Keyboard_Key_keyIcon));
-        mDisabledIconId = KeySpecParser.getIconId(style.getString(keyAttr,
+        final int disabledIconId = KeySpecParser.getIconId(style.getString(keyAttr,
                 R.styleable.Keyboard_Key_keyIconDisabled));
-        mPreviewIconId = KeySpecParser.getIconId(style.getString(keyAttr,
+        final int previewIconId = KeySpecParser.getIconId(style.getString(keyAttr,
                 R.styleable.Keyboard_Key_keyIconPreview));
 
         mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags)
@@ -332,11 +341,20 @@
         } else {
             mCode = KeySpecParser.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale);
         }
-        mOutputText = outputText;
-        mAltCode = KeySpecParser.toUpperCaseOfCodeForLocale(
+        final int altCode = KeySpecParser.toUpperCaseOfCodeForLocale(
                 KeySpecParser.parseCode(style.getString(keyAttr,
                 R.styleable.Keyboard_Key_altCode), params.mCodesSet, CODE_UNSPECIFIED),
                 needsToUpperCase, locale);
+        if (outputText == null && altCode == CODE_UNSPECIFIED
+                && disabledIconId == ICON_UNDEFINED && previewIconId == ICON_UNDEFINED
+                && visualInsetsLeft == 0 && visualInsetsRight == 0) {
+            mOptionalAttributes = null;
+        } else {
+            mOptionalAttributes = new OptionalAttributes(outputText, altCode,
+                    disabledIconId, previewIconId,
+                    visualInsetsLeft, visualInsetsRight);
+        }
+
         mHashCode = computeHashCode(this);
 
         keyAttr.recycle();
@@ -371,17 +389,17 @@
                 key.mIconId,
                 key.mBackgroundType,
                 Arrays.hashCode(key.mMoreKeys),
-                key.mOutputText,
+                key.getOutputText(),
                 key.mActionFlags,
                 key.mLabelFlags,
                 // Key can be distinguishable without the following members.
-                // key.mAltCode,
-                // key.mDisabledIconId,
-                // key.mPreviewIconId,
+                // key.mOptionalAttributes.mAltCode,
+                // key.mOptionalAttributes.mDisabledIconId,
+                // key.mOptionalAttributes.mPreviewIconId,
                 // key.mHorizontalGap,
                 // key.mVerticalGap,
-                // key.mVisualInsetLeft,
-                // key.mVisualInsetRight,
+                // key.mOptionalAttributes.mVisualInsetLeft,
+                // key.mOptionalAttributes.mVisualInsetRight,
                 // key.mMaxMoreKeysColumn,
         });
     }
@@ -398,7 +416,7 @@
                 && o.mIconId == mIconId
                 && o.mBackgroundType == mBackgroundType
                 && Arrays.equals(o.mMoreKeys, mMoreKeys)
-                && TextUtils.equals(o.mOutputText, mOutputText)
+                && TextUtils.equals(o.getOutputText(), getOutputText())
                 && o.mActionFlags == mActionFlags
                 && o.mLabelFlags == mLabelFlags;
     }
@@ -578,8 +596,20 @@
         return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY) != 0;
     }
 
+    public String getOutputText() {
+        final OptionalAttributes attrs = mOptionalAttributes;
+        return (attrs != null) ? attrs.mOutputText : null;
+    }
+
+    public int getAltCode() {
+        final OptionalAttributes attrs = mOptionalAttributes;
+        return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED;
+    }
+
     public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
-        final int iconId = mEnabled ? mIconId : mDisabledIconId;
+        final OptionalAttributes attrs = mOptionalAttributes;
+        final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED;
+        final int iconId = mEnabled ? mIconId : disabledIconId;
         final Drawable icon = iconSet.getIconDrawable(iconId);
         if (icon != null) {
             icon.setAlpha(alpha);
@@ -588,9 +618,21 @@
     }
 
     public Drawable getPreviewIcon(KeyboardIconsSet iconSet) {
-        return mPreviewIconId != ICON_UNDEFINED
-                ? iconSet.getIconDrawable(mPreviewIconId)
-                : iconSet.getIconDrawable(mIconId);
+        final OptionalAttributes attrs = mOptionalAttributes;
+        final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED;
+        return previewIconId != ICON_UNDEFINED
+                ? iconSet.getIconDrawable(previewIconId) : iconSet.getIconDrawable(mIconId);
+    }
+
+    public int getDrawX() {
+        final OptionalAttributes attrs = mOptionalAttributes;
+        return (attrs == null) ? mX : mX + attrs.mVisualInsetsLeft;
+    }
+
+    public int getDrawWidth() {
+        final OptionalAttributes attrs = mOptionalAttributes;
+        return (attrs == null) ? mWidth
+                : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight;
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index a32b3e7..b8e5e95 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -396,14 +396,14 @@
         }
 
         private void updateHistogram(Key key) {
-            final int height = key.mHeight + key.mVerticalGap;
+            final int height = key.mHeight + mVerticalGap;
             final int heightCount = updateHistogramCounter(mHeightHistogram, height);
             if (heightCount > mMaxHeightCount) {
                 mMaxHeightCount = heightCount;
                 mMostCommonKeyHeight = height;
             }
 
-            final int width = key.mWidth + key.mHorizontalGap;
+            final int width = key.mWidth + mHorizontalGap;
             final int widthCount = updateHistogramCounter(mWidthHistogram, width);
             if (widthCount > mMaxWidthCount) {
                 mMaxWidthCount = widthCount;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 6a23c91..b909a3a 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -608,7 +608,7 @@
     }
 
     private void onDrawKey(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
-        final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft();
+        final int keyDrawX = key.getDrawX() + getPaddingLeft();
         final int keyDrawY = key.mY + getPaddingTop();
         canvas.translate(keyDrawX, keyDrawY);
 
@@ -623,8 +623,7 @@
 
     // Draw key background.
     protected void onDrawKeyBackground(Key key, Canvas canvas, KeyDrawParams params) {
-        final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight
-                + params.mPadding.left + params.mPadding.right;
+        final int bgWidth = key.getDrawWidth() + params.mPadding.left + params.mPadding.right;
         final int bgHeight = key.mHeight + params.mPadding.top + params.mPadding.bottom;
         final int bgX = -params.mPadding.left;
         final int bgY = -params.mPadding.top;
@@ -645,7 +644,7 @@
 
     // Draw key top visuals.
     protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
-        final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+        final int keyWidth = key.getDrawWidth();
         final int keyHeight = key.mHeight;
         final float centerX = keyWidth * 0.5f;
         final float centerY = keyHeight * 0.5f;
@@ -821,7 +820,7 @@
 
     // Draw popup hint "..." at the bottom right corner of the key.
     protected void drawKeyPopupHint(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
-        final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+        final int keyWidth = key.getDrawWidth();
         final int keyHeight = key.mHeight;
 
         paint.setTypeface(params.mKeyTypeface);
@@ -1012,7 +1011,11 @@
     @SuppressWarnings("deprecation") // setBackgroundDrawable is replaced by setBackground in API16
     @Override
     public void showKeyPreview(PointerTracker tracker) {
-        if (!mShowKeyPreviewPopup) return;
+        final KeyPreviewDrawParams params = mKeyPreviewDrawParams;
+        if (!mShowKeyPreviewPopup) {
+            params.mPreviewVisibleOffset = -mKeyboard.mVerticalGap;
+            return;
+        }
 
         final TextView previewText = getKeyPreviewText(tracker.mPointerId);
         // If the key preview has no parent view yet, add it to the ViewGroup which can place
@@ -1029,7 +1032,6 @@
         if (key == null)
             return;
 
-        final KeyPreviewDrawParams params = mKeyPreviewDrawParams;
         final String label = key.isShiftedLetterActivated() ? key.mHintLabel : key.mLabel;
         // What we show as preview should match what we show on a key top in onDraw().
         if (label != null) {
@@ -1052,7 +1054,7 @@
 
         previewText.measure(
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-        final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+        final int keyDrawWidth = key.getDrawWidth();
         final int previewWidth = previewText.getMeasuredWidth();
         final int previewHeight = params.mPreviewHeight;
         // The width and height of visible part of the key preview background. The content marker
@@ -1068,8 +1070,7 @@
         // The key preview is horizontally aligned with the center of the visible part of the
         // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
         // the left/right background is used if such background is specified.
-        int previewX = key.mX + key.mVisualInsetsLeft - (previewWidth - keyDrawWidth) / 2
-                + params.mCoordinates[0];
+        int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2 + params.mCoordinates[0];
         if (previewX < 0) {
             previewX = 0;
             if (params.mPreviewLeftBackground != null) {
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 358061b..e8e6c15 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -334,7 +334,7 @@
                 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
         final Resources res = getResources();
         final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
-               ResourceUtils.getDeviceOverrideValue(res,
+                ResourceUtils.getDeviceOverrideValue(res,
                         R.array.phantom_sudden_move_event_device_list, "false"));
         PointerTracker.init(mHasDistinctMultitouch, needsPhantomSuddenMoveEventHack);
 
@@ -618,9 +618,9 @@
         // The more keys keyboard is usually vertically aligned with the top edge of the parent key
         // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically
         // aligned with the bottom edge of the visible part of the key preview.
-        final int pointY = parentKey.mY + (keyPreviewEnabled
-                ? mKeyPreviewDrawParams.mPreviewVisibleOffset
-                : -parentKey.mVerticalGap);
+        // {@code mPreviewVisibleOffset} has been set appropriately in
+        // {@link KeyboardView#showKeyPreview(PointerTracker)}.
+        final int pointY = parentKey.mY + mKeyPreviewDrawParams.mPreviewVisibleOffset;
         moreKeysPanel.showMoreKeysPanel(
                 this, this, pointX, pointY, mMoreKeysWindow, mKeyboardActionListener);
         final int translatedX = moreKeysPanel.translateX(tracker.getLastX());
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index be101cf..5a79d50 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -330,10 +330,10 @@
             final int y) {
         final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
         final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
-        final int code = altersCode ? key.mAltCode : primaryCode;
+        final int code = altersCode ? key.getAltCode() : primaryCode;
         if (DEBUG_LISTENER) {
-            Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) + " text=" + key.mOutputText
-                    + " x=" + x + " y=" + y
+            Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code)
+                    + " text=" + key.getOutputText() + " x=" + x + " y=" + y
                     + " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode
                     + " enabled=" + key.isEnabled());
         }
@@ -347,7 +347,7 @@
         // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
         if (key.isEnabled() || altersCode) {
             if (code == Keyboard.CODE_OUTPUT_TEXT) {
-                mListener.onTextInput(key.mOutputText);
+                mListener.onTextInput(key.getOutputText());
             } else if (code != Keyboard.CODE_UNSPECIFIED) {
                 mListener.onCodeInput(code, x, y);
             }
@@ -440,13 +440,13 @@
         }
 
         if (key.altCodeWhileTyping()) {
-            final int altCode = key.mAltCode;
+            final int altCode = key.getAltCode();
             final Key altKey = mKeyboard.getKey(altCode);
             if (altKey != null) {
                 updateReleaseKeyGraphics(altKey);
             }
             for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
-                if (k != key && k.mAltCode == altCode) {
+                if (k != key && k.getAltCode() == altCode) {
                     updateReleaseKeyGraphics(k);
                 }
             }
@@ -479,13 +479,13 @@
         }
 
         if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) {
-            final int altCode = key.mAltCode;
+            final int altCode = key.getAltCode();
             final Key altKey = mKeyboard.getKey(altCode);
             if (altKey != null) {
                 updatePressKeyGraphics(altKey);
             }
             for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
-                if (k != key && k.mAltCode == altCode) {
+                if (k != key && k.getAltCode() == altCode) {
                     updatePressKeyGraphics(k);
                 }
             }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 161b94c..99fef34 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -188,6 +188,54 @@
     // suspicion that a bug might be causing an infinite loop.
     private static final int MAX_PASSES = 24;
 
+    private interface FusionDictionaryBufferInterface {
+        public int readUnsignedByte();
+        public int readUnsignedShort();
+        public int readUnsignedInt24();
+        public int readInt();
+        public int position();
+        public void position(int newPosition);
+    }
+
+    private static final class ByteBufferWrapper implements FusionDictionaryBufferInterface {
+        private ByteBuffer buffer;
+        ByteBufferWrapper(final ByteBuffer buffer) {
+            this.buffer = buffer;
+        }
+
+        @Override
+        public int readUnsignedByte() {
+            return ((int)buffer.get()) & 0xFF;
+        }
+
+        @Override
+        public int readUnsignedShort() {
+            return ((int)buffer.getShort()) & 0xFFFF;
+        }
+
+        @Override
+        public int readUnsignedInt24() {
+            final int retval = readUnsignedByte();
+            return (retval << 16) + readUnsignedShort();
+        }
+
+        @Override
+        public int readInt() {
+            return buffer.getInt();
+        }
+
+        @Override
+        public int position() {
+            return buffer.position();
+        }
+
+        @Override
+        public void position(int newPos) {
+            buffer.position(newPos);
+            return;
+        }
+    }
+
     /**
      * A class grouping utility function for our specific character encoding.
      */
@@ -310,9 +358,9 @@
         }
 
         /**
-         * Reads a string from a ByteBuffer. This is the converse of the above method.
+         * Reads a string from a buffer. This is the converse of the above method.
          */
-        private static String readString(final ByteBuffer buffer) {
+        private static String readString(final FusionDictionaryBufferInterface buffer) {
             final StringBuilder s = new StringBuilder();
             int character = readChar(buffer);
             while (character != INVALID_CHARACTER) {
@@ -323,19 +371,19 @@
         }
 
         /**
-         * Reads a character from the ByteBuffer.
+         * Reads a character from the buffer.
          *
          * This follows the character format documented earlier in this source file.
          *
          * @param buffer the buffer, positioned over an encoded character.
          * @return the character code.
          */
-        private static int readChar(final ByteBuffer buffer) {
-            int character = readUnsignedByte(buffer);
+        private static int readChar(final FusionDictionaryBufferInterface buffer) {
+            int character = buffer.readUnsignedByte();
             if (!fitsOnOneByte(character)) {
                 if (GROUP_CHARACTERS_TERMINATOR == character) return INVALID_CHARACTER;
                 character <<= 16;
-                character += readUnsignedShort(buffer);
+                character += buffer.readUnsignedShort();
             }
             return character;
         }
@@ -1093,10 +1141,10 @@
     // readDictionaryBinary is the public entry point for them.
 
     static final int[] characterBuffer = new int[MAX_WORD_LENGTH];
-    private static CharGroupInfo readCharGroup(final ByteBuffer buffer,
+    private static CharGroupInfo readCharGroup(final FusionDictionaryBufferInterface buffer,
             final int originalGroupAddress) {
         int addressPointer = originalGroupAddress;
-        final int flags = readUnsignedByte(buffer);
+        final int flags = buffer.readUnsignedByte();
         ++addressPointer;
         final int characters[];
         if (0 != (flags & FLAG_HAS_MULTIPLE_CHARS)) {
@@ -1117,22 +1165,22 @@
         final int frequency;
         if (0 != (FLAG_IS_TERMINAL & flags)) {
             ++addressPointer;
-            frequency = readUnsignedByte(buffer);
+            frequency = buffer.readUnsignedByte();
         } else {
             frequency = CharGroup.NOT_A_TERMINAL;
         }
         int childrenAddress = addressPointer;
         switch (flags & MASK_GROUP_ADDRESS_TYPE) {
         case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
-            childrenAddress += readUnsignedByte(buffer);
+            childrenAddress += buffer.readUnsignedByte();
             addressPointer += 1;
             break;
         case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
-            childrenAddress += readUnsignedShort(buffer);
+            childrenAddress += buffer.readUnsignedShort();
             addressPointer += 2;
             break;
         case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
-            childrenAddress += readUnsignedInt24(buffer);
+            childrenAddress += buffer.readUnsignedInt24();
             addressPointer += 3;
             break;
         case FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
@@ -1144,9 +1192,9 @@
         if (0 != (flags & FLAG_HAS_SHORTCUT_TARGETS)) {
             final int pointerBefore = buffer.position();
             shortcutTargets = new ArrayList<WeightedString>();
-            buffer.getShort(); // Skip the size
+            buffer.readUnsignedShort(); // Skip the size
             while (true) {
-                final int targetFlags = readUnsignedByte(buffer);
+                final int targetFlags = buffer.readUnsignedByte();
                 final String word = CharEncoding.readString(buffer);
                 shortcutTargets.add(new WeightedString(word,
                         targetFlags & FLAG_ATTRIBUTE_FREQUENCY));
@@ -1158,22 +1206,22 @@
         if (0 != (flags & FLAG_HAS_BIGRAMS)) {
             bigrams = new ArrayList<PendingAttribute>();
             while (true) {
-                final int bigramFlags = readUnsignedByte(buffer);
+                final int bigramFlags = buffer.readUnsignedByte();
                 ++addressPointer;
                 final int sign = 0 == (bigramFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1;
                 int bigramAddress = addressPointer;
                 switch (bigramFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) {
                 case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
-                    bigramAddress += sign * readUnsignedByte(buffer);
+                    bigramAddress += sign * buffer.readUnsignedByte();
                     addressPointer += 1;
                     break;
                 case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
-                    bigramAddress += sign * readUnsignedShort(buffer);
+                    bigramAddress += sign * buffer.readUnsignedShort();
                     addressPointer += 2;
                     break;
                 case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
-                    final int offset = (readUnsignedByte(buffer) << 16)
-                            + readUnsignedShort(buffer);
+                    final int offset = (buffer.readUnsignedByte() << 16)
+                            + buffer.readUnsignedShort();
                     bigramAddress += sign * offset;
                     addressPointer += 3;
                     break;
@@ -1192,13 +1240,13 @@
     /**
      * Reads and returns the char group count out of a buffer and forwards the pointer.
      */
-    private static int readCharGroupCount(final ByteBuffer buffer) {
-        final int msb = readUnsignedByte(buffer);
+    private static int readCharGroupCount(final FusionDictionaryBufferInterface buffer) {
+        final int msb = buffer.readUnsignedByte();
         if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) {
             return msb;
         } else {
             return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8)
-                    + readUnsignedByte(buffer);
+                    + buffer.readUnsignedByte();
         }
     }
 
@@ -1215,8 +1263,8 @@
      * @param address the address to seek.
      * @return the word, as a string.
      */
-    private static String getWordAtAddress(final ByteBuffer buffer, final int headerSize,
-            final int address) {
+    private static String getWordAtAddress(final FusionDictionaryBufferInterface buffer,
+            final int headerSize, final int address) {
         final String cachedString = wordCache.get(address);
         if (null != cachedString) return cachedString;
         final int originalPointer = buffer.position();
@@ -1241,7 +1289,7 @@
                     builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
                     buffer.position(last.mChildrenAddress + headerSize);
                     groupOffset = last.mChildrenAddress + 1;
-                    i = readUnsignedByte(buffer);
+                    i = buffer.readUnsignedByte();
                     last = null;
                     continue;
                 }
@@ -1251,7 +1299,7 @@
                 builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
                 buffer.position(last.mChildrenAddress + headerSize);
                 groupOffset = last.mChildrenAddress + 1;
-                i = readUnsignedByte(buffer);
+                i = buffer.readUnsignedByte();
                 last = null;
                 continue;
             }
@@ -1262,10 +1310,10 @@
     }
 
     /**
-     * Reads a single node from a binary file.
+     * Reads a single node from a buffer.
      *
-     * This methods reads the file at the current position of its file pointer. A node is
-     * fully expected to start at the current position.
+     * This methods reads the file at the current position. A node is fully expected to start at
+     * the current position.
      * This will recursively read other nodes into the structure, populating the reverse
      * maps on the fly and using them to keep track of already read nodes.
      *
@@ -1275,7 +1323,7 @@
      * @param reverseGroupMap a mapping from addresses to already read character groups.
      * @return the read node with all his children already read.
      */
-    private static Node readNode(final ByteBuffer buffer, final int headerSize,
+    private static Node readNode(final FusionDictionaryBufferInterface buffer, final int headerSize,
             final Map<Integer, Node> reverseNodeMap, final Map<Integer, CharGroup> reverseGroupMap)
             throws IOException {
         final int nodeOrigin = buffer.position() - headerSize;
@@ -1283,7 +1331,7 @@
         final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>();
         int groupOffset = nodeOrigin + getGroupCountSize(count);
         for (int i = count; i > 0; --i) {
-            CharGroupInfo info =readCharGroup(buffer, groupOffset);
+            CharGroupInfo info = readCharGroup(buffer, groupOffset);
             ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
             ArrayList<WeightedString> bigrams = null;
             if (null != info.mBigrams) {
@@ -1323,42 +1371,49 @@
      * Helper function to get the binary format version from the header.
      * @throws IOException
      */
-    private static int getFormatVersion(final ByteBuffer buffer) throws IOException {
-        final int magic_v1 = readUnsignedShort(buffer);
-        if (VERSION_1_MAGIC_NUMBER == magic_v1) return readUnsignedByte(buffer);
-        final int magic_v2 = (magic_v1 << 16) + readUnsignedShort(buffer);
-        if (VERSION_2_MAGIC_NUMBER == magic_v2) return readUnsignedShort(buffer);
+    private static int getFormatVersion(final FusionDictionaryBufferInterface buffer)
+            throws IOException {
+        final int magic_v1 = buffer.readUnsignedShort();
+        if (VERSION_1_MAGIC_NUMBER == magic_v1) return buffer.readUnsignedByte();
+        final int magic_v2 = (magic_v1 << 16) + buffer.readUnsignedShort();
+        if (VERSION_2_MAGIC_NUMBER == magic_v2) return buffer.readUnsignedShort();
         return NOT_A_VERSION_NUMBER;
     }
 
     /**
-     * Reads options from a file and populate a map with their contents.
+     * Reads options from a buffer and populate a map with their contents.
      *
-     * The file is read at the current file pointer, so the caller must take care the pointer
+     * The buffer is read at the current position, so the caller must take care the pointer
      * is in the right place before calling this.
      */
-    public static void populateOptions(final ByteBuffer buffer, final int headerSize,
-            final HashMap<String, String> options) {
+    public static void populateOptions(final FusionDictionaryBufferInterface buffer,
+            final int headerSize, final HashMap<String, String> options) {
         while (buffer.position() < headerSize) {
             final String key = CharEncoding.readString(buffer);
             final String value = CharEncoding.readString(buffer);
             options.put(key, value);
         }
     }
+    // TODO: remove this method.
+    public static void populateOptions(final ByteBuffer buffer, final int headerSize,
+            final HashMap<String, String> options) {
+        populateOptions(new ByteBufferWrapper(buffer), headerSize, options);
+    }
 
     /**
-     * Reads a byte buffer and returns the memory representation of the dictionary.
+     * Reads a buffer and returns the memory representation of the dictionary.
      *
-     * This high-level method takes a binary file and reads its contents, populating a
+     * This high-level method takes a buffer and reads its contents, populating a
      * FusionDictionary structure. The optional dict argument is an existing dictionary to
-     * which words from the file should be added. If it is null, a new dictionary is created.
+     * which words from the buffer should be added. If it is null, a new dictionary is created.
      *
      * @param buffer the buffer to read.
      * @param dict an optional dictionary to add words to, or null.
      * @return the created (or merged) dictionary.
      */
-    public static FusionDictionary readDictionaryBinary(final ByteBuffer buffer,
-            final FusionDictionary dict) throws IOException, UnsupportedFormatException {
+    public static FusionDictionary readDictionaryBinary(
+            final FusionDictionaryBufferInterface buffer, final FusionDictionary dict)
+                    throws IOException, UnsupportedFormatException {
         // Check file version
         final int version = getFormatVersion(buffer);
         if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION) {
@@ -1371,14 +1426,14 @@
         wordCache.clear();
 
         // Read options
-        final int optionsFlags = readUnsignedShort(buffer);
+        final int optionsFlags = buffer.readUnsignedShort();
 
         final int headerSize;
         final HashMap<String, String> options = new HashMap<String, String>();
         if (version < FIRST_VERSION_WITH_HEADER_SIZE) {
             headerSize = buffer.position();
         } else {
-            headerSize = buffer.getInt();
+            headerSize = buffer.readInt();
             populateOptions(buffer, headerSize, options);
             buffer.position(headerSize);
         }
@@ -1413,26 +1468,10 @@
         return newDict;
     }
 
-    /**
-     * Helper function to read one byte from ByteBuffer.
-     */
-    private static int readUnsignedByte(final ByteBuffer buffer) {
-        return ((int)buffer.get()) & 0xFF;
-    }
-
-    /**
-     * Helper function to read two byte from ByteBuffer.
-     */
-    private static int readUnsignedShort(final ByteBuffer buffer) {
-        return ((int)buffer.getShort()) & 0xFFFF;
-    }
-
-    /**
-     * Helper function to read three byte from ByteBuffer.
-     */
-    private static int readUnsignedInt24(final ByteBuffer buffer) {
-        final int value = readUnsignedByte(buffer) << 16;
-        return value + readUnsignedShort(buffer);
+    // TODO: remove this method.
+    public static FusionDictionary readDictionaryBinary(final ByteBuffer buffer,
+            final FusionDictionary dict) throws IOException, UnsupportedFormatException {
+        return readDictionaryBinary(new ByteBufferWrapper(buffer), dict);
     }
 
     /**
@@ -1450,7 +1489,7 @@
             inStream = new FileInputStream(file);
             final ByteBuffer buffer = inStream.getChannel().map(
                     FileChannel.MapMode.READ_ONLY, 0, file.length());
-            final int version = getFormatVersion(buffer);
+            final int version = getFormatVersion(new ByteBufferWrapper(buffer));
             return (version >= MINIMUM_SUPPORTED_VERSION && version <= MAXIMUM_SUPPORTED_VERSION);
         } catch (FileNotFoundException e) {
             return false;
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 71a6d6a..cd9ff85 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -257,7 +257,7 @@
                     for (Key keyboardKey : keyboardKeys) {
                         mJsonWriter.beginObject();
                         mJsonWriter.name("code").value(keyboardKey.mCode);
-                        mJsonWriter.name("altCode").value(keyboardKey.mAltCode);
+                        mJsonWriter.name("altCode").value(keyboardKey.getAltCode());
                         mJsonWriter.name("x").value(keyboardKey.mX);
                         mJsonWriter.name("y").value(keyboardKey.mY);
                         mJsonWriter.name("w").value(keyboardKey.mWidth);
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 918fcf5..9bb81a0 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -1045,7 +1045,7 @@
             final int y, final boolean ignoreModifierKey, final boolean altersCode,
             final int code) {
         if (key != null) {
-            CharSequence outputText = key.mOutputText;
+            String outputText = key.getOutputText();
             final Object[] values = {
                 Keyboard.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null
                         : scrubDigitsFromString(outputText.toString()),