diff --git a/java/src/com/android/inputmethod/research/JsonUtils.java b/java/src/com/android/inputmethod/research/JsonUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..cb331d7f9ade3f6c3f77cfae1b0616375bb49895
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/JsonUtils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2012 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.research;
+
+import android.content.SharedPreferences;
+import android.util.JsonWriter;
+import android.view.inputmethod.CompletionInfo;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
+import java.io.IOException;
+import java.util.Map;
+
+/* package */ class JsonUtils {
+    private JsonUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    /* package */ static void writeJson(final CompletionInfo[] ci, final JsonWriter jsonWriter)
+            throws IOException {
+        jsonWriter.beginArray();
+        for (int j = 0; j < ci.length; j++) {
+            jsonWriter.value(ci[j].toString());
+        }
+        jsonWriter.endArray();
+    }
+
+    /* package */ static void writeJson(final SharedPreferences prefs, final JsonWriter jsonWriter)
+            throws IOException {
+        jsonWriter.beginObject();
+        for (Map.Entry<String,?> entry : prefs.getAll().entrySet()) {
+            jsonWriter.name(entry.getKey());
+            final Object innerValue = entry.getValue();
+            if (innerValue == null) {
+                jsonWriter.nullValue();
+            } else if (innerValue instanceof Boolean) {
+                jsonWriter.value((Boolean) innerValue);
+            } else if (innerValue instanceof Number) {
+                jsonWriter.value((Number) innerValue);
+            } else {
+                jsonWriter.value(innerValue.toString());
+            }
+        }
+        jsonWriter.endObject();
+    }
+
+    /* package */ static void writeJson(final Key[] keys, final JsonWriter jsonWriter)
+            throws IOException {
+        jsonWriter.beginArray();
+        for (Key key : keys) {
+            writeJson(key, jsonWriter);
+        }
+        jsonWriter.endArray();
+    }
+
+    private static void writeJson(final Key key, final JsonWriter jsonWriter) throws IOException {
+        jsonWriter.beginObject();
+        jsonWriter.name("code").value(key.mCode);
+        jsonWriter.name("altCode").value(key.getAltCode());
+        jsonWriter.name("x").value(key.mX);
+        jsonWriter.name("y").value(key.mY);
+        jsonWriter.name("w").value(key.mWidth);
+        jsonWriter.name("h").value(key.mHeight);
+        jsonWriter.endObject();
+    }
+
+    /* package */ static void writeJson(final SuggestedWords words, final JsonWriter jsonWriter)
+            throws IOException {
+        jsonWriter.beginObject();
+        jsonWriter.name("typedWordValid").value(words.mTypedWordValid);
+        jsonWriter.name("willAutoCorrect")
+                .value(words.mWillAutoCorrect);
+        jsonWriter.name("isPunctuationSuggestions")
+                .value(words.mIsPunctuationSuggestions);
+        jsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
+        jsonWriter.name("isPrediction").value(words.mIsPrediction);
+        jsonWriter.name("words");
+        jsonWriter.beginArray();
+        final int size = words.size();
+        for (int j = 0; j < size; j++) {
+            final SuggestedWordInfo wordInfo = words.getWordInfo(j);
+            jsonWriter.value(wordInfo.toString());
+        }
+        jsonWriter.endArray();
+        jsonWriter.endObject();
+    }
+}
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
index ab9d2f8578c50a4dc97371cde3f2a36015cf736a..884ade06be272092eca395134aa5817e969251bc 100644
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -16,10 +16,21 @@
 
 package com.android.inputmethod.research;
 
+import android.content.SharedPreferences;
+import android.util.JsonWriter;
+import android.util.Log;
+import android.view.inputmethod.CompletionInfo;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.ResearchLogger.LogStatement;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * A group of log statements related to each other.
@@ -36,6 +47,8 @@ import java.util.List;
  * been published recently, or whether the LogUnit contains numbers, etc.
  */
 /* package */ class LogUnit {
+    private static final String TAG = LogUnit.class.getSimpleName();
+    private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
     private final ArrayList<LogStatement> mLogStatementList;
     private final ArrayList<Object[]> mValuesList;
     // Assume that mTimeList is sorted in increasing order.  Do not insert null values into
@@ -77,8 +90,13 @@ import java.util.List;
         mTimeList.add(time);
     }
 
-    public void publishTo(final ResearchLog researchLog, final boolean isIncludingPrivateData) {
+    /**
+     * Publish the contents of this LogUnit to researchLog.
+     */
+    public synchronized void publishTo(final ResearchLog researchLog,
+            final boolean isIncludingPrivateData) {
         final int size = mLogStatementList.size();
+        // Write out any logStatement that passes the privacy filter.
         for (int i = 0; i < size; i++) {
             final LogStatement logStatement = mLogStatementList.get(i);
             if (!isIncludingPrivateData && logStatement.mIsPotentiallyPrivate) {
@@ -87,8 +105,70 @@ import java.util.List;
             if (mIsPartOfMegaword && logStatement.mIsPotentiallyRevealing) {
                 continue;
             }
-            researchLog.outputEvent(mLogStatementList.get(i), mValuesList.get(i), mTimeList.get(i));
+            // Only retrieve the jsonWriter if we need to.  If we don't get this far, then
+            // researchLog.getValidJsonWriter() will not open the file for writing.
+            final JsonWriter jsonWriter = researchLog.getValidJsonWriterLocked();
+            outputLogStatementToLocked(jsonWriter, mLogStatementList.get(i), mValuesList.get(i),
+                    mTimeList.get(i));
+        }
+    }
+
+    private static final String CURRENT_TIME_KEY = "_ct";
+    private static final String UPTIME_KEY = "_ut";
+    private static final String EVENT_TYPE_KEY = "_ty";
+
+    /**
+     * Write the logStatement and its contents out through jsonWriter.
+     *
+     * Note that this method is not thread safe for the same jsonWriter.  Callers must ensure
+     * thread safety.
+     */
+    private boolean outputLogStatementToLocked(final JsonWriter jsonWriter,
+            final LogStatement logStatement, final Object[] values, final Long time) {
+        if (DEBUG) {
+            if (logStatement.mKeys.length != values.length) {
+                Log.d(TAG, "Key and Value list sizes do not match. " + logStatement.mName);
+            }
+        }
+        try {
+            jsonWriter.beginObject();
+            jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
+            jsonWriter.name(UPTIME_KEY).value(time);
+            jsonWriter.name(EVENT_TYPE_KEY).value(logStatement.mName);
+            final String[] keys = logStatement.mKeys;
+            final int length = values.length;
+            for (int i = 0; i < length; i++) {
+                jsonWriter.name(keys[i]);
+                final Object value = values[i];
+                if (value instanceof CharSequence) {
+                    jsonWriter.value(value.toString());
+                } else if (value instanceof Number) {
+                    jsonWriter.value((Number) value);
+                } else if (value instanceof Boolean) {
+                    jsonWriter.value((Boolean) value);
+                } else if (value instanceof CompletionInfo[]) {
+                    JsonUtils.writeJson((CompletionInfo[]) value, jsonWriter);
+                } else if (value instanceof SharedPreferences) {
+                    JsonUtils.writeJson((SharedPreferences) value, jsonWriter);
+                } else if (value instanceof Key[]) {
+                    JsonUtils.writeJson((Key[]) value, jsonWriter);
+                } else if (value instanceof SuggestedWords) {
+                    JsonUtils.writeJson((SuggestedWords) value, jsonWriter);
+                } else if (value == null) {
+                    jsonWriter.nullValue();
+                } else {
+                    Log.w(TAG, "Unrecognized type to be logged: " +
+                            (value == null ? "<null>" : value.getClass().getName()));
+                    jsonWriter.nullValue();
+                }
+            }
+            jsonWriter.endObject();
+        } catch (IOException e) {
+            e.printStackTrace();
+            Log.w(TAG, "Error in JsonWriter; skipping LogStatement");
+            return false;
         }
+        return true;
     }
 
     public void setWord(String word) {
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 3c873199548829e0c255a91912eccb5583b9bbb0..a6b1b889f97180537a4102c3ded1fcc9ac8546d8 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -16,17 +16,10 @@
 
 package com.android.inputmethod.research;
 
-import android.content.SharedPreferences;
-import android.os.SystemClock;
 import android.util.JsonWriter;
 import android.util.Log;
-import android.view.inputmethod.CompletionInfo;
 
-import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.research.ResearchLogger.LogStatement;
 
 import java.io.BufferedWriter;
 import java.io.File;
@@ -34,7 +27,6 @@ import java.io.FileWriter;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
-import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 import java.util.concurrent.RejectedExecutionException;
@@ -204,103 +196,17 @@ public class ResearchLog {
         }
     }
 
-    private static final String CURRENT_TIME_KEY = "_ct";
-    private static final String UPTIME_KEY = "_ut";
-    private static final String EVENT_TYPE_KEY = "_ty";
-
-    void outputEvent(final LogStatement logStatement, final Object[] values, final long time) {
-        // Not thread safe.
-        if (DEBUG) {
-            if (logStatement.mKeys.length != values.length) {
-                Log.d(TAG, "Key and Value list sizes do not match. " + logStatement.mName);
-            }
-        }
+    /**
+     * Return a JsonWriter for this ResearchLog.  It is initialized the first time this method is
+     * called.  The cached value is returned in future calls.
+     */
+    public JsonWriter getValidJsonWriterLocked() {
         try {
             if (mJsonWriter == NULL_JSON_WRITER) {
                 mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
                 mJsonWriter.beginArray();
                 mHasWrittenData = true;
             }
-            mJsonWriter.beginObject();
-            mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
-            mJsonWriter.name(UPTIME_KEY).value(time);
-            mJsonWriter.name(EVENT_TYPE_KEY).value(logStatement.mName);
-            final String[] keys = logStatement.mKeys;
-            final int length = values.length;
-            for (int i = 0; i < length; i++) {
-                mJsonWriter.name(keys[i]);
-                Object value = values[i];
-                if (value instanceof CharSequence) {
-                    mJsonWriter.value(value.toString());
-                } else if (value instanceof Number) {
-                    mJsonWriter.value((Number) value);
-                } else if (value instanceof Boolean) {
-                    mJsonWriter.value((Boolean) value);
-                } else if (value instanceof CompletionInfo[]) {
-                    CompletionInfo[] ci = (CompletionInfo[]) value;
-                    mJsonWriter.beginArray();
-                    for (int j = 0; j < ci.length; j++) {
-                        mJsonWriter.value(ci[j].toString());
-                    }
-                    mJsonWriter.endArray();
-                } else if (value instanceof SharedPreferences) {
-                    SharedPreferences prefs = (SharedPreferences) value;
-                    mJsonWriter.beginObject();
-                    for (Map.Entry<String,?> entry : prefs.getAll().entrySet()) {
-                        mJsonWriter.name(entry.getKey());
-                        final Object innerValue = entry.getValue();
-                        if (innerValue == null) {
-                            mJsonWriter.nullValue();
-                        } else if (innerValue instanceof Boolean) {
-                            mJsonWriter.value((Boolean) innerValue);
-                        } else if (innerValue instanceof Number) {
-                            mJsonWriter.value((Number) innerValue);
-                        } else {
-                            mJsonWriter.value(innerValue.toString());
-                        }
-                    }
-                    mJsonWriter.endObject();
-                } else if (value instanceof Key[]) {
-                    Key[] keyboardKeys = (Key[]) value;
-                    mJsonWriter.beginArray();
-                    for (Key keyboardKey : keyboardKeys) {
-                        mJsonWriter.beginObject();
-                        mJsonWriter.name("code").value(keyboardKey.mCode);
-                        mJsonWriter.name("altCode").value(keyboardKey.getAltCode());
-                        mJsonWriter.name("x").value(keyboardKey.mX);
-                        mJsonWriter.name("y").value(keyboardKey.mY);
-                        mJsonWriter.name("w").value(keyboardKey.mWidth);
-                        mJsonWriter.name("h").value(keyboardKey.mHeight);
-                        mJsonWriter.endObject();
-                    }
-                    mJsonWriter.endArray();
-                } else if (value instanceof SuggestedWords) {
-                    SuggestedWords words = (SuggestedWords) value;
-                    mJsonWriter.beginObject();
-                    mJsonWriter.name("typedWordValid").value(words.mTypedWordValid);
-                    mJsonWriter.name("willAutoCorrect").value(words.mWillAutoCorrect);
-                    mJsonWriter.name("isPunctuationSuggestions")
-                            .value(words.mIsPunctuationSuggestions);
-                    mJsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
-                    mJsonWriter.name("isPrediction").value(words.mIsPrediction);
-                    mJsonWriter.name("words");
-                    mJsonWriter.beginArray();
-                    final int size = words.size();
-                    for (int j = 0; j < size; j++) {
-                        SuggestedWordInfo wordInfo = words.getWordInfo(j);
-                        mJsonWriter.value(wordInfo.toString());
-                    }
-                    mJsonWriter.endArray();
-                    mJsonWriter.endObject();
-                } else if (value == null) {
-                    mJsonWriter.nullValue();
-                } else {
-                    Log.w(TAG, "Unrecognized type to be logged: " +
-                            (value == null ? "<null>" : value.getClass().getName()));
-                    mJsonWriter.nullValue();
-                }
-            }
-            mJsonWriter.endObject();
         } catch (IOException e) {
             e.printStackTrace();
             Log.w(TAG, "Error in JsonWriter; disabling logging");
@@ -315,5 +221,6 @@ public class ResearchLog {
                 mJsonWriter = NULL_JSON_WRITER;
             }
         }
+        return mJsonWriter;
     }
 }