diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index 19e1c3d2ba4d55902be4ba690ae6e582c272be6d..cbfe8b212c7d0eb6ed8ced20b963f18f07c923f1 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -16,194 +16,9 @@
 
 package com.android.inputmethod.latin;
 
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Looper;
-import android.os.MessageQueue;
-import android.preference.PreferenceManager;
-import android.test.ServiceTestCase;
-import android.text.InputType;
-import android.text.SpannableStringBuilder;
-import android.text.style.SuggestionSpan;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-import android.view.View;
-import android.view.inputmethod.BaseInputConnection;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardActionListener;
-import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService; // for proximity info
-import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo;
 
-import java.util.Arrays;
-import java.util.HashMap;
-
-public class InputLogicTests extends ServiceTestCase<LatinIME> {
-
-    private static final String PREF_DEBUG_MODE = "debug_mode";
-
-    private LatinIME mLatinIME;
-    private Keyboard mKeyboard;
-    private TextView mTextView;
-    private InputConnection mInputConnection;
-
-    public InputLogicTests() {
-        super(LatinIME.class);
-    }
-
-    // returns the previous setting value
-    private boolean setDebugMode(final boolean mode) {
-        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME);
-        final boolean previousDebugSetting = prefs.getBoolean(PREF_DEBUG_MODE, false);
-        final SharedPreferences.Editor editor = prefs.edit();
-        editor.putBoolean(PREF_DEBUG_MODE, true);
-        editor.commit();
-        return previousDebugSetting;
-    }
-
-    @Override
-    protected void setUp() {
-        try {
-            super.setUp();
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-        mTextView = new TextView(getContext());
-        mTextView.setInputType(InputType.TYPE_CLASS_TEXT);
-        mTextView.setEnabled(true);
-        setupService();
-        mLatinIME = getService();
-        final boolean previousDebugSetting = setDebugMode(true);
-        mLatinIME.onCreate();
-        setDebugMode(previousDebugSetting);
-        final EditorInfo ei = new EditorInfo();
-        ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
-        final InputConnection ic = mTextView.onCreateInputConnection(ei);
-        ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
-        final LayoutInflater inflater =
-                (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        final ViewGroup vg = new FrameLayout(getContext());
-        final View inputView = inflater.inflate(R.layout.input_view, vg);
-        mLatinIME.setInputView(inputView);
-        mLatinIME.onBindInput();
-        mLatinIME.onCreateInputView();
-        mLatinIME.onStartInputView(ei, false);
-        mLatinIME.onCreateInputMethodInterface().startInput(ic, ei);
-        mInputConnection = ic;
-        mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard();
-        changeLanguage("en_US");
-    }
-
-    // We need to run the messages added to the handler from LatinIME. The only way to do
-    // that is to call Looper#loop() on the right looper, so we're going to get the looper
-    // object and call #loop() here. The messages in the handler actually run on the UI
-    // thread of the keyboard by design of the handler, so we want to call it synchronously
-    // on the same thread that the tests are running on to mimic the actual environment as
-    // closely as possible.
-    // Now, Looper#loop() never exits in normal operation unless the Looper#quit() method
-    // is called, so we need to do that at the right time so that #loop() returns at some
-    // point and we don't end up in an infinite loop.
-    // After we quit, the looper is still technically ready to process more messages but
-    // the handler will refuse to enqueue any because #quit() has been called and it
-    // explicitly tests for it on message enqueuing, so we'll have to reset it so that
-    // it lets us continue normal operation.
-    private void runMessages() {
-        // Here begins deep magic.
-        final Looper looper = mLatinIME.mHandler.getLooper();
-        mLatinIME.mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    looper.quit();
-                }
-            });
-        // The only way to get out of Looper#loop() is to call #quit() on it (or on its queue).
-        // Once #quit() is called remaining messages are not processed, which is why we post
-        // a message that calls it instead of calling it directly.
-        looper.loop();
-
-        // Once #quit() has been called, the message queue has an "mQuiting" field that prevents
-        // any subsequent post in this queue. However the queue itself is still fully functional!
-        // If we have a way of resetting "queue.mQuiting" then we can continue using it as normal,
-        // coming back to this method to run the messages.
-        MessageQueue queue = looper.getQueue();
-        try {
-            // However there is no way of doing it externally, and mQuiting is private.
-            // So... get out the big guns.
-            java.lang.reflect.Field f = MessageQueue.class.getDeclaredField("mQuiting");
-            f.setAccessible(true); // What do you mean "private"?
-            f.setBoolean(queue, false);
-        } catch (NoSuchFieldException e) {
-            throw new RuntimeException(e);
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    // type(int) and type(String): helper methods to send a code point resp. a string to LatinIME.
-    private void type(final int codePoint) {
-        // onPressKey and onReleaseKey are explicitly deactivated here, but they do happen in the
-        // code (although multitouch/slide input and other factors make the sequencing complicated).
-        // They are supposed to be entirely deconnected from the input logic from LatinIME point of
-        // view and only delegates to the parts of the code that care. So we don't include them here
-        // to keep these tests as pinpoint as possible and avoid bringing it too many dependencies,
-        // but keep them in mind if something breaks. Commenting them out as is should work.
-        //mLatinIME.onPressKey(codePoint);
-        for (final Key key : mKeyboard.mKeys) {
-            if (key.mCode == codePoint) {
-                final int x = key.mX + key.mWidth / 2;
-                final int y = key.mY + key.mHeight / 2;
-                mLatinIME.onCodeInput(codePoint, x, y);
-                return;
-            }
-        }
-        mLatinIME.onCodeInput(codePoint,
-                KeyboardActionListener.SPELL_CHECKER_COORDINATE,
-                KeyboardActionListener.SPELL_CHECKER_COORDINATE);
-        //mLatinIME.onReleaseKey(codePoint, false);
-    }
-
-    private void type(final String stringToType) {
-        for (int i = 0; i < stringToType.length(); i = stringToType.offsetByCodePoints(i, 1)) {
-            type(stringToType.codePointAt(i));
-        }
-    }
-
-    private void waitForDictionaryToBeLoaded() {
-        int remainingAttempts = 10;
-        while (remainingAttempts > 0 && !mLatinIME.mSuggest.hasMainDictionary()) {
-            try {
-                Thread.sleep(200);
-            } catch (InterruptedException e) {
-                // Don't do much
-            } finally {
-                --remainingAttempts;
-            }
-        }
-        if (!mLatinIME.mSuggest.hasMainDictionary()) {
-            throw new RuntimeException("Can't initialize the main dictionary");
-        }
-    }
-
-    private void changeLanguage(final String locale) {
-        SubtypeSwitcher.getInstance().updateSubtype(
-                new ArbitrarySubtype(locale, LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE));
-        waitForDictionaryToBeLoaded();
-    }
-
-
-    // Helper to avoid writing the try{}catch block each time
-    private static void sleep(final int milliseconds) {
-        try {
-            Thread.sleep(milliseconds);
-        } catch (InterruptedException e) {}
-    }
+public class InputLogicTests extends InputTestsBase {
 
     public void testTypeWord() {
         final String WORD_TO_TYPE = "abcd";
@@ -581,37 +396,6 @@ public class InputLogicTests extends ServiceTestCase<LatinIME> {
                 EXPECTED_RESULT, mTextView.getText().toString());
     }
 
-    // A helper class to ease span tests
-    private static class Span {
-        final SpannableStringBuilder mInputText;
-        final SuggestionSpan mSpan;
-        final int mStart;
-        final int mEnd;
-        // The supplied CharSequence should be an instance of SpannableStringBuilder,
-        // and it should contain exactly zero or one SuggestionSpan. Otherwise, an exception
-        // is thrown.
-        public Span(final CharSequence inputText) {
-            mInputText = (SpannableStringBuilder)inputText;
-            final SuggestionSpan[] spans =
-                    mInputText.getSpans(0, mInputText.length(), SuggestionSpan.class);
-            if (0 == spans.length) {
-                mSpan = null;
-                mStart = -1;
-                mEnd = -1;
-            } else if (1 == spans.length) {
-                mSpan = spans[0];
-                mStart = mInputText.getSpanStart(mSpan);
-                mEnd = mInputText.getSpanEnd(mSpan);
-            } else {
-                throw new RuntimeException("Expected one SuggestionSpan, found " + spans.length);
-            }
-        }
-        public boolean isAutoCorrectionIndicator() {
-            return 0 != (SuggestionSpan.FLAG_AUTO_CORRECTION & mSpan.getFlags());
-        }
-    }
-
-    static final int DELAY_TO_WAIT_FOR_UNDERLINE = 200; // The message is posted with a 100 ms delay
     public void testBlueUnderline() {
         final String STRING_TO_TYPE = "tgis";
         final int EXPECTED_SPAN_START = 0;
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..2c63993d7dd16cb7cb53774284ab3d77451e45f4
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -0,0 +1,235 @@
+/*
+ * 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.latin;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.preference.PreferenceManager;
+import android.test.ServiceTestCase;
+import android.text.InputType;
+import android.text.SpannableStringBuilder;
+import android.text.style.SuggestionSpan;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardActionListener;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class InputTestsBase extends ServiceTestCase<LatinIME> {
+
+    private static final String PREF_DEBUG_MODE = "debug_mode";
+
+    // The message that sets the underline is posted with a 100 ms delay
+    protected static final int DELAY_TO_WAIT_FOR_UNDERLINE = 200;
+
+    protected LatinIME mLatinIME;
+    protected Keyboard mKeyboard;
+    protected TextView mTextView;
+    protected InputConnection mInputConnection;
+
+    // A helper class to ease span tests
+    public static class Span {
+        final SpannableStringBuilder mInputText;
+        final SuggestionSpan mSpan;
+        final int mStart;
+        final int mEnd;
+        // The supplied CharSequence should be an instance of SpannableStringBuilder,
+        // and it should contain exactly zero or one SuggestionSpan. Otherwise, an exception
+        // is thrown.
+        public Span(final CharSequence inputText) {
+            mInputText = (SpannableStringBuilder)inputText;
+            final SuggestionSpan[] spans =
+                    mInputText.getSpans(0, mInputText.length(), SuggestionSpan.class);
+            if (0 == spans.length) {
+                mSpan = null;
+                mStart = -1;
+                mEnd = -1;
+            } else if (1 == spans.length) {
+                mSpan = spans[0];
+                mStart = mInputText.getSpanStart(mSpan);
+                mEnd = mInputText.getSpanEnd(mSpan);
+            } else {
+                throw new RuntimeException("Expected one SuggestionSpan, found " + spans.length);
+            }
+        }
+        public boolean isAutoCorrectionIndicator() {
+            return 0 != (SuggestionSpan.FLAG_AUTO_CORRECTION & mSpan.getFlags());
+        }
+    }
+
+    public InputTestsBase() {
+        super(LatinIME.class);
+    }
+
+    // returns the previous setting value
+    protected boolean setDebugMode(final boolean mode) {
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME);
+        final boolean previousDebugSetting = prefs.getBoolean(PREF_DEBUG_MODE, false);
+        final SharedPreferences.Editor editor = prefs.edit();
+        editor.putBoolean(PREF_DEBUG_MODE, true);
+        editor.commit();
+        return previousDebugSetting;
+    }
+
+    @Override
+    protected void setUp() {
+        try {
+            super.setUp();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        mTextView = new TextView(getContext());
+        mTextView.setInputType(InputType.TYPE_CLASS_TEXT);
+        mTextView.setEnabled(true);
+        setupService();
+        mLatinIME = getService();
+        final boolean previousDebugSetting = setDebugMode(true);
+        mLatinIME.onCreate();
+        setDebugMode(previousDebugSetting);
+        final EditorInfo ei = new EditorInfo();
+        ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
+        final InputConnection ic = mTextView.onCreateInputConnection(ei);
+        ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
+        final LayoutInflater inflater =
+                (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        final ViewGroup vg = new FrameLayout(getContext());
+        final View inputView = inflater.inflate(R.layout.input_view, vg);
+        mLatinIME.setInputView(inputView);
+        mLatinIME.onBindInput();
+        mLatinIME.onCreateInputView();
+        mLatinIME.onStartInputView(ei, false);
+        mLatinIME.onCreateInputMethodInterface().startInput(ic, ei);
+        mInputConnection = ic;
+        mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard();
+        changeLanguage("en_US");
+    }
+
+    // We need to run the messages added to the handler from LatinIME. The only way to do
+    // that is to call Looper#loop() on the right looper, so we're going to get the looper
+    // object and call #loop() here. The messages in the handler actually run on the UI
+    // thread of the keyboard by design of the handler, so we want to call it synchronously
+    // on the same thread that the tests are running on to mimic the actual environment as
+    // closely as possible.
+    // Now, Looper#loop() never exits in normal operation unless the Looper#quit() method
+    // is called, so we need to do that at the right time so that #loop() returns at some
+    // point and we don't end up in an infinite loop.
+    // After we quit, the looper is still technically ready to process more messages but
+    // the handler will refuse to enqueue any because #quit() has been called and it
+    // explicitly tests for it on message enqueuing, so we'll have to reset it so that
+    // it lets us continue normal operation.
+    protected void runMessages() {
+        // Here begins deep magic.
+        final Looper looper = mLatinIME.mHandler.getLooper();
+        mLatinIME.mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    looper.quit();
+                }
+            });
+        // The only way to get out of Looper#loop() is to call #quit() on it (or on its queue).
+        // Once #quit() is called remaining messages are not processed, which is why we post
+        // a message that calls it instead of calling it directly.
+        looper.loop();
+
+        // Once #quit() has been called, the message queue has an "mQuiting" field that prevents
+        // any subsequent post in this queue. However the queue itself is still fully functional!
+        // If we have a way of resetting "queue.mQuiting" then we can continue using it as normal,
+        // coming back to this method to run the messages.
+        MessageQueue queue = looper.getQueue();
+        try {
+            // However there is no way of doing it externally, and mQuiting is private.
+            // So... get out the big guns.
+            java.lang.reflect.Field f = MessageQueue.class.getDeclaredField("mQuiting");
+            f.setAccessible(true); // What do you mean "private"?
+            f.setBoolean(queue, false);
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    // type(int) and type(String): helper methods to send a code point resp. a string to LatinIME.
+    protected void type(final int codePoint) {
+        // onPressKey and onReleaseKey are explicitly deactivated here, but they do happen in the
+        // code (although multitouch/slide input and other factors make the sequencing complicated).
+        // They are supposed to be entirely deconnected from the input logic from LatinIME point of
+        // view and only delegates to the parts of the code that care. So we don't include them here
+        // to keep these tests as pinpoint as possible and avoid bringing it too many dependencies,
+        // but keep them in mind if something breaks. Commenting them out as is should work.
+        //mLatinIME.onPressKey(codePoint);
+        for (final Key key : mKeyboard.mKeys) {
+            if (key.mCode == codePoint) {
+                final int x = key.mX + key.mWidth / 2;
+                final int y = key.mY + key.mHeight / 2;
+                mLatinIME.onCodeInput(codePoint, x, y);
+                return;
+            }
+        }
+        mLatinIME.onCodeInput(codePoint,
+                KeyboardActionListener.SPELL_CHECKER_COORDINATE,
+                KeyboardActionListener.SPELL_CHECKER_COORDINATE);
+        //mLatinIME.onReleaseKey(codePoint, false);
+    }
+
+    protected void type(final String stringToType) {
+        for (int i = 0; i < stringToType.length(); i = stringToType.offsetByCodePoints(i, 1)) {
+            type(stringToType.codePointAt(i));
+        }
+    }
+
+    protected void waitForDictionaryToBeLoaded() {
+        int remainingAttempts = 10;
+        while (remainingAttempts > 0 && !mLatinIME.mSuggest.hasMainDictionary()) {
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException e) {
+                // Don't do much
+            } finally {
+                --remainingAttempts;
+            }
+        }
+        if (!mLatinIME.mSuggest.hasMainDictionary()) {
+            throw new RuntimeException("Can't initialize the main dictionary");
+        }
+    }
+
+    protected void changeLanguage(final String locale) {
+        SubtypeSwitcher.getInstance().updateSubtype(
+                new ArbitrarySubtype(locale, LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE));
+        waitForDictionaryToBeLoaded();
+    }
+
+
+    // Helper to avoid writing the try{}catch block each time
+    protected static void sleep(final int milliseconds) {
+        try {
+            Thread.sleep(milliseconds);
+        } catch (InterruptedException e) {}
+    }
+}