diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 9016e23b324257bcfcb7cdb92658c13539538b02..6335b195f60a0bc9014fca4527c7e4af3f29b0bc 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -20,11 +20,11 @@ import android.content.Context;
 import android.util.JsonWriter;
 import android.util.Log;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.define.ProductionFlag;
 
 import java.io.BufferedWriter;
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
@@ -54,7 +54,6 @@ public class ResearchLog {
     private static final String TAG = ResearchLog.class.getSimpleName();
     private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
     private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
-    private static final int ABORT_TIMEOUT_IN_MS = 1000 * 4;
 
     /* package */ final ScheduledExecutorService mExecutor;
     /* package */ final File mFile;
@@ -100,7 +99,7 @@ public class ResearchLog {
      *
      * See class comment for details about {@code JsonWriter} construction.
      */
-    public synchronized void close(final Runnable onClosed) {
+    private synchronized void close(final Runnable onClosed) {
         mExecutor.submit(new Callable<Object>() {
             @Override
             public Object call() throws Exception {
@@ -131,15 +130,22 @@ public class ResearchLog {
         mExecutor.shutdown();
     }
 
-    private boolean mIsAbortSuccessful;
-
     /**
-     * Waits for publication requests to finish, closes the {@link JsonWriter}, but then deletes the
-     * backing file used for output.
+     * Block until the research log has shut down and spooled out all output or {@code timeout}
+     * occurs.
      *
-     * See class comment for details about {@code JsonWriter} construction.
+     * @param timeout time to wait for close in milliseconds
+     */
+    public void blockingClose(final long timeout) {
+        close(null);
+        awaitTermination(timeout, TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * Waits for publication requests to finish, closes the JsonWriter, but then deletes the backing
+     * output file.
      */
-    public synchronized void abort() {
+    private synchronized void abort() {
         mExecutor.submit(new Callable<Object>() {
             @Override
             public Object call() throws Exception {
@@ -151,7 +157,7 @@ public class ResearchLog {
                     }
                 } finally {
                     if (mFile != null) {
-                        mIsAbortSuccessful = mFile.delete();
+                        mFile.delete();
                     }
                 }
                 return null;
@@ -161,14 +167,25 @@ public class ResearchLog {
         mExecutor.shutdown();
     }
 
-    public boolean blockingAbort() throws InterruptedException {
+    /**
+     * Block until the research log has aborted or {@code timeout} occurs.
+     *
+     * @param timeout time to wait for close in milliseconds
+     */
+    public void blockingAbort(final long timeout) {
         abort();
-        mExecutor.awaitTermination(ABORT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
-        return mIsAbortSuccessful;
+        awaitTermination(timeout, TimeUnit.MILLISECONDS);
     }
 
-    public void awaitTermination(int delay, TimeUnit timeUnit) throws InterruptedException {
-        mExecutor.awaitTermination(delay, timeUnit);
+    @UsedForTesting
+    public void awaitTermination(final long delay, final TimeUnit timeUnit) {
+        try {
+            if (!mExecutor.awaitTermination(delay, timeUnit)) {
+                Log.e(TAG, "ResearchLog executor timed out while awaiting terminaion");
+            }
+        } catch (final InterruptedException e) {
+            Log.e(TAG, "ResearchLog executor interrupted while awaiting terminaion", e);
+        }
     }
 
     /* package */ synchronized void flush() {
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index e705ddda1a65d7e8afc3d0e1fec72f582277aea0..11d1a5222231976c95550c79f95ebafd96ad32e2 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -154,6 +154,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
     private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
     private static final String PREF_RESEARCH_SAVED_CHANNEL = "pref_research_saved_channel";
 
+    private static final long RESEARCHLOG_CLOSE_TIMEOUT_IN_MS = 5 * 1000;
+    private static final long RESEARCHLOG_ABORT_TIMEOUT_IN_MS = 5 * 1000;
+
     private static final ResearchLogger sInstance = new ResearchLogger();
     private static String sAccountType = null;
     private static String sAllowedAccountDomain = null;
@@ -502,42 +505,29 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
             commitCurrentLogUnit();
             mMainLogBuffer.setIsStopping();
             mMainLogBuffer.shiftAndPublishAll();
-            mMainResearchLog.close(null /* callback */);
+            mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
             mMainLogBuffer = null;
         }
         if (mFeedbackLogBuffer != null) {
-            mFeedbackLog.close(null /* callback */);
+            mFeedbackLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
             mFeedbackLogBuffer = null;
         }
     }
 
-    public boolean abort() {
+    public void abort() {
         if (DEBUG) {
             Log.d(TAG, "abort called");
         }
-        boolean didAbortMainLog = false;
         if (mMainLogBuffer != null) {
             mMainLogBuffer.clear();
-            try {
-                didAbortMainLog = mMainResearchLog.blockingAbort();
-            } catch (InterruptedException e) {
-                // Don't know whether this succeeded or not.  We assume not; this is reported
-                // to the caller.
-            }
+            mMainResearchLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
             mMainLogBuffer = null;
         }
-        boolean didAbortFeedbackLog = false;
         if (mFeedbackLogBuffer != null) {
             mFeedbackLogBuffer.clear();
-            try {
-                didAbortFeedbackLog = mFeedbackLog.blockingAbort();
-            } catch (InterruptedException e) {
-                // Don't know whether this succeeded or not.  We assume not; this is reported
-                // to the caller.
-            }
+            mFeedbackLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
             mFeedbackLogBuffer = null;
         }
-        return didAbortMainLog && didAbortFeedbackLog;
     }
 
     private void restart() {
@@ -620,7 +610,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
 
     private void startRecordingInternal() {
         if (mUserRecordingLog != null) {
-            mUserRecordingLog.abort();
+            mUserRecordingLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
         }
         mUserRecordingFile = createUserRecordingFile(mFilesDir);
         mUserRecordingLog = new ResearchLog(mUserRecordingFile, mLatinIME);
@@ -658,7 +648,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
 
     private void cancelRecording() {
         if (mUserRecordingLog != null) {
-            mUserRecordingLog.abort();
+            mUserRecordingLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS);
         }
         mUserRecordingLog = null;
         mUserRecordingLogBuffer = null;
@@ -670,7 +660,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
     private void saveRecording() {
         commitCurrentLogUnit();
         publishLogBuffer(mUserRecordingLogBuffer, mUserRecordingLog, true);
-        mUserRecordingLog.close(null);
+        mUserRecordingLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
         mUserRecordingLog = null;
         mUserRecordingLogBuffer = null;
 
@@ -782,12 +772,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
                 feedbackContents, accountName, recording);
         mFeedbackLogBuffer.shiftIn(feedbackLogUnit);
         publishLogBuffer(mFeedbackLogBuffer, mSavedFeedbackLog, true /* isIncludingPrivateData */);
-        mSavedFeedbackLog.close(new Runnable() {
-            @Override
-            public void run() {
-                uploadNow();
-            }
-        });
+        mSavedFeedbackLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
+        uploadNow();
 
         if (isIncludingRecording && DEBUG_REPLAY_AFTER_FEEDBACK) {
             final Handler handler = new Handler();