diff --git a/java/proguard.flags b/java/proguard.flags
index 0a5d2dda9e0486b64c0b8adf9c0ff8f8c54cbd5a..829a096c049a456545d8c8fabb7d59210a25cf08 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -1,3 +1,8 @@
 -keep class com.android.inputmethod.latin.BinaryDictionary {
   int mDictLength;
+  <init>(...); 
+}
+
+-keep class com.android.inputmethod.latin.Suggest {
+  <init>(...);
 }
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 4901b210bf84d8e449ad12eb6d8932558c7ff829..6473f45580444065b6c3939318d0fc95f136304b 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -68,6 +68,26 @@ public class BinaryDictionary extends Dictionary {
         }
     }
 
+    /**
+     * Create a dictionary from a byte buffer. This is used for testing.
+     * @param context application context for reading resources
+     * @param resId the resource containing the raw binary dictionary
+     */
+    public BinaryDictionary(Context context, ByteBuffer byteBuffer) {
+        if (byteBuffer != null) {
+            if (byteBuffer.isDirect()) {
+                mNativeDictDirectBuffer = byteBuffer;
+            } else {
+                mNativeDictDirectBuffer = ByteBuffer.allocateDirect(byteBuffer.capacity());
+                byteBuffer.rewind();
+                mNativeDictDirectBuffer.put(byteBuffer);
+            }
+            mDictLength = byteBuffer.capacity();
+            mNativeDict = openNative(mNativeDictDirectBuffer,
+                    TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER);
+        }
+    }
+
     private native int openNative(ByteBuffer bb, int typedLetterMultiplier, int fullWordMultiplier);
     private native void closeNative(int dict);
     private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index a70bea003a0e4552f7ca00d1542286a332faee02..010913d6d6276f7235b6a36e55843aac99954a8b 100755
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -16,18 +16,17 @@
 
 package com.android.inputmethod.latin;
 
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 import android.content.Context;
 import android.text.AutoText;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import com.android.inputmethod.latin.WordComposer;
-
 /**
  * This class loads a dictionary and provides a list of suggestions for a given sequence of 
  * characters. This includes corrections and completions.
@@ -69,9 +68,17 @@ public class Suggest implements Dictionary.WordCallback {
 
     private int mCorrectionMode = CORRECTION_BASIC;
 
-
     public Suggest(Context context, int dictionaryResId) {
         mMainDict = new BinaryDictionary(context, dictionaryResId);
+        initPool();
+    }
+
+    public Suggest(Context context, ByteBuffer byteBuffer) {
+        mMainDict = new BinaryDictionary(context, byteBuffer);
+        initPool();
+    }
+
+    private void initPool() {
         for (int i = 0; i < mPrefMaxSuggestions; i++) {
             StringBuilder sb = new StringBuilder(32);
             mStringPool.add(sb);
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 19f714ae74118d0b20792266d15360bf4ac04bd4..2547aa1331eca3f56b40ada04e85af608a5b2d8a 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -44,7 +44,7 @@ public class WordComposer {
      */
     private boolean mIsCapitalized;
 
-    WordComposer() {
+    public WordComposer() {
         mCodes = new ArrayList<int[]>(12);
         mTypedWord = new StringBuilder(20);
     }
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000000000000000000000000000000000000..fba7a8d743880f7020586c8ff5ea77956f117538
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+LOCAL_CERTIFICATE := shared
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := LatinIMETests
+
+LOCAL_INSTRUMENTATION_FOR := LatinIME
+
+include $(BUILD_PACKAGE)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..210e814899800d867d827648224af570670fc443
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.inputmethod.latin.tests">
+
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <!-- meta-data android:name="com.android.contacts.iconset" android:resource="@xml/iconset" /-->
+        <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="com.android.inputmethod.latin"
+        android:label="LatinIME tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/data/wordlist.xml b/tests/data/wordlist.xml
new file mode 100644
index 0000000000000000000000000000000000000000..22d0caa388fc524e0f948b5a724b683b65b1d74d
--- /dev/null
+++ b/tests/data/wordlist.xml
@@ -0,0 +1,243 @@
+<wordlist>
+ <w f="255">the</w>
+ <w f="246">and</w>
+ <w f="245">of</w>
+ <w f="242">to</w>
+ <w f="231">in</w>
+ <w f="230">that</w>
+ <w f="229">for</w>
+ <w f="224">with</w>
+ <w f="224">on</w>
+ <w f="224">it</w>
+ <w f="223">this</w>
+ <w f="222">you</w>
+ <w f="219">is</w>
+ <w f="219">was</w>
+ <w f="219">by</w>
+ <w f="219">or</w>
+ <w f="218">from</w>
+ <w f="217">but</w>
+ <w f="216">be</w>
+ <w f="216">Sunday</w>
+ <w f="215">are</w>
+ <w f="215">he</w>
+ <w f="214">so</w>
+ <w f="214">not</w>
+ <w f="213">have</w>
+ <w f="213">as</w>
+ <w f="211">all</w>
+ <w f="211">his</w>
+ <w f="210">my</w>
+ <w f="210">if</w>
+ <w f="210">which</w>
+ <w f="210">they</w>
+ <w f="209">at</w>
+ <w f="207">it's</w>
+ <w f="207">an</w>
+ <w f="207">your</w>
+ <w f="206">will</w>
+ <w f="206">about</w>
+ <w f="206">I'm</w>
+ <w f="205">there</w>
+ <w f="205">had</w>
+ <w f="205">has</w>
+ <w f="204">when</w>
+ <w f="203">no</w>
+ <w f="203">were</w>
+ <w f="203">what</w>
+ <w f="203">more</w>
+ <w f="203">out</w>
+ <w f="203">just</w>
+ <w f="202">their</w>
+ <w f="202">up</w>
+ <w f="202">would</w>
+ <w f="202">here</w>
+ <w f="202">can</w>
+ <w f="201">who</w>
+ <w f="200">her</w>
+ <w f="200">me</w>
+ <w f="200">now</w>
+ <w f="200">our</w>
+ <w f="200">do</w>
+ <w f="200">some</w>
+ <w f="199">been</w>
+ <w f="199">two</w>
+ <w f="199">like</w>
+ <w f="199">them</w>
+ <w f="199">new</w>
+ <w f="198">time</w>
+ <w f="198">we</w>
+ <w f="198">she</w>
+ <w f="197">one</w>
+ <w f="197">over</w>
+ <w f="197">may</w>
+ <w f="197">any</w>
+ <w f="197">him</w>
+ <w f="197">calling</w>
+ <w f="196">other</w>
+ <w f="196">how</w>
+ <w f="196">see</w>
+ <w f="195">because</w>
+ <w f="195">then</w>
+ <w f="195">right</w>
+ <w f="195">into</w>
+ <w f="195">well</w>
+ <w f="195">very</w>
+ <w f="195">said</w>
+ <w f="195">people</w>
+ <w f="194">these</w>
+ <w f="194">than</w>
+ <w f="193">only</w>
+ <w f="193">back</w>
+ <w f="193">first</w>
+ <w f="193">dot</w>
+ <w f="193">after</w>
+ <w f="193">where</w>
+ <w f="192">please</w>
+ <w f="192">could</w>
+ <w f="192">its</w>
+ <w f="192">before</w>
+ <w f="192">us</w>
+ <w f="192">again</w>
+ <w f="192">home</w>
+ <w f="191">also</w>
+ <w f="191">that's</w>
+ <w f="191">think</w>
+ <w f="191">three</w>
+ <w f="191">good</w>
+ <w f="191">get</w>
+ <w f="190">know</w>
+ <w f="190">thank</w>
+ <w f="190">should</w>
+ <w f="190">going</w>
+ <w f="190">down</w>
+ <w f="189">last</w>
+ <w f="189">today</w>
+ <w f="189">those</w>
+ <w f="189">go</w>
+ <w f="189">through</w>
+ <w f="189">such</w>
+ <w f="189">don't</w>
+ <w f="189">did</w>
+ <w f="188">most</w>
+ <w f="188">day</w>
+ <w f="188">man</w>
+ <w f="188">number</w>
+ <w f="188">work</w>
+ <w f="187">too</w>
+ <w f="187">show</w>
+ <w f="187">made</w>
+ <w f="187">even</w>
+ <w f="187">being</w>
+ <w f="187">make</w>
+ <w f="187">give</w>
+ <w f="186">off</w>
+ <w f="186">com</w>
+ <w f="186">much</w>
+ <w f="186">great</w>
+ <w f="186">take</w>
+ <w f="186">call</w>
+ <w f="186">way</w>
+ <w f="186">four</w>
+ <w f="186">say</w>
+ <w f="185">information</w>
+ <w f="185">under</w>
+ <w f="185">page</w>
+ <w f="185">many</w>
+ <w f="185">little</w>
+ <w f="185">thanks</w>
+ <w f="185">okay</w>
+ <w f="185">five</w>
+ <w f="185">we're</w>
+ <w f="185">between</w>
+ <w f="184">use</w>
+ <w f="184">come</w>
+ <w f="184">years</w>
+ <w f="184">office</w>
+ <w f="184">house</w>
+ <w f="184">search</w>
+ <w f="184">free</w>
+ <w f="183">next</w>
+ <w f="183">without</w>
+ <w f="183">still</w>
+ <w f="183">around</w>
+ <w f="183">I've</w>
+ <w f="183">business</w>
+ <w f="183">part</w>
+ <w f="183">every</w>
+ <w f="183">bye</w>
+ <w f="183">upon</w>
+ <w f="183">you're</w>
+ <w f="183">state</w>
+ <w f="183">life</w>
+ <w f="183">year</w>
+ <w f="182">thing</w>
+ <w f="182">since</w>
+ <w f="182">things</w>
+ <w f="182">something</w>
+ <w f="182">long</w>
+ <w f="182">got</w>
+ <w f="182">while</w>
+ <w f="182">I'll</w>
+ <w f="182">help</w>
+ <w f="182">service</w>
+ <w f="182">really</w>
+ <w f="182">must</w>
+ <w f="182">does</w>
+ <w f="182">name</w>
+ <w f="181">both</w>
+ <w f="181">six</w>
+ <w f="181">want</w>
+ <w f="181">same</w>
+ <w f="181">each</w>
+ <w f="181">yet</w>
+ <w f="181">let</w>
+ <w f="181">view</w>
+ <w f="181">place</w>
+ <w f="181">another</w>
+ <w f="181">company</w>
+ <w f="181">talk</w>
+ <w f="181">might</w>
+ <w f="181">am</w>
+ <w f="181">though</w>
+ <w f="181">find</w>
+ <w f="180">details</w>
+ <w f="180">look</w>
+ <w f="180">world</w>
+ <w f="180">old</w>
+ <w f="180">called</w>
+ <w f="180">case</w>
+ <w f="180">system</w>
+ <w f="180">news</w>
+ <w f="179">used</w>
+ <w f="179">contact</w>
+ <w f="179">never</w>
+ <w f="179">seven</w>
+ <w f="179">city</w>
+ <w f="179">until</w>
+ <w f="179">during</w>
+ <w f="179">set</w>
+ <w f="179">why</w>
+ <w f="179">point</w>
+ <w f="179">twenty</w>
+ <w f="179">high</w>
+ <w f="179">love</w>
+ <w f="179">services</w>
+ <w f="170">niño</w>
+ <w f="170">María</w>
+ <w f="0">hmmm</w>
+ <w f="0">hon</w>
+ <w f="0">tty</w>
+ <w f="0">ttyl</w>
+ <w f="0">txt</w>
+ <w f="0">ur</w>
+ <w f="0">wah</w>
+ <w f="0">whatcha</w>
+ <w f="0">woah</w>
+ <w f="0">ya</w>
+ <w f="0">yea</w>
+ <w f="0">yeh</w>
+ <w f="0">yessir</w>
+ <w f="0">yikes</w>
+ <w f="0">yrs</w>
+</wordlist>
diff --git a/tests/res/raw/test.dict b/tests/res/raw/test.dict
new file mode 100644
index 0000000000000000000000000000000000000000..e789aaa9a66cd003e1282870a79fc12a8d731fdd
Binary files /dev/null and b/tests/res/raw/test.dict differ
diff --git a/tests/src/com/android/inputmethod/latin/tests/SuggestTests.java b/tests/src/com/android/inputmethod/latin/tests/SuggestTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..9401d926a27eff0a237c345c2af48cfe069a646b
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/tests/SuggestTests.java
@@ -0,0 +1,248 @@
+package com.android.inputmethod.latin.tests;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.Channels;
+import java.util.List;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.inputmethod.latin.Suggest;
+import com.android.inputmethod.latin.WordComposer;
+
+public class SuggestTests extends AndroidTestCase {
+    private static final String TAG = "SuggestTests";
+
+    private Suggest mSuggest;
+
+    int[][] adjacents = {
+        {'a','s','w','q',-1},
+        {'b','h','v','n','g','j',-1},
+        {'c','v','f','x','g',},
+        {'d','f','r','e','s','x',-1},
+        {'e','w','r','s','d',-1},
+        {'f','g','d','c','t','r',-1},
+        {'g','h','f','y','t','v',-1},
+        {'h','j','u','g','b','y',-1},
+        {'i','o','u','k',-1},
+        {'j','k','i','h','u','n',-1},
+        {'k','l','o','j','i','m',-1},
+        {'l','k','o','p',-1},
+        {'m','k','n','l',-1},
+        {'n','m','j','k','b',-1},
+        {'o','p','i','l',-1},
+        {'p','o',-1},
+        {'q','w',-1},
+        {'r','t','e','f',-1},
+        {'s','d','e','w','a','z',-1},
+        {'t','y','r',-1},
+        {'u','y','i','h','j',-1},
+        {'v','b','g','c','h',-1},
+        {'w','e','q',-1},
+        {'x','c','d','z','f',-1},
+        {'y','u','t','h','g',-1},
+        {'z','s','x','a','d',-1},
+    };
+
+    @Override
+    protected void setUp() {
+        final Context context = getTestContext();
+        InputStream is = context.getResources().openRawResource(R.raw.test);
+        Log.i(TAG, "Stream type is " + is);
+        try {
+            int avail = is.available();
+            if (avail > 0) {
+                ByteBuffer byteBuffer =
+                    ByteBuffer.allocateDirect(avail).order(ByteOrder.nativeOrder());
+                int got = Channels.newChannel(is).read(byteBuffer);
+                if (got != avail) {
+                    Log.e(TAG, "Read " + got + " bytes, expected " + avail);
+                } else {
+                    mSuggest = new Suggest(context, byteBuffer);
+                    Log.i(TAG, "Created mSuggest " + avail + " bytes");
+                }
+            }
+        } catch (IOException ioe) {
+            Log.w(TAG, "No available size for binary dictionary");
+        }
+        mSuggest.setAutoTextEnabled(false);
+        mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL);
+    }
+
+    /************************** Helper functions ************************/
+
+    private WordComposer createWordComposer(CharSequence s) {
+        WordComposer word = new WordComposer();
+        for (int i = 0; i < s.length(); i++) {
+            final char c = s.charAt(i);
+            int[] codes;
+            // If it's not a lowercase letter, don't find adjacent letters
+            if (c < 'a' || c > 'z') {
+                codes = new int[] { c };
+            } else {
+                codes = adjacents[c - 'a'];
+            }
+            word.add(c, codes);
+        }
+        return word;
+    }
+
+    private void showList(String title, List<CharSequence> suggestions) {
+        Log.i(TAG, title);
+        for (int i = 0; i < suggestions.size(); i++) {
+            Log.i(title, suggestions.get(i) + ", ");
+        }
+    }
+
+    private boolean isDefaultSuggestion(List<CharSequence> suggestions, CharSequence word) {
+        // Check if either the word is what you typed or the first alternative
+        return suggestions.size() > 0 &&
+                (/*TextUtils.equals(suggestions.get(0), word) || */
+                  (suggestions.size() > 1 && TextUtils.equals(suggestions.get(1), word)));
+    }
+
+    private boolean isDefaultSuggestion(CharSequence typed, CharSequence expected) {
+        WordComposer word = createWordComposer(typed);
+        List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false);
+        return isDefaultSuggestion(suggestions, expected);
+    }
+
+    private boolean isDefaultCorrection(CharSequence typed, CharSequence expected) {
+        WordComposer word = createWordComposer(typed);
+        List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false);
+        return isDefaultSuggestion(suggestions, expected) && mSuggest.hasMinimalCorrection();
+    }
+
+    private boolean isASuggestion(CharSequence typed, CharSequence expected) {
+        WordComposer word = createWordComposer(typed);
+        List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false);
+        for (int i = 1; i < suggestions.size(); i++) {
+            if (TextUtils.equals(suggestions.get(i), expected)) return true;
+        }
+        return false;
+    }
+
+    private boolean isValid(CharSequence typed) {
+        return mSuggest.isValidWord(typed);
+    }
+
+    /************************** Tests ************************/
+
+    /**
+     * Tests for simple completions of one character.
+     */
+    public void testCompletion1char() {
+        assertTrue(isDefaultSuggestion("peopl", "people"));
+        assertTrue(isDefaultSuggestion("abou", "about"));
+        assertTrue(isDefaultSuggestion("thei", "their"));
+    }
+
+    /**
+     * Tests for simple completions of two characters.
+     */
+    public void testCompletion2char() {
+        assertTrue(isDefaultSuggestion("peop", "people"));
+        assertTrue(isDefaultSuggestion("calli", "calling"));
+        assertTrue(isDefaultSuggestion("busine", "business"));
+    }
+
+    /**
+     * Tests for proximity errors.
+     */
+    public void testProximityPositive() {
+        assertTrue(isDefaultSuggestion("peiple", "people"));
+        assertTrue(isDefaultSuggestion("peoole", "people"));
+        assertTrue(isDefaultSuggestion("pwpple", "people"));
+    }
+
+    /**
+     * Tests for proximity errors - negative, when the error key is not near.
+     */
+    public void testProximityNegative() {
+        assertFalse(isDefaultSuggestion("arout", "about"));
+        assertFalse(isDefaultSuggestion("ire", "are"));
+    }
+
+    /**
+     * Tests for checking if apostrophes are added automatically.
+     */
+    public void testApostropheInsertion() {
+        assertTrue(isDefaultSuggestion("im", "I'm"));
+        assertTrue(isDefaultSuggestion("dont", "don't"));
+    }
+
+    /**
+     * Test to make sure apostrophed word is not suggested for an apostrophed word.
+     */
+    public void testApostrophe() {
+        assertFalse(isDefaultSuggestion("don't", "don't"));
+    }
+
+    /**
+     * Tests for suggestion of capitalized version of a word.
+     */
+    public void testCapitalization() {
+        assertTrue(isDefaultSuggestion("i'm", "I'm"));
+        assertTrue(isDefaultSuggestion("sunday", "Sunday"));
+        assertTrue(isDefaultSuggestion("sundat", "Sunday"));
+    }
+
+    /**
+     * Tests to see if more than one completion is provided for certain prefixes.
+     */
+    public void testMultipleCompletions() {
+        assertTrue(isASuggestion("com", "come"));
+        assertTrue(isASuggestion("com", "company"));
+        assertTrue(isASuggestion("th", "the"));
+        assertTrue(isASuggestion("th", "that"));
+        assertTrue(isASuggestion("th", "this"));
+        assertTrue(isASuggestion("th", "they"));
+    }
+
+    /**
+     * Does the suggestion engine recognize zero frequency words as valid words.
+     */
+    public void testZeroFrequencyAccepted() {
+        assertTrue(isValid("yikes"));
+        assertFalse(isValid("yike"));
+    }
+
+    /**
+     * Tests to make sure that zero frequency words are not suggested as completions.
+     */
+    public void testZeroFrequencySuggestionsNegative() {
+        assertFalse(isASuggestion("yike", "yikes"));
+        assertFalse(isASuggestion("what", "whatcha"));
+    }
+
+    /**
+     * Tests to ensure that words with large edit distances are not suggested, in some cases
+     * and not considered corrections, in some cases.
+     */
+    public void testTooLargeEditDistance() {
+        assertFalse(isASuggestion("sniyr", "about"));
+        assertFalse(isDefaultCorrection("rjw", "the"));
+    }
+
+    /**
+     * Make sure isValid is case-sensitive.
+     */
+    public void testValidityCaseSensitivity() {
+        assertTrue(isValid("Sunday"));
+        assertFalse(isValid("sunday"));
+    }
+
+    /**
+     * Are accented forms of words suggested as corrections?
+     */
+    public void testAccents() {
+        assertTrue(isDefaultCorrection("nino", "ni\u00F1o")); // ni–o
+        assertTrue(isDefaultCorrection("nimo", "ni\u00F1o")); // ni–o
+        assertTrue(isDefaultCorrection("maria", "Mar\u00EDa")); // Mar’a
+    }
+}