auto import from //branches/cupcake/...@138744
diff --git a/Android.mk b/Android.mk
new file mode 100755
index 0000000..e3215e8
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := user
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := LatinIME
+
+LOCAL_CERTIFICATE := shared
+
+LOCAL_JNI_SHARED_LIBRARIES := libjni_latinime
+
+LOCAL_AAPT_FLAGS := -0 .dict
+
+include $(BUILD_PACKAGE)
+include $(LOCAL_PATH)/dictionary/Android.mk
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100755
index 0000000..89cdad2
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
+        package="com.android.inputmethod.latin"
+        android:sharedUserId="android.uid.shared">
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
+    <uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
+
+    <application android:label="@string/english_ime_name">
+        <service android:name="LatinIME"
+                android:label="@string/english_ime_name"
+                android:permission="android.permission.BIND_INPUT_METHOD">
+            <intent-filter>
+                <action android:name="android.view.InputMethod" />
+            </intent-filter>
+            <meta-data android:name="android.view.im" android:resource="@xml/method" />
+        </service>
+        
+        <activity android:name="LatinIMESettings" android:label="@string/english_ime_settings">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </activity>    
+    </application>
+</manifest>
diff --git a/dictionaries/sample.xml b/dictionaries/sample.xml
new file mode 100644
index 0000000..85233b6
--- /dev/null
+++ b/dictionaries/sample.xml
@@ -0,0 +1,16 @@
+<!-- This is a sample wordlist that can be converted to a binary dictionary
+     for use by the Latin IME.
+     The format of the word list is a flat list of word entries.
+     Each entry has a frequency between 255 and 0.
+     Highest frequency words get more weight in the prediction algorithm.
+     You can capitalize words that must always be capitalized, such as "January".
+     You can have a capitalized and a non-capitalized word as separate entries,
+     such as "robin" and "Robin".
+-->
+<wordlist>
+  <w f="255">this</w>
+  <w f="255">is</w>
+  <w f="128">sample</w>
+  <w f="1">wordlist</w>
+</wordlist>
+
diff --git a/dictionary/Android.mk b/dictionary/Android.mk
new file mode 100644
index 0000000..9ba9f75
--- /dev/null
+++ b/dictionary/Android.mk
@@ -0,0 +1,28 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
+
+LOCAL_SRC_FILES := \
+	jni/com_android_inputmethod_latin_BinaryDictionary.cpp \
+	src/dictionary.cpp
+
+LOCAL_C_INCLUDES += \
+    external/icu4c/common \
+	$(JNI_H_INCLUDE)
+
+LOCAL_LDLIBS := -lm
+
+LOCAL_PRELINK_MODULE := false
+
+LOCAL_SHARED_LIBRARIES := \
+    libandroid_runtime \
+    libcutils \
+    libutils \
+    libicuuc
+
+LOCAL_MODULE := libjni_latinime
+
+LOCAL_MODULE_TAGS := user
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/dictionary/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/dictionary/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
new file mode 100644
index 0000000..65c640b
--- /dev/null
+++ b/dictionary/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -0,0 +1,207 @@
+/*
+**
+** Copyright 2009, 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.
+*/
+
+#define LOG_TAG "BinaryDictionary"
+#include "utils/Log.h"
+
+#include <stdio.h>
+#include <assert.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <nativehelper/jni.h>
+#include "utils/AssetManager.h"
+#include "utils/Asset.h"
+
+#include "dictionary.h"
+
+// ----------------------------------------------------------------------------
+
+using namespace latinime;
+
+using namespace android;
+
+static jfieldID sDescriptorField;
+static jfieldID sAssetManagerNativeField;
+static jmethodID sAddWordMethod;
+
+//
+// helper function to throw an exception
+//
+static void throwException(JNIEnv *env, const char* ex, const char* fmt, int data) 
+{
+    if (jclass cls = env->FindClass(ex)) {
+        char msg[1000];
+        sprintf(msg, fmt, data);
+        env->ThrowNew(cls, msg);
+        env->DeleteLocalRef(cls);
+    }
+}
+
+static jint latinime_BinaryDictionary_open
+        (JNIEnv *env, jobject object, jobject assetManager, jstring resourceString,
+         jint typedLetterMultiplier, jint fullWordMultiplier)
+{
+    // Get the native file descriptor from the FileDescriptor object
+    AssetManager *am = (AssetManager*) env->GetIntField(assetManager, sAssetManagerNativeField);
+    if (!am) {
+        LOGE("DICT: Couldn't get AssetManager native peer\n");
+        return 0;
+    }
+    const char *resourcePath = env->GetStringUTFChars(resourceString, NULL);
+
+    Asset *dictAsset = am->openNonAsset(resourcePath, Asset::ACCESS_BUFFER);
+    if (dictAsset == NULL) {
+        LOGE("DICT: Couldn't get asset %s\n", resourcePath); 
+        env->ReleaseStringUTFChars(resourceString, resourcePath);
+        return 0;
+    }
+
+    void *dict = (void*) dictAsset->getBuffer(false);
+    if (dict == NULL) {
+        LOGE("DICT: Dictionary buffer is null\n");
+        env->ReleaseStringUTFChars(resourceString, resourcePath);
+        return 0;
+    }
+    Dictionary *dictionary = new Dictionary(dict, typedLetterMultiplier, fullWordMultiplier);
+    dictionary->setAsset(dictAsset);
+    
+    env->ReleaseStringUTFChars(resourceString, resourcePath);
+    return (jint) dictionary;  
+}
+
+static int latinime_BinaryDictionary_getSuggestions(
+        JNIEnv *env, jobject object, jint dict, jintArray inputArray, jint arraySize, 
+        jcharArray outputArray, jintArray frequencyArray, jint maxWordLength, jint maxWords, 
+        jint maxAlternatives)
+{
+    Dictionary *dictionary = (Dictionary*) dict;
+    if (dictionary == NULL)
+        return 0;
+
+    int *frequencies = env->GetIntArrayElements(frequencyArray, NULL);
+    int *inputCodes = env->GetIntArrayElements(inputArray, NULL);
+    jchar *outputChars = env->GetCharArrayElements(outputArray, NULL);
+    
+    int count = dictionary->getSuggestions(inputCodes, arraySize, (unsigned short*) outputChars, frequencies,
+            maxWordLength, maxWords, maxAlternatives);
+    
+    env->ReleaseIntArrayElements(frequencyArray, frequencies, JNI_COMMIT);
+    env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT);
+    env->ReleaseCharArrayElements(outputArray, outputChars, JNI_COMMIT);
+    
+    return count;
+}
+
+static jboolean latinime_BinaryDictionary_isValidWord
+        (JNIEnv *env, jobject object, jint dict, jcharArray wordArray, jint wordLength)
+{
+    Dictionary *dictionary = (Dictionary*) dict;
+    if (dictionary == NULL) return (jboolean) false;
+    
+    jchar *word = env->GetCharArrayElements(wordArray, NULL);
+    jboolean result = dictionary->isValidWord((unsigned short*) word, wordLength);
+    env->ReleaseCharArrayElements(wordArray, word, JNI_ABORT);
+    
+    return result;
+}
+
+static void latinime_BinaryDictionary_close
+        (JNIEnv *env, jobject object, jint dict) 
+{
+    Dictionary *dictionary = (Dictionary*) dict;
+    ((Asset*) dictionary->getAsset())->close();
+    delete (Dictionary*) dict;
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gMethods[] = {
+    {"openNative",           "(Landroid/content/res/AssetManager;Ljava/lang/String;II)I", 
+                                          (void*)latinime_BinaryDictionary_open},
+    {"closeNative",          "(I)V",            (void*)latinime_BinaryDictionary_close},
+    {"getSuggestionsNative", "(I[II[C[IIII)I",  (void*)latinime_BinaryDictionary_getSuggestions},
+    {"isValidWordNative",    "(I[CI)Z",         (void*)latinime_BinaryDictionary_isValidWord}
+};
+
+static int registerNativeMethods(JNIEnv* env, const char* className,
+    JNINativeMethod* gMethods, int numMethods)
+{
+    jclass clazz;
+
+    clazz = env->FindClass(className);
+    if (clazz == NULL) {
+        fprintf(stderr,
+            "Native registration unable to find class '%s'\n", className);
+        return JNI_FALSE;
+    }
+    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
+        fprintf(stderr, "RegisterNatives failed for '%s'\n", className);
+        return JNI_FALSE;
+    }
+  
+    return JNI_TRUE;
+}
+
+static int registerNatives(JNIEnv *env)
+{
+    const char* const kClassPathName = "com/android/inputmethod/latin/BinaryDictionary";
+    jclass clazz;
+    
+    clazz = env->FindClass("java/io/FileDescriptor");
+    if (clazz == NULL) {
+        LOGE("Can't find %s", "java/io/FileDescriptor");
+        return -1;
+    }
+    sDescriptorField = env->GetFieldID(clazz, "descriptor", "I");
+    
+    clazz = env->FindClass("android/content/res/AssetManager");
+    if (clazz == NULL) {
+        LOGE("Can't find %s", "java/io/FileDescriptor");
+        return -1;
+    }
+    sAssetManagerNativeField = env->GetFieldID(clazz, "mObject", "I");
+    
+    return registerNativeMethods(env,
+            kClassPathName, gMethods, sizeof(gMethods) / sizeof(gMethods[0]));
+}
+
+/*
+ * Returns the JNI version on success, -1 on failure.
+ */
+jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+    JNIEnv* env = NULL;
+    jint result = -1;
+
+    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+        fprintf(stderr, "ERROR: GetEnv failed\n");
+        goto bail;
+    }
+    assert(env != NULL);
+
+    if (!registerNatives(env)) {
+        fprintf(stderr, "ERROR: BinaryDictionary native registration failed\n");
+        goto bail;
+    }
+
+    /* success -- return valid version number */
+    result = JNI_VERSION_1_4;
+
+bail:
+    return result;
+}
diff --git a/dictionary/src/basechars.h b/dictionary/src/basechars.h
new file mode 100644
index 0000000..5a44066
--- /dev/null
+++ b/dictionary/src/basechars.h
@@ -0,0 +1,172 @@
+/**
+ * Table mapping most combined Latin, Greek, and Cyrillic characters
+ * to their base characters.  If c is in range, BASE_CHARS[c] == c
+ * if c is not a combined character, or the base character if it
+ * is combined.
+ */
+static unsigned short BASE_CHARS[] = {
+    0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+    0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+    0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
+    0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+    0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+    0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+    0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+    0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+    0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+    0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+    0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+    0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+    0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+    0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+    0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+    0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
+    0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+    0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
+    0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+    0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
+    0x0020, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
+    0x0020, 0x00a9, 0x0061, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x0020,
+    0x00b0, 0x00b1, 0x0032, 0x0033, 0x0020, 0x03bc, 0x00b6, 0x00b7,
+    0x0020, 0x0031, 0x006f, 0x00bb, 0x0031, 0x0031, 0x0033, 0x00bf,
+    0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00c6, 0x0043,
+    0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049,
+    0x00d0, 0x004e, 0x004f, 0x004f, 0x004f, 0x004f, 0x004f, 0x00d7,
+    0x004f, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00de, 0x0073, // Manually changed d8 to 4f
+                                                                    // Manually changed df to 73
+    0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00e6, 0x0063,
+    0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069,
+    0x00f0, 0x006e, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x00f7,
+    0x006f, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00fe, 0x0079, // Manually changed f8 to 6f
+    0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063,
+    0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064,
+    0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065,
+    0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067,
+    0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127,
+    0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069,
+    0x0049, 0x0131, 0x0049, 0x0069, 0x004a, 0x006a, 0x004b, 0x006b,
+    0x0138, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c,
+    0x006c, 0x0141, 0x0142, 0x004e, 0x006e, 0x004e, 0x006e, 0x004e,
+    0x006e, 0x02bc, 0x014a, 0x014b, 0x004f, 0x006f, 0x004f, 0x006f,
+    0x004f, 0x006f, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072,
+    0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073,
+    0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167,
+    0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075,
+    0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079,
+    0x0059, 0x005a, 0x007a, 0x005a, 0x007a, 0x005a, 0x007a, 0x0073,
+    0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187,
+    0x0188, 0x0189, 0x018a, 0x018b, 0x018c, 0x018d, 0x018e, 0x018f,
+    0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197,
+    0x0198, 0x0199, 0x019a, 0x019b, 0x019c, 0x019d, 0x019e, 0x019f,
+    0x004f, 0x006f, 0x01a2, 0x01a3, 0x01a4, 0x01a5, 0x01a6, 0x01a7,
+    0x01a8, 0x01a9, 0x01aa, 0x01ab, 0x01ac, 0x01ad, 0x01ae, 0x0055,
+    0x0075, 0x01b1, 0x01b2, 0x01b3, 0x01b4, 0x01b5, 0x01b6, 0x01b7,
+    0x01b8, 0x01b9, 0x01ba, 0x01bb, 0x01bc, 0x01bd, 0x01be, 0x01bf,
+    0x01c0, 0x01c1, 0x01c2, 0x01c3, 0x0044, 0x0044, 0x0064, 0x004c,
+    0x004c, 0x006c, 0x004e, 0x004e, 0x006e, 0x0041, 0x0061, 0x0049,
+    0x0069, 0x004f, 0x006f, 0x0055, 0x0075, 0x00dc, 0x00fc, 0x00dc,
+    0x00fc, 0x00dc, 0x00fc, 0x00dc, 0x00fc, 0x01dd, 0x00c4, 0x00e4,
+    0x0226, 0x0227, 0x00c6, 0x00e6, 0x01e4, 0x01e5, 0x0047, 0x0067,
+    0x004b, 0x006b, 0x004f, 0x006f, 0x01ea, 0x01eb, 0x01b7, 0x0292,
+    0x006a, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01f6, 0x01f7,
+    0x004e, 0x006e, 0x00c5, 0x00e5, 0x00c6, 0x00e6, 0x00d8, 0x00f8,
+    0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065,
+    0x0049, 0x0069, 0x0049, 0x0069, 0x004f, 0x006f, 0x004f, 0x006f,
+    0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075,
+    0x0053, 0x0073, 0x0054, 0x0074, 0x021c, 0x021d, 0x0048, 0x0068,
+    0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061,
+    0x0045, 0x0065, 0x00d6, 0x00f6, 0x00d5, 0x00f5, 0x004f, 0x006f,
+    0x022e, 0x022f, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237,
+    0x0238, 0x0239, 0x023a, 0x023b, 0x023c, 0x023d, 0x023e, 0x023f,
+    0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247,
+    0x0248, 0x0249, 0x024a, 0x024b, 0x024c, 0x024d, 0x024e, 0x024f,
+    0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257,
+    0x0258, 0x0259, 0x025a, 0x025b, 0x025c, 0x025d, 0x025e, 0x025f,
+    0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267,
+    0x0268, 0x0269, 0x026a, 0x026b, 0x026c, 0x026d, 0x026e, 0x026f,
+    0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277,
+    0x0278, 0x0279, 0x027a, 0x027b, 0x027c, 0x027d, 0x027e, 0x027f,
+    0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287,
+    0x0288, 0x0289, 0x028a, 0x028b, 0x028c, 0x028d, 0x028e, 0x028f,
+    0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297,
+    0x0298, 0x0299, 0x029a, 0x029b, 0x029c, 0x029d, 0x029e, 0x029f,
+    0x02a0, 0x02a1, 0x02a2, 0x02a3, 0x02a4, 0x02a5, 0x02a6, 0x02a7,
+    0x02a8, 0x02a9, 0x02aa, 0x02ab, 0x02ac, 0x02ad, 0x02ae, 0x02af,
+    0x0068, 0x0266, 0x006a, 0x0072, 0x0279, 0x027b, 0x0281, 0x0077,
+    0x0079, 0x02b9, 0x02ba, 0x02bb, 0x02bc, 0x02bd, 0x02be, 0x02bf,
+    0x02c0, 0x02c1, 0x02c2, 0x02c3, 0x02c4, 0x02c5, 0x02c6, 0x02c7,
+    0x02c8, 0x02c9, 0x02ca, 0x02cb, 0x02cc, 0x02cd, 0x02ce, 0x02cf,
+    0x02d0, 0x02d1, 0x02d2, 0x02d3, 0x02d4, 0x02d5, 0x02d6, 0x02d7,
+    0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02de, 0x02df,
+    0x0263, 0x006c, 0x0073, 0x0078, 0x0295, 0x02e5, 0x02e6, 0x02e7,
+    0x02e8, 0x02e9, 0x02ea, 0x02eb, 0x02ec, 0x02ed, 0x02ee, 0x02ef,
+    0x02f0, 0x02f1, 0x02f2, 0x02f3, 0x02f4, 0x02f5, 0x02f6, 0x02f7,
+    0x02f8, 0x02f9, 0x02fa, 0x02fb, 0x02fc, 0x02fd, 0x02fe, 0x02ff,
+    0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307,
+    0x0308, 0x0309, 0x030a, 0x030b, 0x030c, 0x030d, 0x030e, 0x030f,
+    0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317,
+    0x0318, 0x0319, 0x031a, 0x031b, 0x031c, 0x031d, 0x031e, 0x031f,
+    0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327,
+    0x0328, 0x0329, 0x032a, 0x032b, 0x032c, 0x032d, 0x032e, 0x032f,
+    0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337,
+    0x0338, 0x0339, 0x033a, 0x033b, 0x033c, 0x033d, 0x033e, 0x033f,
+    0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347,
+    0x0348, 0x0349, 0x034a, 0x034b, 0x034c, 0x034d, 0x034e, 0x034f,
+    0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357,
+    0x0358, 0x0359, 0x035a, 0x035b, 0x035c, 0x035d, 0x035e, 0x035f,
+    0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367,
+    0x0368, 0x0369, 0x036a, 0x036b, 0x036c, 0x036d, 0x036e, 0x036f,
+    0x0370, 0x0371, 0x0372, 0x0373, 0x02b9, 0x0375, 0x0376, 0x0377,
+    0x0378, 0x0379, 0x0020, 0x037b, 0x037c, 0x037d, 0x003b, 0x037f,
+    0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00a8, 0x0391, 0x00b7,
+    0x0395, 0x0397, 0x0399, 0x038b, 0x039f, 0x038d, 0x03a5, 0x03a9,
+    0x03ca, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
+    0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f,
+    0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7,
+    0x03a8, 0x03a9, 0x0399, 0x03a5, 0x03b1, 0x03b5, 0x03b7, 0x03b9,
+    0x03cb, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7,
+    0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf,
+    0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7,
+    0x03c8, 0x03c9, 0x03b9, 0x03c5, 0x03bf, 0x03c5, 0x03c9, 0x03cf,
+    0x03b2, 0x03b8, 0x03a5, 0x03d2, 0x03d2, 0x03c6, 0x03c0, 0x03d7,
+    0x03d8, 0x03d9, 0x03da, 0x03db, 0x03dc, 0x03dd, 0x03de, 0x03df,
+    0x03e0, 0x03e1, 0x03e2, 0x03e3, 0x03e4, 0x03e5, 0x03e6, 0x03e7,
+    0x03e8, 0x03e9, 0x03ea, 0x03eb, 0x03ec, 0x03ed, 0x03ee, 0x03ef,
+    0x03ba, 0x03c1, 0x03c2, 0x03f3, 0x0398, 0x03b5, 0x03f6, 0x03f7,
+    0x03f8, 0x03a3, 0x03fa, 0x03fb, 0x03fc, 0x03fd, 0x03fe, 0x03ff,
+    0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406,
+    0x0408, 0x0409, 0x040a, 0x040b, 0x041a, 0x0418, 0x0423, 0x040f,
+    0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
+    0x0418, 0x0418, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f,
+    0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
+    0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f,
+    0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
+    0x0438, 0x0438, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
+    0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
+    0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f,
+    0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456,
+    0x0458, 0x0459, 0x045a, 0x045b, 0x043a, 0x0438, 0x0443, 0x045f,
+    0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467,
+    0x0468, 0x0469, 0x046a, 0x046b, 0x046c, 0x046d, 0x046e, 0x046f,
+    0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475,
+    0x0478, 0x0479, 0x047a, 0x047b, 0x047c, 0x047d, 0x047e, 0x047f,
+    0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487,
+    0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x048f,
+    0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497,
+    0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x049f,
+    0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x04a7,
+    0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x04af,
+    0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x04b7,
+    0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x04be, 0x04bf,
+    0x04c0, 0x0416, 0x0436, 0x04c3, 0x04c4, 0x04c5, 0x04c6, 0x04c7,
+    0x04c8, 0x04c9, 0x04ca, 0x04cb, 0x04cc, 0x04cd, 0x04ce, 0x04cf,
+    0x0410, 0x0430, 0x0410, 0x0430, 0x04d4, 0x04d5, 0x0415, 0x0435,
+    0x04d8, 0x04d9, 0x04d8, 0x04d9, 0x0416, 0x0436, 0x0417, 0x0437,
+    0x04e0, 0x04e1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041e, 0x043e,
+    0x04e8, 0x04e9, 0x04e8, 0x04e9, 0x042d, 0x044d, 0x0423, 0x0443,
+    0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04f6, 0x04f7,
+    0x042b, 0x044b, 0x04fa, 0x04fb, 0x04fc, 0x04fd, 0x04fe, 0x04ff,
+};
+
+// generated with:
+// cat UnicodeData.txt | perl -e 'while (<>) { @foo = split(/;/); $foo[5] =~ s/<.*> //; $base[hex($foo[0])] = hex($foo[5]);} for ($i = 0; $i < 0x500; $i += 8) { for ($j = $i; $j < $i + 8; $j++) { printf("0x%04x, ", $base[$j] ? $base[$j] : $j)}; print "\n"; }'
diff --git a/dictionary/src/dictionary.cpp b/dictionary/src/dictionary.cpp
new file mode 100644
index 0000000..6aecb63
--- /dev/null
+++ b/dictionary/src/dictionary.cpp
@@ -0,0 +1,277 @@
+/*
+**
+** Copyright 2009, 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.
+*/
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <string.h>
+#include <cutils/log.h>
+
+#include <unicode/uchar.h>
+
+//#define USE_ASSET_MANAGER
+
+#ifdef USE_ASSET_MANAGER
+#include <utils/AssetManager.h>
+#include <utils/Asset.h>
+#endif
+
+#include "dictionary.h"
+#include "basechars.h"
+
+#define DEBUG_DICT 0
+
+namespace latinime {
+
+Dictionary::Dictionary(void *dict, int typedLetterMultiplier, int fullWordMultiplier)
+{
+    mDict = (unsigned char*) dict;
+    mTypedLetterMultiplier = typedLetterMultiplier;
+    mFullWordMultiplier = fullWordMultiplier;
+}
+
+Dictionary::~Dictionary()
+{
+}
+
+int Dictionary::getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies,
+        int maxWordLength, int maxWords, int maxAlternatives)
+{
+    memset(frequencies, 0, maxWords * sizeof(*frequencies));
+    memset(outWords, 0, maxWords * maxWordLength * sizeof(*outWords));
+
+    mFrequencies = frequencies;
+    mOutputChars = outWords;
+    mInputCodes = codes;
+    mInputLength = codesSize;
+    mMaxAlternatives = maxAlternatives;
+    mMaxWordLength = maxWordLength;
+    mMaxWords = maxWords;
+    mWords = 0;
+
+    getWordsRec(0, 0, mInputLength * 3, false, 1, 0);
+
+    if (DEBUG_DICT) LOGI("Returning %d words", mWords);
+    return mWords;
+}
+
+unsigned short
+Dictionary::getChar(int *pos)
+{
+    unsigned short ch = (unsigned short) (mDict[(*pos)++] & 0xFF);
+    // If the code is 255, then actual 16 bit code follows (in big endian)
+    if (ch == 0xFF) {
+        ch = ((mDict[*pos] & 0xFF) << 8) | (mDict[*pos + 1] & 0xFF);
+        (*pos) += 2;
+    }
+    return ch;
+}
+
+int
+Dictionary::getAddress(int *pos)
+{
+    int address = 0;
+    address += (mDict[*pos] & 0x7F) << 16;
+    address += (mDict[*pos + 1] & 0xFF) << 8;
+    address += (mDict[*pos + 2] & 0xFF);
+    *pos += 3;
+    return address;
+}
+
+int
+Dictionary::wideStrLen(unsigned short *str)
+{
+    if (!str) return 0;
+    unsigned short *end = str;
+    while (*end)
+        end++;
+    return end - str;
+}
+
+bool
+Dictionary::addWord(unsigned short *word, int length, int frequency)
+{
+    word[length] = 0;
+    if (DEBUG_DICT) LOGI("Found word = %s, freq = %d : \n", word, frequency);
+
+    // Find the right insertion point
+    int insertAt = 0;
+    while (insertAt < mMaxWords) {
+        if (frequency > mFrequencies[insertAt]
+                 || (mFrequencies[insertAt] == frequency
+                     && length < wideStrLen(mOutputChars + insertAt * mMaxWordLength))) {
+            break;
+        }
+        insertAt++;
+    }
+    if (insertAt < mMaxWords) {
+        memmove((char*) mFrequencies + (insertAt + 1) * sizeof(mFrequencies[0]),
+               (char*) mFrequencies + insertAt * sizeof(mFrequencies[0]),
+               (mMaxWords - insertAt - 1) * sizeof(mFrequencies[0]));
+        mFrequencies[insertAt] = frequency;
+        memmove((char*) mOutputChars + (insertAt + 1) * mMaxWordLength * sizeof(short),
+               (char*) mOutputChars + (insertAt    ) * mMaxWordLength * sizeof(short),
+               (mMaxWords - insertAt - 1) * sizeof(short) * mMaxWordLength);
+        unsigned short *dest = mOutputChars + (insertAt    ) * mMaxWordLength;
+        while (length--) {
+            *dest++ = *word++;
+        }
+        *dest = 0; // NULL terminate
+        // Update the word count
+        if (insertAt + 1 > mWords) mWords = insertAt + 1;
+        if (DEBUG_DICT) LOGI("Added word at %d\n", insertAt);
+        return true;
+    }
+    return false;
+}
+
+unsigned short
+Dictionary::toLowerCase(unsigned short c, const int depth) {
+    if (c < sizeof(BASE_CHARS) / sizeof(BASE_CHARS[0])) {
+        c = BASE_CHARS[c];
+    }
+    if (depth == 0) {
+        if (c >='A' && c <= 'Z') {
+            c |= 32;
+        } else if (c > 127) {
+            c = u_tolower(c);
+        }
+    }
+    return c;
+}
+
+bool
+Dictionary::sameAsTyped(unsigned short *word, int length)
+{
+    if (length != mInputLength) {
+        return false;
+    }
+    int *inputCodes = mInputCodes;
+    while (length--) {
+        if ((unsigned int) *inputCodes != (unsigned int) *word) {
+            return false;
+        }
+        inputCodes += mMaxAlternatives;
+        word++;
+    }
+    return true;
+}
+
+static char QUOTE = '\'';
+
+void
+Dictionary::getWordsRec(int pos, int depth, int maxDepth, bool completion, int snr, int inputIndex)
+{
+    // Optimization: Prune out words that are too long compared to how much was typed.
+    if (depth > maxDepth) {
+        return;
+    }
+    int count = getCount(&pos);
+    int *currentChars = NULL;
+    if (mInputLength <= inputIndex) {
+        completion = true;
+    } else {
+        currentChars = mInputCodes + (inputIndex * mMaxAlternatives);
+    }
+
+    for (int i = 0; i < count; i++) {
+        unsigned short c = getChar(&pos);
+        unsigned short lowerC = toLowerCase(c, depth);
+        bool terminal = getTerminal(&pos);
+        int childrenAddress = getAddress(&pos);
+        int freq = getFreq(&pos);
+        // If we are only doing completions, no need to look at the typed characters.
+        if (completion) {
+            mWord[depth] = c;
+            if (terminal) {
+                addWord(mWord, depth + 1, freq * snr);
+            }
+            if (childrenAddress != 0) {
+                getWordsRec(childrenAddress, depth + 1, maxDepth,
+                            completion, snr, inputIndex);
+            }
+        } else if (c == QUOTE && currentChars[0] != QUOTE) {
+            // Skip the ' and continue deeper
+            mWord[depth] = QUOTE;
+            if (childrenAddress != 0) {
+                getWordsRec(childrenAddress, depth + 1, maxDepth, false, snr, inputIndex);
+            }
+        } else {
+            int j = 0;
+            while (currentChars[j] > 0) {
+                int addedWeight = j == 0 ? mTypedLetterMultiplier : 1;
+                if (currentChars[j] == lowerC || currentChars[j] == c) {
+                    mWord[depth] = c;
+                    if (mInputLength == inputIndex + 1) {
+                        if (terminal) {
+                            if (//INCLUDE_TYPED_WORD_IF_VALID ||
+                                !sameAsTyped(mWord, depth + 1)) {
+                                addWord(mWord, depth + 1,
+                                    (freq * snr * addedWeight * mFullWordMultiplier));
+                            }
+                        }
+                        if (childrenAddress != 0) {
+                            getWordsRec(childrenAddress, depth + 1,
+                                    maxDepth, true, snr * addedWeight, inputIndex + 1);
+                        }
+                    } else if (childrenAddress != 0) {
+                        getWordsRec(childrenAddress, depth + 1, maxDepth,
+                                false, snr * addedWeight, inputIndex + 1);
+                    }
+                }
+                j++;
+            }
+        }
+    }
+}
+
+bool
+Dictionary::isValidWord(unsigned short *word, int length)
+{
+    return isValidWordRec(0, word, 0, length);
+}
+
+bool
+Dictionary::isValidWordRec(int pos, unsigned short *word, int offset, int length) {
+    int count = getCount(&pos);
+    unsigned short currentChar = (unsigned short) word[offset];
+    for (int j = 0; j < count; j++) {
+        unsigned short c = getChar(&pos);
+        int terminal = getTerminal(&pos);
+        int childPos = getAddress(&pos);
+        if (c == currentChar) {
+            if (offset == length - 1) {
+                if (terminal) {
+                    return true;
+                }
+            } else {
+                if (childPos != 0) {
+                    if (isValidWordRec(childPos, word, offset + 1, length)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        getFreq(&pos);
+        // There could be two instances of each alphabet - upper and lower case. So continue
+        // looking ...
+    }
+    return false;
+}
+
+
+} // namespace latinime
diff --git a/dictionary/src/dictionary.h b/dictionary/src/dictionary.h
new file mode 100644
index 0000000..8574e07
--- /dev/null
+++ b/dictionary/src/dictionary.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+#ifndef LATINIME_DICTIONARY_H
+#define LATINIME_DICTIONARY_H
+
+namespace latinime {
+
+class Dictionary {
+public:
+    Dictionary(void *dict, int typedLetterMultipler, int fullWordMultiplier);
+    int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies, 
+        int maxWordLength, int maxWords, int maxAlternatives);
+    bool isValidWord(unsigned short *word, int length);
+    void setAsset(void *asset) { mAsset = asset; }
+    void *getAsset() { return mAsset; }
+    ~Dictionary();
+    
+private:
+
+    int getAddress(int *pos);
+    bool getTerminal(int *pos) { return (mDict[*pos] & 0x80) > 0; }
+    int getFreq(int *pos) { return mDict[(*pos)++] & 0xFF; }
+    int getCount(int *pos) { return mDict[(*pos)++] & 0xFF; }
+    unsigned short getChar(int *pos);
+    int wideStrLen(unsigned short *str);
+    
+    bool sameAsTyped(unsigned short *word, int length);
+    bool addWord(unsigned short *word, int length, int frequency);
+    unsigned short toLowerCase(unsigned short c, int depth);
+    void getWordsRec(int pos, int depth, int maxDepth, bool completion, int frequency, 
+            int inputIndex);
+    bool isValidWordRec(int pos, unsigned short *word, int offset, int length);
+
+    unsigned char *mDict;
+    void *mAsset;
+    
+    int *mFrequencies;
+    int mMaxWords;
+    int mMaxWordLength;
+    int mWords;
+    unsigned short *mOutputChars;
+    int *mInputCodes;
+    int mInputLength;
+    int mMaxAlternatives;
+    unsigned short mWord[128];
+    
+    int mFullWordMultiplier;
+    int mTypedLetterMultiplier;
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace latinime
+
+#endif // LATINIME_DICTIONARY_H
diff --git a/res/drawable-land/keyboard_suggest_strip_divider.png b/res/drawable-land/keyboard_suggest_strip_divider.png
new file mode 100644
index 0000000..e54c5b0
--- /dev/null
+++ b/res/drawable-land/keyboard_suggest_strip_divider.png
Binary files differ
diff --git a/res/drawable/candidate_feedback_background.9.png b/res/drawable/candidate_feedback_background.9.png
new file mode 100644
index 0000000..2a80f09
--- /dev/null
+++ b/res/drawable/candidate_feedback_background.9.png
Binary files differ
diff --git a/res/drawable/dialog_bubble_step02.9.png b/res/drawable/dialog_bubble_step02.9.png
new file mode 100755
index 0000000..d77f85f
--- /dev/null
+++ b/res/drawable/dialog_bubble_step02.9.png
Binary files differ
diff --git a/res/drawable/dialog_bubble_step03.9.png b/res/drawable/dialog_bubble_step03.9.png
new file mode 100755
index 0000000..16b4d02
--- /dev/null
+++ b/res/drawable/dialog_bubble_step03.9.png
Binary files differ
diff --git a/res/drawable/dialog_bubble_step04.9.png b/res/drawable/dialog_bubble_step04.9.png
new file mode 100755
index 0000000..a24012d
--- /dev/null
+++ b/res/drawable/dialog_bubble_step04.9.png
Binary files differ
diff --git a/res/drawable/highlight_pressed.png b/res/drawable/highlight_pressed.png
new file mode 100644
index 0000000..d27f106
--- /dev/null
+++ b/res/drawable/highlight_pressed.png
Binary files differ
diff --git a/res/drawable/ic_dialog_keyboard.png b/res/drawable/ic_dialog_keyboard.png
new file mode 100644
index 0000000..9a5aada
--- /dev/null
+++ b/res/drawable/ic_dialog_keyboard.png
Binary files differ
diff --git a/res/drawable/ic_suggest_scroll_background.xml b/res/drawable/ic_suggest_scroll_background.xml
new file mode 100644
index 0000000..9d246e4
--- /dev/null
+++ b/res/drawable/ic_suggest_scroll_background.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="false"
+        android:drawable="@android:color/transparent" />
+
+    <item android:state_pressed="true"
+        android:drawable="@drawable/highlight_pressed" />
+
+</selector>
\ No newline at end of file
diff --git a/res/drawable/ic_suggest_strip_scroll_left_arrow.png b/res/drawable/ic_suggest_strip_scroll_left_arrow.png
new file mode 100644
index 0000000..a9adef2
--- /dev/null
+++ b/res/drawable/ic_suggest_strip_scroll_left_arrow.png
Binary files differ
diff --git a/res/drawable/ic_suggest_strip_scroll_right_arrow.png b/res/drawable/ic_suggest_strip_scroll_right_arrow.png
new file mode 100644
index 0000000..639a287
--- /dev/null
+++ b/res/drawable/ic_suggest_strip_scroll_right_arrow.png
Binary files differ
diff --git a/res/drawable/keyboard_suggest_strip.9.png b/res/drawable/keyboard_suggest_strip.9.png
new file mode 100644
index 0000000..71bf5e8
--- /dev/null
+++ b/res/drawable/keyboard_suggest_strip.9.png
Binary files differ
diff --git a/res/drawable/keyboard_suggest_strip_divider.png b/res/drawable/keyboard_suggest_strip_divider.png
new file mode 100644
index 0000000..e54c5b0
--- /dev/null
+++ b/res/drawable/keyboard_suggest_strip_divider.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_delete.png b/res/drawable/sym_keyboard_delete.png
new file mode 100644
index 0000000..f1f7c58
--- /dev/null
+++ b/res/drawable/sym_keyboard_delete.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_done.png b/res/drawable/sym_keyboard_done.png
new file mode 100755
index 0000000..c0d6d13
--- /dev/null
+++ b/res/drawable/sym_keyboard_done.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_feedback_delete.png b/res/drawable/sym_keyboard_feedback_delete.png
new file mode 100644
index 0000000..3c90839
--- /dev/null
+++ b/res/drawable/sym_keyboard_feedback_delete.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_feedback_done.png b/res/drawable/sym_keyboard_feedback_done.png
new file mode 100755
index 0000000..0d7ebd4
--- /dev/null
+++ b/res/drawable/sym_keyboard_feedback_done.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_feedback_numalt.png b/res/drawable/sym_keyboard_feedback_numalt.png
new file mode 100644
index 0000000..aac7376
--- /dev/null
+++ b/res/drawable/sym_keyboard_feedback_numalt.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_feedback_numpound.png b/res/drawable/sym_keyboard_feedback_numpound.png
new file mode 100644
index 0000000..6b6561e
--- /dev/null
+++ b/res/drawable/sym_keyboard_feedback_numpound.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_feedback_numstar.png b/res/drawable/sym_keyboard_feedback_numstar.png
new file mode 100644
index 0000000..05f7b4f
--- /dev/null
+++ b/res/drawable/sym_keyboard_feedback_numstar.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_feedback_return.png b/res/drawable/sym_keyboard_feedback_return.png
new file mode 100644
index 0000000..03d9c9b
--- /dev/null
+++ b/res/drawable/sym_keyboard_feedback_return.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_feedback_search.png b/res/drawable/sym_keyboard_feedback_search.png
new file mode 100755
index 0000000..f4af341
--- /dev/null
+++ b/res/drawable/sym_keyboard_feedback_search.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_feedback_shift.png b/res/drawable/sym_keyboard_feedback_shift.png
new file mode 100644
index 0000000..97f4661
--- /dev/null
+++ b/res/drawable/sym_keyboard_feedback_shift.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_feedback_shift_locked.png b/res/drawable/sym_keyboard_feedback_shift_locked.png
new file mode 100755
index 0000000..7194b30
--- /dev/null
+++ b/res/drawable/sym_keyboard_feedback_shift_locked.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_feedback_space.png b/res/drawable/sym_keyboard_feedback_space.png
new file mode 100644
index 0000000..739db68
--- /dev/null
+++ b/res/drawable/sym_keyboard_feedback_space.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_num0.png b/res/drawable/sym_keyboard_num0.png
new file mode 100644
index 0000000..e7007c8
--- /dev/null
+++ b/res/drawable/sym_keyboard_num0.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_num1.png b/res/drawable/sym_keyboard_num1.png
new file mode 100644
index 0000000..aaac11b
--- /dev/null
+++ b/res/drawable/sym_keyboard_num1.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_num2.png b/res/drawable/sym_keyboard_num2.png
new file mode 100644
index 0000000..4372eb8
--- /dev/null
+++ b/res/drawable/sym_keyboard_num2.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_num3.png b/res/drawable/sym_keyboard_num3.png
new file mode 100644
index 0000000..6f54c85
--- /dev/null
+++ b/res/drawable/sym_keyboard_num3.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_num4.png b/res/drawable/sym_keyboard_num4.png
new file mode 100644
index 0000000..3e50bb9
--- /dev/null
+++ b/res/drawable/sym_keyboard_num4.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_num5.png b/res/drawable/sym_keyboard_num5.png
new file mode 100644
index 0000000..c39ef44
--- /dev/null
+++ b/res/drawable/sym_keyboard_num5.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_num6.png b/res/drawable/sym_keyboard_num6.png
new file mode 100644
index 0000000..ea88ceb
--- /dev/null
+++ b/res/drawable/sym_keyboard_num6.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_num7.png b/res/drawable/sym_keyboard_num7.png
new file mode 100644
index 0000000..4d75583
--- /dev/null
+++ b/res/drawable/sym_keyboard_num7.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_num8.png b/res/drawable/sym_keyboard_num8.png
new file mode 100644
index 0000000..1a8ff94
--- /dev/null
+++ b/res/drawable/sym_keyboard_num8.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_num9.png b/res/drawable/sym_keyboard_num9.png
new file mode 100644
index 0000000..8b344c0
--- /dev/null
+++ b/res/drawable/sym_keyboard_num9.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_numalt.png b/res/drawable/sym_keyboard_numalt.png
new file mode 100644
index 0000000..32a2cf3
--- /dev/null
+++ b/res/drawable/sym_keyboard_numalt.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_numpound.png b/res/drawable/sym_keyboard_numpound.png
new file mode 100644
index 0000000..b2419d9
--- /dev/null
+++ b/res/drawable/sym_keyboard_numpound.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_numstar.png b/res/drawable/sym_keyboard_numstar.png
new file mode 100644
index 0000000..cb66f96
--- /dev/null
+++ b/res/drawable/sym_keyboard_numstar.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_return.png b/res/drawable/sym_keyboard_return.png
new file mode 100644
index 0000000..17f2574
--- /dev/null
+++ b/res/drawable/sym_keyboard_return.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_search.png b/res/drawable/sym_keyboard_search.png
new file mode 100755
index 0000000..127755d
--- /dev/null
+++ b/res/drawable/sym_keyboard_search.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_shift.png b/res/drawable/sym_keyboard_shift.png
new file mode 100644
index 0000000..0566e5a
--- /dev/null
+++ b/res/drawable/sym_keyboard_shift.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_shift_locked.png b/res/drawable/sym_keyboard_shift_locked.png
new file mode 100755
index 0000000..ccaf05d
--- /dev/null
+++ b/res/drawable/sym_keyboard_shift_locked.png
Binary files differ
diff --git a/res/drawable/sym_keyboard_space.png b/res/drawable/sym_keyboard_space.png
new file mode 100644
index 0000000..4e6273b
--- /dev/null
+++ b/res/drawable/sym_keyboard_space.png
Binary files differ
diff --git a/res/layout/bubble_text.xml b/res/layout/bubble_text.xml
new file mode 100644
index 0000000..af8abce
--- /dev/null
+++ b/res/layout/bubble_text.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2009, 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.
+*/
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content" 
+    android:layout_height="wrap_content"
+    android:textSize="14sp"
+    android:textColor="?android:attr/textColorPrimary"
+    android:minWidth="32dip"
+    />
diff --git a/res/layout/candidate_preview.xml b/res/layout/candidate_preview.xml
new file mode 100755
index 0000000..fe2002d
--- /dev/null
+++ b/res/layout/candidate_preview.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content" 
+    android:layout_height="wrap_content"
+    android:textSize="18sp"
+    android:textColor="?android:attr/textColorPrimaryInverse"
+    android:minWidth="32dip"
+    android:gravity="center"
+    android:background="@drawable/candidate_feedback_background"
+    />
diff --git a/res/layout/candidates.xml b/res/layout/candidates.xml
new file mode 100755
index 0000000..edd779a
--- /dev/null
+++ b/res/layout/candidates.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<com.android.inputmethod.latin.CandidateViewContainer
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/keyboard_suggest_strip"
+        >
+
+    <LinearLayout
+            android:id="@+id/candidate_left_parent"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:orientation="horizontal">
+        <ImageButton
+            android:id="@+id/candidate_left"
+            android:background="@drawable/ic_suggest_scroll_background"
+            android:src="@drawable/ic_suggest_strip_scroll_left_arrow" 
+            android:layout_width="36dp"
+            android:layout_height="fill_parent"
+            android:clickable="true"
+            />
+        
+        <ImageView
+            android:src="@drawable/keyboard_suggest_strip_divider"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            />
+    </LinearLayout>
+    
+    <com.android.inputmethod.latin.CandidateView
+        android:id="@+id/candidates"
+        android:layout_width="wrap_content"
+        android:layout_height="38dp"
+        android:layout_weight="1"
+        />
+        
+    <LinearLayout
+            android:id="@+id/candidate_right_parent"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:clickable="true"
+            android:orientation="horizontal">
+        <ImageView
+            android:src="@drawable/keyboard_suggest_strip_divider"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            />
+        
+        <ImageButton
+            android:id="@+id/candidate_right"
+            android:background="@drawable/ic_suggest_scroll_background"
+            android:src="@drawable/ic_suggest_strip_scroll_right_arrow" 
+            android:layout_width="36dp"
+            android:layout_height="fill_parent"
+            android:clickable="true"
+            />
+    </LinearLayout>        
+    
+</com.android.inputmethod.latin.CandidateViewContainer>
\ No newline at end of file
diff --git a/res/layout/input.xml b/res/layout/input.xml
new file mode 100755
index 0000000..c4bcc91
--- /dev/null
+++ b/res/layout/input.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<com.android.inputmethod.latin.LatinKeyboardView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@android:id/keyboardView"
+        android:layout_alignParentBottom="true"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        />
diff --git a/res/raw/main.dict b/res/raw/main.dict
new file mode 100755
index 0000000..2b473be
--- /dev/null
+++ b/res/raw/main.dict
Binary files differ
diff --git a/res/raw/type3.ogg b/res/raw/type3.ogg
new file mode 100755
index 0000000..20e6708
--- /dev/null
+++ b/res/raw/type3.ogg
Binary files differ
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
new file mode 100644
index 0000000..4080385
--- /dev/null
+++ b/res/values-cs/strings.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (5849054103817924472) -->
+    <skip />
+    <!-- no translation found for english_ime_settings (8408735206364332137) -->
+    <skip />
+    <string name="vibrate_on_keypress">"Při stisku klávesy vibrovat"</string>
+    <string name="sound_on_keypress">"Zvuk při stisku klávesy"</string>
+    <!-- no translation found for hit_correction (4855351009261318389) -->
+    <skip />
+    <!-- no translation found for hit_correction_summary (8761701873008070796) -->
+    <skip />
+    <!-- no translation found for hit_correction_land (2567691684825205448) -->
+    <skip />
+    <!-- no translation found for hit_correction_land_summary (4076803842198368328) -->
+    <skip />
+    <!-- no translation found for auto_correction (7911639788808958255) -->
+    <skip />
+    <!-- no translation found for auto_correction_summary (6881047311475758267) -->
+    <skip />
+    <!-- no translation found for prediction (466220283138359837) -->
+    <skip />
+    <!-- no translation found for prediction_category (7027100625580696660) -->
+    <skip />
+    <!-- no translation found for prediction_summary (459788228830873110) -->
+    <skip />
+    <!-- no translation found for auto_complete_dialog_title (2172048590607201920) -->
+    <skip />
+    <!-- no translation found for prediction_landscape (4874601565593216183) -->
+    <skip />
+    <!-- no translation found for prediction_landscape_summary (6736551095997839472) -->
+    <skip />
+    <!-- no translation found for auto_cap (1719746674854628252) -->
+    <skip />
+    <!-- no translation found for auto_cap_summary (3260681697600786825) -->
+    <skip />
+    <string name="auto_punctuate">"Automatická interpunkce"</string>
+    <!-- no translation found for auto_punctuate_summary (7849876837879793721) -->
+    <skip />
+    <!-- no translation found for prediction_modes:0 (4870266572388153286) -->
+    <!-- no translation found for prediction_modes:1 (1669461741568287396) -->
+    <!-- no translation found for prediction_modes:2 (4894328801530136615) -->
+    <!-- no translation found for prediction_none (2472795101338047944) -->
+    <skip />
+    <!-- no translation found for prediction_basic (8407291081834155558) -->
+    <skip />
+    <!-- no translation found for prediction_full (3765102052052510268) -->
+    <skip />
+    <!-- no translation found for prediction_modes_values:0 (1346378763221728910) -->
+    <!-- no translation found for prediction_modes_values:1 (7980848218230433021) -->
+    <!-- no translation found for prediction_modes_values:2 (7444980361469942622) -->
+    <string name="added_word">"<xliff:g id="WORD">%s</xliff:g> : Uloženo"</string>
+    <string name="alternates_for_a">"áàâãäåæ"</string>
+    <string name="alternates_for_e">"éěèêë"</string>
+    <string name="alternates_for_i">"íìîï"</string>
+    <string name="alternates_for_o">"óòôõöœø"</string>
+    <string name="alternates_for_u">"ůúùûü"</string>
+    <string name="alternates_for_s">"š§ß"</string>
+    <string name="alternates_for_n">"ňñ"</string>
+    <string name="alternates_for_c">"čç"</string>
+    <string name="alternates_for_y">"ýÿ"</string>
+    <!-- no translation found for alternates_for_z (243837378542028049) -->
+    <skip />
+    <!-- no translation found for tip_long_press (6101270866284343344) -->
+    <skip />
+    <!-- no translation found for tip_dismiss (7585579046862204381) -->
+    <skip />
+    <!-- no translation found for tip_access_symbols (6344098517525531652) -->
+    <skip />
+    <!-- no translation found for tip_add_to_dictionary (1487293888469227817) -->
+    <skip />
+    <!-- no translation found for label_go_key (1635148082137219148) -->
+    <skip />
+    <!-- no translation found for label_next_key (362972844525672568) -->
+    <skip />
+    <!-- no translation found for label_send_key (2815056534433717444) -->
+    <skip />
+    <!-- no translation found for label_symbol_key (6175820506864489453) -->
+    <skip />
+    <!-- no translation found for label_phone_key (4275497665515080551) -->
+    <skip />
+    <!-- no translation found for label_alpha_key (8864943487292437456) -->
+    <skip />
+    <!-- no translation found for label_alt_key (2846315350346694811) -->
+    <skip />
+</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
new file mode 100644
index 0000000..4c99208
--- /dev/null
+++ b/res/values-de/strings.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (5849054103817924472) -->
+    <skip />
+    <!-- no translation found for english_ime_settings (8408735206364332137) -->
+    <skip />
+    <string name="vibrate_on_keypress">"Vibrieren auf Tastendruck"</string>
+    <string name="sound_on_keypress">"Sound bei Tastendruck"</string>
+    <!-- no translation found for hit_correction (4855351009261318389) -->
+    <skip />
+    <!-- no translation found for hit_correction_summary (8761701873008070796) -->
+    <skip />
+    <!-- no translation found for hit_correction_land (2567691684825205448) -->
+    <skip />
+    <!-- no translation found for hit_correction_land_summary (4076803842198368328) -->
+    <skip />
+    <!-- no translation found for auto_correction (7911639788808958255) -->
+    <skip />
+    <!-- no translation found for auto_correction_summary (6881047311475758267) -->
+    <skip />
+    <!-- no translation found for prediction (466220283138359837) -->
+    <skip />
+    <!-- no translation found for prediction_category (7027100625580696660) -->
+    <skip />
+    <!-- no translation found for prediction_summary (459788228830873110) -->
+    <skip />
+    <!-- no translation found for auto_complete_dialog_title (2172048590607201920) -->
+    <skip />
+    <!-- no translation found for prediction_landscape (4874601565593216183) -->
+    <skip />
+    <!-- no translation found for prediction_landscape_summary (6736551095997839472) -->
+    <skip />
+    <!-- no translation found for auto_cap (1719746674854628252) -->
+    <skip />
+    <!-- no translation found for auto_cap_summary (3260681697600786825) -->
+    <skip />
+    <string name="auto_punctuate">"Autom. Zeichensetzung"</string>
+    <!-- no translation found for auto_punctuate_summary (7849876837879793721) -->
+    <skip />
+    <!-- no translation found for prediction_modes:0 (4870266572388153286) -->
+    <!-- no translation found for prediction_modes:1 (1669461741568287396) -->
+    <!-- no translation found for prediction_modes:2 (4894328801530136615) -->
+    <!-- no translation found for prediction_none (2472795101338047944) -->
+    <skip />
+    <!-- no translation found for prediction_basic (8407291081834155558) -->
+    <skip />
+    <!-- no translation found for prediction_full (3765102052052510268) -->
+    <skip />
+    <!-- no translation found for prediction_modes_values:0 (1346378763221728910) -->
+    <!-- no translation found for prediction_modes_values:1 (7980848218230433021) -->
+    <!-- no translation found for prediction_modes_values:2 (7444980361469942622) -->
+    <string name="added_word">"<xliff:g id="WORD">%s</xliff:g> : Gespeichert"</string>
+    <string name="alternates_for_a">"àáâãäåæ"</string>
+    <string name="alternates_for_e">"èéêë"</string>
+    <string name="alternates_for_i">"ìíîï"</string>
+    <string name="alternates_for_o">"òóôõöœø"</string>
+    <string name="alternates_for_u">"ùúûü"</string>
+    <string name="alternates_for_s">"§ß"</string>
+    <string name="alternates_for_n">"ñ"</string>
+    <string name="alternates_for_c">"ç"</string>
+    <string name="alternates_for_y">"ýÿ"</string>
+    <!-- no translation found for alternates_for_z (243837378542028049) -->
+    <skip />
+    <!-- no translation found for tip_long_press (6101270866284343344) -->
+    <skip />
+    <!-- no translation found for tip_dismiss (7585579046862204381) -->
+    <skip />
+    <!-- no translation found for tip_access_symbols (6344098517525531652) -->
+    <skip />
+    <!-- no translation found for tip_add_to_dictionary (1487293888469227817) -->
+    <skip />
+    <!-- no translation found for label_go_key (1635148082137219148) -->
+    <skip />
+    <!-- no translation found for label_next_key (362972844525672568) -->
+    <skip />
+    <!-- no translation found for label_send_key (2815056534433717444) -->
+    <skip />
+    <!-- no translation found for label_symbol_key (6175820506864489453) -->
+    <skip />
+    <!-- no translation found for label_phone_key (4275497665515080551) -->
+    <skip />
+    <!-- no translation found for label_alpha_key (8864943487292437456) -->
+    <skip />
+    <!-- no translation found for label_alt_key (2846315350346694811) -->
+    <skip />
+</resources>
diff --git a/res/values-en/bools.xml b/res/values-en/bools.xml
new file mode 100644
index 0000000..897f4b3
--- /dev/null
+++ b/res/values-en/bools.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+<resources>
+	<bool name="im_is_default">true</bool>
+</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
new file mode 100644
index 0000000..08815ee
--- /dev/null
+++ b/res/values-es/strings.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (5849054103817924472) -->
+    <skip />
+    <!-- no translation found for english_ime_settings (8408735206364332137) -->
+    <skip />
+    <string name="vibrate_on_keypress">"Vibrar al pulsar tecla"</string>
+    <string name="sound_on_keypress">"Sonido al pulsar una tecla"</string>
+    <!-- no translation found for hit_correction (4855351009261318389) -->
+    <skip />
+    <!-- no translation found for hit_correction_summary (8761701873008070796) -->
+    <skip />
+    <!-- no translation found for hit_correction_land (2567691684825205448) -->
+    <skip />
+    <!-- no translation found for hit_correction_land_summary (4076803842198368328) -->
+    <skip />
+    <!-- no translation found for auto_correction (7911639788808958255) -->
+    <skip />
+    <!-- no translation found for auto_correction_summary (6881047311475758267) -->
+    <skip />
+    <!-- no translation found for prediction (466220283138359837) -->
+    <skip />
+    <!-- no translation found for prediction_category (7027100625580696660) -->
+    <skip />
+    <!-- no translation found for prediction_summary (459788228830873110) -->
+    <skip />
+    <!-- no translation found for auto_complete_dialog_title (2172048590607201920) -->
+    <skip />
+    <!-- no translation found for prediction_landscape (4874601565593216183) -->
+    <skip />
+    <!-- no translation found for prediction_landscape_summary (6736551095997839472) -->
+    <skip />
+    <!-- no translation found for auto_cap (1719746674854628252) -->
+    <skip />
+    <!-- no translation found for auto_cap_summary (3260681697600786825) -->
+    <skip />
+    <string name="auto_punctuate">"Puntuación automática"</string>
+    <!-- no translation found for auto_punctuate_summary (7849876837879793721) -->
+    <skip />
+    <!-- no translation found for prediction_modes:0 (4870266572388153286) -->
+    <!-- no translation found for prediction_modes:1 (1669461741568287396) -->
+    <!-- no translation found for prediction_modes:2 (4894328801530136615) -->
+    <!-- no translation found for prediction_none (2472795101338047944) -->
+    <skip />
+    <!-- no translation found for prediction_basic (8407291081834155558) -->
+    <skip />
+    <!-- no translation found for prediction_full (3765102052052510268) -->
+    <skip />
+    <!-- no translation found for prediction_modes_values:0 (1346378763221728910) -->
+    <!-- no translation found for prediction_modes_values:1 (7980848218230433021) -->
+    <!-- no translation found for prediction_modes_values:2 (7444980361469942622) -->
+    <string name="added_word">"<xliff:g id="WORD">%s</xliff:g> : Guardada"</string>
+    <string name="alternates_for_a">"àáâãäåæ"</string>
+    <string name="alternates_for_e">"èéêë"</string>
+    <string name="alternates_for_i">"ìíîï"</string>
+    <string name="alternates_for_o">"òóôõöœø"</string>
+    <string name="alternates_for_u">"ùúûü"</string>
+    <string name="alternates_for_s">"§ß"</string>
+    <string name="alternates_for_n">"ñ"</string>
+    <string name="alternates_for_c">"ç"</string>
+    <string name="alternates_for_y">"ýÿ"</string>
+    <!-- no translation found for alternates_for_z (243837378542028049) -->
+    <skip />
+    <!-- no translation found for tip_long_press (6101270866284343344) -->
+    <skip />
+    <!-- no translation found for tip_dismiss (7585579046862204381) -->
+    <skip />
+    <!-- no translation found for tip_access_symbols (6344098517525531652) -->
+    <skip />
+    <!-- no translation found for tip_add_to_dictionary (1487293888469227817) -->
+    <skip />
+    <!-- no translation found for label_go_key (1635148082137219148) -->
+    <skip />
+    <!-- no translation found for label_next_key (362972844525672568) -->
+    <skip />
+    <!-- no translation found for label_send_key (2815056534433717444) -->
+    <skip />
+    <!-- no translation found for label_symbol_key (6175820506864489453) -->
+    <skip />
+    <!-- no translation found for label_phone_key (4275497665515080551) -->
+    <skip />
+    <!-- no translation found for label_alpha_key (8864943487292437456) -->
+    <skip />
+    <!-- no translation found for label_alt_key (2846315350346694811) -->
+    <skip />
+</resources>
diff --git a/res/values-fr/donottranslate.xml b/res/values-fr/donottranslate.xml
new file mode 100644
index 0000000..527f15e
--- /dev/null
+++ b/res/values-fr/donottranslate.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2009, 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Symbols that are commonly considered word separators in this language -->
+    <string name="word_separators">.\u0020,;:!?\'\n()[]*&amp;@{}/&lt;&gt;_+=|\u0022</string>
+    <!-- Symbols that are sentence separators, for purposes of making it hug the last sentence. -->
+    <string name="sentence_separators">.,</string>
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
new file mode 100644
index 0000000..767004d
--- /dev/null
+++ b/res/values-fr/strings.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (5849054103817924472) -->
+    <skip />
+    <!-- no translation found for english_ime_settings (8408735206364332137) -->
+    <skip />
+    <string name="vibrate_on_keypress">"Vibrer à chaque touche enfoncée"</string>
+    <string name="sound_on_keypress">"Son à chaque touche enfoncée"</string>
+    <!-- no translation found for hit_correction (4855351009261318389) -->
+    <skip />
+    <!-- no translation found for hit_correction_summary (8761701873008070796) -->
+    <skip />
+    <!-- no translation found for hit_correction_land (2567691684825205448) -->
+    <skip />
+    <!-- no translation found for hit_correction_land_summary (4076803842198368328) -->
+    <skip />
+    <!-- no translation found for auto_correction (7911639788808958255) -->
+    <skip />
+    <!-- no translation found for auto_correction_summary (6881047311475758267) -->
+    <skip />
+    <!-- no translation found for prediction (466220283138359837) -->
+    <skip />
+    <!-- no translation found for prediction_category (7027100625580696660) -->
+    <skip />
+    <!-- no translation found for prediction_summary (459788228830873110) -->
+    <skip />
+    <!-- no translation found for auto_complete_dialog_title (2172048590607201920) -->
+    <skip />
+    <!-- no translation found for prediction_landscape (4874601565593216183) -->
+    <skip />
+    <!-- no translation found for prediction_landscape_summary (6736551095997839472) -->
+    <skip />
+    <!-- no translation found for auto_cap (1719746674854628252) -->
+    <skip />
+    <!-- no translation found for auto_cap_summary (3260681697600786825) -->
+    <skip />
+    <string name="auto_punctuate">"Ponctuation automatique"</string>
+    <!-- no translation found for auto_punctuate_summary (7849876837879793721) -->
+    <skip />
+    <!-- no translation found for prediction_modes:0 (4870266572388153286) -->
+    <!-- no translation found for prediction_modes:1 (1669461741568287396) -->
+    <!-- no translation found for prediction_modes:2 (4894328801530136615) -->
+    <!-- no translation found for prediction_none (2472795101338047944) -->
+    <skip />
+    <!-- no translation found for prediction_basic (8407291081834155558) -->
+    <skip />
+    <!-- no translation found for prediction_full (3765102052052510268) -->
+    <skip />
+    <!-- no translation found for prediction_modes_values:0 (1346378763221728910) -->
+    <!-- no translation found for prediction_modes_values:1 (7980848218230433021) -->
+    <!-- no translation found for prediction_modes_values:2 (7444980361469942622) -->
+    <string name="added_word">"<xliff:g id="WORD">%s</xliff:g> : enregistré"</string>
+    <string name="alternates_for_a">"àáâãäåæ"</string>
+    <string name="alternates_for_e">"èéêë"</string>
+    <string name="alternates_for_i">"ìíîï"</string>
+    <string name="alternates_for_o">"òóôõöœø"</string>
+    <string name="alternates_for_u">"ùúûü"</string>
+    <string name="alternates_for_s">"§ß"</string>
+    <string name="alternates_for_n">"ñ"</string>
+    <string name="alternates_for_c">"ç"</string>
+    <string name="alternates_for_y">"ýÿ"</string>
+    <!-- no translation found for alternates_for_z (243837378542028049) -->
+    <skip />
+    <!-- no translation found for tip_long_press (6101270866284343344) -->
+    <skip />
+    <!-- no translation found for tip_dismiss (7585579046862204381) -->
+    <skip />
+    <!-- no translation found for tip_access_symbols (6344098517525531652) -->
+    <skip />
+    <!-- no translation found for tip_add_to_dictionary (1487293888469227817) -->
+    <skip />
+    <!-- no translation found for label_go_key (1635148082137219148) -->
+    <skip />
+    <!-- no translation found for label_next_key (362972844525672568) -->
+    <skip />
+    <!-- no translation found for label_send_key (2815056534433717444) -->
+    <skip />
+    <!-- no translation found for label_symbol_key (6175820506864489453) -->
+    <skip />
+    <!-- no translation found for label_phone_key (4275497665515080551) -->
+    <skip />
+    <!-- no translation found for label_alpha_key (8864943487292437456) -->
+    <skip />
+    <!-- no translation found for label_alt_key (2846315350346694811) -->
+    <skip />
+</resources>
diff --git a/res/values-it/donottranslate.xml b/res/values-it/donottranslate.xml
new file mode 100644
index 0000000..5cb72ad
--- /dev/null
+++ b/res/values-it/donottranslate.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2009, 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Symbols that are commonly considered word separators in this language -->
+    <string name="word_separators">.\u0020,;:!?\'\n()[]*&amp;@{}/&lt;&gt;_+=|\u0022</string>
+</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
new file mode 100644
index 0000000..9fd770a
--- /dev/null
+++ b/res/values-it/strings.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (5849054103817924472) -->
+    <skip />
+    <!-- no translation found for english_ime_settings (8408735206364332137) -->
+    <skip />
+    <string name="vibrate_on_keypress">"Vibra quando premi un tasto"</string>
+    <string name="sound_on_keypress">"Suona quando premi un tasto"</string>
+    <!-- no translation found for hit_correction (4855351009261318389) -->
+    <skip />
+    <!-- no translation found for hit_correction_summary (8761701873008070796) -->
+    <skip />
+    <!-- no translation found for hit_correction_land (2567691684825205448) -->
+    <skip />
+    <!-- no translation found for hit_correction_land_summary (4076803842198368328) -->
+    <skip />
+    <!-- no translation found for auto_correction (7911639788808958255) -->
+    <skip />
+    <!-- no translation found for auto_correction_summary (6881047311475758267) -->
+    <skip />
+    <!-- no translation found for prediction (466220283138359837) -->
+    <skip />
+    <!-- no translation found for prediction_category (7027100625580696660) -->
+    <skip />
+    <!-- no translation found for prediction_summary (459788228830873110) -->
+    <skip />
+    <!-- no translation found for auto_complete_dialog_title (2172048590607201920) -->
+    <skip />
+    <!-- no translation found for prediction_landscape (4874601565593216183) -->
+    <skip />
+    <!-- no translation found for prediction_landscape_summary (6736551095997839472) -->
+    <skip />
+    <!-- no translation found for auto_cap (1719746674854628252) -->
+    <skip />
+    <!-- no translation found for auto_cap_summary (3260681697600786825) -->
+    <skip />
+    <string name="auto_punctuate">"Punteggiatura automatica"</string>
+    <!-- no translation found for auto_punctuate_summary (7849876837879793721) -->
+    <skip />
+    <!-- no translation found for prediction_modes:0 (4870266572388153286) -->
+    <!-- no translation found for prediction_modes:1 (1669461741568287396) -->
+    <!-- no translation found for prediction_modes:2 (4894328801530136615) -->
+    <!-- no translation found for prediction_none (2472795101338047944) -->
+    <skip />
+    <!-- no translation found for prediction_basic (8407291081834155558) -->
+    <skip />
+    <!-- no translation found for prediction_full (3765102052052510268) -->
+    <skip />
+    <!-- no translation found for prediction_modes_values:0 (1346378763221728910) -->
+    <!-- no translation found for prediction_modes_values:1 (7980848218230433021) -->
+    <!-- no translation found for prediction_modes_values:2 (7444980361469942622) -->
+    <string name="added_word">"<xliff:g id="WORD">%s</xliff:g> : parola salvata"</string>
+    <string name="alternates_for_a">"àáâãäåæ"</string>
+    <string name="alternates_for_e">"èéêë"</string>
+    <string name="alternates_for_i">"ìíîï"</string>
+    <string name="alternates_for_o">"òóôõöœø"</string>
+    <string name="alternates_for_u">"ùúûü"</string>
+    <string name="alternates_for_s">"§ß"</string>
+    <string name="alternates_for_n">"ñ"</string>
+    <string name="alternates_for_c">"ç"</string>
+    <string name="alternates_for_y">"ýÿ"</string>
+    <!-- no translation found for alternates_for_z (243837378542028049) -->
+    <skip />
+    <!-- no translation found for tip_long_press (6101270866284343344) -->
+    <skip />
+    <!-- no translation found for tip_dismiss (7585579046862204381) -->
+    <skip />
+    <!-- no translation found for tip_access_symbols (6344098517525531652) -->
+    <skip />
+    <!-- no translation found for tip_add_to_dictionary (1487293888469227817) -->
+    <skip />
+    <!-- no translation found for label_go_key (1635148082137219148) -->
+    <skip />
+    <!-- no translation found for label_next_key (362972844525672568) -->
+    <skip />
+    <!-- no translation found for label_send_key (2815056534433717444) -->
+    <skip />
+    <!-- no translation found for label_symbol_key (6175820506864489453) -->
+    <skip />
+    <!-- no translation found for label_phone_key (4275497665515080551) -->
+    <skip />
+    <!-- no translation found for label_alpha_key (8864943487292437456) -->
+    <skip />
+    <!-- no translation found for label_alt_key (2846315350346694811) -->
+    <skip />
+</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
new file mode 100644
index 0000000..8a6c76f
--- /dev/null
+++ b/res/values-ja/strings.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (5849054103817924472) -->
+    <skip />
+    <!-- no translation found for english_ime_settings (8408735206364332137) -->
+    <skip />
+    <string name="vibrate_on_keypress">"キーのバイブレーション"</string>
+    <string name="sound_on_keypress">"キーを押したときの音"</string>
+    <!-- no translation found for hit_correction (4855351009261318389) -->
+    <skip />
+    <!-- no translation found for hit_correction_summary (8761701873008070796) -->
+    <skip />
+    <!-- no translation found for hit_correction_land (2567691684825205448) -->
+    <skip />
+    <!-- no translation found for hit_correction_land_summary (4076803842198368328) -->
+    <skip />
+    <!-- no translation found for auto_correction (7911639788808958255) -->
+    <skip />
+    <!-- no translation found for auto_correction_summary (6881047311475758267) -->
+    <skip />
+    <!-- no translation found for prediction (466220283138359837) -->
+    <skip />
+    <!-- no translation found for prediction_category (7027100625580696660) -->
+    <skip />
+    <!-- no translation found for prediction_summary (459788228830873110) -->
+    <skip />
+    <!-- no translation found for auto_complete_dialog_title (2172048590607201920) -->
+    <skip />
+    <!-- no translation found for prediction_landscape (4874601565593216183) -->
+    <skip />
+    <!-- no translation found for prediction_landscape_summary (6736551095997839472) -->
+    <skip />
+    <!-- no translation found for auto_cap (1719746674854628252) -->
+    <skip />
+    <!-- no translation found for auto_cap_summary (3260681697600786825) -->
+    <skip />
+    <string name="auto_punctuate">"句読点を自動入力"</string>
+    <!-- no translation found for auto_punctuate_summary (7849876837879793721) -->
+    <skip />
+    <!-- no translation found for prediction_modes:0 (4870266572388153286) -->
+    <!-- no translation found for prediction_modes:1 (1669461741568287396) -->
+    <!-- no translation found for prediction_modes:2 (4894328801530136615) -->
+    <!-- no translation found for prediction_none (2472795101338047944) -->
+    <skip />
+    <!-- no translation found for prediction_basic (8407291081834155558) -->
+    <skip />
+    <!-- no translation found for prediction_full (3765102052052510268) -->
+    <skip />
+    <!-- no translation found for prediction_modes_values:0 (1346378763221728910) -->
+    <!-- no translation found for prediction_modes_values:1 (7980848218230433021) -->
+    <!-- no translation found for prediction_modes_values:2 (7444980361469942622) -->
+    <string name="added_word">"<xliff:g id="WORD">%s</xliff:g>:保存しました"</string>
+    <string name="alternates_for_a">"àáâãäåæ"</string>
+    <string name="alternates_for_e">"èéêë"</string>
+    <string name="alternates_for_i">"ìíîï"</string>
+    <string name="alternates_for_o">"òóôõöœø"</string>
+    <string name="alternates_for_u">"ùúûü"</string>
+    <string name="alternates_for_s">"§ß"</string>
+    <string name="alternates_for_n">"ñ"</string>
+    <string name="alternates_for_c">"ç"</string>
+    <string name="alternates_for_y">"ýÿ"</string>
+    <!-- no translation found for alternates_for_z (243837378542028049) -->
+    <skip />
+    <!-- no translation found for tip_long_press (6101270866284343344) -->
+    <skip />
+    <!-- no translation found for tip_dismiss (7585579046862204381) -->
+    <skip />
+    <!-- no translation found for tip_access_symbols (6344098517525531652) -->
+    <skip />
+    <!-- no translation found for tip_add_to_dictionary (1487293888469227817) -->
+    <skip />
+    <!-- no translation found for label_go_key (1635148082137219148) -->
+    <skip />
+    <!-- no translation found for label_next_key (362972844525672568) -->
+    <skip />
+    <!-- no translation found for label_send_key (2815056534433717444) -->
+    <skip />
+    <!-- no translation found for label_symbol_key (6175820506864489453) -->
+    <skip />
+    <!-- no translation found for label_phone_key (4275497665515080551) -->
+    <skip />
+    <!-- no translation found for label_alpha_key (8864943487292437456) -->
+    <skip />
+    <!-- no translation found for label_alt_key (2846315350346694811) -->
+    <skip />
+</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
new file mode 100644
index 0000000..96d9295
--- /dev/null
+++ b/res/values-ko/strings.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (5849054103817924472) -->
+    <skip />
+    <!-- no translation found for english_ime_settings (8408735206364332137) -->
+    <skip />
+    <string name="vibrate_on_keypress">"키를 누를 때 진동 발생"</string>
+    <string name="sound_on_keypress">"버튼을 누를 때 소리 발생"</string>
+    <!-- no translation found for hit_correction (4855351009261318389) -->
+    <skip />
+    <!-- no translation found for hit_correction_summary (8761701873008070796) -->
+    <skip />
+    <!-- no translation found for hit_correction_land (2567691684825205448) -->
+    <skip />
+    <!-- no translation found for hit_correction_land_summary (4076803842198368328) -->
+    <skip />
+    <!-- no translation found for auto_correction (7911639788808958255) -->
+    <skip />
+    <!-- no translation found for auto_correction_summary (6881047311475758267) -->
+    <skip />
+    <!-- no translation found for prediction (466220283138359837) -->
+    <skip />
+    <!-- no translation found for prediction_category (7027100625580696660) -->
+    <skip />
+    <!-- no translation found for prediction_summary (459788228830873110) -->
+    <skip />
+    <!-- no translation found for auto_complete_dialog_title (2172048590607201920) -->
+    <skip />
+    <!-- no translation found for prediction_landscape (4874601565593216183) -->
+    <skip />
+    <!-- no translation found for prediction_landscape_summary (6736551095997839472) -->
+    <skip />
+    <!-- no translation found for auto_cap (1719746674854628252) -->
+    <skip />
+    <!-- no translation found for auto_cap_summary (3260681697600786825) -->
+    <skip />
+    <string name="auto_punctuate">"자동 구두점 입력"</string>
+    <!-- no translation found for auto_punctuate_summary (7849876837879793721) -->
+    <skip />
+    <!-- no translation found for prediction_modes:0 (4870266572388153286) -->
+    <!-- no translation found for prediction_modes:1 (1669461741568287396) -->
+    <!-- no translation found for prediction_modes:2 (4894328801530136615) -->
+    <!-- no translation found for prediction_none (2472795101338047944) -->
+    <skip />
+    <!-- no translation found for prediction_basic (8407291081834155558) -->
+    <skip />
+    <!-- no translation found for prediction_full (3765102052052510268) -->
+    <skip />
+    <!-- no translation found for prediction_modes_values:0 (1346378763221728910) -->
+    <!-- no translation found for prediction_modes_values:1 (7980848218230433021) -->
+    <!-- no translation found for prediction_modes_values:2 (7444980361469942622) -->
+    <string name="added_word">"<xliff:g id="WORD">%s</xliff:g> : 저장됨"</string>
+    <string name="alternates_for_a">"àáâãäåæ"</string>
+    <string name="alternates_for_e">"èéêë"</string>
+    <string name="alternates_for_i">"ìíîï"</string>
+    <string name="alternates_for_o">"òóôõöœø"</string>
+    <string name="alternates_for_u">"ùúûü"</string>
+    <string name="alternates_for_s">"§ß"</string>
+    <string name="alternates_for_n">"ñ"</string>
+    <string name="alternates_for_c">"ç"</string>
+    <string name="alternates_for_y">"ýÿ"</string>
+    <!-- no translation found for alternates_for_z (243837378542028049) -->
+    <skip />
+    <!-- no translation found for tip_long_press (6101270866284343344) -->
+    <skip />
+    <!-- no translation found for tip_dismiss (7585579046862204381) -->
+    <skip />
+    <!-- no translation found for tip_access_symbols (6344098517525531652) -->
+    <skip />
+    <!-- no translation found for tip_add_to_dictionary (1487293888469227817) -->
+    <skip />
+    <!-- no translation found for label_go_key (1635148082137219148) -->
+    <skip />
+    <!-- no translation found for label_next_key (362972844525672568) -->
+    <skip />
+    <!-- no translation found for label_send_key (2815056534433717444) -->
+    <skip />
+    <!-- no translation found for label_symbol_key (6175820506864489453) -->
+    <skip />
+    <!-- no translation found for label_phone_key (4275497665515080551) -->
+    <skip />
+    <!-- no translation found for label_alpha_key (8864943487292437456) -->
+    <skip />
+    <!-- no translation found for label_alt_key (2846315350346694811) -->
+    <skip />
+</resources>
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
new file mode 100644
index 0000000..c5c828e
--- /dev/null
+++ b/res/values-land/dimens.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<resources>
+    <dimen name="key_height">47dip</dimen>
+</resources>
\ No newline at end of file
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
new file mode 100644
index 0000000..a010318
--- /dev/null
+++ b/res/values-nb/strings.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (5849054103817924472) -->
+    <skip />
+    <!-- no translation found for english_ime_settings (8408735206364332137) -->
+    <skip />
+    <string name="vibrate_on_keypress">"Vibrer ved tastetrykk"</string>
+    <string name="sound_on_keypress">"Lyd ved tastetrykk"</string>
+    <!-- no translation found for hit_correction (4855351009261318389) -->
+    <skip />
+    <!-- no translation found for hit_correction_summary (8761701873008070796) -->
+    <skip />
+    <!-- no translation found for hit_correction_land (2567691684825205448) -->
+    <skip />
+    <!-- no translation found for hit_correction_land_summary (4076803842198368328) -->
+    <skip />
+    <!-- no translation found for auto_correction (7911639788808958255) -->
+    <skip />
+    <string name="auto_correction_summary">"Autokorriger forrige ord ved mellomrom eller linjeskift"</string>
+    <!-- no translation found for prediction (466220283138359837) -->
+    <skip />
+    <!-- no translation found for prediction_category (7027100625580696660) -->
+    <skip />
+    <!-- no translation found for prediction_summary (459788228830873110) -->
+    <skip />
+    <!-- no translation found for auto_complete_dialog_title (2172048590607201920) -->
+    <skip />
+    <!-- no translation found for prediction_landscape (4874601565593216183) -->
+    <skip />
+    <!-- no translation found for prediction_landscape_summary (6736551095997839472) -->
+    <skip />
+    <string name="auto_cap">"Stor forbokstav"</string>
+    <string name="auto_cap_summary">"Start automatisk setninger med stor bokstav"</string>
+    <string name="auto_punctuate">"Automatisk punktum"</string>
+    <!-- no translation found for auto_punctuate_summary (7849876837879793721) -->
+    <skip />
+    <!-- no translation found for prediction_modes:0 (4870266572388153286) -->
+    <!-- no translation found for prediction_modes:1 (1669461741568287396) -->
+    <!-- no translation found for prediction_modes:2 (4894328801530136615) -->
+    <!-- no translation found for prediction_none (2472795101338047944) -->
+    <skip />
+    <!-- no translation found for prediction_basic (8407291081834155558) -->
+    <skip />
+    <!-- no translation found for prediction_full (3765102052052510268) -->
+    <skip />
+    <!-- no translation found for prediction_modes_values:0 (1346378763221728910) -->
+    <!-- no translation found for prediction_modes_values:1 (7980848218230433021) -->
+    <!-- no translation found for prediction_modes_values:2 (7444980361469942622) -->
+    <string name="added_word">"<xliff:g id="WORD">%s</xliff:g>: Lagret"</string>
+    <string name="alternates_for_a">"åæáàâãä"</string>
+    <string name="alternates_for_e">"éèêë"</string>
+    <string name="alternates_for_i">"íìîï"</string>
+    <string name="alternates_for_o">"ôóòöõœø"</string>
+    <string name="alternates_for_u">"üùúû"</string>
+    <string name="alternates_for_s">"ß§"</string>
+    <string name="alternates_for_n">"ñ"</string>
+    <string name="alternates_for_c">"ç"</string>
+    <string name="alternates_for_y">"ýÿ"</string>
+    <!-- no translation found for alternates_for_z (243837378542028049) -->
+    <skip />
+    <!-- no translation found for tip_long_press (6101270866284343344) -->
+    <skip />
+    <!-- no translation found for tip_dismiss (7585579046862204381) -->
+    <skip />
+    <!-- no translation found for tip_access_symbols (6344098517525531652) -->
+    <skip />
+    <string name="tip_add_to_dictionary">"Trykk lenge på ordet lengst til venstre for å legge det til i ordlisten"</string>
+    <string name="label_go_key">"Gå"</string>
+    <!-- no translation found for label_next_key (362972844525672568) -->
+    <skip />
+    <!-- no translation found for label_send_key (2815056534433717444) -->
+    <skip />
+    <string name="label_symbol_key">"?123"</string>
+    <string name="label_phone_key">"123"</string>
+    <string name="label_alpha_key">"ABC"</string>
+    <string name="label_alt_key">"ALT"</string>
+</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
new file mode 100644
index 0000000..b172def
--- /dev/null
+++ b/res/values-nl/strings.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (5849054103817924472) -->
+    <skip />
+    <!-- no translation found for english_ime_settings (8408735206364332137) -->
+    <skip />
+    <string name="vibrate_on_keypress">"Trillen bij druk op een toets"</string>
+    <string name="sound_on_keypress">"Geluid bij druk op een toets"</string>
+    <!-- no translation found for hit_correction (4855351009261318389) -->
+    <skip />
+    <!-- no translation found for hit_correction_summary (8761701873008070796) -->
+    <skip />
+    <!-- no translation found for hit_correction_land (2567691684825205448) -->
+    <skip />
+    <!-- no translation found for hit_correction_land_summary (4076803842198368328) -->
+    <skip />
+    <!-- no translation found for auto_correction (7911639788808958255) -->
+    <skip />
+    <!-- no translation found for auto_correction_summary (6881047311475758267) -->
+    <skip />
+    <!-- no translation found for prediction (466220283138359837) -->
+    <skip />
+    <!-- no translation found for prediction_category (7027100625580696660) -->
+    <skip />
+    <!-- no translation found for prediction_summary (459788228830873110) -->
+    <skip />
+    <!-- no translation found for auto_complete_dialog_title (2172048590607201920) -->
+    <skip />
+    <!-- no translation found for prediction_landscape (4874601565593216183) -->
+    <skip />
+    <!-- no translation found for prediction_landscape_summary (6736551095997839472) -->
+    <skip />
+    <!-- no translation found for auto_cap (1719746674854628252) -->
+    <skip />
+    <!-- no translation found for auto_cap_summary (3260681697600786825) -->
+    <skip />
+    <string name="auto_punctuate">"Automatische interpunctie"</string>
+    <!-- no translation found for auto_punctuate_summary (7849876837879793721) -->
+    <skip />
+    <!-- no translation found for prediction_modes:0 (4870266572388153286) -->
+    <!-- no translation found for prediction_modes:1 (1669461741568287396) -->
+    <!-- no translation found for prediction_modes:2 (4894328801530136615) -->
+    <!-- no translation found for prediction_none (2472795101338047944) -->
+    <skip />
+    <!-- no translation found for prediction_basic (8407291081834155558) -->
+    <skip />
+    <!-- no translation found for prediction_full (3765102052052510268) -->
+    <skip />
+    <!-- no translation found for prediction_modes_values:0 (1346378763221728910) -->
+    <!-- no translation found for prediction_modes_values:1 (7980848218230433021) -->
+    <!-- no translation found for prediction_modes_values:2 (7444980361469942622) -->
+    <string name="added_word">"<xliff:g id="WORD">%s</xliff:g> : Opgeslagen"</string>
+    <string name="alternates_for_a">"àáâãäåæ"</string>
+    <string name="alternates_for_e">"èéêë"</string>
+    <string name="alternates_for_i">"ìíîï"</string>
+    <string name="alternates_for_o">"òóôõöœø"</string>
+    <string name="alternates_for_u">"ùúûü"</string>
+    <string name="alternates_for_s">"§ß"</string>
+    <string name="alternates_for_n">"ñ"</string>
+    <string name="alternates_for_c">"ç"</string>
+    <string name="alternates_for_y">"ýÿ"</string>
+    <!-- no translation found for alternates_for_z (243837378542028049) -->
+    <skip />
+    <!-- no translation found for tip_long_press (6101270866284343344) -->
+    <skip />
+    <!-- no translation found for tip_dismiss (7585579046862204381) -->
+    <skip />
+    <!-- no translation found for tip_access_symbols (6344098517525531652) -->
+    <skip />
+    <!-- no translation found for tip_add_to_dictionary (1487293888469227817) -->
+    <skip />
+    <!-- no translation found for label_go_key (1635148082137219148) -->
+    <skip />
+    <!-- no translation found for label_next_key (362972844525672568) -->
+    <skip />
+    <!-- no translation found for label_send_key (2815056534433717444) -->
+    <skip />
+    <!-- no translation found for label_symbol_key (6175820506864489453) -->
+    <skip />
+    <!-- no translation found for label_phone_key (4275497665515080551) -->
+    <skip />
+    <!-- no translation found for label_alpha_key (8864943487292437456) -->
+    <skip />
+    <!-- no translation found for label_alt_key (2846315350346694811) -->
+    <skip />
+</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
new file mode 100644
index 0000000..eb1a128
--- /dev/null
+++ b/res/values-pl/strings.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (5849054103817924472) -->
+    <skip />
+    <!-- no translation found for english_ime_settings (8408735206364332137) -->
+    <skip />
+    <string name="vibrate_on_keypress">"Wibruj przy naciśnięciu klawisza"</string>
+    <string name="sound_on_keypress">"Dźwięk przy naciśnięciu klawisza"</string>
+    <!-- no translation found for hit_correction (4855351009261318389) -->
+    <skip />
+    <!-- no translation found for hit_correction_summary (8761701873008070796) -->
+    <skip />
+    <!-- no translation found for hit_correction_land (2567691684825205448) -->
+    <skip />
+    <!-- no translation found for hit_correction_land_summary (4076803842198368328) -->
+    <skip />
+    <!-- no translation found for auto_correction (7911639788808958255) -->
+    <skip />
+    <!-- no translation found for auto_correction_summary (6881047311475758267) -->
+    <skip />
+    <!-- no translation found for prediction (466220283138359837) -->
+    <skip />
+    <!-- no translation found for prediction_category (7027100625580696660) -->
+    <skip />
+    <!-- no translation found for prediction_summary (459788228830873110) -->
+    <skip />
+    <!-- no translation found for auto_complete_dialog_title (2172048590607201920) -->
+    <skip />
+    <!-- no translation found for prediction_landscape (4874601565593216183) -->
+    <skip />
+    <!-- no translation found for prediction_landscape_summary (6736551095997839472) -->
+    <skip />
+    <!-- no translation found for auto_cap (1719746674854628252) -->
+    <skip />
+    <!-- no translation found for auto_cap_summary (3260681697600786825) -->
+    <skip />
+    <string name="auto_punctuate">"Automatyczna interpunkcja"</string>
+    <!-- no translation found for auto_punctuate_summary (7849876837879793721) -->
+    <skip />
+    <!-- no translation found for prediction_modes:0 (4870266572388153286) -->
+    <!-- no translation found for prediction_modes:1 (1669461741568287396) -->
+    <!-- no translation found for prediction_modes:2 (4894328801530136615) -->
+    <!-- no translation found for prediction_none (2472795101338047944) -->
+    <skip />
+    <!-- no translation found for prediction_basic (8407291081834155558) -->
+    <skip />
+    <!-- no translation found for prediction_full (3765102052052510268) -->
+    <skip />
+    <!-- no translation found for prediction_modes_values:0 (1346378763221728910) -->
+    <!-- no translation found for prediction_modes_values:1 (7980848218230433021) -->
+    <!-- no translation found for prediction_modes_values:2 (7444980361469942622) -->
+    <string name="added_word">"<xliff:g id="WORD">%s</xliff:g> : Zapisano"</string>
+    <string name="alternates_for_a">"àáâãäåæ"</string>
+    <string name="alternates_for_e">"èéêë"</string>
+    <string name="alternates_for_i">"ìíîï"</string>
+    <string name="alternates_for_o">"òóôõöœø"</string>
+    <string name="alternates_for_u">"ùúûü"</string>
+    <string name="alternates_for_s">"§ß"</string>
+    <string name="alternates_for_n">"ñ"</string>
+    <string name="alternates_for_c">"ç"</string>
+    <string name="alternates_for_y">"ýÿ"</string>
+    <!-- no translation found for alternates_for_z (243837378542028049) -->
+    <skip />
+    <!-- no translation found for tip_long_press (6101270866284343344) -->
+    <skip />
+    <!-- no translation found for tip_dismiss (7585579046862204381) -->
+    <skip />
+    <!-- no translation found for tip_access_symbols (6344098517525531652) -->
+    <skip />
+    <!-- no translation found for tip_add_to_dictionary (1487293888469227817) -->
+    <skip />
+    <!-- no translation found for label_go_key (1635148082137219148) -->
+    <skip />
+    <!-- no translation found for label_next_key (362972844525672568) -->
+    <skip />
+    <!-- no translation found for label_send_key (2815056534433717444) -->
+    <skip />
+    <!-- no translation found for label_symbol_key (6175820506864489453) -->
+    <skip />
+    <!-- no translation found for label_phone_key (4275497665515080551) -->
+    <skip />
+    <!-- no translation found for label_alpha_key (8864943487292437456) -->
+    <skip />
+    <!-- no translation found for label_alt_key (2846315350346694811) -->
+    <skip />
+</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
new file mode 100644
index 0000000..7ca302b
--- /dev/null
+++ b/res/values-ru/strings.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (5849054103817924472) -->
+    <skip />
+    <!-- no translation found for english_ime_settings (8408735206364332137) -->
+    <skip />
+    <string name="vibrate_on_keypress">"Вибрация при нажатии клавиш"</string>
+    <string name="sound_on_keypress">"Звук при нажатии клавиш"</string>
+    <!-- no translation found for hit_correction (4855351009261318389) -->
+    <skip />
+    <!-- no translation found for hit_correction_summary (8761701873008070796) -->
+    <skip />
+    <!-- no translation found for hit_correction_land (2567691684825205448) -->
+    <skip />
+    <!-- no translation found for hit_correction_land_summary (4076803842198368328) -->
+    <skip />
+    <!-- no translation found for auto_correction (7911639788808958255) -->
+    <skip />
+    <!-- no translation found for auto_correction_summary (6881047311475758267) -->
+    <skip />
+    <!-- no translation found for prediction (466220283138359837) -->
+    <skip />
+    <!-- no translation found for prediction_category (7027100625580696660) -->
+    <skip />
+    <!-- no translation found for prediction_summary (459788228830873110) -->
+    <skip />
+    <!-- no translation found for auto_complete_dialog_title (2172048590607201920) -->
+    <skip />
+    <!-- no translation found for prediction_landscape (4874601565593216183) -->
+    <skip />
+    <!-- no translation found for prediction_landscape_summary (6736551095997839472) -->
+    <skip />
+    <!-- no translation found for auto_cap (1719746674854628252) -->
+    <skip />
+    <!-- no translation found for auto_cap_summary (3260681697600786825) -->
+    <skip />
+    <string name="auto_punctuate">"Автоматическая пунктуация"</string>
+    <!-- no translation found for auto_punctuate_summary (7849876837879793721) -->
+    <skip />
+    <!-- no translation found for prediction_modes:0 (4870266572388153286) -->
+    <!-- no translation found for prediction_modes:1 (1669461741568287396) -->
+    <!-- no translation found for prediction_modes:2 (4894328801530136615) -->
+    <!-- no translation found for prediction_none (2472795101338047944) -->
+    <skip />
+    <!-- no translation found for prediction_basic (8407291081834155558) -->
+    <skip />
+    <!-- no translation found for prediction_full (3765102052052510268) -->
+    <skip />
+    <!-- no translation found for prediction_modes_values:0 (1346378763221728910) -->
+    <!-- no translation found for prediction_modes_values:1 (7980848218230433021) -->
+    <!-- no translation found for prediction_modes_values:2 (7444980361469942622) -->
+    <string name="added_word">"<xliff:g id="WORD">%s</xliff:g> : сохранено"</string>
+    <string name="alternates_for_a">"àáâãäåæ"</string>
+    <string name="alternates_for_e">"èéêë"</string>
+    <string name="alternates_for_i">"ìíîï"</string>
+    <string name="alternates_for_o">"òóôõöœø"</string>
+    <string name="alternates_for_u">"ùúûü"</string>
+    <string name="alternates_for_s">"§ß"</string>
+    <string name="alternates_for_n">"ñ"</string>
+    <string name="alternates_for_c">"ç"</string>
+    <string name="alternates_for_y">"ýÿ"</string>
+    <!-- no translation found for alternates_for_z (243837378542028049) -->
+    <skip />
+    <!-- no translation found for tip_long_press (6101270866284343344) -->
+    <skip />
+    <!-- no translation found for tip_dismiss (7585579046862204381) -->
+    <skip />
+    <!-- no translation found for tip_access_symbols (6344098517525531652) -->
+    <skip />
+    <!-- no translation found for tip_add_to_dictionary (1487293888469227817) -->
+    <skip />
+    <!-- no translation found for label_go_key (1635148082137219148) -->
+    <skip />
+    <!-- no translation found for label_next_key (362972844525672568) -->
+    <skip />
+    <!-- no translation found for label_send_key (2815056534433717444) -->
+    <skip />
+    <!-- no translation found for label_symbol_key (6175820506864489453) -->
+    <skip />
+    <!-- no translation found for label_phone_key (4275497665515080551) -->
+    <skip />
+    <!-- no translation found for label_alpha_key (8864943487292437456) -->
+    <skip />
+    <!-- no translation found for label_alt_key (2846315350346694811) -->
+    <skip />
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..3525a48
--- /dev/null
+++ b/res/values-zh-rCN/strings.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (5849054103817924472) -->
+    <skip />
+    <!-- no translation found for english_ime_settings (8408735206364332137) -->
+    <skip />
+    <string name="vibrate_on_keypress">"按键时振动"</string>
+    <string name="sound_on_keypress">"按键时发出声音"</string>
+    <!-- no translation found for hit_correction (4855351009261318389) -->
+    <skip />
+    <!-- no translation found for hit_correction_summary (8761701873008070796) -->
+    <skip />
+    <!-- no translation found for hit_correction_land (2567691684825205448) -->
+    <skip />
+    <!-- no translation found for hit_correction_land_summary (4076803842198368328) -->
+    <skip />
+    <!-- no translation found for auto_correction (7911639788808958255) -->
+    <skip />
+    <!-- no translation found for auto_correction_summary (6881047311475758267) -->
+    <skip />
+    <!-- no translation found for prediction (466220283138359837) -->
+    <skip />
+    <!-- no translation found for prediction_category (7027100625580696660) -->
+    <skip />
+    <!-- no translation found for prediction_summary (459788228830873110) -->
+    <skip />
+    <!-- no translation found for auto_complete_dialog_title (2172048590607201920) -->
+    <skip />
+    <!-- no translation found for prediction_landscape (4874601565593216183) -->
+    <skip />
+    <!-- no translation found for prediction_landscape_summary (6736551095997839472) -->
+    <skip />
+    <!-- no translation found for auto_cap (1719746674854628252) -->
+    <skip />
+    <!-- no translation found for auto_cap_summary (3260681697600786825) -->
+    <skip />
+    <string name="auto_punctuate">"自动加标点"</string>
+    <!-- no translation found for auto_punctuate_summary (7849876837879793721) -->
+    <skip />
+    <!-- no translation found for prediction_modes:0 (4870266572388153286) -->
+    <!-- no translation found for prediction_modes:1 (1669461741568287396) -->
+    <!-- no translation found for prediction_modes:2 (4894328801530136615) -->
+    <!-- no translation found for prediction_none (2472795101338047944) -->
+    <skip />
+    <!-- no translation found for prediction_basic (8407291081834155558) -->
+    <skip />
+    <!-- no translation found for prediction_full (3765102052052510268) -->
+    <skip />
+    <!-- no translation found for prediction_modes_values:0 (1346378763221728910) -->
+    <!-- no translation found for prediction_modes_values:1 (7980848218230433021) -->
+    <!-- no translation found for prediction_modes_values:2 (7444980361469942622) -->
+    <string name="added_word">"<xliff:g id="WORD">%s</xliff:g>:已保存"</string>
+    <string name="alternates_for_a">"àáâãäåæ"</string>
+    <string name="alternates_for_e">"èéêë"</string>
+    <string name="alternates_for_i">"ìíîï"</string>
+    <string name="alternates_for_o">"òóôõöœø"</string>
+    <string name="alternates_for_u">"ùúûü"</string>
+    <string name="alternates_for_s">"§ß"</string>
+    <string name="alternates_for_n">"ñ"</string>
+    <string name="alternates_for_c">"ç"</string>
+    <string name="alternates_for_y">"ýÿ"</string>
+    <!-- no translation found for alternates_for_z (243837378542028049) -->
+    <skip />
+    <!-- no translation found for tip_long_press (6101270866284343344) -->
+    <skip />
+    <!-- no translation found for tip_dismiss (7585579046862204381) -->
+    <skip />
+    <!-- no translation found for tip_access_symbols (6344098517525531652) -->
+    <skip />
+    <!-- no translation found for tip_add_to_dictionary (1487293888469227817) -->
+    <skip />
+    <!-- no translation found for label_go_key (1635148082137219148) -->
+    <skip />
+    <!-- no translation found for label_next_key (362972844525672568) -->
+    <skip />
+    <!-- no translation found for label_send_key (2815056534433717444) -->
+    <skip />
+    <!-- no translation found for label_symbol_key (6175820506864489453) -->
+    <skip />
+    <!-- no translation found for label_phone_key (4275497665515080551) -->
+    <skip />
+    <!-- no translation found for label_alpha_key (8864943487292437456) -->
+    <skip />
+    <!-- no translation found for label_alt_key (2846315350346694811) -->
+    <skip />
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..f880fd7
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (5849054103817924472) -->
+    <skip />
+    <!-- no translation found for english_ime_settings (8408735206364332137) -->
+    <skip />
+    <string name="vibrate_on_keypress">"按下按鍵時震動"</string>
+    <string name="sound_on_keypress">"按下按鍵時播放音效"</string>
+    <!-- no translation found for hit_correction (4855351009261318389) -->
+    <skip />
+    <!-- no translation found for hit_correction_summary (8761701873008070796) -->
+    <skip />
+    <!-- no translation found for hit_correction_land (2567691684825205448) -->
+    <skip />
+    <!-- no translation found for hit_correction_land_summary (4076803842198368328) -->
+    <skip />
+    <!-- no translation found for auto_correction (7911639788808958255) -->
+    <skip />
+    <!-- no translation found for auto_correction_summary (6881047311475758267) -->
+    <skip />
+    <!-- no translation found for prediction (466220283138359837) -->
+    <skip />
+    <!-- no translation found for prediction_category (7027100625580696660) -->
+    <skip />
+    <!-- no translation found for prediction_summary (459788228830873110) -->
+    <skip />
+    <!-- no translation found for auto_complete_dialog_title (2172048590607201920) -->
+    <skip />
+    <!-- no translation found for prediction_landscape (4874601565593216183) -->
+    <skip />
+    <!-- no translation found for prediction_landscape_summary (6736551095997839472) -->
+    <skip />
+    <!-- no translation found for auto_cap (1719746674854628252) -->
+    <skip />
+    <!-- no translation found for auto_cap_summary (3260681697600786825) -->
+    <skip />
+    <string name="auto_punctuate">"自動標點"</string>
+    <!-- no translation found for auto_punctuate_summary (7849876837879793721) -->
+    <skip />
+    <!-- no translation found for prediction_modes:0 (4870266572388153286) -->
+    <!-- no translation found for prediction_modes:1 (1669461741568287396) -->
+    <!-- no translation found for prediction_modes:2 (4894328801530136615) -->
+    <!-- no translation found for prediction_none (2472795101338047944) -->
+    <skip />
+    <!-- no translation found for prediction_basic (8407291081834155558) -->
+    <skip />
+    <!-- no translation found for prediction_full (3765102052052510268) -->
+    <skip />
+    <!-- no translation found for prediction_modes_values:0 (1346378763221728910) -->
+    <!-- no translation found for prediction_modes_values:1 (7980848218230433021) -->
+    <!-- no translation found for prediction_modes_values:2 (7444980361469942622) -->
+    <string name="added_word">"<xliff:g id="WORD">%s</xliff:g>:已儲存"</string>
+    <string name="alternates_for_a">"àáâãäåæ"</string>
+    <string name="alternates_for_e">"èéêë"</string>
+    <string name="alternates_for_i">"ìíîï"</string>
+    <string name="alternates_for_o">"òóôõöœø"</string>
+    <string name="alternates_for_u">"ùúûü"</string>
+    <string name="alternates_for_s">"§ß"</string>
+    <string name="alternates_for_n">"ñ"</string>
+    <string name="alternates_for_c">"ç"</string>
+    <string name="alternates_for_y">"ýÿ"</string>
+    <!-- no translation found for alternates_for_z (243837378542028049) -->
+    <skip />
+    <!-- no translation found for tip_long_press (6101270866284343344) -->
+    <skip />
+    <!-- no translation found for tip_dismiss (7585579046862204381) -->
+    <skip />
+    <!-- no translation found for tip_access_symbols (6344098517525531652) -->
+    <skip />
+    <!-- no translation found for tip_add_to_dictionary (1487293888469227817) -->
+    <skip />
+    <!-- no translation found for label_go_key (1635148082137219148) -->
+    <skip />
+    <!-- no translation found for label_next_key (362972844525672568) -->
+    <skip />
+    <!-- no translation found for label_send_key (2815056534433717444) -->
+    <skip />
+    <!-- no translation found for label_symbol_key (6175820506864489453) -->
+    <skip />
+    <!-- no translation found for label_phone_key (4275497665515080551) -->
+    <skip />
+    <!-- no translation found for label_alpha_key (8864943487292437456) -->
+    <skip />
+    <!-- no translation found for label_alt_key (2846315350346694811) -->
+    <skip />
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..c90d9f6
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+<resources>
+    <color name="candidate_normal">#FF000000</color>
+    <color name="candidate_recommended">#FFE35900</color>
+    <color name="candidate_other">#ff808080</color>
+</resources>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000..d757f09
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<resources>
+    <dimen name="key_height">50dip</dimen>
+    <dimen name="bubble_pointer_offset">22dip</dimen>
+</resources>
\ No newline at end of file
diff --git a/res/values/donottranslate.xml b/res/values/donottranslate.xml
new file mode 100644
index 0000000..8a879bd
--- /dev/null
+++ b/res/values/donottranslate.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2009, 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Symbols that are commonly considered word separators in this language -->
+    <string name="word_separators">.\u0020,;:!?\n()[]*&amp;@{}/&lt;&gt;_+=|\u0022</string>
+    <!-- Symbols that are sentence separators, for purposes of making it hug the last sentence. -->
+    <string name="sentence_separators">.,;:!?</string>
+</resources>
diff --git a/res/values/durations.xml b/res/values/durations.xml
new file mode 100644
index 0000000..92af68e
--- /dev/null
+++ b/res/values/durations.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<resources>
+    <!-- Vibration duration in milliseconds, for key presses in the IME. This can be hardware
+        dependent and may require overriding with a device specific overlay. -->
+    <integer name="vibrate_duration_ms">40</integer>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..41809c1
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Title for Latin keyboard  -->
+    <string name="english_ime_name">Android keyboard</string>
+    <!-- Title for Latin keyboard settings activity / dialog -->
+    <string name="english_ime_settings">Android keyboard settings</string>
+
+    <!-- Option to provide vibrate/haptic feedback on keypress -->
+    <string name="vibrate_on_keypress">Vibrate on keypress</string>
+    <!-- Option to play back sound on keypress in soft keyboard -->
+    <string name="sound_on_keypress">Sound on keypress</string>
+    
+    <!-- Option to enable using nearby keys when correcting/predicting -->
+    <string name="hit_correction">Correct typing errors</string>
+    
+    <!-- Description for hit_correction  -->
+    <string name="hit_correction_summary">Enable input error correction</string>
+    
+    <!-- Option to enable using nearby keys when correcting/predicting in landscape-->
+    <string name="hit_correction_land">Landscape input errors</string>
+    
+    <!-- Description for hit_correction in landscape -->
+    <string name="hit_correction_land_summary">Enable input error correction</string>
+    
+    <!-- Option to automatically correct word on hitting space -->
+    <string name="auto_correction">Word suggestions</string> 
+    
+    <!-- Description for auto_correction -->
+    <string name="auto_correction_summary">Automatically correct the previous word</string>
+	
+    <!-- Option to enable text prediction -->
+    <string name="prediction">Word suggestions</string>
+    <!-- Category title for text prediction -->
+    <string name="prediction_category">Word suggestion settings</string>
+    <!-- Description for text prediction -->
+    <string name="prediction_summary">Enable auto completion while typing</string>
+	
+    <!-- Dialog title for auto complete choices -->
+    <string name="auto_complete_dialog_title">Auto completion</string>
+    
+    <!-- Option to enable text prediction in landscape -->
+    <string name="prediction_landscape">Increase text field size</string> 
+    <!-- Description for text prediction -->
+    <string name="prediction_landscape_summary">Hide word suggestions in landscape view</string>
+	
+    <!-- Option to enable auto capitalization of sentences -->
+    <string name="auto_cap">Auto-capitalization</string> 
+    <!-- Description for auto cap -->
+    <string name="auto_cap_summary">Capitalize the start of a sentence</string>
+    <!-- Option to enable auto punctuate -->
+    <string name="auto_punctuate">Auto-punctuate</string> 
+    <!-- Description for auto punctuate -->
+    <string name="auto_punctuate_summary"></string>
+    
+    <!-- Array of prediction modes -->
+    <string-array name="prediction_modes">
+        <item>None</item>
+        <item>Basic</item>
+        <item>Advanced</item>
+    </string-array>
+    
+    <!-- Don't translate -->
+    <string name="prediction_none" >0</string>
+    <!-- Don't translate -->
+    <string name="prediction_basic">1</string>
+    <!-- Don't translate -->
+    <string name="prediction_full" >2</string>
+
+    <string-array name="prediction_modes_values">
+        <item>@string/prediction_none</item>
+        <item>@string/prediction_basic</item>
+        <item>@string/prediction_full</item>
+    </string-array>
+
+    <!-- Indicates that a word has been added to the dictionary -->
+    <string name="added_word"><xliff:g id="word">%s</xliff:g> : Saved</string>
+    <!-- Accented forms of "a" -->
+    <string name="alternates_for_a">àáâãäåæ</string>
+    <!-- Accented forms of "e" -->
+    <string name="alternates_for_e">èéêë</string>
+    <!-- Accented forms of "i" -->
+    <string name="alternates_for_i">ìíîï</string>
+    <!-- Accented forms of "o" -->
+    <string name="alternates_for_o">òóôõöœø</string>
+    <!-- Accented forms of "u" -->
+    <string name="alternates_for_u">ùúûü</string>
+    <!-- Letters associated with "s" -->
+    <string name="alternates_for_s">§ß</string>
+    <!-- Accented forms of "n" -->
+    <string name="alternates_for_n">ñ</string>
+    <!-- Accented forms of "c" -->
+    <string name="alternates_for_c">ç</string>
+    <!-- Accented forms of "y" -->
+    <string name="alternates_for_y">ýÿ</string>
+    <!-- Accented forms of "z" -->
+    <string name="alternates_for_z"></string>
+    
+    <!-- Tip to long press on keys -->
+    <string name="tip_long_press">Hold a key down to see accents (ø, ö, etc.)</string>
+    <!-- Tip to dismiss keyboard -->
+    <string name="tip_dismiss">Press the back key \u21B6 to close the keyboard at any point</string>
+    <!-- Tip to press ?123 to access numbers and symbols -->
+    <string name="tip_access_symbols">Access numbers and symbols</string>    
+    <!-- Tip to long press on typed word to add to dictionary -->
+    <string name="tip_add_to_dictionary">Press and hold the left-most word to add it to the dictionary
+        </string>
+
+    <!-- Label for soft enter key when it performs GO action.  Must be short to fit on key! -->
+    <string name="label_go_key">Go</string>
+    <!-- Label for soft enter key when it performs NEXT action.  Must be short to fit on key! -->
+    <string name="label_next_key">Next</string>
+    <!-- Label for soft enter key when it performs DONE action.  Must be short to fit on key! -->
+    <string name="label_done_key">Done</string>
+    <!-- Label for soft enter key when it performs SEND action.  Must be short to fit on key! -->
+    <string name="label_send_key">Send</string>
+    <!-- Label for "switch to symbols" key.  Must be short to fit on key! -->
+    <string name="label_symbol_key">\?123</string>
+    <!-- Label for "switch to numeric" key.  Must be short to fit on key! -->
+    <string name="label_phone_key">123</string>
+    <!-- Label for "switch to alphabetic" key.  Must be short to fit on key! -->
+    <string name="label_alpha_key">ABC</string>
+    <!-- Label for ALT modifier key.  Must be short to fit on key! -->
+    <string name="label_alt_key">ALT</string>
+</resources>
diff --git a/res/xml-de/kbd_qwerty.xml b/res/xml-de/kbd_qwerty.xml
new file mode 100755
index 0000000..763492d
--- /dev/null
+++ b/res/xml-de/kbd_qwerty.xml
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+        <Key android:codes="119" android:keyLabel="w"/>
+        <Key android:codes="101" android:keyLabel="e"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_e"
+        />
+        <Key android:codes="114" android:keyLabel="r"/>
+        <Key android:codes="116" android:keyLabel="t"/>
+        <Key android:codes="122" android:keyLabel="z" />
+        <Key android:codes="117" android:keyLabel="u"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_u"
+        />
+        <Key android:codes="105" android:keyLabel="i"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_i"
+        />
+        <Key android:codes="111" android:keyLabel="o"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_o"
+        />
+        <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row>
+        <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_a"
+                android:keyEdgeFlags="left"/>
+        <Key android:codes="115" android:keyLabel="s"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_s"
+        />
+        <Key android:codes="100" android:keyLabel="d"/>
+        <Key android:codes="102" android:keyLabel="f"/>
+        <Key android:codes="103" android:keyLabel="g"/>
+        <Key android:codes="104" android:keyLabel="h"/>
+        <Key android:codes="106" android:keyLabel="j"/>
+        <Key android:codes="107" android:keyLabel="k"/>
+        <Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row>
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift" 
+                android:keyWidth="15%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:codes="121" android:keyLabel="y"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_y"
+        />
+        <Key android:codes="120" android:keyLabel="x"/>
+        <Key android:codes="99" android:keyLabel="c"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_c"
+        />
+        <Key android:codes="118" android:keyLabel="v"/>
+        <Key android:codes="98" android:keyLabel="b"/>
+        <Key android:codes="110" android:keyLabel="n"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_n"
+        />
+        <Key android:codes="109" android:keyLabel="m"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" 
+                android:keyWidth="15%p" android:keyEdgeFlags="right" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+    
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="," android:keyWidth="15%p" />
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="30%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="15%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel=".com" android:keyOutputText=".com"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="15%p"/>
+        <Key android:keyLabel="/" android:keyWidth="15%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="15%p" android:isRepeatable="true"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="15%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="," android:keyWidth="15%p" />
+        <Key android:keyLabel="\@" android:keyWidth="15%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="15%p" android:isRepeatable="true"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="15%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="," android:keyWidth="15%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="30%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="15%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+
+    </Row>
+</Keyboard>
+    
\ No newline at end of file
diff --git a/res/xml-fr/kbd_qwerty.xml b/res/xml-fr/kbd_qwerty.xml
new file mode 100644
index 0000000..573f08a
--- /dev/null
+++ b/res/xml-fr/kbd_qwerty.xml
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="97" android:keyLabel="a" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_a"
+                android:keyEdgeFlags="left"/>
+        <Key android:codes="122" android:keyLabel="z"/>
+        <Key android:codes="101" android:keyLabel="e"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_e"
+        />
+        <Key android:codes="114" android:keyLabel="r"/>
+        <Key android:codes="116" android:keyLabel="t"/>
+        <Key android:codes="121" android:keyLabel="y"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_y"
+        />
+        <Key android:codes="117" android:keyLabel="u"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_u"
+        />
+        <Key android:codes="105" android:keyLabel="i"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_i"
+        />
+        <Key android:codes="111" android:keyLabel="o"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_o"
+        />
+        <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row>
+        <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+        <Key android:codes="115" android:keyLabel="s"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_s"
+        />
+        <Key android:codes="100" android:keyLabel="d"/>
+        <Key android:codes="102" android:keyLabel="f"/>
+        <Key android:codes="103" android:keyLabel="g"/>
+        <Key android:codes="104" android:keyLabel="h"/>
+        <Key android:codes="106" android:keyLabel="j"/>
+        <Key android:codes="107" android:keyLabel="k"/>
+        <Key android:codes="108" android:keyLabel="l"/>
+        <Key android:codes="109" android:keyLabel="m" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift" 
+                android:keyWidth="15%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:codes="119" android:keyLabel="w"/>
+        <Key android:codes="120" android:keyLabel="x"/>
+        <Key android:codes="99" android:keyLabel="c"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_c"
+        />
+        <Key android:codes="118" android:keyLabel="v"/>
+        <Key android:codes="98" android:keyLabel="b"/>
+        <Key android:codes="110" android:keyLabel="n"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_n"
+        />
+        <!--Key android:codes="233,224,232,234" android:keyLabel="é"/-->
+        <Key android:keyLabel="\'"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" 
+                android:keyWidth="15%p" android:keyEdgeFlags="right" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+    
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="," android:keyWidth="15%p" />
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="30%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="15%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel=".com" android:keyOutputText=".com"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="15%p"/>
+        <Key android:keyLabel="/" android:keyWidth="15%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="15%p" android:isRepeatable="true"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="15%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="," android:keyWidth="15%p" />
+        <Key android:keyLabel="\@" android:keyWidth="15%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="15%p" android:isRepeatable="true"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="15%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="," android:keyWidth="15%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="30%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="15%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+</Keyboard>
+    
\ No newline at end of file
diff --git a/res/xml/azerty.xml b/res/xml/azerty.xml
new file mode 100644
index 0000000..614aa49
--- /dev/null
+++ b/res/xml/azerty.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="97" android:keyLabel="a" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_a"
+                android:keyEdgeFlags="left"/>
+        <Key android:codes="122" android:keyLabel="z"/>
+        <Key android:codes="101" android:keyLabel="e"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_e"
+        />
+        <Key android:codes="114" android:keyLabel="r"/>
+        <Key android:codes="116" android:keyLabel="t"/>
+        <Key android:codes="121" android:keyLabel="y"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_y"
+        />
+        <Key android:codes="117" android:keyLabel="u"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_u"
+        />
+        <Key android:codes="105" android:keyLabel="i"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_i"
+        />
+        <Key android:codes="111" android:keyLabel="o"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_o"
+        />
+        <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row>
+        <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+        <Key android:codes="115" android:keyLabel="s"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_s"
+        />
+        <Key android:codes="100" android:keyLabel="d"/>
+        <Key android:codes="102" android:keyLabel="f"/>
+        <Key android:codes="103" android:keyLabel="g"/>
+        <Key android:codes="104" android:keyLabel="h"/>
+        <Key android:codes="106" android:keyLabel="j"/>
+        <Key android:codes="107" android:keyLabel="k"/>
+        <Key android:codes="108" android:keyLabel="l"/>
+        <Key android:codes="109" android:keyLabel="m" android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row>
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift" 
+                android:keyWidth="15%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:codes="119" android:keyLabel="w"/>
+        <Key android:codes="120" android:keyLabel="x"/>
+        <Key android:codes="99" android:keyLabel="c"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_c"
+        />
+        <Key android:codes="118" android:keyLabel="v"/>
+        <Key android:codes="98" android:keyLabel="b"/>
+        <Key android:codes="110" android:keyLabel="n"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_n"
+        />
+        <Key android:codes="233,224,232,234" android:keyLabel="é"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" 
+                android:keyWidth="15%p" android:keyEdgeFlags="right" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+    
+    <Row android:rowEdgeFlags="bottom">
+        <Key android:codes="-3" android:keyIcon="@drawable/sym_keyboard_done" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_done" 
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="-2" android:keyLabel="123" android:keyWidth="15%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="30%p" android:isRepeatable="true"/>
+        <Key android:codes="46,44" android:keyLabel=". ," 
+                android:popupKeyboard="@xml/popup_punctuation" android:keyWidth="15%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+</Keyboard>
+    
\ No newline at end of file
diff --git a/res/xml/kbd_alpha.xml b/res/xml/kbd_alpha.xml
new file mode 100644
index 0000000..4e8af33
--- /dev/null
+++ b/res/xml/kbd_alpha.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:keyLabel="a" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_a"
+                android:keyEdgeFlags="left" />
+        <Key android:keyLabel="b" />
+        <Key android:keyLabel="c" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_c" />
+        <Key android:keyLabel="d" />
+        <Key android:keyLabel="e" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_e" />
+        <Key android:keyLabel="f" />
+        <Key android:keyLabel="g" />
+        <Key android:keyLabel="h" />
+        <Key android:keyLabel="i"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_i" />
+        <Key android:keyLabel="j" android:keyEdgeFlags="right" />
+    </Row>
+    <Row>
+        <Key android:keyLabel="k" android:keyEdgeFlags="left" />
+        <Key android:keyLabel="l" />
+        <Key android:keyLabel="m" />
+        <Key android:keyLabel="n"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_n" />
+        <Key android:keyLabel="o"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_o" />
+        <Key android:keyLabel="p" />
+        <Key android:keyLabel="q" />
+        <Key android:keyLabel="r" />
+        <Key android:keyLabel="s"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_s" />
+        <Key android:keyLabel="t" android:keyEdgeFlags="right" />
+    </Row>
+
+    <Row>
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift" 
+                android:keyWidth="15%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="u"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_u" />
+        <Key android:keyLabel="v"/>
+        <Key android:keyLabel="w"/>
+        <Key android:keyLabel="x"/>
+        <Key android:keyLabel="y"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_y"
+        />
+        <Key android:keyLabel="z"/>
+        <Key android:keyLabel=","/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" 
+                android:keyWidth="15%p" android:keyEdgeFlags="right" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:rowEdgeFlags="bottom">
+        <Key android:codes="-3" android:keyIcon="@drawable/sym_keyboard_done" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_done" 
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:codes="-2" android:keyLabel="123" android:keyWidth="15%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="30%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." 
+                android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="15%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+</Keyboard>
\ No newline at end of file
diff --git a/res/xml/kbd_phone.xml b/res/xml/kbd_phone.xml
new file mode 100755
index 0000000..880d961
--- /dev/null
+++ b/res/xml/kbd_phone.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="26.67%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="49" android:keyIcon="@drawable/sym_keyboard_num1" android:keyEdgeFlags="left"/>
+        <Key android:codes="50" android:keyIcon="@drawable/sym_keyboard_num2"/>
+        <Key android:codes="51" android:keyIcon="@drawable/sym_keyboard_num3"/>
+        <Key android:keyLabel="-" android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:codes="52" android:keyIcon="@drawable/sym_keyboard_num4" android:keyEdgeFlags="left"/>
+        <Key android:codes="53" android:keyIcon="@drawable/sym_keyboard_num5"/>
+        <Key android:codes="54" android:keyIcon="@drawable/sym_keyboard_num6"/>
+        <Key android:keyLabel="." android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row>
+        <Key android:codes="55" android:keyIcon="@drawable/sym_keyboard_num7" android:keyEdgeFlags="left"/>
+        <Key android:codes="56" android:keyIcon="@drawable/sym_keyboard_num8"/>
+        <Key android:codes="57" android:keyIcon="@drawable/sym_keyboard_num9"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:keyWidth="20%p"
+                android:isRepeatable="true" android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyIcon="@drawable/sym_keyboard_numalt"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:iconPreview="@drawable/sym_keyboard_feedback_numalt"/>
+
+        <Key android:codes="48" android:keyIcon="@drawable/sym_keyboard_num0"/>
+
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:isRepeatable="true"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:keyWidth="20%p"
+                android:keyEdgeFlags="right"/>
+    </Row>
+    
+</Keyboard>
\ No newline at end of file
diff --git a/res/xml/kbd_phone_symbols.xml b/res/xml/kbd_phone_symbols.xml
new file mode 100755
index 0000000..9ce7a1a
--- /dev/null
+++ b/res/xml/kbd_phone_symbols.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="26.67%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:keyLabel="(" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="/"/>
+        <Key android:keyLabel=")"/>
+        <Key android:keyLabel="-" android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row>
+        <Key android:keyLabel="N" android:keyEdgeFlags="left"/>
+        <!-- Pause is a comma. 
+                Check PhoneNumberUtils.java to see if this has changed. -->
+        <Key android:codes="44" android:keyLabel="Pause"/>
+        <Key android:keyLabel=","/>
+        <Key android:keyLabel="." android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row>
+        <Key android:codes="42" android:keyIcon="@drawable/sym_keyboard_numstar"
+                android:keyEdgeFlags="left"/>
+        <!-- Wait is w -->
+        <Key android:codes="w" android:keyLabel="Wait"/>
+        <Key android:codes="35" android:keyIcon="@drawable/sym_keyboard_numpound"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:keyWidth="20%p"
+                android:isRepeatable="true" android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_phone_key" 
+                android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="+"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:isRepeatable="true"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return"
+                android:keyWidth="20%p"
+                android:keyEdgeFlags="right"/>
+    </Row>
+    
+</Keyboard>
\ No newline at end of file
diff --git a/res/xml/kbd_popup_template.xml b/res/xml/kbd_popup_template.xml
new file mode 100644
index 0000000..aca4693
--- /dev/null
+++ b/res/xml/kbd_popup_template.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+</Keyboard>
diff --git a/res/xml/kbd_qwerty.xml b/res/xml/kbd_qwerty.xml
new file mode 100755
index 0000000..34e9912
--- /dev/null
+++ b/res/xml/kbd_qwerty.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+        <Key android:codes="119" android:keyLabel="w"/>
+        <Key android:codes="101" android:keyLabel="e"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_e"
+        />
+        <Key android:codes="114" android:keyLabel="r"/>
+        <Key android:codes="116" android:keyLabel="t"/>
+        <Key android:codes="121" android:keyLabel="y"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_y"
+        />
+        <Key android:codes="117" android:keyLabel="u"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_u"
+        />
+        <Key android:codes="105" android:keyLabel="i"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_i"
+        />
+        <Key android:codes="111" android:keyLabel="o"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_o"
+        />
+        <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row>
+        <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_a"
+                android:keyEdgeFlags="left"/>
+        <Key android:codes="115" android:keyLabel="s"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_s"
+        />
+        <Key android:codes="100" android:keyLabel="d"/>
+        <Key android:codes="102" android:keyLabel="f"/>
+        <Key android:codes="103" android:keyLabel="g"/>
+        <Key android:codes="104" android:keyLabel="h"/>
+        <Key android:codes="106" android:keyLabel="j"/>
+        <Key android:codes="107" android:keyLabel="k"/>
+        <Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row>
+        <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift" 
+                android:keyWidth="15%p" android:isModifier="true"
+                android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:codes="122" android:keyLabel="z"/>
+        <Key android:codes="120" android:keyLabel="x"/>
+        <Key android:codes="99" android:keyLabel="c"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_c"
+        />
+        <Key android:codes="118" android:keyLabel="v"/>
+        <Key android:codes="98" android:keyLabel="b"/>
+        <Key android:codes="110" android:keyLabel="n"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="@string/alternates_for_n"
+        />
+        <Key android:codes="109" android:keyLabel="m"/>
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" 
+                android:keyWidth="15%p" android:keyEdgeFlags="right" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="," android:keyWidth="15%p" />
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="30%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="15%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_url" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel=".com" android:keyOutputText=".com"
+                android:popupKeyboard="@xml/popup_domains"
+                android:keyWidth="15%p"/>
+        <Key android:keyLabel="/" android:keyWidth="15%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="15%p" android:isRepeatable="true"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="15%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_email" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="," android:keyWidth="15%p" />
+        <Key android:keyLabel="\@" android:keyWidth="15%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="15%p" android:isRepeatable="true"/>
+        <Key android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="15%p"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+
+    <Row android:keyboardMode="@+id/mode_im" android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_symbol_key" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="," android:keyWidth="15%p"/>
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:keyWidth="30%p" android:isRepeatable="true"/>
+        <Key android:codes="46" android:keyLabel="." android:popupKeyboard="@xml/popup_punctuation" 
+                android:keyWidth="15%p"/>
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) "
+                android:popupKeyboard="@xml/popup_smileys"
+                android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+    </Row>
+</Keyboard>
+    
\ No newline at end of file
diff --git a/res/xml/kbd_symbols.xml b/res/xml/kbd_symbols.xml
new file mode 100755
index 0000000..2a15039
--- /dev/null
+++ b/res/xml/kbd_symbols.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:codes="49" android:keyLabel="1" android:keyEdgeFlags="left"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="¹½⅓¼⅛"
+        />
+        <Key android:codes="50" android:keyLabel="2"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="²⅔"
+        />
+        <Key android:codes="51" android:keyLabel="3"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="³¾⅜"
+        />
+        <Key android:codes="52" android:keyLabel="4"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="⁴"
+        />
+        <Key android:codes="53" android:keyLabel="5"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="⅝"
+        />
+        <Key android:codes="54" android:keyLabel="6"/>
+        <Key android:codes="55" android:keyLabel="7"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="⅞"
+        />
+        <Key android:codes="56" android:keyLabel="8"/>
+        <Key android:codes="57" android:keyLabel="9"/>
+        <Key android:codes="48" android:keyLabel="0" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ⁿ∅"        
+                android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row>
+        <Key android:codes="64" android:keyLabel="\@" android:keyEdgeFlags="left"/>
+        <Key android:codes="35" android:keyLabel="\#"/>
+        <Key android:codes="36" android:keyLabel="$"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="¢£€¥₣₤₱"
+        />        
+        <Key android:codes="37" android:keyLabel="%"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="‰"
+        />
+        <Key android:codes="38" android:keyLabel="&amp;"/>
+        <Key android:codes="42" android:keyLabel="*"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="†‡"
+        />
+        <Key android:codes="45" android:keyLabel="-"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="–—"
+        />
+        <Key android:keyLabel="+"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="±"
+        />
+        <Key android:codes="40" android:keyLabel="("
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="[{&lt;"
+        />
+        <Key android:codes="41" android:keyLabel=")" android:keyEdgeFlags="right"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="]}&gt;"
+        />
+    </Row>
+    
+    <Row>
+        <Key android:codes="-1" android:keyLabel="@string/label_alt_key" 
+                android:keyWidth="15%p" android:isModifier="true" 
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:codes="33" android:keyLabel="!" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="¡"
+        />
+        <Key android:codes="34" android:keyLabel="&quot;"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="“”«»˝"
+        />
+        <Key android:codes="39" android:keyLabel="\'"/>
+        <Key android:codes="58" android:keyLabel=":"/>
+        <Key android:codes="59" android:keyLabel=";"/>
+        <Key android:codes="47" android:keyLabel="/" />
+        <Key android:codes="63" android:keyLabel="\?"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="¿"
+        />
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" android:keyWidth="15%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+    
+    <Row  android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_alpha_key" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="," android:keyWidth="15%p" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="‚„"
+        />
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" android:keyWidth="30%p" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:isRepeatable="true"/>
+        <Key android:keyLabel="." android:keyWidth="15%p" />
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" android:keyWidth="20%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                />
+    </Row>
+</Keyboard>
\ No newline at end of file
diff --git a/res/xml/kbd_symbols_shift.xml b/res/xml/kbd_symbols_shift.xml
new file mode 100755
index 0000000..6a472a4
--- /dev/null
+++ b/res/xml/kbd_symbols_shift.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row>
+        <Key android:keyLabel="~" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="`"/>
+        <Key android:keyLabel="|"/>
+        <Key android:keyLabel="•"/>
+        <Key android:keyLabel="√"/>
+        <Key android:keyLabel="π"/>
+        <Key android:keyLabel="÷"/>
+        <Key android:keyLabel="×"/>
+        <Key android:keyLabel="{"/>
+        <Key android:keyLabel="}" android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row>
+        <Key android:keyLabel="¥" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="£"/>
+        <Key android:keyLabel="¢"/>
+        <Key android:keyLabel="€"/>
+        <Key android:keyLabel="°"/>
+        <Key android:keyLabel="^"/>
+        <Key android:keyLabel="_"/>
+        <Key android:keyLabel="="
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="≠≈∞"
+        />
+        <Key android:keyLabel="["/>
+        <Key android:keyLabel="]" android:keyEdgeFlags="right"/>
+    </Row>
+    
+    <Row>
+        <Key android:codes="-1" android:keyLabel="@string/label_alt_key" 
+                android:keyWidth="15%p" android:isModifier="true"
+                android:isSticky="true" android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="™"/>
+        <Key android:keyLabel="®"/>
+        <Key android:keyLabel="©"/>
+        <Key android:keyLabel="¶"/>
+        <Key android:keyLabel="\\"/>
+        <Key android:keyLabel="&lt;" 
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="≤«‹"
+        />
+        <Key android:keyLabel="&gt;"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="≥»›"
+        />
+        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" android:keyWidth="15%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+                android:isRepeatable="true"/>
+    </Row>
+    
+    <Row android:rowEdgeFlags="bottom">
+        <Key android:codes="-2" android:keyLabel="@string/label_alpha_key" android:keyWidth="20%p"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters=""
+                android:keyEdgeFlags="left"/>
+        <Key android:keyLabel="„" android:keyWidth="15%p" />
+        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" android:keyWidth="30%p" 
+                android:iconPreview="@drawable/sym_keyboard_feedback_space"
+                android:isRepeatable="true"/>
+        <Key android:keyLabel="…" android:keyWidth="15%p" />
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" android:keyWidth="20%p" android:keyEdgeFlags="right"
+                android:iconPreview="@drawable/sym_keyboard_feedback_return"
+                />
+    </Row>
+</Keyboard>
diff --git a/res/xml/method.xml b/res/xml/method.xml
new file mode 100644
index 0000000..e5654e9
--- /dev/null
+++ b/res/xml/method.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2008, 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.
+ */
+-->
+
+<!-- The attributes in this XML file provide configuration information -->
+<!-- for the Input Method Manager. -->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android"
+        android:settingsActivity="com.android.inputmethod.latin.LatinIMESettings"
+        android:isDefault="@bool/im_is_default"
+/>
diff --git a/res/xml/popup_domains.xml b/res/xml/popup_domains.xml
new file mode 100644
index 0000000..5c86386
--- /dev/null
+++ b/res/xml/popup_domains.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="15%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row android:rowEdgeFlags="top|bottom">
+        <Key android:keyLabel=".net" android:keyOutputText=".net" android:keyEdgeFlags="left" />
+        <Key android:keyLabel=".org" android:keyOutputText=".org"/>
+        <Key android:keyLabel=".gov" android:keyOutputText=".gov"/>
+        <Key android:keyLabel=".tv" android:keyOutputText=".tv" android:keyEdgeFlags="right" />
+    </Row>
+</Keyboard>
diff --git a/res/xml/popup_punctuation.xml b/res/xml/popup_punctuation.xml
new file mode 100644
index 0000000..9d4575d
--- /dev/null
+++ b/res/xml/popup_punctuation.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="10%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row android:rowEdgeFlags="top|bottom">
+        <Key android:codes="58" android:keyLabel=":" android:keyEdgeFlags="left" />
+        <Key android:codes="47" android:keyLabel="/" />
+        <Key android:codes="64" android:keyLabel="\@" />
+        <Key android:codes="39" android:keyLabel="\'" />
+        <Key android:codes="34" android:keyLabel="&quot;" />
+        <Key android:codes="63" android:keyLabel="\?" />
+        <Key android:codes="33" android:keyLabel="!" android:keyEdgeFlags="right" />
+    </Row>
+</Keyboard>
diff --git a/res/xml/popup_smileys.xml b/res/xml/popup_smileys.xml
new file mode 100644
index 0000000..5663fef
--- /dev/null
+++ b/res/xml/popup_smileys.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* 
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+    android:keyWidth="15%p"
+    android:horizontalGap="0px"
+    android:verticalGap="0px"
+    android:keyHeight="@dimen/key_height"
+    >
+
+    <Row android:rowEdgeFlags="top">
+        <Key android:keyLabel=":-)" android:keyOutputText=":-) " android:keyEdgeFlags="left" />
+        <Key android:keyLabel=":-(" android:keyOutputText=":-( "/>
+        <Key android:keyLabel=";-)" android:keyOutputText=";-) "/>
+        <Key android:keyLabel=":-P" android:keyOutputText=":-P "/>
+        <Key android:keyLabel="=-O" android:keyOutputText="=-O " android:keyEdgeFlags="right" />
+    </Row>
+    <Row>
+        <Key android:keyLabel=":-*" android:keyOutputText=":-* " android:keyEdgeFlags="left" />
+        <Key android:keyLabel=":O" android:keyOutputText=":O "/>
+        <Key android:keyLabel="B-)" android:keyOutputText="B-) "/>
+        <Key android:keyLabel=":-$" android:keyOutputText=":-$ "/>
+        <Key android:keyLabel=":-!" android:keyOutputText=":-! " android:keyEdgeFlags="right" />
+    </Row>
+    <Row android:rowEdgeFlags="bottom">
+        <Key android:keyLabel=":-[" android:keyOutputText=":-[ " android:keyEdgeFlags="left" />
+        <Key android:keyLabel="O:-)" android:keyOutputText="O:-) "/>
+        <Key android:keyLabel=":-\\" android:keyOutputText=":-\\ "/>
+        <Key android:keyLabel=":'(" android:keyOutputText=":'( "/>
+        <Key android:keyLabel=":-D" android:keyOutputText=":-D " android:keyEdgeFlags="right" />
+    </Row>
+</Keyboard>
diff --git a/res/xml/prefs.xml b/res/xml/prefs.xml
new file mode 100644
index 0000000..1721384
--- /dev/null
+++ b/res/xml/prefs.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+        android:title="@string/english_ime_settings"
+        android:key="english_ime_settings">
+
+    <CheckBoxPreference
+            android:key="vibrate_on"
+            android:title="@string/vibrate_on_keypress"
+            android:defaultValue="true"
+            android:persistent="true"
+            />
+
+    <CheckBoxPreference
+            android:key="sound_on"
+            android:title="@string/sound_on_keypress"
+            android:persistent="true" 
+            />
+
+    <CheckBoxPreference
+            android:key="auto_cap"
+            android:title="@string/auto_cap"
+            android:persistent="true"
+            android:defaultValue="true"
+            />
+
+    <!--CheckBoxPreference
+            android:key="auto_punctuate"
+            android:title="@string/auto_punctuate"
+            android:persistent="true"
+            android:defaultValue="true"
+            android:visible="false"
+            /-->
+
+    <ListPreference
+            android:key="prediction_mode"
+            android:title="@string/prediction"
+            android:dialogTitle="@string/auto_complete_dialog_title"
+            android:summary="@string/prediction_summary"
+            android:persistent="true" 
+            android:entries="@array/prediction_modes"
+            android:entryValues="@array/prediction_modes_values"
+            android:defaultValue="@string/prediction_basic"
+            />
+
+    <PreferenceCategory
+            android:title="@string/prediction_category"
+            android:key="prediction_settings">
+            
+        <CheckBoxPreference
+            android:key="hit_correction"
+            android:title="@string/hit_correction"
+            android:summary="@string/hit_correction_summary"
+            android:persistent="true" 
+            android:defaultValue="true"
+            />
+
+        <CheckBoxPreference
+            android:key="prediction_landscape"
+            android:title="@string/prediction_landscape"
+            android:summary="@string/prediction_landscape_summary"
+            android:persistent="true" 
+            android:defaultValue="false"
+            />
+            
+    </PreferenceCategory>            
+</PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/inputmethod/latin/BinaryDictionary.java b/src/com/android/inputmethod/latin/BinaryDictionary.java
new file mode 100644
index 0000000..bb4f1ba
--- /dev/null
+++ b/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ * 
+ * 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 java.util.Arrays;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.util.Log;
+
+/**
+ * Implements a static, compacted, binary dictionary of standard words.
+ */
+public class BinaryDictionary extends Dictionary {
+
+    public static final int MAX_WORD_LENGTH = 48;
+    private static final int MAX_ALTERNATIVES = 16;
+    private static final int MAX_WORDS = 16;
+
+    private static final int TYPED_LETTER_MULTIPLIER = 2;
+
+    private int mNativeDict;
+    private int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_ALTERNATIVES];
+    private WordCallback mWordCallback;
+    private char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
+    private int[] mFrequencies = new int[MAX_WORDS];
+
+    static {
+        try {
+            System.loadLibrary("jni_latinime");
+        } catch (UnsatisfiedLinkError ule) {
+            Log.e("BinaryDictionary", "Could not load native library jni_latinime");
+        }
+    }
+
+    /**
+     * Create a dictionary from a raw resource file
+     * @param context application context for reading resources
+     * @param resId the resource containing the raw binary dictionary
+     */
+    public BinaryDictionary(Context context, int resId) {
+        if (resId != 0) {
+            loadDictionary(context, resId);
+        }
+    }
+
+    private native int openNative(AssetManager am, String resourcePath, int typedLetterMultiplier,
+            int fullWordMultiplier);
+    private native void closeNative(int dict);
+    private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
+    private native int getSuggestionsNative(int dict, int[] inputCodes, int codesSize, 
+            char[] outputChars, int[] frequencies,
+            int maxWordLength, int maxWords, int maxAlternatives);
+    private native void setParamsNative(int typedLetterMultiplier,
+            int fullWordMultiplier);
+
+    private final void loadDictionary(Context context, int resId) {
+        AssetManager am = context.getResources().getAssets();
+        String assetName = context.getResources().getString(resId);
+        mNativeDict = openNative(am, assetName, TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER);
+    }
+
+    @Override
+    public void getWords(final WordComposer codes, final WordCallback callback) {
+        mWordCallback = callback;
+        final int codesSize = codes.size();
+        // Wont deal with really long words.
+        if (codesSize > MAX_WORD_LENGTH - 1) return;
+        
+        Arrays.fill(mInputCodes, -1);
+        for (int i = 0; i < codesSize; i++) {
+            int[] alternatives = codes.getCodesAt(i);
+            System.arraycopy(alternatives, 0, mInputCodes, i * MAX_ALTERNATIVES,
+                    Math.min(alternatives.length, MAX_ALTERNATIVES));
+        }
+        Arrays.fill(mOutputChars, (char) 0);
+
+        int count = getSuggestionsNative(mNativeDict, mInputCodes, codesSize, mOutputChars, mFrequencies,
+                MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES);
+
+        for (int j = 0; j < count; j++) {
+            if (mFrequencies[j] < 1) break;
+            int start = j * MAX_WORD_LENGTH;
+            int len = 0;
+            while (mOutputChars[start + len] != 0) {
+                len++;
+            }
+            if (len > 0) {
+                callback.addWord(mOutputChars, start, len, mFrequencies[j]);
+            }
+        }
+    }
+
+    @Override
+    public boolean isValidWord(CharSequence word) {
+        if (word == null) return false;
+        char[] chars = word.toString().toLowerCase().toCharArray();
+        return isValidWordNative(mNativeDict, chars, chars.length);
+    }
+    
+    public synchronized void close() {
+        if (mNativeDict != 0) {
+            closeNative(mNativeDict);
+            mNativeDict = 0;
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        close();
+        super.finalize();
+    }
+}
diff --git a/src/com/android/inputmethod/latin/CandidateView.java b/src/com/android/inputmethod/latin/CandidateView.java
new file mode 100755
index 0000000..08c68dc
--- /dev/null
+++ b/src/com/android/inputmethod/latin/CandidateView.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ * 
+ * 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+public class CandidateView extends View {
+
+    private static final int OUT_OF_BOUNDS = -1;
+    private static final List<CharSequence> EMPTY_LIST = new ArrayList<CharSequence>();
+
+    private LatinIME mService;
+    private List<CharSequence> mSuggestions = EMPTY_LIST;
+    private boolean mShowingCompletions;
+    private CharSequence mSelectedString;
+    private int mSelectedIndex;
+    private int mTouchX = OUT_OF_BOUNDS;
+    private Drawable mSelectionHighlight;
+    private boolean mTypedWordValid;
+    
+    private boolean mHaveMinimalSuggestion;
+    
+    private Rect mBgPadding;
+
+    private TextView mPreviewText;
+    private PopupWindow mPreviewPopup;
+    private int mCurrentWordIndex;
+    private Drawable mDivider;
+    
+    private static final int MAX_SUGGESTIONS = 32;
+    private static final int SCROLL_PIXELS = 20;
+    
+    private static final int MSG_REMOVE_PREVIEW = 1;
+    private static final int MSG_REMOVE_THROUGH_PREVIEW = 2;
+    
+    private int[] mWordWidth = new int[MAX_SUGGESTIONS];
+    private int[] mWordX = new int[MAX_SUGGESTIONS];
+    private int mPopupPreviewX;
+    private int mPopupPreviewY;
+
+    private static final int X_GAP = 10;
+    
+    private int mColorNormal;
+    private int mColorRecommended;
+    private int mColorOther;
+    private Paint mPaint;
+    private int mDescent;
+    private boolean mScrolled;
+    private int mTargetScrollX;
+    
+    private int mTotalWidth;
+    
+    private GestureDetector mGestureDetector;
+
+    Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_REMOVE_PREVIEW:
+                    mPreviewText.setVisibility(GONE);
+                    break;
+                case MSG_REMOVE_THROUGH_PREVIEW:
+                    mPreviewText.setVisibility(GONE);
+                    if (mTouchX != OUT_OF_BOUNDS) {
+                        removeHighlight();
+                    }
+                    break;
+            }
+            
+        }
+    };
+
+    /**
+     * Construct a CandidateView for showing suggested words for completion.
+     * @param context
+     * @param attrs
+     */
+    public CandidateView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mSelectionHighlight = context.getResources().getDrawable(
+                com.android.internal.R.drawable.list_selector_background_pressed);
+
+        LayoutInflater inflate =
+            (LayoutInflater) context
+                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mPreviewPopup = new PopupWindow(context);
+        mPreviewText = (TextView) inflate.inflate(R.layout.candidate_preview, null);
+        mPreviewPopup.setWindowLayoutMode(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+        mPreviewPopup.setContentView(mPreviewText);
+        mPreviewPopup.setBackgroundDrawable(null);
+        mColorNormal = context.getResources().getColor(R.color.candidate_normal);
+        mColorRecommended = context.getResources().getColor(R.color.candidate_recommended);
+        mColorOther = context.getResources().getColor(R.color.candidate_other);
+        mDivider = context.getResources().getDrawable(R.drawable.keyboard_suggest_strip_divider);
+
+        mPaint = new Paint();
+        mPaint.setColor(mColorNormal);
+        mPaint.setAntiAlias(true);
+        mPaint.setTextSize(mPreviewText.getTextSize());
+        mPaint.setStrokeWidth(0);
+        mDescent = (int) mPaint.descent();
+        
+        mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public void onLongPress(MotionEvent me) {
+                if (mSuggestions.size() > 0) {
+                    if (me.getX() + mScrollX < mWordWidth[0] && mScrollX < 10) {
+                        longPressFirstWord();
+                    }
+                }
+            }
+            
+            @Override
+            public boolean onScroll(MotionEvent e1, MotionEvent e2,
+                    float distanceX, float distanceY) {
+                mScrolled = true;
+                mScrollX += distanceX;
+                if (mScrollX < 0) {
+                    mScrollX = 0;
+                }
+                if (mScrollX + getWidth() > mTotalWidth) {                    
+                    mScrollX -= distanceX;
+                }
+                mTargetScrollX = mScrollX;
+                showPreview(OUT_OF_BOUNDS, null);
+                invalidate();
+                return true;
+            }
+        });
+        setHorizontalFadingEdgeEnabled(true);
+        setWillNotDraw(false);
+        setHorizontalScrollBarEnabled(false);
+        setVerticalScrollBarEnabled(false);
+        mScrollX = 0;
+    }
+    
+    /**
+     * A connection back to the service to communicate with the text field
+     * @param listener
+     */
+    public void setService(LatinIME listener) {
+        mService = listener;
+    }
+    
+    @Override
+    public int computeHorizontalScrollRange() {
+        return mTotalWidth;
+    }
+
+    /**
+     * If the canvas is null, then only touch calculations are performed to pick the target
+     * candidate.
+     */
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (canvas != null) {
+            super.onDraw(canvas);
+        }
+        mTotalWidth = 0;
+        if (mSuggestions == null) return;
+        
+        final int height = getHeight();
+        if (mBgPadding == null) {
+            mBgPadding = new Rect(0, 0, 0, 0);
+            if (getBackground() != null) {
+                getBackground().getPadding(mBgPadding);
+            }
+            mDivider.setBounds(0, mBgPadding.top, mDivider.getIntrinsicWidth(), 
+                    mDivider.getIntrinsicHeight());
+        }
+        int x = 0;
+        final int count = mSuggestions.size(); 
+        final int width = getWidth();
+        final Rect bgPadding = mBgPadding;
+        final Paint paint = mPaint;
+        final int touchX = mTouchX;
+        final int scrollX = mScrollX;
+        final boolean scrolled = mScrolled;
+        final boolean typedWordValid = mTypedWordValid;
+        final int y = (int) (height + mPaint.getTextSize() - mDescent) / 2;
+
+        for (int i = 0; i < count; i++) {
+            CharSequence suggestion = mSuggestions.get(i);
+            if (suggestion == null) continue;
+            paint.setColor(mColorNormal);
+            if (mHaveMinimalSuggestion 
+                    && ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid))) {
+                paint.setTypeface(Typeface.DEFAULT_BOLD);
+                paint.setColor(mColorRecommended);
+            } else if (i != 0) {
+                paint.setColor(mColorOther);
+            }
+            final int wordWidth;
+            if (mWordWidth[i] != 0) {
+                wordWidth = mWordWidth[i];
+            } else {
+                float textWidth =  paint.measureText(suggestion, 0, suggestion.length());
+                wordWidth = (int) textWidth + X_GAP * 2;
+                mWordWidth[i] = wordWidth;
+            }
+
+            mWordX[i] = x;
+
+            if (touchX + scrollX >= x && touchX + scrollX < x + wordWidth && !scrolled) {
+                if (canvas != null) {
+                    canvas.translate(x, 0);
+                    mSelectionHighlight.setBounds(0, bgPadding.top, wordWidth, height);
+                    mSelectionHighlight.draw(canvas);
+                    canvas.translate(-x, 0);
+                    showPreview(i, null);
+                }
+                mSelectedString = suggestion;
+                mSelectedIndex = i;
+            }
+
+            if (canvas != null) {
+                canvas.drawText(suggestion, 0, suggestion.length(), x + X_GAP, y, paint);
+                paint.setColor(mColorOther);
+                canvas.translate(x + wordWidth, 0);
+                mDivider.draw(canvas);
+                canvas.translate(-x - wordWidth, 0);
+            }
+            paint.setTypeface(Typeface.DEFAULT);
+            x += wordWidth;
+        }
+        mTotalWidth = x;
+        if (mTargetScrollX != mScrollX) {
+            scrollToTarget();
+        }
+    }
+    
+    private void scrollToTarget() {
+        if (mTargetScrollX > mScrollX) {
+            mScrollX += SCROLL_PIXELS;
+            if (mScrollX >= mTargetScrollX) {
+                mScrollX = mTargetScrollX;
+                requestLayout();
+            }
+        } else {
+            mScrollX -= SCROLL_PIXELS;
+            if (mScrollX <= mTargetScrollX) {
+                mScrollX = mTargetScrollX;
+                requestLayout();
+            }
+        }
+        invalidate();
+    }
+    
+    public void setSuggestions(List<CharSequence> suggestions, boolean completions,
+            boolean typedWordValid, boolean haveMinimalSuggestion) {
+        clear();
+        if (suggestions != null) {
+            mSuggestions = new ArrayList<CharSequence>(suggestions);
+        }
+        mShowingCompletions = completions;
+        mTypedWordValid = typedWordValid;
+        mScrollX = 0;
+        mTargetScrollX = 0;
+        mHaveMinimalSuggestion = haveMinimalSuggestion;
+        // Compute the total width
+        onDraw(null);
+        invalidate();
+        requestLayout();
+    }
+
+    public void scrollPrev() {
+        int i = 0;
+        final int count = mSuggestions.size();
+        int firstItem = 0; // Actually just before the first item, if at the boundary
+        while (i < count) {
+            if (mWordX[i] < mScrollX 
+                    && mWordX[i] + mWordWidth[i] >= mScrollX - 1) {
+                firstItem = i;
+                break;
+            }
+            i++;
+        }
+        int leftEdge = mWordX[firstItem] + mWordWidth[firstItem] - getWidth();
+        if (leftEdge < 0) leftEdge = 0;
+        updateScrollPosition(leftEdge);
+    }
+    
+    public void scrollNext() {
+        int i = 0;
+        int targetX = mScrollX;
+        final int count = mSuggestions.size();
+        int rightEdge = mScrollX + getWidth();
+        while (i < count) {
+            if (mWordX[i] <= rightEdge &&
+                    mWordX[i] + mWordWidth[i] >= rightEdge) {
+                targetX = Math.min(mWordX[i], mTotalWidth - getWidth());
+                break;
+            }
+            i++;
+        }
+        updateScrollPosition(targetX);
+    }
+
+    private void updateScrollPosition(int targetX) {
+        if (targetX != mScrollX) {
+            // TODO: Animate
+            mTargetScrollX = targetX;
+            requestLayout();
+            invalidate();
+            mScrolled = true;
+        }
+    }
+    
+    public void clear() {
+        mSuggestions = EMPTY_LIST;
+        mTouchX = OUT_OF_BOUNDS;
+        mSelectedString = null;
+        mSelectedIndex = -1;
+        invalidate();
+        Arrays.fill(mWordWidth, 0);
+        Arrays.fill(mWordX, 0);
+        if (mPreviewPopup.isShowing()) {
+            mPreviewPopup.dismiss();
+        }
+    }
+    
+    @Override
+    public boolean onTouchEvent(MotionEvent me) {
+
+        if (mGestureDetector.onTouchEvent(me)) {
+            return true;
+        }
+
+        int action = me.getAction();
+        int x = (int) me.getX();
+        int y = (int) me.getY();
+        mTouchX = x;
+
+        switch (action) {
+        case MotionEvent.ACTION_DOWN:
+            mScrolled = false;
+            invalidate();
+            break;
+        case MotionEvent.ACTION_MOVE:
+            if (y <= 0) {
+                // Fling up!?
+                if (mSelectedString != null) {
+                    if (!mShowingCompletions) {
+                        TextEntryState.acceptedSuggestion(mSuggestions.get(0),
+                                mSelectedString);
+                    }
+                    mService.pickSuggestionManually(mSelectedIndex, mSelectedString);
+                    mSelectedString = null;
+                    mSelectedIndex = -1;
+                }
+            }
+            invalidate();
+            break;
+        case MotionEvent.ACTION_UP:
+            if (!mScrolled) {
+                if (mSelectedString != null) {
+                    if (!mShowingCompletions) {
+                        TextEntryState.acceptedSuggestion(mSuggestions.get(0),
+                                mSelectedString);
+                    }
+                    mService.pickSuggestionManually(mSelectedIndex, mSelectedString);
+                }
+            }
+            mSelectedString = null;
+            mSelectedIndex = -1;
+            removeHighlight();
+            showPreview(OUT_OF_BOUNDS, null);
+            requestLayout();
+            break;
+        }
+        return true;
+    }
+    
+    /**
+     * For flick through from keyboard, call this method with the x coordinate of the flick 
+     * gesture.
+     * @param x
+     */
+    public void takeSuggestionAt(float x) {
+        mTouchX = (int) x;
+        // To detect candidate
+        onDraw(null);
+        if (mSelectedString != null) {
+            if (!mShowingCompletions) {
+                TextEntryState.acceptedSuggestion(mSuggestions.get(0), mSelectedString);
+            }
+            mService.pickSuggestionManually(mSelectedIndex, mSelectedString);
+        }
+        invalidate();
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_REMOVE_THROUGH_PREVIEW), 200);
+    }
+
+    private void showPreview(int wordIndex, String altText) {
+        int oldWordIndex = mCurrentWordIndex;
+        mCurrentWordIndex = wordIndex;
+        // If index changed or changing text
+        if (oldWordIndex != mCurrentWordIndex || altText != null) {
+            if (wordIndex == OUT_OF_BOUNDS) {
+                if (mPreviewPopup.isShowing()) {
+                    mHandler.sendMessageDelayed(mHandler
+                            .obtainMessage(MSG_REMOVE_PREVIEW), 60);
+                }
+            } else {
+                CharSequence word = altText != null? altText : mSuggestions.get(wordIndex);
+                mPreviewText.setText(word);
+                mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 
+                        MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+                int wordWidth = (int) (mPaint.measureText(word, 0, word.length()) + X_GAP * 2);
+                final int popupWidth = wordWidth
+                        + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight();
+                final int popupHeight = mPreviewText.getMeasuredHeight();
+                //mPreviewText.setVisibility(INVISIBLE);
+                mPopupPreviewX = mWordX[wordIndex] - mPreviewText.getPaddingLeft() - mScrollX;
+                mPopupPreviewY = - popupHeight;
+                mHandler.removeMessages(MSG_REMOVE_PREVIEW);
+                int [] offsetInWindow = new int[2];
+                getLocationInWindow(offsetInWindow);
+                if (mPreviewPopup.isShowing()) {
+                    mPreviewPopup.update(mPopupPreviewX, mPopupPreviewY + offsetInWindow[1], 
+                            popupWidth, popupHeight);
+                } else {
+                    mPreviewPopup.setWidth(popupWidth);
+                    mPreviewPopup.setHeight(popupHeight);
+                    mPreviewPopup.showAtLocation(this, Gravity.NO_GRAVITY, mPopupPreviewX, 
+                            mPopupPreviewY + offsetInWindow[1]);
+                }
+                mPreviewText.setVisibility(VISIBLE);
+            }
+        }
+    }
+    
+    private void removeHighlight() {
+        mTouchX = OUT_OF_BOUNDS;
+        invalidate();
+    }
+    
+    private void longPressFirstWord() {
+        CharSequence word = mSuggestions.get(0);
+        if (mService.addWordToDictionary(word.toString())) {
+            showPreview(0, getContext().getResources().getString(R.string.added_word, word));
+        }
+    }
+}
diff --git a/src/com/android/inputmethod/latin/CandidateViewContainer.java b/src/com/android/inputmethod/latin/CandidateViewContainer.java
new file mode 100644
index 0000000..e13f273
--- /dev/null
+++ b/src/com/android/inputmethod/latin/CandidateViewContainer.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ * 
+ * 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.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.widget.LinearLayout;
+
+public class CandidateViewContainer extends LinearLayout implements OnTouchListener {
+
+    private View mButtonLeft;
+    private View mButtonRight;
+    private View mButtonLeftLayout;
+    private View mButtonRightLayout;
+    private CandidateView mCandidates;
+    
+    public CandidateViewContainer(Context screen, AttributeSet attrs) {
+        super(screen, attrs);
+    }
+
+    public void initViews() {
+        if (mCandidates == null) {
+            mButtonLeftLayout = findViewById(R.id.candidate_left_parent);
+            mButtonLeft = findViewById(R.id.candidate_left);
+            if (mButtonLeft != null) {
+                mButtonLeft.setOnTouchListener(this);
+            }
+            mButtonRightLayout = findViewById(R.id.candidate_right_parent);
+            mButtonRight = findViewById(R.id.candidate_right);
+            if (mButtonRight != null) {
+                mButtonRight.setOnTouchListener(this);
+            }
+            mCandidates = (CandidateView) findViewById(R.id.candidates);
+        }
+    }
+
+    @Override
+    public void requestLayout() {
+        if (mCandidates != null) {
+            int availableWidth = mCandidates.getWidth();
+            int neededWidth = mCandidates.computeHorizontalScrollRange();
+            int x = mCandidates.getScrollX();
+            boolean leftVisible = x > 0;
+            boolean rightVisible = x + availableWidth < neededWidth;
+            if (mButtonLeftLayout != null) {
+                mButtonLeftLayout.setVisibility(leftVisible ? VISIBLE : GONE);
+            }
+            if (mButtonRightLayout != null) {
+                mButtonRightLayout.setVisibility(rightVisible ? VISIBLE : GONE);
+            }
+        }
+        super.requestLayout();
+    }
+
+    public boolean onTouch(View v, MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            if (v == mButtonRight) {
+                mCandidates.scrollNext();
+            } else if (v == mButtonLeft) {
+                mCandidates.scrollPrev();
+            }
+        }
+        return false;
+    }
+    
+}
diff --git a/src/com/android/inputmethod/latin/Dictionary.java b/src/com/android/inputmethod/latin/Dictionary.java
new file mode 100644
index 0000000..fdf3426
--- /dev/null
+++ b/src/com/android/inputmethod/latin/Dictionary.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ * 
+ * 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;
+
+/**
+ * Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key
+ * strokes.
+ */
+abstract public class Dictionary {
+    
+    /**
+     * Whether or not to replicate the typed word in the suggested list, even if it's valid.
+     */
+    protected static final boolean INCLUDE_TYPED_WORD_IF_VALID = false;
+    
+    /**
+     * The weight to give to a word if it's length is the same as the number of typed characters.
+     */
+    protected static final int FULL_WORD_FREQ_MULTIPLIER = 2;
+    
+    /**
+     * Interface to be implemented by classes requesting words to be fetched from the dictionary.
+     * @see #getWords(WordComposer, WordCallback)
+     */
+    public interface WordCallback {
+        /**
+         * Adds a word to a list of suggestions. The word is expected to be ordered based on
+         * the provided frequency. 
+         * @param word the character array containing the word
+         * @param wordOffset starting offset of the word in the character array
+         * @param wordLength length of valid characters in the character array
+         * @param frequency the frequency of occurence. This is normalized between 1 and 255, but
+         * can exceed those limits
+         * @return true if the word was added, false if no more words are required
+         */
+        boolean addWord(char[] word, int wordOffset, int wordLength, int frequency);
+    }
+
+    /**
+     * Searches for words in the dictionary that match the characters in the composer. Matched 
+     * words are added through the callback object.
+     * @param composer the key sequence to match
+     * @param callback the callback object to send matched words to as possible candidates
+     * @see WordCallback#addWord(char[], int, int)
+     */
+    abstract public void getWords(final WordComposer composer, final WordCallback callback);
+
+    /**
+     * Checks if the given word occurs in the dictionary
+     * @param word the word to search for. The search should be case-insensitive.
+     * @return true if the word exists, false otherwise
+     */
+    abstract public boolean isValidWord(CharSequence word);
+    
+    /**
+     * Compares the contents of the character array with the typed word and returns true if they
+     * are the same.
+     * @param word the array of characters that make up the word
+     * @param length the number of valid characters in the character array
+     * @param typedWord the word to compare with
+     * @return true if they are the same, false otherwise.
+     */
+    protected boolean same(final char[] word, final int length, final CharSequence typedWord) {
+        if (typedWord.length() != length) {
+            return false;
+        }
+        for (int i = 0; i < length; i++) {
+            if (word[i] != typedWord.charAt(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+}
diff --git a/src/com/android/inputmethod/latin/KeyboardSwitcher.java b/src/com/android/inputmethod/latin/KeyboardSwitcher.java
new file mode 100644
index 0000000..b3fcbda
--- /dev/null
+++ b/src/com/android/inputmethod/latin/KeyboardSwitcher.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ * 
+ * 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.inputmethodservice.Keyboard;
+
+import java.util.List;
+
+public class KeyboardSwitcher {
+
+    public static final int MODE_TEXT = 1;
+    public static final int MODE_SYMBOLS = 2;
+    public static final int MODE_PHONE = 3;
+    public static final int MODE_URL = 4;
+    public static final int MODE_EMAIL = 5;
+    public static final int MODE_IM = 6;
+    
+    public static final int MODE_TEXT_QWERTY = 0;
+    public static final int MODE_TEXT_ALPHA = 1;
+    public static final int MODE_TEXT_COUNT = 2;
+    
+    public static final int KEYBOARDMODE_NORMAL = R.id.mode_normal;
+    public static final int KEYBOARDMODE_URL = R.id.mode_url;
+    public static final int KEYBOARDMODE_EMAIL = R.id.mode_email;
+    public static final int KEYBOARDMODE_IM = R.id.mode_im;
+    
+
+    LatinKeyboardView mInputView;
+    LatinIME mContext;
+    
+    private LatinKeyboard mPhoneKeyboard;
+    private LatinKeyboard mPhoneSymbolsKeyboard;
+    private LatinKeyboard mSymbolsKeyboard;
+    private LatinKeyboard mSymbolsShiftedKeyboard;
+    private LatinKeyboard mQwertyKeyboard;
+    private LatinKeyboard mAlphaKeyboard;
+    private LatinKeyboard mUrlKeyboard;
+    private LatinKeyboard mEmailKeyboard;
+    private LatinKeyboard mIMKeyboard;
+    
+    List<Keyboard> mKeyboards;
+    
+    private int mMode;
+    private int mImeOptions;
+    private int mTextMode = MODE_TEXT_QWERTY;
+
+    private int mLastDisplayWidth;
+
+    KeyboardSwitcher(LatinIME context) {
+        mContext = context;
+    }
+
+    void setInputView(LatinKeyboardView inputView) {
+        mInputView = inputView;
+    }
+    
+    void makeKeyboards() {
+        // Configuration change is coming after the keyboard gets recreated. So don't rely on that.
+        // If keyboards have already been made, check if we have a screen width change and 
+        // create the keyboard layouts again at the correct orientation
+        if (mKeyboards != null) {
+            int displayWidth = mContext.getMaxWidth();
+            if (displayWidth == mLastDisplayWidth) return;
+            mLastDisplayWidth = displayWidth;
+        }
+        // Delayed creation when keyboard mode is set.
+        mQwertyKeyboard = null;
+        mAlphaKeyboard = null;
+        mUrlKeyboard = null;
+        mEmailKeyboard = null;
+        mIMKeyboard = null;
+        mPhoneKeyboard = null;
+        mPhoneSymbolsKeyboard = null;
+        mSymbolsKeyboard = null;
+        mSymbolsShiftedKeyboard = null;
+    }
+
+    void setKeyboardMode(int mode, int imeOptions) {
+        mMode = mode;
+        mImeOptions = imeOptions;
+        LatinKeyboard keyboard = (LatinKeyboard) mInputView.getKeyboard();
+        mInputView.setPreviewEnabled(true);
+        switch (mode) {
+            case MODE_TEXT:
+                if (mTextMode == MODE_TEXT_QWERTY) {
+                    if (mQwertyKeyboard == null) {
+                        mQwertyKeyboard = new LatinKeyboard(mContext, R.xml.kbd_qwerty,
+                                KEYBOARDMODE_NORMAL);
+                        mQwertyKeyboard.enableShiftLock();
+                    }
+                    keyboard = mQwertyKeyboard;
+                } else if (mTextMode == MODE_TEXT_ALPHA) {
+                    if (mAlphaKeyboard == null) {
+                        mAlphaKeyboard = new LatinKeyboard(mContext, R.xml.kbd_alpha,
+                                KEYBOARDMODE_NORMAL);
+                        mAlphaKeyboard.enableShiftLock();
+                    }
+                    keyboard = mAlphaKeyboard;
+                }
+                break;
+            case MODE_SYMBOLS:
+                if (mSymbolsKeyboard == null) {
+                    mSymbolsKeyboard = new LatinKeyboard(mContext, R.xml.kbd_symbols);
+                }
+                if (mSymbolsShiftedKeyboard == null) {
+                    mSymbolsShiftedKeyboard = new LatinKeyboard(mContext, R.xml.kbd_symbols_shift);
+                }
+                keyboard = mSymbolsKeyboard;
+                break;
+            case MODE_PHONE:
+                if (mPhoneKeyboard == null) {
+                    mPhoneKeyboard = new LatinKeyboard(mContext, R.xml.kbd_phone);
+                }
+                mInputView.setPhoneKeyboard(mPhoneKeyboard);
+                if (mPhoneSymbolsKeyboard == null) {
+                    mPhoneSymbolsKeyboard = new LatinKeyboard(mContext, R.xml.kbd_phone_symbols);
+                }
+                keyboard = mPhoneKeyboard;
+                mInputView.setPreviewEnabled(false);
+                break;
+            case MODE_URL:
+                if (mUrlKeyboard == null) {
+                    mUrlKeyboard = new LatinKeyboard(mContext, R.xml.kbd_qwerty, KEYBOARDMODE_URL);
+                    mUrlKeyboard.enableShiftLock();
+                }
+                keyboard = mUrlKeyboard;
+                break;
+            case MODE_EMAIL:
+                if (mEmailKeyboard == null) {
+                    mEmailKeyboard = new LatinKeyboard(mContext, R.xml.kbd_qwerty, KEYBOARDMODE_EMAIL);
+                    mEmailKeyboard.enableShiftLock();
+                }
+                keyboard = mEmailKeyboard;
+                break;
+            case MODE_IM:
+                if (mIMKeyboard == null) {
+                    mIMKeyboard = new LatinKeyboard(mContext, R.xml.kbd_qwerty, KEYBOARDMODE_IM);
+                    mIMKeyboard.enableShiftLock();
+                }
+                keyboard = mIMKeyboard;
+                break;
+        }
+        mInputView.setKeyboard(keyboard);
+        keyboard.setShifted(false);
+        keyboard.setShiftLocked(keyboard.isShiftLocked());
+        keyboard.setImeOptions(mContext.getResources(), mMode, imeOptions);
+    }
+
+    int getKeyboardMode() {
+        return mMode;
+    }
+    
+    boolean isTextMode() {
+        return mMode == MODE_TEXT;
+    }
+    
+    int getTextMode() {
+        return mTextMode;
+    }
+    
+    void setTextMode(int position) {
+        if (position < MODE_TEXT_COUNT && position >= 0) {
+            mTextMode = position;
+        }
+        if (isTextMode()) {
+            setKeyboardMode(MODE_TEXT, mImeOptions);
+        }
+    }
+
+    int getTextModeCount() {
+        return MODE_TEXT_COUNT;
+    }
+
+    boolean isAlphabetMode() {
+        Keyboard current = mInputView.getKeyboard();
+        if (current == mQwertyKeyboard
+                || current == mAlphaKeyboard
+                || current == mUrlKeyboard
+                || current == mIMKeyboard
+                || current == mEmailKeyboard) {
+            return true;
+        }
+        return false;
+    }
+
+    void toggleShift() {
+        Keyboard currentKeyboard = mInputView.getKeyboard();
+        if (currentKeyboard == mSymbolsKeyboard) {
+            mSymbolsKeyboard.setShifted(true);
+            mInputView.setKeyboard(mSymbolsShiftedKeyboard);
+            mSymbolsShiftedKeyboard.setShifted(true);
+            mSymbolsShiftedKeyboard.setImeOptions(mContext.getResources(), mMode, mImeOptions);
+        } else if (currentKeyboard == mSymbolsShiftedKeyboard) {
+            mSymbolsShiftedKeyboard.setShifted(false);
+            mInputView.setKeyboard(mSymbolsKeyboard);
+            mSymbolsKeyboard.setShifted(false);
+            mSymbolsKeyboard.setImeOptions(mContext.getResources(), mMode, mImeOptions);
+        }
+    }
+
+    void toggleSymbols() {
+        Keyboard current = mInputView.getKeyboard();
+        if (mSymbolsKeyboard == null) {
+            mSymbolsKeyboard = new LatinKeyboard(mContext, R.xml.kbd_symbols);
+        }
+        if (mSymbolsShiftedKeyboard == null) {
+            mSymbolsShiftedKeyboard = new LatinKeyboard(mContext, R.xml.kbd_symbols_shift);
+        }
+        if (current == mSymbolsKeyboard || current == mSymbolsShiftedKeyboard) {
+            setKeyboardMode(mMode, mImeOptions); // Could be qwerty, alpha, url, email or im
+            return;
+        } else if (current == mPhoneKeyboard) {
+            current = mPhoneSymbolsKeyboard;
+            mPhoneSymbolsKeyboard.setImeOptions(mContext.getResources(), mMode, mImeOptions);
+        } else if (current == mPhoneSymbolsKeyboard) {
+            current = mPhoneKeyboard;
+            mPhoneKeyboard.setImeOptions(mContext.getResources(), mMode, mImeOptions);
+        } else {
+            current = mSymbolsKeyboard;
+            mSymbolsKeyboard.setImeOptions(mContext.getResources(), mMode, mImeOptions);
+        }
+        mInputView.setKeyboard(current);
+        if (current == mSymbolsKeyboard) {
+            current.setShifted(false);
+        }
+    }
+}
diff --git a/src/com/android/inputmethod/latin/LatinIME.java b/src/com/android/inputmethod/latin/LatinIME.java
new file mode 100644
index 0000000..8671bf2
--- /dev/null
+++ b/src/com/android/inputmethod/latin/LatinIME.java
@@ -0,0 +1,1091 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ * 
+ * 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.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Configuration;
+import android.inputmethodservice.InputMethodService;
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.KeyboardView;
+import android.media.AudioManager;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.Vibrator;
+import android.preference.PreferenceManager;
+import android.text.ClipboardManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Input method implementation for Qwerty'ish keyboard.
+ */
+public class LatinIME extends InputMethodService 
+        implements KeyboardView.OnKeyboardActionListener {
+    static final boolean DEBUG = false;
+    static final boolean TRACE = false;
+    
+    private static final String PREF_VIBRATE_ON = "vibrate_on";
+    private static final String PREF_SOUND_ON = "sound_on";
+    private static final String PREF_PROXIMITY_CORRECTION = "hit_correction";
+    private static final String PREF_PREDICTION = "prediction_mode";
+    private static final String PREF_PREDICTION_LANDSCAPE = "prediction_landscape";
+    private static final String PREF_AUTO_CAP = "auto_cap";
+    static final String PREF_TUTORIAL_RUN = "tutorial_run";
+
+    private static final int MSG_UPDATE_SUGGESTIONS = 0;
+    private static final int MSG_CHECK_TUTORIAL = 1;
+    
+    // How many continuous deletes at which to start deleting at a higher speed.
+    private static final int DELETE_ACCELERATE_AT = 20;
+    // Key events coming any faster than this are long-presses.
+    private static final int QUICK_PRESS = 200; 
+    
+    private static final int KEYCODE_ENTER = 10;
+    private static final int KEYCODE_SPACE = ' ';
+
+    // Contextual menu positions
+    private static final int POS_SETTINGS = 0;
+    private static final int POS_METHOD = 1;
+    
+    private LatinKeyboardView mInputView;
+    private CandidateViewContainer mCandidateViewContainer;
+    private CandidateView mCandidateView;
+    private Suggest mSuggest;
+    private CompletionInfo[] mCompletions;
+    
+    private AlertDialog mOptionsDialog;
+    
+    private KeyboardSwitcher mKeyboardSwitcher;
+    
+    private UserDictionary mUserDictionary;
+    
+    private String mLocale;
+
+    private StringBuilder mComposing = new StringBuilder();
+    private WordComposer mWord = new WordComposer();
+    private int mCommittedLength;
+    private boolean mPredicting;
+    private CharSequence mBestWord;
+    private boolean mPredictionOn;
+    private boolean mCompletionOn;
+    private boolean mPasswordMode;
+    private boolean mAutoSpace;
+    private boolean mAutoCorrectOn;
+    private boolean mCapsLock;
+    private long    mLastShiftTime;
+    private boolean mVibrateOn;
+    private boolean mSoundOn;
+    private boolean mProximityCorrection;
+    private int     mCorrectionMode;
+    private boolean mAutoCap;
+    private boolean mAutoPunctuate;
+    private boolean mTutorialShownBefore;
+    // Indicates whether the suggestion strip is to be on in landscape
+    private boolean mShowSuggestInLand;
+    private boolean mJustAccepted;
+    private CharSequence mJustRevertedSeparator;
+    private int mDeleteCount;
+    private long mLastKeyTime;
+    
+    private Tutorial mTutorial;
+
+    private Vibrator mVibrator;
+    private long mVibrateDuration;
+
+    private AudioManager mAudioManager;
+    private final float FX_VOLUME = 1.0f;
+    private boolean mSilentMode;
+
+    private String mWordSeparators;
+    private String mSentenceSeparators;
+    
+    Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE_SUGGESTIONS:
+                    updateSuggestions();
+                    break;
+                case MSG_CHECK_TUTORIAL:
+                    if (!mTutorialShownBefore) {
+                        mTutorial = new Tutorial(mInputView);
+                        mTutorial.start();
+                    }
+                    break;
+            }
+        }
+    };
+
+    @Override public void onCreate() {
+        super.onCreate();
+        //setStatusIcon(R.drawable.ime_qwerty);
+        mKeyboardSwitcher = new KeyboardSwitcher(this);
+        initSuggest(getResources().getConfiguration().locale.toString());
+        
+        mVibrateDuration = getResources().getInteger(R.integer.vibrate_duration_ms);
+        
+        // register to receive ringer mode changes for silent mode
+        IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
+        registerReceiver(mReceiver, filter);
+    }
+    
+    private void initSuggest(String locale) {
+        mLocale = locale;
+        mSuggest = new Suggest(this, R.raw.main);
+        mSuggest.setCorrectionMode(mCorrectionMode);
+        mUserDictionary = new UserDictionary(this);
+        mSuggest.setUserDictionary(mUserDictionary);
+        mWordSeparators = getResources().getString(R.string.word_separators);
+        mSentenceSeparators = getResources().getString(R.string.sentence_separators);
+    }
+    
+    @Override public void onDestroy() {
+        mUserDictionary.close();
+        unregisterReceiver(mReceiver);
+        super.onDestroy();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration conf) {
+        if (!TextUtils.equals(conf.locale.toString(), mLocale)) {
+            initSuggest(conf.locale.toString());
+        }
+        if (!mTutorialShownBefore && mTutorial != null) {
+            mTutorial.close(false);
+        }
+        super.onConfigurationChanged(conf);
+    }
+    
+    @Override
+    public View onCreateInputView() {
+        mInputView = (LatinKeyboardView) getLayoutInflater().inflate(
+                R.layout.input, null);
+        mKeyboardSwitcher.setInputView(mInputView);
+        mKeyboardSwitcher.makeKeyboards();
+        mInputView.setOnKeyboardActionListener(this);
+        mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, 0);
+        return mInputView;
+    }
+
+    @Override
+    public View onCreateCandidatesView() {
+        mKeyboardSwitcher.makeKeyboards();
+        mCandidateViewContainer = (CandidateViewContainer) getLayoutInflater().inflate(
+                R.layout.candidates, null);
+        mCandidateViewContainer.initViews();
+        mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates);
+        mCandidateView.setService(this);
+        setCandidatesViewShown(true);
+        return mCandidateViewContainer;
+    }
+
+    @Override 
+    public void onStartInputView(EditorInfo attribute, boolean restarting) {
+        // In landscape mode, this method gets called without the input view being created.
+        if (mInputView == null) {
+            return;
+        }
+
+        mKeyboardSwitcher.makeKeyboards();
+        
+        TextEntryState.newSession(this);
+        
+        mPredictionOn = false;
+        mCompletionOn = false;
+        mCompletions = null;
+        mCapsLock = false;
+        switch (attribute.inputType&EditorInfo.TYPE_MASK_CLASS) {
+            case EditorInfo.TYPE_CLASS_NUMBER:
+            case EditorInfo.TYPE_CLASS_DATETIME:
+                mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT,
+                        attribute.imeOptions);
+                mKeyboardSwitcher.toggleSymbols();
+                break;
+            case EditorInfo.TYPE_CLASS_PHONE:
+                mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_PHONE,
+                        attribute.imeOptions);
+                break;
+            case EditorInfo.TYPE_CLASS_TEXT:
+                mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT,
+                        attribute.imeOptions);
+                //startPrediction();
+                mPredictionOn = true;
+                // Make sure that passwords are not displayed in candidate view
+                int variation = attribute.inputType &  EditorInfo.TYPE_MASK_VARIATION;
+                if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD ||
+                        variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ) {
+                    mPasswordMode = true;
+                    mPredictionOn = false;
+                } else {
+                    mPasswordMode = false;
+                }
+                if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+                        || variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) {
+                    mAutoSpace = false;
+                } else {
+                    mAutoSpace = true;
+                }
+                if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {
+                    mPredictionOn = false;
+                    mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_EMAIL,
+                            attribute.imeOptions);
+                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) {
+                    mPredictionOn = false;
+                    mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_URL,
+                            attribute.imeOptions);
+                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
+                    mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_IM,
+                            attribute.imeOptions);
+                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) {
+                    mPredictionOn = false;
+                }
+                if ((attribute.inputType&EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
+                    mPredictionOn = false;
+                    mCompletionOn = true && isFullscreenMode();
+                }
+                updateShiftKeyState(attribute);
+                break;
+            default:
+                mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT,
+                        attribute.imeOptions);
+                updateShiftKeyState(attribute);
+        }
+        mInputView.closing();
+        mComposing.setLength(0);
+        mPredicting = false;
+        mDeleteCount = 0;
+        setCandidatesViewShown(false);
+        if (mCandidateView != null) mCandidateView.setSuggestions(null, false, false, false);
+        loadSettings();
+        mInputView.setProximityCorrectionEnabled(mProximityCorrection);
+        if (mSuggest != null) {
+            mSuggest.setCorrectionMode(mCorrectionMode);
+        }
+        if (!mTutorialShownBefore && mTutorial == null) {
+            mHandler.sendEmptyMessageDelayed(MSG_CHECK_TUTORIAL, 1000);
+        }
+        mPredictionOn = mPredictionOn && mCorrectionMode > 0;
+        if (TRACE) Debug.startMethodTracing("latinime");
+    }
+
+    @Override
+    public void onFinishInput() {
+        super.onFinishInput();
+
+        if (mInputView != null) {
+            mInputView.closing();
+        }
+        if (!mTutorialShownBefore && mTutorial != null) {
+            mTutorial.close(false);
+        }        
+    }
+
+    @Override
+    public void onUpdateSelection(int oldSelStart, int oldSelEnd,
+            int newSelStart, int newSelEnd,
+            int candidatesStart, int candidatesEnd) {
+        super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
+                candidatesStart, candidatesEnd);
+        // If the current selection in the text view changes, we should
+        // clear whatever candidate text we have.
+        if (mComposing.length() > 0 && mPredicting && (newSelStart != candidatesEnd
+                || newSelEnd != candidatesEnd)) {
+            mComposing.setLength(0);
+            mPredicting = false;
+            updateSuggestions();
+            TextEntryState.reset();
+            InputConnection ic = getCurrentInputConnection();
+            if (ic != null) {
+                ic.finishComposingText();
+            }
+        } else if (!mPredicting && !mJustAccepted
+                && TextEntryState.getState() == TextEntryState.STATE_ACCEPTED_DEFAULT) {
+            TextEntryState.reset();
+        }
+        mJustAccepted = false;
+    }
+
+    @Override
+    public void hideWindow() {
+        if (TRACE) Debug.stopMethodTracing();
+        super.hideWindow();
+        TextEntryState.endSession();
+    }
+
+    @Override
+    public void onDisplayCompletions(CompletionInfo[] completions) {
+        if (false) {
+            Log.i("foo", "Received completions:");
+            for (int i=0; i<(completions != null ? completions.length : 0); i++) {
+                Log.i("foo", "  #" + i + ": " + completions[i]);
+            }
+        }
+        if (mCompletionOn) {
+            mCompletions = completions;
+            if (completions == null) {
+                mCandidateView.setSuggestions(null, false, false, false);
+                return;
+            }
+            
+            List<CharSequence> stringList = new ArrayList<CharSequence>();
+            for (int i=0; i<(completions != null ? completions.length : 0); i++) {
+                CompletionInfo ci = completions[i];
+                if (ci != null) stringList.add(ci.getText());
+            }
+            //CharSequence typedWord = mWord.getTypedWord();
+            mCandidateView.setSuggestions(stringList, true, true, true);
+            mBestWord = null;
+            setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn);
+        }
+    }
+
+    @Override
+    public void setCandidatesViewShown(boolean shown) {
+        // TODO: Remove this if we support candidates with hard keyboard
+        if (onEvaluateInputViewShown()) {
+            super.setCandidatesViewShown(shown);
+        }
+    }
+    
+    @Override
+    public void onComputeInsets(InputMethodService.Insets outInsets) {
+        super.onComputeInsets(outInsets);
+        outInsets.contentTopInsets = outInsets.visibleTopInsets;
+    }
+    
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_BACK:
+                if (event.getRepeatCount() == 0 && mInputView != null) {
+                    if (mInputView.handleBack()) {
+                        return true;
+                    } else if (!mTutorialShownBefore && mTutorial != null) {
+                        mTutorial.close(true);
+                    }
+                }
+                break;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+            case KeyEvent.KEYCODE_DPAD_UP:
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                // Enable shift key and DPAD to do selections
+                if (mInputView != null && mInputView.isShown() && mInputView.isShifted()) {
+                    event = new KeyEvent(event.getDownTime(), event.getEventTime(), 
+                            event.getAction(), event.getKeyCode(), event.getRepeatCount(),
+                            event.getDeviceId(), event.getScanCode(),
+                            KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON);
+                    InputConnection ic = getCurrentInputConnection();
+                    if (ic != null) ic.sendKeyEvent(event);
+                    return true;
+                }
+                break;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    private void commitTyped(InputConnection inputConnection) {
+        if (mPredicting) {
+            mPredicting = false;
+            if (mComposing.length() > 0) {
+                if (inputConnection != null) {
+                    inputConnection.commitText(mComposing, 1);
+                }
+                mCommittedLength = mComposing.length();
+                TextEntryState.acceptedTyped(mComposing);
+            }
+            updateSuggestions();
+        }
+    }
+
+    public void updateShiftKeyState(EditorInfo attr) {
+        InputConnection ic = getCurrentInputConnection();
+        if (attr != null && mInputView != null && mKeyboardSwitcher.isAlphabetMode()
+                && ic != null) {
+            int caps = 0;
+            EditorInfo ei = getCurrentInputEditorInfo();
+            if (mAutoCap && ei != null && ei.inputType != EditorInfo.TYPE_NULL) {
+                caps = ic.getCursorCapsMode(attr.inputType);
+            }
+            mInputView.setShifted(mCapsLock || caps != 0);
+        }
+    }
+    
+    private void swapPunctuationAndSpace() {
+        final InputConnection ic = getCurrentInputConnection();
+        if (ic == null) return;
+        CharSequence lastTwo = ic.getTextBeforeCursor(2, 0);
+        if (lastTwo != null && lastTwo.length() == 2
+                && lastTwo.charAt(0) == KEYCODE_SPACE && isSentenceSeparator(lastTwo.charAt(1))) {
+            ic.beginBatchEdit();
+            ic.deleteSurroundingText(2, 0);
+            ic.commitText(lastTwo.charAt(1) + " ", 1);
+            ic.endBatchEdit();
+            updateShiftKeyState(getCurrentInputEditorInfo());
+        }
+    }
+    
+    private void doubleSpace() {
+        //if (!mAutoPunctuate) return;
+        if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
+        final InputConnection ic = getCurrentInputConnection();
+        if (ic == null) return;
+        CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
+        if (lastThree != null && lastThree.length() == 3
+                && Character.isLetterOrDigit(lastThree.charAt(0))
+                && lastThree.charAt(1) == KEYCODE_SPACE && lastThree.charAt(2) == KEYCODE_SPACE) {
+            ic.beginBatchEdit();
+            ic.deleteSurroundingText(2, 0);
+            ic.commitText(". ", 1);
+            ic.endBatchEdit();
+            updateShiftKeyState(getCurrentInputEditorInfo());
+        }
+    }
+    
+    public boolean addWordToDictionary(String word) {
+        mUserDictionary.addWord(word, 128);
+        return true;
+    }
+
+    private boolean isAlphabet(int code) {
+        if (Character.isLetter(code)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+    
+    // Implementation of KeyboardViewListener
+
+    public void onKey(int primaryCode, int[] keyCodes) {
+        long when = SystemClock.uptimeMillis();
+        if (primaryCode != Keyboard.KEYCODE_DELETE || 
+                when > mLastKeyTime + QUICK_PRESS) {
+            mDeleteCount = 0;
+        }
+        mLastKeyTime = when;
+        switch (primaryCode) {
+            case Keyboard.KEYCODE_DELETE:
+                handleBackspace();
+                mDeleteCount++;
+                break;
+            case Keyboard.KEYCODE_SHIFT:
+                handleShift();
+                break;
+            case Keyboard.KEYCODE_CANCEL:
+                if (mOptionsDialog == null || !mOptionsDialog.isShowing()) {
+                    handleClose();
+                }
+                break;
+            case LatinKeyboardView.KEYCODE_OPTIONS:
+                showOptionsMenu();
+                break;
+            case LatinKeyboardView.KEYCODE_SHIFT_LONGPRESS:
+                if (mCapsLock) {
+                    handleShift();
+                } else {
+                    toggleCapsLock();
+                }
+                break;
+            case Keyboard.KEYCODE_MODE_CHANGE:
+                changeKeyboardMode();
+                break;
+            default:
+                if (isWordSeparator(primaryCode)) {
+                    handleSeparator(primaryCode);
+                } else {
+                    handleCharacter(primaryCode, keyCodes);
+                }
+                // Cancel the just reverted state
+                mJustRevertedSeparator = null;
+        }
+    }
+    
+    public void onText(CharSequence text) {
+        InputConnection ic = getCurrentInputConnection();
+        if (ic == null) return;
+        ic.beginBatchEdit();
+        if (mPredicting) {
+            commitTyped(ic);
+        }
+        ic.commitText(text, 1);
+        ic.endBatchEdit();
+        updateShiftKeyState(getCurrentInputEditorInfo());
+        mJustRevertedSeparator = null;
+    }
+
+    private void handleBackspace() {
+        boolean deleteChar = false;
+        InputConnection ic = getCurrentInputConnection();
+        if (ic == null) return;
+        if (mPredicting) {
+            final int length = mComposing.length();
+            if (length > 0) {
+                mComposing.delete(length - 1, length);
+                mWord.deleteLast();
+                ic.setComposingText(mComposing, 1);
+                if (mComposing.length() == 0) {
+                    mPredicting = false;
+                }
+                postUpdateSuggestions();
+            } else {
+                ic.deleteSurroundingText(1, 0);
+            }
+        } else {
+            //getCurrentInputConnection().deleteSurroundingText(1, 0);
+            deleteChar = true;
+            //sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+        }
+        updateShiftKeyState(getCurrentInputEditorInfo());
+        TextEntryState.backspace();
+        if (TextEntryState.getState() == TextEntryState.STATE_UNDO_COMMIT) {
+            revertLastWord(deleteChar);
+            return;
+        } else if (deleteChar) {
+            sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+            if (mDeleteCount > DELETE_ACCELERATE_AT) {
+                sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+            }
+        }
+        mJustRevertedSeparator = null;
+    }
+
+    private void handleShift() {
+        Keyboard currentKeyboard = mInputView.getKeyboard();
+        if (mKeyboardSwitcher.isAlphabetMode()) {
+            // Alphabet keyboard
+            checkToggleCapsLock();
+            mInputView.setShifted(mCapsLock || !mInputView.isShifted());
+        } else {
+            mKeyboardSwitcher.toggleShift();
+        }
+    }
+    
+    private void handleCharacter(int primaryCode, int[] keyCodes) {
+        if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) {
+            if (!mPredicting) {
+                mPredicting = true;
+                mComposing.setLength(0);
+                mWord.reset();
+            }
+        }
+        if (mInputView.isShifted()) {
+            primaryCode = Character.toUpperCase(primaryCode);
+        }
+        if (mPredicting) {
+            if (mInputView.isShifted() && mComposing.length() == 0) {
+                mWord.setCapitalized(true);
+            }
+            mComposing.append((char) primaryCode);
+            mWord.add(primaryCode, keyCodes);
+            InputConnection ic = getCurrentInputConnection();
+            if (ic != null) {
+                ic.setComposingText(mComposing, 1);
+            }
+            postUpdateSuggestions();
+        } else {
+            sendKeyChar((char)primaryCode);
+        }
+        updateShiftKeyState(getCurrentInputEditorInfo());
+        measureCps();
+        TextEntryState.typedCharacter((char) primaryCode, isWordSeparator(primaryCode));
+    }
+
+    private void handleSeparator(int primaryCode) {
+        boolean pickedDefault = false;
+        // Handle separator
+        InputConnection ic = getCurrentInputConnection();
+        if (ic != null) {
+            ic.beginBatchEdit();
+        }
+        if (mPredicting) {
+            // In certain languages where single quote is a separator, it's better
+            // not to auto correct, but accept the typed word. For instance, 
+            // in Italian dov' should not be expanded to dove' because the elision
+            // requires the last vowel to be removed.
+            if (mAutoCorrectOn && primaryCode != '\'' && 
+                    (mJustRevertedSeparator == null 
+                            || mJustRevertedSeparator.length() == 0 
+                            || mJustRevertedSeparator.charAt(0) != primaryCode)) {
+                pickDefaultSuggestion();
+                pickedDefault = true;
+            } else {
+                commitTyped(ic);
+            }
+        }
+        sendKeyChar((char)primaryCode);
+        TextEntryState.typedCharacter((char) primaryCode, true);
+        if (TextEntryState.getState() == TextEntryState.STATE_PUNCTUATION_AFTER_ACCEPTED 
+                && primaryCode != KEYCODE_ENTER) {
+            swapPunctuationAndSpace();
+        } else if (isPredictionOn() && primaryCode == ' ') { 
+        //else if (TextEntryState.STATE_SPACE_AFTER_ACCEPTED) {
+            doubleSpace();
+        }
+        if (pickedDefault && mBestWord != null) {
+            TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord);
+        }
+        updateShiftKeyState(getCurrentInputEditorInfo());
+        if (ic != null) {
+            ic.endBatchEdit();
+        }
+    }
+    
+    private void handleClose() {
+        commitTyped(getCurrentInputConnection());
+        if (!mTutorialShownBefore && mTutorial != null) {
+            mTutorial.close(true);
+        }
+        requestHideSelf(0);
+        mInputView.closing();
+        TextEntryState.endSession();
+    }
+
+    private void checkToggleCapsLock() {
+        if (mInputView.getKeyboard().isShifted()) {
+            toggleCapsLock();
+        }
+    }
+    
+    private void toggleCapsLock() {
+        mCapsLock = !mCapsLock;
+        if (mKeyboardSwitcher.isAlphabetMode()) {
+            ((LatinKeyboard) mInputView.getKeyboard()).setShiftLocked(mCapsLock);
+        }
+    }
+
+    private void postUpdateSuggestions() {
+        mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SUGGESTIONS), 100);
+    }
+    
+    private boolean isPredictionOn() {
+        boolean predictionOn = mPredictionOn;
+        //if (isFullscreenMode()) predictionOn &= mPredictionLandscape;
+        return predictionOn;
+    }
+    
+    private boolean isCandidateStripVisible() {
+        boolean visible = isPredictionOn() &&
+                (!isFullscreenMode() ||
+                 mCorrectionMode == Suggest.CORRECTION_FULL ||
+                 mShowSuggestInLand);
+        return visible;
+    }
+
+    private void updateSuggestions() {
+        // Check if we have a suggestion engine attached.
+        if (mSuggest == null || !isPredictionOn()) {
+            return;
+        }
+        
+        if (!mPredicting) {
+            mCandidateView.setSuggestions(null, false, false, false);
+            return;
+        }
+
+        List<CharSequence> stringList = mSuggest.getSuggestions(mInputView, mWord, false);
+        boolean correctionAvailable = mSuggest.hasMinimalCorrection();
+        //|| mCorrectionMode == mSuggest.CORRECTION_FULL;
+        CharSequence typedWord = mWord.getTypedWord();
+        // If we're in basic correct
+        boolean typedWordValid = mSuggest.isValidWord(typedWord);
+        if (mCorrectionMode == Suggest.CORRECTION_FULL) {
+            correctionAvailable |= typedWordValid;
+        }
+        
+        mCandidateView.setSuggestions(stringList, false, typedWordValid, correctionAvailable); 
+        if (stringList.size() > 0) {
+            if (correctionAvailable && !typedWordValid && stringList.size() > 1) {
+                mBestWord = stringList.get(1);
+            } else {
+                mBestWord = typedWord;
+            }
+        } else {
+            mBestWord = null;
+        }
+        setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn);
+    }
+
+    private void pickDefaultSuggestion() {
+        // Complete any pending candidate query first
+        if (mHandler.hasMessages(MSG_UPDATE_SUGGESTIONS)) {
+            mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
+            updateSuggestions();
+        }
+        if (mBestWord != null) {
+            TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord);
+            mJustAccepted = true;
+            pickSuggestion(mBestWord);
+        }
+    }
+
+    public void pickSuggestionManually(int index, CharSequence suggestion) {
+        if (mCompletionOn && mCompletions != null && index >= 0
+                && index < mCompletions.length) {
+            CompletionInfo ci = mCompletions[index];
+            InputConnection ic = getCurrentInputConnection();
+            if (ic != null) {
+                ic.commitCompletion(ci);
+            }
+            mCommittedLength = suggestion.length();
+            if (mCandidateView != null) {
+                mCandidateView.clear();
+            }
+            updateShiftKeyState(getCurrentInputEditorInfo());
+            return;
+        }
+        pickSuggestion(suggestion);
+        TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion);
+        // Follow it with a space
+        if (mAutoSpace) {
+            sendSpace();
+        }
+        // Fool the state watcher so that a subsequent backspace will not do a revert
+        TextEntryState.typedCharacter((char) KEYCODE_SPACE, true);
+    }
+    
+    private void pickSuggestion(CharSequence suggestion) {
+        if (mCapsLock) {
+            suggestion = suggestion.toString().toUpperCase();
+        } else if (preferCapitalization() 
+                || (mKeyboardSwitcher.isAlphabetMode() && mInputView.isShifted())) {
+            suggestion = Character.toUpperCase(suggestion.charAt(0)) 
+                    + suggestion.subSequence(1, suggestion.length()).toString();
+        }
+        InputConnection ic = getCurrentInputConnection();
+        if (ic != null) {
+            ic.commitText(suggestion, 1);
+        }
+        mPredicting = false;
+        mCommittedLength = suggestion.length();
+        if (mCandidateView != null) {
+            mCandidateView.setSuggestions(null, false, false, false);
+        }
+        updateShiftKeyState(getCurrentInputEditorInfo());
+    }
+
+    private boolean isCursorTouchingWord() {
+        InputConnection ic = getCurrentInputConnection();
+        if (ic == null) return false;
+        CharSequence toLeft = ic.getTextBeforeCursor(1, 0);
+        CharSequence toRight = ic.getTextAfterCursor(1, 0);
+        if (!TextUtils.isEmpty(toLeft)
+                && !isWordSeparator(toLeft.charAt(0))) {
+            return true;
+        }
+        if (!TextUtils.isEmpty(toRight) 
+                && !isWordSeparator(toRight.charAt(0))) {
+            return true;
+        }
+        return false;
+    }
+    
+    public void revertLastWord(boolean deleteChar) {
+        final int length = mComposing.length();
+        if (!mPredicting && length > 0) {
+            final InputConnection ic = getCurrentInputConnection();
+            mPredicting = true;
+            ic.beginBatchEdit();
+            mJustRevertedSeparator = ic.getTextBeforeCursor(1, 0);
+            if (deleteChar) ic.deleteSurroundingText(1, 0);
+            int toDelete = mCommittedLength;
+            CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
+            if (toTheLeft != null && toTheLeft.length() > 0 
+                    && isWordSeparator(toTheLeft.charAt(0))) {
+                toDelete--;
+            }
+            ic.deleteSurroundingText(toDelete, 0);
+            ic.setComposingText(mComposing, 1);
+            TextEntryState.backspace();
+            ic.endBatchEdit();
+            postUpdateSuggestions();
+        } else {
+            sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+            mJustRevertedSeparator = null;
+        }
+    }
+
+    protected String getWordSeparators() {
+        return mWordSeparators;
+    }
+    
+    public boolean isWordSeparator(int code) {
+        String separators = getWordSeparators();
+        return separators.contains(String.valueOf((char)code));
+    }
+
+    public boolean isSentenceSeparator(int code) {
+        return mSentenceSeparators.contains(String.valueOf((char)code));
+    }
+
+    private void sendSpace() {
+        sendKeyChar((char)KEYCODE_SPACE);
+        updateShiftKeyState(getCurrentInputEditorInfo());
+        //onKey(KEY_SPACE[0], KEY_SPACE);
+    }
+
+    public boolean preferCapitalization() {
+        return mWord.isCapitalized();
+    }
+
+    public void swipeRight() {
+        if (LatinKeyboardView.DEBUG_AUTO_PLAY) {
+            ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE));
+            CharSequence text = cm.getText();
+            if (!TextUtils.isEmpty(text)) {
+                mInputView.startPlaying(text.toString());
+            }
+        }
+//        if (mAutoCorrectOn) {
+//            commitTyped(getCurrentInputConnection());
+//        } else if (mPredicting) {
+//            pickDefaultSuggestion();
+//        }
+//        if (mAutoSpace) {
+//            sendSpace();
+//        }
+    }
+    
+    public void swipeLeft() {
+        //handleBackspace();
+    }
+
+    public void swipeDown() {
+        //handleClose();
+    }
+
+    public void swipeUp() {
+        //launchSettings();
+    }
+
+    public void onPress(int primaryCode) {
+        vibrate();
+        playKeyClick(primaryCode);
+    }
+
+    public void onRelease(int primaryCode) {
+        //vibrate();
+    }
+
+    // receive ringer mode changes to detect silent mode
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            updateRingerMode();
+        }
+    };
+
+    // update flags for silent mode
+    private void updateRingerMode() {
+        if (mAudioManager == null) {
+            mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        }
+        if (mAudioManager != null) {
+            mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
+        }
+    }
+
+    private void playKeyClick(int primaryCode) {
+        // if mAudioManager is null, we don't have the ringer state yet
+        // mAudioManager will be set by updateRingerMode
+        if (mAudioManager == null) {
+            if (mInputView != null) {
+                updateRingerMode();
+            }
+        }
+        if (mSoundOn && !mSilentMode) {
+            // FIXME: Volume and enable should come from UI settings
+            // FIXME: These should be triggered after auto-repeat logic
+            int sound = AudioManager.FX_KEYPRESS_STANDARD;
+            switch (primaryCode) {
+                case Keyboard.KEYCODE_DELETE:
+                    sound = AudioManager.FX_KEYPRESS_DELETE;
+                    break;
+                case KEYCODE_ENTER:
+                    sound = AudioManager.FX_KEYPRESS_RETURN;
+                    break;
+                case KEYCODE_SPACE:
+                    sound = AudioManager.FX_KEYPRESS_SPACEBAR;
+                    break;
+            }
+            mAudioManager.playSoundEffect(sound, FX_VOLUME);
+        }
+    }
+
+    private void vibrate() {
+        if (!mVibrateOn) {
+            return;
+        }
+        if (mVibrator == null) {
+            mVibrator = new Vibrator();
+        }
+        mVibrator.vibrate(mVibrateDuration);
+    }
+
+    private void launchSettings() {
+        handleClose();
+        Intent intent = new Intent();
+        intent.setClass(LatinIME.this, LatinIMESettings.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(intent);
+    }
+
+    private void loadSettings() {
+        // Get the settings preferences
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
+        mProximityCorrection = sp.getBoolean(PREF_PROXIMITY_CORRECTION, true);
+        mVibrateOn = sp.getBoolean(PREF_VIBRATE_ON, true);
+        mSoundOn = sp.getBoolean(PREF_SOUND_ON, false);
+        String predictionBasic = getString(R.string.prediction_basic);
+        String mode = sp.getString(PREF_PREDICTION, predictionBasic);
+        if (mode.equals(getString(R.string.prediction_full))) {
+            mCorrectionMode = 2;
+        } else if (mode.equals(predictionBasic)) {
+            mCorrectionMode = 1;
+        } else {
+            mCorrectionMode = 0;
+        }
+        mAutoCorrectOn = mSuggest != null && mCorrectionMode > 0;
+        
+        mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true);
+        //mAutoPunctuate = sp.getBoolean(PREF_AUTO_PUNCTUATE, mCorrectionMode > 0);
+        mShowSuggestInLand = !sp.getBoolean(PREF_PREDICTION_LANDSCAPE, false);
+        mTutorialShownBefore = sp.getBoolean(PREF_TUTORIAL_RUN, false);
+    }
+
+    private void showOptionsMenu() {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setCancelable(true);
+        builder.setIcon(R.drawable.ic_dialog_keyboard);
+        builder.setNegativeButton(android.R.string.cancel, null);
+        CharSequence itemSettings = getString(R.string.english_ime_settings);
+        CharSequence itemInputMethod = getString(com.android.internal.R.string.inputMethod);
+        builder.setItems(new CharSequence[] {
+                itemSettings, itemInputMethod},
+                new DialogInterface.OnClickListener() {
+
+            public void onClick(DialogInterface di, int position) {
+                di.dismiss();
+                switch (position) {
+                    case POS_SETTINGS:
+                        launchSettings();
+                        break;
+                    case POS_METHOD:
+                        InputMethodManager.getInstance(LatinIME.this).showInputMethodPicker();
+                        break;
+                }
+            }
+        });
+        builder.setTitle(getResources().getString(R.string.english_ime_name));
+        mOptionsDialog = builder.create();
+        Window window = mOptionsDialog.getWindow();
+        WindowManager.LayoutParams lp = window.getAttributes();
+        lp.token = mInputView.getWindowToken();
+        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+        window.setAttributes(lp);
+        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+        mOptionsDialog.show();
+    }
+
+    private void changeKeyboardMode() {
+        mKeyboardSwitcher.toggleSymbols();
+        if (mCapsLock && mKeyboardSwitcher.isAlphabetMode()) {
+            ((LatinKeyboard) mInputView.getKeyboard()).setShiftLocked(mCapsLock);
+        }
+
+        updateShiftKeyState(getCurrentInputEditorInfo());
+    }
+    
+    @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+        super.dump(fd, fout, args);
+        
+        final Printer p = new PrintWriterPrinter(fout);
+        p.println("LatinIME state :");
+        p.println("  Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode());
+        p.println("  mCapsLock=" + mCapsLock);
+        p.println("  mComposing=" + mComposing.toString());
+        p.println("  mPredictionOn=" + mPredictionOn);
+        p.println("  mCorrectionMode=" + mCorrectionMode);
+        p.println("  mPredicting=" + mPredicting);
+        p.println("  mAutoCorrectOn=" + mAutoCorrectOn);
+        p.println("  mAutoSpace=" + mAutoSpace);
+        p.println("  mCompletionOn=" + mCompletionOn);
+        p.println("  TextEntryState.state=" + TextEntryState.getState());
+        p.println("  mSoundOn=" + mSoundOn);
+        p.println("  mVibrateOn=" + mVibrateOn);
+    }
+    
+    
+    private static final int[] KEY_SPACE = { KEYCODE_SPACE };
+    
+    
+    // Characters per second measurement
+    
+    private static final boolean PERF_DEBUG = false;
+    private long mLastCpsTime;
+    private static final int CPS_BUFFER_SIZE = 16;
+    private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE];
+    private int mCpsIndex;
+    
+    private void measureCps() {
+        if (!LatinIME.PERF_DEBUG) return;
+        long now = System.currentTimeMillis();
+        if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial
+        mCpsIntervals[mCpsIndex] = now - mLastCpsTime;
+        mLastCpsTime = now;
+        mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE;
+        long total = 0;
+        for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i];
+        System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total));
+    }
+    
+}
+
+
+
diff --git a/src/com/android/inputmethod/latin/LatinIMESettings.java b/src/com/android/inputmethod/latin/LatinIMESettings.java
new file mode 100644
index 0000000..2c23263
--- /dev/null
+++ b/src/com/android/inputmethod/latin/LatinIMESettings.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ * 
+ * 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.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+
+public class LatinIMESettings extends PreferenceActivity 
+        implements OnSharedPreferenceChangeListener{
+    
+    private static final String CORRECTION_MODE_KEY = "prediction_mode";
+    private static final String PREDICTION_SETTINGS_KEY = "prediction_settings";
+    private static final String PREDICTION_LANDSCAPE_KEY = "prediction_landscape";
+    
+    private ListPreference mCorrectionMode;
+    private PreferenceGroup mPredictionSettings;
+    private Preference mPredictionLandscape;
+    
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        addPreferencesFromResource(R.xml.prefs);
+        mCorrectionMode = (ListPreference) findPreference(CORRECTION_MODE_KEY);
+        mPredictionSettings = (PreferenceGroup) findPreference(PREDICTION_SETTINGS_KEY);
+        mPredictionLandscape = findPreference(PREDICTION_LANDSCAPE_KEY);
+        updatePredictionSettings();
+        getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+    }
+    
+    @Override
+    protected void onDestroy() {
+        getPreferenceScreen().getSharedPreferences()
+                .unregisterOnSharedPreferenceChangeListener(this);
+        super.onDestroy();
+    }
+    
+    private void updatePredictionSettings() {
+        if (mCorrectionMode != null && mPredictionSettings != null) {
+            String correctionMode = mCorrectionMode.getValue();
+            if (correctionMode.equals(getResources().getString(R.string.prediction_none))) {
+                mPredictionSettings.setEnabled(false);
+            } else {
+                mPredictionSettings.setEnabled(true);
+                boolean suggestionsInLandscape = 
+                    !correctionMode.equals(getResources().getString(R.string.prediction_full));
+                mPredictionLandscape.setEnabled(suggestionsInLandscape);
+            }
+        }
+    }
+
+    public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
+        if (key.equals(CORRECTION_MODE_KEY)) {
+            updatePredictionSettings();
+        }
+    }
+}
diff --git a/src/com/android/inputmethod/latin/LatinKeyboard.java b/src/com/android/inputmethod/latin/LatinKeyboard.java
new file mode 100644
index 0000000..94b72b8
--- /dev/null
+++ b/src/com/android/inputmethod/latin/LatinKeyboard.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ * 
+ * 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.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.inputmethodservice.Keyboard;
+import android.view.inputmethod.EditorInfo;
+
+public class LatinKeyboard extends Keyboard {
+
+    private Drawable mShiftLockIcon;
+    private Drawable mShiftLockPreviewIcon;
+    private Drawable mOldShiftIcon;
+    private Drawable mOldShiftPreviewIcon;
+    private Key mShiftKey;
+    private Key mEnterKey;
+    
+    private static final int SHIFT_OFF = 0;
+    private static final int SHIFT_ON = 1;
+    private static final int SHIFT_LOCKED = 2;
+    
+    private int mShiftState = SHIFT_OFF;
+    
+    public LatinKeyboard(Context context, int xmlLayoutResId) {
+        this(context, xmlLayoutResId, 0);
+    }
+
+    public LatinKeyboard(Context context, int xmlLayoutResId, int mode) {
+        super(context, xmlLayoutResId, mode);
+        mShiftLockIcon = context.getResources()
+                .getDrawable(R.drawable.sym_keyboard_shift_locked);
+        mShiftLockPreviewIcon = context.getResources()
+                .getDrawable(R.drawable.sym_keyboard_feedback_shift_locked);
+        mShiftLockPreviewIcon.setBounds(0, 0, 
+                mShiftLockPreviewIcon.getIntrinsicWidth(),
+                mShiftLockPreviewIcon.getIntrinsicHeight());
+    }
+
+    public LatinKeyboard(Context context, int layoutTemplateResId, 
+            CharSequence characters, int columns, int horizontalPadding) {
+        super(context, layoutTemplateResId, characters, columns, horizontalPadding);
+    }
+
+    @Override
+    protected Key createKeyFromXml(Resources res, Row parent, int x, int y, 
+            XmlResourceParser parser) {
+        Key key = new LatinKey(res, parent, x, y, parser);
+        if (key.codes[0] == 10) {
+            mEnterKey = key;
+        }
+        return key;
+    }
+    
+    void setImeOptions(Resources res, int mode, int options) {
+        if (mEnterKey != null) {
+            // Reset some of the rarely used attributes.
+            mEnterKey.popupCharacters = null;
+            mEnterKey.popupResId = 0;
+            mEnterKey.text = null;
+            switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) {
+                case EditorInfo.IME_ACTION_GO:
+                    mEnterKey.iconPreview = null;
+                    mEnterKey.icon = null;
+                    mEnterKey.label = res.getText(R.string.label_go_key);
+                    break;
+                case EditorInfo.IME_ACTION_NEXT:
+                    mEnterKey.iconPreview = null;
+                    mEnterKey.icon = null;
+                    mEnterKey.label = res.getText(R.string.label_next_key);
+                    break;
+                case EditorInfo.IME_ACTION_DONE:
+                    mEnterKey.iconPreview = null;
+                    mEnterKey.icon = null;
+                    mEnterKey.label = res.getText(R.string.label_done_key);
+                    break;
+                case EditorInfo.IME_ACTION_SEARCH:
+                    mEnterKey.iconPreview = res.getDrawable(
+                            R.drawable.sym_keyboard_feedback_search);
+                    mEnterKey.icon = res.getDrawable(
+                            R.drawable.sym_keyboard_search);
+                    mEnterKey.label = null;
+                    break;
+                case EditorInfo.IME_ACTION_SEND:
+                    mEnterKey.iconPreview = null;
+                    mEnterKey.icon = null;
+                    mEnterKey.label = res.getText(R.string.label_send_key);
+                    break;
+                default:
+                    if (mode == KeyboardSwitcher.MODE_IM) {
+                        mEnterKey.icon = null;
+                        mEnterKey.iconPreview = null;
+                        mEnterKey.label = ":-)";
+                        mEnterKey.text = ":-) ";
+                        mEnterKey.popupResId = R.xml.popup_smileys;
+                    } else {
+                        mEnterKey.iconPreview = res.getDrawable(
+                                R.drawable.sym_keyboard_feedback_return);
+                        mEnterKey.icon = res.getDrawable(
+                                R.drawable.sym_keyboard_return);
+                        mEnterKey.label = null;
+                    }
+                    break;
+            }
+            // Set the initial size of the preview icon
+            if (mEnterKey.iconPreview != null) {
+                mEnterKey.iconPreview.setBounds(0, 0, 
+                        mEnterKey.iconPreview.getIntrinsicWidth(),
+                        mEnterKey.iconPreview.getIntrinsicHeight());
+            }
+        }
+    }
+    
+    void enableShiftLock() {
+        int index = getShiftKeyIndex();
+        if (index >= 0) {
+            mShiftKey = getKeys().get(index);
+            if (mShiftKey instanceof LatinKey) {
+                ((LatinKey)mShiftKey).enableShiftLock();
+            }
+            mOldShiftIcon = mShiftKey.icon;
+            mOldShiftPreviewIcon = mShiftKey.iconPreview;
+        }
+    }
+
+    void setShiftLocked(boolean shiftLocked) {
+        if (mShiftKey != null) {
+            if (shiftLocked) {
+                mShiftKey.on = true;
+                mShiftKey.icon = mShiftLockIcon;
+                mShiftState = SHIFT_LOCKED;
+            } else {
+                mShiftKey.on = false;
+                mShiftKey.icon = mShiftLockIcon;
+                mShiftState = SHIFT_ON;
+            }
+        }
+    }
+
+    boolean isShiftLocked() {
+        return mShiftState == SHIFT_LOCKED;
+    }
+    
+    @Override
+    public boolean setShifted(boolean shiftState) {
+        boolean shiftChanged = false;
+        if (mShiftKey != null) {
+            if (shiftState == false) {
+                shiftChanged = mShiftState != SHIFT_OFF;
+                mShiftState = SHIFT_OFF;
+                mShiftKey.on = false;
+                mShiftKey.icon = mOldShiftIcon;
+            } else {
+                if (mShiftState == SHIFT_OFF) {
+                    shiftChanged = mShiftState == SHIFT_OFF;
+                    mShiftState = SHIFT_ON;
+                    mShiftKey.icon = mShiftLockIcon;
+                }
+            }
+        } else {
+            return super.setShifted(shiftState);
+        }
+        return shiftChanged;
+    }
+    
+    @Override
+    public boolean isShifted() {
+        if (mShiftKey != null) {
+            return mShiftState != SHIFT_OFF;
+        } else {
+            return super.isShifted();
+        }
+    }
+
+    static class LatinKey extends Keyboard.Key {
+        
+        private boolean mShiftLockEnabled;
+        
+        public LatinKey(Resources res, Keyboard.Row parent, int x, int y, 
+                XmlResourceParser parser) {
+            super(res, parent, x, y, parser);
+        }
+        
+        void enableShiftLock() {
+            mShiftLockEnabled = true;
+        }
+
+        @Override
+        public void onReleased(boolean inside) {
+            if (!mShiftLockEnabled) {
+                super.onReleased(inside);
+            } else {
+                pressed = !pressed;
+            }
+        }
+        
+        /**
+         * Overriding this method so that we can reduce the target area for certain keys.
+         */
+        @Override
+        public boolean isInside(int x, int y) {
+            if ((edgeFlags & Keyboard.EDGE_BOTTOM) != 0 ||
+                    codes[0] == KEYCODE_SHIFT ||
+                    codes[0] == KEYCODE_DELETE) {
+                y -= height / 10;
+            }
+            if (codes[0] == KEYCODE_SHIFT) x += width / 6;
+            if (codes[0] == KEYCODE_DELETE) x -= width / 6;
+            return super.isInside(x, y);
+        }
+    }
+}
diff --git a/src/com/android/inputmethod/latin/LatinKeyboardView.java b/src/com/android/inputmethod/latin/LatinKeyboardView.java
new file mode 100644
index 0000000..363dcd0
--- /dev/null
+++ b/src/com/android/inputmethod/latin/LatinKeyboardView.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ * 
+ * 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.graphics.Canvas;
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.KeyboardView;
+import android.inputmethodservice.Keyboard.Key;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import java.util.List;
+
+public class LatinKeyboardView extends KeyboardView {
+
+    static final int KEYCODE_OPTIONS = -100;
+    static final int KEYCODE_SHIFT_LONGPRESS = -101;
+    
+    private Keyboard mPhoneKeyboard;
+
+    public LatinKeyboardView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+    
+    public void setPhoneKeyboard(Keyboard phoneKeyboard) {
+        mPhoneKeyboard = phoneKeyboard;
+    }
+    
+    @Override
+    protected boolean onLongPress(Key key) {
+        if (key.codes[0] == Keyboard.KEYCODE_MODE_CHANGE) {
+            getOnKeyboardActionListener().onKey(KEYCODE_OPTIONS, null);
+            return true;
+        } else if (key.codes[0] == Keyboard.KEYCODE_SHIFT) {
+            getOnKeyboardActionListener().onKey(KEYCODE_SHIFT_LONGPRESS, null);
+            invalidate();
+            return true;
+        } else if (key.codes[0] == '0' && getKeyboard() == mPhoneKeyboard) {
+            // Long pressing on 0 in phone number keypad gives you a '+'.
+            getOnKeyboardActionListener().onKey('+', null);
+            return true;
+        } else {
+            return super.onLongPress(key);
+        }
+    }
+
+    
+    /****************************  INSTRUMENTATION  *******************************/
+
+    static final boolean DEBUG_AUTO_PLAY = false;
+    private static final int MSG_TOUCH_DOWN = 1;
+    private static final int MSG_TOUCH_UP = 2;
+    
+    Handler mHandler2;
+    
+    private String mStringToPlay;
+    private int mStringIndex;
+    private boolean mDownDelivered;
+    private Key[] mAsciiKeys = new Key[256];
+    private boolean mPlaying;
+
+    @Override
+    public void setKeyboard(Keyboard k) {
+        super.setKeyboard(k);
+        if (DEBUG_AUTO_PLAY) {
+            findKeys();
+            if (mHandler2 == null) {
+                mHandler2 = new Handler() {
+                    @Override
+                    public void handleMessage(Message msg) {
+                        removeMessages(MSG_TOUCH_DOWN);
+                        removeMessages(MSG_TOUCH_UP);
+                        if (mPlaying == false) return;
+                        
+                        switch (msg.what) {
+                            case MSG_TOUCH_DOWN:
+                                if (mStringIndex >= mStringToPlay.length()) {
+                                    mPlaying = false;
+                                    return;
+                                }
+                                char c = mStringToPlay.charAt(mStringIndex);
+                                while (c > 255 || mAsciiKeys[(int) c] == null) {
+                                    mStringIndex++;
+                                    if (mStringIndex >= mStringToPlay.length()) {
+                                        mPlaying = false;
+                                        return;
+                                    }
+                                    c = mStringToPlay.charAt(mStringIndex);
+                                }
+                                int x = mAsciiKeys[c].x + 10;
+                                int y = mAsciiKeys[c].y + 26;
+                                MotionEvent me = MotionEvent.obtain(SystemClock.uptimeMillis(), 
+                                        SystemClock.uptimeMillis(), 
+                                        MotionEvent.ACTION_DOWN, x, y, 0);
+                                LatinKeyboardView.this.dispatchTouchEvent(me);
+                                me.recycle();
+                                sendEmptyMessageDelayed(MSG_TOUCH_UP, 500); // Deliver up in 500ms if nothing else
+                                // happens
+                                mDownDelivered = true;
+                                break;
+                            case MSG_TOUCH_UP:
+                                char cUp = mStringToPlay.charAt(mStringIndex);
+                                int x2 = mAsciiKeys[cUp].x + 10;
+                                int y2 = mAsciiKeys[cUp].y + 26;
+                                mStringIndex++;
+                                
+                                MotionEvent me2 = MotionEvent.obtain(SystemClock.uptimeMillis(), 
+                                        SystemClock.uptimeMillis(), 
+                                        MotionEvent.ACTION_UP, x2, y2, 0);
+                                LatinKeyboardView.this.dispatchTouchEvent(me2);
+                                me2.recycle();
+                                sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 500); // Deliver up in 500ms if nothing else
+                                // happens
+                                mDownDelivered = false;
+                                break;
+                        }
+                    }
+                };
+
+            }
+        }
+    }
+
+    private void findKeys() {
+        List<Key> keys = getKeyboard().getKeys();
+        // Get the keys on this keyboard
+        for (int i = 0; i < keys.size(); i++) {
+            int code = keys.get(i).codes[0];
+            if (code >= 0 && code <= 255) { 
+                mAsciiKeys[code] = keys.get(i);
+            }
+        }
+    }
+    
+    void startPlaying(String s) {
+        if (!DEBUG_AUTO_PLAY) return;
+        if (s == null) return;
+        mStringToPlay = s.toLowerCase();
+        mPlaying = true;
+        mDownDelivered = false;
+        mStringIndex = 0;
+        mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 10);
+    }
+
+    @Override
+    public void draw(Canvas c) {
+        super.draw(c);
+        if (DEBUG_AUTO_PLAY && mPlaying) {
+            mHandler2.removeMessages(MSG_TOUCH_DOWN);
+            mHandler2.removeMessages(MSG_TOUCH_UP);
+            if (mDownDelivered) {
+                mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_UP, 20);
+            } else {
+                mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 20);
+            }
+        }
+    }
+}
diff --git a/src/com/android/inputmethod/latin/Suggest.java b/src/com/android/inputmethod/latin/Suggest.java
new file mode 100755
index 0000000..91decd6
--- /dev/null
+++ b/src/com/android/inputmethod/latin/Suggest.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ * 
+ * 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.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;
+
+/**
+ * This class loads a dictionary and provides a list of suggestions for a given sequence of 
+ * characters. This includes corrections and completions.
+ * @hide pending API Council Approval
+ */
+public class Suggest implements Dictionary.WordCallback {
+
+    public static final int CORRECTION_NONE = 0;
+    public static final int CORRECTION_BASIC = 1;
+    public static final int CORRECTION_FULL = 2;
+    
+    private Dictionary mMainDict;
+    
+    private Dictionary mUserDictionary;
+    
+    private int mPrefMaxSuggestions = 12;
+    
+    private int[] mPriorities = new int[mPrefMaxSuggestions];
+    private List<CharSequence> mSuggestions = new ArrayList<CharSequence>();
+    private boolean mIncludeTypedWordIfValid;
+    private List<CharSequence> mStringPool = new ArrayList<CharSequence>();
+    private Context mContext;
+    private boolean mHaveCorrection;
+    private CharSequence mOriginalWord;
+    private String mLowerOriginalWord;
+
+    private int mCorrectionMode = CORRECTION_BASIC;
+
+
+    public Suggest(Context context, int dictionaryResId) {
+        mContext = context;
+        mMainDict = new BinaryDictionary(context, dictionaryResId);
+        for (int i = 0; i < mPrefMaxSuggestions; i++) {
+            StringBuilder sb = new StringBuilder(32);
+            mStringPool.add(sb);
+        }
+    }
+    
+    public int getCorrectionMode() {
+        return mCorrectionMode;
+    }
+    
+    public void setCorrectionMode(int mode) {
+        mCorrectionMode = mode;
+    }
+
+    /**
+     * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted
+     * before the main dictionary, if set.
+     */
+    public void setUserDictionary(Dictionary userDictionary) {
+        mUserDictionary = userDictionary;
+    }
+
+    /**
+     * Number of suggestions to generate from the input key sequence. This has
+     * to be a number between 1 and 100 (inclusive).
+     * @param maxSuggestions
+     * @throws IllegalArgumentException if the number is out of range
+     */
+    public void setMaxSuggestions(int maxSuggestions) {
+        if (maxSuggestions < 1 || maxSuggestions > 100) {
+            throw new IllegalArgumentException("maxSuggestions must be between 1 and 100");
+        }
+        mPrefMaxSuggestions = maxSuggestions;
+        mPriorities = new int[mPrefMaxSuggestions];
+        collectGarbage();
+        while (mStringPool.size() < mPrefMaxSuggestions) {
+            StringBuilder sb = new StringBuilder(32);
+            mStringPool.add(sb);
+        }
+    }
+    
+    private boolean haveSufficientCommonality(String original, CharSequence suggestion) {
+        final int len = Math.min(original.length(), suggestion.length());
+        if (len <= 2) return true;
+        int matching = 0;
+        for (int i = 0; i < len; i++) {
+            if (UserDictionary.toLowerCase(original.charAt(i)) 
+                    == UserDictionary.toLowerCase(suggestion.charAt(i))) {
+                matching++;
+            }
+        }
+        if (len <= 4) {
+            return matching >= 2;
+        } else {
+            return matching > len / 2;
+        }
+    }
+    
+    /**
+     * Returns a list of words that match the list of character codes passed in.
+     * This list will be overwritten the next time this function is called.
+     * @param a view for retrieving the context for AutoText
+     * @param codes the list of codes. Each list item contains an array of character codes
+     * in order of probability where the character at index 0 in the array has the highest 
+     * probability. 
+     * @return list of suggestions.
+     */
+    public List<CharSequence> getSuggestions(View view, WordComposer wordComposer, 
+            boolean includeTypedWordIfValid) {
+        mHaveCorrection = false;
+        collectGarbage();
+        Arrays.fill(mPriorities, 0);
+        mIncludeTypedWordIfValid = includeTypedWordIfValid;
+        
+        // Save a lowercase version of the original word
+        mOriginalWord = wordComposer.getTypedWord();
+        if (mOriginalWord != null) {
+            mOriginalWord = mOriginalWord.toString();
+            mLowerOriginalWord = mOriginalWord.toString().toLowerCase();
+        } else {
+            mLowerOriginalWord = "";
+        }
+        // Search the dictionary only if there are at least 2 characters
+        if (wordComposer.size() > 1) {
+            if (mUserDictionary != null) {
+                mUserDictionary.getWords(wordComposer, this);
+                if (mSuggestions.size() > 0 && isValidWord(mOriginalWord)) {
+                    mHaveCorrection = true;
+                }
+            }
+            mMainDict.getWords(wordComposer, this);
+            if (mCorrectionMode == CORRECTION_FULL && mSuggestions.size() > 0) {
+                mHaveCorrection = true;
+            }
+        }
+        if (mOriginalWord != null) {
+            mSuggestions.add(0, mOriginalWord.toString());
+        }
+        
+        // Check if the first suggestion has a minimum number of characters in common
+        if (mCorrectionMode == CORRECTION_FULL && mSuggestions.size() > 1) {
+            if (!haveSufficientCommonality(mLowerOriginalWord, mSuggestions.get(1))) {
+                mHaveCorrection = false;
+            }
+        }
+        
+        int i = 0;
+        int max = 6;
+        // Don't autotext the suggestions from the dictionaries
+        if (mCorrectionMode == CORRECTION_BASIC) max = 1;
+        while (i < mSuggestions.size() && i < max) {
+            String suggestedWord = mSuggestions.get(i).toString().toLowerCase();
+            CharSequence autoText =
+                    AutoText.get(suggestedWord, 0, suggestedWord.length(), view);
+            // Is there an AutoText correction?
+            boolean canAdd = autoText != null;
+            // Is that correction already the current prediction (or original word)?
+            canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i));
+            // Is that correction already the next predicted word?
+            if (canAdd && i + 1 < mSuggestions.size() && mCorrectionMode != CORRECTION_BASIC) {
+                canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i + 1));
+            }
+            if (canAdd) {
+                mHaveCorrection = true;
+                mSuggestions.add(i + 1, autoText);
+                i++;
+            }
+            i++;
+        }
+        
+        return mSuggestions;
+    }
+
+    public boolean hasMinimalCorrection() {
+        return mHaveCorrection;
+    }
+
+    private boolean compareCaseInsensitive(final String mLowerOriginalWord, 
+            final char[] word, final int offset, final int length) {
+        final int originalLength = mLowerOriginalWord.length();
+        if (originalLength == length && Character.isUpperCase(word[offset])) {
+            for (int i = 0; i < originalLength; i++) {
+                if (mLowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public boolean addWord(final char[] word, final int offset, final int length, final int freq) {
+        int pos = 0;
+        final int[] priorities = mPriorities;
+        final int prefMaxSuggestions = mPrefMaxSuggestions;
+        // Check if it's the same word, only caps are different
+        if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) {
+            pos = 0;
+        } else {
+            // Check the last one's priority and bail
+            if (priorities[prefMaxSuggestions - 1] >= freq) return true;
+            while (pos < prefMaxSuggestions) {
+                if (priorities[pos] < freq
+                        || (priorities[pos] == freq && length < mSuggestions
+                                .get(pos).length())) {
+                    break;
+                }
+                pos++;
+            }
+        }
+        
+        if (pos >= prefMaxSuggestions) {
+            return true;
+        }
+        System.arraycopy(priorities, pos, priorities, pos + 1,
+                prefMaxSuggestions - pos - 1);
+        priorities[pos] = freq;
+        int poolSize = mStringPool.size();
+        StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) 
+                : new StringBuilder(32);
+        sb.setLength(0);
+        sb.append(word, offset, length);
+        mSuggestions.add(pos, sb);
+        if (mSuggestions.size() > prefMaxSuggestions) {
+            CharSequence garbage = mSuggestions.remove(prefMaxSuggestions);
+            if (garbage instanceof StringBuilder) {
+                mStringPool.add(garbage);
+            }
+        }
+        return true;
+    }
+
+    public boolean isValidWord(final CharSequence word) {
+        if (word == null || word.length() == 0) {
+            return false;
+        }
+        return (mCorrectionMode == CORRECTION_FULL && mMainDict.isValidWord(word)) 
+                || (mCorrectionMode > CORRECTION_NONE && 
+                    (mUserDictionary != null && mUserDictionary.isValidWord(word)));
+    }
+    
+    private void collectGarbage() {
+        int poolSize = mStringPool.size();
+        int garbageSize = mSuggestions.size();
+        while (poolSize < mPrefMaxSuggestions && garbageSize > 0) {
+            CharSequence garbage = mSuggestions.get(garbageSize - 1);
+            if (garbage != null && garbage instanceof StringBuilder) {
+                mStringPool.add(garbage);
+                poolSize++;
+            }
+            garbageSize--;
+        }
+        if (poolSize == mPrefMaxSuggestions + 1) {
+            Log.w("Suggest", "String pool got too big: " + poolSize);
+        }
+        mSuggestions.clear();
+    }
+}
diff --git a/src/com/android/inputmethod/latin/TextEntryState.java b/src/com/android/inputmethod/latin/TextEntryState.java
new file mode 100644
index 0000000..90c364a
--- /dev/null
+++ b/src/com/android/inputmethod/latin/TextEntryState.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ * 
+ * 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.text.format.DateFormat;
+import android.util.Log;
+
+import android.inputmethodservice.Keyboard.Key;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Calendar;
+
+public class TextEntryState {
+    
+    private static boolean LOGGING = false;
+    
+    private static int sBackspaceCount = 0;
+    
+    private static int sAutoSuggestCount = 0;
+    
+    private static int sAutoSuggestUndoneCount = 0;
+    
+    private static int sManualSuggestCount = 0;
+    
+    private static int sWordNotInDictionaryCount = 0;
+    
+    private static int sSessionCount = 0;
+    
+    private static int sTypedChars;
+    
+    private static int sActualChars;
+    
+    private static final String[] STATES = {
+        "Unknown",
+        "Start", 
+        "In word",
+        "Accepted default",
+        "Picked suggestion",
+        "Punc. after word",
+        "Punc. after accepted",
+        "Space after accepted",
+        "Space after picked",
+        "Undo commit"
+    };
+    
+    public static final int STATE_UNKNOWN = 0;
+    public static final int STATE_START = 1;
+    public static final int STATE_IN_WORD = 2;
+    public static final int STATE_ACCEPTED_DEFAULT = 3;
+    public static final int STATE_PICKED_SUGGESTION = 4;
+    public static final int STATE_PUNCTUATION_AFTER_WORD = 5;
+    public static final int STATE_PUNCTUATION_AFTER_ACCEPTED = 6;
+    public static final int STATE_SPACE_AFTER_ACCEPTED = 7;
+    public static final int STATE_SPACE_AFTER_PICKED = 8;
+    public static final int STATE_UNDO_COMMIT = 9;
+    
+    private static int sState = STATE_UNKNOWN;
+    
+    private static FileOutputStream sKeyLocationFile;
+    private static FileOutputStream sUserActionFile;
+    
+    public static void newSession(Context context) {
+        sSessionCount++;
+        sAutoSuggestCount = 0;
+        sBackspaceCount = 0;
+        sAutoSuggestUndoneCount = 0;
+        sManualSuggestCount = 0;
+        sWordNotInDictionaryCount = 0;
+        sTypedChars = 0;
+        sActualChars = 0;
+        sState = STATE_START;
+        
+        if (LOGGING) {
+            try {
+                sKeyLocationFile = context.openFileOutput("key.txt", Context.MODE_APPEND);
+                sUserActionFile = context.openFileOutput("action.txt", Context.MODE_APPEND);
+            } catch (IOException ioe) {
+                Log.e("TextEntryState", "Couldn't open file for output: " + ioe);
+            }
+        }
+    }
+    
+    public static void endSession() {
+        if (sKeyLocationFile == null) {
+            return;
+        }
+        try {
+            sKeyLocationFile.close();
+            // Write to log file            
+            // Write timestamp, settings,
+            String out = DateFormat.format("MM:dd hh:mm:ss", Calendar.getInstance().getTime())
+                    .toString()
+                    + " BS: " + sBackspaceCount
+                    + " auto: " + sAutoSuggestCount
+                    + " manual: " + sManualSuggestCount
+                    + " typed: " + sWordNotInDictionaryCount
+                    + " undone: " + sAutoSuggestUndoneCount
+                    + " saved: " + ((float) (sActualChars - sTypedChars) / sActualChars)
+                    + "\n";
+            sUserActionFile.write(out.getBytes());
+            sUserActionFile.close();
+            sKeyLocationFile = null;
+            sUserActionFile = null;
+        } catch (IOException ioe) {
+            
+        }
+    }
+    
+    public static void acceptedDefault(CharSequence typedWord, CharSequence actualWord) {
+        if (!typedWord.equals(actualWord)) {
+            sAutoSuggestCount++;
+        }
+        sTypedChars += typedWord.length();
+        sActualChars += actualWord.length();
+        sState = STATE_ACCEPTED_DEFAULT;
+    }
+    
+    public static void acceptedTyped(CharSequence typedWord) {
+        sWordNotInDictionaryCount++;
+        sState = STATE_PICKED_SUGGESTION;
+    }
+
+    public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) {
+        sManualSuggestCount++;
+        if (typedWord.equals(actualWord)) {
+            acceptedTyped(typedWord);
+        }
+        sState = STATE_PICKED_SUGGESTION;
+    }
+    
+    public static void typedCharacter(char c, boolean isSeparator) {
+        boolean isSpace = c == ' ';
+        switch (sState) {
+            case STATE_IN_WORD:
+                if (isSpace || isSeparator) {
+                    sState = STATE_START;
+                } else {
+                    // State hasn't changed.
+                }
+                break;
+            case STATE_ACCEPTED_DEFAULT:
+            case STATE_SPACE_AFTER_PICKED:
+                if (isSpace) {
+                    sState = STATE_SPACE_AFTER_ACCEPTED;
+                } else if (isSeparator) {
+                    sState = STATE_PUNCTUATION_AFTER_ACCEPTED;
+                } else {
+                    sState = STATE_IN_WORD;
+                }
+                break;
+            case STATE_PICKED_SUGGESTION:
+                if (isSpace) {
+                    sState = STATE_SPACE_AFTER_PICKED;
+                } else if (isSeparator) {
+                    // Swap 
+                    sState = STATE_PUNCTUATION_AFTER_ACCEPTED;
+                } else {
+                    sState = STATE_IN_WORD;
+                }
+                break;
+            case STATE_START:
+            case STATE_UNKNOWN:
+            case STATE_SPACE_AFTER_ACCEPTED:
+            case STATE_PUNCTUATION_AFTER_ACCEPTED:
+            case STATE_PUNCTUATION_AFTER_WORD:
+                if (!isSpace && !isSeparator) {
+                    sState = STATE_IN_WORD;
+                } else {
+                    sState = STATE_START;
+                }
+                break;
+            case STATE_UNDO_COMMIT:
+                if (isSpace || isSeparator) {
+                    sState = STATE_ACCEPTED_DEFAULT;
+                } else {
+                    sState = STATE_IN_WORD;
+                }
+        }
+    }
+    
+    public static void backspace() {
+        if (sState == STATE_ACCEPTED_DEFAULT) {
+            sState = STATE_UNDO_COMMIT;
+            sAutoSuggestUndoneCount++;
+        } else if (sState == STATE_UNDO_COMMIT) {
+            sState = STATE_IN_WORD;
+        }
+        sBackspaceCount++;
+    }
+    
+    public static void reset() {
+        sState = STATE_START;
+    }
+    
+    public static int getState() {
+        return sState;
+    }
+    
+    public static void keyPressedAt(Key key, int x, int y) {
+        if (LOGGING && sKeyLocationFile != null && key.codes[0] >= 32) {
+            String out = 
+                    "KEY: " + (char) key.codes[0] 
+                    + " X: " + x 
+                    + " Y: " + y
+                    + " MX: " + (key.x + key.width / 2)
+                    + " MY: " + (key.y + key.height / 2) 
+                    + "\n";
+            try {
+                sKeyLocationFile.write(out.getBytes());
+            } catch (IOException ioe) {
+                // TODO: May run out of space
+            }
+        }
+    }
+}
+
diff --git a/src/com/android/inputmethod/latin/Tutorial.java b/src/com/android/inputmethod/latin/Tutorial.java
new file mode 100644
index 0000000..2b3138b
--- /dev/null
+++ b/src/com/android/inputmethod/latin/Tutorial.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ * 
+ * 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.content.SharedPreferences.Editor;
+import android.graphics.drawable.Drawable;
+import android.opengl.Visibility;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.preference.PreferenceManager;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Tutorial {
+    
+    private List<Bubble> mBubbles = new ArrayList<Bubble>();
+    private long mStartTime;
+    private static final long MINIMUM_TIME = 6000;
+    private static final long MAXIMUM_TIME = 20000;
+    private View mInputView;
+    private int[] mLocation = new int[2];
+    private int mBubblePointerOffset;
+    
+    private static final int MSG_SHOW_BUBBLE = 0;
+    private static final int MSG_HIDE_ALL = 1;
+    
+    Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SHOW_BUBBLE:
+                    Bubble bubba = (Bubble) msg.obj;
+                    bubba.show(mLocation[0], mLocation[1]);
+                    break;
+                case MSG_HIDE_ALL:
+                    close(true);
+            }
+        }
+    };
+
+    class Bubble {
+        Drawable bubbleBackground;
+        int x;
+        int y;
+        int width;
+        int gravity;
+        String text;
+        boolean dismissOnTouch;
+        boolean dismissOnClose;
+        PopupWindow window;
+        TextView textView;
+        View inputView;
+        
+        Bubble(Context context, View inputView,
+                int backgroundResource, int bx, int by, int bw, int gravity, int textResource,
+                boolean dismissOnTouch, boolean dismissOnClose) {
+            bubbleBackground = context.getResources().getDrawable(backgroundResource);
+            x = bx; 
+            y = by;
+            width = bw;
+            this.gravity = gravity;
+            text = context.getResources().getString(textResource);
+            this.dismissOnTouch = dismissOnTouch;
+            this.dismissOnClose = dismissOnClose;
+            this.inputView = inputView;
+            window = new PopupWindow(context);
+            window.setBackgroundDrawable(null);
+            LayoutInflater inflate =
+                (LayoutInflater) context
+                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            textView = (TextView) inflate.inflate(R.layout.bubble_text, null);
+            textView.setBackgroundDrawable(bubbleBackground);
+            textView.setText(text);
+            window.setContentView(textView);
+            window.setFocusable(false);
+            window.setTouchable(true);
+            window.setOutsideTouchable(false);
+            textView.setOnTouchListener(new View.OnTouchListener() {
+                public boolean onTouch(View view, MotionEvent me) {
+                    Tutorial.this.touched();
+                    return true;
+                }
+            });
+        }
+
+        private void chooseSize(PopupWindow pop, View parentView, CharSequence text, TextView tv) {
+            int wid = tv.getPaddingLeft() + tv.getPaddingRight();
+            int ht = tv.getPaddingTop() + tv.getPaddingBottom();
+
+            /*
+             * Figure out how big the text would be if we laid it out to the
+             * full width of this view minus the border.
+             */
+            int cap = width - wid;
+
+            Layout l = new StaticLayout(text, tv.getPaint(), cap,
+                                        Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
+            float max = 0;
+            for (int i = 0; i < l.getLineCount(); i++) {
+                max = Math.max(max, l.getLineWidth(i));
+            }
+
+            /*
+             * Now set the popup size to be big enough for the text plus the border.
+             */
+            pop.setWidth(width);
+            pop.setHeight(ht + l.getHeight());
+        }
+
+        void show(int offx, int offy) {
+            chooseSize(window, inputView, text, textView);
+            if (inputView.getVisibility() == View.VISIBLE 
+                    && inputView.getWindowVisibility() == View.VISIBLE) {
+                try {
+                    if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) offy -= window.getHeight();
+                    if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) offx -= window.getWidth();
+                    window.showAtLocation(inputView, Gravity.NO_GRAVITY, x + offx, y + offy);
+                } catch (Exception e) {
+                    // Input view is not valid
+                }
+            }
+        }
+        
+        void hide() {
+            textView.setOnTouchListener(null);
+            if (window.isShowing()) {
+                window.dismiss();
+            }
+        }
+    }
+    
+    public Tutorial(LatinKeyboardView inputView) {
+        Context context = inputView.getContext();
+        int inputHeight = inputView.getHeight();
+        int inputWidth = inputView.getWidth();
+        mBubblePointerOffset = inputView.getContext().getResources()
+            .getDimensionPixelOffset(R.dimen.bubble_pointer_offset);
+        Bubble b0 = new Bubble(context, inputView, 
+                R.drawable.dialog_bubble_step02, 0, 0, 
+                inputWidth,
+                Gravity.BOTTOM | Gravity.LEFT,
+                R.string.tip_dismiss,
+                false, true);
+        mBubbles.add(b0);
+        Bubble b1 = new Bubble(context, inputView, 
+                R.drawable.dialog_bubble_step03, 
+                (int) (inputWidth * 0.85) + mBubblePointerOffset, inputHeight / 5, 
+                (int) (inputWidth * 0.45),
+                Gravity.TOP | Gravity.RIGHT,
+                R.string.tip_long_press,
+                true, false);
+        mBubbles.add(b1);
+        Bubble b2 = new Bubble(inputView.getContext(), inputView, 
+                R.drawable.dialog_bubble_step04, 
+                inputWidth / 10 - mBubblePointerOffset, inputHeight - inputHeight / 5,
+                (int) (inputWidth * 0.45),
+                Gravity.BOTTOM | Gravity.LEFT,
+                R.string.tip_access_symbols,
+                true, false);
+        mBubbles.add(b2);
+        mInputView = inputView;
+    }
+    
+    void start() {
+        mInputView.getLocationInWindow(mLocation);
+        long delayMillis = 0;
+        for (int i = 0; i < mBubbles.size(); i++) {
+            mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_BUBBLE, mBubbles.get(i)), delayMillis);
+            delayMillis += 2000;
+        }
+        //mHandler.sendEmptyMessageDelayed(MSG_HIDE_ALL, MAXIMUM_TIME);
+        mStartTime = SystemClock.uptimeMillis();
+    }
+    
+    void touched() {
+        if (SystemClock.uptimeMillis() - mStartTime < MINIMUM_TIME) {
+            return;
+        }
+        for (int i = 0; i < mBubbles.size(); i++) {
+            Bubble bubba = mBubbles.get(i);
+            if (bubba.dismissOnTouch) {
+                bubba.hide();
+            }
+        }
+    }
+    
+    void close(boolean completed) {
+        mHandler.removeMessages(MSG_SHOW_BUBBLE);
+        for (int i = 0; i < mBubbles.size(); i++) {
+            mBubbles.get(i).hide();
+        }
+        if (completed) {
+            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
+                    mInputView.getContext());
+            Editor editor = sp.edit();
+            editor.putBoolean(LatinIME.PREF_TUTORIAL_RUN, true);
+            editor.commit();
+        }
+    }
+}
diff --git a/src/com/android/inputmethod/latin/UserDictionary.java b/src/com/android/inputmethod/latin/UserDictionary.java
new file mode 100644
index 0000000..09549bf
--- /dev/null
+++ b/src/com/android/inputmethod/latin/UserDictionary.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2008 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 java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.provider.UserDictionary.Words;
+
+public class UserDictionary extends Dictionary {
+    
+    private static final String[] PROJECTION = {
+        Words._ID,
+        Words.WORD,
+        Words.FREQUENCY
+    };
+    
+    private static final int INDEX_WORD = 1;
+    private static final int INDEX_FREQUENCY = 2;
+    
+    private static final char QUOTE = '\'';
+    
+    private Context mContext;
+    
+    List<Node> mRoots;
+    private int mMaxDepth;
+    private int mInputLength;
+
+    public static final int MAX_WORD_LENGTH = 32;
+
+    private char[] mWordBuilder = new char[MAX_WORD_LENGTH];
+   
+    private ContentObserver mObserver;
+    
+    static class Node {
+        char code;
+        int frequency;
+        boolean terminal;
+        List<Node> children;
+    }
+    
+    private boolean mRequiresReload;
+    
+    public UserDictionary(Context context) {
+        mContext = context;
+        // Perform a managed query. The Activity will handle closing and requerying the cursor
+        // when needed.
+        ContentResolver cres = context.getContentResolver();
+        
+        cres.registerContentObserver(Words.CONTENT_URI, true, mObserver = new ContentObserver(null) {
+            @Override
+            public void onChange(boolean self) {
+                mRequiresReload = true;
+            }
+        });
+
+        loadDictionary();
+    }
+    
+    public synchronized void close() {
+        if (mObserver != null) {
+            mContext.getContentResolver().unregisterContentObserver(mObserver);
+            mObserver = null;
+        }
+    }
+    
+    private synchronized void loadDictionary() {
+        Cursor cursor = mContext.getContentResolver()
+                .query(Words.CONTENT_URI, PROJECTION, "(locale IS NULL) or (locale=?)", 
+                        new String[] { Locale.getDefault().toString() }, null);
+        addWords(cursor);
+        mRequiresReload = false;
+    }
+
+    /**
+     * Adds a word to the dictionary and makes it persistent.
+     * @param word the word to add. If the word is capitalized, then the dictionary will
+     * recognize it as a capitalized word when searched.
+     * @param frequency the frequency of occurrence of the word. A frequency of 255 is considered
+     * the highest.
+     * @TODO use a higher or float range for frequency
+     */
+    public synchronized void addWord(String word, int frequency) {
+        if (mRequiresReload) loadDictionary();
+        // Safeguard against adding long words. Can cause stack overflow.
+        if (word.length() >= MAX_WORD_LENGTH) return;
+        addWordRec(mRoots, word, 0, frequency);
+        Words.addWord(mContext, word, frequency, Words.LOCALE_TYPE_CURRENT);
+        // In case the above does a synchronous callback of the change observer
+        mRequiresReload = false;
+    }
+
+    @Override
+    public synchronized void getWords(final WordComposer codes, final WordCallback callback) {
+        if (mRequiresReload) loadDictionary();
+        mInputLength = codes.size();
+        mMaxDepth = mInputLength * 3;
+        getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1.0f, 0, callback);
+    }
+
+    @Override
+    public synchronized boolean isValidWord(CharSequence word) {
+        if (mRequiresReload) loadDictionary();
+        return isValidWordRec(mRoots, word, 0, word.length());
+    }
+    
+    private boolean isValidWordRec(final List<Node> children, final CharSequence word, 
+            final int offset, final int length) {
+        final int count = children.size();
+        char currentChar = word.charAt(offset);
+        for (int j = 0; j < count; j++) {
+            final Node node = children.get(j);
+            if (node.code == currentChar) {
+                if (offset == length - 1) {
+                    if (node.terminal) {
+                        return true;
+                    }
+                } else {
+                    if (node.children != null) {
+                        if (isValidWordRec(node.children, word, offset + 1, length)) {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    static char toLowerCase(char c) {
+        if (c < BASE_CHARS.length) {
+            c = BASE_CHARS[c];
+        }
+        c = Character.toLowerCase(c);
+        return c;
+    }
+
+    /**
+     * Recursively traverse the tree for words that match the input. Input consists of
+     * a list of arrays. Each item in the list is one input character position. An input
+     * character is actually an array of multiple possible candidates. This function is not
+     * optimized for speed, assuming that the user dictionary will only be a few hundred words in
+     * size.
+     * @param roots node whose children have to be search for matches
+     * @param codes the input character codes
+     * @param word the word being composed as a possible match
+     * @param depth the depth of traversal - the length of the word being composed thus far
+     * @param completion whether the traversal is now in completion mode - meaning that we've
+     * exhausted the input and we're looking for all possible suffixes.
+     * @param snr current weight of the word being formed
+     * @param inputIndex position in the input characters. This can be off from the depth in 
+     * case we skip over some punctuations such as apostrophe in the traversal. That is, if you type
+     * "wouldve", it could be matching "would've", so the depth will be one more than the
+     * inputIndex
+     * @param callback the callback class for adding a word
+     */
+    private void getWordsRec(List<Node> roots, final WordComposer codes, final char[] word, 
+            final int depth, boolean completion, float snr, int inputIndex,
+            WordCallback callback) {
+        final int count = roots.size();
+        final int codeSize = mInputLength;
+        // Optimization: Prune out words that are too long compared to how much was typed.
+        if (depth > mMaxDepth) {
+            return;
+        }
+        int[] currentChars = null;
+        if (codeSize <= inputIndex) {
+            completion = true;
+        } else {
+            currentChars = codes.getCodesAt(inputIndex);
+        }
+
+        for (int i = 0; i < count; i++) {
+            final Node node = roots.get(i); 
+            final char c = node.code;
+            final char lowerC = toLowerCase(c);
+            boolean terminal = node.terminal;
+            List<Node> children = node.children;
+            int freq = node.frequency;
+            if (completion) {
+                word[depth] = c;
+                if (terminal) {
+                    if (!callback.addWord(word, 0, depth + 1, (int) (freq * snr))) {
+                        return;
+                    }
+                }
+                if (children != null) {
+                    getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex, 
+                            callback);
+                }
+            } else if (c == QUOTE && currentChars[0] != QUOTE) {
+                // Skip the ' and continue deeper
+                word[depth] = QUOTE;
+                if (children != null) {
+                    getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex, 
+                            callback);
+                }
+            } else {
+                for (int j = 0; j < currentChars.length; j++) {
+                    float addedAttenuation = (j > 0 ? 1f : 3f);
+                    if (currentChars[j] == -1) {
+                        break;
+                    }
+                    if (currentChars[j] == lowerC || currentChars[j] == c) {
+                        word[depth] = c;
+
+                        if (codes.size() == depth + 1) {
+                            if (terminal) {
+                                if (INCLUDE_TYPED_WORD_IF_VALID 
+                                        || !same(word, depth + 1, codes.getTypedWord())) {
+                                    callback.addWord(word, 0, depth + 1,
+                                        (int) (freq * snr * addedAttenuation 
+                                                * FULL_WORD_FREQ_MULTIPLIER));
+                                }
+                            }
+                            if (children != null) {
+                                getWordsRec(children, codes, word, depth + 1, 
+                                        true, snr * addedAttenuation, inputIndex + 1, callback);
+                            }
+                        } else if (children != null) {
+                            getWordsRec(children, codes, word, depth + 1, 
+                                    false, snr * addedAttenuation, inputIndex + 1, callback);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void addWords(Cursor cursor) {
+        mRoots = new ArrayList<Node>();
+        
+        if (cursor.moveToFirst()) {
+            while (!cursor.isAfterLast()) {
+                String word = cursor.getString(INDEX_WORD);
+                int frequency = cursor.getInt(INDEX_FREQUENCY);
+                // Safeguard against adding really long words. Stack may overflow due
+                // to recursion
+                if (word.length() < MAX_WORD_LENGTH) {
+                    addWordRec(mRoots, word, 0, frequency);
+                }
+                cursor.moveToNext();
+            }
+        }
+        cursor.close();
+    }
+    
+    private void addWordRec(List<Node> children, final String word, 
+            final int depth, final int frequency) {
+        
+        final int wordLength = word.length();
+        final char c = word.charAt(depth);
+        // Does children have the current character?
+        final int childrenLength = children.size();
+        Node childNode = null;
+        boolean found = false;
+        for (int i = 0; i < childrenLength; i++) {
+            childNode = children.get(i);
+            if (childNode.code == c) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            childNode = new Node();
+            childNode.code = c;
+            children.add(childNode);
+        }
+        if (wordLength == depth + 1) {
+            // Terminate this word
+            childNode.terminal = true;
+            childNode.frequency += frequency; // If there are multiple similar words
+            return;
+        }
+        if (childNode.children == null) {
+            childNode.children = new ArrayList<Node>(); 
+        }
+        addWordRec(childNode.children, word, depth + 1, frequency);
+    }
+
+    /**
+     * Table mapping most combined Latin, Greek, and Cyrillic characters
+     * to their base characters.  If c is in range, BASE_CHARS[c] == c
+     * if c is not a combined character, or the base character if it
+     * is combined.
+     */
+    static final char BASE_CHARS[] = {
+        0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 
+        0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, 
+        0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 
+        0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, 
+        0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 
+        0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, 
+        0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 
+        0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, 
+        0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 
+        0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 
+        0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 
+        0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 
+        0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 
+        0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 
+        0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 
+        0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, 
+        0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 
+        0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, 
+        0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 
+        0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, 
+        0x0020, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 
+        0x0020, 0x00a9, 0x0061, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x0020, 
+        0x00b0, 0x00b1, 0x0032, 0x0033, 0x0020, 0x03bc, 0x00b6, 0x00b7, 
+        0x0020, 0x0031, 0x006f, 0x00bb, 0x0031, 0x0031, 0x0033, 0x00bf, 
+        0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00c6, 0x0043, 
+        0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049, 
+        0x00d0, 0x004e, 0x004f, 0x004f, 0x004f, 0x004f, 0x004f, 0x00d7, 
+        0x004f, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00de, 0x0073, // Manually changed d8 to 4f
+                                                                        // Manually changed df to 73
+        0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00e6, 0x0063, 
+        0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069, 
+        0x00f0, 0x006e, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x00f7, 
+        0x006f, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00fe, 0x0079, // Manually changed f8 to 6f
+        0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063, 
+        0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064, 
+        0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065, 
+        0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067, 
+        0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127, 
+        0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 
+        0x0049, 0x0131, 0x0049, 0x0069, 0x004a, 0x006a, 0x004b, 0x006b, 
+        0x0138, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c, 
+        0x006c, 0x0141, 0x0142, 0x004e, 0x006e, 0x004e, 0x006e, 0x004e, 
+        0x006e, 0x02bc, 0x014a, 0x014b, 0x004f, 0x006f, 0x004f, 0x006f, 
+        0x004f, 0x006f, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072, 
+        0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073, 
+        0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167, 
+        0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 
+        0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079, 
+        0x0059, 0x005a, 0x007a, 0x005a, 0x007a, 0x005a, 0x007a, 0x0073, 
+        0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187, 
+        0x0188, 0x0189, 0x018a, 0x018b, 0x018c, 0x018d, 0x018e, 0x018f, 
+        0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197, 
+        0x0198, 0x0199, 0x019a, 0x019b, 0x019c, 0x019d, 0x019e, 0x019f, 
+        0x004f, 0x006f, 0x01a2, 0x01a3, 0x01a4, 0x01a5, 0x01a6, 0x01a7, 
+        0x01a8, 0x01a9, 0x01aa, 0x01ab, 0x01ac, 0x01ad, 0x01ae, 0x0055, 
+        0x0075, 0x01b1, 0x01b2, 0x01b3, 0x01b4, 0x01b5, 0x01b6, 0x01b7, 
+        0x01b8, 0x01b9, 0x01ba, 0x01bb, 0x01bc, 0x01bd, 0x01be, 0x01bf, 
+        0x01c0, 0x01c1, 0x01c2, 0x01c3, 0x0044, 0x0044, 0x0064, 0x004c, 
+        0x004c, 0x006c, 0x004e, 0x004e, 0x006e, 0x0041, 0x0061, 0x0049, 
+        0x0069, 0x004f, 0x006f, 0x0055, 0x0075, 0x00dc, 0x00fc, 0x00dc, 
+        0x00fc, 0x00dc, 0x00fc, 0x00dc, 0x00fc, 0x01dd, 0x00c4, 0x00e4, 
+        0x0226, 0x0227, 0x00c6, 0x00e6, 0x01e4, 0x01e5, 0x0047, 0x0067, 
+        0x004b, 0x006b, 0x004f, 0x006f, 0x01ea, 0x01eb, 0x01b7, 0x0292, 
+        0x006a, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01f6, 0x01f7, 
+        0x004e, 0x006e, 0x00c5, 0x00e5, 0x00c6, 0x00e6, 0x00d8, 0x00f8, 
+        0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065, 
+        0x0049, 0x0069, 0x0049, 0x0069, 0x004f, 0x006f, 0x004f, 0x006f, 
+        0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075, 
+        0x0053, 0x0073, 0x0054, 0x0074, 0x021c, 0x021d, 0x0048, 0x0068, 
+        0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061, 
+        0x0045, 0x0065, 0x00d6, 0x00f6, 0x00d5, 0x00f5, 0x004f, 0x006f, 
+        0x022e, 0x022f, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237, 
+        0x0238, 0x0239, 0x023a, 0x023b, 0x023c, 0x023d, 0x023e, 0x023f, 
+        0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247, 
+        0x0248, 0x0249, 0x024a, 0x024b, 0x024c, 0x024d, 0x024e, 0x024f, 
+        0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257, 
+        0x0258, 0x0259, 0x025a, 0x025b, 0x025c, 0x025d, 0x025e, 0x025f, 
+        0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267, 
+        0x0268, 0x0269, 0x026a, 0x026b, 0x026c, 0x026d, 0x026e, 0x026f, 
+        0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277, 
+        0x0278, 0x0279, 0x027a, 0x027b, 0x027c, 0x027d, 0x027e, 0x027f, 
+        0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287, 
+        0x0288, 0x0289, 0x028a, 0x028b, 0x028c, 0x028d, 0x028e, 0x028f, 
+        0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297, 
+        0x0298, 0x0299, 0x029a, 0x029b, 0x029c, 0x029d, 0x029e, 0x029f, 
+        0x02a0, 0x02a1, 0x02a2, 0x02a3, 0x02a4, 0x02a5, 0x02a6, 0x02a7, 
+        0x02a8, 0x02a9, 0x02aa, 0x02ab, 0x02ac, 0x02ad, 0x02ae, 0x02af, 
+        0x0068, 0x0266, 0x006a, 0x0072, 0x0279, 0x027b, 0x0281, 0x0077, 
+        0x0079, 0x02b9, 0x02ba, 0x02bb, 0x02bc, 0x02bd, 0x02be, 0x02bf, 
+        0x02c0, 0x02c1, 0x02c2, 0x02c3, 0x02c4, 0x02c5, 0x02c6, 0x02c7, 
+        0x02c8, 0x02c9, 0x02ca, 0x02cb, 0x02cc, 0x02cd, 0x02ce, 0x02cf, 
+        0x02d0, 0x02d1, 0x02d2, 0x02d3, 0x02d4, 0x02d5, 0x02d6, 0x02d7, 
+        0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02de, 0x02df, 
+        0x0263, 0x006c, 0x0073, 0x0078, 0x0295, 0x02e5, 0x02e6, 0x02e7, 
+        0x02e8, 0x02e9, 0x02ea, 0x02eb, 0x02ec, 0x02ed, 0x02ee, 0x02ef, 
+        0x02f0, 0x02f1, 0x02f2, 0x02f3, 0x02f4, 0x02f5, 0x02f6, 0x02f7, 
+        0x02f8, 0x02f9, 0x02fa, 0x02fb, 0x02fc, 0x02fd, 0x02fe, 0x02ff, 
+        0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307, 
+        0x0308, 0x0309, 0x030a, 0x030b, 0x030c, 0x030d, 0x030e, 0x030f, 
+        0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, 
+        0x0318, 0x0319, 0x031a, 0x031b, 0x031c, 0x031d, 0x031e, 0x031f, 
+        0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327, 
+        0x0328, 0x0329, 0x032a, 0x032b, 0x032c, 0x032d, 0x032e, 0x032f, 
+        0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337, 
+        0x0338, 0x0339, 0x033a, 0x033b, 0x033c, 0x033d, 0x033e, 0x033f, 
+        0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347, 
+        0x0348, 0x0349, 0x034a, 0x034b, 0x034c, 0x034d, 0x034e, 0x034f, 
+        0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357, 
+        0x0358, 0x0359, 0x035a, 0x035b, 0x035c, 0x035d, 0x035e, 0x035f, 
+        0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, 
+        0x0368, 0x0369, 0x036a, 0x036b, 0x036c, 0x036d, 0x036e, 0x036f, 
+        0x0370, 0x0371, 0x0372, 0x0373, 0x02b9, 0x0375, 0x0376, 0x0377, 
+        0x0378, 0x0379, 0x0020, 0x037b, 0x037c, 0x037d, 0x003b, 0x037f, 
+        0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00a8, 0x0391, 0x00b7, 
+        0x0395, 0x0397, 0x0399, 0x038b, 0x039f, 0x038d, 0x03a5, 0x03a9, 
+        0x03ca, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 
+        0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, 
+        0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, 
+        0x03a8, 0x03a9, 0x0399, 0x03a5, 0x03b1, 0x03b5, 0x03b7, 0x03b9, 
+        0x03cb, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, 
+        0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, 
+        0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, 
+        0x03c8, 0x03c9, 0x03b9, 0x03c5, 0x03bf, 0x03c5, 0x03c9, 0x03cf, 
+        0x03b2, 0x03b8, 0x03a5, 0x03d2, 0x03d2, 0x03c6, 0x03c0, 0x03d7, 
+        0x03d8, 0x03d9, 0x03da, 0x03db, 0x03dc, 0x03dd, 0x03de, 0x03df, 
+        0x03e0, 0x03e1, 0x03e2, 0x03e3, 0x03e4, 0x03e5, 0x03e6, 0x03e7, 
+        0x03e8, 0x03e9, 0x03ea, 0x03eb, 0x03ec, 0x03ed, 0x03ee, 0x03ef, 
+        0x03ba, 0x03c1, 0x03c2, 0x03f3, 0x0398, 0x03b5, 0x03f6, 0x03f7, 
+        0x03f8, 0x03a3, 0x03fa, 0x03fb, 0x03fc, 0x03fd, 0x03fe, 0x03ff, 
+        0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406, 
+        0x0408, 0x0409, 0x040a, 0x040b, 0x041a, 0x0418, 0x0423, 0x040f, 
+        0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 
+        0x0418, 0x0418, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, 
+        0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 
+        0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, 
+        0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 
+        0x0438, 0x0438, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, 
+        0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 
+        0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, 
+        0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456, 
+        0x0458, 0x0459, 0x045a, 0x045b, 0x043a, 0x0438, 0x0443, 0x045f, 
+        0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467, 
+        0x0468, 0x0469, 0x046a, 0x046b, 0x046c, 0x046d, 0x046e, 0x046f, 
+        0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475, 
+        0x0478, 0x0479, 0x047a, 0x047b, 0x047c, 0x047d, 0x047e, 0x047f, 
+        0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 
+        0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x048f, 
+        0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497, 
+        0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x049f, 
+        0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x04a7, 
+        0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x04af, 
+        0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x04b7, 
+        0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x04be, 0x04bf, 
+        0x04c0, 0x0416, 0x0436, 0x04c3, 0x04c4, 0x04c5, 0x04c6, 0x04c7, 
+        0x04c8, 0x04c9, 0x04ca, 0x04cb, 0x04cc, 0x04cd, 0x04ce, 0x04cf, 
+        0x0410, 0x0430, 0x0410, 0x0430, 0x04d4, 0x04d5, 0x0415, 0x0435, 
+        0x04d8, 0x04d9, 0x04d8, 0x04d9, 0x0416, 0x0436, 0x0417, 0x0437, 
+        0x04e0, 0x04e1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041e, 0x043e, 
+        0x04e8, 0x04e9, 0x04e8, 0x04e9, 0x042d, 0x044d, 0x0423, 0x0443, 
+        0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04f6, 0x04f7, 
+        0x042b, 0x044b, 0x04fa, 0x04fb, 0x04fc, 0x04fd, 0x04fe, 0x04ff, 
+    };
+
+    // generated with:
+    // cat UnicodeData.txt | perl -e 'while (<>) { @foo = split(/;/); $foo[5] =~ s/<.*> //; $base[hex($foo[0])] = hex($foo[5]);} for ($i = 0; $i < 0x500; $i += 8) { for ($j = $i; $j < $i + 8; $j++) { printf("0x%04x, ", $base[$j] ? $base[$j] : $j)}; print "\n"; }'
+
+}
diff --git a/src/com/android/inputmethod/latin/WordComposer.java b/src/com/android/inputmethod/latin/WordComposer.java
new file mode 100644
index 0000000..c950a7f
--- /dev/null
+++ b/src/com/android/inputmethod/latin/WordComposer.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2008-2009 Google Inc.
+ * 
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A place to store the currently composing word with information such as adjacent key codes as well
+ */
+public class WordComposer {
+    /**
+     * The list of unicode values for each keystroke (including surrounding keys)
+     */
+    private List<int[]> mCodes;
+    
+    /**
+     * The word chosen from the candidate list, until it is committed.
+     */
+    private String mPreferredWord;
+    
+    private StringBuilder mTypedWord;
+    
+    /**
+     * Whether the user chose to capitalize the word.
+     */
+    private boolean mIsCapitalized;
+
+    WordComposer() {
+        mCodes = new ArrayList<int[]>(12);
+        mTypedWord = new StringBuilder(20);
+    }
+
+    /**
+     * Clear out the keys registered so far.
+     */
+    public void reset() {
+        mCodes.clear();
+        mIsCapitalized = false;
+        mPreferredWord = null;
+        mTypedWord.setLength(0);
+    }
+
+    /**
+     * Number of keystrokes in the composing word.
+     * @return the number of keystrokes
+     */
+    public int size() {
+        return mCodes.size();
+    }
+
+    /**
+     * Returns the codes at a particular position in the word.
+     * @param index the position in the word
+     * @return the unicode for the pressed and surrounding keys
+     */
+    public int[] getCodesAt(int index) {
+        return mCodes.get(index);
+    }
+
+    /**
+     * Add a new keystroke, with codes[0] containing the pressed key's unicode and the rest of
+     * the array containing unicode for adjacent keys, sorted by reducing probability/proximity.
+     * @param codes the array of unicode values
+     */
+    public void add(int primaryCode, int[] codes) {
+        mTypedWord.append((char) primaryCode);
+        mCodes.add(codes);
+    }
+
+    /**
+     * Delete the last keystroke as a result of hitting backspace.
+     */
+    public void deleteLast() {
+        mCodes.remove(mCodes.size() - 1);
+        mTypedWord.deleteCharAt(mTypedWord.length() - 1);
+    }
+
+    /**
+     * Returns the word as it was typed, without any correction applied.
+     * @return the word that was typed so far
+     */
+    public CharSequence getTypedWord() {
+        int wordSize = mCodes.size();
+        if (wordSize == 0) {
+            return null;
+        }
+//        StringBuffer sb = new StringBuffer(wordSize);
+//        for (int i = 0; i < wordSize; i++) {
+//            char c = (char) mCodes.get(i)[0];
+//            if (i == 0 && mIsCapitalized) {
+//                c = Character.toUpperCase(c);
+//            }
+//            sb.append(c);
+//        }
+//        return sb;
+        return mTypedWord;
+    }
+
+    public void setCapitalized(boolean capitalized) {
+        mIsCapitalized = capitalized;
+    }
+    
+    /**
+     * Whether or not the user typed a capital letter as the first letter in the word
+     * @return capitalization preference
+     */
+    public boolean isCapitalized() {
+        return mIsCapitalized;
+    }
+    
+    /**
+     * Stores the user's selected word, before it is actually committed to the text field.
+     * @param preferred
+     */
+    public void setPreferredWord(String preferred) {
+        mPreferredWord = preferred;
+    }
+    
+    /**
+     * Return the word chosen by the user, or the typed word if no other word was chosen.
+     * @return the preferred word
+     */
+    public CharSequence getPreferredWord() {
+        return mPreferredWord != null ? mPreferredWord : getTypedWord();
+    }
+}