Merge "Fix: BinaryDictionaryDecayingTests."
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index a638b23..9ba4620 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -34,7 +34,6 @@
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -71,6 +70,9 @@
 import com.android.inputmethod.latin.utils.ViewLayoutUtils;
 import com.android.inputmethod.research.ResearchLogger;
 
+import java.util.ArrayDeque;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.WeakHashMap;
 
 /**
@@ -158,7 +160,10 @@
     private final int mKeyPreviewLayoutId;
     private final int mKeyPreviewOffset;
     private final int mKeyPreviewHeight;
-    private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
+    // Free {@link TextView} pool that can be used for key preview.
+    private final ArrayDeque<TextView> mFreeKeyPreviewTextViews = CollectionUtils.newArrayDeque();
+    // Map from {@link Key} to {@link TextView} that is currently being displayed as key preview.
+    private final HashMap<Key,TextView> mShowingKeyPreviewTextViews = CollectionUtils.newHashMap();
     private final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams();
     private boolean mShowKeyPreviewPopup = true;
     private int mKeyPreviewLingerTimeout;
@@ -381,13 +386,18 @@
         public void handleMessage(final Message msg) {
             final MainKeyboardView mainKeyboardView = getOuterInstance();
             if (mainKeyboardView == null) return;
-            final PointerTracker tracker = (PointerTracker) msg.obj;
             switch (msg.what) {
             case MSG_DISMISS_KEY_PREVIEW:
-                final TextView previewText = mainKeyboardView.mKeyPreviewTexts.get(
-                        tracker.mPointerId);
-                if (previewText != null) {
-                    previewText.setVisibility(INVISIBLE);
+                final Key key = (Key)msg.obj;
+                if (key != null) {
+                    final TextView previewTextView =
+                            mainKeyboardView.mShowingKeyPreviewTextViews.remove(key);
+                    if (previewTextView != null) {
+                        previewTextView.setVisibility(INVISIBLE);
+                        mainKeyboardView.mFreeKeyPreviewTextViews.add(previewTextView);
+                    }
+                    // To redraw key top letter.
+                    mainKeyboardView.invalidateKey(key);
                 }
                 break;
             case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
@@ -396,12 +406,8 @@
             }
         }
 
-        public void dismissKeyPreview(final long delay, final PointerTracker tracker) {
-            sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
-        }
-
-        public void cancelDismissKeyPreview(final PointerTracker tracker) {
-            removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
+        public void dismissKeyPreview(final long delay, final Key key) {
+            sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, key), delay);
         }
 
         private void cancelAllDismissKeyPreviews() {
@@ -681,33 +687,34 @@
         return mShowKeyPreviewPopup;
     }
 
-    private void addKeyPreview(final TextView keyPreview) {
-        locatePreviewPlacerView();
-        mPreviewPlacerView.addView(
-                keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
-    }
-
-    private TextView getKeyPreviewText(final int pointerId) {
-        TextView previewText = mKeyPreviewTexts.get(pointerId);
-        if (previewText != null) {
-            return previewText;
+    private TextView getKeyPreviewTextView(final Key key) {
+        TextView previewTextView = mShowingKeyPreviewTextViews.remove(key);
+        if (previewTextView != null) {
+            return previewTextView;
+        }
+        previewTextView = mFreeKeyPreviewTextViews.poll();
+        if (previewTextView != null) {
+            return previewTextView;
         }
         final Context context = getContext();
         if (mKeyPreviewLayoutId != 0) {
-            previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
+            previewTextView = (TextView)LayoutInflater.from(context)
+                    .inflate(mKeyPreviewLayoutId, null);
         } else {
-            previewText = new TextView(context);
+            previewTextView = new TextView(context);
         }
-        mKeyPreviewTexts.put(pointerId, previewText);
-        return previewText;
+        locatePreviewPlacerView();
+        mPreviewPlacerView.addView(
+                previewTextView, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
+        return previewTextView;
     }
 
     private void dismissAllKeyPreviews() {
-        final int pointerCount = mKeyPreviewTexts.size();
-        for (int id = 0; id < pointerCount; id++) {
-            final TextView previewText = mKeyPreviewTexts.get(id);
-            if (previewText != null) {
-                previewText.setVisibility(INVISIBLE);
+        for (final Key key : new HashSet<Key>(mShowingKeyPreviewTextViews.keySet())) {
+            final TextView previewTextView = mShowingKeyPreviewTextViews.remove(key);
+            if (previewTextView != null) {
+                previewTextView.setVisibility(INVISIBLE);
+                mFreeKeyPreviewTextViews.add(previewTextView);
             }
         }
         PointerTracker.setReleasedKeyGraphicsToAllKeys();
@@ -735,23 +742,7 @@
     private static final int STATE_HAS_MOREKEYS = 1;
 
     @Override
-    public void showKeyPreview(final PointerTracker tracker) {
-        final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
-        final Keyboard keyboard = getKeyboard();
-        if (!mShowKeyPreviewPopup) {
-            previewParams.mPreviewVisibleOffset = -keyboard.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
-        // key preview absolutely in SoftInputWindow.
-        if (previewText.getParent() == null) {
-            addKeyPreview(previewText);
-        }
-
-        mDrawingHandler.cancelDismissKeyPreview(tracker);
-        final Key key = tracker.getKey();
+    public void showKeyPreview(final Key key) {
         // If key is invalid or IME is already closed, we must not show key preview.
         // Trying to show key preview while root window is closed causes
         // WindowManager.BadTokenException.
@@ -759,38 +750,47 @@
             return;
         }
 
+        final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
+        final Keyboard keyboard = getKeyboard();
+        if (!mShowKeyPreviewPopup) {
+            previewParams.mPreviewVisibleOffset = -keyboard.mVerticalGap;
+            return;
+        }
+
+        final TextView previewTextView = getKeyPreviewTextView(key);
         final KeyDrawParams drawParams = mKeyDrawParams;
-        previewText.setTextColor(drawParams.mPreviewTextColor);
-        final Drawable background = previewText.getBackground();
+        previewTextView.setTextColor(drawParams.mPreviewTextColor);
+        final Drawable background = previewTextView.getBackground();
         final String label = key.getPreviewLabel();
         // What we show as preview should match what we show on a key top in onDraw().
         if (label != null) {
             // TODO Should take care of temporaryShiftLabel here.
-            previewText.setCompoundDrawables(null, null, null, null);
-            previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+            previewTextView.setCompoundDrawables(null, null, null, null);
+            previewTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
                     key.selectPreviewTextSize(drawParams));
-            previewText.setTypeface(key.selectPreviewTypeface(drawParams));
-            previewText.setText(label);
+            previewTextView.setTypeface(key.selectPreviewTypeface(drawParams));
+            previewTextView.setText(label);
         } else {
-            previewText.setCompoundDrawables(null, null, null,
+            previewTextView.setCompoundDrawables(null, null, null,
                     key.getPreviewIcon(keyboard.mIconsSet));
-            previewText.setText(null);
+            previewTextView.setText(null);
         }
 
-        previewText.measure(
+        previewTextView.measure(
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
         final int keyDrawWidth = key.getDrawWidth();
-        final int previewWidth = previewText.getMeasuredWidth();
+        final int previewWidth = previewTextView.getMeasuredWidth();
         final int previewHeight = mKeyPreviewHeight;
         // The width and height of visible part of the key preview background. The content marker
         // of the background 9-patch have to cover the visible part of the background.
-        previewParams.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
-                - previewText.getPaddingRight();
-        previewParams.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
-                - previewText.getPaddingBottom();
+        previewParams.mPreviewVisibleWidth = previewWidth - previewTextView.getPaddingLeft()
+                - previewTextView.getPaddingRight();
+        previewParams.mPreviewVisibleHeight = previewHeight - previewTextView.getPaddingTop()
+                - previewTextView.getPaddingBottom();
         // The distance between the top edge of the parent key and the bottom of the visible part
         // of the key preview background.
-        previewParams.mPreviewVisibleOffset = mKeyPreviewOffset - previewText.getPaddingBottom();
+        previewParams.mPreviewVisibleOffset =
+                mKeyPreviewOffset - previewTextView.getPaddingBottom();
         getLocationInWindow(mOriginCoords);
         // 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
@@ -817,13 +817,14 @@
             background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
         }
         ViewLayoutUtils.placeViewAt(
-                previewText, previewX, previewY, previewWidth, previewHeight);
-        previewText.setVisibility(VISIBLE);
+                previewTextView, previewX, previewY, previewWidth, previewHeight);
+        previewTextView.setVisibility(VISIBLE);
+        mShowingKeyPreviewTextViews.put(key, previewTextView);
     }
 
     @Override
-    public void dismissKeyPreview(final PointerTracker tracker) {
-        mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, tracker);
+    public void dismissKeyPreview(final Key key) {
+        mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, key);
     }
 
     public void setSlidingKeyInputPreviewEnabled(final boolean enabled) {
@@ -1175,6 +1176,12 @@
         if (key.altCodeWhileTyping() && key.isEnabled()) {
             params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
         }
+        // Don't draw key top letter when key preview is showing.
+        if (mShowingKeyPreviewTextViews.containsKey(key)) {
+            // TODO: Fade out animation for the key top letter, and fade in animation for the key
+            // background color when the user presses the key.
+            return;
+        }
         final int code = key.getCode();
         if (code == Constants.CODE_SPACE) {
             drawSpacebar(key, canvas, paint);
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index 3851239..fca727b 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -223,7 +223,7 @@
         }
 
         public int getDefaultKeyCoordX() {
-            return mLeftKeys * mColumnWidth;
+            return mLeftKeys * mColumnWidth + mLeftPadding;
         }
 
         public int getX(final int n, final int row) {
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 973128d..8492d93 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -81,11 +81,13 @@
         mListener = listener;
         final View container = getContainerView();
         // The coordinates of panel's left-top corner in parentView's coordinate system.
-        final int x = pointX - getDefaultCoordX() - container.getPaddingLeft();
-        final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom();
+        // We need to consider background drawable paddings.
+        final int x = pointX - getDefaultCoordX() - container.getPaddingLeft() - getPaddingLeft();
+        final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom()
+                + getPaddingBottom();
 
         parentView.getLocationInWindow(mCoordinates);
-        // Ensure the horizontal position of the panel does not extend past the screen edges.
+        // Ensure the horizontal position of the panel does not extend past the parentView edges.
         final int maxX = parentView.getMeasuredWidth() - container.getMeasuredWidth();
         final int panelX = Math.max(0, Math.min(maxX, x)) + CoordinateUtils.x(mCoordinates);
         final int panelY = y + CoordinateUtils.y(mCoordinates);
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 52f190e..8860ed3 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -84,8 +84,8 @@
 
     public interface DrawingProxy {
         public void invalidateKey(Key key);
-        public void showKeyPreview(PointerTracker tracker);
-        public void dismissKeyPreview(PointerTracker tracker);
+        public void showKeyPreview(Key key);
+        public void dismissKeyPreview(Key key);
         public void showSlidingKeyInputPreview(PointerTracker tracker);
         public void dismissSlidingKeyInputPreview();
         public void showGestureTrail(PointerTracker tracker, boolean showsFloatingPreviewText);
@@ -637,7 +637,7 @@
     }
 
     private void setReleasedKeyGraphics(final Key key) {
-        mDrawingProxy.dismissKeyPreview(this);
+        mDrawingProxy.dismissKeyPreview(key);
         if (key == null) {
             return;
         }
@@ -685,7 +685,7 @@
         }
 
         if (!key.noKeyPreview() && !sInGesture && !needsToSuppressKeyPreviewPopup(eventTime)) {
-            mDrawingProxy.showKeyPreview(this);
+            mDrawingProxy.showKeyPreview(key);
         }
         updatePressKeyGraphics(key);
 
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index e770d98..2c5401b 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -97,7 +97,8 @@
         ver4_patricia_trie_node_reader.cpp \
         ver4_patricia_trie_node_writer.cpp \
         ver4_patricia_trie_policy.cpp \
-        ver4_patricia_trie_reading_utils.cpp ) \
+        ver4_patricia_trie_reading_utils.cpp \
+        ver4_patricia_trie_writing_helper.cpp) \
     $(addprefix suggest/policyimpl/dictionary/utils/, \
         buffer_with_extendable_buffer.cpp \
         byte_array_utils.cpp \
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
index 063b84c..903d553 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
@@ -17,13 +17,13 @@
 #include "suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h"
 
 #include <stdint.h>
-#include <string>
 
 #include "defines.h"
 #include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h"
 #include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_policy.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h"
+#include "suggest/policyimpl/dictionary/utils/file_utils.h"
 #include "suggest/policyimpl/dictionary/utils/format_utils.h"
 #include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
 
@@ -49,17 +49,15 @@
             return DictionaryStructureWithBufferPolicy::StructurePoilcyPtr(
                     new DynamicPatriciaTriePolicy(mmappedBuffer));
         case FormatUtils::VERSION_4: {
-            std::string dictDirPath(path);
-            const std::string::size_type pos =
-                    dictDirPath.rfind(Ver4DictConstants::TRIE_FILE_EXTENSION);
-            if (pos == std::string::npos) {
+            const int dictDirPathBufSize = strlen(path) + 1 /* terminator */;
+            char dictDirPath[dictDirPathBufSize];
+            if (!FileUtils::getFilePathWithoutSuffix(path, Ver4DictConstants::TRIE_FILE_EXTENSION,
+                    dictDirPathBufSize, dictDirPath)) {
                 // Dictionary file name is not valid as a version 4 dictionary.
                 return DictionaryStructureWithBufferPolicy::StructurePoilcyPtr(0);
             }
-            // Removing extension to get the base path.
-            dictDirPath.erase(pos);
             const Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers =
-                    Ver4DictBuffers::openVer4DictBuffers(dictDirPath.c_str(), mmappedBuffer);
+                    Ver4DictBuffers::openVer4DictBuffers(dictDirPath, mmappedBuffer);
             if (!dictBuffers.get()->isValid()) {
                 AKLOGE("DICT: The dictionary doesn't satisfy ver4 format requirements.");
                 ASSERT(false);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.h
index eaf18b5..873b240 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.h
@@ -67,8 +67,28 @@
         return mSize;
     }
 
-    bool flushToFile(const char *const dictDirPath) const {
-        return flush(dictDirPath, Ver4DictConstants::TERMINAL_ADDRESS_TABLE_FILE_EXTENSION);
+    bool flushToFile(const char *const dictDirPath, const int newHeaderRegionSize) const {
+        const int headerRegionSizeDiff = newHeaderRegionSize - mHeaderRegionSize;
+        // If header region size has been changed, terminal PtNode positions have to be adjusted
+        // depending on the new header region size.
+        if (headerRegionSizeDiff != 0) {
+            TerminalPositionLookupTable lookupTableToWrite;
+            for (int i = 0; i < mSize; ++i) {
+                const int terminalPtNodePosition = getTerminalPtNodePosition(i)
+                        + headerRegionSizeDiff;
+                if (!lookupTableToWrite.setTerminalPtNodePosition(i, terminalPtNodePosition)) {
+                    AKLOGE("Cannot set terminal position to lookupTableToWrite."
+                            " terminalId: %d, position: %d", i, terminalPtNodePosition);
+                    return false;
+                }
+            }
+            return lookupTableToWrite.flush(dictDirPath,
+                    Ver4DictConstants::TERMINAL_ADDRESS_TABLE_FILE_EXTENSION);
+        } else {
+            // We can simply use this lookup table because the header region size has not been
+            // changed.
+            return flush(dictDirPath, Ver4DictConstants::TERMINAL_ADDRESS_TABLE_FILE_EXTENSION);
+        }
     }
 
  private:
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp
index e17c5ea..d312531 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp
@@ -25,7 +25,8 @@
 
 namespace latinime {
 
-bool Ver4DictBuffers::flush(const char *const dictDirPath) const {
+bool Ver4DictBuffers::flushHeaderAndDictBuffers(const char *const dictDirPath,
+        const BufferWithExtendableBuffer *const headerBuffer) const {
     // Create temporary directory.
     const int tmpDirPathBufSize = FileUtils::getFilePathWithSuffixBufSize(dictDirPath,
             DictFileWritingUtils::TEMP_FILE_SUFFIX_FOR_WRITING_DICT_FILE);
@@ -38,8 +39,7 @@
         return false;
     }
     // Write trie file.
-    const BufferWithExtendableBuffer *buffers[] =
-            {&mExpandableHeaderBuffer, &mExpandableTrieBuffer};
+    const BufferWithExtendableBuffer *buffers[] = {headerBuffer, &mExpandableTrieBuffer};
     if (!DictFileWritingUtils::flushBuffersToFileInDir(tmpDirPath,
             Ver4DictConstants::TRIE_FILE_EXTENSION, buffers, 2 /* bufferCount */)) {
         AKLOGE("Dictionary trie file %s/%s cannot be written.", tmpDirPath,
@@ -47,7 +47,7 @@
         return false;
     }
     // Write dictionary contents.
-    if (!mTerminalPositionLookupTable.flushToFile(tmpDirPath)) {
+    if (!mTerminalPositionLookupTable.flushToFile(tmpDirPath, headerBuffer->getTailPosition())) {
         AKLOGE("Terminal position lookup table cannot be written. %s", tmpDirPath);
         return false;
     }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h
index 0684bdd..bfd0bbd 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h
@@ -57,6 +57,10 @@
         return &mExpandableTrieBuffer;
     }
 
+    AK_FORCE_INLINE const BufferWithExtendableBuffer *getTrieBuffer() const {
+        return &mExpandableTrieBuffer;
+    }
+
     AK_FORCE_INLINE TerminalPositionLookupTable *getUpdatableTerminalPositionLookupTable() {
         return &mTerminalPositionLookupTable;
     }
@@ -89,7 +93,12 @@
         return mIsUpdatable;
     }
 
-    bool flush(const char *const dictDirPath) const;
+    bool flush(const char *const dictDirPath) const {
+        return flushHeaderAndDictBuffers(dictDirPath, &mExpandableHeaderBuffer);
+    }
+
+    bool flushHeaderAndDictBuffers(const char *const dictDirPath,
+            const BufferWithExtendableBuffer *const headerBuffer) const;
 
  private:
     DISALLOW_COPY_AND_ASSIGN(Ver4DictBuffers);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
index 698483a..8ee15e0 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
@@ -216,7 +216,11 @@
 }
 
 void Ver4PatriciaTriePolicy::flush(const char *const filePath) {
-    // TODO: Implement.
+    if (!mBuffers.get()->isUpdatable()) {
+        AKLOGI("Warning: flush() is called for non-updatable dictionary. filePath: %s", filePath);
+        return;
+    }
+    mWritingHelper.writeToDictFile(filePath, &mHeaderPolicy, mUnigramCount, mBigramCount);
 }
 
 void Ver4PatriciaTriePolicy::flushWithGC(const char *const filePath) {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h
index e8fdf55..605de96 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h
@@ -26,6 +26,7 @@
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.h"
 #include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
 
 namespace latinime {
@@ -50,6 +51,7 @@
                       &mShortcutPolicy),
               mUpdatingHelper(mDictBuffer, &mNodeReader, &mNodeWriter,
                       mHeaderPolicy.isDecayingDict()),
+              mWritingHelper(mBuffers.get()),
               mUnigramCount(mHeaderPolicy.getUnigramCount()),
               mBigramCount(mHeaderPolicy.getBigramCount()) {};
 
@@ -120,6 +122,7 @@
     Ver4PatriciaTrieNodeReader mNodeReader;
     Ver4PatriciaTrieNodeWriter mNodeWriter;
     DynamicPatriciaTrieUpdatingHelper mUpdatingHelper;
+    Ver4PatriciaTrieWritingHelper mWritingHelper;
     int mUnigramCount;
     int mBigramCount;
 };
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp
new file mode 100644
index 0000000..c85a632
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.h"
+
+#include <cstring>
+
+#include "suggest/policyimpl/dictionary/header/header_policy.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+#include "suggest/policyimpl/dictionary/utils/file_utils.h"
+
+namespace latinime {
+
+void Ver4PatriciaTrieWritingHelper::writeToDictFile(const char *const trieFilePath,
+        const HeaderPolicy *const headerPolicy, const int unigramCount,
+        const int bigramCount) const {
+    const int dirPathBufSize = strlen(trieFilePath) + 1 /* terminator */;
+    char dirPath[dirPathBufSize];
+    FileUtils::getDirPath(trieFilePath, dirPathBufSize, dirPath);
+    BufferWithExtendableBuffer headerBuffer(
+            BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE);
+    const int extendedRegionSize = headerPolicy->getExtendedRegionSize()
+            + mBuffers->getTrieBuffer()->getUsedAdditionalBufferSize();
+    if (!headerPolicy->writeHeaderToBuffer(&headerBuffer, false /* updatesLastUpdatedTime */,
+            false /* updatesLastDecayedTime */, unigramCount, bigramCount, extendedRegionSize)) {
+        AKLOGE("Cannot write header structure to buffer. updatesLastUpdatedTime: %d, "
+                "updatesLastDecayedTime: %d, unigramCount: %d, bigramCount: %d, "
+                "extendedRegionSize: %d", false, false, unigramCount, bigramCount,
+                extendedRegionSize);
+        return;
+    }
+    mBuffers->flushHeaderAndDictBuffers(dirPath, &headerBuffer);
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.h
new file mode 100644
index 0000000..80d6315
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_VER4_PATRICIA_TRIE_WRITING_HELPER_H
+#define LATINIME_VER4_PATRICIA_TRIE_WRITING_HELPER_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class HeaderPolicy;
+class Ver4DictBuffers;
+
+class Ver4PatriciaTrieWritingHelper {
+ public:
+    Ver4PatriciaTrieWritingHelper(Ver4DictBuffers *const buffers)
+            : mBuffers(buffers) {}
+
+    void writeToDictFile(const char *const trieFilePath, const HeaderPolicy *const headerPolicy,
+            const int unigramCount, const int bigramCount) const;
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(Ver4PatriciaTrieWritingHelper);
+
+    Ver4DictBuffers *const mBuffers;
+};
+} // namespace latinime
+
+#endif /* LATINIME_VER4_PATRICIA_TRIE_WRITING_HELPER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/file_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/file_utils.cpp
index 1748d5a..dedcd7a 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/file_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/file_utils.cpp
@@ -88,4 +88,43 @@
     snprintf(outFilePath, filePathBufSize, "%s/%s", dirPath, fileName);
 }
 
+/* static */ bool FileUtils::getFilePathWithoutSuffix(const char *const filePath,
+        const char *const suffix, const int outDirPathBufSize, char *const outDirPath) {
+    const int filePathLength = strlen(filePath);
+    const int suffixLength = strlen(suffix);
+    if (filePathLength <= suffixLength) {
+        AKLOGE("File path length (%s:%d) is shorter that suffix length (%s:%d).",
+                filePath, filePathLength, suffix, suffixLength);
+        return false;
+    }
+    const int resultFilePathLength = filePathLength - suffixLength;
+    if (outDirPathBufSize <= resultFilePathLength) {
+        AKLOGE("outDirPathBufSize is too small. filePath: %s, suffix: %s, outDirPathBufSize: %d",
+                filePath, suffix, outDirPathBufSize);
+        return false;
+    }
+    if (strncmp(filePath + resultFilePathLength, suffix, suffixLength) != 0) {
+        AKLOGE("File Path %s does not have %s as a suffix", filePath, suffix);
+        return false;
+    }
+    snprintf(outDirPath, resultFilePathLength + 1 /* terminator */, "%s", filePath);
+    return true;
+}
+
+/* static */ void FileUtils::getDirPath(const char *const filePath, const int outDirPathBufSize,
+        char *const outDirPath) {
+    for (int i = strlen(filePath) - 1; i >= 0; --i) {
+        if (filePath[i] == '/') {
+            if (i >= outDirPathBufSize) {
+                AKLOGE("outDirPathBufSize is too small. filePath: %s, outDirPathBufSize: %d",
+                        filePath, outDirPathBufSize);
+                ASSERT(false);
+                return;
+            }
+            snprintf(outDirPath, i + 1 /* terminator */, "%s", filePath);
+            return;
+        }
+    }
+}
+
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/file_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/file_utils.h
index fc27aee..7dcdef8 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/file_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/file_utils.h
@@ -39,6 +39,13 @@
     static void getFilePath(const char *const dirPath, const char *const fileName,
             const int filePathBufSize, char *const outFilePath);
 
+    // Returns whether the filePath have the suffix.
+    static bool getFilePathWithoutSuffix(const char *const filePath, const char *const suffix,
+            const int dirPathBufSize, char *const outDirPath);
+
+    static void getDirPath(const char *const filePath, const int dirPathBufSize,
+            char *const outDirPath);
+
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(FileUtils);
 };
diff --git a/tests/src/com/android/inputmethod/latin/Ver4BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/Ver4BinaryDictionaryTests.java
index 15d990c..b51a86b 100644
--- a/tests/src/com/android/inputmethod/latin/Ver4BinaryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/Ver4BinaryDictionaryTests.java
@@ -250,4 +250,51 @@
         binaryDictionary.removeBigramWords("bcc", "aaa");
     }
 
+    public void testFlushDictionary() {
+        final String dictVersion = Long.toString(System.currentTimeMillis());
+        File trieFile = null;
+        try {
+            trieFile = createEmptyDictionaryAndGetTrieFile(dictVersion);
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(trieFile.getAbsolutePath(),
+                0 /* offset */, trieFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        final int probability = 100;
+        binaryDictionary.addUnigramWord("aaa", probability);
+        binaryDictionary.addUnigramWord("abcd", probability);
+        // Close without flushing.
+        binaryDictionary.close();
+
+        binaryDictionary = new BinaryDictionary(trieFile.getAbsolutePath(),
+                0 /* offset */, trieFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        assertEquals(Dictionary.NOT_A_PROBABILITY, binaryDictionary.getFrequency("aaa"));
+        assertEquals(Dictionary.NOT_A_PROBABILITY, binaryDictionary.getFrequency("abcd"));
+
+        binaryDictionary.addUnigramWord("aaa", probability);
+        binaryDictionary.addUnigramWord("abcd", probability);
+        binaryDictionary.flush();
+        binaryDictionary.close();
+
+        binaryDictionary = new BinaryDictionary(trieFile.getAbsolutePath(),
+                0 /* offset */, trieFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        assertEquals(probability, binaryDictionary.getFrequency("aaa"));
+        assertEquals(probability, binaryDictionary.getFrequency("abcd"));
+        binaryDictionary.addUnigramWord("bcde", probability);
+        binaryDictionary.flush();
+        binaryDictionary.close();
+
+        binaryDictionary = new BinaryDictionary(trieFile.getAbsolutePath(),
+                0 /* offset */, trieFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        assertEquals(probability, binaryDictionary.getFrequency("bcde"));
+        binaryDictionary.close();
+    }
+
 }