diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 6029ba963d1d45c5b5ba1180ad16bd50823aa1e9..46d2314a819686569b9b9a2cd2bdd2c2ae6362a4 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -20,11 +20,7 @@ import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOAR
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
-import android.app.AlertDialog;
-import android.app.Dialog;
 import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
@@ -34,7 +30,6 @@ import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Paint.Style;
-import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -46,8 +41,6 @@ import android.text.format.DateUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
-import android.view.Window;
-import android.view.WindowManager;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -70,6 +63,7 @@ import com.android.inputmethod.latin.Suggest;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.MotionEventReader.ReplayData;
+import com.android.inputmethod.research.ui.SplashScreen;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -94,12 +88,12 @@ import java.util.regex.Pattern;
  * This functionality is off by default. See
  * {@link ProductionFlag#USES_DEVELOPMENT_ONLY_DIAGNOSTICS}.
  */
-public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
+public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener,
+        SplashScreen.UserConsentListener {
     // TODO: This class has grown quite large and combines several concerns that should be
     // separated.  The following refactorings will be applied as soon as possible after adding
     // support for replaying historical events, fixing some replay bugs, adding some ui constraints
     // on the feedback dialog, and adding the survey dialog.
-    // TODO: Refactor.  Move splash screen code into separate class.
     // TODO: Refactor.  Move feedback screen code into separate class.
     // TODO: Refactor.  Move logging invocations into their own class.
     // TODO: Refactor.  Move currentLogUnit management into separate class.
@@ -184,6 +178,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
     private final MotionEventReader mMotionEventReader = new MotionEventReader();
     private final Replayer mReplayer = Replayer.getInstance();
     private ResearchLogDirectory mResearchLogDirectory;
+    private SplashScreen mSplashScreen;
 
     private Intent mUploadIntent;
     private Intent mUploadNowIntent;
@@ -301,62 +296,19 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
         }
     }
 
-    private Dialog mSplashDialog = null;
-
     private void maybeShowSplashScreen() {
-        if (ResearchSettings.readHasSeenSplash(mPrefs)) {
-            return;
-        }
-        if (mSplashDialog != null && mSplashDialog.isShowing()) {
-            return;
-        }
-        final IBinder windowToken = mMainKeyboardView != null
-                ? mMainKeyboardView.getWindowToken() : null;
-        if (windowToken == null) {
-            return;
-        }
-        final AlertDialog.Builder builder = new AlertDialog.Builder(mLatinIME)
-                .setTitle(R.string.research_splash_title)
-                .setMessage(R.string.research_splash_content)
-                .setPositiveButton(android.R.string.yes,
-                        new DialogInterface.OnClickListener() {
-                            @Override
-                            public void onClick(DialogInterface dialog, int which) {
-                                onUserLoggingConsent();
-                                mSplashDialog.dismiss();
-                            }
-                })
-                .setNegativeButton(android.R.string.no,
-                        new DialogInterface.OnClickListener() {
-                            @Override
-                            public void onClick(DialogInterface dialog, int which) {
-                                final String packageName = mLatinIME.getPackageName();
-                                final Uri packageUri = Uri.parse("package:" + packageName);
-                                final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE,
-                                        packageUri);
-                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                                mLatinIME.startActivity(intent);
-                            }
-                })
-                .setCancelable(true)
-                .setOnCancelListener(
-                        new OnCancelListener() {
-                            @Override
-                            public void onCancel(DialogInterface dialog) {
-                                mLatinIME.requestHideSelf(0);
-                            }
-                });
-        mSplashDialog = builder.create();
-        final Window w = mSplashDialog.getWindow();
-        final WindowManager.LayoutParams lp = w.getAttributes();
-        lp.token = windowToken;
-        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
-        w.setAttributes(lp);
-        w.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
-        mSplashDialog.show();
-    }
-
-    public void onUserLoggingConsent() {
+        if (ResearchSettings.readHasSeenSplash(mPrefs)) return;
+        if (mSplashScreen != null && mSplashScreen.isShowing()) return;
+        if (mMainKeyboardView == null) return;
+        final IBinder windowToken = mMainKeyboardView.getWindowToken();
+        if (windowToken == null) return;
+
+        mSplashScreen = new SplashScreen(mLatinIME, this);
+        mSplashScreen.showSplashScreen(windowToken);
+    }
+
+    @Override
+    public void onSplashScreenUserClickedOk() {
         if (mPrefs == null) {
             mPrefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME);
             if (mPrefs == null) return;
diff --git a/java/src/com/android/inputmethod/research/ui/SplashScreen.java b/java/src/com/android/inputmethod/research/ui/SplashScreen.java
new file mode 100644
index 0000000000000000000000000000000000000000..78ed668d1c755da8ef23a0d466b93287f40d1775
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/ui/SplashScreen.java
@@ -0,0 +1,111 @@
+/*
+ * 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.research.ui;
+
+import android.app.AlertDialog.Builder;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
+import android.inputmethodservice.InputMethodService;
+import android.net.Uri;
+import android.os.IBinder;
+import android.view.Window;
+import android.view.WindowManager.LayoutParams;
+
+import com.android.inputmethod.latin.R.string;
+
+/**
+ * Show a dialog when the user first opens the keyboard.
+ *
+ * The splash screen is a modal dialog box presented when the user opens this keyboard for the first
+ * time.  It is useful for giving specific warnings that must be shown to the user before use.
+ *
+ * While the splash screen does share with the setup wizard the common goal of presenting
+ * information to the user before use, they are presented at different times and with different
+ * capabilities.  The setup wizard is launched by tapping on the icon, and walks the user through
+ * the setup process.  It can, however, be bypassed by enabling the keyboard from Settings directly.
+ * The splash screen cannot be bypassed, and is therefore more appropriate for obtaining user
+ * consent.
+ */
+public class SplashScreen {
+    public interface UserConsentListener {
+        public void onSplashScreenUserClickedOk();
+    }
+
+    final UserConsentListener mListener;
+    final Dialog mSplashDialog;
+
+    public SplashScreen(final InputMethodService inputMethodService,
+            final UserConsentListener listener) {
+        mListener = listener;
+        final Builder builder = new Builder(inputMethodService)
+                .setTitle(string.research_splash_title)
+                .setMessage(string.research_splash_content)
+                .setPositiveButton(android.R.string.yes,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                mListener.onSplashScreenUserClickedOk();
+                                mSplashDialog.dismiss();
+                            }
+                })
+                .setNegativeButton(android.R.string.no,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                final String packageName = inputMethodService.getPackageName();
+                                final Uri packageUri = Uri.parse("package:" + packageName);
+                                final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE,
+                                        packageUri);
+                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                                inputMethodService.startActivity(intent);
+                            }
+                })
+                .setCancelable(true)
+                .setOnCancelListener(
+                        new OnCancelListener() {
+                            @Override
+                            public void onCancel(DialogInterface dialog) {
+                                inputMethodService.requestHideSelf(0);
+                            }
+                });
+        mSplashDialog = builder.create();
+    }
+
+    /**
+     * Show the splash screen.
+     *
+     * The user must consent to the terms presented in the SplashScreen before they can use the
+     * keyboard.  If they cancel instead, they are given the option to uninstall the keybard.
+     *
+     * @param windowToken {@link IBinder} to attach dialog to
+     */
+    public void showSplashScreen(final IBinder windowToken) {
+        final Window window = mSplashDialog.getWindow();
+        final LayoutParams lp = window.getAttributes();
+        lp.token = windowToken;
+        lp.type = LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+        window.setAttributes(lp);
+        window.addFlags(LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+        mSplashDialog.show();
+    }
+
+    public boolean isShowing() {
+        return mSplashDialog.isShowing();
+    }
+}