Merge "Optimizations and safeguards."
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index a71e7cc..8a67336 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -108,6 +108,14 @@
         <attr name="backgroundDimAlpha" format="integer" />
         <!-- More keys keyboard will shown at touched point. -->
         <attr name="showMoreKeysKeyboardAtTouchedPoint" format="boolean" />
+        <!-- Minimum distance between gesture preview trail sampling points. -->
+        <attr name="gesturePreviewTrailMinSamplingDistance" format="dimension" />
+        <!-- Maximum angular threshold between gesture preview trail interpolation segments in degree. -->
+        <attr name="gesturePreviewTrailMaxInterpolationAngularThreshold" format="integer" />
+        <!-- Maximum distance threshold between gesture preview trail interpolation segments. -->
+        <attr name="gesturePreviewTrailMaxInterpolationDistanceThreshold" format="dimension" />
+        <!-- Maximum number of gesture preview trail interpolation segments. -->
+        <attr name="gesturePreviewTrailMaxInterpolationSegments" format="integer" />
         <!-- Delay after gesture trail starts fading out in millisecond. -->
         <attr name="gesturePreviewTrailFadeoutStartDelay" format="integer" />
         <!-- Duration while gesture preview trail is fading out in millisecond. -->
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index da735cf..5c33275 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -101,6 +101,14 @@
     <fraction name="center_suggestion_percentile">36%</fraction>
 
     <!-- Gesture preview trail parameters -->
+    <!-- Minimum distance between gesture preview trail sampling points. -->
+    <dimen name="gesture_preview_trail_min_sampling_distance">6.4dp</dimen>
+    <!-- Maximum angular threshold between gesture preview trails interpolation segments in degree. -->
+    <integer name="gesture_preview_trail_max_interpolation_angular_threshold">15</integer>
+    <!-- Maximum distance threshold between gesture preview trails interpolation segments. -->
+    <dimen name="gesture_preview_trail_max_interpolation_distance_threshold">16.0dp</dimen>
+    <!-- Maximum number of gesture preview trail interpolation segments. -->
+    <integer name="gesture_preview_trail_max_interpolation_segments">6</integer>
     <dimen name="gesture_preview_trail_start_width">10.0dp</dimen>
     <dimen name="gesture_preview_trail_end_width">2.5dp</dimen>
     <!-- Percentages of gesture preview taril body and shadow, in proportion to the trail width.
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index dad7e20..fa40e51 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -64,6 +64,10 @@
         <item name="gestureFloatingPreviewHorizontalPadding">@dimen/gesture_floating_preview_horizontal_padding</item>
         <item name="gestureFloatingPreviewVerticalPadding">@dimen/gesture_floating_preview_vertical_padding</item>
         <item name="gestureFloatingPreviewRoundRadius">@dimen/gesture_floating_preview_round_radius</item>
+        <item name="gesturePreviewTrailMinSamplingDistance">@dimen/gesture_preview_trail_min_sampling_distance</item>
+        <item name="gesturePreviewTrailMaxInterpolationAngularThreshold">@integer/gesture_preview_trail_max_interpolation_angular_threshold</item>
+        <item name="gesturePreviewTrailMaxInterpolationDistanceThreshold">@dimen/gesture_preview_trail_max_interpolation_distance_threshold</item>
+        <item name="gesturePreviewTrailMaxInterpolationSegments">@integer/gesture_preview_trail_max_interpolation_segments</item>
         <item name="gesturePreviewTrailFadeoutStartDelay">@integer/config_gesture_preview_trail_fadeout_start_delay</item>
         <item name="gesturePreviewTrailFadeoutDuration">@integer/config_gesture_preview_trail_fadeout_duration</item>
         <item name="gesturePreviewTrailUpdateInterval">@integer/config_gesture_preview_trail_update_interval</item>
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 0556fdd..2d79164 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -25,6 +25,7 @@
 import com.android.inputmethod.keyboard.internal.GestureStroke;
 import com.android.inputmethod.keyboard.internal.GestureStroke.GestureStrokeParams;
 import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints;
+import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints.GestureStrokePreviewParams;
 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
@@ -161,6 +162,7 @@
     // Parameters for pointer handling.
     private static PointerTrackerParams sParams;
     private static GestureStrokeParams sGestureStrokeParams;
+    private static GestureStrokePreviewParams sGesturePreviewParams;
     private static boolean sNeedsPhantomSuddenMoveEventHack;
     // Move this threshold to resource.
     // TODO: Device specific parameter would be better for device specific hack?
@@ -339,12 +341,14 @@
         sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
         sParams = PointerTrackerParams.DEFAULT;
         sGestureStrokeParams = GestureStrokeParams.DEFAULT;
+        sGesturePreviewParams = GestureStrokePreviewParams.DEFAULT;
         sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams);
     }
 
     public static void setParameters(final TypedArray mainKeyboardViewAttr) {
         sParams = new PointerTrackerParams(mainKeyboardViewAttr);
         sGestureStrokeParams = new GestureStrokeParams(mainKeyboardViewAttr);
+        sGesturePreviewParams = new GestureStrokePreviewParams(mainKeyboardViewAttr);
         sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams);
     }
 
@@ -428,7 +432,7 @@
         }
         mPointerId = id;
         mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints(
-                id, sGestureStrokeParams);
+                id, sGestureStrokeParams, sGesturePreviewParams);
         setKeyDetectorInner(handler.getKeyDetector());
         mListener = handler.getKeyboardActionListener();
         mDrawingProxy = handler.getDrawingProxy();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
index 7a51e25..312fd21 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
@@ -16,6 +16,9 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import android.content.res.TypedArray;
+
+import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.ResizableIntArray;
 
 public final class GestureStrokeWithPreviewPoints extends GestureStroke {
@@ -25,6 +28,8 @@
     private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
     private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
 
+    private final GestureStrokePreviewParams mPreviewParams;
+
     private int mStrokeId;
     private int mLastPreviewSize;
     private final HermiteInterpolator mInterpolator = new HermiteInterpolator();
@@ -32,23 +37,53 @@
 
     private int mLastX;
     private int mLastY;
-    private double mMinPreviewSamplingDistance;
     private double mDistanceFromLastSample;
-    private double mInterpolationDistanceThreshold;
 
-    // TODO: Move these constants to resource.
-    // TODO: Use "dp" instead of ratio to the keyWidth because table has rather large keys.
-    // The minimum trail distance between sample points for preview in keyWidth unit when using
-    // interpolation.
-    private static final float MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH = 0.2f;
-    // The angular threshold to use interpolation in radian. PI/12 is 15 degree.
-    private static final double INTERPOLATION_ANGULAR_THRESHOLD = Math.PI / 12.0d;
-    // The distance threshold to use interpolation in keyWidth unit.
-    private static final float INTERPOLATION_DISTANCE_THRESHOLD_TO_KEY_WIDTH = 0.5f;
-    private static final int MAX_INTERPOLATION_PARTITIONS = 6;
+    public static final class GestureStrokePreviewParams {
+        public final double mMinSamplingDistance; // in pixel
+        public final double mMaxInterpolationAngularThreshold; // in radian
+        public final double mMaxInterpolationDistanceThreshold; // in pixel
+        public final int mMaxInterpolationSegments;
 
-    public GestureStrokeWithPreviewPoints(final int pointerId, final GestureStrokeParams params) {
-        super(pointerId, params);
+        public static final GestureStrokePreviewParams DEFAULT = new GestureStrokePreviewParams();
+
+        private static final int DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD = 15; // in degree
+
+        private GestureStrokePreviewParams() {
+            mMinSamplingDistance = 0.0d;
+            mMaxInterpolationAngularThreshold =
+                    degreeToRadian(DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD);
+            mMaxInterpolationDistanceThreshold = mMinSamplingDistance;
+            mMaxInterpolationSegments = 4;
+        }
+
+        private static double degreeToRadian(final int degree) {
+            return (double)degree / 180.0d * Math.PI;
+        }
+
+        public GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr) {
+            mMinSamplingDistance = mainKeyboardViewAttr.getDimension(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailMinSamplingDistance,
+                    (float)DEFAULT.mMinSamplingDistance);
+            final int interpolationAngularDegree = mainKeyboardViewAttr.getInteger(R.styleable
+                    .MainKeyboardView_gesturePreviewTrailMaxInterpolationAngularThreshold, 0);
+            mMaxInterpolationAngularThreshold = (interpolationAngularDegree <= 0)
+                    ? DEFAULT.mMaxInterpolationAngularThreshold
+                    : degreeToRadian(interpolationAngularDegree);
+            mMaxInterpolationDistanceThreshold = mainKeyboardViewAttr.getDimension(R.styleable
+                    .MainKeyboardView_gesturePreviewTrailMaxInterpolationDistanceThreshold,
+                    (float)DEFAULT.mMaxInterpolationDistanceThreshold);
+            mMaxInterpolationSegments = mainKeyboardViewAttr.getInteger(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailMaxInterpolationSegments,
+                    DEFAULT.mMaxInterpolationSegments);
+        }
+    }
+
+    public GestureStrokeWithPreviewPoints(final int pointerId,
+            final GestureStrokeParams strokeParams,
+            final GestureStrokePreviewParams previewParams) {
+        super(pointerId, strokeParams);
+        mPreviewParams = previewParams;
     }
 
     @Override
@@ -66,19 +101,12 @@
         return mStrokeId;
     }
 
-    @Override
-    public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) {
-        super.setKeyboardGeometry(keyWidth, keyboardHeight);
-        mMinPreviewSamplingDistance = keyWidth * MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH;
-        mInterpolationDistanceThreshold = keyWidth * INTERPOLATION_DISTANCE_THRESHOLD_TO_KEY_WIDTH;
-    }
-
     private boolean needsSampling(final int x, final int y) {
         mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY);
         mLastX = x;
         mLastY = y;
         final boolean isDownEvent = (mPreviewEventTimes.getLength() == 0);
-        if (mDistanceFromLastSample >= mMinPreviewSamplingDistance || isDownEvent) {
+        if (mDistanceFromLastSample >= mPreviewParams.mMinSamplingDistance || isDownEvent) {
             mDistanceFromLastSample = 0.0d;
             return true;
         }
@@ -144,19 +172,19 @@
             final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X);
             final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X);
             final double deltaAngle = Math.abs(angularDiff(m2, m1));
-            final int partitionsByAngle = (int)Math.ceil(
-                    deltaAngle / INTERPOLATION_ANGULAR_THRESHOLD);
+            final int segmentsByAngle = (int)Math.ceil(
+                    deltaAngle / mPreviewParams.mMaxInterpolationAngularThreshold);
             final double deltaDistance = Math.hypot(mInterpolator.mP1X - mInterpolator.mP2X,
                     mInterpolator.mP1Y - mInterpolator.mP2Y);
-            final int partitionsByDistance = (int)Math.ceil(deltaDistance
-                    / mInterpolationDistanceThreshold);
-            final int partitions = Math.min(MAX_INTERPOLATION_PARTITIONS,
-                    Math.max(partitionsByAngle, partitionsByDistance));
+            final int segmentsByDistance = (int)Math.ceil(deltaDistance
+                    / mPreviewParams.mMaxInterpolationDistanceThreshold);
+            final int segments = Math.min(mPreviewParams.mMaxInterpolationSegments,
+                    Math.max(segmentsByAngle, segmentsByDistance));
             final int t1 = eventTimes.get(d1);
             final int dt = pt[p2] - pt[p1];
             d1++;
-            for (int i = 1; i < partitions; i++) {
-                final float t = i / (float)partitions;
+            for (int i = 1; i < segments; i++) {
+                final float t = i / (float)segments;
                 mInterpolator.interpolate(t);
                 eventTimes.add(d1, (int)(dt * t) + t1);
                 xCoords.add(d1, (int)mInterpolator.mInterpolatedX);
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index d660f70..b9db9a0 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.latin.personalization.AccountUtils;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -28,6 +30,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.util.List;
 import java.util.Locale;
 
 public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
@@ -105,11 +108,27 @@
     @Override
     public void loadDictionaryAsync() {
         clearFusionDictionary();
+        loadDeviceAccountsEmailAddresses();
         loadDictionaryAsyncForUri(ContactsContract.Profile.CONTENT_URI);
         // TODO: Switch this URL to the newer ContactsContract too
         loadDictionaryAsyncForUri(Contacts.CONTENT_URI);
     }
 
+    private void loadDeviceAccountsEmailAddresses() {
+        final List<String> accountVocabulary =
+                AccountUtils.getDeviceAccountsEmailAddresses(mContext);
+        if (accountVocabulary == null || accountVocabulary.isEmpty()) {
+            return;
+        }
+        for (String word : accountVocabulary) {
+            if (DEBUG) {
+                Log.d(TAG, "loadAccountVocabulary: " + word);
+            }
+            super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS,
+                    false /* isNotAWord */);
+        }
+    }
+
     private void loadDictionaryAsyncForUri(final Uri uri) {
         try {
             Cursor cursor = mContext.getContentResolver()
diff --git a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
new file mode 100644
index 0000000..93687e1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 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.personalization;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.util.Patterns;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AccountUtils {
+    private AccountUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    private static Account[] getAccounts(final Context context) {
+        return AccountManager.get(context).getAccounts();
+    }
+
+    public static List<String> getDeviceAccountsEmailAddresses(final Context context) {
+        final ArrayList<String> retval = new ArrayList<String>();
+        for (final Account account : getAccounts(context)) {
+            final String name = account.name;
+            if (Patterns.EMAIL_ADDRESS.matcher(name).matches()) {
+                retval.add(name);
+                retval.add(name.split("@")[0]);
+            }
+        }
+        return retval;
+    }
+}
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 11fa3da..1dd68ea 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -109,7 +109,8 @@
     }
     Dictionary *dictionary = 0;
     if (BinaryFormat::UNKNOWN_FORMAT
-            == BinaryFormat::detectFormat(static_cast<uint8_t *>(dictBuf))) {
+            == BinaryFormat::detectFormat(static_cast<uint8_t *>(dictBuf),
+                    static_cast<int>(dictSize))) {
         AKLOGE("DICT: dictionary format is unknown, bad magic number");
 #ifdef USE_MMAP_FOR_DICTIONARY
         releaseDictBuf(static_cast<const char *>(dictBuf) - adjust, adjDictSize, fd);
diff --git a/native/jni/src/binary_format.h b/native/jni/src/binary_format.h
index 06f50dc..9824153 100644
--- a/native/jni/src/binary_format.h
+++ b/native/jni/src/binary_format.h
@@ -64,13 +64,14 @@
     static const int UNKNOWN_FORMAT = -1;
     static const int SHORTCUT_LIST_SIZE_SIZE = 2;
 
-    static int detectFormat(const uint8_t *const dict);
-    static int getHeaderSize(const uint8_t *const dict);
-    static int getFlags(const uint8_t *const dict);
+    static int detectFormat(const uint8_t *const dict, const int dictSize);
+    static int getHeaderSize(const uint8_t *const dict, const int dictSize);
+    static int getFlags(const uint8_t *const dict, const int dictSize);
     static bool hasBlacklistedOrNotAWordFlag(const int flags);
-    static void readHeaderValue(const uint8_t *const dict, const char *const key, int *outValue,
-            const int outValueSize);
-    static int readHeaderValueInt(const uint8_t *const dict, const char *const key);
+    static void readHeaderValue(const uint8_t *const dict, const int dictSize,
+            const char *const key, int *outValue, const int outValueSize);
+    static int readHeaderValueInt(const uint8_t *const dict, const int dictSize,
+            const char *const key);
     static int getGroupCountAndForwardPointer(const uint8_t *const dict, int *pos);
     static uint8_t getFlagsAndForwardPointer(const uint8_t *const dict, int *pos);
     static int getCodePointAndForwardPointer(const uint8_t *const dict, int *pos);
@@ -96,7 +97,7 @@
             const uint8_t *bigramFilter, const int unigramProbability);
     static int getBigramProbabilityFromHashMap(const int position,
             const hash_map_compat<int, int> *bigramMap, const int unigramProbability);
-    static float getMultiWordCostMultiplier(const uint8_t *const dict);
+    static float getMultiWordCostMultiplier(const uint8_t *const dict, const int dictSize);
     static void fillBigramProbabilityToHashMap(const uint8_t *const root, int position,
             hash_map_compat<int, int> *bigramMap);
     static int getBigramProbability(const uint8_t *const root, int position,
@@ -122,6 +123,8 @@
     static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
     static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
 
+    // Any file smaller than this is not a dictionary.
+    static const int DICTIONARY_MINIMUM_SIZE = 4;
     // Originally, format version 1 had a 16-bit magic number, then the version number `01'
     // then options that must be 0. Hence the first 32-bits of the format are always as follow
     // and it's okay to consider them a magic number as a whole.
@@ -131,6 +134,8 @@
     // number, so we had to change it so that version 2 files would be rejected by older
     // implementations. On this occasion, we made the magic number 32 bits long.
     static const int FORMAT_VERSION_2_MAGIC_NUMBER = -1681835266; // 0x9BC13AFE
+    // Magic number (4 bytes), version (2 bytes), options (2 bytes), header size (4 bytes) = 12
+    static const int FORMAT_VERSION_2_MINIMUM_SIZE = 12;
 
     static const int CHARACTER_ARRAY_TERMINATOR_SIZE = 1;
     static const int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
@@ -141,8 +146,11 @@
     static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos);
 };
 
-AK_FORCE_INLINE int BinaryFormat::detectFormat(const uint8_t *const dict) {
+AK_FORCE_INLINE int BinaryFormat::detectFormat(const uint8_t *const dict, const int dictSize) {
     // The magic number is stored big-endian.
+    // If the dictionary is less than 4 bytes, we can't even read the magic number, so we don't
+    // understand this format.
+    if (dictSize < DICTIONARY_MINIMUM_SIZE) return UNKNOWN_FORMAT;
     const int magicNumber = (dict[0] << 24) + (dict[1] << 16) + (dict[2] << 8) + dict[3];
     switch (magicNumber) {
     case FORMAT_VERSION_1_MAGIC_NUMBER:
@@ -152,6 +160,10 @@
         // Options (2 bytes) must be 0x00 0x00
         return 1;
     case FORMAT_VERSION_2_MAGIC_NUMBER:
+        // Version 2 dictionaries are at least 12 bytes long (see below details for the header).
+        // If this dictionary has the version 2 magic number but is less than 12 bytes long, then
+        // it's an unknown format and we need to avoid confidently reading the next bytes.
+        if (dictSize < FORMAT_VERSION_2_MINIMUM_SIZE) return UNKNOWN_FORMAT;
         // Format 2 header is as follows:
         // Magic number (4 bytes) 0x9B 0xC1 0x3A 0xFE
         // Version number (2 bytes) 0x00 0x02
@@ -163,8 +175,8 @@
     }
 }
 
-inline int BinaryFormat::getFlags(const uint8_t *const dict) {
-    switch (detectFormat(dict)) {
+inline int BinaryFormat::getFlags(const uint8_t *const dict, const int dictSize) {
+    switch (detectFormat(dict, dictSize)) {
     case 1:
         return NO_FLAGS; // TODO: NO_FLAGS is unused anywhere else?
     default:
@@ -176,8 +188,8 @@
     return (flags & (FLAG_IS_BLACKLISTED | FLAG_IS_NOT_A_WORD)) != 0;
 }
 
-inline int BinaryFormat::getHeaderSize(const uint8_t *const dict) {
-    switch (detectFormat(dict)) {
+inline int BinaryFormat::getHeaderSize(const uint8_t *const dict, const int dictSize) {
+    switch (detectFormat(dict, dictSize)) {
     case 1:
         return FORMAT_VERSION_1_HEADER_SIZE;
     case 2:
@@ -188,12 +200,12 @@
     }
 }
 
-inline void BinaryFormat::readHeaderValue(const uint8_t *const dict, const char *const key,
-        int *outValue, const int outValueSize) {
+inline void BinaryFormat::readHeaderValue(const uint8_t *const dict, const int dictSize,
+        const char *const key, int *outValue, const int outValueSize) {
     int outValueIndex = 0;
     // Only format 2 and above have header attributes as {key,value} string pairs. For prior
     // formats, we just return an empty string, as if the key wasn't found.
-    if (2 <= detectFormat(dict)) {
+    if (2 <= detectFormat(dict, dictSize)) {
         const int headerOptionsOffset = 4 /* magic number */
                 + 2 /* dictionary version */ + 2 /* flags */;
         const int headerSize =
@@ -236,11 +248,12 @@
     if (outValueIndex >= 0) outValue[outValueIndex] = 0;
 }
 
-inline int BinaryFormat::readHeaderValueInt(const uint8_t *const dict, const char *const key) {
+inline int BinaryFormat::readHeaderValueInt(const uint8_t *const dict, const int dictSize,
+        const char *const key) {
     const int bufferSize = LARGEST_INT_DIGIT_COUNT;
     int intBuffer[bufferSize];
     char charBuffer[bufferSize];
-    BinaryFormat::readHeaderValue(dict, key, intBuffer, bufferSize);
+    BinaryFormat::readHeaderValue(dict, dictSize, key, intBuffer, bufferSize);
     for (int i = 0; i < bufferSize; ++i) {
         charBuffer[i] = intBuffer[i];
     }
@@ -256,8 +269,10 @@
     return ((msb & 0x7F) << 8) | dict[(*pos)++];
 }
 
-inline float BinaryFormat::getMultiWordCostMultiplier(const uint8_t *const dict) {
-    const int headerValue = readHeaderValueInt(dict, "MULTIPLE_WORDS_DEMOTION_RATE");
+inline float BinaryFormat::getMultiWordCostMultiplier(const uint8_t *const dict,
+        const int dictSize) {
+    const int headerValue = readHeaderValueInt(dict, dictSize,
+            "MULTIPLE_WORDS_DEMOTION_RATE");
     if (headerValue == S_INT_MIN) {
         return 1.0f;
     }
diff --git a/native/jni/src/dictionary.cpp b/native/jni/src/dictionary.cpp
index c998c06..dadb2ba 100644
--- a/native/jni/src/dictionary.cpp
+++ b/native/jni/src/dictionary.cpp
@@ -34,9 +34,11 @@
 
 Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust)
         : mDict(static_cast<unsigned char *>(dict)),
-          mOffsetDict((static_cast<unsigned char *>(dict)) + BinaryFormat::getHeaderSize(mDict)),
+          mOffsetDict((static_cast<unsigned char *>(dict))
+                  + BinaryFormat::getHeaderSize(mDict, dictSize)),
           mDictSize(dictSize), mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust),
-          mUnigramDictionary(new UnigramDictionary(mOffsetDict, BinaryFormat::getFlags(mDict))),
+          mUnigramDictionary(new UnigramDictionary(mOffsetDict,
+                  BinaryFormat::getFlags(mDict, dictSize))),
           mBigramDictionary(new BigramDictionary(mOffsetDict)),
           mGestureSuggest(new Suggest(GestureSuggestPolicyFactory::getGestureSuggestPolicy())),
           mTypingSuggest(new Suggest(TypingSuggestPolicyFactory::getTypingSuggestPolicy())) {
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.cpp b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
index 5116585..6408f01 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.cpp
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
@@ -64,7 +64,8 @@
 void DicTraverseSession::init(const Dictionary *const dictionary, const int *prevWord,
         int prevWordLength) {
     mDictionary = dictionary;
-    mMultiWordCostMultiplier = BinaryFormat::getMultiWordCostMultiplier(mDictionary->getDict());
+    mMultiWordCostMultiplier = BinaryFormat::getMultiWordCostMultiplier(mDictionary->getDict(),
+            mDictionary->getDictSize());
     if (!prevWord) {
         mPrevWordPos = NOT_VALID_WORD;
         return;