Merge "Make KeySpecParser case insensitive"
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index efb0d77..d50d096 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -467,6 +467,9 @@
         mSpaceIcon = (mSpaceKey != null) ? mSpaceKey.getIcon(keyboard.mIconsSet) : null;
         final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
         mSpacebarTextSize = keyHeight * mSpacebarTextRatio;
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinKeyboardView_setKeyboard(keyboard);
+        }
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java b/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
index 392afca..5712df1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
@@ -18,9 +18,6 @@
 
 import android.util.Log;
 
-import com.android.inputmethod.latin.ResearchLogger;
-import com.android.inputmethod.latin.define.ProductionFlag;
-
 public class AlphabetShiftState {
     private static final String TAG = AlphabetShiftState.class.getSimpleName();
     private static final boolean DEBUG = false;
@@ -62,9 +59,6 @@
         }
         if (DEBUG)
             Log.d(TAG, "setShifted(" + newShiftState + "): " + toString(oldState) + " > " + this);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.alphabetShiftState_setShifted(newShiftState, oldState, this);
-        }
     }
 
     public void setShiftLocked(boolean newShiftLockState) {
@@ -84,9 +78,6 @@
         if (DEBUG)
             Log.d(TAG, "setShiftLocked(" + newShiftLockState + "): " + toString(oldState)
                     + " > " + this);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.alphabetShiftState_setShiftLocked(newShiftLockState, oldState, this);
-        }
     }
 
     public void setAutomaticShifted() {
@@ -94,9 +85,6 @@
         mState = AUTOMATIC_SHIFTED;
         if (DEBUG)
             Log.d(TAG, "setAutomaticShifted: " + toString(oldState) + " > " + this);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.alphabetShiftState_setAutomaticShifted(oldState, this);
-        }
     }
 
     public boolean isShiftedOrShiftLocked() {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 6949c9d..2d80a79 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -141,9 +141,6 @@
         if (DEBUG_EVENT) {
             Log.d(TAG, "onSaveKeyboardState: saved=" + state + " " + this);
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.keyboardState_onSaveKeyboardState(this, state.toString());
-        }
     }
 
     private void onRestoreKeyboardState() {
@@ -151,9 +148,6 @@
         if (DEBUG_EVENT) {
             Log.d(TAG, "onRestoreKeyboardState: saved=" + state + " " + this);
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.keyboardState_onRestoreKeyboardState(this, state.toString());
-        }
         if (!state.mIsValid || state.mIsAlphabetMode) {
             setAlphabetKeyboard();
         } else {
@@ -186,9 +180,6 @@
         if (DEBUG_ACTION) {
             Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode) + " " + this);
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.keyboardState_setShifted(this, shiftModeToString(shiftMode));
-        }
         if (!mIsAlphabetMode) return;
         final int prevShiftMode;
         if (mAlphabetShiftState.isAutomaticShifted()) {
@@ -228,9 +219,6 @@
         if (DEBUG_ACTION) {
             Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked + " " + this);
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.keyboardState_setShiftLocked(this, shiftLocked);
-        }
         if (!mIsAlphabetMode) return;
         if (shiftLocked && (!mAlphabetShiftState.isShiftLocked()
                 || mAlphabetShiftState.isShiftLockShifted())) {
@@ -246,9 +234,6 @@
         if (DEBUG_ACTION) {
             Log.d(TAG, "toggleAlphabetAndSymbols: " + this);
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.keyboardState_toggleAlphabetAndSymbols(this);
-        }
         if (mIsAlphabetMode) {
             mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked();
             if (mPrevSymbolsKeyboardWasShifted) {
@@ -280,9 +265,6 @@
             Log.d(TAG, "setAlphabetKeyboard");
         }
 
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.keyboardState_setAlphabetKeyboard();
-        }
         mSwitchActions.setAlphabetKeyboard();
         mIsAlphabetMode = true;
         mIsSymbolShifted = false;
@@ -294,9 +276,6 @@
         if (DEBUG_ACTION) {
             Log.d(TAG, "setSymbolsKeyboard");
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.keyboardState_setSymbolsKeyboard();
-        }
         mSwitchActions.setSymbolsKeyboard();
         mIsAlphabetMode = false;
         mIsSymbolShifted = false;
@@ -309,9 +288,6 @@
         if (DEBUG_ACTION) {
             Log.d(TAG, "setSymbolsShiftedKeyboard");
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.keyboardState_setSymbolsShiftedKeyboard();
-        }
         mSwitchActions.setSymbolsShiftedKeyboard();
         mIsAlphabetMode = false;
         mIsSymbolShifted = true;
@@ -399,9 +375,6 @@
         if (DEBUG_EVENT) {
             Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + " " + this);
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.keyboardState_onUpdateShiftState(this, autoCaps);
-        }
         updateAlphabetShiftState(autoCaps);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 41884d3..861c23b 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -634,7 +634,7 @@
                             editorInfo.inputType, editorInfo.imeOptions));
         }
         if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_onStartInputViewInternal(editorInfo);
+            ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, mPrefs);
         }
         if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
             Log.w(TAG, "Deprecated private IME option specified: "
diff --git a/java/src/com/android/inputmethod/latin/ResearchLogger.java b/java/src/com/android/inputmethod/latin/ResearchLogger.java
index 7072dda..566af70 100644
--- a/java/src/com/android/inputmethod/latin/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/latin/ResearchLogger.java
@@ -18,10 +18,12 @@
 
 import android.content.SharedPreferences;
 import android.inputmethodservice.InputMethodService;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
 import android.os.SystemClock;
+import android.preference.PreferenceManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -31,7 +33,6 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.internal.AlphabetShiftState;
 import com.android.inputmethod.keyboard.internal.KeyboardState;
 import com.android.inputmethod.latin.define.ProductionFlag;
 
@@ -45,6 +46,7 @@
 import java.nio.CharBuffer;
 import java.nio.channels.FileChannel;
 import java.nio.charset.Charset;
+import java.util.Map;
 
 /**
  * Logs the use of the LatinIME keyboard.
@@ -68,7 +70,7 @@
      * Isolates management of files. This variable should never be null, but can be changed
      * to support testing.
      */
-    private LogFileManager mLogFileManager;
+    /* package */ LogFileManager mLogFileManager;
 
     /**
      * Manages the file(s) that stores the logs.
@@ -93,63 +95,53 @@
             mIms = ims;
         }
 
-        public synchronized boolean createLogFile() {
-            try {
-                return createLogFile(DEFAULT_FILENAME);
-            } catch (IOException e) {
-                e.printStackTrace();
-                Log.w(TAG, e);
-                return false;
-            }
+        public synchronized void createLogFile() throws IOException {
+            createLogFile(DEFAULT_FILENAME);
         }
 
-        public synchronized boolean createLogFile(final SharedPreferences prefs) {
-            try {
-                final String filename =
-                        prefs.getString(RESEARCH_LOG_FILENAME_KEY, DEFAULT_FILENAME);
-                return createLogFile(filename);
-            } catch (IOException e) {
-                Log.w(TAG, e);
-                e.printStackTrace();
-            }
-            return false;
+        public synchronized void createLogFile(final SharedPreferences prefs)
+                throws IOException {
+            final String filename =
+                    prefs.getString(RESEARCH_LOG_FILENAME_KEY, DEFAULT_FILENAME);
+            createLogFile(filename);
         }
 
-        public synchronized boolean createLogFile(final String filename)
+        public synchronized void createLogFile(final String filename)
                 throws IOException {
             if (mIms == null) {
-                Log.w(TAG, "InputMethodService is not configured.  Logging is off.");
-                return false;
+                final String msg = "InputMethodService is not configured.  Logging is off.";
+                Log.w(TAG, msg);
+                throw new IOException(msg);
             }
             final File filesDir = mIms.getFilesDir();
             if (filesDir == null || !filesDir.exists()) {
-                Log.w(TAG, "Storage directory does not exist.  Logging is off.");
-                return false;
+                final String msg = "Storage directory does not exist.  Logging is off.";
+                Log.w(TAG, msg);
+                throw new IOException(msg);
             }
             close();
             final File file = new File(filesDir, filename);
             mFile = file;
-            file.setReadable(false, false);
             boolean append = true;
             if (file.exists() && file.lastModified() + LOGFILE_PURGE_INTERVAL <
                     System.currentTimeMillis()) {
                 append = false;
             }
             mPrintWriter = new PrintWriter(new BufferedWriter(new FileWriter(file, append)), true);
-            return true;
         }
 
         public synchronized boolean append(final String s) {
-            final PrintWriter printWriter = mPrintWriter;
-            if (printWriter == null) {
+            PrintWriter printWriter = mPrintWriter;
+            if (printWriter == null || !mFile.exists()) {
                 if (DEBUG) {
                     Log.w(TAG, "PrintWriter is null... attempting to create default log file");
                 }
-                if (!createLogFile()) {
-                    if (DEBUG) {
-                        Log.w(TAG, "Failed to create log file.  Not logging.");
-                        return false;
-                    }
+                try {
+                    createLogFile();
+                    printWriter = mPrintWriter;
+                } catch (IOException e) {
+                    Log.w(TAG, "Failed to create log file.  Not logging.");
+                    return false;
                 }
             }
             printWriter.print(s);
@@ -161,9 +153,15 @@
             if (mPrintWriter != null) {
                 mPrintWriter.close();
                 mPrintWriter = null;
+                if (DEBUG) {
+                    Log.d(TAG, "logfile closed");
+                }
             }
             if (mFile != null) {
                 mFile.delete();
+                if (DEBUG) {
+                    Log.d(TAG, "logfile deleted");
+                }
                 mFile = null;
             }
         }
@@ -173,6 +171,9 @@
                 mPrintWriter.close();
                 mPrintWriter = null;
                 mFile = null;
+                if (DEBUG) {
+                    Log.d(TAG, "logfile closed");
+                }
             }
         }
 
@@ -240,12 +241,16 @@
         sInstance.initInternal(ims, prefs);
     }
 
-    public void initInternal(final InputMethodService ims, final SharedPreferences prefs) {
+    /* package */ void initInternal(final InputMethodService ims, final SharedPreferences prefs) {
         mIms = ims;
         final LogFileManager logFileManager = mLogFileManager;
         if (logFileManager != null) {
             logFileManager.init(ims);
-            logFileManager.createLogFile(prefs);
+            try {
+                logFileManager.createLogFile(prefs);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
         }
         if (prefs != null) {
             sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
@@ -254,19 +259,6 @@
     }
 
     /**
-     * Change to a different logFileManager.
-     *
-     * @throws IllegalArgumentException if logFileManager is null
-     */
-    void setLogFileManager(final LogFileManager manager) {
-        if (manager == null) {
-            throw new IllegalArgumentException("warning: trying to set null logFileManager");
-        } else {
-            mLogFileManager = manager;
-        }
-    }
-
-    /**
      * Represents a category of logging events that share the same subfield structure.
      */
     private static enum LogGroup {
@@ -334,26 +326,11 @@
     public static class UnsLogGroup {
         private static final boolean DEFAULT_ENABLED = true;
 
-        private static final boolean ALPHABETSHIFTSTATE_SETSHIFTED_ENABLED = DEFAULT_ENABLED;
-        private static final boolean ALPHABETSHIFTSTATE_SETSHIFTLOCKED_ENABLED = DEFAULT_ENABLED;
-        private static final boolean ALPHABETSHIFTSTATE_SETAUTOMATICSHIFTED_ENABLED
-                = DEFAULT_ENABLED;
         private static final boolean KEYBOARDSTATE_ONCANCELINPUT_ENABLED = DEFAULT_ENABLED;
         private static final boolean KEYBOARDSTATE_ONCODEINPUT_ENABLED = DEFAULT_ENABLED;
         private static final boolean KEYBOARDSTATE_ONLONGPRESSTIMEOUT_ENABLED = DEFAULT_ENABLED;
         private static final boolean KEYBOARDSTATE_ONPRESSKEY_ENABLED = DEFAULT_ENABLED;
         private static final boolean KEYBOARDSTATE_ONRELEASEKEY_ENABLED = DEFAULT_ENABLED;
-        private static final boolean KEYBOARDSTATE_ONRESTOREKEYBOARDSTATE_ENABLED = DEFAULT_ENABLED;
-        private static final boolean KEYBOARDSTATE_ONSAVEKEYBOARDSTATE_ENABLED = DEFAULT_ENABLED;
-        private static final boolean KEYBOARDSTATE_ONUPDATESHIFTSTATE_ENABLED = DEFAULT_ENABLED;
-        private static final boolean KEYBOARDSTATE_SETALPHABETKEYBOARD_ENABLED = DEFAULT_ENABLED;
-        private static final boolean KEYBOARDSTATE_SETSHIFTED_ENABLED = DEFAULT_ENABLED;
-        private static final boolean KEYBOARDSTATE_SETSHIFTLOCKED_ENABLED = DEFAULT_ENABLED;
-        private static final boolean KEYBOARDSTATE_SETSYMBOLSKEYBOARD_ENABLED = DEFAULT_ENABLED;
-        private static final boolean KEYBOARDSTATE_SETSYMBOLSSHIFTEDKEYBOARD_ENABLED
-                = DEFAULT_ENABLED;
-        private static final boolean KEYBOARDSTATE_TOGGLEALPHABETANDSYMBOLS_ENABLED
-                = DEFAULT_ENABLED;
         private static final boolean LATINIME_COMMITCURRENTAUTOCORRECTION_ENABLED = DEFAULT_ENABLED;
         private static final boolean LATINIME_COMMITTEXT_ENABLED = DEFAULT_ENABLED;
         private static final boolean LATINIME_DELETESURROUNDINGTEXT_ENABLED = DEFAULT_ENABLED;
@@ -377,6 +354,7 @@
         private static final boolean LATINKEYBOARDVIEW_ONLONGPRESS_ENABLED = DEFAULT_ENABLED;
         private static final boolean LATINKEYBOARDVIEW_ONPROCESSMOTIONEVENT_ENABLED
                 = DEFAULT_ENABLED;
+        private static final boolean LATINKEYBOARDVIEW_SETKEYBOARD_ENABLED = DEFAULT_ENABLED;
         private static final boolean POINTERTRACKER_CALLLISTENERONCANCELINPUT_ENABLED
                 = DEFAULT_ENABLED;
         private static final boolean POINTERTRACKER_CALLLISTENERONCODEINPUT_ENABLED
@@ -413,12 +391,21 @@
                 if (DEBUG) {
                     Log.d(TAG, "Write: " + '[' + logGroup.mLogString + ']' + log);
                 }
-                if (mLogFileManager.append(builder.toString())) {
+                final String s = builder.toString();
+                if (mLogFileManager.append(s)) {
                     // success
                 } else {
                     if (DEBUG) {
                         Log.w(TAG, "Unable to write to log.");
                     }
+                    // perhaps logfile was deleted.  try to recreate and relog.
+                    try {
+                        mLogFileManager.createLogFile(PreferenceManager
+                                .getDefaultSharedPreferences(mIms));
+                        mLogFileManager.append(s);
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
                 }
             }
         });
@@ -448,32 +435,6 @@
         sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
     }
 
-    public static void alphabetShiftState_setShifted(final boolean newShiftState,
-            final int oldState, final AlphabetShiftState alphabetShiftState) {
-        if (UnsLogGroup.ALPHABETSHIFTSTATE_SETSHIFTED_ENABLED) {
-            final String s = "setShifted(" + newShiftState + "): " + oldState
-                    + " > " + alphabetShiftState;
-            logUnstructured("AlphabetShiftState_setShifted", s);
-        }
-    }
-
-    public static void alphabetShiftState_setShiftLocked(final boolean newShiftLockState,
-            final int oldState, final AlphabetShiftState alphabetShiftState) {
-        if (UnsLogGroup.ALPHABETSHIFTSTATE_SETSHIFTLOCKED_ENABLED) {
-            final String s = "setShiftLocked(" + newShiftLockState + "): "
-                    + oldState + " > " + alphabetShiftState;
-            logUnstructured("AlphabetShiftState_setShiftLocked", s);
-        }
-    }
-
-    public static void alphabetShiftState_setAutomaticShifted(final int oldState,
-            final AlphabetShiftState alphabetShiftState) {
-        if (UnsLogGroup.ALPHABETSHIFTSTATE_SETAUTOMATICSHIFTED_ENABLED) {
-            final String s = "setAutomaticShifted: " + oldState + " > " + alphabetShiftState;
-            logUnstructured("AlphabetShiftState_setAutomaticShifted", s);
-        }
-    }
-
     public static void keyboardState_onCancelInput(final boolean isSinglePointer,
             final KeyboardState keyboardState) {
         if (UnsLogGroup.KEYBOARDSTATE_ONCANCELINPUT_ENABLED) {
@@ -520,76 +481,6 @@
         }
     }
 
-    public static void keyboardState_onRestoreKeyboardState(final KeyboardState keyboardState,
-            final String savedKeyboardState) {
-        if (UnsLogGroup.KEYBOARDSTATE_ONRESTOREKEYBOARDSTATE_ENABLED) {
-            final String s = "onRestoreKeyboardState: saved=" + savedKeyboardState + " "
-                    + keyboardState;
-            logUnstructured("KeyboardState_onRestoreKeyboardState", s);
-        }
-    }
-
-    public static void keyboardState_onSaveKeyboardState(final KeyboardState keyboardState,
-            final String savedKeyboardState) {
-        if (UnsLogGroup.KEYBOARDSTATE_ONSAVEKEYBOARDSTATE_ENABLED) {
-            final String s = "onSaveKeyboardState: saved=" + savedKeyboardState + " "
-                    + keyboardState;
-            logUnstructured("KeyboardState_onSaveKeyboardState", s);
-        }
-    }
-
-    public static void keyboardState_onUpdateShiftState(final KeyboardState keyboardState,
-            final boolean autoCaps) {
-        if (UnsLogGroup.KEYBOARDSTATE_ONUPDATESHIFTSTATE_ENABLED) {
-            final String s = "onUpdateShiftState: autoCaps=" + autoCaps + " " + keyboardState;
-            logUnstructured("KeyboardState_onUpdateShiftState", s);
-        }
-    }
-
-    public static void keyboardState_setAlphabetKeyboard() {
-        if (UnsLogGroup.KEYBOARDSTATE_SETALPHABETKEYBOARD_ENABLED) {
-            final String s = "setAlphabetKeyboard";
-            logUnstructured("KeyboardState_setAlphabetKeyboard", s);
-        }
-    }
-
-    public static void keyboardState_setShifted(final KeyboardState keyboardState,
-            final String shiftMode) {
-        if (UnsLogGroup.KEYBOARDSTATE_SETSHIFTED_ENABLED) {
-            final String s = "setShifted: shiftMode=" + shiftMode + " " + keyboardState;
-            logUnstructured("KeyboardState_setShifted", s);
-        }
-    }
-
-    public static void keyboardState_setShiftLocked(final KeyboardState keyboardState,
-            final boolean shiftLocked) {
-        if (UnsLogGroup.KEYBOARDSTATE_SETSHIFTLOCKED_ENABLED) {
-            final String s = "setShiftLocked: shiftLocked=" + shiftLocked + " " + keyboardState;
-            logUnstructured("KeyboardState_setShiftLocked", s);
-        }
-    }
-
-    public static void keyboardState_setSymbolsKeyboard() {
-        if (UnsLogGroup.KEYBOARDSTATE_SETSYMBOLSKEYBOARD_ENABLED) {
-            final String s = "setSymbolsKeyboard";
-            logUnstructured("KeyboardState_setSymbolsKeyboard", s);
-        }
-    }
-
-    public static void keyboardState_setSymbolsShiftedKeyboard() {
-        if (UnsLogGroup.KEYBOARDSTATE_SETSYMBOLSSHIFTEDKEYBOARD_ENABLED) {
-            final String s = "setSymbolsShiftedKeyboard";
-            logUnstructured("KeyboardState_setSymbolsShiftedKeyboard", s);
-        }
-    }
-
-    public static void keyboardState_toggleAlphabetAndSymbols(final KeyboardState keyboardState) {
-        if (UnsLogGroup.KEYBOARDSTATE_TOGGLEALPHABETANDSYMBOLS_ENABLED) {
-            final String s = "toggleAlphabetAndSymbols: " + keyboardState;
-            logUnstructured("KeyboardState_toggleAlphabetAndSymbols", s);
-        }
-    }
-
     public static void latinIME_commitCurrentAutoCorrection(final String typedWord,
             final String autoCorrection) {
         if (UnsLogGroup.LATINIME_COMMITCURRENTAUTOCORRECTION_ENABLED) {
@@ -637,14 +528,22 @@
         }
     }
 
-    public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo) {
+    public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
+            final SharedPreferences prefs) {
         if (UnsLogGroup.LATINIME_ONSTARTINPUTVIEWINTERNAL_ENABLED) {
             final StringBuilder builder = new StringBuilder();
             builder.append("onStartInputView: editorInfo:");
-            builder.append("inputType=");
-            builder.append(editorInfo.inputType);
-            builder.append("imeOptions=");
-            builder.append(editorInfo.imeOptions);
+            builder.append("\tinputType=");
+            builder.append(Integer.toHexString(editorInfo.inputType));
+            builder.append("\timeOptions=");
+            builder.append(Integer.toHexString(editorInfo.imeOptions));
+            builder.append("\tdisplay="); builder.append(Build.DISPLAY);
+            builder.append("\tmodel="); builder.append(Build.MODEL);
+            for (Map.Entry<String,?> entry : prefs.getAll().entrySet()) {
+                builder.append("\t" + entry.getKey());
+                Object value = entry.getValue();
+                builder.append("=" + ((value == null) ? "<null>" : value.toString()));
+            }
             logUnstructured("LatinIME_onStartInputViewInternal", builder.toString());
         }
     }
@@ -745,6 +644,42 @@
         }
     }
 
+    public static void latinKeyboardView_setKeyboard(final Keyboard keyboard) {
+        if (UnsLogGroup.LATINKEYBOARDVIEW_SETKEYBOARD_ENABLED) {
+            StringBuilder builder = new StringBuilder();
+            builder.append("id=");
+            builder.append(keyboard.mId);
+            builder.append("\tw=");
+            builder.append(keyboard.mOccupiedWidth);
+            builder.append("\th=");
+            builder.append(keyboard.mOccupiedHeight);
+            builder.append("\tkeys=[");
+            boolean first = true;
+            for (Key key : keyboard.mKeys) {
+                if (first) {
+                    first = false;
+                } else {
+                    builder.append(",");
+                }
+                builder.append("{code:");
+                builder.append(key.mCode);
+                builder.append(",altCode:");
+                builder.append(key.mAltCode);
+                builder.append(",x:");
+                builder.append(key.mX);
+                builder.append(",y:");
+                builder.append(key.mY);
+                builder.append(",w:");
+                builder.append(key.mWidth);
+                builder.append(",h:");
+                builder.append(key.mHeight);
+                builder.append("}");
+            }
+            builder.append("]");
+            logUnstructured("LatinKeyboardView_setKeyboard", builder.toString());
+        }
+    }
+
     public static void latinIME_revertCommit(final String originallyTypedWord) {
         if (UnsLogGroup.LATINIME_REVERTCOMMIT_ENABLED) {
             logUnstructured("LatinIME_revertCommit", originallyTypedWord);
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index d223321..97df98e 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -21,6 +21,7 @@
 import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 
+import java.io.ByteArrayOutputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -272,6 +273,29 @@
         }
 
         /**
+         * Writes a string with our character format to a ByteArrayOutputStream.
+         *
+         * This will also write the terminator byte.
+         *
+         * @param buffer the ByteArrayOutputStream to write to.
+         * @param word the string to write.
+         */
+        private static void writeString(ByteArrayOutputStream buffer, final String word) {
+            final int length = word.length();
+            for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
+                final int codePoint = word.codePointAt(i);
+                if (1 == getCharSize(codePoint)) {
+                    buffer.write((byte) codePoint);
+                } else {
+                    buffer.write((byte) (0xFF & (codePoint >> 16)));
+                    buffer.write((byte) (0xFF & (codePoint >> 8)));
+                    buffer.write((byte) (0xFF & codePoint));
+                }
+            }
+            buffer.write(GROUP_CHARACTERS_TERMINATOR);
+        }
+
+        /**
          * Reads a string from a RandomAccessFile. This is the converse of the above method.
          */
         private static String readString(final RandomAccessFile source) throws IOException {
@@ -894,15 +918,11 @@
             final FusionDictionary dict, final int version)
             throws IOException, UnsupportedFormatException {
 
-        // Addresses are limited to 3 bytes, so we'll just make a 16MB buffer. Since addresses
-        // can be relative to each node, the structure itself is not limited to 16MB at all, but
-        // I doubt this will ever be shot. If it is, deciding the order of the nodes becomes
-        // a quite complicated problem, because though the dictionary itself does not have a
-        // size limit, each node must still be within 16MB of all its children and parents.
-        // As long as this is ensured, the dictionary file may grow to any size.
-        // Anyway, to make a dictionary bigger than 16MB just increase the size of this buffer.
-        final byte[] buffer = new byte[1 << 24];
-        int index = 0;
+        // Addresses are limited to 3 bytes, but since addresses can be relative to each node, the
+        // structure itself is not limited to 16MB. However, if it is over 16MB deciding the order
+        // of the nodes becomes a quite complicated problem, because though the dictionary itself
+        // does not have a size limit, each node must still be within 16MB of all its children and
+        // parents. As long as this is ensured, the dictionary file may grow to any size.
 
         if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION) {
             throw new UnsupportedFormatException("Requested file format version " + version
@@ -910,47 +930,54 @@
                     + MINIMUM_SUPPORTED_VERSION + " through " + MAXIMUM_SUPPORTED_VERSION);
         }
 
+        ByteArrayOutputStream headerBuffer = new ByteArrayOutputStream(256);
+
         // The magic number in big-endian order.
         if (version >= FIRST_VERSION_WITH_HEADER_SIZE) {
             // Magic number for version 2+.
-            buffer[index++] = (byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 24));
-            buffer[index++] = (byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 16));
-            buffer[index++] = (byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 8));
-            buffer[index++] = (byte) (0xFF & VERSION_2_MAGIC_NUMBER);
+            headerBuffer.write((byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 24)));
+            headerBuffer.write((byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 16)));
+            headerBuffer.write((byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 8)));
+            headerBuffer.write((byte) (0xFF & VERSION_2_MAGIC_NUMBER));
             // Dictionary version.
-            buffer[index++] = (byte) (0xFF & (version >> 8));
-            buffer[index++] = (byte) (0xFF & version);
+            headerBuffer.write((byte) (0xFF & (version >> 8)));
+            headerBuffer.write((byte) (0xFF & version));
         } else {
             // Magic number for version 1.
-            buffer[index++] = (byte) (0xFF & (VERSION_1_MAGIC_NUMBER >> 8));
-            buffer[index++] = (byte) (0xFF & VERSION_1_MAGIC_NUMBER);
+            headerBuffer.write((byte) (0xFF & (VERSION_1_MAGIC_NUMBER >> 8)));
+            headerBuffer.write((byte) (0xFF & VERSION_1_MAGIC_NUMBER));
             // Dictionary version.
-            buffer[index++] = (byte) (0xFF & version);
+            headerBuffer.write((byte) (0xFF & version));
         }
         // Options flags
         final int options = makeOptionsValue(dict.mOptions);
-        buffer[index++] = (byte) (0xFF & (options >> 8));
-        buffer[index++] = (byte) (0xFF & options);
+        headerBuffer.write((byte) (0xFF & (options >> 8)));
+        headerBuffer.write((byte) (0xFF & options));
         if (version >= FIRST_VERSION_WITH_HEADER_SIZE) {
-            final int headerSizeOffset = index;
-            index += 4; // Size of the header size
-
+            final int headerSizeOffset = headerBuffer.size();
+            // Placeholder to be written later with header size.
+            for (int i = 0; i < 4; ++i) {
+                headerBuffer.write(0);
+            }
             // Write out the options.
             for (final String key : dict.mOptions.mAttributes.keySet()) {
                 final String value = dict.mOptions.mAttributes.get(key);
-                index += CharEncoding.writeString(buffer, index, key);
-                index += CharEncoding.writeString(buffer, index, value);
+                CharEncoding.writeString(headerBuffer, key);
+                CharEncoding.writeString(headerBuffer, value);
             }
-
+            final int size = headerBuffer.size();
+            final byte[] bytes = headerBuffer.toByteArray();
             // Write out the header size.
-            buffer[headerSizeOffset] = (byte) (0xFF & (index >> 24));
-            buffer[headerSizeOffset + 1] = (byte) (0xFF & (index >> 16));
-            buffer[headerSizeOffset + 2] = (byte) (0xFF & (index >> 8));
-            buffer[headerSizeOffset + 3] = (byte) (0xFF & (index >> 0));
+            bytes[headerSizeOffset] = (byte) (0xFF & (size >> 24));
+            bytes[headerSizeOffset + 1] = (byte) (0xFF & (size >> 16));
+            bytes[headerSizeOffset + 2] = (byte) (0xFF & (size >> 8));
+            bytes[headerSizeOffset + 3] = (byte) (0xFF & (size >> 0));
+            destination.write(bytes);
+        } else {
+            headerBuffer.writeTo(destination);
         }
 
-        destination.write(buffer, 0, index);
-        index = 0;
+        headerBuffer.close();
 
         // Leave the choice of the optimal node order to the flattenTree function.
         MakedictLog.i("Flattening the tree...");
@@ -961,6 +988,12 @@
         MakedictLog.i("Checking array...");
         checkFlatNodeArray(flatNodes);
 
+        // Create a buffer that matches the final dictionary size.
+        final Node lastNode = flatNodes.get(flatNodes.size() - 1);
+        final int bufferSize =(lastNode.mCachedAddress + lastNode.mCachedSize);
+        final byte[] buffer = new byte[bufferSize];
+        int index = 0;
+
         MakedictLog.i("Writing file...");
         int dataEndOffset = 0;
         for (Node n : flatNodes) {
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index 838effe..c73a931 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -107,31 +107,15 @@
         return setBooleanPreference(PREF_DEBUG_MODE, value, false);
     }
 
-    // overload this to configure preferences in a way specific to a subclass's tests
-    protected void configurePreferences() {
-        // please avoid changing preferences any more than is necessary, as an interruption
-        // during a test will leave the user's preferences in a bad state.
-    }
-
-    // restore any preferences set in configurePreferences()
-    protected void restorePreferences() {
-        // undo any effects from configurePreferences()
-    }
-
     @Override
-    protected void setUp() {
-        try {
-            super.setUp();
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
+    protected void setUp() throws Exception {
+        super.setUp();
         mTextView = new TextView(getContext());
         mTextView.setInputType(InputType.TYPE_CLASS_TEXT);
         mTextView.setEnabled(true);
         setupService();
         mLatinIME = getService();
         final boolean previousDebugSetting = setDebugMode(true);
-        configurePreferences();
         mLatinIME.onCreate();
         setDebugMode(previousDebugSetting);
         initSubtypeMap();
@@ -153,12 +137,6 @@
         changeLanguage("en_US");
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        restorePreferences();
-    }
-
     private void initSubtypeMap() {
         final InputMethodManager imm = (InputMethodManager)mLatinIME.getSystemService(
                 Context.INPUT_METHOD_SERVICE);