Merge "Introduce ExtendableBuffer for dynamic update."
diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
index 269b3a2..55df263 100644
--- a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
@@ -19,10 +19,11 @@
 import android.content.Context;
 import android.util.Log;
 
+import com.android.inputmethod.latin.makedict.DictEncoder;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 
 // TODO: Quit extending Dictionary after implementing dynamic binary dictionary.
@@ -49,32 +50,21 @@
 
     abstract public void removeBigramWords(final String word0, final String word1);
 
-    abstract protected void writeBinaryDictionary(final FileOutputStream out)
+    abstract protected void writeDictionary(final DictEncoder dictEncoder)
             throws IOException, UnsupportedFormatException;
 
     public void write(final String fileName) {
         final String tempFileName = fileName + ".temp";
         final File file = new File(mContext.getFilesDir(), fileName);
         final File tempFile = new File(mContext.getFilesDir(), tempFileName);
-        FileOutputStream out = null;
         try {
-            out = new FileOutputStream(tempFile);
-            writeBinaryDictionary(out);
-            out.flush();
-            out.close();
+            final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+            writeDictionary(dictEncoder);
             tempFile.renameTo(file);
         } catch (IOException e) {
             Log.e(TAG, "IO exception while writing file", e);
         } catch (UnsupportedFormatException e) {
             Log.e(TAG, "Unsupported format", e);
-        } finally {
-            if (out != null) {
-                try {
-                    out.close();
-                } catch (IOException e) {
-                    // ignore
-                }
-            }
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryWriter.java b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
index b3a5728..a97e053 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
@@ -20,7 +20,7 @@
 
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.makedict.BinaryDictEncoderUtils;
+import com.android.inputmethod.latin.makedict.DictEncoder;
 import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
@@ -28,7 +28,6 @@
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -85,9 +84,9 @@
     }
 
     @Override
-    protected void writeBinaryDictionary(final FileOutputStream out)
+    protected void writeDictionary(final DictEncoder dictEncoder)
             throws IOException, UnsupportedFormatException {
-        BinaryDictEncoderUtils.writeDictionaryBinary(out, mFusionDictionary, FORMAT_OPTIONS);
+        dictEncoder.writeDictionary(mFusionDictionary, FORMAT_OPTIONS);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d67195b..3a2de59 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -73,6 +73,7 @@
 import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
 import com.android.inputmethod.latin.personalization.PersonalizationDictionaryHelper;
 import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegister;
 import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
@@ -173,6 +174,7 @@
     private UserBinaryDictionary mUserDictionary;
     private UserHistoryPredictionDictionary mUserHistoryPredictionDictionary;
     private PersonalizationPredictionDictionary mPersonalizationPredictionDictionary;
+    private PersonalizationDictionary mPersonalizationDictionary;
     private boolean mIsUserDictionaryAvailable;
 
     private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
@@ -567,6 +569,9 @@
         mUserHistoryPredictionDictionary = PersonalizationDictionaryHelper
                 .getUserHistoryPredictionDictionary(this, localeStr, prefs);
         newSuggest.setUserHistoryPredictionDictionary(mUserHistoryPredictionDictionary);
+        mPersonalizationDictionary = PersonalizationDictionaryHelper
+                .getPersonalizationDictionary(this, localeStr, prefs);
+        newSuggest.setPersonalizationDictionary(mPersonalizationDictionary);
         mPersonalizationPredictionDictionary = PersonalizationDictionaryHelper
                 .getPersonalizationPredictionDictionary(this, localeStr, prefs);
         newSuggest.setPersonalizationPredictionDictionary(mPersonalizationPredictionDictionary);
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 8766e0f..c8a151a 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -24,6 +24,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
 import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
 import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
 import com.android.inputmethod.latin.settings.Settings;
@@ -200,6 +201,12 @@
                 personalizationPredictionDictionary);
     }
 
+    public void setPersonalizationDictionary(
+            final PersonalizationDictionary personalizationDictionary) {
+        addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION,
+                personalizationDictionary);
+    }
+
     public void setAutoCorrectionThreshold(float threshold) {
         mAutoCorrectionThreshold = threshold;
     }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
index 3fe3ae6..71c1d42 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
@@ -911,13 +911,11 @@
     /**
      * Dumps a FusionDictionary to a file.
      *
-     * This is the public entry point to write a dictionary to a file.
-     *
      * @param destination the stream to write the binary data to.
      * @param dict the dictionary to write.
      * @param formatOptions file format options.
      */
-    public static void writeDictionaryBinary(final OutputStream destination,
+    /* package */ static void writeDictionaryBinary(final OutputStream destination,
             final FusionDictionary dict, final FormatOptions formatOptions)
             throws IOException, UnsupportedFormatException {
 
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index 8e1e14e..9f8842c 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -375,7 +375,7 @@
      * @param info the char group info to be written.
      * @return the size written, in bytes.
      */
-    public static int writeCharGroup(final OutputStream destination, final CharGroupInfo info)
+    private static int writeCharGroup(final OutputStream destination, final CharGroupInfo info)
             throws IOException {
         int size = FormatSpec.GROUP_FLAGS_SIZE;
         destination.write((byte)info.mFlags);
@@ -507,29 +507,6 @@
         return size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
     }
 
-    /**
-     * Find a word using the Ver3DictDecoder.
-     *
-     * @param dictDecoder the dict reader
-     * @param word the word searched
-     * @return the found group
-     * @throws IOException
-     * @throws UnsupportedFormatException
-     */
-    @UsedForTesting
-    public static CharGroupInfo findWordByBinaryDictReader(final Ver3DictDecoder dictDecoder,
-            final String word) throws IOException, UnsupportedFormatException {
-        int position = getTerminalPosition(dictDecoder, word);
-        final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
-        if (position != FormatSpec.NOT_VALID_WORD) {
-            dictBuffer.position(0);
-            final FileHeader header = dictDecoder.readHeader();
-            dictBuffer.position(position);
-            return dictDecoder.readPtNode(position, header.mFormatOptions);
-        }
-        return null;
-    }
-
     private static final int HEADER_READING_BUFFER_SIZE = 16384;
     /**
      * Convenience method to read the header of a binary file.
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java
new file mode 100644
index 0000000..89c982e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java
@@ -0,0 +1,29 @@
+/*
+ * 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.makedict;
+
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+
+import java.io.IOException;
+
+/**
+ * An interface of binary dictionary encoder.
+ */
+public interface DictEncoder {
+    public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions)
+            throws IOException, UnsupportedFormatException;
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
new file mode 100644
index 0000000..e81fd45
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
@@ -0,0 +1,68 @@
+/*
+ * 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.makedict;
+
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An implementation of DictEncoder for version 3 binary dictionary.
+ */
+public class Ver3DictEncoder implements DictEncoder {
+
+    private final File mDictFile;
+    private OutputStream mOutStream;
+
+    public Ver3DictEncoder(final File dictFile) {
+        mDictFile = dictFile;
+        mOutStream = null;
+    }
+
+    // This constructor is used only by BinaryDictOffdeviceUtilsTests.
+    // If you want to use this in the production code, you should consider keeping consistency of
+    // the interface of Ver3DictDecoder by using factory.
+    public Ver3DictEncoder(final OutputStream outStream) {
+        mDictFile = null;
+        mOutStream = outStream;
+    }
+
+    private void openStream() throws FileNotFoundException {
+        mOutStream = new FileOutputStream(mDictFile);
+    }
+
+    private void close() throws IOException {
+        if (mOutStream != null) {
+            mOutStream.close();
+            mOutStream = null;
+        }
+    }
+
+    @Override
+    public void writeDictionary(FusionDictionary dict, FormatOptions formatOptions)
+            throws IOException, UnsupportedFormatException {
+        if (mOutStream == null) {
+            openStream();
+        }
+        BinaryDictEncoderUtils.writeDictionaryBinary(mOutStream, dict, formatOptions);
+        close();
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
index b565b2f..d046da8 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
@@ -28,8 +28,10 @@
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.makedict.DictEncoder;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
+import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils;
@@ -40,7 +42,6 @@
 
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.concurrent.atomic.AtomicReference;
@@ -332,24 +333,9 @@
             final String fileName =
                     mDynamicPredictionDictionary.getDictionaryFileName();
             final File file = new File(mContext.getFilesDir(), fileName);
-            FileOutputStream out = null;
 
-            try {
-                out = new FileOutputStream(file);
-                UserHistoryDictIOUtils.writeDictionaryBinary(out, this, mBigramList, VERSION3);
-                out.flush();
-                out.close();
-            } catch (IOException e) {
-                Log.e(TAG, "IO Exception while writing file", e);
-            } finally {
-                if (out != null) {
-                    try {
-                        out.close();
-                    } catch (IOException e) {
-                        // ignore
-                    }
-                }
-            }
+            final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+            UserHistoryDictIOUtils.writeDictionary(dictEncoder, this, mBigramList, VERSION3);
 
             // Save the timestamp after we finish writing the binary dictionary.
             Settings.writeLastUserHistoryWriteTime(mPrefs, mLocale);
@@ -403,7 +389,7 @@
     }
 
     public void registerUpdateSession(PersonalizationDictionaryUpdateSession session) {
-        session.setPredictionDictionary(mLocale, this);
+        session.setPredictionDictionary(this);
         mSessions.add(session);
         session.onDictionaryReady();
     }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
index 275ce2f..f257165 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
@@ -18,28 +18,32 @@
 
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.util.ArrayList;
 
 /**
  * This class is a dictionary for the personalized language model that uses binary dictionary.
  */
 public class PersonalizationDictionary extends ExpandableBinaryDictionary {
     private static final String NAME = "personalization";
-
-    public static void registerUpdateListener(PersonalizationDictionaryUpdateSession listener) {
-        // TODO: Implement
-    }
+    private final ArrayList<PersonalizationDictionaryUpdateSession> mSessions =
+            CollectionUtils.newArrayList();
 
     /** Locale for which this user history dictionary is storing words */
     private final String mLocale;
 
-    // Singleton
-    private PersonalizationDictionary(final Context context, final String locale) {
+    public PersonalizationDictionary(final Context context, final String locale,
+            final SharedPreferences prefs) {
         // TODO: Make isUpdatable true.
         super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_PERSONALIZATION,
                 false /* isUpdatable */);
         mLocale = locale;
+        // TODO: Restore last updated time
+        loadDictionary();
     }
 
     @Override
@@ -49,15 +53,21 @@
 
     @Override
     protected boolean hasContentChanged() {
-        // TODO: Implement
         return false;
     }
 
     @Override
     protected boolean needsToReloadBeforeWriting() {
-        // TODO: Implement
         return false;
     }
 
-    // TODO: Implement
+    public void registerUpdateSession(PersonalizationDictionaryUpdateSession session) {
+        session.setDictionary(this);
+        mSessions.add(session);
+        session.onDictionaryReady();
+    }
+
+    public void unRegisterUpdateSession(PersonalizationDictionaryUpdateSession session) {
+        mSessions.remove(session);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
index b4fd250..6798f136 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
@@ -35,7 +35,13 @@
 
     private static final ConcurrentHashMap<String,
             SoftReference<PersonalizationPredictionDictionary>>
-                    sLangPersonalizationDictCache = CollectionUtils.newConcurrentHashMap();
+                    sLangPersonalizationPredictionDictCache =
+                            CollectionUtils.newConcurrentHashMap();
+
+    private static final ConcurrentHashMap<String,
+            SoftReference<PersonalizationDictionary>>
+                    sLangPersonalizationDictCache =
+                            CollectionUtils.newConcurrentHashMap();
 
     public static UserHistoryPredictionDictionary getUserHistoryPredictionDictionary(
             final Context context, final String locale, final SharedPreferences sp) {
@@ -60,20 +66,45 @@
     }
 
     public static void registerPersonalizationDictionaryUpdateSession(final Context context,
-            final PersonalizationDictionaryUpdateSession session) {
-        final PersonalizationPredictionDictionary dictionary =
-                getPersonalizationPredictionDictionary(context,
-                        context.getResources().getConfiguration().locale.toString(),
+            final PersonalizationDictionaryUpdateSession session, String locale) {
+        final PersonalizationPredictionDictionary predictionDictionary =
+                getPersonalizationPredictionDictionary(context, locale,
+                        PreferenceManager.getDefaultSharedPreferences(context));
+        predictionDictionary.registerUpdateSession(session);
+        final PersonalizationDictionary dictionary =
+                getPersonalizationDictionary(context, locale,
                         PreferenceManager.getDefaultSharedPreferences(context));
         dictionary.registerUpdateSession(session);
     }
 
-    public static PersonalizationPredictionDictionary getPersonalizationPredictionDictionary(
+    public static PersonalizationDictionary getPersonalizationDictionary(
             final Context context, final String locale, final SharedPreferences sp) {
         synchronized (sLangPersonalizationDictCache) {
             if (sLangPersonalizationDictCache.containsKey(locale)) {
-                final SoftReference<PersonalizationPredictionDictionary> ref =
+                final SoftReference<PersonalizationDictionary> ref =
                         sLangPersonalizationDictCache.get(locale);
+                final PersonalizationDictionary dict = ref == null ? null : ref.get();
+                if (dict != null) {
+                    if (DEBUG) {
+                        Log.w(TAG, "Use cached PersonalizationDictCache for " + locale);
+                    }
+                    return dict;
+                }
+            }
+            final PersonalizationDictionary dict =
+                    new PersonalizationDictionary(context, locale, sp);
+            sLangPersonalizationDictCache.put(
+                    locale, new SoftReference<PersonalizationDictionary>(dict));
+            return dict;
+        }
+    }
+
+    public static PersonalizationPredictionDictionary getPersonalizationPredictionDictionary(
+            final Context context, final String locale, final SharedPreferences sp) {
+        synchronized (sLangPersonalizationPredictionDictCache) {
+            if (sLangPersonalizationPredictionDictCache.containsKey(locale)) {
+                final SoftReference<PersonalizationPredictionDictionary> ref =
+                        sLangPersonalizationPredictionDictCache.get(locale);
                 final PersonalizationPredictionDictionary dict = ref == null ? null : ref.get();
                 if (dict != null) {
                     if (DEBUG) {
@@ -84,7 +115,7 @@
             }
             final PersonalizationPredictionDictionary dict =
                     new PersonalizationPredictionDictionary(context, locale, sp);
-            sLangPersonalizationDictCache.put(
+            sLangPersonalizationPredictionDictCache.put(
                     locale, new SoftReference<PersonalizationPredictionDictionary>(dict));
             return dict;
         }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
index 77f0cdb..9788049 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin.personalization;
 
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+
 import android.content.Context;
 
 import java.lang.ref.WeakReference;
@@ -45,22 +47,41 @@
     }
 
     // TODO: Use a dynamic binary dictionary instead
+    public WeakReference<PersonalizationDictionary> mDictionary;
     public WeakReference<DynamicPredictionDictionaryBase> mPredictionDictionary;
-    public String mLocale;
+    public final String mLocale;
+    public PersonalizationDictionaryUpdateSession(String locale) {
+        mLocale = locale;
+    }
 
     public abstract void onDictionaryReady();
 
     public abstract void onDictionaryClosed(Context context);
 
-    public void setPredictionDictionary(String locale, DynamicPredictionDictionaryBase dictionary) {
+    public void setDictionary(PersonalizationDictionary dictionary) {
+        mDictionary = new WeakReference<PersonalizationDictionary>(dictionary);
+    }
+
+    public void setPredictionDictionary(DynamicPredictionDictionaryBase dictionary) {
         mPredictionDictionary = new WeakReference<DynamicPredictionDictionaryBase>(dictionary);
-        mLocale = locale;
+    }
+
+    protected PersonalizationDictionary getDictionary() {
+        return mDictionary == null ? null : mDictionary.get();
     }
 
     protected DynamicPredictionDictionaryBase getPredictionDictionary() {
         return mPredictionDictionary == null ? null : mPredictionDictionary.get();
     }
 
+    private void unsetDictionary() {
+        final PersonalizationDictionary dictionary = getDictionary();
+        if (dictionary == null) {
+            return;
+        }
+        dictionary.unRegisterUpdateSession(this);
+    }
+
     private void unsetPredictionDictionary() {
         final DynamicPredictionDictionaryBase dictionary = getPredictionDictionary();
         if (dictionary == null) {
@@ -78,6 +99,7 @@
     }
 
     public void closeSession(Context context) {
+        unsetDictionary();
         unsetPredictionDictionary();
         onDictionaryClosed(context);
     }
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
index 771db3a..9d3d8a5 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
@@ -19,8 +19,8 @@
 import android.util.Log;
 
 import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.BinaryDictEncoderUtils;
 import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
+import com.android.inputmethod.latin.makedict.DictEncoder;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
@@ -30,7 +30,6 @@
 import com.android.inputmethod.latin.personalization.UserHistoryDictionaryBigramList;
 
 import java.io.IOException;
-import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
@@ -57,12 +56,12 @@
     /**
      * Writes dictionary to file.
      */
-    public static void writeDictionaryBinary(final OutputStream destination,
+    public static void writeDictionary(final DictEncoder dictEncoder,
             final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams,
             final FormatOptions formatOptions) {
         final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams);
         try {
-            BinaryDictEncoderUtils.writeDictionaryBinary(destination, fusionDict, formatOptions);
+            dictEncoder.writeDictionary(fusionDict, formatOptions);
             Log.d(TAG, "end writing");
         } catch (IOException e) {
             Log.e(TAG, "IO exception while writing file", e);
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
index f30be97..83ccdec 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
@@ -32,7 +32,6 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -204,17 +203,14 @@
         long now = -1, diff = -1;
 
         try {
-            final FileOutputStream out = new FileOutputStream(file);
+            final DictEncoder dictEncoder = new Ver3DictEncoder(file);
 
             now = System.currentTimeMillis();
             // If you need to dump the dict to a textual file, uncomment the line below and the
             // function above
             // dumpToCombinedFileForDebug(file, "/tmp/foo");
-            BinaryDictEncoderUtils.writeDictionaryBinary(out, dict, formatOptions);
+            dictEncoder.writeDictionary(dict, formatOptions);
             diff = System.currentTimeMillis() - now;
-
-            out.flush();
-            out.close();
         } catch (IOException e) {
             Log.e(TAG, "IO exception while writing file", e);
         } catch (UnsupportedFormatException e) {
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
index 68976a7..bfdc040 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
@@ -161,13 +161,35 @@
         return position;
     }
 
+    /**
+     * Find a word using the Ver3DictDecoder.
+     *
+     * @param dictDecoder the dict decoder
+     * @param word the word searched
+     * @return the found group
+     * @throws IOException
+     * @throws UnsupportedFormatException
+     */
+    private static CharGroupInfo findWordByBinaryDictReader(final Ver3DictDecoder dictDecoder,
+            final String word) throws IOException, UnsupportedFormatException {
+        int position = BinaryDictIOUtils.getTerminalPosition(dictDecoder, word);
+        final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
+        if (position != FormatSpec.NOT_VALID_WORD) {
+            dictBuffer.position(0);
+            final FileHeader header = dictDecoder.readHeader();
+            dictBuffer.position(position);
+            return dictDecoder.readPtNode(position, header.mFormatOptions);
+        }
+        return null;
+    }
+
     private CharGroupInfo findWordFromFile(final File file, final String word) {
         final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file);
         CharGroupInfo info = null;
         try {
             dictDecoder.openDictBuffer(
                     new Ver3DictDecoder.DictionaryBufferFromReadOnlyByteBufferFactory());
-            info = BinaryDictIOUtils.findWordByBinaryDictReader(dictDecoder, word);
+            info = findWordByBinaryDictReader(dictDecoder, word);
         } catch (IOException e) {
         } catch (UnsupportedFormatException e) {
         }
@@ -252,9 +274,8 @@
         dict.add("abcd", 10, null, false);
 
         try {
-            final FileOutputStream out = new FileOutputStream(file);
-            BinaryDictEncoderUtils.writeDictionaryBinary(out, dict, FORMAT_OPTIONS);
-            out.close();
+            final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+            dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         } catch (UnsupportedFormatException e) {
@@ -304,9 +325,8 @@
         dict.add("efgh", 15, null, false);
 
         try {
-            final FileOutputStream out = new FileOutputStream(file);
-            BinaryDictEncoderUtils.writeDictionaryBinary(out, dict, FORMAT_OPTIONS);
-            out.close();
+            final DictEncoder dictEncoder = new Ver3DictEncoder(file); 
+            dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         } catch (UnsupportedFormatException e) {
@@ -342,9 +362,8 @@
         dict.add("initial", 10, null, false);
 
         try {
-            final FileOutputStream out = new FileOutputStream(file);
-            BinaryDictEncoderUtils.writeDictionaryBinary(out, dict, FORMAT_OPTIONS);
-            out.close();
+            final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+            dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
         } catch (IOException e) {
             assertTrue(false);
         } catch (UnsupportedFormatException e) {
diff --git a/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
index eca12c0..7b3a01c 100644
--- a/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
@@ -21,17 +21,18 @@
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
+import com.android.inputmethod.latin.makedict.DictEncoder;
 import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
 import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
+import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
 import com.android.inputmethod.latin.personalization.UserHistoryDictionaryBigramList;
 import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.BigramDictionaryInterface;
 import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.OnAddWordListener;
 
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -136,14 +137,8 @@
 
     private void writeDictToFile(final File file,
             final UserHistoryDictionaryBigramList bigramList) {
-        try {
-            final FileOutputStream out = new FileOutputStream(file);
-            UserHistoryDictIOUtils.writeDictionaryBinary(out, this, bigramList, FORMAT_OPTIONS);
-            out.flush();
-            out.close();
-        } catch (IOException e) {
-            Log.e(TAG, "IO exception while writing file", e);
-        }
+        final DictEncoder dictEncoder = new Ver3DictEncoder(file);
+        UserHistoryDictIOUtils.writeDictionary(dictEncoder, this, bigramList, FORMAT_OPTIONS);
     }
 
     private void readDictFromFile(final File file, final OnAddWordListener listener) {
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
index fee4453..6c1a9de 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
@@ -17,18 +17,18 @@
 package com.android.inputmethod.latin.dicttool;
 
 import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils;
-import com.android.inputmethod.latin.makedict.BinaryDictEncoderUtils;
+import com.android.inputmethod.latin.makedict.DictEncoder;
 import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.MakedictLog;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
+import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
 
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.util.Arrays;
@@ -358,8 +358,8 @@
             throws FileNotFoundException, IOException, UnsupportedFormatException {
         final File outputFile = new File(outputFilename);
         final FormatSpec.FormatOptions formatOptions = new FormatSpec.FormatOptions(version);
-        BinaryDictEncoderUtils.writeDictionaryBinary(new FileOutputStream(outputFilename), dict,
-                formatOptions);
+        final DictEncoder dictEncoder = new Ver3DictEncoder(outputFile);
+        dictEncoder.writeDictionary(dict, formatOptions);
     }
 
     /**
diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
index da51387..2b2661a 100644
--- a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
+++ b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
@@ -17,13 +17,14 @@
 package com.android.inputmethod.latin.dicttool;
 
 import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils;
-import com.android.inputmethod.latin.makedict.BinaryDictEncoderUtils;
+import com.android.inputmethod.latin.makedict.DictEncoder;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
+import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
 
 import junit.framework.TestCase;
 
@@ -53,12 +54,13 @@
 
         final File dst = File.createTempFile("testGetRawDict", ".tmp");
         dst.deleteOnExit();
+
         final OutputStream out = Compress.getCompressedStream(
                 Compress.getCompressedStream(
                         Compress.getCompressedStream(
                                 new BufferedOutputStream(new FileOutputStream(dst)))));
-
-        BinaryDictEncoderUtils.writeDictionaryBinary(out, dict, new FormatOptions(2, false));
+        final DictEncoder dictEncoder = new Ver3DictEncoder(out);
+        dictEncoder.writeDictionary(dict, new FormatOptions(2, false));
 
         // Test for an actually compressed dictionary and its contents
         final BinaryDictOffdeviceUtils.DecoderChainSpec decodeSpec =